diff --git a/examples/trailingArguments/.gitignore b/examples/trailingArguments/.gitignore new file mode 100644 index 0000000..d33d363 --- /dev/null +++ b/examples/trailingArguments/.gitignore @@ -0,0 +1 @@ +trailingArguments diff --git a/examples/trailingArguments/main.go b/examples/trailingArguments/main.go new file mode 100644 index 0000000..418e696 --- /dev/null +++ b/examples/trailingArguments/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + + "github.com/integrii/flaggy" +) + +func main() { + + // Declare variables and their defaults + var someString = "" + var someInt = 3 + var someBool bool + var positionalValue string + + // add a global bool flag for fun + flaggy.Bool(&someBool, "y", "yes", "A sample boolean flag") + flaggy.String(&someString, "s", "string", "A sample string flag") + flaggy.Int(&someInt, "i", "int", "A sample int flag") + + // this positional value will be parsed specifically before all trailing + // arguments are parsed + flaggy.AddPositionalValue(&positionalValue, "testPositional", 1, false, "a test positional") + + flaggy.DebugMode = false + flaggy.ShowHelpOnUnexpectedDisable() + + // Parse the subcommand and all flags + flaggy.Parse() + + // here you will see all arguments passsed after the first positional 'testPositional' string is parsed + fmt.Println(flaggy.TrailingArguments) + // Input: + // ./trailingArguments one two three + // Output: + // [two three] +} diff --git a/main.go b/flaggy.go similarity index 98% rename from main.go rename to flaggy.go index b242cb6..e850823 100644 --- a/main.go +++ b/flaggy.go @@ -57,7 +57,8 @@ func ResetParser() { } } -// Parse parses flags as requested in the default package parser +// Parse parses flags as requested in the default package parser. All trailing arguments +// that result from parsing are placed in the global TrailingArguments variable. func Parse() { err := DefaultParser.Parse() TrailingArguments = DefaultParser.TrailingArguments @@ -67,7 +68,8 @@ func Parse() { } // ParseArgs parses the passed args as if they were the arguments to the -// running binary. Targets the default main parser for the package. +// running binary. Targets the default main parser for the package. All trailing +// arguments are set in the global TrailingArguments variable. func ParseArgs(args []string) { err := DefaultParser.ParseArgs(args) TrailingArguments = DefaultParser.TrailingArguments diff --git a/flaggy_test.go b/flaggy_test.go index 7cfbd02..5bee0c9 100644 --- a/flaggy_test.go +++ b/flaggy_test.go @@ -7,10 +7,11 @@ import ( "github.com/integrii/flaggy" ) -// TestTrailingArguments tests trailing argument parsing -func TestTrailingArguments(t *testing.T) { +// TestTrailingArgumentsDashes tests trailing argument parsing when --- is used +func TestTrailingArgumentsDashes(t *testing.T) { + flaggy.ResetParser() - args := []string{"./flaggy.text", "--", "one", "two"} + args := []string{"./flaggy.test", "--", "one", "two"} os.Args = args flaggy.Parse() if len(flaggy.TrailingArguments) != 2 { @@ -24,7 +25,35 @@ func TestTrailingArguments(t *testing.T) { if flaggy.TrailingArguments[1] != "two" { t.Fatal("incorrect argument parsed. Got", flaggy.TrailingArguments[1], "but expected two") } +} + +// TestTrailingArgumentsNoDashes tests trailing argument parsing without using --- +func TestTrailingArgumentsNoDashes(t *testing.T) { + + flaggy.ResetParser() + var positionalValue string + args := []string{"./flaggy.test", "positional", "one", "two"} + os.Args = args + flaggy.ShowHelpOnUnexpectedDisable() + flaggy.AddPositionalValue(&positionalValue, "testPositional", 1, false, "a test positional") + + flaggy.Parse() + if len(flaggy.TrailingArguments) != 2 { + t.Fatal("incorrect argument count parsed. Got", len(flaggy.TrailingArguments), "but expected", 2) + } + + if flaggy.TrailingArguments[0] != "one" { + t.Fatal("incorrect argument parsed. Got", flaggy.TrailingArguments[0], "but expected one") + } + + if flaggy.TrailingArguments[1] != "two" { + t.Fatal("incorrect argument parsed. Got", flaggy.TrailingArguments[1], "but expected two") + } + + if positionalValue != "positional" { + t.Fatal("expected positional value was not found set to the string 'positional'") + } } // TestComplexNesting tests various levels of nested subcommands and diff --git a/helpValues_blackbox_test.go b/helpValues_blackbox_test.go index f28cc5f..35f8eef 100644 --- a/helpValues_blackbox_test.go +++ b/helpValues_blackbox_test.go @@ -57,7 +57,11 @@ func TestHelpWithMissingSCName(t *testing.T) { } }() flaggy.ResetParser() - flaggy.NewSubcommand("") + flaggy.PanicInsteadOfExit = true + sc := flaggy.NewSubcommand("") + sc.ShortName = "sn" + flaggy.AttachSubcommand(sc, 1) + flaggy.ParseArgs([]string{"x"}) } // TestHelpOutput tests the dislay of help with -h diff --git a/subCommand.go b/subCommand.go index 2857955..c82c081 100644 --- a/subCommand.go +++ b/subCommand.go @@ -115,7 +115,7 @@ func (sc *Subcommand) parseAllFlagsFromArgs(p *Parser, args []string) ([]string, argType := determineArgType(a) // strip flags from arg - // debugPrint("Parsing flag named", a, "of type", argType) + debugPrint("Parsing flag named", a, "of type", argType) // depending on the flag type, parse the key and value out, then apply it switch argType { @@ -301,39 +301,49 @@ func (sc *Subcommand) parse(p *Parser, args []string, depth int) error { // if there aren't any positional flags but there are subcommands that // were not used, display a useful message with subcommand options. - if !foundPositional && p.ShowHelpOnUnexpected { - debugPrint("No positional at position", relativeDepth) - var foundSubcommandAtDepth bool - for _, cmd := range sc.Subcommands { - if cmd.Position == relativeDepth { - foundSubcommandAtDepth = true - } - } - - // if there is a subcommand here but it was not specified, display them all - // as a suggestion to the user before exiting. - if foundSubcommandAtDepth { - // determine which name to use in upcoming help output - fmt.Fprintln(os.Stderr, sc.Name+":", "No subcommand or positional value found at position", strconv.Itoa(relativeDepth)+".") - var output string + if !foundPositional { + if p.ShowHelpOnUnexpected { + debugPrint("No positional at position", relativeDepth) + var foundSubcommandAtDepth bool for _, cmd := range sc.Subcommands { - if cmd.Hidden { - continue + if cmd.Position == relativeDepth { + foundSubcommandAtDepth = true } - output = output + " " + cmd.Name } - // if there are available subcommands, let the user know - if len(output) > 0 { - output = strings.TrimLeft(output, " ") - fmt.Println("Available subcommands:", output) + + // if there is a subcommand here but it was not specified, display them all + // as a suggestion to the user before exiting. + if foundSubcommandAtDepth { + // determine which name to use in upcoming help output + fmt.Fprintln(os.Stderr, sc.Name+":", "No subcommand or positional value found at position", strconv.Itoa(relativeDepth)+".") + var output string + for _, cmd := range sc.Subcommands { + if cmd.Hidden { + continue + } + output = output + " " + cmd.Name + } + // if there are available subcommands, let the user know + if len(output) > 0 { + output = strings.TrimLeft(output, " ") + fmt.Println("Available subcommands:", output) + } + exitOrPanic(2) } + + // if there were not any flags or subcommands at this position at all, then + // throw an error (display Help if necessary) + p.ShowHelpWithMessage("Unexpected argument: " + v) exitOrPanic(2) - } + } else { + // if no positional value was registered at this position, but the parser is not + // configured to show help when any unexpected command is found, add this positional + // to the list of trailing arguments. This allows for any number of unspecified + // values to be added at the end of the arguments list without using the --- + // trailing arguments separator. + p.TrailingArguments = append(p.TrailingArguments, v) - // if there were not any flags or subcommands at this position at all, then - // throw an error (display Help if necessary) - p.ShowHelpWithMessage("Unexpected argument: " + v) - exitOrPanic(2) + } } }