Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ func ExampleApp_Run_bashComplete_withLongFlag() {
Aliases: []string{"x"},
},
&StringFlag{
Name: "some-flag,s",
Name: "some-flag",
Aliases: []string{"s"},
},
&StringFlag{
Name: "similar-flag",
Expand All @@ -295,6 +296,29 @@ func ExampleApp_Run_bashComplete_withLongFlag() {
// --some-flag
// --similar-flag
}

func TestApp_Run_ErrorsOnLegacyV1FlagAliasSyntax(t *testing.T) {
app := &App{
Name: "greet",
Flags: []Flag{
&StringFlag{
Name: "config, cfg",
},
},
}

err := app.Run([]string{"greet"})
if err == nil {
t.Fatalf("expected an error for legacy alias syntax, got nil")
}

if !strings.Contains(err.Error(), "invalid flag name") {
t.Fatalf("expected invalid flag name error, got %q", err)
}
if !strings.Contains(err.Error(), "Aliases") {
t.Fatalf("expected alias migration hint in error, got %q", err)
}
}
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
os.Setenv("SHELL", "bash")
os.Args = []string{"greet", "--st", "--generate-bash-completion"}
Expand Down
44 changes: 44 additions & 0 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"reflect"
"regexp"
"runtime"
"strings"
Expand All @@ -26,6 +27,13 @@ var (
commaWhitespace = regexp.MustCompile("[, ]+.*")
)

func validateFlagName(name string) error {
if name != "" && (strings.Contains(name, ",") || strings.Contains(name, " ")) {
return fmt.Errorf("invalid flag name %q: move alternate names to Aliases", name)
}
return nil
}

// BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag Flag = &BoolFlag{
Name: "generate-bash-completion",
Expand Down Expand Up @@ -167,10 +175,46 @@ type Countable interface {
Count() int
}

func rawFlagName(f any) (string, bool) {
if f == nil {
return "", false
}

rv := reflect.ValueOf(f)
for rv.Kind() == reflect.Pointer {
if rv.IsNil() {
return "", false
}
rv = rv.Elem()
}

if rv.Kind() != reflect.Struct {
return "", false
}

if target := rv.FieldByName("Target"); target.IsValid() {
if name, ok := rawFlagName(target.Interface()); ok {
return name, true
}
}

nameField := rv.FieldByName("Name")
if !nameField.IsValid() || nameField.Kind() != reflect.String {
return "", false
}

return nameField.String(), true
}

func flagSet(name string, flags []Flag, spec separatorSpec) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)

for _, f := range flags {
if name, ok := rawFlagName(f); ok {
if err := validateFlagName(name); err != nil {
return nil, err
}
}
if c, ok := f.(customizedSeparator); ok {
c.WithSeparatorSpec(spec)
}
Expand Down
Loading