Add template support and Vars

This commit is contained in:
rwxrob 2022-04-10 17:05:53 -04:00
parent 87300a5828
commit 1cbae4c543
No known key found for this signature in database
GPG Key ID: 2B9111F33082AE77
5 changed files with 133 additions and 85 deletions

View File

@ -39,20 +39,18 @@ type Configurer interface {
QueryPrint(q string) // prints result to os.Stdout
}
// CacheMap specifies how to persist (cache) variable state data,
// key-value combinations of strings with their own types. Types should
// be strings that are easy to understand, although internally they
// should probably be implemented as integers for performance.
// Implementations of CacheMap can persist to disk (Protobuf, etc.) or
// to network storage, or to cloud databases (Redis, etc.)
// CacheMap specifies how to persist (cache) simple string key/value
// data. Implementations of CacheMap can persist in different ways,
// files, network storage, or cloud databases, etc. Must log errors
// rather than panic (unavailable source, etc.)
type CacheMap interface {
Var(key, typ string)
Type(key string) string
Get(key string) string
Set(key, val string)
Del(key string)
String() string // YAML key: value
Print() // print YAML
Init() error // initialize completely new cache
Data() string // k=v with \r and \n escaped in v
Print() // (printed)
Get(key string) string // accessor
Set(key, val string) error // mutator
Del(key string) // destroyer
OverWrite(with string) error // safely replace all cache
}
// Completer defines a function to complete the given leaf Command with
@ -97,7 +95,7 @@ type Command interface {
GetMinParm() int
GetMaxParm() int
GetReqConf() bool
GetReqCache() bool
GetReqVars() bool
GetUsageFunc() UsageFunc
}

View File

@ -81,22 +81,22 @@ var Commands map[string][]any
// implementation to switch everything that depends on configuration.
var Conf bonzai.Configurer
// Cache may be optionally assigned any implementation of
// Vars may be optionally assigned any implementation of
// a bonzai.CacheMap. Once assigned it should not be reassigned at any
// later time during runtime. Certain Bonzai branches and commands may
// require Z.Cache to be defined and those that do generally require the
// require Z.Vars to be defined and those that do generally require the
// same implementation throughout all of runtime. Commands that require
// Z.Cache should set ReqCache to true. Other than the exceptional case
// Z.Vars should set ReqVars to true. Other than the exceptional case
// of configuration commands that fulfill bonzai.CacheMap (and usually
// assign themselves to Z.Cache at init() time), commands must never
// require a specific implementation of bonzai.Cache. This
// assign themselves to Z.Vars at init() time), commands must never
// require a specific implementation of bonzai.CacheMap. This
// encourages command creators and Bonzai tree composers to centralize
// on a single form of caching without creating brittle
// dependencies and tight coupling. Caching persistence can be
// implemented in any number of ways without a problem and Bonzai trees
// simply need to be recompiled with a different bonzai.CachMap
// simply need to be recompiled with a different bonzai.CacheMap
// implementation to switch everything that depends on cached variables.
var Cache bonzai.CacheMap
var Vars bonzai.CacheMap
// UsageText is used for one-line UsageErrors. It's exported to allow
// for different languages.

138
z/cmd.go
View File

