package flags import ( "bytes" "io" "os" "path" "path/filepath" "reflect" "runtime" "strings" "testing" ) type TestComplete struct { } func (t *TestComplete) Complete(match string) []Completion { options := []string{ "hello world", "hello universe", "hello multiverse", } ret := make([]Completion, 0, len(options)) for _, o := range options { if strings.HasPrefix(o, match) { ret = append(ret, Completion{ Item: o, }) } } return ret } var completionTestOptions struct { Verbose bool `short:"v" long:"verbose" description:"Verbose messages"` Debug bool `short:"d" long:"debug" description:"Enable debug"` Info bool `short:"i" description:"Display info"` Version bool `long:"version" description:"Show version"` Required bool `long:"required" required:"true" description:"This is required"` Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"` AddCommand struct { Positional struct { Filename Filename } `positional-args:"yes"` } `command:"add" description:"add an item"` AddMultiCommand struct { Positional struct { Filename []Filename } `positional-args:"yes"` Extra []Filename `short:"f"` } `command:"add-multi" description:"add multiple items"` AddMultiCommandFlag struct { Files []Filename `short:"f"` } `command:"add-multi-flag" description:"add multiple items via flags"` RemoveCommand struct { Other bool `short:"o"` File Filename `short:"f" long:"filename"` } `command:"rm" description:"remove an item"` RenameCommand struct { Completed TestComplete `short:"c" long:"completed"` } `command:"rename" description:"rename an item"` HiddenCommand struct { } `command:"hidden" description:"hidden command" hidden:"true"` } type completionTest struct { Args []string Completed []string ShowDescriptions bool } var completionTests []completionTest func init() { _, sourcefile, _, _ := runtime.Caller(0) completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...) completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")} completionTestSubdir := []string{ filepath.Join(completionTestSourcedir, "examples/add.go"), filepath.Join(completionTestSourcedir, "examples/bash-completion"), filepath.Join(completionTestSourcedir, "examples/main.go"), filepath.Join(completionTestSourcedir, "examples/rm.go"), } completionTests = []completionTest{ { // Short names []string{"-"}, []string{"--debug", "--required", "--verbose", "--version", "-i"}, false, }, { // Short names full []string{"-i"}, []string{"-i"}, false, }, { // Short names concatenated []string{"-dv"}, []string{"-dv"}, false, }, { // Long names []string{"--"}, []string{"--debug", "--required", "--verbose", "--version"}, false, }, { // Long names with descriptions []string{"--"}, []string{ "--debug # Enable debug", "--required # This is required", "--verbose # Verbose messages", "--version # Show version", }, true, }, { // Long names partial []string{"--ver"}, []string{"--verbose", "--version"}, false, }, { // Commands []string{""}, []string{"add", "add-multi", "add-multi-flag", "rename", "rm"}, false, }, { // Commands with descriptions []string{""}, []string{ "add # add an item", "add-multi # add multiple items", "add-multi-flag # add multiple items via flags", "rename # rename an item", "rm # remove an item", }, true, }, { // Commands partial []string{"r"}, []string{"rename", "rm"}, false, }, { // Positional filename []string{"add", filepath.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Multiple positional filename (1 arg) []string{"add-multi", filepath.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Multiple positional filename (2 args) []string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Multiple positional filename (3 args) []string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Flag filename []string{"rm", "-f", path.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Flag short concat last filename []string{"rm", "-of", path.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // Flag concat filename []string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")}, []string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]}, false, }, { // Flag equal concat filename []string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")}, []string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]}, false, }, { // Flag concat long filename []string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")}, []string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]}, false, }, { // Flag long filename []string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, { // To subdir []string{"rm", "--filename", path.Join(completionTestSourcedir, "examples/bash-")}, []string{path.Join(completionTestSourcedir, "examples/bash-completion/")}, false, }, { // Subdirectory []string{"rm", "--filename", path.Join(completionTestSourcedir, "examples") + "/"}, completionTestSubdir, false, }, { // Custom completed []string{"rename", "-c", "hello un"}, []string{"hello universe"}, false, }, { // Multiple flag filename []string{"add-multi-flag", "-f", filepath.Join(completionTestSourcedir, "completion")}, completionTestFilename, false, }, } } func TestCompletion(t *testing.T) { p := NewParser(&completionTestOptions, Default) c := &completion{parser: p} for _, test := range completionTests { if test.ShowDescriptions { continue } ret := c.complete(test.Args) items := make([]string, len(ret)) for i, v := range ret { items[i] = v.Item } if !reflect.DeepEqual(items, test.Completed) { t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items) } } } func TestParserCompletion(t *testing.T) { for _, test := range completionTests { if test.ShowDescriptions { os.Setenv("GO_FLAGS_COMPLETION", "verbose") } else { os.Setenv("GO_FLAGS_COMPLETION", "1") } tmp := os.Stdout r, w, _ := os.Pipe() os.Stdout = w out := make(chan string) go func() { var buf bytes.Buffer io.Copy(&buf, r) out <- buf.String() }() p := NewParser(&completionTestOptions, None) p.CompletionHandler = func(items []Completion) { comp := &completion{parser: p} comp.print(items, test.ShowDescriptions) } _, err := p.ParseArgs(test.Args) w.Close() os.Stdout = tmp if err != nil { t.Fatalf("Unexpected error: %s", err) } got := strings.Split(strings.Trim(<-out, "\n"), "\n") if !reflect.DeepEqual(got, test.Completed) { t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got) } } os.Setenv("GO_FLAGS_COMPLETION", "") }