go-arg-1.6.0/0000775000175000017510000000000015014334473012227 5ustar nileshnileshgo-arg-1.6.0/usage_test.go0000664000175000017510000006653615014334473014741 0ustar nileshnileshpackage arg import ( "bytes" "errors" "fmt" "os" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type NameDotName struct { Head, Tail string } func (n *NameDotName) UnmarshalText(b []byte) error { s := string(b) pos := strings.Index(s, ".") if pos == -1 { return fmt.Errorf("missing period in %s", s) } n.Head = s[:pos] n.Tail = s[pos+1:] return nil } func (n *NameDotName) MarshalText() (text []byte, err error) { text = []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)) return } func TestWriteUsage(t *testing.T) { expectedUsage := "Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--testenv TESTENV] [--file FILE] INPUT [OUTPUT [OUTPUT ...]]" expectedHelp := ` Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--testenv TESTENV] [--file FILE] INPUT [OUTPUT [OUTPUT ...]] Positional arguments: INPUT OUTPUT list of outputs Options: --name NAME name to use [default: Foo Bar] --value VALUE secret value [default: 42] --verbose, -v verbosity level --dataset DATASET dataset to use --optimize OPTIMIZE, -O OPTIMIZE optimization level --ids IDS Ids --values VALUES Values --workers WORKERS, -w WORKERS number of workers to start [default: 10, env: WORKERS] --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 { Input string `arg:"positional,required"` Output []string `arg:"positional" help:"list of outputs"` Name string `help:"name to use"` Value int `help:"secret value"` Verbose bool `arg:"-v" help:"verbosity level"` Dataset string `help:"dataset to use"` Optimize int `arg:"-O" help:"optimization level"` Ids []int64 `help:"Ids"` 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" args.Value = 42 args.File = &NameDotName{"scratch", "txt"} p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) os.Args[0] = "example" 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())) } type MyEnum int func (n *MyEnum) UnmarshalText(b []byte) error { return nil } func (n *MyEnum) MarshalText() ([]byte, error) { return nil, errors.New("There was a problem") } func TestUsageWithDefaults(t *testing.T) { expectedUsage := "Usage: example [--label LABEL] [--content CONTENT]" expectedHelp := ` Usage: example [--label LABEL] [--content CONTENT] Options: --label LABEL [default: cat] --content CONTENT [default: dog] --help, -h display this help and exit ` var args struct { Label string Content string `default:"dog"` } args.Label = "cat" p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) args.Label = "should_ignore_this" 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 TestUsageCannotMarshalToString(t *testing.T) { var args struct { Name *MyEnum } v := MyEnum(42) args.Name = &v _, err := NewParser(Config{Program: "example"}, &args) assert.EqualError(t, err, `args.Name: error marshaling default value to string: There was a problem`) } func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) { expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]" expectedHelp := ` Usage: example [VERYLONGPOSITIONALWITHHELP] Positional arguments: VERYLONGPOSITIONALWITHHELP this positional argument is very long but cannot include commas Options: --help, -h display this help and exit ` var args struct { VeryLongPositionalWithHelp string `arg:"positional,help:this positional argument is very long but cannot include commas"` } p, err := NewParser(Config{Program: "example"}, &args) require.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 TestUsageLongPositionalWithHelp_newForm(t *testing.T) { expectedUsage := "Usage: example [VERYLONGPOSITIONALWITHHELP]" expectedHelp := ` Usage: example [VERYLONGPOSITIONALWITHHELP] Positional arguments: VERYLONGPOSITIONALWITHHELP this positional argument is very long, and includes: commas, colons etc Options: --help, -h display this help and exit ` var args struct { VeryLongPositionalWithHelp string `arg:"positional" help:"this positional argument is very long, and includes: commas, colons etc"` } p, err := NewParser(Config{Program: "example"}, &args) require.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 TestUsageWithProgramName(t *testing.T) { expectedUsage := "Usage: myprogram" expectedHelp := ` Usage: myprogram Options: --help, -h display this help and exit ` config := Config{ Program: "myprogram", } p, err := NewParser(config, &struct{}{}) require.NoError(t, err) os.Args[0] = "example" 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())) } type versioned struct{} // Version returns the version for this program func (versioned) Version() string { return "example 3.2.1" } func TestUsageWithVersion(t *testing.T) { expectedUsage := "Usage: example" expectedHelp := ` example 3.2.1 Usage: example Options: --help, -h display this help and exit --version display version and exit ` os.Args[0] = "example" p, err := NewParser(Config{}, &versioned{}) require.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 TestUsageWithUserDefinedVersionFlag(t *testing.T) { expectedUsage := "Usage: example [--version]" expectedHelp := ` Usage: example [--version] Options: --version this is a user-defined version flag --help, -h display this help and exit ` var args struct { ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.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 TestUsageWithVersionAndUserDefinedVersionFlag(t *testing.T) { expectedUsage := "Usage: example [--version]" expectedHelp := ` Usage: example [--version] Options: --version this is a user-defined version flag --help, -h display this help and exit ` var args struct { versioned ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.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())) } type subcommand struct { Number int `arg:"-n,--number" help:"compute something on the given number"` } func TestUsageWithVersionAndSubcommand(t *testing.T) { expectedUsage := "Usage: example []" expectedHelp := ` example 3.2.1 Usage: example [] Options: --help, -h display this help and exit --version display version and exit Commands: cmd ` var args struct { versioned Cmd *subcommand `arg:"subcommand"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.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())) expectedUsage = "Usage: example cmd [--number NUMBER]" expectedHelp = ` example 3.2.1 Usage: example cmd [--number NUMBER] Options: --number NUMBER, -n NUMBER compute something on the given number --help, -h display this help and exit --version display version and exit ` _ = p.Parse([]string{"cmd"}) help = bytes.Buffer{} p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) usage = bytes.Buffer{} p.WriteUsage(&usage) assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) } func TestUsageWithUserDefinedVersionFlagAndSubcommand(t *testing.T) { expectedUsage := "Usage: example [--version] []" expectedHelp := ` Usage: example [--version] [] Options: --version this is a user-defined version flag --help, -h display this help and exit Commands: cmd ` var args struct { Cmd *subcommand `arg:"subcommand"` ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.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())) expectedUsage = "Usage: example cmd [--number NUMBER]" expectedHelp = ` Usage: example cmd [--number NUMBER] Options: --number NUMBER, -n NUMBER compute something on the given number Global options: --version this is a user-defined version flag --help, -h display this help and exit ` _ = p.Parse([]string{"cmd"}) help = bytes.Buffer{} p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) usage = bytes.Buffer{} p.WriteUsage(&usage) assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) } func TestUsageWithVersionAndUserDefinedVersionFlagAndSubcommand(t *testing.T) { expectedUsage := "Usage: example [--version] []" expectedHelp := ` Usage: example [--version] [] Options: --version this is a user-defined version flag --help, -h display this help and exit Commands: cmd ` var args struct { versioned Cmd *subcommand `arg:"subcommand"` ShowVersion bool `arg:"--version" help:"this is a user-defined version flag"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.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())) expectedUsage = "Usage: example cmd [--number NUMBER]" expectedHelp = ` Usage: example cmd [--number NUMBER] Options: --number NUMBER, -n NUMBER compute something on the given number Global options: --version this is a user-defined version flag --help, -h display this help and exit ` _ = p.Parse([]string{"cmd"}) help = bytes.Buffer{} p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) usage = bytes.Buffer{} p.WriteUsage(&usage) assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) } type described struct{} // Described returns the description for this program func (described) Description() string { return "this program does this and that" } func TestUsageWithDescription(t *testing.T) { expectedUsage := "Usage: example" expectedHelp := ` this program does this and that Usage: example Options: --help, -h display this help and exit ` os.Args[0] = "example" p, err := NewParser(Config{}, &described{}) require.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())) } type epilogued struct{} // Epilogued returns the epilogue for this program func (epilogued) Epilogue() string { return "For more information visit github.com/alexflint/go-arg" } func TestUsageWithEpilogue(t *testing.T) { expectedUsage := "Usage: example" expectedHelp := ` Usage: example Options: --help, -h display this help and exit For more information visit github.com/alexflint/go-arg ` os.Args[0] = "example" p, err := NewParser(Config{}, &epilogued{}) require.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 TestUsageForRequiredPositionals(t *testing.T) { expectedUsage := "Usage: example REQUIRED1 REQUIRED2\n" var args struct { Required1 string `arg:"positional,required"` Required2 string `arg:"positional,required"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, usage.String()) } func TestUsageForMixedPositionals(t *testing.T) { expectedUsage := "Usage: example REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]]\n" var args struct { Required1 string `arg:"positional,required"` Required2 string `arg:"positional,required"` Optional1 string `arg:"positional"` Optional2 string `arg:"positional"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, usage.String()) } func TestUsageForRepeatedPositionals(t *testing.T) { expectedUsage := "Usage: example REQUIRED1 REQUIRED2 REPEATED [REPEATED ...]\n" var args struct { Required1 string `arg:"positional,required"` Required2 string `arg:"positional,required"` Repeated []string `arg:"positional,required"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, usage.String()) } func TestUsageForMixedAndRepeatedPositionals(t *testing.T) { expectedUsage := "Usage: example REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2 [REPEATED [REPEATED ...]]]]\n" var args struct { Required1 string `arg:"positional,required"` Required2 string `arg:"positional,required"` Optional1 string `arg:"positional"` Optional2 string `arg:"positional"` Repeated []string `arg:"positional"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, usage.String()) } func TestRequiredMultiplePositionals(t *testing.T) { expectedUsage := "Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]\n" expectedHelp := ` Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...] Positional arguments: REQUIREDMULTIPLE required multiple positional Options: --help, -h display this help and exit ` var args struct { RequiredMultiple []string `arg:"positional,required" help:"required multiple positional"` } p, err := NewParser(Config{Program: "example"}, &args) require.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, usage.String()) } func TestUsageWithSubcommands(t *testing.T) { expectedUsage := "Usage: example child [--values VALUES]" expectedHelp := ` Usage: example child [--values VALUES] Options: --values VALUES Values Global options: --verbose, -v verbosity level --help, -h display this help and exit ` var args struct { Verbose bool `arg:"-v" help:"verbosity level"` Child *struct { Values []float64 `help:"Values"` } `arg:"subcommand:child"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.NoError(t, err) _ = p.Parse([]string{"child"}) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) var help2 bytes.Buffer p.WriteHelpForSubcommand(&help2, "child") assert.Equal(t, expectedHelp[1:], help2.String()) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) var usage2 bytes.Buffer p.WriteUsageForSubcommand(&usage2, "child") assert.Equal(t, expectedUsage, strings.TrimSpace(usage2.String())) } func TestUsageWithNestedSubcommands(t *testing.T) { expectedUsage := "Usage: example child nested [--enable] OUTPUT" expectedHelp := ` Usage: example child nested [--enable] OUTPUT Positional arguments: OUTPUT Options: --enable Global options: --values VALUES Values --verbose, -v verbosity level --help, -h display this help and exit ` var args struct { Verbose bool `arg:"-v" help:"verbosity level"` Child *struct { Values []float64 `help:"Values"` Nested *struct { Enable bool Output string `arg:"positional,required"` } `arg:"subcommand:nested"` } `arg:"subcommand:child"` } os.Args[0] = "example" p, err := NewParser(Config{}, &args) require.NoError(t, err) _ = p.Parse([]string{"child", "nested", "value"}) assert.Equal(t, []string{"child", "nested"}, p.SubcommandNames()) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) var help2 bytes.Buffer p.WriteHelpForSubcommand(&help2, "child", "nested") assert.Equal(t, expectedHelp[1:], help2.String()) var usage bytes.Buffer p.WriteUsage(&usage) assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String())) var usage2 bytes.Buffer p.WriteUsageForSubcommand(&usage2, "child", "nested") assert.Equal(t, expectedUsage, strings.TrimSpace(usage2.String())) } func TestNonexistentSubcommand(t *testing.T) { var args struct { sub *struct{} `arg:"subcommand"` } p, err := NewParser(Config{Exit: func(int) {}}, &args) require.NoError(t, err) var b bytes.Buffer err = p.WriteUsageForSubcommand(&b, "does_not_exist") assert.Error(t, err) err = p.WriteHelpForSubcommand(&b, "does_not_exist") assert.Error(t, err) err = p.FailSubcommand("something went wrong", "does_not_exist") assert.Error(t, err) err = p.WriteUsageForSubcommand(&b, "sub", "does_not_exist") assert.Error(t, err) err = p.WriteHelpForSubcommand(&b, "sub", "does_not_exist") assert.Error(t, err) err = p.FailSubcommand("something went wrong", "sub", "does_not_exist") assert.Error(t, err) } func TestUsageWithoutLongNames(t *testing.T) { expectedUsage := "Usage: example [-a PLACEHOLDER] -b SHORTONLY2" expectedHelp := ` Usage: example [-a PLACEHOLDER] -b SHORTONLY2 Options: -a PLACEHOLDER some help [default: some val] -b SHORTONLY2 some help2 --help, -h display this help and exit ` var args struct { ShortOnly string `arg:"-a,--" help:"some help" default:"some val" placeholder:"PLACEHOLDER"` ShortOnly2 string `arg:"-b,--,required" help:"some help2"` } p, err := NewParser(Config{Program: "example"}, &args) require.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 TestUsageWithEmptyPlaceholder(t *testing.T) { expectedUsage := "Usage: example [-a] [--b] [--c]" expectedHelp := ` Usage: example [-a] [--b] [--c] Options: -a some help for a --b some help for b --c, -c some help for c --help, -h display this help and exit ` var args struct { ShortOnly string `arg:"-a,--" placeholder:"" help:"some help for a"` LongOnly string `arg:"--b" placeholder:"" help:"some help for b"` Both string `arg:"-c,--c" placeholder:"" help:"some help for c"` } p, err := NewParser(Config{Program: "example"}, &args) require.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 TestUsageWithShortFirst(t *testing.T) { expectedUsage := "Usage: example [-c CAT] [--dog DOG]" expectedHelp := ` Usage: example [-c CAT] [--dog DOG] Options: -c CAT --dog DOG --help, -h display this help and exit ` var args struct { Dog string Cat string `arg:"-c,--"` } 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 TestUsageWithEnvOptions(t *testing.T) { expectedUsage := "Usage: example [-s SHORT]" expectedHelp := ` 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"` } 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 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 exit := func(code int) { exitCode = code } expectedStdout := ` Usage: example [--foo FOO] error: something went wrong ` var args struct { Foo int } p, err := NewParser(Config{Program: "example", Exit: exit, Out: &stdout}, &args) require.NoError(t, err) p.Fail("something went wrong") assert.Equal(t, expectedStdout[1:], stdout.String()) assert.Equal(t, 2, exitCode) } func TestFailSubcommand(t *testing.T) { var stdout bytes.Buffer var exitCode int exit := func(code int) { exitCode = code } expectedStdout := ` Usage: example sub error: something went wrong ` var args struct { Sub *struct{} `arg:"subcommand"` } p, err := NewParser(Config{Program: "example", Exit: exit, Out: &stdout}, &args) require.NoError(t, err) err = p.FailSubcommand("something went wrong", "sub") require.NoError(t, err) assert.Equal(t, expectedStdout[1:], stdout.String()) assert.Equal(t, 2, exitCode) } type lengthOf struct { Length int } func (p *lengthOf) UnmarshalText(b []byte) error { p.Length = len(b) return nil } func TestHelpShowsDefaultValueFromOriginalTag(t *testing.T) { // check that the usage text prints the original string from the default tag, not // the serialization of the parsed value expectedHelp := ` Usage: example [--test TEST] Options: --test TEST [default: some_default_value] --help, -h display this help and exit ` var args struct { Test *lengthOf `default:"some_default_value"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) } func TestHelpShowsSubcommandAliases(t *testing.T) { expectedHelp := ` Usage: example [] Options: --help, -h display this help and exit Commands: remove, rm, r remove something from somewhere simple do something simple halt, stop stop now ` var args struct { Remove *struct{} `arg:"subcommand:remove|rm|r" help:"remove something from somewhere"` Simple *struct{} `arg:"subcommand" help:"do something simple"` Stop *struct{} `arg:"subcommand:halt|stop" help:"stop now"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) } func TestHelpShowsPositionalWithDefault(t *testing.T) { expectedHelp := ` Usage: example [FOO] Positional arguments: FOO this is a positional with a default [default: bar] Options: --help, -h display this help and exit ` var args struct { Foo string `arg:"positional" default:"bar" help:"this is a positional with a default"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) } func TestHelpShowsPositionalWithEnv(t *testing.T) { expectedHelp := ` Usage: example [FOO] Positional arguments: FOO this is a positional with an env variable [env: FOO] Options: --help, -h display this help and exit ` var args struct { Foo string `arg:"positional,env:FOO" help:"this is a positional with an env variable"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) } func TestHelpShowsPositionalWithDefaultAndEnv(t *testing.T) { expectedHelp := ` Usage: example [FOO] Positional arguments: FOO this is a positional with a default and an env variable [default: bar, env: FOO] Options: --help, -h display this help and exit ` var args struct { Foo string `arg:"positional,env:FOO" default:"bar" help:"this is a positional with a default and an env variable"` } p, err := NewParser(Config{Program: "example"}, &args) require.NoError(t, err) var help bytes.Buffer p.WriteHelp(&help) assert.Equal(t, expectedHelp[1:], help.String()) } go-arg-1.6.0/usage.go0000664000175000017510000002244515014334473013671 0ustar nileshnileshpackage arg import ( "fmt" "io" "strings" ) // the width of the left column const colWidth = 25 // Fail prints usage information to p.Config.Out and exits with status code 2. func (p *Parser) Fail(msg string) { p.FailSubcommand(msg) } // FailSubcommand prints usage information for a specified subcommand to p.Config.Out, // then exits with status code 2. To write usage information for a top-level // subcommand, provide just the name of that subcommand. To write usage // information for a subcommand that is nested under another subcommand, provide // a sequence of subcommand names starting with the top-level subcommand and so // on down the tree. func (p *Parser) FailSubcommand(msg string, subcommand ...string) error { err := p.WriteUsageForSubcommand(p.config.Out, subcommand...) if err != nil { return err } fmt.Fprintln(p.config.Out, "error:", msg) p.config.Exit(2) return nil } // WriteUsage writes usage information to the given writer func (p *Parser) WriteUsage(w io.Writer) { p.WriteUsageForSubcommand(w, p.subcommand...) } // WriteUsageForSubcommand writes the usage information for a specified // subcommand. To write usage information for a top-level subcommand, provide // just the name of that subcommand. To write usage information for a subcommand // that is nested under another subcommand, provide a sequence of subcommand // names starting with the top-level subcommand and so on down the tree. func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error { cmd, err := p.lookupCommand(subcommand...) if err != nil { return err } var positionals, longOptions, shortOptions []*spec for _, spec := range cmd.specs { switch { case spec.positional: positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) case spec.short != "": shortOptions = append(shortOptions, spec) } } // print the beginning of the usage string fmt.Fprintf(w, "Usage: %s", p.cmd.name) for _, s := range subcommand { fmt.Fprint(w, " "+s) } // write the option component of the usage message for _, spec := range shortOptions { // prefix with a space fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") } fmt.Fprint(w, synopsis(spec, "-"+spec.short)) if !spec.required { fmt.Fprint(w, "]") } } for _, spec := range longOptions { // prefix with a space fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") } fmt.Fprint(w, synopsis(spec, "--"+spec.long)) if !spec.required { fmt.Fprint(w, "]") } } // When we parse positionals, we check that: // 1. required positionals come before non-required positionals // 2. there is at most one multiple-value positional // 3. if there is a multiple-value positional then it comes after all other positionals // Here we merely print the usage string, so we do not explicitly re-enforce those rules // write the positionals in following form: // REQUIRED1 REQUIRED2 // REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]] // REQUIRED1 REQUIRED2 REPEATED [REPEATED ...] // REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]] // REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]] var closeBrackets int for _, spec := range positionals { fmt.Fprint(w, " ") if !spec.required { fmt.Fprint(w, "[") closeBrackets += 1 } if spec.cardinality == multiple { fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder) } else { fmt.Fprint(w, spec.placeholder) } } fmt.Fprint(w, strings.Repeat("]", closeBrackets)) // if the program supports subcommands, give a hint to the user about their existence if len(cmd.subcommands) > 0 { fmt.Fprint(w, " []") } fmt.Fprint(w, "\n") return nil } // print prints a line like this: // // --option FOO A description of the option [default: 123] // // If the text on the left is longer than a certain threshold, the description is moved to the next line: // // --verylongoptionoption VERY_LONG_VARIABLE // A description of the option [default: 123] // // If multiple "extras" are provided then they are put inside a single set of square brackets: // // --option FOO A description of the option [default: 123, env: FOO] func print(w io.Writer, item, description string, bracketed ...string) { lhs := " " + item fmt.Fprint(w, lhs) if description != "" { if len(lhs)+2 < colWidth { fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs))) } else { fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) } fmt.Fprint(w, description) } var brack string for _, s := range bracketed { if s != "" { if brack != "" { brack += ", " } brack += s } } if brack != "" { fmt.Fprintf(w, " [%s]", brack) } fmt.Fprint(w, "\n") } func withDefault(s string) string { if s == "" { return "" } return "default: " + s } func withEnv(env string) string { if env == "" { return "" } return "env: " + env } // WriteHelp writes the usage string followed by the full help string for each option func (p *Parser) WriteHelp(w io.Writer) { p.WriteHelpForSubcommand(w, p.subcommand...) } // WriteHelpForSubcommand writes the usage string followed by the full help // string for a specified subcommand. To write help for a top-level subcommand, // provide just the name of that subcommand. To write help for a subcommand that // is nested under another subcommand, provide a sequence of subcommand names // starting with the top-level subcommand and so on down the tree. func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error { cmd, err := p.lookupCommand(subcommand...) if err != nil { return err } var positionals, longOptions, shortOptions, envOnlyOptions []*spec var hasVersionOption bool for _, spec := range cmd.specs { switch { case spec.positional: positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) if spec.long == "version" { hasVersionOption = true } case spec.short != "": shortOptions = append(shortOptions, spec) case spec.short == "" && spec.long == "": envOnlyOptions = append(envOnlyOptions, spec) } } // obtain a flattened list of options from all ancestors // also determine if any ancestor has a version option spec var globals []*spec ancestor := cmd.parent for ancestor != nil { for _, spec := range ancestor.specs { if spec.long == "version" { hasVersionOption = true break } } globals = append(globals, ancestor.specs...) ancestor = ancestor.parent } if p.description != "" { fmt.Fprintln(w, p.description) } if !hasVersionOption && p.version != "" { fmt.Fprintln(w, p.version) } p.WriteUsageForSubcommand(w, subcommand...) // write the list of positionals if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { print(w, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } // write the list of options with the short-only ones first to match the usage string if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil { fmt.Fprint(w, "\nOptions:\n") for _, spec := range shortOptions { p.printOption(w, spec) } for _, spec := range longOptions { p.printOption(w, spec) } } // write the list of global options if len(globals) > 0 { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) } } // write the list of built in options p.printOption(w, &spec{ cardinality: zero, long: "help", short: "h", help: "display this help and exit", }) if !hasVersionOption && p.version != "" { p.printOption(w, &spec{ cardinality: zero, long: "version", help: "display version and exit", }) } // 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") for _, subcmd := range cmd.subcommands { names := append([]string{subcmd.name}, subcmd.aliases...) print(w, strings.Join(names, ", "), subcmd.help) } } if p.epilogue != "" { fmt.Fprintln(w, "\n"+p.epilogue) } return nil } func (p *Parser) printOption(w io.Writer, spec *spec) { ways := make([]string, 0, 2) if spec.long != "" { ways = append(ways, synopsis(spec, "--"+spec.long)) } if spec.short != "" { ways = append(ways, synopsis(spec, "-"+spec.short)) } if len(ways) > 0 { print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } 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) } print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString)) } func synopsis(spec *spec, form string) string { // if the user omits the placeholder tag then we pick one automatically, // but if the user explicitly specifies an empty placeholder then we // leave out the placeholder in the help message if spec.cardinality == zero || spec.placeholder == "" { return form } return form + " " + spec.placeholder } go-arg-1.6.0/subcommand_test.go0000664000175000017510000002660415014334473015755 0ustar nileshnileshpackage arg import ( "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // This file contains tests for parse.go but I decided to put them here // since that file is getting large func TestSubcommandNotAPointer(t *testing.T) { var args struct { A string `arg:"subcommand"` } _, err := NewParser(Config{}, &args) assert.Error(t, err) } func TestSubcommandNotAPointerToStruct(t *testing.T) { var args struct { A struct{} `arg:"subcommand"` } _, err := NewParser(Config{}, &args) assert.Error(t, err) } func TestPositionalAndSubcommandNotAllowed(t *testing.T) { var args struct { A string `arg:"positional"` B *struct{} `arg:"subcommand"` } _, err := NewParser(Config{}, &args) assert.Error(t, err) } func TestMinimalSubcommand(t *testing.T) { type listCmd struct { } var args struct { List *listCmd `arg:"subcommand"` } p, err := pparse("list", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, args.List, p.Subcommand()) assert.Equal(t, []string{"list"}, p.SubcommandNames()) } func TestSubcommandNamesBeforeParsing(t *testing.T) { type listCmd struct{} var args struct { List *listCmd `arg:"subcommand"` } p, err := NewParser(Config{}, &args) require.NoError(t, err) assert.Nil(t, p.Subcommand()) assert.Nil(t, p.SubcommandNames()) } func TestNoSuchSubcommand(t *testing.T) { type listCmd struct { } var args struct { List *listCmd `arg:"subcommand"` } _, err := pparse("invalid", &args) assert.Error(t, err) } func TestNamedSubcommand(t *testing.T) { type listCmd struct { } var args struct { List *listCmd `arg:"subcommand:ls"` } p, err := pparse("ls", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, args.List, p.Subcommand()) assert.Equal(t, []string{"ls"}, p.SubcommandNames()) } func TestSubcommandAliases(t *testing.T) { type listCmd struct { } var args struct { List *listCmd `arg:"subcommand:list|ls"` } p, err := pparse("ls", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, args.List, p.Subcommand()) assert.Equal(t, []string{"ls"}, p.SubcommandNames()) } func TestEmptySubcommand(t *testing.T) { type listCmd struct { } var args struct { List *listCmd `arg:"subcommand"` } p, err := pparse("", &args) require.NoError(t, err) assert.Nil(t, args.List) assert.Nil(t, p.Subcommand()) assert.Empty(t, p.SubcommandNames()) } func TestTwoSubcommands(t *testing.T) { type getCmd struct { } type listCmd struct { } var args struct { Get *getCmd `arg:"subcommand"` List *listCmd `arg:"subcommand"` } p, err := pparse("list", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List, p.Subcommand()) assert.Equal(t, []string{"list"}, p.SubcommandNames()) } func TestTwoSubcommandsWithAliases(t *testing.T) { type getCmd struct { } type listCmd struct { } var args struct { Get *getCmd `arg:"subcommand:get|g"` List *listCmd `arg:"subcommand:list|ls"` } p, err := pparse("ls", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List, p.Subcommand()) assert.Equal(t, []string{"ls"}, p.SubcommandNames()) } func TestSubcommandsWithOptions(t *testing.T) { type getCmd struct { Name string } type listCmd struct { Limit int } type cmd struct { Verbose bool Get *getCmd `arg:"subcommand"` List *listCmd `arg:"subcommand"` } { var args cmd err := parse("list", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) } { var args cmd err := parse("list --limit 3", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List.Limit, 3) } { var args cmd err := parse("list --limit 3 --verbose", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List.Limit, 3) assert.True(t, args.Verbose) } { var args cmd err := parse("list --verbose --limit 3", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List.Limit, 3) assert.True(t, args.Verbose) } { var args cmd err := parse("--verbose list --limit 3", &args) require.NoError(t, err) assert.Nil(t, args.Get) assert.NotNil(t, args.List) assert.Equal(t, args.List.Limit, 3) assert.True(t, args.Verbose) } { var args cmd err := parse("get", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Nil(t, args.List) } { var args cmd err := parse("get --name test", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Nil(t, args.List) assert.Equal(t, args.Get.Name, "test") } } func TestSubcommandsWithEnvVars(t *testing.T) { type getCmd struct { Name string `arg:"env"` } type listCmd struct { Limit int `arg:"env"` } type cmd struct { Verbose bool Get *getCmd `arg:"subcommand"` List *listCmd `arg:"subcommand"` } { var args cmd setenv(t, "LIMIT", "123") err := parse("list", &args) require.NoError(t, err) require.NotNil(t, args.List) assert.Equal(t, 123, args.List.Limit) } { var args cmd setenv(t, "LIMIT", "not_an_integer") err := parse("list", &args) assert.Error(t, err) } } func TestNestedSubcommands(t *testing.T) { type child struct{} type parent struct { Child *child `arg:"subcommand"` } type grandparent struct { Parent *parent `arg:"subcommand"` } type root struct { Grandparent *grandparent `arg:"subcommand"` } { var args root p, err := pparse("grandparent parent child", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.NotNil(t, args.Grandparent.Parent) require.NotNil(t, args.Grandparent.Parent.Child) assert.Equal(t, args.Grandparent.Parent.Child, p.Subcommand()) assert.Equal(t, []string{"grandparent", "parent", "child"}, p.SubcommandNames()) } { var args root p, err := pparse("grandparent parent", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.NotNil(t, args.Grandparent.Parent) require.Nil(t, args.Grandparent.Parent.Child) assert.Equal(t, args.Grandparent.Parent, p.Subcommand()) assert.Equal(t, []string{"grandparent", "parent"}, p.SubcommandNames()) } { var args root p, err := pparse("grandparent", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.Nil(t, args.Grandparent.Parent) assert.Equal(t, args.Grandparent, p.Subcommand()) assert.Equal(t, []string{"grandparent"}, p.SubcommandNames()) } { var args root p, err := pparse("", &args) require.NoError(t, err) require.Nil(t, args.Grandparent) assert.Nil(t, p.Subcommand()) assert.Empty(t, p.SubcommandNames()) } } func TestNestedSubcommandsWithAliases(t *testing.T) { type child struct{} type parent struct { Child *child `arg:"subcommand:child|ch"` } type grandparent struct { Parent *parent `arg:"subcommand:parent|pa"` } type root struct { Grandparent *grandparent `arg:"subcommand:grandparent|gp"` } { var args root p, err := pparse("gp parent child", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.NotNil(t, args.Grandparent.Parent) require.NotNil(t, args.Grandparent.Parent.Child) assert.Equal(t, args.Grandparent.Parent.Child, p.Subcommand()) assert.Equal(t, []string{"gp", "parent", "child"}, p.SubcommandNames()) } { var args root p, err := pparse("grandparent pa", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.NotNil(t, args.Grandparent.Parent) require.Nil(t, args.Grandparent.Parent.Child) assert.Equal(t, args.Grandparent.Parent, p.Subcommand()) assert.Equal(t, []string{"grandparent", "pa"}, p.SubcommandNames()) } { var args root p, err := pparse("grandparent", &args) require.NoError(t, err) require.NotNil(t, args.Grandparent) require.Nil(t, args.Grandparent.Parent) assert.Equal(t, args.Grandparent, p.Subcommand()) assert.Equal(t, []string{"grandparent"}, p.SubcommandNames()) } { var args root p, err := pparse("", &args) require.NoError(t, err) require.Nil(t, args.Grandparent) assert.Nil(t, p.Subcommand()) assert.Empty(t, p.SubcommandNames()) } } func TestSubcommandsWithPositionals(t *testing.T) { type listCmd struct { Pattern string `arg:"positional"` } type cmd struct { Format string List *listCmd `arg:"subcommand"` } { var args cmd err := parse("list", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "", args.List.Pattern) } { var args cmd err := parse("list --format json", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "", args.List.Pattern) assert.Equal(t, "json", args.Format) } { var args cmd err := parse("list somepattern", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "somepattern", args.List.Pattern) } { var args cmd err := parse("list somepattern --format json", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "somepattern", args.List.Pattern) assert.Equal(t, "json", args.Format) } { var args cmd err := parse("list --format json somepattern", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "somepattern", args.List.Pattern) assert.Equal(t, "json", args.Format) } { var args cmd err := parse("--format json list somepattern", &args) require.NoError(t, err) assert.NotNil(t, args.List) assert.Equal(t, "somepattern", args.List.Pattern) assert.Equal(t, "json", args.Format) } { var args cmd err := parse("--format json", &args) require.NoError(t, err) assert.Nil(t, args.List) assert.Equal(t, "json", args.Format) } } func TestSubcommandsWithMultiplePositionals(t *testing.T) { type getCmd struct { Items []string `arg:"positional"` } type cmd struct { Limit int Get *getCmd `arg:"subcommand"` } { var args cmd err := parse("get", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Empty(t, args.Get.Items) } { var args cmd err := parse("get --limit 5", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Empty(t, args.Get.Items) assert.Equal(t, 5, args.Limit) } { var args cmd err := parse("get item1", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Equal(t, []string{"item1"}, args.Get.Items) } { var args cmd err := parse("get item1 item2 item3", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Equal(t, []string{"item1", "item2", "item3"}, args.Get.Items) } { var args cmd err := parse("get item1 --limit 5 item2", &args) require.NoError(t, err) assert.NotNil(t, args.Get) assert.Equal(t, []string{"item1", "item2"}, args.Get.Items) assert.Equal(t, 5, args.Limit) } } func TestValForNilStruct(t *testing.T) { type subcmd struct{} var cmd struct { Sub *subcmd `arg:"subcommand"` } p, err := NewParser(Config{}, &cmd) require.NoError(t, err) typ := reflect.TypeOf(cmd) subField, _ := typ.FieldByName("Sub") v := p.val(path{fields: []reflect.StructField{subField, subField}}) assert.False(t, v.IsValid()) } func TestSubcommandInvalidInternal(t *testing.T) { // this situation should never arise in practice but still good to test for it var cmd struct{} p, err := NewParser(Config{}, &cmd) require.NoError(t, err) p.subcommand = []string{"should", "never", "happen"} sub := p.Subcommand() assert.Nil(t, sub) } go-arg-1.6.0/subcommand.go0000664000175000017510000000262315014334473014711 0ustar nileshnileshpackage arg import "fmt" // Subcommand returns the user struct for the subcommand selected by // the command line arguments most recently processed by the parser. // The return value is always a pointer to a struct. If no subcommand // was specified then it returns the top-level arguments struct. If // no command line arguments have been processed by this parser then it // returns nil. func (p *Parser) Subcommand() interface{} { if len(p.subcommand) == 0 { return nil } cmd, err := p.lookupCommand(p.subcommand...) if err != nil { return nil } return p.val(cmd.dest).Interface() } // SubcommandNames returns the sequence of subcommands specified by the // user. If no subcommands were given then it returns an empty slice. func (p *Parser) SubcommandNames() []string { return p.subcommand } // 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 // root command is returned. If no such subcommand exists then an error is // returned. func (p *Parser) lookupCommand(path ...string) (*command, error) { cmd := p.cmd for _, name := range path { found := findSubcommand(cmd.subcommands, name) if found == nil { return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name) } cmd = found } return cmd, nil } go-arg-1.6.0/sequence_test.go0000664000175000017510000001065515014334473015434 0ustar nileshnileshpackage arg import ( "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestSetSliceWithoutClearing(t *testing.T) { xs := []int{10} entries := []string{"1", "2", "3"} err := setSlice(reflect.ValueOf(&xs).Elem(), entries, false) require.NoError(t, err) assert.Equal(t, []int{10, 1, 2, 3}, xs) } func TestSetSliceAfterClearing(t *testing.T) { xs := []int{100} entries := []string{"1", "2", "3"} err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true) require.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, xs) } func TestSetSliceInvalid(t *testing.T) { xs := []int{100} entries := []string{"invalid"} err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true) assert.Error(t, err) } func TestSetSlicePtr(t *testing.T) { var xs []*int entries := []string{"1", "2", "3"} err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true) require.NoError(t, err) require.Len(t, xs, 3) assert.Equal(t, 1, *xs[0]) assert.Equal(t, 2, *xs[1]) assert.Equal(t, 3, *xs[2]) } func TestSetSliceTextUnmarshaller(t *testing.T) { // textUnmarshaler is a struct that captures the length of the string passed to it var xs []*textUnmarshaler entries := []string{"a", "aa", "aaa"} err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true) require.NoError(t, err) require.Len(t, xs, 3) assert.Equal(t, 1, xs[0].val) assert.Equal(t, 2, xs[1].val) assert.Equal(t, 3, xs[2].val) } func TestSetMapWithoutClearing(t *testing.T) { m := map[string]int{"foo": 10} entries := []string{"a=1", "b=2"} err := setMap(reflect.ValueOf(&m).Elem(), entries, false) require.NoError(t, err) require.Len(t, m, 3) assert.Equal(t, 1, m["a"]) assert.Equal(t, 2, m["b"]) assert.Equal(t, 10, m["foo"]) } func TestSetMapAfterClearing(t *testing.T) { m := map[string]int{"foo": 10} entries := []string{"a=1", "b=2"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) require.NoError(t, err) require.Len(t, m, 2) assert.Equal(t, 1, m["a"]) assert.Equal(t, 2, m["b"]) } func TestSetMapWithKeyPointer(t *testing.T) { // textUnmarshaler is a struct that captures the length of the string passed to it var m map[*string]int entries := []string{"abc=123"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) require.NoError(t, err) require.Len(t, m, 1) } func TestSetMapWithValuePointer(t *testing.T) { // textUnmarshaler is a struct that captures the length of the string passed to it var m map[string]*int entries := []string{"abc=123"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) require.NoError(t, err) require.Len(t, m, 1) assert.Equal(t, 123, *m["abc"]) } func TestSetMapTextUnmarshaller(t *testing.T) { // textUnmarshaler is a struct that captures the length of the string passed to it var m map[textUnmarshaler]*textUnmarshaler entries := []string{"a=123", "aa=12", "aaa=1"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) require.NoError(t, err) require.Len(t, m, 3) assert.Equal(t, &textUnmarshaler{3}, m[textUnmarshaler{1}]) assert.Equal(t, &textUnmarshaler{2}, m[textUnmarshaler{2}]) assert.Equal(t, &textUnmarshaler{1}, m[textUnmarshaler{3}]) } func TestSetMapInvalidKey(t *testing.T) { var m map[int]int entries := []string{"invalid=123"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) assert.Error(t, err) } func TestSetMapInvalidValue(t *testing.T) { var m map[int]int entries := []string{"123=invalid"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) assert.Error(t, err) } func TestSetMapMalformed(t *testing.T) { // textUnmarshaler is a struct that captures the length of the string passed to it var m map[string]string entries := []string{"missing_equals_sign"} err := setMap(reflect.ValueOf(&m).Elem(), entries, true) assert.Error(t, err) } func TestSetSliceOrMapErrors(t *testing.T) { var err error var dest reflect.Value // converting a slice to a reflect.Value in this way will make it read only var cannotSet []int dest = reflect.ValueOf(cannotSet) err = setSliceOrMap(dest, nil, false) assert.Error(t, err) // check what happens when we pass in something that is not a slice or a map var notSliceOrMap string dest = reflect.ValueOf(¬SliceOrMap).Elem() err = setSliceOrMap(dest, nil, false) assert.Error(t, err) // check what happens when we pass in a pointer to something that is not a slice or a map var stringPtr *string dest = reflect.ValueOf(&stringPtr).Elem() err = setSliceOrMap(dest, nil, false) assert.Error(t, err) } go-arg-1.6.0/sequence.go0000664000175000017510000000555615014334473014401 0ustar nileshnileshpackage arg import ( "fmt" "reflect" "strings" scalar "github.com/alexflint/go-scalar" ) // setSliceOrMap parses a sequence of strings into a slice or map. If clear is // true then any values already in the slice or map are first removed. func setSliceOrMap(dest reflect.Value, values []string, clear bool) error { if !dest.CanSet() { return fmt.Errorf("field is not writable") } t := dest.Type() if t.Kind() == reflect.Ptr { dest = dest.Elem() t = t.Elem() } switch t.Kind() { case reflect.Slice: return setSlice(dest, values, clear) case reflect.Map: return setMap(dest, values, clear) default: return fmt.Errorf("setSliceOrMap cannot insert values into a %v", t) } } // setSlice parses a sequence of strings and inserts them into a slice. If clear // is true then any values already in the slice are removed. func setSlice(dest reflect.Value, values []string, clear bool) error { var ptr bool elem := dest.Type().Elem() if elem.Kind() == reflect.Ptr && !elem.Implements(textUnmarshalerType) { ptr = true elem = elem.Elem() } // clear the slice in case default values exist if clear && !dest.IsNil() { dest.SetLen(0) } // parse the values one-by-one for _, s := range values { v := reflect.New(elem) if err := scalar.ParseValue(v.Elem(), s); err != nil { return err } if !ptr { v = v.Elem() } dest.Set(reflect.Append(dest, v)) } return nil } // setMap parses a sequence of name=value strings and inserts them into a map. // If clear is true then any values already in the map are removed. func setMap(dest reflect.Value, values []string, clear bool) error { // determine the key and value type var keyIsPtr bool keyType := dest.Type().Key() if keyType.Kind() == reflect.Ptr && !keyType.Implements(textUnmarshalerType) { keyIsPtr = true keyType = keyType.Elem() } var valIsPtr bool valType := dest.Type().Elem() if valType.Kind() == reflect.Ptr && !valType.Implements(textUnmarshalerType) { valIsPtr = true valType = valType.Elem() } // clear the slice in case default values exist if clear && !dest.IsNil() { for _, k := range dest.MapKeys() { dest.SetMapIndex(k, reflect.Value{}) } } // allocate the map if it is not allocated if dest.IsNil() { dest.Set(reflect.MakeMap(dest.Type())) } // parse the values one-by-one for _, s := range values { // split at the first equals sign pos := strings.Index(s, "=") if pos == -1 { return fmt.Errorf("cannot parse %q into a map, expected format key=value", s) } // parse the key k := reflect.New(keyType) if err := scalar.ParseValue(k.Elem(), s[:pos]); err != nil { return err } if !keyIsPtr { k = k.Elem() } // parse the value v := reflect.New(valType) if err := scalar.ParseValue(v.Elem(), s[pos+1:]); err != nil { return err } if !valIsPtr { v = v.Elem() } // add it to the map dest.SetMapIndex(k, v) } return nil } go-arg-1.6.0/reflect_test.go0000664000175000017510000000670615014334473015252 0ustar nileshnileshpackage arg import ( "reflect" "testing" "github.com/stretchr/testify/assert" ) func assertCardinality(t *testing.T, typ reflect.Type, expected cardinality) { actual, err := cardinalityOf(typ) assert.Equal(t, expected, actual, "expected %v to have cardinality %v but got %v", typ, expected, actual) if expected == unsupported { assert.Error(t, err) } } func TestCardinalityOf(t *testing.T) { var b bool var i int var s string var f float64 var bs []bool var is []int var m map[string]int var unsupported1 struct{} var unsupported2 []struct{} var unsupported3 map[string]struct{} var unsupported4 map[struct{}]string assertCardinality(t, reflect.TypeOf(b), zero) assertCardinality(t, reflect.TypeOf(i), one) assertCardinality(t, reflect.TypeOf(s), one) assertCardinality(t, reflect.TypeOf(f), one) assertCardinality(t, reflect.TypeOf(&b), zero) assertCardinality(t, reflect.TypeOf(&s), one) assertCardinality(t, reflect.TypeOf(&i), one) assertCardinality(t, reflect.TypeOf(&f), one) assertCardinality(t, reflect.TypeOf(bs), multiple) assertCardinality(t, reflect.TypeOf(is), multiple) assertCardinality(t, reflect.TypeOf(&bs), multiple) assertCardinality(t, reflect.TypeOf(&is), multiple) assertCardinality(t, reflect.TypeOf(m), multiple) assertCardinality(t, reflect.TypeOf(&m), multiple) assertCardinality(t, reflect.TypeOf(unsupported1), unsupported) assertCardinality(t, reflect.TypeOf(&unsupported1), unsupported) assertCardinality(t, reflect.TypeOf(unsupported2), unsupported) assertCardinality(t, reflect.TypeOf(&unsupported2), unsupported) assertCardinality(t, reflect.TypeOf(unsupported3), unsupported) assertCardinality(t, reflect.TypeOf(&unsupported3), unsupported) assertCardinality(t, reflect.TypeOf(unsupported4), unsupported) assertCardinality(t, reflect.TypeOf(&unsupported4), unsupported) } type implementsTextUnmarshaler struct{} func (*implementsTextUnmarshaler) UnmarshalText(text []byte) error { return nil } func TestCardinalityTextUnmarshaler(t *testing.T) { var x implementsTextUnmarshaler var s []implementsTextUnmarshaler var m []implementsTextUnmarshaler assertCardinality(t, reflect.TypeOf(x), one) assertCardinality(t, reflect.TypeOf(&x), one) assertCardinality(t, reflect.TypeOf(s), multiple) assertCardinality(t, reflect.TypeOf(&s), multiple) assertCardinality(t, reflect.TypeOf(m), multiple) assertCardinality(t, reflect.TypeOf(&m), multiple) } func TestIsExported(t *testing.T) { assert.True(t, isExported("Exported")) assert.False(t, isExported("notExported")) assert.False(t, isExported("")) assert.False(t, isExported(string([]byte{255}))) } func TestCardinalityString(t *testing.T) { assert.Equal(t, "zero", zero.String()) assert.Equal(t, "one", one.String()) assert.Equal(t, "multiple", multiple.String()) assert.Equal(t, "unsupported", unsupported.String()) assert.Equal(t, "unknown(42)", cardinality(42).String()) } func TestIsZero(t *testing.T) { var zero int var notZero = 3 var nilSlice []int var nonNilSlice = []int{1, 2, 3} var nilMap map[string]string var nonNilMap = map[string]string{"foo": "bar"} var uncomparable = func() {} assert.True(t, isZero(reflect.ValueOf(zero))) assert.False(t, isZero(reflect.ValueOf(notZero))) assert.True(t, isZero(reflect.ValueOf(nilSlice))) assert.False(t, isZero(reflect.ValueOf(nonNilSlice))) assert.True(t, isZero(reflect.ValueOf(nilMap))) assert.False(t, isZero(reflect.ValueOf(nonNilMap))) assert.False(t, isZero(reflect.ValueOf(uncomparable))) } go-arg-1.6.0/reflect.go0000664000175000017510000000556115014334473014211 0ustar nileshnileshpackage arg import ( "encoding" "fmt" "reflect" "unicode" "unicode/utf8" scalar "github.com/alexflint/go-scalar" ) var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() // cardinality tracks how many tokens are expected for a given spec // - zero is a boolean, which does to expect any value // - one is an ordinary option that will be parsed from a single token // - multiple is a slice or map that can accept zero or more tokens type cardinality int const ( zero cardinality = iota one multiple unsupported ) func (k cardinality) String() string { switch k { case zero: return "zero" case one: return "one" case multiple: return "multiple" case unsupported: return "unsupported" default: return fmt.Sprintf("unknown(%d)", int(k)) } } // cardinalityOf returns true if the type can be parsed from a string func cardinalityOf(t reflect.Type) (cardinality, error) { if scalar.CanParse(t) { if isBoolean(t) { return zero, nil } return one, nil } // look inside pointer types if t.Kind() == reflect.Ptr { t = t.Elem() } // look inside slice and map types switch t.Kind() { case reflect.Slice: if !scalar.CanParse(t.Elem()) { return unsupported, fmt.Errorf("cannot parse into %v because %v not supported", t, t.Elem()) } return multiple, nil case reflect.Map: if !scalar.CanParse(t.Key()) { return unsupported, fmt.Errorf("cannot parse into %v because key type %v not supported", t, t.Elem()) } if !scalar.CanParse(t.Elem()) { return unsupported, fmt.Errorf("cannot parse into %v because value type %v not supported", t, t.Elem()) } return multiple, nil default: return unsupported, fmt.Errorf("cannot parse into %v", t) } } // isBoolean returns true if the type is a boolean or a pointer to a boolean func isBoolean(t reflect.Type) bool { switch { case isTextUnmarshaler(t): return false case t.Kind() == reflect.Bool: return true case t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Bool: return true default: return false } } // isTextUnmarshaler returns true if the type or its pointer implements encoding.TextUnmarshaler func isTextUnmarshaler(t reflect.Type) bool { return t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) } // isExported returns true if the struct field name is exported func isExported(field string) bool { r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8 return unicode.IsLetter(r) && unicode.IsUpper(r) } // isZero returns true if v contains the zero value for its type func isZero(v reflect.Value) bool { t := v.Type() if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice || t.Kind() == reflect.Map || t.Kind() == reflect.Chan || t.Kind() == reflect.Interface { return v.IsNil() } if !t.Comparable() { return false } return v.Interface() == reflect.Zero(t).Interface() } go-arg-1.6.0/parse_test.go0000664000175000017510000012201015014334473014723 0ustar nileshnileshpackage arg import ( "bytes" "encoding/json" "fmt" "net" "net/mail" "net/url" "os" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setenv(t *testing.T, name, val string) { if err := os.Setenv(name, val); err != nil { t.Error(err) } } func parse(cmdline string, dest interface{}) error { _, err := pparse(cmdline, dest) return err } func pparse(cmdline string, dest interface{}) (*Parser, error) { return parseWithEnv(Config{}, cmdline, nil, dest) } func parseWithEnv(config Config, cmdline string, env []string, dest interface{}) (*Parser, error) { p, err := NewParser(config, dest) if err != nil { return nil, err } // split the command line var parts []string if len(cmdline) > 0 { parts = strings.Split(cmdline, " ") } // split the environment vars for _, s := range env { pos := strings.Index(s, "=") if pos == -1 { return nil, fmt.Errorf("missing equals sign in %q", s) } err := os.Setenv(s[:pos], s[pos+1:]) if err != nil { return nil, err } } // execute the parser return p, p.Parse(parts) } func TestString(t *testing.T) { var args struct { Foo string Ptr *string } err := parse("--foo bar --ptr baz", &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) assert.Equal(t, "baz", *args.Ptr) } func TestBool(t *testing.T) { var args struct { A bool B bool C *bool D *bool } err := parse("--a --c", &args) require.NoError(t, err) assert.True(t, args.A) assert.False(t, args.B) assert.True(t, *args.C) assert.Nil(t, args.D) } func TestInt(t *testing.T) { var args struct { Foo int Ptr *int } err := parse("--foo 7 --ptr 8", &args) require.NoError(t, err) assert.EqualValues(t, 7, args.Foo) assert.EqualValues(t, 8, *args.Ptr) } func TestHexOctBin(t *testing.T) { var args struct { Hex int Oct int Bin int Underscored int } err := parse("--hex 0xA --oct 0o10 --bin 0b101 --underscored 123_456", &args) require.NoError(t, err) assert.EqualValues(t, 10, args.Hex) assert.EqualValues(t, 8, args.Oct) assert.EqualValues(t, 5, args.Bin) assert.EqualValues(t, 123456, args.Underscored) } func TestNegativeInt(t *testing.T) { var args struct { Foo int } err := parse("-foo -100", &args) require.NoError(t, err) assert.EqualValues(t, args.Foo, -100) } func TestNegativeFloat(t *testing.T) { var args struct { Foo float64 } err := parse("-foo -99", &args) require.NoError(t, err) assert.EqualValues(t, args.Foo, -99) } func TestNumericFlag(t *testing.T) { var args struct { UseIPv6 bool `arg:"-6"` Foo int } err := parse("-6", &args) require.NoError(t, err) assert.EqualValues(t, args.UseIPv6, true) } func TestNumericFlagTakesPrecedence(t *testing.T) { var args struct { UseIPv6 bool `arg:"-6"` Foo int } err := parse("-foo -6", &args) require.Error(t, err) } func TestRepeatedNegativeInts(t *testing.T) { var args struct { Ints []int `arg:"--numbers"` } err := parse("--numbers -1 -2 -6", &args) require.NoError(t, err) assert.EqualValues(t, args.Ints, []int{-1, -2, -6}) } func TestRepeatedNegativeFloats(t *testing.T) { var args struct { Floats []float32 `arg:"--numbers"` } err := parse("--numbers -1 -2 -6", &args) require.NoError(t, err) assert.EqualValues(t, args.Floats, []float32{-1, -2, -6}) } func TestRepeatedNegativeFloatsThenNumericFlag(t *testing.T) { var args struct { Floats []float32 `arg:"--numbers"` UseIPv6 bool `arg:"-6"` } err := parse("--numbers -1 -2 -6", &args) require.NoError(t, err) assert.EqualValues(t, args.Floats, []float32{-1, -2}) assert.True(t, args.UseIPv6) } func TestRepeatedNegativeFloatsThenNonexistentFlag(t *testing.T) { var args struct { Floats []float32 `arg:"--numbers"` UseIPv6 bool `arg:"-6"` } err := parse("--numbers -1 -2 -n", &args) require.Error(t, err, "unknown argument -n") } func TestRepeatedNegativeIntsThenFloat(t *testing.T) { var args struct { Ints []int `arg:"--numbers"` } err := parse("--numbers -1 -2 -0.1", &args) require.Error(t, err, "unknown argument -0.1") } func TestNegativeIntAndFloatAndTricks(t *testing.T) { var args struct { Foo int Bar float64 N int `arg:"--100"` } err := parse("-foo -99 -bar -60.14 -100 -101", &args) require.NoError(t, err) assert.EqualValues(t, args.Foo, -99) assert.EqualValues(t, args.Bar, -60.14) assert.EqualValues(t, args.N, -101) } func TestUint(t *testing.T) { var args struct { Foo uint Ptr *uint } err := parse("--foo 7 --ptr 8", &args) require.NoError(t, err) assert.EqualValues(t, 7, args.Foo) assert.EqualValues(t, 8, *args.Ptr) } func TestFloat(t *testing.T) { var args struct { Foo float32 Ptr *float32 } err := parse("--foo 3.4 --ptr 3.5", &args) require.NoError(t, err) assert.EqualValues(t, 3.4, args.Foo) assert.EqualValues(t, 3.5, *args.Ptr) } func TestDuration(t *testing.T) { var args struct { Foo time.Duration Ptr *time.Duration } err := parse("--foo 3ms --ptr 4ms", &args) require.NoError(t, err) assert.Equal(t, 3*time.Millisecond, args.Foo) assert.Equal(t, 4*time.Millisecond, *args.Ptr) } func TestInvalidDuration(t *testing.T) { var args struct { Foo time.Duration } err := parse("--foo xxx", &args) require.Error(t, err) } func TestIntPtr(t *testing.T) { var args struct { Foo *int } err := parse("--foo 123", &args) require.NoError(t, err) require.NotNil(t, args.Foo) assert.Equal(t, 123, *args.Foo) } func TestIntPtrNotPresent(t *testing.T) { var args struct { Foo *int } err := parse("", &args) require.NoError(t, err) assert.Nil(t, args.Foo) } func TestMixed(t *testing.T) { var args struct { Foo string `arg:"-f"` Bar int Baz uint `arg:"positional"` Ham bool Spam float32 } args.Bar = 3 err := parse("123 -spam=1.2 -ham -f xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) assert.Equal(t, 3, args.Bar) assert.Equal(t, uint(123), args.Baz) assert.Equal(t, true, args.Ham) assert.EqualValues(t, 1.2, args.Spam) } func TestRequired(t *testing.T) { var args struct { Foo string `arg:"required"` } err := parse("", &args) require.Error(t, err, "--foo is required") } func TestRequiredWithEnv(t *testing.T) { var args struct { Foo string `arg:"required,env:FOO"` } err := parse("", &args) 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(Config{}, "", []string{}, &args) require.Error(t, err, "environment variable FOO is required") } func TestShortFlag(t *testing.T) { var args struct { Foo string `arg:"-f"` } err := parse("-f xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) err = parse("-foo xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) err = parse("--foo xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) } func TestInvalidShortFlag(t *testing.T) { var args struct { Foo string `arg:"-foo"` } err := parse("", &args) assert.Error(t, err) } func TestLongFlag(t *testing.T) { var args struct { Foo string `arg:"--abc"` } err := parse("-abc xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) err = parse("--abc xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) } func TestSlice(t *testing.T) { var args struct { Strings []string } err := parse("--strings a b c", &args) require.NoError(t, err) assert.Equal(t, []string{"a", "b", "c"}, args.Strings) } func TestSliceOfBools(t *testing.T) { var args struct { B []bool } err := parse("--b true false true", &args) require.NoError(t, err) assert.Equal(t, []bool{true, false, true}, args.B) } func TestMap(t *testing.T) { var args struct { Values map[string]int } err := parse("--values a=1 b=2 c=3", &args) require.NoError(t, err) assert.Len(t, args.Values, 3) assert.Equal(t, 1, args.Values["a"]) assert.Equal(t, 2, args.Values["b"]) assert.Equal(t, 3, args.Values["c"]) } func TestMapPositional(t *testing.T) { var args struct { Values map[string]int `arg:"positional"` } err := parse("a=1 b=2 c=3", &args) require.NoError(t, err) assert.Len(t, args.Values, 3) assert.Equal(t, 1, args.Values["a"]) assert.Equal(t, 2, args.Values["b"]) assert.Equal(t, 3, args.Values["c"]) } func TestMapWithSeparate(t *testing.T) { var args struct { Values map[string]int `arg:"separate"` } err := parse("--values a=1 --values b=2 --values c=3", &args) require.NoError(t, err) assert.Len(t, args.Values, 3) assert.Equal(t, 1, args.Values["a"]) assert.Equal(t, 2, args.Values["b"]) assert.Equal(t, 3, args.Values["c"]) } func TestPlaceholder(t *testing.T) { var args struct { Input string `arg:"positional" placeholder:"SRC"` Output []string `arg:"positional" placeholder:"DST"` Optimize int `arg:"-O" placeholder:"LEVEL"` MaxJobs int `arg:"-j" placeholder:"N"` } err := parse("-O 5 --maxjobs 2 src dest1 dest2", &args) assert.NoError(t, err) } func TestNoLongName(t *testing.T) { var args struct { ShortOnly string `arg:"-s,--"` EnvOnly string `arg:"--,env"` } setenv(t, "ENVONLY", "TestVal") err := parse("-s TestVal2", &args) assert.NoError(t, err) assert.Equal(t, "TestVal", args.EnvOnly) assert.Equal(t, "TestVal2", args.ShortOnly) } func TestCaseSensitive(t *testing.T) { var args struct { Lower bool `arg:"-v"` Upper bool `arg:"-V"` } err := parse("-v", &args) require.NoError(t, err) assert.True(t, args.Lower) assert.False(t, args.Upper) } func TestCaseSensitive2(t *testing.T) { var args struct { Lower bool `arg:"-v"` Upper bool `arg:"-V"` } err := parse("-V", &args) require.NoError(t, err) assert.False(t, args.Lower) assert.True(t, args.Upper) } func TestPositional(t *testing.T) { var args struct { Input string `arg:"positional"` Output string `arg:"positional"` } err := parse("foo", &args) require.NoError(t, err) assert.Equal(t, "foo", args.Input) assert.Equal(t, "", args.Output) } func TestPositionalPointer(t *testing.T) { var args struct { Input string `arg:"positional"` Output []*string `arg:"positional"` } err := parse("foo bar baz", &args) require.NoError(t, err) assert.Equal(t, "foo", args.Input) bar := "bar" baz := "baz" assert.Equal(t, []*string{&bar, &baz}, args.Output) } func TestRequiredPositional(t *testing.T) { var args struct { Input string `arg:"positional"` Output string `arg:"positional,required"` } err := parse("foo", &args) assert.Error(t, err) } func TestRequiredPositionalMultiple(t *testing.T) { var args struct { Input string `arg:"positional"` Multiple []string `arg:"positional,required"` } err := parse("foo", &args) assert.Error(t, err) } func TestTooManyPositional(t *testing.T) { var args struct { Input string `arg:"positional"` Output string `arg:"positional"` } err := parse("foo bar baz", &args) assert.Error(t, err) } func TestMultiple(t *testing.T) { var args struct { Foo []int Bar []string } err := parse("--foo 1 2 3 --bar x y z", &args) require.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, args.Foo) assert.Equal(t, []string{"x", "y", "z"}, args.Bar) } func TestMultiplePositionals(t *testing.T) { var args struct { Input string `arg:"positional"` Multiple []string `arg:"positional,required"` } err := parse("foo a b c", &args) assert.NoError(t, err) assert.Equal(t, "foo", args.Input) assert.Equal(t, []string{"a", "b", "c"}, args.Multiple) } func TestMultipleWithEq(t *testing.T) { var args struct { Foo []int Bar []string } err := parse("--foo 1 2 3 --bar=x", &args) require.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, args.Foo) assert.Equal(t, []string{"x"}, args.Bar) } func TestMultipleWithDefault(t *testing.T) { var args struct { Foo []int Bar []string } args.Foo = []int{42} args.Bar = []string{"foo"} err := parse("--foo 1 2 3 --bar x y z", &args) require.NoError(t, err) assert.Equal(t, []int{1, 2, 3}, args.Foo) assert.Equal(t, []string{"x", "y", "z"}, args.Bar) } func TestExemptField(t *testing.T) { var args struct { Foo string Bar interface{} `arg:"-"` } err := parse("--foo xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.Foo) } func TestUnknownField(t *testing.T) { var args struct { Foo string } err := parse("--bar xyz", &args) assert.Error(t, err) } func TestMissingRequired(t *testing.T) { var args struct { Foo string `arg:"required"` X []string `arg:"positional"` } err := parse("x", &args) assert.Error(t, err) } func TestNonsenseKey(t *testing.T) { var args struct { X []string `arg:"positional, nonsense"` } err := parse("x", &args) assert.Error(t, err) } func TestMissingValueAtEnd(t *testing.T) { var args struct { Foo string } err := parse("--foo", &args) assert.Error(t, err) } func TestMissingValueInMiddle(t *testing.T) { var args struct { Foo string Bar string } err := parse("--foo --bar=abc", &args) assert.Error(t, err) } func TestInvalidInt(t *testing.T) { var args struct { Foo int } err := parse("--foo=xyz", &args) assert.Error(t, err) } func TestInvalidUint(t *testing.T) { var args struct { Foo uint } err := parse("--foo=xyz", &args) assert.Error(t, err) } func TestInvalidFloat(t *testing.T) { var args struct { Foo float64 } err := parse("--foo xyz", &args) require.Error(t, err) } func TestInvalidBool(t *testing.T) { var args struct { Foo bool } err := parse("--foo=xyz", &args) require.Error(t, err) } func TestInvalidIntSlice(t *testing.T) { var args struct { Foo []int } err := parse("--foo 1 2 xyz", &args) require.Error(t, err) } func TestInvalidPositional(t *testing.T) { var args struct { Foo int `arg:"positional"` } err := parse("xyz", &args) require.Error(t, err) } func TestInvalidPositionalSlice(t *testing.T) { var args struct { Foo []int `arg:"positional"` } err := parse("1 2 xyz", &args) require.Error(t, err) } func TestNoMoreOptions(t *testing.T) { var args struct { Foo string Bar []string `arg:"positional"` } err := parse("abc -- --foo xyz", &args) require.NoError(t, err) assert.Equal(t, "", args.Foo) assert.Equal(t, []string{"abc", "--foo", "xyz"}, args.Bar) } func TestNoMoreOptionsBeforeHelp(t *testing.T) { var args struct { Foo int } err := parse("not_an_integer -- --help", &args) assert.NotEqual(t, ErrHelp, err) } func TestNoMoreOptionsTwice(t *testing.T) { var args struct { X []string `arg:"positional"` } err := parse("-- --", &args) require.NoError(t, err) assert.Equal(t, []string{"--"}, args.X) } func TestHelpFlag(t *testing.T) { var args struct { Foo string Bar interface{} `arg:"-"` } err := parse("--help", &args) assert.Equal(t, ErrHelp, err) } func TestPanicOnNonPointer(t *testing.T) { var args struct{} assert.Panics(t, func() { _ = parse("", args) }) } func TestErrorOnNonStruct(t *testing.T) { var args string err := parse("", &args) assert.Error(t, err) } func TestUnsupportedType(t *testing.T) { var args struct { Foo interface{} } err := parse("--foo", &args) assert.Error(t, err) } func TestUnsupportedSliceElement(t *testing.T) { var args struct { Foo []interface{} } err := parse("--foo 3", &args) assert.Error(t, err) } func TestUnsupportedSliceElementMissingValue(t *testing.T) { var args struct { Foo []interface{} } err := parse("--foo", &args) assert.Error(t, err) } func TestUnknownTag(t *testing.T) { var args struct { Foo string `arg:"this_is_not_valid"` } err := parse("--foo xyz", &args) assert.Error(t, err) } func TestParse(t *testing.T) { var args struct { Foo string } os.Args = []string{"example", "--foo", "bar"} err := Parse(&args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestParseError(t *testing.T) { var args struct { Foo string `arg:"this_is_not_valid"` } os.Args = []string{"example", "--bar"} err := Parse(&args) assert.Error(t, err) } func TestMustParse(t *testing.T) { var args struct { Foo string } os.Args = []string{"example", "--foo", "bar"} parser := MustParse(&args) assert.Equal(t, "bar", args.Foo) assert.NotNil(t, parser) } func TestMustParseError(t *testing.T) { var args struct { Foo []string `default:""` } var exitCode int var stdout bytes.Buffer mustParseExit = func(code int) { exitCode = code } mustParseOut = &stdout os.Args = []string{"example"} parser := MustParse(&args) assert.Nil(t, parser) assert.Equal(t, 2, exitCode) assert.Contains(t, stdout.String(), "default values are not supported for slice or map fields") } func TestEnvironmentVariable(t *testing.T) { var args struct { Foo string `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestEnvironmentVariableNotPresent(t *testing.T) { var args struct { NotPresent string `arg:"env"` } _, err := parseWithEnv(Config{}, "", nil, &args) require.NoError(t, err) assert.Equal(t, "", args.NotPresent) } func TestEnvironmentVariableOverrideName(t *testing.T) { var args struct { Foo string `arg:"env:BAZ"` } _, err := parseWithEnv(Config{}, "", []string{"BAZ=bar"}, &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestEnvironmentVariableOverrideArgument(t *testing.T) { var args struct { Foo string `arg:"env"` } _, err := parseWithEnv(Config{}, "--foo zzz", []string{"FOO=bar"}, &args) require.NoError(t, err) assert.Equal(t, "zzz", args.Foo) } func TestEnvironmentVariableError(t *testing.T) { var args struct { Foo int `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args) assert.Error(t, err) } func TestEnvironmentVariableRequired(t *testing.T) { var args struct { Foo string `arg:"env,required"` } _, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestEnvironmentVariableSliceArgumentString(t *testing.T) { var args struct { Foo []string `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=bar,"baz, qux"`}, &args) require.NoError(t, err) assert.Equal(t, []string{"bar", "baz, qux"}, args.Foo) } func TestEnvironmentVariableSliceEmpty(t *testing.T) { var args struct { Foo []string `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args) require.NoError(t, err) assert.Len(t, args.Foo, 0) } func TestEnvironmentVariableSliceArgumentInteger(t *testing.T) { var args struct { Foo []int `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=1,99`}, &args) require.NoError(t, err) assert.Equal(t, []int{1, 99}, args.Foo) } func TestEnvironmentVariableSliceArgumentFloat(t *testing.T) { var args struct { Foo []float32 `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=1.1,99.9`}, &args) require.NoError(t, err) assert.Equal(t, []float32{1.1, 99.9}, args.Foo) } func TestEnvironmentVariableSliceArgumentBool(t *testing.T) { var args struct { Foo []bool `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=true,false,0,1`}, &args) require.NoError(t, err) assert.Equal(t, []bool{true, false, false, true}, args.Foo) } func TestEnvironmentVariableSliceArgumentWrongCsv(t *testing.T) { var args struct { Foo []int `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=1,99\"`}, &args) assert.Error(t, err) } func TestEnvironmentVariableSliceArgumentWrongType(t *testing.T) { var args struct { Foo []bool `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=one,two`}, &args) assert.Error(t, err) } func TestEnvironmentVariableMap(t *testing.T) { var args struct { Foo map[int]string `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=1=one,99=ninetynine`}, &args) require.NoError(t, err) assert.Len(t, args.Foo, 2) assert.Equal(t, "one", args.Foo[1]) assert.Equal(t, "ninetynine", args.Foo[99]) } func TestEnvironmentVariableEmptyMap(t *testing.T) { var args struct { Foo map[int]string `arg:"env"` } _, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args) require.NoError(t, err) assert.Len(t, args.Foo, 0) } func TestEnvironmentVariableWithPrefix(t *testing.T) { var args struct { Foo string `arg:"env"` } _, err := parseWithEnv(Config{EnvPrefix: "MYAPP_"}, "", []string{"MYAPP_FOO=bar"}, &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestEnvironmentVariableIgnored(t *testing.T) { var args struct { Foo string `arg:"env"` } setenv(t, "FOO", "abc") p, err := NewParser(Config{IgnoreEnv: true}, &args) require.NoError(t, err) err = p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "", args.Foo) } func TestDefaultValuesIgnored(t *testing.T) { var args struct { Foo string `default:"bad"` } p, err := NewParser(Config{IgnoreDefault: true}, &args) require.NoError(t, err) err = p.Parse(nil) assert.NoError(t, err) assert.Equal(t, "", args.Foo) } func TestRequiredEnvironmentOnlyVariableIsMissing(t *testing.T) { var args struct { Foo string `arg:"required,--,env:FOO"` } _, err := parseWithEnv(Config{}, "", []string{""}, &args) assert.Error(t, err) } func TestOptionalEnvironmentOnlyVariable(t *testing.T) { var args struct { Foo string `arg:"env:FOO"` } _, err := parseWithEnv(Config{}, "", []string{}, &args) assert.NoError(t, err) } func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) { var args struct { Sub *struct { Foo string `arg:"env"` } `arg:"subcommand"` } setenv(t, "FOO", "abc") p, err := NewParser(Config{IgnoreEnv: true}, &args) require.NoError(t, err) err = p.Parse([]string{"sub"}) require.NoError(t, err) require.NotNil(t, args.Sub) assert.Equal(t, "", args.Sub.Foo) } func TestParserMustParseEmptyArgs(t *testing.T) { // this mirrors TestEmptyArgs p, err := NewParser(Config{}, &struct{}{}) require.NoError(t, err) assert.NotNil(t, p) p.MustParse(nil) } func TestParserMustParse(t *testing.T) { tests := []struct { name string args versioned cmdLine []string code int output string }{ {name: "help", args: struct{}{}, cmdLine: []string{"--help"}, code: 0, output: "display this help and exit"}, {name: "version", args: versioned{}, cmdLine: []string{"--version"}, code: 0, output: "example 3.2.1"}, {name: "invalid", args: struct{}{}, cmdLine: []string{"invalid"}, code: 2, output: ""}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { var exitCode int var stdout bytes.Buffer exit := func(code int) { exitCode = code } p, err := NewParser(Config{Exit: exit, Out: &stdout}, &tt.args) require.NoError(t, err) assert.NotNil(t, p) p.MustParse(tt.cmdLine) assert.NotNil(t, exitCode) assert.Equal(t, tt.code, exitCode) assert.Contains(t, stdout.String(), tt.output) }) } } type textUnmarshaler struct { val int } func (f *textUnmarshaler) UnmarshalText(b []byte) error { f.val = len(b) return nil } func TestTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo textUnmarshaler } err := parse("--foo abc", &args) require.NoError(t, err) assert.Equal(t, 3, args.Foo.val) } func TestPtrToTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo *textUnmarshaler } err := parse("--foo abc", &args) require.NoError(t, err) assert.Equal(t, 3, args.Foo.val) } func TestRepeatedTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo []textUnmarshaler } err := parse("--foo abc d ef", &args) require.NoError(t, err) require.Len(t, args.Foo, 3) assert.Equal(t, 3, args.Foo[0].val) assert.Equal(t, 1, args.Foo[1].val) assert.Equal(t, 2, args.Foo[2].val) } func TestRepeatedPtrToTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo []*textUnmarshaler } err := parse("--foo abc d ef", &args) require.NoError(t, err) require.Len(t, args.Foo, 3) assert.Equal(t, 3, args.Foo[0].val) assert.Equal(t, 1, args.Foo[1].val) assert.Equal(t, 2, args.Foo[2].val) } func TestPositionalTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo []textUnmarshaler `arg:"positional"` } err := parse("abc d ef", &args) require.NoError(t, err) require.Len(t, args.Foo, 3) assert.Equal(t, 3, args.Foo[0].val) assert.Equal(t, 1, args.Foo[1].val) assert.Equal(t, 2, args.Foo[2].val) } func TestPositionalPtrToTextUnmarshaler(t *testing.T) { // fields that implement TextUnmarshaler should be parsed using that interface var args struct { Foo []*textUnmarshaler `arg:"positional"` } err := parse("abc d ef", &args) require.NoError(t, err) require.Len(t, args.Foo, 3) assert.Equal(t, 3, args.Foo[0].val) assert.Equal(t, 1, args.Foo[1].val) assert.Equal(t, 2, args.Foo[2].val) } type boolUnmarshaler bool func (p *boolUnmarshaler) UnmarshalText(b []byte) error { *p = len(b)%2 == 0 return nil } func TestBoolUnmarhsaler(t *testing.T) { // test that a bool type that implements TextUnmarshaler is // handled as a TextUnmarshaler not as a bool var args struct { Foo *boolUnmarshaler } err := parse("--foo ab", &args) require.NoError(t, err) assert.EqualValues(t, true, *args.Foo) } type sliceUnmarshaler []int func (p *sliceUnmarshaler) UnmarshalText(b []byte) error { *p = sliceUnmarshaler{len(b)} return nil } func TestSliceUnmarhsaler(t *testing.T) { // test that a slice type that implements TextUnmarshaler is // handled as a TextUnmarshaler not as a slice var args struct { Foo *sliceUnmarshaler Bar string `arg:"positional"` } err := parse("--foo abcde xyz", &args) require.NoError(t, err) require.Len(t, *args.Foo, 1) assert.EqualValues(t, 5, (*args.Foo)[0]) assert.Equal(t, "xyz", args.Bar) } func TestIP(t *testing.T) { var args struct { Host net.IP } err := parse("--host 192.168.0.1", &args) require.NoError(t, err) assert.Equal(t, "192.168.0.1", args.Host.String()) } func TestPtrToIP(t *testing.T) { var args struct { Host *net.IP } err := parse("--host 192.168.0.1", &args) require.NoError(t, err) assert.Equal(t, "192.168.0.1", args.Host.String()) } func TestURL(t *testing.T) { var args struct { URL url.URL } err := parse("--url https://example.com/get?item=xyz", &args) require.NoError(t, err) assert.Equal(t, "https://example.com/get?item=xyz", args.URL.String()) } func TestPtrToURL(t *testing.T) { var args struct { URL *url.URL } err := parse("--url http://example.com/#xyz", &args) require.NoError(t, err) assert.Equal(t, "http://example.com/#xyz", args.URL.String()) } func TestIPSlice(t *testing.T) { var args struct { Host []net.IP } err := parse("--host 192.168.0.1 127.0.0.1", &args) require.NoError(t, err) require.Len(t, args.Host, 2) assert.Equal(t, "192.168.0.1", args.Host[0].String()) assert.Equal(t, "127.0.0.1", args.Host[1].String()) } func TestInvalidIPAddress(t *testing.T) { var args struct { Host net.IP } err := parse("--host xxx", &args) assert.Error(t, err) } func TestMAC(t *testing.T) { var args struct { Host net.HardwareAddr } err := parse("--host 0123.4567.89ab", &args) require.NoError(t, err) assert.Equal(t, "01:23:45:67:89:ab", args.Host.String()) } func TestInvalidMac(t *testing.T) { var args struct { Host net.HardwareAddr } err := parse("--host xxx", &args) assert.Error(t, err) } func TestMailAddr(t *testing.T) { var args struct { Recipient mail.Address } err := parse("--recipient foo@example.com", &args) require.NoError(t, err) assert.Equal(t, "", args.Recipient.String()) } func TestInvalidMailAddr(t *testing.T) { var args struct { Recipient mail.Address } err := parse("--recipient xxx", &args) assert.Error(t, err) } type A struct { X string } type B struct { Y int } func TestEmbedded(t *testing.T) { var args struct { A B Z bool } err := parse("--x=hello --y=321 --z", &args) require.NoError(t, err) assert.Equal(t, "hello", args.X) assert.Equal(t, 321, args.Y) assert.Equal(t, true, args.Z) } func TestEmbeddedPtr(t *testing.T) { // embedded pointer fields are not supported so this should return an error var args struct { *A } err := parse("--x=hello", &args) require.Error(t, err) } func TestEmbeddedPtrIgnored(t *testing.T) { // embedded pointer fields are not normally supported but here // we explicitly exclude it so the non-nil embedded structs // should work as expected var args struct { *A `arg:"-"` B } err := parse("--y=321", &args) require.NoError(t, err) assert.Equal(t, 321, args.Y) } func TestEmbeddedWithDuplicateField(t *testing.T) { // see https://github.com/alexflint/go-arg/issues/100 type T struct { A string `arg:"--cat"` } type U struct { A string `arg:"--dog"` } var args struct { T U } err := parse("--cat=cat --dog=dog", &args) require.NoError(t, err) assert.Equal(t, "cat", args.T.A) assert.Equal(t, "dog", args.U.A) } func TestEmbeddedWithDuplicateField2(t *testing.T) { // see https://github.com/alexflint/go-arg/issues/100 type T struct { A string } type U struct { A string } var args struct { T U } err := parse("--a=xyz", &args) require.NoError(t, err) assert.Equal(t, "xyz", args.T.A) assert.Equal(t, "", args.U.A) } func TestUnexportedEmbedded(t *testing.T) { type embeddedArgs struct { Foo string } var args struct { embeddedArgs } err := parse("--foo bar", &args) require.NoError(t, err) assert.Equal(t, "bar", args.Foo) } func TestIgnoredEmbedded(t *testing.T) { type embeddedArgs struct { Foo string } var args struct { embeddedArgs `arg:"-"` } err := parse("--foo bar", &args) require.Error(t, err) } func TestEmptyArgs(t *testing.T) { origArgs := os.Args // test what happens if somehow os.Args is empty os.Args = nil var args struct { Foo string } MustParse(&args) // put the original arguments back os.Args = origArgs } func TestTooManyHyphens(t *testing.T) { var args struct { TooManyHyphens string `arg:"---x"` } err := parse("--foo -", &args) assert.Error(t, err) } func TestHyphenAsOption(t *testing.T) { var args struct { Foo string } err := parse("--foo -", &args) require.NoError(t, err) assert.Equal(t, "-", args.Foo) } func TestHyphenAsPositional(t *testing.T) { var args struct { Foo string `arg:"positional"` } err := parse("-", &args) require.NoError(t, err) assert.Equal(t, "-", args.Foo) } func TestHyphenInMultiOption(t *testing.T) { var args struct { Foo []string Bar int } err := parse("--foo --- x - y --bar 3", &args) require.NoError(t, err) assert.Equal(t, []string{"---", "x", "-", "y"}, args.Foo) assert.Equal(t, 3, args.Bar) } func TestHyphenInMultiPositional(t *testing.T) { var args struct { Foo []string `arg:"positional"` } err := parse("--- x - y", &args) require.NoError(t, err) assert.Equal(t, []string{"---", "x", "-", "y"}, args.Foo) } func TestSeparate(t *testing.T) { for _, val := range []string{"-f one", "-f=one", "--foo one", "--foo=one"} { var args struct { Foo []string `arg:"--foo,-f,separate"` } err := parse(val, &args) require.NoError(t, err) assert.Equal(t, []string{"one"}, args.Foo) } } func TestSeparateWithDefault(t *testing.T) { args := struct { Foo []string `arg:"--foo,-f,separate"` }{ Foo: []string{"default"}, } err := parse("-f one -f=two", &args) require.NoError(t, err) assert.Equal(t, []string{"default", "one", "two"}, args.Foo) } func TestSeparateWithPositional(t *testing.T) { var args struct { Foo []string `arg:"--foo,-f,separate"` Bar string `arg:"positional"` Moo string `arg:"positional"` } err := parse("zzz --foo one -f=two --foo=three -f four aaa", &args) require.NoError(t, err) assert.Equal(t, []string{"one", "two", "three", "four"}, args.Foo) assert.Equal(t, "zzz", args.Bar) assert.Equal(t, "aaa", args.Moo) } func TestSeparatePositionalInterweaved(t *testing.T) { var args struct { Foo []string `arg:"--foo,-f,separate"` Bar []string `arg:"--bar,-b,separate"` Pre string `arg:"positional"` Post []string `arg:"positional"` } err := parse("zzz -f foo1 -b=bar1 --foo=foo2 -b bar2 post1 -b bar3 post2 post3", &args) require.NoError(t, err) assert.Equal(t, []string{"foo1", "foo2"}, args.Foo) assert.Equal(t, []string{"bar1", "bar2", "bar3"}, args.Bar) assert.Equal(t, "zzz", args.Pre) assert.Equal(t, []string{"post1", "post2", "post3"}, args.Post) } func TestSpacesAllowedInTags(t *testing.T) { var args struct { Foo []string `arg:"--foo, -f, separate, required, help:quite nice really"` } err := parse("--foo one -f=two --foo=three -f four", &args) require.NoError(t, err) assert.Equal(t, []string{"one", "two", "three", "four"}, args.Foo) } func TestReuseParser(t *testing.T) { var args struct { Foo string `arg:"required"` } p, err := NewParser(Config{}, &args) require.NoError(t, err) err = p.Parse([]string{"--foo=abc"}) require.NoError(t, err) assert.Equal(t, args.Foo, "abc") err = p.Parse([]string{}) assert.Error(t, err) } func TestNoVersion(t *testing.T) { var args struct{} p, err := NewParser(Config{}, &args) require.NoError(t, err) err = p.Parse([]string{"--version"}) assert.Error(t, err) assert.NotEqual(t, ErrVersion, err) } func TestBuiltinVersion(t *testing.T) { var args struct{} p, err := NewParser(Config{}, &args) require.NoError(t, err) p.version = "example 3.2.1" err = p.Parse([]string{"--version"}) assert.Equal(t, ErrVersion, err) } func TestArgsVersion(t *testing.T) { var args struct { Version bool `arg:"--version"` } p, err := NewParser(Config{}, &args) require.NoError(t, err) err = p.Parse([]string{"--version"}) require.NoError(t, err) require.Equal(t, args.Version, true) } func TestArgsAndBuiltinVersion(t *testing.T) { var args struct { Version bool `arg:"--version"` } p, err := NewParser(Config{}, &args) require.NoError(t, err) p.version = "example 3.2.1" err = p.Parse([]string{"--version"}) require.NoError(t, err) require.Equal(t, args.Version, true) } func TestMultipleTerminates(t *testing.T) { var args struct { X []string Y string `arg:"positional"` } err := parse("--x a b -- c", &args) require.NoError(t, err) assert.Equal(t, []string{"a", "b"}, args.X) assert.Equal(t, "c", args.Y) } func TestDefaultOptionValues(t *testing.T) { var args struct { A int `default:"123"` B *int `default:"123"` C string `default:"abc"` D *string `default:"abc"` E float64 `default:"1.23"` F *float64 `default:"1.23"` G bool `default:"true"` H *bool `default:"true"` } err := parse("--c=xyz --e=4.56", &args) require.NoError(t, err) assert.Equal(t, 123, args.A) if assert.NotNil(t, args.B) { assert.Equal(t, 123, *args.B) } assert.Equal(t, "xyz", args.C) if assert.NotNil(t, args.D) { assert.Equal(t, "abc", *args.D) } assert.Equal(t, 4.56, args.E) if assert.NotNil(t, args.F) { assert.Equal(t, 1.23, *args.F) } assert.True(t, args.G) if assert.NotNil(t, args.H) { assert.True(t, *args.H) } } func TestDefaultUnparseable(t *testing.T) { var args struct { A int `default:"x"` } err := parse("", &args) assert.EqualError(t, err, `.A: error processing default value: strconv.ParseInt: parsing "x": invalid syntax`) } func TestDefaultPositionalValues(t *testing.T) { var args struct { A int `arg:"positional" default:"123"` B *int `arg:"positional" default:"123"` C string `arg:"positional" default:"abc"` D *string `arg:"positional" default:"abc"` E float64 `arg:"positional" default:"1.23"` F *float64 `arg:"positional" default:"1.23"` G bool `arg:"positional" default:"true"` H *bool `arg:"positional" default:"true"` } err := parse("456 789", &args) require.NoError(t, err) assert.Equal(t, 456, args.A) if assert.NotNil(t, args.B) { assert.Equal(t, 789, *args.B) } assert.Equal(t, "abc", args.C) if assert.NotNil(t, args.D) { assert.Equal(t, "abc", *args.D) } assert.Equal(t, 1.23, args.E) if assert.NotNil(t, args.F) { assert.Equal(t, 1.23, *args.F) } assert.True(t, args.G) if assert.NotNil(t, args.H) { assert.True(t, *args.H) } } func TestDefaultValuesNotAllowedWithRequired(t *testing.T) { var args struct { A int `arg:"required" default:"123"` // required not allowed with default! } err := parse("", &args) assert.EqualError(t, err, ".A: 'required' cannot be used when a default value is specified") } func TestDefaultValuesNotAllowedWithSlice(t *testing.T) { var args struct { A []int `default:"invalid"` // default values not allowed with slices } err := parse("", &args) assert.EqualError(t, err, ".A: default values are not supported for slice or map fields") } func TestUnexportedFieldsSkipped(t *testing.T) { var args struct { unexported struct{} } _, err := NewParser(Config{}, &args) require.NoError(t, err) } func TestMustParseInvalidParser(t *testing.T) { var exitCode int var stdout bytes.Buffer exit := func(code int) { exitCode = code } var args struct { CannotParse struct{} } parser := mustParse(Config{Out: &stdout, Exit: exit}, &args) assert.Nil(t, parser) assert.Equal(t, 2, exitCode) } func TestMustParsePrintsHelp(t *testing.T) { originalArgs := os.Args defer func() { os.Args = originalArgs }() os.Args = []string{"someprogram", "--help"} var exitCode int var stdout bytes.Buffer exit := func(code int) { exitCode = code } var args struct{} parser := mustParse(Config{Out: &stdout, Exit: exit}, &args) assert.NotNil(t, parser) assert.Equal(t, 0, exitCode) } func TestMustParsePrintsVersion(t *testing.T) { originalArgs := os.Args defer func() { os.Args = originalArgs }() var exitCode int var stdout bytes.Buffer exit := func(code int) { exitCode = code } os.Args = []string{"someprogram", "--version"} var args versioned parser := mustParse(Config{Out: &stdout, Exit: exit}, &args) require.NotNil(t, parser) assert.Equal(t, 0, exitCode) assert.Equal(t, "example 3.2.1\n", stdout.String()) } type mapWithUnmarshalText struct { val map[string]string } func (v *mapWithUnmarshalText) UnmarshalText(data []byte) error { return json.Unmarshal(data, &v.val) } func TestTextUnmarshalerEmpty(t *testing.T) { // based on https://github.com/alexflint/go-arg/issues/184 var args struct { Config mapWithUnmarshalText `arg:"--config"` } err := parse("", &args) require.NoError(t, err) assert.Empty(t, args.Config) } func TestTextUnmarshalerEmptyPointer(t *testing.T) { // a slight variant on https://github.com/alexflint/go-arg/issues/184 var args struct { Config *mapWithUnmarshalText `arg:"--config"` } err := parse("", &args) require.NoError(t, err) assert.Nil(t, args.Config) } // similar to the above but also implements MarshalText type mapWithMarshalText struct { val map[string]string } func (v *mapWithMarshalText) MarshalText(data []byte) error { return json.Unmarshal(data, &v.val) } func (v *mapWithMarshalText) UnmarshalText(data []byte) error { return json.Unmarshal(data, &v.val) } func TestTextMarshalerUnmarshalerEmpty(t *testing.T) { // based on https://github.com/alexflint/go-arg/issues/184 var args struct { Config mapWithMarshalText `arg:"--config"` } err := parse("", &args) require.NoError(t, err) assert.Empty(t, args.Config) } func TestTextMarshalerUnmarshalerEmptyPointer(t *testing.T) { // a slight variant on https://github.com/alexflint/go-arg/issues/184 var args struct { Config *mapWithMarshalText `arg:"--config"` } err := parse("", &args) require.NoError(t, err) assert.Nil(t, args.Config) } func TestSubcommandGlobalFlag_Before(t *testing.T) { var args struct { Global bool `arg:"-g"` Sub *struct { } `arg:"subcommand"` } p, err := NewParser(Config{StrictSubcommands: false}, &args) require.NoError(t, err) err = p.Parse([]string{"-g", "sub"}) assert.NoError(t, err) assert.True(t, args.Global) } func TestSubcommandGlobalFlag_InCommand(t *testing.T) { var args struct { Global bool `arg:"-g"` Sub *struct { } `arg:"subcommand"` } p, err := NewParser(Config{StrictSubcommands: false}, &args) require.NoError(t, err) err = p.Parse([]string{"sub", "-g"}) assert.NoError(t, err) assert.True(t, args.Global) } func TestSubcommandGlobalFlag_Before_Strict(t *testing.T) { var args struct { Global bool `arg:"-g"` Sub *struct { } `arg:"subcommand"` } p, err := NewParser(Config{StrictSubcommands: true}, &args) require.NoError(t, err) err = p.Parse([]string{"-g", "sub"}) assert.NoError(t, err) assert.True(t, args.Global) } func TestSubcommandGlobalFlag_InCommand_Strict(t *testing.T) { var args struct { Global bool `arg:"-g"` Sub *struct { } `arg:"subcommand"` } p, err := NewParser(Config{StrictSubcommands: true}, &args) require.NoError(t, err) err = p.Parse([]string{"sub", "-g"}) assert.Error(t, err) } func TestSubcommandGlobalFlag_InCommand_Strict_Inner(t *testing.T) { var args struct { Global bool `arg:"-g"` Sub *struct { Guard bool `arg:"-g"` } `arg:"subcommand"` } p, err := NewParser(Config{StrictSubcommands: true}, &args) require.NoError(t, err) err = p.Parse([]string{"sub", "-g"}) require.NoError(t, err) assert.False(t, args.Global) require.NotNil(t, args.Sub) assert.True(t, args.Sub.Guard) } func TestExitFunctionAndOutStreamGetFilledIn(t *testing.T) { var args struct{} p, err := NewParser(Config{}, &args) require.NoError(t, err) assert.NotNil(t, p.config.Exit) // go prohibits function pointer comparison assert.Equal(t, p.config.Out, os.Stdout) } go-arg-1.6.0/parse.go0000664000175000017510000006145015014334473013676 0ustar nileshnileshpackage arg import ( "encoding" "encoding/csv" "errors" "fmt" "io" "os" "path/filepath" "reflect" "strings" scalar "github.com/alexflint/go-scalar" ) // path represents a sequence of steps to find the output location for an // argument or subcommand in the final destination struct type path struct { root int // index of the destination struct fields []reflect.StructField // sequence of struct fields to traverse } // String gets a string representation of the given path func (p path) String() string { s := "args" for _, f := range p.fields { s += "." + f.Name } return s } // Child gets a new path representing a child of this path. func (p path) Child(f reflect.StructField) path { // copy the entire slice of fields to avoid possible slice overwrite subfields := make([]reflect.StructField, len(p.fields)+1) copy(subfields, p.fields) subfields[len(subfields)-1] = f return path{ root: p.root, fields: subfields, } } // spec represents a command line option type spec struct { dest path field reflect.StructField // the struct field from which this option was created long string // the --long form for this option, or empty if none short string // the -s short form for this option, or empty if none cardinality cardinality // determines how many tokens will be present (possible values: zero, one, multiple) required bool // if true, this option must be present on the command line positional bool // if true, this option will be looked for in the positional flags separate bool // if true, each slice and map entry will have its own --flag help string // the help text for this option env string // the name of the environment variable for this option, or empty for none defaultValue reflect.Value // default value for this option defaultString string // default value for this option, in string form to be displayed in help text placeholder string // placeholder string in help } // command represents a named subcommand, or the top-level command type command struct { name string aliases []string help string dest path specs []*spec subcommands []*command parent *command } // ErrHelp indicates that the builtin -h or --help were provided var ErrHelp = errors.New("help requested by user") // ErrVersion indicates that the builtin --version was provided var ErrVersion = errors.New("version requested by user") // for monkey patching in example and test code var mustParseExit = os.Exit var mustParseOut io.Writer = os.Stdout // MustParse processes command line arguments and exits upon failure func MustParse(dest ...interface{}) *Parser { return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, dest...) } // mustParse is a helper that facilitates testing func mustParse(config Config, dest ...interface{}) *Parser { p, err := NewParser(config, dest...) if err != nil { fmt.Fprintln(config.Out, err) config.Exit(2) return nil } p.MustParse(flags()) return p } // Parse processes command line arguments and stores them in dest func Parse(dest ...interface{}) error { p, err := NewParser(Config{}, dest...) if err != nil { return err } return p.Parse(flags()) } // flags gets all command line arguments other than the first (program name) func flags() []string { if len(os.Args) == 0 { // os.Args could be empty return nil } return os.Args[1:] } // Config represents configuration options for an argument parser type Config struct { // Program is the name of the program used in the help text Program string // IgnoreEnv instructs the library not to read environment variables IgnoreEnv bool // IgnoreDefault instructs the library not to reset the variables to the // default values, including pointers to sub commands IgnoreDefault bool // StrictSubcommands intructs the library not to allow global commands after // subcommand StrictSubcommands bool // EnvPrefix instructs the library to use a name prefix when reading environment variables. EnvPrefix string // Exit is called to terminate the process with an error code (defaults to os.Exit) Exit func(int) // Out is where help text, usage text, and failure messages are printed (defaults to os.Stdout) Out io.Writer } // Parser represents a set of command line options with destination values type Parser struct { cmd *command roots []reflect.Value config Config version string description string epilogue string // the following field changes during processing of command line arguments subcommand []string } // Versioned is the interface that the destination struct should implement to // make a version string appear at the top of the help message. type Versioned interface { // Version returns the version string that will be printed on a line by itself // at the top of the help message. Version() string } // Described is the interface that the destination struct should implement to // make a description string appear at the top of the help message. type Described interface { // Description returns the string that will be printed on a line by itself // at the top of the help message. Description() string } // Epilogued is the interface that the destination struct should implement to // add an epilogue string at the bottom of the help message. type Epilogued interface { // Epilogue returns the string that will be printed on a line by itself // at the end of the help message. Epilogue() string } // walkFields calls a function for each field of a struct, recursively expanding struct fields. func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) { walkFieldsImpl(t, visit, nil) } func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool, path []int) { for i := 0; i < t.NumField(); i++ { field := t.Field(i) field.Index = make([]int, len(path)+1) copy(field.Index, append(path, i)) expand := visit(field, t) if expand && field.Type.Kind() == reflect.Struct { var subpath []int if field.Anonymous { subpath = append(path, i) } walkFieldsImpl(field.Type, visit, subpath) } } } // NewParser constructs a parser from a list of destination structs func NewParser(config Config, dests ...interface{}) (*Parser, error) { // fill in defaults if config.Exit == nil { config.Exit = os.Exit } if config.Out == nil { config.Out = os.Stdout } // first pick a name for the command for use in the usage text var name string switch { case config.Program != "": name = config.Program case len(os.Args) > 0: name = filepath.Base(os.Args[0]) default: name = "program" } // construct a parser p := Parser{ cmd: &command{name: name}, config: config, } // make a list of roots for _, dest := range dests { p.roots = append(p.roots, reflect.ValueOf(dest)) } // process each of the destination values for i, dest := range dests { t := reflect.TypeOf(dest) if t.Kind() != reflect.Ptr { panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t)) } cmd, err := cmdFromStruct(name, path{root: i}, t, config.EnvPrefix) if err != nil { return nil, err } // for backwards compatibility, add nonzero field values as defaults // this applies only to the top-level command, not to subcommands (this inconsistency // is the reason that this method for setting default values was deprecated) for _, spec := range cmd.specs { // get the value v := p.val(spec.dest) // if the value is the "zero value" (e.g. nil pointer, empty struct) then ignore if isZero(v) { continue } // store as a default spec.defaultValue = v // we need a string to display in help text // if MarshalText is implemented then use that if m, ok := v.Interface().(encoding.TextMarshaler); ok { s, err := m.MarshalText() if err != nil { return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err) } spec.defaultString = string(s) } else { spec.defaultString = fmt.Sprintf("%v", v) } } p.cmd.specs = append(p.cmd.specs, cmd.specs...) p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...) if dest, ok := dest.(Versioned); ok { p.version = dest.Version() } if dest, ok := dest.(Described); ok { p.description = dest.Description() } if dest, ok := dest.(Epilogued); ok { p.epilogue = dest.Epilogue() } } // Set the parent of the subcommands to be the top-level command // to make sure that global options work when there is more than one // dest supplied. for _, subcommand := range p.cmd.subcommands { subcommand.parent = p.cmd } return &p, nil } func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*command, error) { // commands can only be created from pointers to structs if t.Kind() != reflect.Ptr { return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s", dest, t.Kind()) } t = t.Elem() if t.Kind() != reflect.Struct { return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a pointer to %s", dest, t.Kind()) } cmd := command{ name: name, dest: dest, } var errs []string walkFields(t, func(field reflect.StructField, t reflect.Type) bool { // check for the ignore switch in the tag tag := field.Tag.Get("arg") if tag == "-" { return false } // if this is an embedded struct then recurse into its fields, even if // it is unexported, because exported fields on unexported embedded // structs are still writable if field.Anonymous && field.Type.Kind() == reflect.Struct { return true } // ignore any other unexported field if !isExported(field.Name) { return false } // duplicate the entire path to avoid slice overwrites subdest := dest.Child(field) spec := spec{ dest: subdest, field: field, long: strings.ToLower(field.Name), } help, exists := field.Tag.Lookup("help") if exists { spec.help = help } // process each comma-separated part of the tag var isSubcommand bool for _, key := range strings.Split(tag, ",") { if key == "" { continue } key = strings.TrimLeft(key, " ") var value string if pos := strings.Index(key, ":"); pos != -1 { value = key[pos+1:] key = key[:pos] } switch { case strings.HasPrefix(key, "---"): errs = append(errs, fmt.Sprintf("%s.%s: too many hyphens", t.Name(), field.Name)) case strings.HasPrefix(key, "--"): spec.long = key[2:] case strings.HasPrefix(key, "-"): if len(key) > 2 { errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only", t.Name(), field.Name)) return false } spec.short = key[1:] case key == "required": spec.required = true case key == "positional": spec.positional = true case key == "separate": spec.separate = true case key == "help": // deprecated spec.help = value case key == "env": // Use override name if provided if value != "" { spec.env = envPrefix + value } else { spec.env = envPrefix + strings.ToUpper(field.Name) } case key == "subcommand": // decide on a name for the subcommand var cmdnames []string if value == "" { cmdnames = []string{strings.ToLower(field.Name)} } else { cmdnames = strings.Split(value, "|") } for i := range cmdnames { cmdnames[i] = strings.TrimSpace(cmdnames[i]) } // parse the subcommand recursively subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix) if err != nil { errs = append(errs, err.Error()) return false } subcmd.aliases = cmdnames[1:] subcmd.parent = &cmd subcmd.help = field.Tag.Get("help") cmd.subcommands = append(cmd.subcommands, subcmd) isSubcommand = true default: errs = append(errs, fmt.Sprintf("unrecognized tag '%s' on field %s", key, tag)) return false } } // placeholder is the string used in the help text like this: "--somearg PLACEHOLDER" placeholder, hasPlaceholder := field.Tag.Lookup("placeholder") if hasPlaceholder { spec.placeholder = placeholder } else if spec.long != "" { spec.placeholder = strings.ToUpper(spec.long) } else { spec.placeholder = strings.ToUpper(spec.field.Name) } // if this is a subcommand then we've done everything we need to do if isSubcommand { return false } // check whether this field is supported. It's good to do this here rather than // wait until ParseValue because it means that a program with invalid argument // fields will always fail regardless of whether the arguments it received // exercised those fields. var err error spec.cardinality, err = cardinalityOf(field.Type) if err != nil { errs = append(errs, fmt.Sprintf("%s.%s: %s fields are not supported", t.Name(), field.Name, field.Type.String())) return false } defaultString, hasDefault := field.Tag.Lookup("default") if hasDefault { // we do not support default values for maps and slices if spec.cardinality == multiple { errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields", t.Name(), field.Name)) return false } // a required field cannot also have a default value if spec.required { errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified", t.Name(), field.Name)) return false } // parse the default value spec.defaultString = defaultString if field.Type.Kind() == reflect.Ptr { // here we have a field of type *T and we create a new T, no need to dereference // in order for the value to be settable spec.defaultValue = reflect.New(field.Type.Elem()) } else { // here we have a field of type T and we create a new T and then dereference it // so that the resulting value is settable spec.defaultValue = reflect.New(field.Type).Elem() } err := scalar.ParseValue(spec.defaultValue, defaultString) if err != nil { errs = append(errs, fmt.Sprintf("%s.%s: error processing default value: %v", t.Name(), field.Name, err)) return false } } // add the spec to the list of specs cmd.specs = append(cmd.specs, &spec) // if this was an embedded field then we already returned true up above return false }) if len(errs) > 0 { return nil, errors.New(strings.Join(errs, "\n")) } // check that we don't have both positionals and subcommands var hasPositional bool for _, spec := range cmd.specs { if spec.positional { hasPositional = true } } if hasPositional && len(cmd.subcommands) > 0 { return nil, fmt.Errorf("%s cannot have both subcommands and positional arguments", dest) } return &cmd, nil } // Parse processes the given command line option, storing the results in the fields // of the structs from which NewParser was constructed. // // It returns ErrHelp if "--help" is one of the command line args and ErrVersion if // "--version" is one of the command line args (the latter only applies if the // destination struct passed to NewParser implements Versioned.) // // To respond to --help and --version in the way that MustParse does, see examples // in the README under "Custom handling of --help and --version". func (p *Parser) Parse(args []string) error { err := p.process(args) if err != nil { // If -h or --help were specified then make sure help text supercedes other errors for _, arg := range args { if arg == "-h" || arg == "--help" { return ErrHelp } if arg == "--" { break } } } return err } func (p *Parser) MustParse(args []string) { err := p.Parse(args) switch { case err == ErrHelp: p.WriteHelpForSubcommand(p.config.Out, p.subcommand...) p.config.Exit(0) case err == ErrVersion: fmt.Fprintln(p.config.Out, p.version) p.config.Exit(0) case err != nil: p.FailSubcommand(err.Error(), p.subcommand...) } } // process environment vars for the given arguments func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error { for _, spec := range specs { if spec.env == "" { continue } value, found := os.LookupEnv(spec.env) if !found { continue } if spec.cardinality == multiple { // expect a CSV string in an environment // variable in the case of multiple values var values []string var err error if len(strings.TrimSpace(value)) > 0 { values, err = csv.NewReader(strings.NewReader(value)).Read() if err != nil { return fmt.Errorf( "error reading a CSV string from environment variable %s with multiple values: %v", spec.env, err, ) } } if err = setSliceOrMap(p.val(spec.dest), values, !spec.separate); err != nil { return fmt.Errorf( "error processing environment variable %s with multiple values: %v", spec.env, err, ) } } else { if err := scalar.ParseValue(p.val(spec.dest), value); err != nil { return fmt.Errorf("error processing environment variable %s: %v", spec.env, err) } } wasPresent[spec] = true } return nil } // process goes through arguments one-by-one, parses them, and assigns the result to // the underlying struct field func (p *Parser) process(args []string) error { // track the options we have seen wasPresent := make(map[*spec]bool) // union of specs for the chain of subcommands encountered so far curCmd := p.cmd p.subcommand = nil // make a copy of the specs because we will add to this list each time we expand a subcommand specs := make([]*spec, len(curCmd.specs)) copy(specs, curCmd.specs) // deal with environment vars if !p.config.IgnoreEnv { err := p.captureEnvVars(specs, wasPresent) if err != nil { return err } } // determine if the current command has a version option spec var hasVersionOption bool for _, spec := range curCmd.specs { if spec.long == "version" { hasVersionOption = true break } } // process each string from the command line var allpositional bool var positionals []string // must use explicit for loop, not range, because we manipulate i inside the loop for i := 0; i < len(args); i++ { arg := args[i] if arg == "--" && !allpositional { allpositional = true continue } if !isFlag(arg) || allpositional { // each subcommand can have either subcommands or positionals, but not both if len(curCmd.subcommands) == 0 { positionals = append(positionals, arg) continue } // if we have a subcommand then make sure it is valid for the current context subcmd := findSubcommand(curCmd.subcommands, arg) if subcmd == nil { return fmt.Errorf("invalid subcommand: %s", arg) } // instantiate the field to point to a new struct v := p.val(subcmd.dest) if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers } // add the new options to the set of allowed options if p.config.StrictSubcommands { specs = make([]*spec, len(subcmd.specs)) copy(specs, subcmd.specs) } else { specs = append(specs, subcmd.specs...) } // capture environment vars for these new options if !p.config.IgnoreEnv { err := p.captureEnvVars(subcmd.specs, wasPresent) if err != nil { return err } } curCmd = subcmd p.subcommand = append(p.subcommand, arg) continue } // check for special --help and --version flags switch arg { case "-h", "--help": return ErrHelp case "--version": if !hasVersionOption && p.version != "" { return ErrVersion } } // check for an equals sign, as in "--foo=bar" var value string opt := strings.TrimLeft(arg, "-") if pos := strings.Index(opt, "="); pos != -1 { value = opt[pos+1:] opt = opt[:pos] } // 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 || opt == "" { return fmt.Errorf("unknown argument %s", arg) } wasPresent[spec] = true // deal with the case of multiple values if spec.cardinality == multiple { var values []string if value == "" { for i+1 < len(args) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" { values = append(values, args[i+1]) i++ if spec.separate { break } } } else { values = append(values, value) } err := setSliceOrMap(p.val(spec.dest), values, !spec.separate) if err != nil { return fmt.Errorf("error processing %s: %v", arg, err) } continue } // if it's a flag and it has no value then set the value to true // use boolean because this takes account of TextUnmarshaler if spec.cardinality == zero && value == "" { value = "true" } // if we have something like "--foo" then the value is the next argument if value == "" { if i+1 == len(args) { return fmt.Errorf("missing value for %s", arg) } if !isValue(args[i+1], spec.field.Type, specs) { return fmt.Errorf("missing value for %s", arg) } value = args[i+1] i++ } err := scalar.ParseValue(p.val(spec.dest), value) if err != nil { return fmt.Errorf("error processing %s: %v", arg, err) } } // process positionals for _, spec := range specs { if !spec.positional { continue } if len(positionals) == 0 { break } wasPresent[spec] = true if spec.cardinality == multiple { err := setSliceOrMap(p.val(spec.dest), positionals, true) if err != nil { return fmt.Errorf("error processing %s: %v", spec.placeholder, err) } positionals = nil } else { err := scalar.ParseValue(p.val(spec.dest), positionals[0]) if err != nil { return fmt.Errorf("error processing %s: %v", spec.placeholder, err) } positionals = positionals[1:] } } if len(positionals) > 0 { return fmt.Errorf("too many positional arguments at '%s'", positionals[0]) } // fill in defaults and check that all the required args were provided for _, spec := range specs { if wasPresent[spec] { continue } 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", spec.placeholder) if spec.env != "" { msg += " (or environment variable " + spec.env + ")" } return errors.New(msg) } if spec.defaultValue.IsValid() && !p.config.IgnoreDefault { // One issue here is that if the user now modifies the value then // the default value stored in the spec will be corrupted. There // is no general way to "deep-copy" values in Go, and we still // support the old-style method for specifying defaults as // Go values assigned directly to the struct field, so we are stuck. p.val(spec.dest).Set(spec.defaultValue) } } return nil } // isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--" func isFlag(s string) bool { return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != "" } // isValue returns true if a token should be consumed as a value for a flag of type t. This // is almost always the inverse of isFlag. The one exception is for negative numbers, in which // case we check the list of active options and return true if its not present there. func isValue(s string, t reflect.Type, specs []*spec) bool { switch t.Kind() { case reflect.Ptr, reflect.Slice: return isValue(s, t.Elem(), specs) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: v := reflect.New(t) err := scalar.ParseValue(v, s) // if value can be parsed and is not an explicit option declared elsewhere, then use it as a value if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) { return true } } // default case that is used in all cases other than negative numbers: inverse of isFlag return !isFlag(s) } // val returns a reflect.Value corresponding to the current value for the // given path func (p *Parser) val(dest path) reflect.Value { v := p.roots[dest.root] for _, field := range dest.fields { if v.Kind() == reflect.Ptr { if v.IsNil() { return reflect.Value{} } v = v.Elem() } v = v.FieldByIndex(field.Index) } return v } // findOption finds an option from its name, or returns null if no spec is found func findOption(specs []*spec, name string) *spec { for _, spec := range specs { if spec.positional { continue } if spec.long == name || spec.short == name { return spec } } return nil } // findSubcommand finds a subcommand using its name, or returns null if no subcommand is found func findSubcommand(cmds []*command, name string) *command { for _, cmd := range cmds { if cmd.name == name { return cmd } for _, alias := range cmd.aliases { if alias == name { return cmd } } } return nil } go-arg-1.6.0/go.sum0000664000175000017510000000263115014334473013364 0ustar nileshnileshgithub.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-arg-1.6.0/go.mod0000664000175000017510000000042715014334473013340 0ustar nileshnileshmodule github.com/alexflint/go-arg require ( github.com/alexflint/go-scalar v1.2.0 github.com/stretchr/testify v1.7.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.0 // indirect ) go 1.18 go-arg-1.6.0/example_test.go0000664000175000017510000003313615014334473015256 0ustar nileshnileshpackage arg import ( "fmt" "net" "net/mail" "net/url" "os" "strings" "time" ) func split(s string) []string { return strings.Split(s, " ") } // This example demonstrates basic usage func Example() { // These are the args you would pass in on the command line os.Args = split("./example --foo=hello --bar") var args struct { Foo string Bar bool } MustParse(&args) fmt.Println(args.Foo, args.Bar) // output: hello true } // This example demonstrates arguments that have default values func Example_defaultValues() { // These are the args you would pass in on the command line os.Args = split("./example") var args struct { Foo string `default:"abc"` } MustParse(&args) fmt.Println(args.Foo) // output: abc } // This example demonstrates arguments that are required func Example_requiredArguments() { // These are the args you would pass in on the command line os.Args = split("./example --foo=abc --bar") var args struct { Foo string `arg:"required"` Bar bool } MustParse(&args) fmt.Println(args.Foo, args.Bar) // output: abc true } // This example demonstrates positional arguments func Example_positionalArguments() { // These are the args you would pass in on the command line os.Args = split("./example in out1 out2 out3") var args struct { Input string `arg:"positional"` Output []string `arg:"positional"` } MustParse(&args) fmt.Println("In:", args.Input) fmt.Println("Out:", args.Output) // output: // In: in // Out: [out1 out2 out3] } // This example demonstrates arguments that have multiple values func Example_multipleValues() { // The args you would pass in on the command line os.Args = split("./example --database localhost --ids 1 2 3") var args struct { Database string IDs []int64 } MustParse(&args) fmt.Printf("Fetching the following IDs from %s: %v", args.Database, args.IDs) // output: Fetching the following IDs from localhost: [1 2 3] } // This example demonstrates arguments with keys and values func Example_mappings() { // The args you would pass in on the command line os.Args = split("./example --userids john=123 mary=456") var args struct { UserIDs map[string]int } MustParse(&args) fmt.Println(args.UserIDs) // output: map[john:123 mary:456] } type commaSeparated struct { M map[string]string } func (c *commaSeparated) UnmarshalText(b []byte) error { c.M = make(map[string]string) for _, part := range strings.Split(string(b), ",") { pos := strings.Index(part, "=") if pos == -1 { return fmt.Errorf("error parsing %q, expected format key=value", part) } c.M[part[:pos]] = part[pos+1:] } return nil } // This example demonstrates arguments with keys and values separated by commas func Example_mappingWithCommas() { // The args you would pass in on the command line os.Args = split("./example --values one=two,three=four") var args struct { Values commaSeparated } MustParse(&args) fmt.Println(args.Values.M) // output: map[one:two three:four] } // This eample demonstrates multiple value arguments that can be mixed with // other arguments. func Example_multipleMixed() { os.Args = split("./example -c cmd1 db1 -f file1 db2 -c cmd2 -f file2 -f file3 db3 -c cmd3") var args struct { Commands []string `arg:"-c,separate"` Files []string `arg:"-f,separate"` Databases []string `arg:"positional"` } MustParse(&args) fmt.Println("Commands:", args.Commands) fmt.Println("Files:", args.Files) fmt.Println("Databases:", args.Databases) // output: // Commands: [cmd1 cmd2 cmd3] // Files: [file1 file2 file3] // Databases: [db1 db2 db3] } // This example shows the usage string generated by go-arg func Example_helpText() { // These are the args you would pass in on the command line os.Args = split("./example --help") var args struct { Input string `arg:"positional,required"` Output []string `arg:"positional"` Verbose bool `arg:"-v" help:"verbosity level"` Dataset string `help:"dataset to use"` Optimize int `arg:"-O,--optim" help:"optimization level"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example [--verbose] [--dataset DATASET] [--optim OPTIM] INPUT [OUTPUT [OUTPUT ...]] // // Positional arguments: // INPUT // OUTPUT // // Options: // --verbose, -v verbosity level // --dataset DATASET dataset to use // --optim OPTIM, -O OPTIM // optimization level // --help, -h display this help and exit } // This example shows the usage string generated by go-arg with customized placeholders func Example_helpPlaceholder() { // These are the args you would pass in on the command line os.Args = split("./example --help") var args struct { Input string `arg:"positional,required" placeholder:"SRC"` Output []string `arg:"positional" placeholder:"DST"` Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"` MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] // // Positional arguments: // SRC // DST // // Options: // --optimize LEVEL, -O LEVEL // optimization level // --maxjobs N, -j N maximum number of simultaneous jobs // --help, -h display this help and exit } // This example shows the usage string generated by go-arg when using subcommands func Example_helpTextWithSubcommand() { // These are the args you would pass in on the command line os.Args = split("./example --help") type getCmd struct { Item string `arg:"positional" help:"item to fetch"` } type listCmd struct { Format string `help:"output format"` Limit int } var args struct { Verbose bool Get *getCmd `arg:"subcommand" help:"fetch an item and print it"` List *listCmd `arg:"subcommand" help:"list available items"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example [--verbose] [] // // Options: // --verbose // --help, -h display this help and exit // // Commands: // get fetch an item and print it // list list available items } // This example shows the usage string generated by go-arg when using subcommands func Example_helpTextWhenUsingSubcommand() { // These are the args you would pass in on the command line os.Args = split("./example get --help") type getCmd struct { Item string `arg:"positional,required" help:"item to fetch"` } type listCmd struct { Format string `help:"output format"` Limit int } var args struct { Verbose bool Get *getCmd `arg:"subcommand" help:"fetch an item and print it"` List *listCmd `arg:"subcommand" help:"list available items"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example get ITEM // // Positional arguments: // ITEM item to fetch // // Global options: // --verbose // --help, -h display this help and exit } // This example shows how to print help for an explicit subcommand func Example_writeHelpForSubcommand() { // These are the args you would pass in on the command line os.Args = split("./example get --help") type getCmd struct { Item string `arg:"positional" help:"item to fetch"` } type listCmd struct { Format string `help:"output format"` Limit int } var args struct { Verbose bool Get *getCmd `arg:"subcommand" help:"fetch an item and print it"` List *listCmd `arg:"subcommand" help:"list available items"` } // This is only necessary when running inside golang's runnable example harness exit := func(int) {} p, err := NewParser(Config{Exit: exit}, &args) if err != nil { fmt.Println(err) os.Exit(1) } err = p.WriteHelpForSubcommand(os.Stdout, "list") if err != nil { fmt.Println(err) os.Exit(1) } // output: // Usage: example list [--format FORMAT] [--limit LIMIT] // // Options: // --format FORMAT output format // --limit LIMIT // // Global options: // --verbose // --help, -h display this help and exit } // This example shows how to print help for a subcommand that is nested several levels deep func Example_writeHelpForSubcommandNested() { // These are the args you would pass in on the command line os.Args = split("./example get --help") type mostNestedCmd struct { Item string } type nestedCmd struct { MostNested *mostNestedCmd `arg:"subcommand"` } type topLevelCmd struct { Nested *nestedCmd `arg:"subcommand"` } var args struct { TopLevel *topLevelCmd `arg:"subcommand"` } // This is only necessary when running inside golang's runnable example harness exit := func(int) {} p, err := NewParser(Config{Exit: exit}, &args) if err != nil { fmt.Println(err) os.Exit(1) } err = p.WriteHelpForSubcommand(os.Stdout, "toplevel", "nested", "mostnested") if err != nil { fmt.Println(err) os.Exit(1) } // output: // Usage: example toplevel nested mostnested [--item ITEM] // // Options: // --item ITEM // --help, -h display this help and exit } // This example shows the error string generated by go-arg when an invalid option is provided func Example_errorText() { // These are the args you would pass in on the command line os.Args = split("./example --optimize INVALID") var args struct { Input string `arg:"positional,required"` Output []string `arg:"positional"` Verbose bool `arg:"-v" help:"verbosity level"` Dataset string `help:"dataset to use"` Optimize int `arg:"-O,help:optimization level"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] INPUT [OUTPUT [OUTPUT ...]] // error: error processing --optimize: strconv.ParseInt: parsing "INVALID": invalid syntax } // This example shows the error string generated by go-arg when an invalid option is provided func Example_errorTextForSubcommand() { // These are the args you would pass in on the command line os.Args = split("./example get --count INVALID") type getCmd struct { Count int } var args struct { Get *getCmd `arg:"subcommand"` } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) // output: // Usage: example get [--count COUNT] // error: error processing --count: strconv.ParseInt: parsing "INVALID": invalid syntax } // This example demonstrates use of subcommands func Example_subcommand() { // These are the args you would pass in on the command line os.Args = split("./example commit -a -m what-this-commit-is-about") type CheckoutCmd struct { Branch string `arg:"positional"` Track bool `arg:"-t"` } type CommitCmd struct { All bool `arg:"-a"` Message string `arg:"-m"` } type PushCmd struct { Remote string `arg:"positional"` Branch string `arg:"positional"` SetUpstream bool `arg:"-u"` } var args struct { Checkout *CheckoutCmd `arg:"subcommand:checkout"` Commit *CommitCmd `arg:"subcommand:commit"` Push *PushCmd `arg:"subcommand:push"` Quiet bool `arg:"-q"` // this flag is global to all subcommands } // This is only necessary when running inside golang's runnable example harness mustParseExit = func(int) {} mustParseOut = os.Stdout MustParse(&args) switch { case args.Checkout != nil: fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch) case args.Commit != nil: fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message) case args.Push != nil: fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote) } // output: // commit requested with message "what-this-commit-is-about" } func Example_allSupportedTypes() { // These are the args you would pass in on the command line os.Args = []string{} var args struct { Bool bool Byte byte Rune rune Int int Int8 int8 Int16 int16 Int32 int32 Int64 int64 Float32 float32 Float64 float64 String string Duration time.Duration URL url.URL Email mail.Address MAC net.HardwareAddr } // go-arg supports each of the types above, as well as pointers to any of // the above and slices of any of the above. It also supports any types that // implements encoding.TextUnmarshaler. MustParse(&args) // 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 } go-arg-1.6.0/doc.go0000664000175000017510000000257715014334473013336 0ustar nileshnilesh// Package arg parses command line arguments using the fields from a struct. // // For example, // // var args struct { // Iter int // Debug bool // } // arg.MustParse(&args) // // defines two command line arguments, which can be set using any of // // ./example --iter=1 --debug // debug is a boolean flag so its value is set to true // ./example -iter 1 // debug defaults to its zero value (false) // ./example --debug=true // iter defaults to its zero value (zero) // // The fastest way to see how to use go-arg is to read the examples below. // // Fields can be bool, string, any float type, or any signed or unsigned integer type. // They can also be slices of any of the above, or slices of pointers to any of the above. // // Tags can be specified using the `arg` and `help` tag names: // // var args struct { // Input string `arg:"positional"` // Log string `arg:"positional,required"` // Debug bool `arg:"-d" help:"turn on debug mode"` // RealMode bool `arg:"--real" // Wr io.Writer `arg:"-"` // } // // Any tag string that starts with a single hyphen is the short form for an argument // (e.g. `./example -d`), and any tag string that starts with two hyphens is the long // form for the argument (instead of the field name). // // Other valid tag strings are `positional` and `required`. // // Fields can be excluded from processing with `arg:"-"`. package arg go-arg-1.6.0/README.md0000664000175000017510000004326715014334473013522 0ustar nileshnilesh

go-arg
go-arg

Struct-based argument parsing for Go

Sourcegraph Documentation Build Status Coverage Status Go Report Card


Declare command line arguments for your program by defining a struct. ```go var args struct { Foo string Bar bool } arg.MustParse(&args) fmt.Println(args.Foo, args.Bar) ``` ```shell $ ./example --foo=hello --bar hello true ``` ### Installation ```shell go get github.com/alexflint/go-arg ``` ### Required arguments ```go var args struct { ID int `arg:"required"` Timeout time.Duration } arg.MustParse(&args) ``` ```shell $ ./example Usage: example --id ID [--timeout TIMEOUT] error: --id is required ``` ### Positional arguments ```go var args struct { Input string `arg:"positional"` Output []string `arg:"positional"` } arg.MustParse(&args) fmt.Println("Input:", args.Input) fmt.Println("Output:", args.Output) ``` ```shell $ ./example src.txt x.out y.out z.out Input: src.txt Output: [x.out y.out z.out] ``` ### Environment variables ```go var args struct { Workers int `arg:"env"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ```shell $ WORKERS=4 ./example Workers: 4 ``` ```shell $ WORKERS=4 ./example --workers=6 Workers: 6 ``` You can also override the name of the environment variable: ```go var args struct { Workers int `arg:"env:NUM_WORKERS"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ```shell $ NUM_WORKERS=4 ./example Workers: 4 ``` You can provide multiple values in environment variables using commas: ```go var args struct { Workers []int `arg:"env"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ```shell $ WORKERS='1,99' ./example Workers: [1 99] ``` Command line arguments take precedence over environment variables: ```go var args struct { Workers int `arg:"--count,env:NUM_WORKERS"` } arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` ```shell $ NUM_WORKERS=6 ./example Workers: 6 $ NUM_WORKERS=6 ./example --count 4 Workers: 4 ``` Configuring a global environment variable name prefix is also possible: ```go var args struct { Workers int `arg:"--count,env:NUM_WORKERS"` } p, err := arg.NewParser(arg.Config{ EnvPrefix: "MYAPP_", }, &args) p.MustParse(os.Args[1:]) fmt.Println("Workers:", args.Workers) ``` ```shell $ MYAPP_NUM_WORKERS=6 ./example Workers: 6 ``` ### Usage strings ```go var args struct { Input string `arg:"positional"` Output []string `arg:"positional"` Verbose bool `arg:"-v,--verbose" help:"verbosity level"` Dataset string `help:"dataset to use"` Optimize int `arg:"-O" help:"optimization level"` } arg.MustParse(&args) ``` ```shell $ ./example -h Usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] Positional arguments: INPUT OUTPUT Options: --verbose, -v verbosity level --dataset DATASET dataset to use --optimize OPTIMIZE, -O OPTIMIZE optimization level --help, -h print this help message ``` ### Default values ```go var args struct { Foo string `default:"abc"` Bar bool } arg.MustParse(&args) ``` Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value. ```go var args struct { Test string `arg:"-t,env:TEST" default:"something"` } arg.MustParse(&args) ``` #### Ignoring environment variables and/or default values ```go var args struct { Test string `arg:"-t,env:TEST" default:"something"` } p, err := arg.NewParser(arg.Config{ IgnoreEnv: true, IgnoreDefault: true, }, &args) err = p.Parse(os.Args[1:]) ``` ### Arguments with multiple values ```go var args struct { Database string IDs []int64 } arg.MustParse(&args) fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs) ``` ```shell ./example -database foo -ids 1 2 3 Fetching the following IDs from foo: [1 2 3] ``` ### Arguments that can be specified multiple times, mixed with positionals ```go var args struct { Commands []string `arg:"-c,separate"` Files []string `arg:"-f,separate"` Databases []string `arg:"positional"` } arg.MustParse(&args) ``` ```shell ./example -c cmd1 db1 -f file1 db2 -c cmd2 -f file2 -f file3 db3 -c cmd3 Commands: [cmd1 cmd2 cmd3] Files [file1 file2 file3] Databases [db1 db2 db3] ``` ### Arguments with keys and values ```go var args struct { UserIDs map[string]int } arg.MustParse(&args) fmt.Println(args.UserIDs) ``` ```shell ./example --userids john=123 mary=456 map[john:123 mary:456] ``` ### Version strings ```go type args struct { ... } func (args) Version() string { return "someprogram 4.3.0" } func main() { var args args arg.MustParse(&args) } ``` ```shell $ ./example --version someprogram 4.3.0 ``` > **Note** > If a `--version` flag is defined in `args` or any subcommand, it overrides the built-in versioning. ### Custom validation ```go var args struct { Foo string Bar string } p := arg.MustParse(&args) if args.Foo == "" && args.Bar == "" { p.Fail("you must provide either --foo or --bar") } ``` ```shell ./example Usage: samples [--foo FOO] [--bar BAR] error: you must provide either --foo or --bar ``` ### Overriding option names ```go var args struct { Short string `arg:"-s"` Long string `arg:"--custom-long-option"` ShortAndLong string `arg:"-x,--my-option"` OnlyShort string `arg:"-o,--"` } arg.MustParse(&args) ``` ```shell $ ./example --help Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION] Options: --short SHORT, -s SHORT --custom-long-option CUSTOM-LONG-OPTION --my-option MY-OPTION, -x MY-OPTION -o ONLYSHORT --help, -h display this help and exit ``` ### Embedded structs The fields of embedded structs are treated just like regular fields: ```go type DatabaseOptions struct { Host string Username string Password string } type LogOptions struct { LogFile string Verbose bool } func main() { var args struct { DatabaseOptions LogOptions } arg.MustParse(&args) } ``` As usual, any field tagged with `arg:"-"` is ignored. ### Supported types The following types may be used as arguments: - built-in integer types: `int, int8, int16, int32, int64, byte, rune` - built-in floating point types: `float32, float64` - strings - booleans - URLs represented as `url.URL` - time durations represented as `time.Duration` - email addresses represented as `mail.Address` - MAC addresses represented as `net.HardwareAddr` - pointers to any of the above - slices of any of the above - maps using any of the above as keys and values - any type that implements `encoding.TextUnmarshaler` ### Custom parsing Implement `encoding.TextUnmarshaler` to define your own parsing logic. ```go // Accepts command line arguments of the form "head.tail" type NameDotName struct { Head, Tail string } func (n *NameDotName) UnmarshalText(b []byte) error { s := string(b) pos := strings.Index(s, ".") if pos == -1 { return fmt.Errorf("missing period in %s", s) } n.Head = s[:pos] n.Tail = s[pos+1:] return nil } func main() { var args struct { Name NameDotName } arg.MustParse(&args) fmt.Printf("%#v\n", args.Name) } ``` ```shell $ ./example --name=foo.bar main.NameDotName{Head:"foo", Tail:"bar"} $ ./example --name=oops Usage: example [--name NAME] error: error processing --name: missing period in "oops" ``` ### Custom parsing with default values Implement `encoding.TextMarshaler` to define your own default value strings: ```go // Accepts command line arguments of the form "head.tail" type NameDotName struct { Head, Tail string } func (n *NameDotName) UnmarshalText(b []byte) error { // same as previous example } // this is only needed if you want to display a default value in the usage string func (n *NameDotName) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail)), nil } func main() { var args struct { Name NameDotName `default:"file.txt"` } arg.MustParse(&args) fmt.Printf("%#v\n", args.Name) } ``` ```shell $ ./example --help Usage: test [--name NAME] Options: --name NAME [default: file.txt] --help, -h display this help and exit $ ./example main.NameDotName{Head:"file", Tail:"txt"} ``` ### Custom placeholders Use the `placeholder` tag to control which placeholder text is used in the usage text. ```go var args struct { Input string `arg:"positional" placeholder:"SRC"` Output []string `arg:"positional" placeholder:"DST"` Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"` MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"` } arg.MustParse(&args) ``` ```shell $ ./example -h Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] Positional arguments: SRC DST Options: --optimize LEVEL, -O LEVEL optimization level --maxjobs N, -j N maximum number of simultaneous jobs --help, -h display this help and exit ``` ### Description strings A descriptive message can be added at the top of the help text by implementing a `Description` function that returns a string. ```go type args struct { Foo string } func (args) Description() string { return "this program does this and that" } func main() { var args args arg.MustParse(&args) } ``` ```shell $ ./example -h this program does this and that Usage: example [--foo FOO] Options: --foo FOO --help, -h display this help and exit ``` Similarly an epilogue can be added at the end of the help text by implementing the `Epilogue` function. ```go type args struct { Foo string } func (args) Epilogue() string { return "For more information visit github.com/alexflint/go-arg" } func main() { var args args arg.MustParse(&args) } ``` ```shell $ ./example -h Usage: example [--foo FOO] Options: --foo FOO --help, -h display this help and exit For more information visit github.com/alexflint/go-arg ``` ### Subcommands Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool: ```shell $ git checkout [arguments specific to checking out code] $ git commit [arguments specific to committing] $ git push [arguments specific to pushing] ``` The strings "checkout", "commit", and "push" are different from simple positional arguments because the options available to the user change depending on which subcommand they choose. This can be implemented with `go-arg` as follows: ```go type CheckoutCmd struct { Branch string `arg:"positional"` Track bool `arg:"-t"` } type CommitCmd struct { All bool `arg:"-a"` Message string `arg:"-m"` } type PushCmd struct { Remote string `arg:"positional"` Branch string `arg:"positional"` SetUpstream bool `arg:"-u"` } var args struct { Checkout *CheckoutCmd `arg:"subcommand:checkout"` Commit *CommitCmd `arg:"subcommand:commit"` Push *PushCmd `arg:"subcommand:push"` Quiet bool `arg:"-q"` // this flag is global to all subcommands } arg.MustParse(&args) switch { case args.Checkout != nil: fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch) case args.Commit != nil: fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message) case args.Push != nil: fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote) } ``` Some additional rules apply when working with subcommands: * The `subcommand` tag can only be used with fields that are pointers to structs * Any struct that contains a subcommand must not contain any positionals This package allows to have a program that accepts subcommands, but also does something else when no subcommands are specified. If on the other hand you want the program to terminate when no subcommands are specified, the recommended way is: ```go p := arg.MustParse(&args) if p.Subcommand() == nil { p.Fail("missing subcommand") } ``` ### Custom handling of --help and --version The following reproduces the internal logic of `MustParse` for the simple case where you are not using subcommands or --version. This allows you to respond programatically to --help, and to any errors that come up. ```go var args struct { Something string } p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // indicates that user wrote "--help" on command line p.WriteHelp(os.Stdout) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsage(os.Stdout) os.Exit(1) } ``` ```shell $ go run ./example --help Usage: ./example --something SOMETHING Options: --something SOMETHING --help, -h display this help and exit $ ./example --wrong error: unknown argument --wrong Usage: ./example --something SOMETHING $ ./example error: --something is required Usage: ./example --something SOMETHING ``` To also handle --version programatically, use the following: ```go type args struct { Something string } func (args) Version() string { return "1.2.3" } func main() { var args args p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // found "--help" on command line p.WriteHelp(os.Stdout) os.Exit(0) case err == arg.ErrVersion: // found "--version" on command line fmt.Println(args.Version()) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsage(os.Stdout) os.Exit(1) } fmt.Printf("got %q\n", args.Something) } ``` ```shell $ ./example --version 1.2.3 $ go run ./example --help 1.2.3 Usage: example --something SOMETHING Options: --something SOMETHING --help, -h display this help and exit $ ./example --wrong 1.2.3 error: unknown argument --wrong Usage: example --something SOMETHING $ ./example error: --something is required Usage: example --something SOMETHING ``` To generate subcommand-specific help messages, use the following most general version (this also works in absence of subcommands but is a bit more complex): ```go type fetchCmd struct { Count int } type args struct { Something string Fetch *fetchCmd `arg:"subcommand"` } func (args) Version() string { return "1.2.3" } func main() { var args args p, err := arg.NewParser(arg.Config{}, &args) if err != nil { log.Fatalf("there was an error in the definition of the Go struct: %v", err) } err = p.Parse(os.Args[1:]) switch { case err == arg.ErrHelp: // found "--help" on command line p.WriteHelpForSubcommand(os.Stdout, p.SubcommandNames()...) os.Exit(0) case err == arg.ErrVersion: // found "--version" on command line fmt.Println(args.Version()) os.Exit(0) case err != nil: fmt.Printf("error: %v\n", err) p.WriteUsageForSubcommand(os.Stdout, p.SubcommandNames()...) os.Exit(1) } } ``` ```shell $ ./example --version 1.2.3 $ ./example --help 1.2.3 Usage: example [--something SOMETHING] [] Options: --something SOMETHING --help, -h display this help and exit --version display version and exit Commands: fetch $ ./example fetch --help 1.2.3 Usage: example fetch [--count COUNT] Options: --count COUNT Global options: --something SOMETHING --help, -h display this help and exit --version display version and exit ``` ### API Documentation https://pkg.go.dev/github.com/alexflint/go-arg ### Rationale There are many command line argument parsing libraries for Go, including one in the standard library, so why build another? The `flag` library that ships in the standard library seems awkward to me. Positional arguments must precede options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms. Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags. The idea behind `go-arg` is that Go already has an excellent way to describe data structures using structs, so there is no need to develop additional levels of abstraction. Instead of one API to specify which arguments your program accepts, and then another API to get the values of those arguments, `go-arg` replaces both with a single struct. ### Backward compatibility notes Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which makes it possible to include commas inside help text. go-arg-1.6.0/LICENSE0000664000175000017510000000241615014334473013237 0ustar nileshnileshCopyright (c) 2015, Alex Flint All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-arg-1.6.0/.gitignore0000664000175000017510000000041215014334473014214 0ustar nileshnilesh# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof