mirror of
https://github.com/rwxrob/bonzai
synced 2024-11-14 18:12:59 +00:00
Add template support and Vars
This commit is contained in:
parent
87300a5828
commit
1cbae4c543
26
bonzai.go
26
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
|
||||
}
|
||||
|
||||
|
14
z/bonzai.go
14
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.
|
||||
|
138
z/cmd.go
138
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user