@ -41,13 +41,13 @@ type Cmd struct {
Completer bonzai.Completer `json:"-"`
UsageFunc bonzai.UsageFunc `json:"-"`
Caller *Cmd `json:"-"`
Call Method `json:"-"`
MinArgs int `json:"-"` // minimum number of args required (including parms)
MinParm int `json:"-"` // minimum number of params required
MaxParm int `json:"-"` // maximum number of params required
ReqConf bool `json:"-"` // requires Z.Conf be assigned
ReqCache bool `json:"-"` // requires Z.Cache be assigned
Caller *Cmd `json:"-"`
Call Method `json:"-"`
MinArgs int `json:"-"` // minimum number of args required (including parms)
MinParm int `json:"-"` // minimum number of params required
MaxParm int `json:"-"` // maximum number of params required
ReqConf bool `json:"-"` // requires Z.Conf be assigned
ReqVars bool `json:"-"` // requires Z.Var be assigned
Dynamic template.FuncMap `json:"-"` // dynamic attributes
@ -109,27 +109,39 @@ func (x *Cmd) Title() string {
}
}
// Legal returns a single line with the combined values of the
// GetLegal returns a single line with the combined values of the
// Name, Version, Copyright, and License. If Version is empty or nil an
// empty string is returned instead. Legal() is used by the
// empty string is returned instead. GetLegal() is used by the
// version builtin command to aggregate all the version information into
// a single output.
func (x *Cmd) Legal() string {
func (x *Cmd) GetLegal() string {
copyright := x.GetCopyright()
license := x.GetLicense()
version := x.GetVersion()
switch {
case len(x.Copyright) > 0 && len(x.License) == 0 && len(x.Version) == 0:
return x.Name + " " + x.Copyright
case len(x.Copyright) > 0 && len(x.License) > 0 && len(x.Version) > 0:
return x.Name + " (" + x.Version + ") " +
x.Copyright + "\nLicense " + x.License
case len(x.Copyright) > 0 && len(x.License) > 0:
return x.Name + " " + x.Copyright + "\nLicense " + x.License
case len(x.Copyright) > 0 && len(x.Version) > 0:
return x.Name + " (" + x.Version + ") " + x.Copyright
case len(x.Copyright) > 0:
return x.Name + "\n" + x.Copyright
case len(copyright) > 0 && len(license) == 0 && len(version) == 0:
return x.Name + " " + copyright
case len(copyright) > 0 && len(license) > 0 && len(version) > 0:
return x.Name + " (" + version + ") " +
copyright + "\nLicense " + license
case len(copyright) > 0 && len(license) > 0:
return x.Name + " " + copyright + "\nLicense " + license
case len(copyright) > 0 && len(version) > 0:
return x.Name + " (" + version + ") " + copyright
case len(copyright) > 0:
return x.Name + "\n" + copyright
default:
return ""
}
}
// OtherTitles returns just the ordered titles from Other.
@ -240,8 +252,8 @@ func (x *Cmd) Run() {
ExitError(cmd.ReqConfError())
}
if x.ReqCache && Cache == nil {
ExitError(cmd.ReqCacheError())
if x.ReqVars && Vars == nil {
ExitError(cmd.ReqVarsError())
}
// delegate
@ -276,11 +288,11 @@ func (x *Cmd) ReqConfError() error {
)
}
// ReqCacheError returns stating that the given command requires that
// Z.Cache be set to something besides null.
func (x *Cmd) ReqCacheError() error {
// ReqVarsError returns stating that the given command requires that
// Z.Vars be set to something besides null.
func (x *Cmd) ReqVarsError() error {
return fmt.Errorf(
"cmd %q requires cached variables (Z.Cache must be assigned)",
"cmd %q requires cached variables (Z.Vars must be assigned)",
x.Name,
)
}
@ -293,7 +305,7 @@ func (x *Cmd) Unimplemented() error {
// MissingConfig returns an error showing the expected configuration
// entry that is missing from the given path.
func (x *Cmd) MissingConfig(path string) error {
return fmt.Errorf("missing config: %v", x.PathString()+"."+path)
return fmt.Errorf("missing config: %v", x.Path()+"."+path)
}
// Add creates a new Cmd and sets the name and aliases and adds to
@ -353,7 +365,7 @@ func (x *Cmd) UsageCmdTitles() string {
var summaries []string
for _, c := range x.Commands {
set = append(set, strings.Join(c.Names(), "|"))
summaries = append(summaries, c.Summary)
summaries = append(summaries, c.GetSummary())
}
longest := redu.Longest(set)
var buf string
@ -394,6 +406,9 @@ func (x *Cmd) IsHidden(name string) bool {
return false
}
// Seek checks the args for command names returning the deepest along
// with the remaining arguments. Typically the args passed are directly
// from the command line.
func (x *Cmd) Seek(args []string) (*Cmd, []string) {
if args == nil || x.Commands == nil {
return x, args
@ -411,11 +426,11 @@ func (x *Cmd) Seek(args []string) (*Cmd, []string) {
return cur, args[n:]
}
// Path returns the path of command names used to arrive at this
// PathNames returns the path of command names used to arrive at this
// command. The path is determined by walking backward from current
// Caller up rather than depending on anything from the command line
// used to invoke the composing binary. Also see PathString.
func (x *Cmd) Path() []string {
// used to invoke the composing binary. Also see Path.
func (x *Cmd) PathNames() []string {
path := qstack.New[string]()
path.Unshift(x.Name)
for p := x.Caller; p != nil; p = p.Caller {
@ -425,12 +440,12 @@ func (x *Cmd) Path() []string {
return path.Items()
}
// PathString returns a dotted notation of the Path including an initial
// Path returns a dotted notation of the PathNames including an initial
// dot (for root). This is compatible yq query expressions and useful
// for associating configuration and other data specifically with this
// command.
func (x *Cmd) PathString() string {
return "." + strings.Join(x.Path(), ".")
func (x *Cmd) Path() string {
return "." + strings.Join(x.PathNames(), ".")
}
// Log is currently short for log.Printf() but may be supplemented in
@ -439,7 +454,7 @@ func (x *Cmd) Log(format string, a ...any) {
log.Printf(format, a...)
}
// Q is a shorter version of Z.Conf.Query(x.PathString()+"."+q) for
// Q is a shorter version of Z.Conf.Query(x.Path()+"."+q) for
// convenience. Logs the error and returns a blank string if Z.Conf is
// not defined (see ReqConf).
func (x *Cmd) Q(q string) string {
@ -447,12 +462,13 @@ func (x *Cmd) Q(q string) string {
log.Printf("cmd %q requires a configurer (Z.Conf must be assigned)", x.Name)
return ""
}
return Conf.Query(x.PathString() + "." + q)
return Conf.Query(x.Path() + "." + q)
}
// Fill fills out the passed text/template string using the Cmd instance
// as the data object source for the template. It is called by the Get*
// family of field accessors but can be called directly as well.
// family of field accessors but can be called directly as well. Also
// see markfunc.go for list of predefined template functions.
func (x *Cmd) Fill(tmpl string) string {
funcs := to.MergedMaps(markFuncMap, x.Dynamic)
t, err := template.New("t").Funcs(funcs).Parse(tmpl)
@ -468,58 +484,58 @@ func (x *Cmd) Fill(tmpl string) string {
// --------------------- bonzai.Command interface ---------------------
// GetName fulfills the bonzai.Command interface.
// GetName fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetName() string { return x.Fill(x.Name) }
// GetTitle fulfills the bonzai.Command interface.
// GetTitle fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetTitle() string { return x.Fill(x.Title()) }
// GetAliases fulfills the bonzai.Command interface.
// GetAliases fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetAliases() []string { return x.Aliases }
// Summary fulfills the bonzai.Command interface.
// GetSummary fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetSummary() string { return x.Fill(x.Summary) }
// Usage fulfills the bonzai.Command interface.
// GetUsage fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetUsage() string { return x.Fill(x.Usage) }
// Version fulfills the bonzai.Command interface.
// GetVersion fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetVersion() string { return x.Fill(x.Version) }
// Copyright fulfills the bonzai.Command interface.
// GetCopyright fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetCopyright() string { return x.Fill(x.Copyright) }
// License fulfills the bonzai.Command interface.
// GetLicense fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetLicense() string { return x.Fill(x.License) }
// Description fulfills the bonzai.Command interface.
// GetDescription fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetDescription() string { return x.Fill(x.Description) }
// Site fulfills the bonzai.Command interface.
// GetSite fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetSite() string { return x.Fill(x.Site) }
// Source fulfills the bonzai.Command interface.
// GetSource fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetSource() string { return x.Fill(x.Source) }
// Issues fulfills the bonzai.Command interface.
// GetIssues fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetIssues() string { return x.Fill(x.Issues) }
// MinArgs fulfills the bonzai.Command interface.
// GetMinArgs fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetMinArgs() int { return x.MinArgs }
// MinParm fulfills the bonzai.Command interface.
// GetMinParm fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetMinParm() int { return x.MinParm }
// MaxParm fulfills the bonzai.Command interface.
// GetMaxParm fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetMaxParm() int { return x.MaxParm }
// ReqConf fulfills the bonzai.Command interface.
// GetReqConf fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetReqConf() bool { return x.ReqConf }
// ReqCache fulfills the bonzai.Command interface.
func (x *Cmd) GetReqCache() bool { return x.ReqCache }
// GetReqVars fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetReqVars() bool { return x.ReqVars }
// UsageFunc fulfills the bonzai.Command interface.
// GetUsageFunc fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetUsageFunc() bonzai.UsageFunc { return x.UsageFunc }
// GetCommands fulfills the bonzai.Command interface.
@ -531,7 +547,7 @@ func (x *Cmd) GetCommands() []bonzai.Command {
return commands
}
// GetCommandNames fulfills the bonzai.Command interface.
// GetCommandNames fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetCommandNames() []string { return x.CmdNames() }
// GetHidden fulfills the bonzai.Command interface.
@ -540,7 +556,7 @@ func (x *Cmd) GetHidden() []string { return x.Hidden }
// GetParams fulfills the bonzai.Command interface.
func (x *Cmd) GetParams() []string { return x.Params }
// GetOther fulfills the bonzai.Command interface.
// GetOther fulfills the bonzai.Command interface. Uses Fill.
func (x *Cmd) GetOther() []bonzai.Section {
var sections []bonzai.Section
for _, s := range x.Other {
@ -550,11 +566,11 @@ func (x *Cmd) GetOther() []bonzai.Section {
return sections
}
// GetOtherTitles fulfills the bonzai.Command interface.
// GetOtherTitles fulfills the bonzai.Command interface. No Fill.
func (x *Cmd) GetOtherTitles() []string {
var titles []string
for _, title := range x.OtherTitles() {
titles = append(titles, x.Fill(title))
titles = append(titles, title)
}
return titles
}

View File

@ -113,7 +113,7 @@ func ExampleCmd_GetParams() {
// [box bing and]
}
func ExampleCmd_PathString() {
func ExampleCmd_Path() {
Z.ExitOff()
z := new(Z.Cmd)
@ -125,7 +125,7 @@ func ExampleCmd_PathString() {
//fmt.Print(z.Commands[0].Commands[0].Commands[0].Name)
c.Call = func(x *Z.Cmd, _ ...string) error {
fmt.Println(x.PathString())
fmt.Println(x.Path())
return nil
}

View File

@ -1,6 +1,8 @@
package Z
import (
"os"
"path/filepath"
"text/template"
"github.com/rwxrob/to"
@ -11,9 +13,41 @@ import (
// allowed.
var markFuncMap = template.FuncMap{
"indent": indent,
"indent": indent,
"exepath": exepath,
"exename": exename,
"execachedir": execachedir,
"execonfdir": execonfdir,
"cachedir": cachedir,
"confdir": confdir,
}
func indent(n int, in string) string {
return to.Indented(in, IndentBy+n)
}
func cachedir() string {
dir, _ := os.UserCacheDir()
return dir
}
func confdir() string {
dir, _ := os.UserCacheDir()
return dir
}
func exepath() string { return ExePath }
func exename() string { return ExeName }
func execachedir(a ...string) string {
path := filepath.Join(cachedir(), ExeName)
extra := filepath.Join(a...)
return filepath.Join(path, extra)
}
func execonfdir(a ...string) string {
path := filepath.Join(confdir(), ExeName)
extra := filepath.Join(a...)
return filepath.Join(path, extra)
}