Merge branch 'complete-positional-rest-args'
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f19b2e8
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,35 @@
+language: go
+
+install:
+ # go-flags
+ - go get -d -v ./...
+ - go build -v ./...
+
+ # linting
+ - go get code.google.com/p/go.tools/cmd/vet
+ - go get github.com/golang/lint
+ - go install github.com/golang/lint/golint
+
+ # code coverage
+ - go get code.google.com/p/go.tools/cmd/cover
+ - go get github.com/onsi/ginkgo/ginkgo
+ - go get github.com/modocache/gover
+ - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then go get github.com/mattn/goveralls; fi
+
+script:
+ # go-flags
+ - $(exit $(gofmt -l . | wc -l))
+ - go test -v ./...
+
+ # linting
+ - go tool vet -all=true -v=true .
+ - $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/golint .
+
+ # code coverage
+ - $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/ginkgo -r -cover
+ - $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/gover
+ - if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
+
+env:
+ # coveralls.io
+ secure: "RCYbiB4P0RjQRIoUx/vG/AjP3mmYCbzOmr86DCww1Z88yNcy3hYr3Cq8rpPtYU5v0g7wTpu4adaKIcqRE9xknYGbqj3YWZiCoBP1/n4Z+9sHW3Dsd9D/GRGeHUus0laJUGARjWoCTvoEtOgTdGQDoX7mH+pUUY0FBltNYUdOiiU="
diff --git a/README.md b/README.md
index 7daf91c..b6faef6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
go-flags: a go library for parsing command line arguments
=========================================================
+[![GoDoc](https://godoc.org/github.com/jessevdk/go-flags?status.png)](https://godoc.org/github.com/jessevdk/go-flags) [![Build Status](https://travis-ci.org/jessevdk/go-flags.svg?branch=master)](https://travis-ci.org/jessevdk/go-flags) [![Coverage Status](https://img.shields.io/coveralls/jessevdk/go-flags.svg)](https://coveralls.io/r/jessevdk/go-flags?branch=master)
+
This library provides similar functionality to the builtin flag library of
go, but provides much more functionality and nicer formatting. From the
documentation:
diff --git a/assert_test.go b/assert_test.go
index 2cb550d..8e06636 100644
--- a/assert_test.go
+++ b/assert_test.go
@@ -2,6 +2,10 @@
import (
"fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
"path"
"runtime"
"testing"
@@ -123,3 +127,51 @@
assertError(t, err, typ, msg)
return ret
}
+
+func diff(a, b string) (string, error) {
+ atmp, err := ioutil.TempFile("", "help-diff")
+
+ if err != nil {
+ return "", err
+ }
+
+ btmp, err := ioutil.TempFile("", "help-diff")
+
+ if err != nil {
+ return "", err
+ }
+
+ if _, err := io.WriteString(atmp, a); err != nil {
+ return "", err
+ }
+
+ if _, err := io.WriteString(btmp, b); err != nil {
+ return "", err
+ }
+
+ ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
+
+ os.Remove(atmp.Name())
+ os.Remove(btmp.Name())
+
+ if err.Error() == "exit status 1" {
+ return string(ret), nil
+ }
+
+ return string(ret), err
+}
+
+func assertDiff(t *testing.T, actual, expected, msg string) {
+ if actual == expected {
+ return
+ }
+
+ ret, err := diff(actual, expected)
+
+ if err != nil {
+ assertErrorf(t, "Unexpected diff error: %s", err)
+ assertErrorf(t, "Unexpected %s, expected:\n\n%s\n\nbut got\n\n%s", msg, expected, actual)
+ } else {
+ assertErrorf(t, "Unexpected %s:\n\n%s", msg, ret)
+ }
+}
diff --git a/closest.go b/closest.go
index 14f58d5..3b51875 100644
--- a/closest.go
+++ b/closest.go
@@ -9,35 +9,33 @@
return len(s)
}
- var l1, l2, l3 int
-
- if len(s) == 1 {
- l1 = len(t) + 1
- } else {
- l1 = levenshtein(s[1:len(s)-1], t) + 1
+ dists := make([][]int, len(s)+1)
+ for i := range dists {
+ dists[i] = make([]int, len(t)+1)
+ dists[i][0] = i
}
- if len(t) == 1 {
- l2 = len(s) + 1
- } else {
- l2 = levenshtein(t[1:len(t)-1], s) + 1
+ for j := range t {
+ dists[0][j] = j
}
- l3 = levenshtein(s[1:len(s)], t[1:len(t)])
-
- if s[0] != t[0] {
- l3++
+ for i, sc := range s {
+ for j, tc := range t {
+ if sc == tc {
+ dists[i+1][j+1] = dists[i][j]
+ } else {
+ dists[i+1][j+1] = dists[i][j] + 1
+ if dists[i+1][j] < dists[i+1][j+1] {
+ dists[i+1][j+1] = dists[i+1][j] + 1
+ }
+ if dists[i][j+1] < dists[i+1][j+1] {
+ dists[i+1][j+1] = dists[i][j+1] + 1
+ }
+ }
+ }
}
- if l2 < l1 {
- l1 = l2
- }
-
- if l1 < l3 {
- return l1
- }
-
- return l3
+ return dists[len(s)][len(t)]
}
func closestChoice(cmd string, choices []string) (string, int) {
diff --git a/command_test.go b/command_test.go
index 8507782..a093e15 100644
--- a/command_test.go
+++ b/command_test.go
@@ -115,6 +115,23 @@
assertError(t, err, ErrCommandRequired, "Please specify one command of: add or remove")
}
+func TestCommandEstimate2(t *testing.T) {
+ var opts = struct {
+ Value bool `short:"v"`
+
+ Cmd1 struct {
+ } `command:"remove"`
+
+ Cmd2 struct {
+ } `command:"add"`
+ }{}
+
+ p := NewParser(&opts, None)
+ _, err := p.ParseArgs([]string{"rmive"})
+
+ assertError(t, err, ErrUnknownCommand, "Unknown command `rmive', did you mean `remove'?")
+}
+
type testCommand struct {
G bool `short:"g"`
Executed bool
diff --git a/convert_test.go b/convert_test.go
index 9ee8ebb..0de0eea 100644
--- a/convert_test.go
+++ b/convert_test.go
@@ -171,13 +171,5 @@
occaecat cupidatat non proident, sunt in culpa qui officia
deserunt mollit anim id est laborum.`
- if got != expected {
- ret, err := helpDiff(got, expected)
-
- if err != nil {
- t.Errorf("Unexpected wrapped text, expected:\n\n%s\n\nbut got\n\n%s", expected, got)
- } else {
- t.Errorf("Unexpected wrapped text:\n\n%s", ret)
- }
- }
+ assertDiff(t, got, expected, "wrapped text")
}
diff --git a/error.go b/error.go
index 3a67693..fce9d31 100644
--- a/error.go
+++ b/error.go
@@ -2,7 +2,6 @@
import (
"fmt"
- "reflect"
)
// ErrorType represents the type of error.
@@ -55,7 +54,36 @@
)
func (e ErrorType) String() string {
- return reflect.TypeOf(e).Name()
+ switch e {
+ case ErrUnknown:
+ return "unknown"
+ case ErrExpectedArgument:
+ return "expected argument"
+ case ErrUnknownFlag:
+ return "unknown flag"
+ case ErrUnknownGroup:
+ return "unknown group"
+ case ErrMarshal:
+ return "marshal"
+ case ErrHelp:
+ return "help"
+ case ErrNoArgumentForBool:
+ return "no argument for bool"
+ case ErrRequired:
+ return "required"
+ case ErrShortNameTooLong:
+ return "short name too long"
+ case ErrDuplicatedFlag:
+ return "duplicated flag"
+ case ErrTag:
+ return "tag"
+ case ErrCommandRequired:
+ return "command required"
+ case ErrUnknownCommand:
+ return "unknown command"
+ }
+
+ return "unrecognized error type"
}
// Error represents a parser error. The error returned from Parse is of this
diff --git a/flags.go b/flags.go
index 830b1f7..923379b 100644
--- a/flags.go
+++ b/flags.go
@@ -17,6 +17,7 @@
Options with long names (--verbose)
Options with and without arguments (bool v.s. other type)
Options with optional arguments and default values
+ Option default values from ENVIRONMENT_VARIABLES, including slice and map values
Multiple option groups each containing a set of options
Generate and print well-formatted help message
Passing remaining command line arguments after -- (optional)
@@ -95,6 +96,12 @@
showing up in the help. If default-mask takes the special
value "-", then no default value will be shown at all
(optional)
+ env: the default value of the option is overridden from the
+ specified environment variable, if one has been defined.
+ (optional)
+ env-delim: the 'env' default value from environment is split into
+ multiple values with the given delimiter string, use with
+ slices and maps (optional)
value-name: the name of the argument value (to be shown in the help,
(optional)
@@ -219,6 +226,8 @@
complete -F _completion_example completion-example
+Completion requires the parser option PassDoubleDash and is therefore enforced if the environment variable GO_FLAGS_COMPLETION is set.
+
Customized completion for argument values is supported by implementing
the flags.Completer interface for the argument value type. An example
of a type which does so is the flags.Filename type, an alias of string
diff --git a/group_private.go b/group_private.go
index 26ee92a..15251ce 100644
--- a/group_private.go
+++ b/group_private.go
@@ -124,6 +124,7 @@
description := mtag.Get("description")
def := mtag.GetMany("default")
+
optionalValue := mtag.GetMany("optional-value")
valueName := mtag.Get("value-name")
defaultMask := mtag.Get("default-mask")
@@ -136,6 +137,8 @@
ShortName: short,
LongName: longname,
Default: def,
+ EnvDefaultKey: mtag.Get("env"),
+ EnvDefaultDelim: mtag.Get("env-delim"),
OptionalArgument: optional,
OptionalValue: optionalValue,
Required: required,
diff --git a/help.go b/help.go
index 0139215..d16f3b1 100644
--- a/help.go
+++ b/help.go
@@ -10,6 +10,7 @@
"fmt"
"io"
"reflect"
+ "runtime"
"strings"
"unicode/utf8"
)
@@ -173,12 +174,24 @@
def = strings.Join(defs, ", ")
}
+ var envDef string
+ if option.EnvDefaultKey != "" {
+ var envPrintable string
+ if runtime.GOOS == "windows" {
+ envPrintable = "%" + option.EnvDefaultKey + "%"
+ } else {
+ envPrintable = "$" + option.EnvDefaultKey
+ }
+ envDef = fmt.Sprintf(" [%s]", envPrintable)
+ }
+
var desc string
if def != "" {
- desc = fmt.Sprintf("%s (%v)", option.Description, def)
+ desc = fmt.Sprintf("%s (%v)%s", option.Description, def,
+ envDef)
} else {
- desc = option.Description
+ desc = option.Description + envDef
}
writer.WriteString(wrapText(desc,
diff --git a/help_test.go b/help_test.go
index 27de672..def3043 100644
--- a/help_test.go
+++ b/help_test.go
@@ -3,48 +3,12 @@
import (
"bytes"
"fmt"
- "io"
- "io/ioutil"
"os"
- "os/exec"
"runtime"
"testing"
"time"
)
-func helpDiff(a, b string) (string, error) {
- atmp, err := ioutil.TempFile("", "help-diff")
-
- if err != nil {
- return "", err
- }
-
- btmp, err := ioutil.TempFile("", "help-diff")
-
- if err != nil {
- return "", err
- }
-
- if _, err := io.WriteString(atmp, a); err != nil {
- return "", err
- }
-
- if _, err := io.WriteString(btmp, b); err != nil {
- return "", err
- }
-
- ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
-
- os.Remove(atmp.Name())
- os.Remove(btmp.Name())
-
- if err.Error() == "exit status 1" {
- return string(ret), nil
- }
-
- return string(ret), err
-}
-
type helpOptions struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
Call func(string) `short:"c" description:"Call phone number" ini-name:"call"`
@@ -54,6 +18,8 @@
Default string `long:"default" default:"Some value" description:"Test default value"`
DefaultArray []string `long:"default-array" default:"Some value" default:"Another value" description:"Test default array value"`
DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
+ EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
+ EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
@@ -81,8 +47,11 @@
}
func TestHelp(t *testing.T) {
- var opts helpOptions
+ oldEnv := EnvSnapshot()
+ defer oldEnv.Restore()
+ os.Setenv("ENV_DEFAULT", "env-def")
+ var opts helpOptions
p := NewNamedParser("TestHelp", HelpFlag)
p.AddGroup("Application Options", "The application options", &opts)
@@ -113,6 +82,8 @@
/default: Test default value (Some value)
/default-array: Test default array value (Some value, Another value)
/default-map: Testdefault map value (some:value, another:value)
+ /env-default1: Test env-default1 value (Some value) [%ENV_DEFAULT%]
+ /env-default2: Test env-default2 value [%ENV_DEFAULT%]
Other Options:
/s: A slice of strings (some, value)
@@ -147,6 +118,8 @@
--default= Test default value (Some value)
--default-array= Test default array value (Some value, Another value)
--default-map= Testdefault map value (some:value, another:value)
+ --env-default1= Test env-default1 value (Some value) [$ENV_DEFAULT]
+ --env-default2= Test env-default2 value [$ENV_DEFAULT]
Other Options:
-s= A slice of strings (some, value)
@@ -170,22 +143,16 @@
`
}
- if e.Message != expected {
- ret, err := helpDiff(e.Message, expected)
-
- if err != nil {
- t.Errorf("Unexpected diff error: %s", err)
- t.Errorf("Unexpected help message, expected:\n\n%s\n\nbut got\n\n%s", expected, e.Message)
- } else {
- t.Errorf("Unexpected help message:\n\n%s", ret)
- }
- }
+ assertDiff(t, e.Message, expected, "help message")
}
}
func TestMan(t *testing.T) {
- var opts helpOptions
+ oldEnv := EnvSnapshot()
+ defer oldEnv.Restore()
+ os.Setenv("ENV_DEFAULT", "env-def")
+ var opts helpOptions
p := NewNamedParser("TestMan", HelpFlag)
p.ShortDescription = "Test manpage generation"
p.LongDescription = "This is a somewhat `longer' description of what this does"
@@ -229,6 +196,12 @@
\fB--default-map\fP
Testdefault map value
.TP
+\fB--env-default1\fP
+Test env-default1 value
+.TP
+\fB--env-default2\fP
+Test env-default2 value
+.TP
\fB-s\fP
A slice of strings
.TP
@@ -246,6 +219,9 @@
Longer \fBcommand\fP description
+\fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
+
+
\fBAliases\fP: cm, cmd
.TP
@@ -253,15 +229,7 @@
Use for extra verbosity
`, tt.Format("2 January 2006"))
- if got != expected {
- ret, err := helpDiff(got, expected)
-
- if err != nil {
- t.Errorf("Unexpected man page, expected:\n\n%s\n\nbut got\n\n%s", expected, got)
- } else {
- t.Errorf("Unexpected man page:\n\n%s", ret)
- }
- }
+ assertDiff(t, got, expected, "man page")
}
type helpCommandNoOptions struct {
@@ -270,8 +238,11 @@
}
func TestHelpCommand(t *testing.T) {
- var opts helpCommandNoOptions
+ oldEnv := EnvSnapshot()
+ defer oldEnv.Restore()
+ os.Setenv("ENV_DEFAULT", "env-def")
+ var opts helpCommandNoOptions
p := NewNamedParser("TestHelpCommand", HelpFlag)
p.AddGroup("Application Options", "The application options", &opts)
@@ -307,15 +278,6 @@
`
}
- if e.Message != expected {
- ret, err := helpDiff(e.Message, expected)
-
- if err != nil {
- t.Errorf("Unexpected diff error: %s", err)
- t.Errorf("Unexpected help message, expected:\n\n%s\n\nbut got\n\n%s", expected, e.Message)
- } else {
- t.Errorf("Unexpected help message:\n\n%s", ret)
- }
- }
+ assertDiff(t, e.Message, expected, "help message")
}
}
diff --git a/ini_private.go b/ini_private.go
index 1a671f5..2e72b87 100644
--- a/ini_private.go
+++ b/ini_private.go
@@ -58,13 +58,19 @@
return option.field.Name
}
-func writeGroupIni(group *Group, namespace string, writer io.Writer, options IniOptions) {
+func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
var sname string
if len(namespace) != 0 {
- sname = namespace + "." + group.ShortDescription
- } else {
- sname = group.ShortDescription
+ sname = namespace
+ }
+
+ if cmd.Group != group && len(group.ShortDescription) != 0 {
+ if len(sname) != 0 {
+ sname += "."
+ }
+
+ sname += group.ShortDescription
}
sectionwritten := false
@@ -154,7 +160,7 @@
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
command.eachGroup(func(group *Group) {
- writeGroupIni(group, namespace, writer, options)
+ writeGroupIni(command, group, namespace, writer, options)
})
for _, c := range command.commands {
diff --git a/ini_test.go b/ini_test.go
index eb752e9..e2b5cdc 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -2,6 +2,7 @@
import (
"bytes"
+ "fmt"
"io/ioutil"
"os"
"strings"
@@ -9,6 +10,10 @@
)
func TestWriteIni(t *testing.T) {
+ oldEnv := EnvSnapshot()
+ defer oldEnv.Restore()
+ os.Setenv("ENV_DEFAULT", "env-def")
+
var tests = []struct {
args []string
options IniOptions
@@ -22,6 +27,12 @@
verbose = true
verbose = true
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
[Other Options]
; A map from string to int
int-map = a:2
@@ -53,6 +64,12 @@
DefaultMap = another:value
DefaultMap = some:value
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
; Option only available in ini
only-ini =
@@ -73,7 +90,7 @@
; This is a subsubgroup option
Opt =
-[command.A command]
+[command]
; Use for extra verbosity
; ExtraVerbose =
@@ -102,6 +119,12 @@
; DefaultMap = another:value
; DefaultMap = some:value
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
; Option only available in ini
; only-ini =
@@ -121,7 +144,7 @@
; This is a subsubgroup option
; Opt =
-[command.A command]
+[command]
; Use for extra verbosity
; ExtraVerbose =
@@ -148,6 +171,12 @@
; Testdefault map value
DefaultMap = new:value
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
; Option only available in ini
; only-ini =
@@ -167,7 +196,7 @@
; This is a subsubgroup option
; Opt =
-[command.A command]
+[command]
; Use for extra verbosity
; ExtraVerbose =
@@ -195,15 +224,8 @@
got := b.String()
expected := test.expected
- if got != expected {
- ret, err := helpDiff(got, expected)
-
- if err != nil {
- t.Errorf("Unexpected ini with arguments %+v and ini options %b, expected:\n\n%s\n\nbut got\n\n%s", test.args, test.options, expected, got)
- } else {
- t.Errorf("Unexpected ini with arguments %+v and ini options %b:\n\n%s", test.args, test.options, ret)
- }
- }
+ msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options)
+ assertDiff(t, got, expected, msg)
}
}
@@ -285,6 +307,7 @@
[add.Other Options]
other = subgroup
+
`
b := strings.NewReader(inic)
@@ -301,6 +324,13 @@
}
assertString(t, opts.Add.Other.O, "subgroup")
+
+ // Test writing it back
+ buf := &bytes.Buffer{}
+
+ inip.Write(buf, IniDefault)
+
+ assertDiff(t, buf.String(), inic, "ini contents")
}
func TestIniNoIni(t *testing.T) {
@@ -383,9 +413,7 @@
expected := "[Application Options]\nValue = 123\n\n"
- if string(found) != expected {
- t.Fatalf("Expected file content to be \"%s\" but was \"%s\"", expected, found)
- }
+ assertDiff(t, string(found), expected, "ini content")
}
func TestOverwriteRequiredOptions(t *testing.T) {
diff --git a/man.go b/man.go
index 3097156..e8e5916 100644
--- a/man.go
+++ b/man.go
@@ -74,11 +74,11 @@
nn = c.Name
}
- writeManPageCommand(wr, nn, c)
+ writeManPageCommand(wr, nn, root, c)
}
}
-func writeManPageCommand(wr io.Writer, name string, command *Command) {
+func writeManPageCommand(wr io.Writer, name string, root *Command, command *Command) {
fmt.Fprintf(wr, ".SS %s\n", name)
fmt.Fprintln(wr, command.ShortDescription)
@@ -98,6 +98,24 @@
}
}
+ var usage string
+ if us, ok := command.data.(Usage); ok {
+ usage = us.Usage()
+ } else if command.hasCliOptions() {
+ usage = fmt.Sprintf("[%s-OPTIONS]", command.Name)
+ }
+
+ var pre string
+ if root.hasCliOptions() {
+ pre = fmt.Sprintf("%s [OPTIONS] %s", root.Name, command.Name)
+ } else {
+ pre = fmt.Sprintf("%s %s", root.Name, command.Name)
+ }
+
+ if len(usage) > 0 {
+ fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", pre, usage)
+ }
+
if len(command.Aliases) > 0 {
fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", strings.Join(command.Aliases, ", "))
}
diff --git a/option.go b/option.go
index 325d9c8..26f2441 100644
--- a/option.go
+++ b/option.go
@@ -27,6 +27,12 @@
// The default value of the option.
Default []string
+ // The optional environment default value key name.
+ EnvDefaultKey string
+
+ // The optional delimiter string for EnvDefaultKey values.
+ EnvDefaultDelim string
+
// If true, specifies that the argument to an option flag is optional.
// When no argument to the flag is specified on the command line, the
// value of Default will be set in the field this option represents.
diff --git a/option_private.go b/option_private.go
index 6801885..d36c841 100644
--- a/option_private.go
+++ b/option_private.go
@@ -2,6 +2,8 @@
import (
"reflect"
+ "strings"
+ "syscall"
)
// Set the value of an option to the specified value. An error will be returned
@@ -42,14 +44,30 @@
}
func (option *Option) empty() {
- option.value.Set(option.emptyValue())
+ if !option.isFunc() {
+ option.value.Set(option.emptyValue())
+ }
}
func (option *Option) clearDefault() {
- if len(option.Default) > 0 {
+ usedDefault := option.Default
+ if envKey := option.EnvDefaultKey; envKey != "" {
+ // os.Getenv() makes no distinction between undefined and
+ // empty values, so we use syscall.Getenv()
+ if value, ok := syscall.Getenv(envKey); ok {
+ if option.EnvDefaultDelim != "" {
+ usedDefault = strings.Split(value,
+ option.EnvDefaultDelim)
+ } else {
+ usedDefault = []string{value}
+ }
+ }
+ }
+
+ if len(usedDefault) > 0 {
option.empty()
- for _, d := range option.Default {
+ for _, d := range usedDefault {
option.set(&d)
}
} else {
diff --git a/parser.go b/parser.go
index 2664f24..9156b5d 100644
--- a/parser.go
+++ b/parser.go
@@ -119,6 +119,8 @@
if len(os.Getenv("GO_FLAGS_COMPLETION")) != 0 {
p.AddCommand("__complete", "completion", "automatic flags completion", &completion{parser: p})
+
+ p.Options |= PassDoubleDash
}
return p
diff --git a/parser_private.go b/parser_private.go
index 4ebce4f..ee637e9 100644
--- a/parser_private.go
+++ b/parser_private.go
@@ -46,7 +46,7 @@
func (p *parseState) checkRequired(parser *Parser) error {
c := parser.Command
- required := make([]*Option, 0)
+ var required []*Option
for c != nil {
c.eachGroup(func(g *Group) {
@@ -62,7 +62,7 @@
if len(required) == 0 {
if len(p.positional) > 0 && p.command.ArgsRequired {
- reqnames := make([]string, 0)
+ var reqnames []string
for _, arg := range p.positional {
if arg.isRemaining() {
@@ -255,22 +255,22 @@
return nil
}
-func (s *parseState) addArgs(args ...string) error {
- for len(s.positional) > 0 && len(args) > 0 {
- arg := s.positional[0]
+func (p *parseState) addArgs(args ...string) error {
+ for len(p.positional) > 0 && len(args) > 0 {
+ arg := p.positional[0]
if err := convert(args[0], arg.value, arg.tag); err != nil {
return err
}
if !arg.isRemaining() {
- s.positional = s.positional[1:]
+ p.positional = p.positional[1:]
}
args = args[1:]
}
- s.retargs = append(s.retargs, args...)
+ p.retargs = append(p.retargs, args...)
return nil
}
diff --git a/parser_test.go b/parser_test.go
index b227244..add87d8 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,7 +1,9 @@
package flags
import (
+ "os"
"reflect"
+ "strings"
"testing"
"time"
)
@@ -96,3 +98,126 @@
}
}
}
+
+// envRestorer keeps a copy of a set of env variables and can restore the env from them
+type envRestorer struct {
+ env map[string]string
+}
+
+func (r *envRestorer) Restore() {
+ os.Clearenv()
+ for k, v := range r.env {
+ os.Setenv(k, v)
+ }
+}
+
+// EnvSnapshot returns a snapshot of the currently set env variables
+func EnvSnapshot() *envRestorer {
+ r := envRestorer{make(map[string]string)}
+ for _, kv := range os.Environ() {
+ parts := strings.SplitN(kv, "=", 2)
+ if len(parts) != 2 {
+ panic("got a weird env variable: " + kv)
+ }
+ r.env[parts[0]] = parts[1]
+ }
+ return &r
+}
+
+type envDefaultOptions struct {
+ Int int `long:"i" default:"1" env:"TEST_I"`
+ Time time.Duration `long:"t" default:"1m" env:"TEST_T"`
+ Map map[string]int `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
+ Slice []int `long:"s" default:"1" default:"2" env:"TEST_S" env-delim:","`
+}
+
+func TestEnvDefaults(t *testing.T) {
+ var tests = []struct {
+ msg string
+ args []string
+ expected envDefaultOptions
+ env map[string]string
+ }{
+ {
+ msg: "no arguments, no env, expecting default values",
+ args: []string{},
+ expected: envDefaultOptions{
+ Int: 1,
+ Time: time.Minute,
+ Map: map[string]int{"a": 1},
+ Slice: []int{1, 2},
+ },
+ },
+ {
+ msg: "no arguments, env defaults, expecting env default values",
+ args: []string{},
+ expected: envDefaultOptions{
+ Int: 2,
+ Time: 2 * time.Minute,
+ Map: map[string]int{"a": 2, "b": 3},
+ Slice: []int{4, 5, 6},
+ },
+ env: map[string]string{
+ "TEST_I": "2",
+ "TEST_T": "2m",
+ "TEST_M": "a:2;b:3",
+ "TEST_S": "4,5,6",
+ },
+ },
+ {
+ msg: "non-zero value arguments, expecting overwritten arguments",
+ args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3"},
+ expected: envDefaultOptions{
+ Int: 3,
+ Time: 3 * time.Millisecond,
+ Map: map[string]int{"c": 3},
+ Slice: []int{3},
+ },
+ env: map[string]string{
+ "TEST_I": "2",
+ "TEST_T": "2m",
+ "TEST_M": "a:2;b:3",
+ "TEST_S": "4,5,6",
+ },
+ },
+ {
+ msg: "zero value arguments, expecting overwritten arguments",
+ args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0"},
+ expected: envDefaultOptions{
+ Int: 0,
+ Time: 0,
+ Map: map[string]int{"": 0},
+ Slice: []int{0},
+ },
+ env: map[string]string{
+ "TEST_I": "2",
+ "TEST_T": "2m",
+ "TEST_M": "a:2;b:3",
+ "TEST_S": "4,5,6",
+ },
+ },
+ }
+
+ oldEnv := EnvSnapshot()
+ defer oldEnv.Restore()
+
+ for _, test := range tests {
+ var opts envDefaultOptions
+ oldEnv.Restore()
+ for envKey, envValue := range test.env {
+ os.Setenv(envKey, envValue)
+ }
+ _, err := ParseArgs(&opts, test.args)
+ if err != nil {
+ t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
+ }
+
+ if opts.Slice == nil {
+ opts.Slice = []int{}
+ }
+
+ if !reflect.DeepEqual(opts, test.expected) {
+ t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
+ }
+ }
+}