package cobra import ( "reflect" "strings" "testing" commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta" ) func flagGrammarTree() *commandmeta.Command { return &commandmeta.Command{Name: "app", Children: []*commandmeta.Command{{ Name: "run", Runnable: true, Flags: []commandmeta.Flag{ {Name: "color", Short: "c"}, {Name: "verbose", Short: "v", Boolean: true}, {Name: "quiet", Short: "q", Boolean: true}, {Name: "message", Short: "m"}, {Name: "tag", Short: "t", Repeated: true}, }, }}} } func flagGrammarOptions() ParseOptions { return ParseOptions{FlagAliases: []commandmeta.FlagAlias{ {Source: "--colour", Target: "--color"}, {Source: "-C", Target: "--color"}, {Source: "-w", Target: "--verbose"}, {Source: "-z", Target: "--quiet"}, {Source: "--taggy", Target: "-t"}, {Source: "--hlep", Target: "--help"}, }} } func reportingFlagTree() *commandmeta.Command { return &commandmeta.Command{Name: "app", Children: []*commandmeta.Command{{ Name: "run", Runnable: true, CoreFlags: commandmeta.ReportingFlags(), Flags: []commandmeta.Flag{{Name: "message"}}, }}} } func reportingFlagOptions() ParseOptions { return ParseOptions{FlagAliases: []commandmeta.FlagAlias{ {Source: "-q", Target: "--quiet"}, {Source: "-v", Target: "--verbose"}, {Source: "-d", Target: "--debug"}, }} } func TestParseReportsFlagOccurrencesInEnteredOrder(t *testing.T) { result := Parse(reportingFlagTree(), []string{ "run", "--verbose=true", "-qd", "--progress=false", "--message=--quiet", "--", "--debug", }, reportingFlagOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } want := []commandmeta.FlagOccurrence{ {Name: "verbose", Spelling: "--verbose", RawValue: "true"}, {Name: "quiet", Spelling: "-q", RawValue: "true"}, {Name: "debug", Spelling: "-d", RawValue: "true"}, {Name: "progress", Spelling: "--progress", RawValue: "false"}, {Name: "message", Spelling: "--message", RawValue: "--quiet"}, } if !reflect.DeepEqual(result.FlagOccurrences, want) { t.Fatalf("flag occurrences = %#v, want %#v", result.FlagOccurrences, want) } if !reflect.DeepEqual(result.FlagValues, [][]string{{"--quiet"}}) || !reflect.DeepEqual(result.FlagSpellings, [][]string{{"--message"}}) { t.Fatalf("consumer values or spellings changed: %#v, %#v", result.FlagValues, result.FlagSpellings) } } func TestParseFlagOccurrencesRetainOnlyConsumedPrefixOnFailure(t *testing.T) { result := Parse(reportingFlagTree(), []string{"run", "--verbose", "--unknown", "--quiet"}, reportingFlagOptions()) if result.Failure == nil || len(result.FlagOccurrences) != 1 || result.FlagOccurrences[0].Name != "verbose" { t.Fatalf("unknown flag result = %#v", result) } result = Parse(reportingFlagTree(), []string{"run", "-qd", "--message"}, reportingFlagOptions()) if result.Failure == nil || len(result.FlagOccurrences) != 2 || result.FlagOccurrences[0].Name != "quiet" || result.FlagOccurrences[1].Name != "debug" { t.Fatalf("missing value result = %#v", result) } result = Parse(reportingFlagTree(), []string{"missing", "--quiet"}, reportingFlagOptions()) if result.Failure == nil || len(result.FlagOccurrences) != 0 { t.Fatalf("malformed path result = %#v", result) } } func TestParseFlagOccurrenceKeepsOptionLookingValueAndConsumesTerminator(t *testing.T) { result := Parse(reportingFlagTree(), []string{"run", "--message", "--quiet", "--", "--verbose"}, reportingFlagOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } want := []commandmeta.FlagOccurrence{{Name: "message", Spelling: "--message", RawValue: "--quiet"}} if !reflect.DeepEqual(result.FlagOccurrences, want) { t.Fatalf("flag occurrences = %#v, want %#v", result.FlagOccurrences, want) } if !reflect.DeepEqual(result.Operands, []string{"--verbose"}) { t.Fatalf("operands = %#v", result.Operands) } } func TestParseCanonicalAndAliasShortGrammar(t *testing.T) { for _, test := range []struct { name string args []string values [][]string spellings [][]string }{ {"boolean cluster", []string{"run", "-vq"}, [][]string{nil, {"true"}, {"true"}, nil, nil}, [][]string{nil, {"-v"}, {"-q"}, nil, nil}}, {"aliased boolean cluster", []string{"run", "-wz"}, [][]string{nil, {"true"}, {"true"}, nil, nil}, [][]string{nil, {"-w"}, {"-z"}, nil, nil}}, {"value short separated", []string{"run", "-c", "blue"}, [][]string{{"blue"}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"value short equals", []string{"run", "-c=blue"}, [][]string{{"blue"}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"value short attached", []string{"run", "-cblue"}, [][]string{{"blue"}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"long alias", []string{"run", "--colour=blue"}, [][]string{{"blue"}, nil, nil, nil, nil}, [][]string{{"--colour"}, nil, nil, nil, nil}}, {"short alias", []string{"run", "-Cblue"}, [][]string{{"blue"}, nil, nil, nil, nil}, [][]string{{"-C"}, nil, nil, nil, nil}}, {"cluster value consumes suffix", []string{"run", "-vqcblue"}, [][]string{{"blue"}, {"true"}, {"true"}, nil, nil}, [][]string{{"-c"}, {"-v"}, {"-q"}, nil, nil}}, {"cluster value consumes next", []string{"run", "-vqc", "blue"}, [][]string{{"blue"}, {"true"}, {"true"}, nil, nil}, [][]string{{"-c"}, {"-v"}, {"-q"}, nil, nil}}, {"mixed canonical and alias cluster", []string{"run", "-wqcblue"}, [][]string{{"blue"}, {"true"}, {"true"}, nil, nil}, [][]string{{"-c"}, {"-w"}, {"-q"}, nil, nil}}, {"value takes remaining suffix", []string{"run", "-cvq"}, [][]string{{"vq"}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"boolean explicit false", []string{"run", "-v=false"}, [][]string{nil, {"false"}, nil, nil, nil}, [][]string{nil, {"-v"}, nil, nil, nil}}, {"boolean explicit false in cluster", []string{"run", "-qv=false"}, [][]string{nil, {"false"}, {"true"}, nil, nil}, [][]string{nil, {"-v"}, {"-q"}, nil, nil}}, {"empty canonical short", []string{"run", "-c="}, [][]string{{""}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"empty canonical long", []string{"run", "--color="}, [][]string{{""}, nil, nil, nil, nil}, [][]string{{"--color"}, nil, nil, nil, nil}}, {"empty separated short", []string{"run", "-c", ""}, [][]string{{""}, nil, nil, nil, nil}, [][]string{{"-c"}, nil, nil, nil, nil}}, {"empty aliased long", []string{"run", "--colour="}, [][]string{{""}, nil, nil, nil, nil}, [][]string{{"--colour"}, nil, nil, nil, nil}}, {"empty aliased short", []string{"run", "-C="}, [][]string{{""}, nil, nil, nil, nil}, [][]string{{"-C"}, nil, nil, nil, nil}}, {"empty boolean short", []string{"run", "-v="}, [][]string{nil, {""}, nil, nil, nil}, [][]string{nil, {"-v"}, nil, nil, nil}}, } { t.Run(test.name, func(t *testing.T) { result := Parse(flagGrammarTree(), test.args, flagGrammarOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } if !reflect.DeepEqual(result.FlagValues, test.values) { t.Errorf("values = %#v, want %#v", result.FlagValues, test.values) } if !reflect.DeepEqual(result.FlagSpellings, test.spellings) { t.Errorf("spellings = %#v, want %#v", result.FlagSpellings, test.spellings) } }) } } func TestParseFlagAliasesPreserveValuesAndTerminators(t *testing.T) { result := Parse(flagGrammarTree(), []string{"run", "--message", "--hlep", "--message=--hlep", "--taggy", "one", "--", "--taggy"}, flagGrammarOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } if got, want := result.FlagValues[3], []string{"--hlep", "--hlep"}; !reflect.DeepEqual(got, want) { t.Fatalf("message values = %#v, want %#v", got, want) } if got, want := result.FlagSpellings[3], []string{"--message", "--message"}; !reflect.DeepEqual(got, want) { t.Fatalf("message spellings = %#v, want %#v", got, want) } if got, want := result.FlagValues[4], []string{"one"}; !reflect.DeepEqual(got, want) { t.Fatalf("tag values = %#v, want %#v", got, want) } if got, want := result.Operands, []string{"--taggy"}; !reflect.DeepEqual(got, want) { t.Fatalf("operands = %#v, want %#v", got, want) } } func TestParseConsumesDoubleDashAsAFlagValue(t *testing.T) { for _, spelling := range []string{"--color", "--colour", "-c", "-C"} { t.Run(spelling, func(t *testing.T) { result := Parse(flagGrammarTree(), []string{"run", spelling, "--", "--taggy=one", "--colour=red", "--", "--taggy=operand"}, flagGrammarOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } if !reflect.DeepEqual(result.FlagValues[0], []string{"--", "red"}) || !reflect.DeepEqual(result.FlagValues[4], []string{"one"}) { t.Errorf("flag values = %#v", result.FlagValues) } if !reflect.DeepEqual(result.FlagSpellings[0], []string{spelling, "--colour"}) || !reflect.DeepEqual(result.Operands, []string{"--taggy=operand"}) { t.Errorf("spellings = %#v, operands = %#v", result.FlagSpellings, result.Operands) } }) } for _, args := range [][]string{{"run", "-v", "--", "--colour"}, {"run", "--color", "blue", "--", "--colour"}} { result := Parse(flagGrammarTree(), args, flagGrammarOptions()) if result.Failure != nil || !reflect.DeepEqual(result.Operands, []string{"--colour"}) { t.Errorf("Parse(%q) = %#v", args, result) } } } func TestParseHelpAliasAndUnknownShortMembers(t *testing.T) { result := Parse(flagGrammarTree(), []string{"run", "--hlep"}, flagGrammarOptions()) if result.Failure != nil || !result.HelpRequested { t.Fatalf("help alias result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "-vx"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("unknown short cluster member result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "--color"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("missing value result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "--colour"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("missing aliased value result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "-c"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("missing short value result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "-C"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("missing aliased short value result = %#v", result) } result = Parse(flagGrammarTree(), []string{"run", "-vfalse"}, flagGrammarOptions()) if result.Failure == nil || result.Failure.Message == "" { t.Fatalf("boolean suffix without equals result = %#v", result) } } func TestParseFlagAliasOccurrenceOrderAndSpellings(t *testing.T) { result := Parse(flagGrammarTree(), []string{"run", "--taggy=first", "-t", "second", "--taggy", "third"}, flagGrammarOptions()) if result.Failure != nil { t.Fatalf("parse failed: %s", result.Failure.Message) } if got, want := result.FlagValues[4], []string{"first", "second", "third"}; !reflect.DeepEqual(got, want) { t.Fatalf("tag values = %#v, want %#v", got, want) } if got, want := result.FlagSpellings[4], []string{"--taggy", "-t", "--taggy"}; !reflect.DeepEqual(got, want) { t.Fatalf("tag spellings = %#v, want %#v", got, want) } } func TestParseFailuresRetainEnteredFlagSpellings(t *testing.T) { for _, spelling := range []string{"--color", "--colour", "-c", "-C"} { t.Run("missing/"+spelling, func(t *testing.T) { result := Parse(flagGrammarTree(), []string{"run", spelling}, flagGrammarOptions()) if result.Failure == nil || !strings.Contains(result.Failure.Message, ": "+spelling) { t.Fatalf("missing value failure = %#v, want entered spelling %q", result.Failure, spelling) } }) } result := Parse(flagGrammarTree(), []string{"run", "-wC"}, flagGrammarOptions()) if result.Failure == nil || !strings.Contains(result.Failure.Message, ": -C") { t.Fatalf("cluster missing value failure = %#v", result.Failure) } for _, test := range []struct { args []string spelling string }{ {[]string{"--hlep=bad"}, "--hlep"}, {[]string{"-h=bad"}, "-h"}, {[]string{"--help=bad"}, "--help"}, {[]string{"--hlep=bad", "--help=true"}, "--hlep"}, {[]string{"-h=bad", "--hlep=true"}, "-h"}, {[]string{"--help=true", "--hlep=bad"}, "--hlep"}, } { t.Run(strings.Join(test.args, " "), func(t *testing.T) { result := Parse(flagGrammarTree(), append([]string{"run"}, test.args...), flagGrammarOptions()) if result.Failure == nil || !strings.Contains(result.Failure.Message, "for "+test.spelling) { t.Fatalf("help failure = %#v, want entered spelling %q", result.Failure, test.spelling) } }) } result = Parse(flagGrammarTree(), []string{"run", "--unknown", "--colour"}, flagGrammarOptions()) if result.Failure == nil || !strings.Contains(result.Failure.Message, "unknown") || strings.Contains(result.Failure.Message, "needs an argument") { t.Fatalf("earlier parser failure = %#v", result.Failure) } }