From fbf01c0f7705d5bd3ac0ead2838cff1bc8e9d56c Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Fri, 6 Dec 2024 21:22:03 +0100 Subject: [PATCH 1/3] start --- docs/go.mod | 4 +++- funcs.go | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/go.mod b/docs/go.mod index b53a2b002e..1c0a6c38dd 100644 --- a/docs/go.mod +++ b/docs/go.mod @@ -1,6 +1,8 @@ module github.com/urfave/cli/docs/v3 -go 1.18 +go 1.22 + +toolchain go1.23.4 replace github.com/urfave/cli/v3 => ../ diff --git a/funcs.go b/funcs.go index e0d1e19c2b..7a302ece1d 100644 --- a/funcs.go +++ b/funcs.go @@ -2,8 +2,12 @@ package cli import "context" +// ShellCompleteDirective is a bit map representing the different behaviors the +// shell can be instructed to have once completions have been provided. +type ShellCompleteDirective int + // ShellCompleteFunc is an action to execute when the shell completion flag is set -type ShellCompleteFunc func(context.Context, *Command) +type ShellCompleteFunc func(ctx context.Context, cmd *Command, args []string, toComplete string) ([]string, ShellCompleteDirective) // BeforeFunc is an action that executes prior to any subcommands being run once // the context is ready. If a non-nil error is returned, no subcommands are From 935ca596deaa918fdaad4cf5e51456214c07952a Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Sat, 7 Dec 2024 00:29:30 +0100 Subject: [PATCH 2/3] create first tests for Zsh completion to sketch out expected behavior --- completion_zsh_test.go | 193 +++++++++++++++++++++++++++++++++++++++++ examples_test.go | 37 -------- 2 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 completion_zsh_test.go diff --git a/completion_zsh_test.go b/completion_zsh_test.go new file mode 100644 index 0000000000..ddb63cd7e5 --- /dev/null +++ b/completion_zsh_test.go @@ -0,0 +1,193 @@ +package cli_test + +import ( + "context" + "fmt" + "log" + "os" + + cli "github.com/urfave/cli/v3" +) + +var devices = []string{"Pixel 7 API 34", "iPhone 12 mini", "iPhone 15"} + +func exampleAction(ctx context.Context, c *cli.Command) error { + fmt.Printf("command %#v called with args: %#v\n", c.Name, c.Args().Slice()) + return nil +} + +func makeExampleApp() *cli.Command { + return &cli.Command{ + Name: "emu-cli", + Usage: "Manage android emulators with ease", + EnableShellCompletion: true, + HideHelpCommand: true, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "quiet", + Aliases: []string{"q"}, + Usage: "do not print invocations of subprocesses", + Action: func(ctx context.Context, c *cli.Command, value bool) error { + return nil + }, + }, + }, + Commands: []*cli.Command{ + { + Name: "run", + Usage: "Start a single device", + Action: exampleAction, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "fast", + Usage: "Run device quickly", + }, + &cli.BoolFlag{ + Name: "slow", + Usage: "Don't hurry up too much", + }, + }, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + for _, device := range devices { + fmt.Println(device) + } + }, + }, + // TODO: Not expressible in urfave/cli + { + Name: "runall", + Usage: "Start many devices", + Action: exampleAction, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + for _, device := range devices { + fmt.Println(device) + } + }, + }, + // TODO: Not expressible in urfave/cli + { + Name: "kill", + Usage: "Kill a single device", + Action: exampleAction, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + for _, device := range devices { + fmt.Println(device) + } + }, + }, + { + Name: "create", + Usage: "Create a new device", + Action: exampleAction, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "os", + Usage: "OS of the device", + // TODO: func ShellComplete + }, + &cli.StringFlag{ + Name: "os-version", + Usage: "OS image version", + // TODO: func ShellComplete + }, + &cli.StringFlag{ + Name: "frame", + Usage: "Frame of the device", + // TODO: func ShellComplete + }, + }, + ShellComplete: func(ctx context.Context, cmd *cli.Command) { + androidCompletions := []string{"Android 14 (API 33)", "Android 14 (API 33) Play Store", "Android 15 (API 34)"} + iosCompletions := []string{"iOS 15", "iOS 16", "iOS 17"} + + completions := make([]string, 0) + completions = append(completions, androidCompletions...) + completions = append(completions, iosCompletions...) + }, + }, + }, + CommandNotFound: func(ctx context.Context, c *cli.Command, command string) { + log.Printf("invalid command '%s'", command) + }, + } +} + +func ExampleCommand_Run_shellComplete_zsh() { + cmd := &cli.Command{ + Name: "greet", + EnableShellCompletion: true, + Commands: []*cli.Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(context.Context, *cli.Command) error { + fmt.Printf("i like to describe things") + return nil + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating bash completion", + Action: func(context.Context, *cli.Command) error { + fmt.Printf("the next example") + return nil + }, + }, + }, + } + + // Simulate a zsh environment and command line arguments + os.Args = []string{"greet", "--generate-shell-completion"} + os.Setenv("SHELL", "/usr/bin/zsh") + + _ = cmd.Run(context.Background(), os.Args) + // Output: + // describeit:use it to see a description + // next:next example + // help:Shows a list of commands or help for one command +} + +func ExampleCommand_Run_completeCommands_1() { + cmd := makeExampleApp() + os.Args = []string{"emu-cli", "", "--generate-shell-completion"} + os.Setenv("SHELL", "/usr/bin/zsh") + + _ = cmd.Run(context.Background(), os.Args) + // Output: + // run:Start a single device + // runall:Start many devices + // kill:Kill a single device + // create:Create a new device +} + +func ExampleCommand_Run_completeCommands_2() { + cmd := makeExampleApp() + os.Args = []string{"emu-cli", "r", "--generate-shell-completion"} + os.Setenv("SHELL", "/usr/bin/zsh") + + // Note: this output is the same as the test above. The actual matching + // is done by the shell, not by us! + + _ = cmd.Run(context.Background(), os.Args) + // Output: + // run:Start a single device + // runall:Start many devices + // kill:Kill a single device + // create:Create a new device +} + +func ExampleCommand_Run_completeFlags() { + cmd := makeExampleApp() + os.Args = []string{"emu-cli", "run", "--", "--generate-shell-completion"} + os.Setenv("SHELL", "/usr/bin/zsh") + + // Note: this output is the same as the test above. The actual matching + // is done by the shell, not by us! + + _ = cmd.Run(context.Background(), os.Args) + // Output: + // --fast:Run device quickly + // --slow:Don't hurry up too much +} diff --git a/examples_test.go b/examples_test.go index 500ecfe6c6..50711d1b83 100644 --- a/examples_test.go +++ b/examples_test.go @@ -372,43 +372,6 @@ func ExampleCommand_Run_shellComplete_bash() { // help } -func ExampleCommand_Run_shellComplete_zsh() { - cmd := &cli.Command{ - Name: "greet", - EnableShellCompletion: true, - Commands: []*cli.Command{ - { - Name: "describeit", - Aliases: []string{"d"}, - Usage: "use it to see a description", - Description: "This is how we describe describeit the function", - Action: func(context.Context, *cli.Command) error { - fmt.Printf("i like to describe things") - return nil - }, - }, { - Name: "next", - Usage: "next example", - Description: "more stuff to see when generating bash completion", - Action: func(context.Context, *cli.Command) error { - fmt.Printf("the next example") - return nil - }, - }, - }, - } - - // Simulate a zsh environment and command line arguments - os.Args = []string{"greet", "--generate-shell-completion"} - os.Setenv("SHELL", "/usr/bin/zsh") - - _ = cmd.Run(context.Background(), os.Args) - // Output: - // describeit:use it to see a description - // next:next example - // help:Shows a list of commands or help for one command -} - func ExampleCommand_Run_sliceValues() { cmd := &cli.Command{ Name: "multi_values", From 2cc84806f6504d4fc7f992ec7ad5893f6e7d441b Mon Sep 17 00:00:00 2001 From: Bartek Pacia Date: Sat, 7 Dec 2024 00:29:44 +0100 Subject: [PATCH 3/3] revert ShellCompleteFunc signature func (too early) --- funcs.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/funcs.go b/funcs.go index 7a302ece1d..8168283740 100644 --- a/funcs.go +++ b/funcs.go @@ -7,7 +7,10 @@ import "context" type ShellCompleteDirective int // ShellCompleteFunc is an action to execute when the shell completion flag is set -type ShellCompleteFunc func(ctx context.Context, cmd *Command, args []string, toComplete string) ([]string, ShellCompleteDirective) +// type ShellCompleteFunc func(ctx context.Context, cmd *Command, args []string, toComplete string) ([]string, ShellCompleteDirective) + +// ShellCompleteFunc is an action to execute when the shell completion flag is set +type ShellCompleteFunc func(context.Context, *Command) // BeforeFunc is an action that executes prior to any subcommands being run once // the context is ready. If a non-nil error is returned, no subcommands are