Skip to content

Commit

Permalink
Merge pull request #80 from integrii/remotes/origin/trailingArgs
Browse files Browse the repository at this point in the history
Fix trailing arguments being considered errant positionals
  • Loading branch information
Eric Greer committed May 28, 2022
2 parents f98079e + fe6d3f5 commit 7f80235
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 34 deletions.
1 change: 1 addition & 0 deletions examples/trailingArguments/.gitignore
@@ -0,0 +1 @@
trailingArguments
38 changes: 38 additions & 0 deletions 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]
}
6 changes: 4 additions & 2 deletions main.go → flaggy.go
Expand Up @@ -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
Expand All @@ -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
Expand Down
35 changes: 32 additions & 3 deletions flaggy_test.go
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion helpValues_blackbox_test.go
Expand Up @@ -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
Expand Down
66 changes: 38 additions & 28 deletions subCommand.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}

Expand Down

0 comments on commit 7f80235

Please sign in to comment.