Skip to content

Commit

Permalink
cmdr: added auto-env-vars-bindings feature
Browse files Browse the repository at this point in the history
  • Loading branch information
hedzr committed Oct 26, 2024
1 parent c19a39b commit 2e68258
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 13 deletions.
17 changes: 17 additions & 0 deletions cli/baseopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ func (c *BaseOpt) GetCommandTitles() string {
return backtraceCmdNamesG(c, " ", false)
}

func (c *BaseOpt) GetAutoEnvVarName(prefix string, upperCase ...bool) string {
t := backtraceCmdNamesG(c, "_", false)
// last := c.Name()
u := false
for _, b := range upperCase {
u = b
}
if u {
t = strings.ToUpper(t)
// last = strings.ToUpper(last)
}
if prefix != "" {
return prefix + "_" + t // + "_" + last
}
return t // + "_" + last
}

// GetTitleName returns name/full/short string
func (c *BaseOpt) GetTitleName() string {
if c.name != "" {
Expand Down
1 change: 1 addition & 0 deletions cli/commandmatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ func (c *CmdS) matchedForTG(ctx context.Context, ff *Flag) *Flag {
}
}
}

// mutual exclusives
if len(ff.mutualExclusives) > 0 {
root := ff.Root()
Expand Down
2 changes: 2 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Config struct {
DebugScreenWriter HelpWriter `json:"debug_screen_writer,omitempty"` // redirect stdout for debugging outputs
Args []string `json:"args,omitempty"` // for testing
Env map[string]string `json:"env,omitempty"` // inject env var & values
AutoEnv bool `json:"auto_env,omitempty"` // enable envvars auto-binding?
AutoEnvPrefix string `json:"auto_env_prefix,omitempty"` // envvars auto-binding prefix
}

// Opt for cmdr system
Expand Down
43 changes: 30 additions & 13 deletions cli/worker/pre.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,42 @@ func (w *workerS) commandsToStoreR(ctx context.Context, root *cli.RootCommand, c
}
return
}
if cx, ok := w.root.Cmd.(*cli.CmdS); ok {
conf.WithinLoading(func() {
cx.WalkEverything(ctx, func(cc, pp cli.Cmd, ff *cli.Flag, cmdIndex, flgIndex, level int) { //nolint:revive
if ff != nil {
if evs := ff.EnvVars(); len(evs) > 0 {
for _, ev := range evs {
if v, has := os.LookupEnv(ev); has {
data := fromString(v, ff.DefaultValue())
ff.SetDefaultValue(data)
}
if w.Config.AutoEnvPrefix == "" {
w.Config.AutoEnvPrefix = "APP"
}
worker := func(cx cli.Cmd) {
// lookup all flags...
// and bind the value with its envvars field;
// also bind the auto-binding env vars;
cx.WalkEverything(ctx, func(cc, pp cli.Cmd, ff *cli.Flag, cmdIndex, flgIndex, level int) { //nolint:revive
if ff != nil {
if evs := ff.EnvVars(); len(evs) > 0 {
for _, ev := range evs {
if v, has := os.LookupEnv(ev); has {
data := fromString(v, ff.DefaultValue())
ff.SetDefaultValue(data)
}
}
if conf != nil {
conf.Set(ff.GetDottedPath(), ff.DefaultValue())
}
if w.Config.AutoEnv {
ev := ff.GetAutoEnvVarName(w.Config.AutoEnvPrefix, true)
if v, has := os.LookupEnv(ev); has {
data := fromString(v, ff.DefaultValue())
ff.SetDefaultValue(data)
}
}
})
if conf != nil {
conf.Set(ff.GetDottedPath(), ff.DefaultValue())
}
}
})
}
if cx, ok := w.root.Cmd.(*cli.CmdS); ok && cx != nil && conf != nil {
// using store.WithinLoading to disable onSet callbacks and reset internal modified states.
conf.WithinLoading(func() { worker(cx) })
} else if cx := w.root.Cmd; cx != nil {
worker(cx)
}
return
}

Expand Down
35 changes: 35 additions & 0 deletions cmdr.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,41 @@ func WithDontExecuteAction(b bool) cli.Opt {
}
}

// WithAutoEnvBindings enables the feature which can auto-bind env-vars
// to flags default value.
//
// For example, APP_JUMP_TO_FULL=1 -> Cmd{"jump.to"}.Flag{"full"}.default-value = true.
// In this case, `APP` is default prefix so the env-var is different
// than other normal OS env-vars (like HOME, etc.).
//
// You may specify another prefix optionally. For instance, prefix
// "CT" will cause CT_JUMP_TO_FULL=1 binding to
// Cmd{"jump.to"}.Flag{"full"}.default-value = true.
//
// Also you can specify the prefix with multiple section just
// like "CT_ACCOUNT_SERVICE", it will be treated as a normal
// plain string and concatted with the rest parts, so
// "CT_ACCOUNT_SERVICE_JUMP_TO_FULL=1" will be bound in.
func WithAutoEnvBindings(b bool, prefix ...string) cli.Opt {
return func(s *cli.Config) {
s.AutoEnv = b
for _, p := range prefix {
s.AutoEnvPrefix = p
}
}
}

// WithConfig allows you passing a [*cli.Config] object directly.
func WithConfig(conf *cli.Config) cli.Opt {
return func(s *cli.Config) {
if conf == nil {
*s = cli.Config{}
} else {
*s = *conf
}
}
}

// func WithOnInterpretLeadingPlusSign(cb func()) cli.Opt {
// return func(s *cli.Config) {
// s.onInterpretLeadingPlusSign = cb
Expand Down
2 changes: 2 additions & 0 deletions tiny/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func main() {

cmdr.WithSortInHelpScreen(true), // default it's false
cmdr.WithDontGroupInHelpScreen(false), // default it's false

cmdr.WithAutoEnvBindings(true),
)

// // simple run the parser of app and trigger the matched command's action
Expand Down

0 comments on commit 2e68258

Please sign in to comment.