diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..aa854e5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..eed750e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: Test +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + +jobs: + go-test: + name: Go Test + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@v6 + - name: Read go version + id: go_version + run: | + # Read the variable from the file + GO_VERSION=$(grep '^go ' go.mod | awk '{print $2}') + # Set the variable as an output + echo "GO_VERSION=$GO_VERSION" >> $GITHUB_OUTPUT + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ${{ steps.go_version.outputs.GO_VERSION }} + + - name: Build + run: | + go get -t ./... + go build . + + - name: Test + run: | + go test . diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9cb28ae..0000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: go - -go: - - 1.6.x - - 1.7.x - -install: - # go-flags - - go get -d -v ./... - - go build -v ./... - - # linting - - go get github.com/golang/lint - - go install golang.org/x/lint/golint - - # code coverage - - go get golang.org/x/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 . || 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/examples/main.go b/examples/main.go index 632c331..95208fe 100644 --- a/examples/main.go +++ b/examples/main.go @@ -3,10 +3,11 @@ package main import ( "errors" "fmt" - "github.com/jessevdk/go-flags" "os" "strconv" "strings" + + flags "github.com/zmap/zflags" ) type EditorOptions struct { diff --git a/fallback_test.go b/fallback_test.go index 6295d92..7def495 100644 --- a/fallback_test.go +++ b/fallback_test.go @@ -1,6 +1,7 @@ package flags import ( + "fmt" "os" "reflect" "testing" @@ -11,9 +12,9 @@ import ( // when `env` is absent, it attempts to fall back to `long` (and to `json`) func TestFallback(t *testing.T) { type Options struct { - Int int `long:"int" json:"json-int" default:"1"` - Time time.Duration `json:"time" default:"1m"` - Map map[string]int `json:"map,omitempty" default:"a:1" env-delim:";"` + Int int `long:"int" json:"json-int" default:"1" env:"Int"` + Time time.Duration `json:"time" default:"1m" env:"Time"` + Map map[string]int `json:"map,omitempty" default:"a:1" env:"Map" env-delim:";"` Slice []int `long:"slice" default:"1" default:"2" env:"OVERRIDE_SLICE" env-delim:","` } @@ -27,19 +28,19 @@ func TestFallback(t *testing.T) { msg: "JSON override", args: []string{}, expected: Options{ - Int: 23, - Time: time.Minute * 3, - Map: map[string]int{"key1": 1}, - Slice: []int{3,4,5}, + Int: 23, + Time: time.Minute * 3, + Map: map[string]int{"key1": 1}, + Slice: []int{3, 4, 5}, }, env: map[string]string{ // since both `json` and `long` are present, `long` ("int") wins "json-int": "4", - "int": "23", - "time": "3m", - "map": "key1:1", + "Int": "23", + "Time": "3m", + "Map": "key1:1", // since both `env` and `long` are present, `env` ("OVERRIDE_SLICE") wins - "slice": "3,2,1", + "Slice": "3,2,1", "OVERRIDE_SLICE": "3,4,5", }, }, @@ -63,9 +64,9 @@ func TestFallback(t *testing.T) { Slice: []int{4, 5, 6}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -75,13 +76,13 @@ func TestFallback(t *testing.T) { expected: Options{ Int: 3, Time: 3 * time.Millisecond, - Map: map[string]int{"c": 3,"d":4}, - Slice: []int{3,1}, + Map: map[string]int{"c": 3, "d": 4}, + Slice: []int{3, 1}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -95,9 +96,9 @@ func TestFallback(t *testing.T) { Slice: []int{0}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -111,9 +112,9 @@ func TestFallback(t *testing.T) { Slice: []int{0}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "Slice": "4,5,6", }, }, @@ -126,9 +127,12 @@ func TestFallback(t *testing.T) { var opts Options oldEnv.Restore() for envKey, envValue := range test.env { - os.Setenv(envKey, envValue) + err := os.Setenv(envKey, envValue) + if err != nil { + t.Fatal(fmt.Errorf("error setting env var (%s): %s", envKey, err)) + } } - _, _, _, err := NewParser(&opts, Default | EnvironmentFallback).ParseCommandLine(test.args) + _, _, _, err := NewParser(&opts, Default|EnvironmentFallback).ParseCommandLine(test.args) if err != nil { t.Fatalf("%s:\nUnexpected error: %v", test.msg, err) } @@ -163,17 +167,17 @@ func TestNoFallback(t *testing.T) { msg: "JSON override", args: []string{}, expected: Options{ - Int: 1, - Time: time.Minute * 1, - Map: map[string]int{"a": 1}, - Slice: []int{3,4,5}, + Int: 1, + Time: time.Minute * 1, + Map: map[string]int{"a": 1}, + Slice: []int{3, 4, 5}, }, env: map[string]string{ - "json-int": "4", - "int": "23", - "time": "3m", - "map": "key1:1", - "slice": "3,2,1", + "json-int": "4", + "int": "23", + "time": "3m", + "map": "key1:1", + "slice": "3,2,1", "OVERRIDE_SLICE": "3,4,5", }, }, @@ -197,9 +201,9 @@ func TestNoFallback(t *testing.T) { Slice: []int{4, 5, 6}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -209,13 +213,13 @@ func TestNoFallback(t *testing.T) { expected: Options{ Int: 3, Time: 3 * time.Millisecond, - Map: map[string]int{"c": 3,"d":4}, - Slice: []int{3,1}, + Map: map[string]int{"c": 3, "d": 4}, + Slice: []int{3, 1}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -229,9 +233,9 @@ func TestNoFallback(t *testing.T) { Slice: []int{0}, }, env: map[string]string{ - "Int": "2", - "Time": "2m", - "Map": "a:2;b:3", + "Int": "2", + "Time": "2m", + "Map": "a:2;b:3", "OVERRIDE_SLICE": "4,5,6", }, }, @@ -246,7 +250,7 @@ func TestNoFallback(t *testing.T) { for envKey, envValue := range test.env { os.Setenv(envKey, envValue) } - _, _, _, err := NewParser(&opts, Default & (^EnvironmentFallback)).ParseCommandLine(test.args) + _, _, _, err := NewParser(&opts, Default&(^EnvironmentFallback)).ParseCommandLine(test.args) if err != nil { t.Fatalf("%s:\nUnexpected error: %v", test.msg, err) } @@ -259,4 +263,4 @@ func TestNoFallback(t *testing.T) { t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts) } } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9c3fc64 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/zmap/zflags + +go 1.24.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/ini.go b/ini.go index e7f866b..6e6ba74 100644 --- a/ini.go +++ b/ini.go @@ -115,18 +115,18 @@ func (i *IniParser) ParseFile(filename string) ([]string, []interface{}, error) // // The format of the ini file is as follows: // -// [Option group name] -// option = value +// [Option group name] +// option = value // // Each section in the ini file represents an option group or command in the // flags parser. The default flags parser option group (i.e. when using // flags.Parse) is named 'Application Options'. The ini option name is matched // in the following order: // -// 1. Compared to the ini-name tag on the option struct field (if present) -// 2. Compared to the struct field name -// 3. Compared to the option long name (if present) -// 4. Compared to the option short name (if present) +// 1. Compared to the ini-name tag on the option struct field (if present) +// 2. Compared to the struct field name +// 3. Compared to the option long name (if present) +// 4. Compared to the option short name (if present) // // Sections for nested groups and commands can be addressed using a dot `.' // namespacing notation (i.e [subcommand.Options]). Group section names are @@ -604,15 +604,17 @@ func (i *IniParser) parse(ini *ini) ([]string, []interface{}, error) { if name != "" && name != "Application Options" { c := i.parser.Find(name) - if cmd, ok := c.data.(ZCommander); ok { - if err := cmd.Validate([]string{}); err != nil { //validate - log.Fatal(err) + if c != nil { + if cmd, ok := c.data.(ZCommander); ok { + if err := cmd.Validate([]string{}); err != nil { //validate + log.Fatal(err) + } + modTypes = append(modTypes, name) + returnFlags = append(returnFlags, c.data) + par, _ := c.parent.(*Command) + c.Name = "-" //remove previous command + par.AddCommand(name, c.ShortDescription, c.LongDescription, c.module) //recreate new group with duplicate module } - modTypes = append(modTypes, name) - returnFlags = append(returnFlags, c.data) - par, _ := c.parent.(*Command) - c.Name = "-" //remove previous command - par.AddCommand(name, c.ShortDescription, c.LongDescription, c.module) //recreate new group with duplicate module } } } diff --git a/ini_test.go b/ini_test.go index 7718b9e..ee5c899 100644 --- a/ini_test.go +++ b/ini_test.go @@ -345,13 +345,14 @@ ns1.opt8=true func TestReadIni(t *testing.T) { var opts helpOptions - - p := NewNamedParser("TestIni", Default) - p.AddGroup("Application Options", "The application options", &opts) - - inip := NewIniParser(p) - - inic := ` + tests := []struct { + name string + ini string + expectedValueOfDefaultOpt string + }{ + { + "Single Application Options section", + ` ; Show verbose debug information verbose = true verbose = true @@ -378,37 +379,75 @@ StringSlice = another value int-map = a:2 int-map = b:3 -` +`, "New\nvalue", + }, { + "Multiple Application Options sections", + ` +; Show verbose debug information +verbose = true +verbose = true - b := strings.NewReader(inic) - _, _, err := inip.Parse(b) +DefaultMap = another:"value\n1" +DefaultMap = some:value 2 - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } +[Application Options] +; A slice of pointers to string +; PtrSlice = + + ;Second Application Options Section +[Application Options] +; Test env-default1 value +EnvDefault1 = New value + +[Other Options] +# A slice of strings +StringSlice = "some\nvalue" +StringSlice = another value - assertBoolArray(t, opts.Verbose, []bool{true, true}) +; A map from string to int +int-map = a:2 +int-map = b:3 - if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) { - t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap) +`, "Some\nvalue", + }, } - assertString(t, opts.Default, "New\nvalue") + for _, test := range tests { + p := NewNamedParser("TestIni", Default) + p.AddGroup("Application Options", "The application options", &opts) - assertString(t, opts.EnvDefault1, "New value") + inip := NewIniParser(p) - assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"}) + b := strings.NewReader(test.ini) + _, _, err := inip.Parse(b) - if v, ok := opts.Other.IntMap["a"]; !ok { - t.Errorf("Expected \"a\" in Other.IntMap") - } else if v != 2 { - t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v) - } + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + assertBoolArray(t, opts.Verbose, []bool{true, true}) - if v, ok := opts.Other.IntMap["b"]; !ok { - t.Errorf("Expected \"b\" in Other.IntMap") - } else if v != 3 { - t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v) + if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) { + t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap) + } + + assertString(t, opts.Default, test.expectedValueOfDefaultOpt) + + assertString(t, opts.EnvDefault1, "New value") + + assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"}) + + if v, ok := opts.Other.IntMap["a"]; !ok { + t.Errorf("Expected \"a\" in Other.IntMap") + } else if v != 2 { + t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v) + } + + if v, ok := opts.Other.IntMap["b"]; !ok { + t.Errorf("Expected \"b\" in Other.IntMap") + } else if v != 3 { + t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v) + } } } @@ -433,9 +472,6 @@ DefaultArray = 1 DefaultArray = "2" DefaultArray = 3 -; Testdefault map value -; DefaultMap = - ; Test env-default1 value EnvDefault1 = env-def @@ -443,9 +479,6 @@ EnvDefault1 = env-def EnvDefault2 = env-def [Other Options] -; A slice of strings -; StringSlice = - ; A map from string to int int-map = a:2 int-map = b:"3" @@ -464,9 +497,6 @@ DefaultArray = 1 DefaultArray = 2 DefaultArray = 3 -; Testdefault map value -; DefaultMap = - ; Test env-default1 value EnvDefault1 = env-def @@ -474,9 +504,6 @@ EnvDefault1 = env-def EnvDefault2 = env-def [Other Options] -; A slice of strings -; StringSlice = - ; A map from string to int int-map = a:2 int-map = b:3 @@ -529,9 +556,6 @@ DefaultArray = "1" DefaultArray = "2" DefaultArray = "3" -; Testdefault map value -; DefaultMap = - ; Test env-default1 value EnvDefault1 = env-def @@ -539,9 +563,6 @@ EnvDefault1 = env-def EnvDefault2 = env-def [Other Options] -; A slice of strings -; StringSlice = - ; A map from string to int int-map = a:"2" int-map = b:"3"