From 1cbae4c5437ef54886dbd4919bde4ae4f1c755f8 Mon Sep 17 00:00:00 2001 From: rwxrob Date: Sun, 10 Apr 2022 17:05:53 -0400 Subject: [PATCH] Add template support and Vars --- bonzai.go | 26 +++++----- z/bonzai.go | 14 ++--- z/cmd.go | 138 ++++++++++++++++++++++++++++---------------------- z/cmd_test.go | 4 +- z/markfunc.go | 36 ++++++++++++- 5 files changed, 133 insertions(+), 85 deletions(-) diff --git a/bonzai.go b/bonzai.go index 0d9f2a0..1241c70 100644 --- a/bonzai.go +++ b/bonzai.go @@ -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 } diff --git a/z/bonzai.go b/z/bonzai.go index 864d92c..b3ee239 100644 --- a/z/bonzai.go +++ b/z/bonzai.go @@ -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. diff --git a/z/cmd.go b/z/cmd.go index 71a53cd..bae7809 100644 --- a/z/cmd.go +++ b/z/cmd.go @@ -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 } diff --git a/z/cmd_test.go b/z/cmd_test.go index fe5a5bd..703825f 100644 --- a/z/cmd_test.go +++ b/z/cmd_test.go @@ -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 } diff --git a/z/markfunc.go b/z/markfunc.go index 1ba6236..276ff5c 100644 --- a/z/markfunc.go +++ b/z/markfunc.go @@ -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) +}