Merge pull request #222 from IljaN/env-only-args
Support for parameters which can only be passed via env
This commit is contained in:
commit
463902ef7d
|
@ -496,3 +496,45 @@ func Example_allSupportedTypes() {
|
|||
|
||||
// output:
|
||||
}
|
||||
|
||||
func Example_envVarOnly() {
|
||||
os.Args = split("./example")
|
||||
_ = os.Setenv("AUTH_KEY", "my_key")
|
||||
|
||||
defer os.Unsetenv("AUTH_KEY")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
fmt.Println(args.AuthKey)
|
||||
// output: my_key
|
||||
}
|
||||
|
||||
func Example_envVarOnlyShouldIgnoreFlag() {
|
||||
os.Args = split("./example --=my_key")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
err := Parse(&args)
|
||||
|
||||
fmt.Println(err)
|
||||
// output: unknown argument --=my_key
|
||||
}
|
||||
|
||||
func Example_envVarOnlyShouldIgnoreShortFlag() {
|
||||
os.Args = split("./example -=my_key")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
err := Parse(&args)
|
||||
|
||||
fmt.Println(err)
|
||||
// output: unknown argument -=my_key
|
||||
}
|
||||
|
|
11
parse.go
11
parse.go
|
@ -343,6 +343,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
|
||||
// Look at the tag
|
||||
var isSubcommand bool // tracks whether this field is a subcommand
|
||||
|
||||
for _, key := range strings.Split(tag, ",") {
|
||||
if key == "" {
|
||||
continue
|
||||
|
@ -360,7 +361,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
case strings.HasPrefix(key, "--"):
|
||||
spec.long = key[2:]
|
||||
case strings.HasPrefix(key, "-"):
|
||||
if len(key) != 2 {
|
||||
if len(key) > 2 {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only",
|
||||
t.Name(), field.Name))
|
||||
return false
|
||||
|
@ -661,7 +662,7 @@ func (p *Parser) process(args []string) error {
|
|||
// lookup the spec for this option (note that the "specs" slice changes as
|
||||
// we expand subcommands so it is better not to use a map)
|
||||
spec := findOption(specs, opt)
|
||||
if spec == nil {
|
||||
if spec == nil || opt == "" {
|
||||
return fmt.Errorf("unknown argument %s", arg)
|
||||
}
|
||||
wasPresent[spec] = true
|
||||
|
@ -750,10 +751,16 @@ func (p *Parser) process(args []string) error {
|
|||
}
|
||||
|
||||
if spec.required {
|
||||
if spec.short == "" && spec.long == "" {
|
||||
msg := fmt.Sprintf("environment variable %s is required", spec.env)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%s is required", name)
|
||||
if spec.env != "" {
|
||||
msg += " (or environment variable " + spec.env + ")"
|
||||
}
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -227,6 +227,14 @@ func TestRequiredWithEnv(t *testing.T) {
|
|||
require.Error(t, err, "--foo is required (or environment variable FOO)")
|
||||
}
|
||||
|
||||
func TestRequiredWithEnvOnly(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"required,--,-,env:FOO"`
|
||||
}
|
||||
_, err := parseWithEnv("", []string{}, &args)
|
||||
require.Error(t, err, "environment variable FOO is required")
|
||||
}
|
||||
|
||||
func TestShortFlag(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"-f"`
|
||||
|
@ -845,6 +853,24 @@ func TestDefaultValuesIgnored(t *testing.T) {
|
|||
assert.Equal(t, "", args.Foo)
|
||||
}
|
||||
|
||||
func TestRequiredEnvironmentOnlyVariableIsMissing(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"required,--,env:FOO"`
|
||||
}
|
||||
|
||||
_, err := parseWithEnv("", []string{""}, &args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestOptionalEnvironmentOnlyVariable(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"env:FOO"`
|
||||
}
|
||||
|
||||
_, err := parseWithEnv("", []string{}, &args)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) {
|
||||
var args struct {
|
||||
Sub *struct {
|
||||
|
|
27
usage.go
27
usage.go
|
@ -208,7 +208,7 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error
|
|||
|
||||
// writeHelp writes the usage string for the given subcommand
|
||||
func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
|
||||
var positionals, longOptions, shortOptions []*spec
|
||||
var positionals, longOptions, shortOptions, envOnlyOptions []*spec
|
||||
for _, spec := range cmd.specs {
|
||||
switch {
|
||||
case spec.positional:
|
||||
|
@ -217,6 +217,8 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
|
|||
longOptions = append(longOptions, spec)
|
||||
case spec.short != "":
|
||||
shortOptions = append(shortOptions, spec)
|
||||
case spec.short == "" && spec.long == "":
|
||||
envOnlyOptions = append(envOnlyOptions, spec)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,6 +277,14 @@ func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
|
|||
})
|
||||
}
|
||||
|
||||
// write the list of environment only variables
|
||||
if len(envOnlyOptions) > 0 {
|
||||
fmt.Fprint(w, "\nEnvironment variables:\n")
|
||||
for _, spec := range envOnlyOptions {
|
||||
p.printEnvOnlyVar(w, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// write the list of subcommands
|
||||
if len(cmd.subcommands) > 0 {
|
||||
fmt.Fprint(w, "\nCommands:\n")
|
||||
|
@ -301,6 +311,21 @@ func (p *Parser) printOption(w io.Writer, spec *spec) {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) {
|
||||
ways := make([]string, 0, 2)
|
||||
if spec.required {
|
||||
ways = append(ways, "Required.")
|
||||
} else {
|
||||
ways = append(ways, "Optional.")
|
||||
}
|
||||
|
||||
if spec.help != "" {
|
||||
ways = append(ways, spec.help)
|
||||
}
|
||||
|
||||
printTwoCols(w, spec.env, strings.Join(ways, " "), spec.defaultString, "")
|
||||
}
|
||||
|
||||
// lookupCommand finds a subcommand based on a sequence of subcommand names. The
|
||||
// first string should be a top-level subcommand, the next should be a child
|
||||
// subcommand of that subcommand, and so on. If no strings are given then the
|
||||
|
|
|
@ -56,6 +56,10 @@ Options:
|
|||
--testenv TESTENV, -a TESTENV [env: TEST_ENV]
|
||||
--file FILE, -f FILE File with mandatory extension [default: scratch.txt]
|
||||
--help, -h display this help and exit
|
||||
|
||||
Environment variables:
|
||||
API_KEY Required. Only via env-var for security reasons
|
||||
TRACE Optional. Record low-level trace
|
||||
`
|
||||
|
||||
var args struct {
|
||||
|
@ -70,6 +74,8 @@ Options:
|
|||
Values []float64 `help:"Values"`
|
||||
Workers int `arg:"-w,env:WORKERS" help:"number of workers to start" default:"10"`
|
||||
TestEnv string `arg:"-a,env:TEST_ENV"`
|
||||
ApiKey string `arg:"required,-,--,env:API_KEY" help:"Only via env-var for security reasons"`
|
||||
Trace bool `arg:"-,--,env" help:"Record low-level trace"`
|
||||
File *NameDotName `arg:"-f" help:"File with mandatory extension"`
|
||||
}
|
||||
args.Name = "Foo Bar"
|
||||
|
@ -552,10 +558,16 @@ Usage: example [-s SHORT]
|
|||
Options:
|
||||
-s SHORT [env: SHORT]
|
||||
--help, -h display this help and exit
|
||||
|
||||
Environment variables:
|
||||
ENVONLY Optional.
|
||||
ENVONLY2 Optional.
|
||||
CUSTOM Optional.
|
||||
`
|
||||
var args struct {
|
||||
Short string `arg:"--,-s,env"`
|
||||
EnvOnly string `arg:"--,env"`
|
||||
EnvOnly2 string `arg:"--,-,env"`
|
||||
EnvOnlyOverriden string `arg:"--,env:CUSTOM"`
|
||||
}
|
||||
|
||||
|
@ -571,6 +583,35 @@ Options:
|
|||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||
}
|
||||
|
||||
func TestEnvOnlyArgs(t *testing.T) {
|
||||
expectedUsage := "Usage: example [--arg ARG]"
|
||||
|
||||
expectedHelp := `
|
||||
Usage: example [--arg ARG]
|
||||
|
||||
Options:
|
||||
--arg ARG, -a ARG [env: MY_ARG]
|
||||
--help, -h display this help and exit
|
||||
|
||||
Environment variables:
|
||||
AUTH_KEY Required.
|
||||
`
|
||||
var args struct {
|
||||
ArgParam string `arg:"-a,--arg,env:MY_ARG"`
|
||||
AuthKey string `arg:"required,--,env:AUTH_KEY"`
|
||||
}
|
||||
p, err := NewParser(Config{Program: "example"}, &args)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp[1:], help.String())
|
||||
|
||||
var usage bytes.Buffer
|
||||
p.WriteUsage(&usage)
|
||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||
}
|
||||
|
||||
func TestFail(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
var exitCode int
|
||||
|
|
Loading…
Reference in New Issue