diff --git a/changelog/16441.txt b/changelog/16441.txt new file mode 100644 index 0000000000000..f265483d86ea2 --- /dev/null +++ b/changelog/16441.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: CLI commands will print a warning if flags will be ignored because they are passed after positional arguments. +``` diff --git a/command/base.go b/command/base.go index 5f6a171dcee2c..bbad7beca2a2e 100644 --- a/command/base.go +++ b/command/base.go @@ -549,6 +549,7 @@ type FlagSets struct { mainSet *flag.FlagSet hiddens map[string]struct{} completions complete.Flags + ui cli.Ui } // NewFlagSets creates a new flag sets. @@ -564,6 +565,7 @@ func NewFlagSets(ui cli.Ui) *FlagSets { mainSet: mainSet, hiddens: make(map[string]struct{}), completions: complete.Flags{}, + ui: ui, } } @@ -582,8 +584,16 @@ func (f *FlagSets) Completions() complete.Flags { } // Parse parses the given flags, returning any errors. +// Warnings, if any, regarding the arguments format are sent to stdout func (f *FlagSets) Parse(args []string) error { - return f.mainSet.Parse(args) + err := f.mainSet.Parse(args) + + warnings := generateFlagWarnings(f.Args()) + if warnings != "" { + f.ui.Warn(warnings) + } + + return err } // Parsed reports whether the command-line flags have been parsed. @@ -603,10 +613,10 @@ func (f *FlagSets) Visit(fn func(*flag.Flag)) { } // Help builds custom help for this command, grouping by flag set. -func (fs *FlagSets) Help() string { +func (f *FlagSets) Help() string { var out bytes.Buffer - for _, set := range fs.flagSets { + for _, set := range f.flagSets { printFlagTitle(&out, set.name+":") set.VisitAll(func(f *flag.Flag) { // Skip any hidden flags diff --git a/command/base_helpers.go b/command/base_helpers.go index f20cfebf71025..03e135a525a37 100644 --- a/command/base_helpers.go +++ b/command/base_helpers.go @@ -292,3 +292,19 @@ func parseFlagFile(raw string) (string, error) { return raw, nil } + +func generateFlagWarnings(args []string) string { + var trailingFlags []string + for _, arg := range args { + if strings.HasPrefix(arg, "-") { + trailingFlags = append(trailingFlags, arg) + } + } + + if len(trailingFlags) > 0 { + return fmt.Sprintf("Flags must be provided before positional arguments. "+ + "The following arguments will not be parsed as flags: [%s]", strings.Join(trailingFlags, ",")) + } else { + return "" + } +} diff --git a/command/base_helpers_test.go b/command/base_helpers_test.go index df764d36e4db7..4703153df1b46 100644 --- a/command/base_helpers_test.go +++ b/command/base_helpers_test.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "os" + "strings" "testing" "time" ) @@ -209,3 +210,48 @@ func TestParseFlagFile(t *testing.T) { }) } } + +func TestArgWarnings(t *testing.T) { + t.Parallel() + + cases := []struct { + args []string + expected string + }{ + { + []string{"a", "b", "c"}, + "", + }, + { + []string{"a", "-b"}, + "-b", + }, + { + []string{"a", "--b"}, + "--b", + }, + { + []string{"a-b", "-c"}, + "-c", + }, + { + []string{"a", "-b-c"}, + "-b-c", + }, + { + []string{"-a", "b"}, + "-a", + }, + } + + for _, tc := range cases { + tc := tc + + t.Run(tc.expected, func(t *testing.T) { + warnings := generateFlagWarnings(tc.args) + if !strings.Contains(warnings, tc.expected) { + t.Fatalf("expected %s to contain %s", warnings, tc.expected) + } + }) + } +}