Skip to content

Commit

Permalink
Allow required options to be satisfied by env var (#43)
Browse files Browse the repository at this point in the history
* Allow required options to be satisfied by env var

If an option is required, but omitted from the argument list,
allow it to fallback to a configured environment variable instead.

Continue to disallow falling back to a default value - ie. the end user
must either supply the option on the command line, or set the
environment variable.
  • Loading branch information
gwatts authored and jawher committed Feb 20, 2017
1 parent 0de3d3b commit d3ffbc2
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 6 deletions.
12 changes: 6 additions & 6 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type optMatcher struct {

func (o *optMatcher) match(args []string, c *parseContext) (bool, []string) {
if len(args) == 0 || c.rejectOptions {
return false, args
return o.theOne.valueSetFromEnv, args
}

idx := 0
Expand All @@ -63,15 +63,15 @@ func (o *optMatcher) match(args []string, c *parseContext) (bool, []string) {
case arg == "-":
idx++
case arg == "--":
return false, nil
return o.theOne.valueSetFromEnv, nil
case strings.HasPrefix(arg, "--"):
matched, consumed, nargs := o.matchLongOpt(args, idx, c)

if matched {
return true, nargs
}
if consumed == 0 {
return false, args
return o.theOne.valueSetFromEnv, args
}
idx += consumed

Expand All @@ -81,15 +81,15 @@ func (o *optMatcher) match(args []string, c *parseContext) (bool, []string) {
return true, nargs
}
if consumed == 0 {
return false, args
return o.theOne.valueSetFromEnv, args
}
idx += consumed

default:
return false, args
return o.theOne.valueSetFromEnv, args
}
}
return false, args
return o.theOne.valueSetFromEnv, args
}

func (o *optMatcher) matchLongOpt(args []string, idx int, c *parseContext) (bool, int, []string) {
Expand Down
92 changes: 92 additions & 0 deletions spec_n_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package cli

import (
"flag"
"os"
"strings"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"testing"
Expand Down Expand Up @@ -1194,3 +1197,92 @@ func TestWardDoesntRunTooSlowly(t *testing.T) {
okCmd(t, spec, init, []string{"--min-length", "10", "--no-symbol", "--no-lower", "--length", "42", "--gen"})

}

func TestEnvOverrideOk(t *testing.T) {
defer os.Unsetenv("envopt")

cases := []struct {
setenv bool
spec string
args []string
envval string
}{
// pickup the value from the environment variable
{true, "--envopt --other", []string{"--other", "otheropt"}, "fromenv"},
{true, "[--envopt] --other", []string{"--other", "otheropt"}, "fromenv"},
{true, "--envopt", []string{}, "fromenv"},
{true, "--envopt", []string{"--"}, "fromenv"},

// override on command line
{true, "--envopt", []string{"-e", "fromopt"}, "fromopt"},
{true, "--envopt", []string{"--envopt", "fromopt"}, "fromopt"},

// no env set
{false, "--envopt", []string{"--envopt", "fromopt"}, "fromopt"},
{false, "--envopt", []string{"-e", "fromopt"}, "fromopt"},

// no env var, fallback to default
{false, "[--envopt]", []string{}, "envdefault"},
{false, "[--envopt] --other", []string{"--other", "otheropt"}, "envdefault"},
}

for _, cas := range cases {
var envopt *string
var otheropt *string

init := func(c *Cmd) {
os.Unsetenv("envopt")
if cas.setenv {
os.Setenv("envopt", "fromenv")
}
envopt = c.String(StringOpt{
Name: "e envopt",
Value: "envdefault",
EnvVar: "envopt",
})
if strings.Contains(cas.spec, "other") {
otheropt = c.StringOpt("o other", "", "")
}
}
okCmd(t, cas.spec, init, cas.args)
if strings.Contains(cas.spec, "other") {
// if the test spec defined --other, make sure it was actually set
assert.Equal(t, "otheropt", *otheropt)
}
// ensure --envopt was actually set to the test's expectations
assert.Equal(t, cas.envval, *envopt)
}
}

// Test that not setting an environment variable correctly causes
// required options to fail if no value is supplied in args.
func TestEnvOverrideFail(t *testing.T) {
os.Unsetenv("envopt")

cases := []struct {
spec string
args []string
envval string
}{
// no env var, not optional; should fail
{"--envopt", []string{}, ""},
{"--envopt --other", []string{"--other", "otheropt"}, ""},
}

for _, cas := range cases {
var envopt *string
var otheropt *string

init := func(c *Cmd) {
envopt = c.String(StringOpt{
Name: "e envopt",
Value: "envdefault",
EnvVar: "envopt",
})
if strings.Contains(cas.spec, "other") {
otheropt = c.StringOpt("o other", "", "")
}
}
failCmd(t, cas.spec, init, cas.args)
}
}

0 comments on commit d3ffbc2

Please sign in to comment.