Skip to content

add env var defaults #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,32 @@ func Test_git(t *testing.T) {
}
},
},
"env var based flag is evaluated": {
gitHandler: func(t *testing.T) Handler {
called := ensureCalled(t)

return func(ctx context.Context) error {
called()

globalGitignore, err := FlagValue[string](ctx, "global-gitignore")
test.Nil(t, err)
test.Equal(t, globalGitignore, "path/to/some/.gitignore")
return nil
}
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Setenv("GLOBAL_GITIGNORE", "path/to/some/.gitignore")

command, err := NewCommand("git", "the stupid content tracker",
SetVersion("v0.1.0"),
AddFlag("git-dir", "Git directory to use"),
AddFlag("global-gitignore", "Global .gitignore file to use.",
SetFlagDefaultEnv("GLOBAL_GITIGNORE"),
),
AddSubCmd("commit", "Record changes to the repository",
AddFlag("message", "commit message",
AddFlagAlias("msg"),
Expand Down
9 changes: 5 additions & 4 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ func ExampleNewCommand() {
AddVersionFlag(AddFlagShort('V')),
AddHelpFlag(AddFlagShort('h')),
AddFlag("port", "Port to run server on.",
SetFlagDefault(3000),
AddFlagShort('p'),
SetFlagDefault(3000),
SetFlagDefaultEnv("PORT"),
),
AddFlag("auth-required", "Whether to require authentication.",
SetFlagDefault(true),
Expand All @@ -26,14 +27,14 @@ func ExampleNewCommand() {
// server v0.1.0: An http server.
//
// Usage:
// server [flags] [sub-commands]
// server [flags] [sub-command]
//
// Sub-commands:
// Sub-command:
// proxy: Proxy requests to another server.
//
// Flags:
// --version -V Print version. (type: bool, default: "false")
// --help -h Print help. (type: bool, default: "false")
// --port -p Port to run server on. (type: int, default: "3000")
// --port -p Port to run server on. (type: int, default: $PORT, "3000")
// --auth-required Whether to require authentication. (type: bool, default: "true")
}
10 changes: 10 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"os"

"github.com/bobg/errors"
)
Expand Down Expand Up @@ -29,6 +30,15 @@ func FlagValue[T any](ctx context.Context, name string) (T, error) {
return flag.value.(T), nil
}

if flag.defaultEnvName != "" {
value, err := flag.parser.Parse(os.Getenv(flag.defaultEnvName))
if err != nil {
return zero, err
}

return value.(T), nil
}

return flag.defaultValue.(T), nil
}

Expand Down
21 changes: 11 additions & 10 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import (
const helpFlagName = "help"

type Flag struct {
name string
description string
aliases []string
shorts []rune
isHelp bool
isVersion bool
isHidden bool
isInherited bool
parser argParser
defaultValue any
name string
description string
aliases []string
shorts []rune
isHelp bool
isVersion bool
isHidden bool
isInherited bool
parser argParser
defaultEnvName string
defaultValue any

value any
}
Expand Down
8 changes: 8 additions & 0 deletions flag_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ func SetFlagDefaultAndParser[T any](defaultValue T, argParser ArgParser[T]) opti
}
}

// SetFlagDefaultEnv sets the default value to that of the corresponding environment variable, and parser of the flag.
func SetFlagDefaultEnv(name string) option.Func[*Flag] {
return func(flag *Flag) (*Flag, error) {
flag.defaultEnvName = name
return flag, nil
}
}

func setFlagIsHelp(isHelp bool) option.Func[*Flag] {
return func(flag *Flag) (*Flag, error) {
flag.isHelp = isHelp
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/bobg/errors v1.1.0
github.com/broothie/option v0.1.0
github.com/broothie/test v0.1.6
github.com/broothie/test v0.1.8
github.com/samber/lo v1.49.1
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/bobg/errors v1.1.0 h1:gsVanPzJMpZQpwY+27/GQYElZez5CuMYwiIpk2A3RGw=
github.com/bobg/errors v1.1.0/go.mod h1:Q4775qBZpnte7EGFJqmvnlB1U4pkI1XmU3qxqdp7Zcc=
github.com/broothie/option v0.1.0 h1:5l6qdv9g1Ajxn7821brKVzOZxIjlVB0gA1MU8QbW8Fw=
github.com/broothie/option v0.1.0/go.mod h1:doEn1r1TpaaBJRdHLZlsdjvnrnH0u1WW+FZA018hE2g=
github.com/broothie/test v0.1.6 h1:lvGSA1O2lFdYJuKDgj9IDpX9AA+rQtY/eYGMvui3PyM=
github.com/broothie/test v0.1.6/go.mod h1:txzDcP9OHro3y7goC+ZznIkASy8NJks4dUcdbwsP0HM=
github.com/broothie/test v0.1.8 h1:II6ZE0z2ZV0y4GJlLl/oBinG6X58mwyioSd/jxNMea0=
github.com/broothie/test v0.1.8/go.mod h1:txzDcP9OHro3y7goC+ZznIkASy8NJks4dUcdbwsP0HM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
Expand Down
7 changes: 6 additions & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,17 @@ func (h helpContext) FlagTable() (string, error) {
shorts = fmt.Sprintf("-%s", string(flag.shorts))
}

helpValues := []string{fmt.Sprintf("%q", fmt.Sprint(flag.defaultValue))}
if flag.defaultEnvName != "" {
helpValues = append([]string{fmt.Sprintf("$%s", flag.defaultEnvName)}, helpValues...)
}

return []string{
"",
strings.Join(longs, " "),
shorts,
flag.description,
fmt.Sprintf("(type: %T, default: %q)", flag.parser.Type(), fmt.Sprint(flag.defaultValue)),
fmt.Sprintf("(type: %T, default: %s)", flag.parser.Type(), strings.Join(helpValues, ", ")),
}, true
}))
}
Expand Down
4 changes: 2 additions & 2 deletions help.tmpl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{{.RootName}}{{ if .Version }} {{.Version}}{{ end }}: {{.RootDescription}}

Usage:
{{.QualifiedName}} {{- if .Flags }} [flags]{{ end -}} {{- if .SubCommands }} [sub-commands]{{ end }}{{ if .ArgumentList }} {{.ArgumentList}}{{ end }}
{{.QualifiedName}} {{- if .Flags }} [flags]{{ end -}} {{- if .SubCommands }} [sub-command]{{ end }}{{ if .ArgumentList }} {{.ArgumentList}}{{ end }}

{{ if .SubCommands -}}
Sub-commands:
Sub-command:
{{.SubCommandsTable}}
{{ end -}}

Expand Down
4 changes: 2 additions & 2 deletions help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ func TestCommand_renderHelp(t *testing.T) {
test v1.2.3-rc10: test command

Usage:
test [flags] [sub-commands]
test [flags] [sub-command]

Sub-commands:
Sub-command:
some-command: some command

Flags:
Expand Down
29 changes: 27 additions & 2 deletions options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func Test_options(t *testing.T) {
AddFlagAlias("addr"),
AddFlagShort('p'),
SetFlagDefault(3000),
SetFlagDefaultEnv("PORT"),
),
AddSubCmd("proxy", "Proxy requests",
AddAlias("p"),
Expand Down Expand Up @@ -59,6 +60,7 @@ func Test_options(t *testing.T) {
test.DeepEqual(t, []string{"addr"}, portFlag.aliases)
test.DeepEqual(t, []rune{'p'}, portFlag.shorts)
test.Equal(t, 3000, portFlag.defaultValue)
test.Equal(t, "PORT", portFlag.defaultEnvName)
test.Nil(t, portFlag.value)
test.Equal(t, reflect.ValueOf(IntParser).Pointer(), reflect.ValueOf(portFlag.parser).Pointer())
test.False(t, portFlag.isBool())
Expand Down Expand Up @@ -126,9 +128,9 @@ func ExampleAddSubCmd() {
// server: An http server.
//
// Usage:
// server [sub-commands]
// server [sub-command]
//
// Sub-commands:
// Sub-command:
// start: Start the server
}

Expand All @@ -151,6 +153,29 @@ func ExampleAddFlag() {
// --port -p Port to run server on (type: int, default: "3000")
}

func ExampleAddFlag_with_env() {
os.Setenv("PORT", "8080")
defer os.Unsetenv("PORT")

command, _ := NewCommand("server", "An http server.",
AddFlag("port", "Port to run server on",
AddFlagShort('p'),
SetFlagDefault(3000),
SetFlagDefaultEnv("PORT"),
),
)

command.renderHelp(os.Stdout)
// Output:
// server: An http server.
//
// Usage:
// server [flags]
//
// Flags:
// --port -p Port to run server on (type: int, default: $PORT, "3000")
}

func ExampleAddArg() {
command, _ := NewCommand("server", "An http server.",
AddArg("port", "Port to run server on", SetArgParser(IntParser)),
Expand Down