Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix trailing arguments being considered errant positionals #80

Merged
merged 4 commits into from May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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