Simple, intuitive CLI framework for Go pkg.go.dev/go.followtheprocess.codes/cli
go cli
0

Configure Feed

Select the types of activity you want to include in your feed.

Remove completion while I figure out a strategy (#213)

authored by

Tom Fleet and committed by
GitHub
(Apr 29, 2026, 8:39 PM +0100) d99c720b 250e5ee7

+9 -949
+8 -30
README.md
··· 25 25 - [Sub Commands](#sub-commands) 26 26 - [Flags](#flags) 27 27 - [Arguments](#arguments) 28 - - [Shell Completion](#shell-completion) 29 28 - [Core Principles](#core-principles) 30 29 - [😱 Well behaved libraries don't panic](#-well-behaved-libraries-dont-panic) 31 30 - [🧘🏻 Keep it Simple](#-keep-it-simple) ··· 331 330 > Slice types are not supported (yet), for those you need to use the `cmd.Args()` method to get the arguments manually. I plan to address this but it can be tricky 332 331 > as slice types will eat up the remainder of the arguments so I need to figure out a good DevEx for this as it could lead to confusing outcomes 333 332 334 - ### Shell Completion 335 - 336 - `cli` has built-in support for shell completions via [carapace-bin]. Wire in `cli.CompletionSubCommand()` alongside your other subcommands: 337 - 338 - ```go 339 - cmd, err := cli.New( 340 - "mytool", 341 - // ... 342 - cli.SubCommands( 343 - buildServeCommand, 344 - buildDeployCommand, 345 - cli.CompletionSubCommand(), // add this 346 - ), 347 - ) 348 - ``` 349 - 350 - Running `mytool completion` outputs a [carapace-spec] YAML document describing your full command tree — all subcommands, flags, and descriptions. Redirect it once to register completions with [carapace-bin]: 351 - 352 - ```shell 353 - mytool completion > ~/.config/carapace/specs/mytool.yaml 354 - ``` 355 - 356 - carapace-bin then provides completions across bash, zsh, fish, nushell, PowerShell, and more — no shell-specific scripts required. 357 - 358 - > [!TIP] 359 - > See the [`./examples/completion`](https://github.com/FollowTheProcess/cli/tree/main/examples/completion) example for a working demonstration, and the [carapace-spec] docs for how to extend the generated YAML with semantic completion hints (file paths, environment variables, etc.) 360 - 361 333 ## Core Principles 362 334 363 335 When designing and implementing `cli`, I had some core goals and guiding principles for implementation. ··· 455 427 cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, "Delete something")) 456 428 ``` 457 429 430 + ## Gaps 431 + 432 + `cli` is new and under active development, so naturally there are some gaps compared to more mature frameworks: 433 + 434 + - No shell completion (yet): I'm thinking about some smart ways to achieve this and haven't made up my mind yet 435 + - Man page generation: Similar to shell completion 436 + - Text wrapping and terminal width detection for `--help` (currently this is on you to format when writing) 437 + 458 438 ## In the Wild 459 439 460 440 I built `cli` for my own uses really, so I've quickly adopted it across a number of tools. See the following projects for some working examples in real code: ··· 468 448 [spf13/pflag]: https://github.com/spf13/pflag 469 449 [urfave/cli]: https://github.com/urfave/cli 470 450 [functional options]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 471 - [carapace-bin]: https://github.com/carapace-sh/carapace-bin 472 - [carapace-spec]: https://github.com/carapace-sh/carapace-spec
-182
completion.go
··· 1 - package cli 2 - 3 - import ( 4 - "context" 5 - "fmt" 6 - "slices" 7 - "strings" 8 - 9 - publicflag "go.followtheprocess.codes/cli/flag" 10 - "go.yaml.in/yaml/v4" 11 - ) 12 - 13 - const completionLong = ` 14 - Outputs a carapace-spec YAML document describing this command's flags and subcommands. 15 - 16 - Redirect the output to register completions with carapace-bin: 17 - 18 - mytool completion > ~/.config/carapace/specs/mytool.yaml 19 - ` 20 - 21 - // CompletionSubCommand returns a [Builder] that constructs a "completion" subcommand. 22 - // 23 - // When run, it outputs a carapace-spec YAML document to stdout describing the full 24 - // command tree, all flags, and all subcommands. 25 - // 26 - // Wire it into your root command: 27 - // 28 - // cli.New("mytool", 29 - // cli.SubCommands(cli.CompletionSubCommand()), 30 - // ... 31 - // ) 32 - // 33 - // Users register completions by running: 34 - // 35 - // mytool completion > ~/.config/carapace/specs/mytool.yaml 36 - // 37 - // carapace-bin then provides completions across bash, zsh, fish, nushell, 38 - // powershell, and more. See https://github.com/carapace-sh/carapace-spec 39 - // for how to extend the generated YAML with semantic completion hints. 40 - func CompletionSubCommand() Builder { 41 - return func() (*Command, error) { 42 - return New( 43 - "completion", 44 - Short("Output a carapace-spec YAML document to stdout"), 45 - Long(completionLong), 46 - Run(func(_ context.Context, cmd *Command) error { 47 - data, err := marshalCompletionSpec(cmd.root()) 48 - if err != nil { 49 - return fmt.Errorf("generating carapace spec: %w", err) 50 - } 51 - 52 - _, err = cmd.Stdout().Write(data) 53 - 54 - return err 55 - }), 56 - ) 57 - } 58 - } 59 - 60 - // specCommand is the internal representation of a carapace-spec YAML command node. 61 - // 62 - // See https://github.com/carapace-sh/carapace-spec for the full schema. 63 - type specCommand struct { 64 - Name string `yaml:"name"` 65 - Description string `yaml:"description,omitempty"` 66 - Flags map[string]string `yaml:"flags,omitempty"` 67 - PersistentFlags map[string]string `yaml:"persistentflags,omitempty"` 68 - Commands []specCommand `yaml:"commands,omitempty"` 69 - } 70 - 71 - // marshalCompletionSpec generates a carapace-spec YAML document for cmd and its 72 - // full subcommand tree. 73 - func marshalCompletionSpec(cmd *Command) ([]byte, error) { 74 - spec := buildSpecTree(cmd) 75 - 76 - // Help and version are on every command; declare them once as persistentflags. 77 - spec.PersistentFlags = persistentFlagsFrom(cmd) 78 - 79 - data, err := yaml.Marshal(spec) 80 - if err != nil { 81 - return nil, fmt.Errorf("marshalling carapace spec: %w", err) 82 - } 83 - 84 - return data, nil 85 - } 86 - 87 - // isSystemFlag reports whether name is an automatically-injected flag. 88 - // These flags appear on every command and are emitted as persistentflags 89 - // on the root rather than repeated in every command's flags map. 90 - func isSystemFlag(name string) bool { 91 - return name == "help" || name == "version" 92 - } 93 - 94 - // buildSpecTree recursively builds a specCommand tree from cmd. 95 - func buildSpecTree(cmd *Command) specCommand { 96 - spec := specCommand{ 97 - Name: cmd.name, 98 - Description: cmd.short, 99 - Flags: specFlagsFrom(cmd), 100 - } 101 - 102 - if len(cmd.subcommands) > 0 { 103 - sorted := make([]*Command, len(cmd.subcommands)) 104 - copy(sorted, cmd.subcommands) 105 - slices.SortFunc(sorted, func(a, b *Command) int { 106 - return strings.Compare(a.name, b.name) 107 - }) 108 - 109 - spec.Commands = make([]specCommand, len(sorted)) 110 - for i, sub := range sorted { 111 - spec.Commands[i] = buildSpecTree(sub) 112 - } 113 - } 114 - 115 - return spec 116 - } 117 - 118 - // specFlagsFrom builds the carapace-spec flags map for cmd, excluding system 119 - // flags (help, version) which are emitted as persistentflags on the root. 120 - func specFlagsFrom(cmd *Command) map[string]string { 121 - flags := make(map[string]string) 122 - 123 - for name, fl := range cmd.flagSet().All() { 124 - if isSystemFlag(name) { 125 - continue 126 - } 127 - 128 - key := specFlagKey(name, fl.Short(), fl.Type(), fl.NoArgValue()) 129 - flags[key] = fl.Usage() 130 - } 131 - 132 - if len(flags) == 0 { 133 - return nil 134 - } 135 - 136 - return flags 137 - } 138 - 139 - // persistentFlagsFrom builds the carapace-spec persistentflags map from the 140 - // system flags (help, version) that are automatically added to every command. 141 - func persistentFlagsFrom(cmd *Command) map[string]string { 142 - flags := make(map[string]string) 143 - 144 - for name, fl := range cmd.flagSet().All() { 145 - if !isSystemFlag(name) { 146 - continue 147 - } 148 - 149 - key := specFlagKey(name, fl.Short(), fl.Type(), fl.NoArgValue()) 150 - flags[key] = fl.Usage() 151 - } 152 - 153 - if len(flags) == 0 { 154 - return nil 155 - } 156 - 157 - return flags 158 - } 159 - 160 - // specFlagKey encodes a flag name, shorthand, and type into a carapace-spec 161 - // flag map key. 162 - func specFlagKey(name string, short rune, typ, noArgValue string) string { 163 - var base string 164 - if short != publicflag.NoShortHand { 165 - base = fmt.Sprintf("-%s, --%s", string(short), name) 166 - } else { 167 - base = "--" + name 168 - } 169 - 170 - switch { 171 - case noArgValue == "": 172 - // Value is required 173 - return base + "=" 174 - case typ == "bool": 175 - return base 176 - case typ == "count": 177 - return base + "*" 178 - default: 179 - // Optional value 180 - return base + "?" 181 - } 182 - }
-315
completion_test.go
··· 1 - package cli_test 2 - 3 - import ( 4 - "bytes" 5 - "context" 6 - "net" 7 - "slices" 8 - "testing" 9 - "time" 10 - 11 - "go.followtheprocess.codes/cli" 12 - "go.followtheprocess.codes/cli/flag" 13 - "go.followtheprocess.codes/snapshot" 14 - "go.followtheprocess.codes/test" 15 - ) 16 - 17 - func TestCompletionSpec(t *testing.T) { 18 - tests := []struct { 19 - name string 20 - options []cli.Option 21 - }{ 22 - { 23 - name: "no user flags", 24 - options: []cli.Option{ 25 - cli.Short("My tool"), 26 - cli.OverrideArgs([]string{"completion"}), 27 - cli.SubCommands(cli.CompletionSubCommand()), 28 - }, 29 - }, 30 - { 31 - name: "bool flag", 32 - options: []cli.Option{ 33 - cli.Short("My tool"), 34 - cli.OverrideArgs([]string{"completion"}), 35 - cli.Flag(new(bool), "force", 'f', "Force deletion"), 36 - cli.SubCommands(cli.CompletionSubCommand()), 37 - }, 38 - }, 39 - { 40 - name: "string flag", 41 - options: []cli.Option{ 42 - cli.Short("My tool"), 43 - cli.OverrideArgs([]string{"completion"}), 44 - cli.Flag(new(string), "output", 'o', "Output file"), 45 - cli.SubCommands(cli.CompletionSubCommand()), 46 - }, 47 - }, 48 - { 49 - name: "count flag", 50 - options: []cli.Option{ 51 - cli.Short("My tool"), 52 - cli.OverrideArgs([]string{"completion"}), 53 - cli.Flag(new(flag.Count), "verbose", 'v', "Verbosity level"), 54 - cli.SubCommands(cli.CompletionSubCommand()), 55 - }, 56 - }, 57 - { 58 - name: "long-only flag", 59 - options: []cli.Option{ 60 - cli.Short("My tool"), 61 - cli.OverrideArgs([]string{"completion"}), 62 - cli.Flag(new(bool), "dry-run", flag.NoShortHand, "Dry run mode"), 63 - cli.SubCommands(cli.CompletionSubCommand()), 64 - }, 65 - }, 66 - { 67 - name: "slice flag", 68 - options: []cli.Option{ 69 - cli.Short("My tool"), 70 - cli.OverrideArgs([]string{"completion"}), 71 - cli.Flag(new([]string), "tags", 't', "Tags to apply"), 72 - cli.SubCommands(cli.CompletionSubCommand()), 73 - }, 74 - }, 75 - { 76 - name: "with subcommands", 77 - options: []cli.Option{ 78 - cli.Short("My tool"), 79 - cli.OverrideArgs([]string{"completion"}), 80 - cli.SubCommands( 81 - cli.CompletionSubCommand(), 82 - func() (*cli.Command, error) { 83 - return cli.New("delete", 84 - cli.Short("Delete a resource"), 85 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 86 - ) 87 - }, 88 - func() (*cli.Command, error) { 89 - return cli.New("get", 90 - cli.Short("Get a resource"), 91 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 92 - ) 93 - }, 94 - ), 95 - }, 96 - }, 97 - { 98 - name: "nested subcommands", 99 - options: []cli.Option{ 100 - cli.Short("My tool"), 101 - cli.OverrideArgs([]string{"completion"}), 102 - cli.SubCommands( 103 - cli.CompletionSubCommand(), 104 - func() (*cli.Command, error) { 105 - return cli.New("repo", 106 - cli.Short("Repository commands"), 107 - cli.SubCommands( 108 - func() (*cli.Command, error) { 109 - return cli.New("clone", 110 - cli.Short("Clone a repository"), 111 - cli.Flag(new(string), "branch", 'b', "Branch to clone"), 112 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 113 - ) 114 - }, 115 - ), 116 - ) 117 - }, 118 - ), 119 - }, 120 - }, 121 - { 122 - name: "three level nesting", 123 - options: []cli.Option{ 124 - cli.Short("My tool"), 125 - cli.OverrideArgs([]string{"completion"}), 126 - cli.SubCommands( 127 - cli.CompletionSubCommand(), 128 - func() (*cli.Command, error) { 129 - return cli.New("repo", 130 - cli.Short("Repository commands"), 131 - cli.SubCommands( 132 - func() (*cli.Command, error) { 133 - return cli.New("pr", 134 - cli.Short("Pull request commands"), 135 - cli.SubCommands( 136 - func() (*cli.Command, error) { 137 - return cli.New("create", 138 - cli.Short("Create a pull request"), 139 - cli.Flag(new(string), "title", 't', "PR title"), 140 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 141 - ) 142 - }, 143 - ), 144 - ) 145 - }, 146 - ), 147 - ) 148 - }, 149 - ), 150 - }, 151 - }, 152 - { 153 - name: "mixed flag types", 154 - options: []cli.Option{ 155 - cli.Short("My tool"), 156 - cli.OverrideArgs([]string{"completion"}), 157 - cli.Flag(new(bool), "force", 'f', "Force the operation"), 158 - cli.Flag(new(string), "output", 'o', "Output file"), 159 - cli.Flag(new(flag.Count), "verbose", 'v', "Verbosity level"), 160 - cli.Flag(new(bool), "dry-run", flag.NoShortHand, "Dry run, make no changes"), 161 - cli.Flag(new(int), "timeout", flag.NoShortHand, "Timeout in seconds"), 162 - cli.Flag(new([]string), "tags", flag.NoShortHand, "Tags to apply"), 163 - cli.SubCommands(cli.CompletionSubCommand()), 164 - }, 165 - }, 166 - { 167 - name: "yaml special chars in usage", 168 - options: []cli.Option{ 169 - cli.Short("My tool"), 170 - cli.OverrideArgs([]string{"completion"}), 171 - // Colons, quotes, and parens in usage strings must be safely marshalled. 172 - cli.Flag(new(bool), "verbose", 'v', "Verbose: show all output"), 173 - cli.Flag(new(string), "config", 'c', `Config file (default: "~/.config.yaml")`), 174 - cli.SubCommands(cli.CompletionSubCommand()), 175 - }, 176 - }, 177 - { 178 - // Subcommands added in non-alphabetical order; output must be sorted. 179 - name: "subcommands sorted alphabetically", 180 - options: []cli.Option{ 181 - cli.Short("My tool"), 182 - cli.OverrideArgs([]string{"completion"}), 183 - cli.SubCommands( 184 - func() (*cli.Command, error) { 185 - return cli.New("repo", 186 - cli.Short("Repository commands"), 187 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 188 - ) 189 - }, 190 - func() (*cli.Command, error) { 191 - return cli.New("deploy", 192 - cli.Short("Deploy commands"), 193 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 194 - ) 195 - }, 196 - cli.CompletionSubCommand(), 197 - ), 198 - }, 199 - }, 200 - { 201 - // An intermediate command that has both user flags and its own subcommands. 202 - name: "mid-level command with flags and subcommands", 203 - options: []cli.Option{ 204 - cli.Short("My tool"), 205 - cli.OverrideArgs([]string{"completion"}), 206 - cli.SubCommands( 207 - cli.CompletionSubCommand(), 208 - func() (*cli.Command, error) { 209 - return cli.New("repo", 210 - cli.Short("Repository commands"), 211 - cli.Flag(new(string), "format", flag.NoShortHand, "Output format"), 212 - cli.SubCommands( 213 - func() (*cli.Command, error) { 214 - return cli.New("clone", 215 - cli.Short("Clone a repository"), 216 - cli.Flag(new(string), "branch", 'b', "Branch to clone"), 217 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 218 - ) 219 - }, 220 - ), 221 - ) 222 - }, 223 - ), 224 - }, 225 - }, 226 - { 227 - // Root has user flags and a deeply nested leaf command also has its own flags. 228 - name: "root flags with nested subcommand flags", 229 - options: []cli.Option{ 230 - cli.Short("My tool"), 231 - cli.OverrideArgs([]string{"completion"}), 232 - cli.Flag(new(bool), "force", 'f', "Force the operation"), 233 - cli.SubCommands( 234 - cli.CompletionSubCommand(), 235 - func() (*cli.Command, error) { 236 - return cli.New("deploy", 237 - cli.Short("Deploy commands"), 238 - cli.SubCommands( 239 - func() (*cli.Command, error) { 240 - return cli.New("prod", 241 - cli.Short("Deploy to production"), 242 - cli.Flag(new(bool), "dry-run", flag.NoShortHand, "Dry run mode"), 243 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 244 - ) 245 - }, 246 - ), 247 - ) 248 - }, 249 - ), 250 - }, 251 - }, 252 - { 253 - name: "duration flag", 254 - options: []cli.Option{ 255 - cli.Short("My tool"), 256 - cli.OverrideArgs([]string{"completion"}), 257 - cli.Flag(new(time.Duration), "timeout", 't', "Request timeout"), 258 - cli.SubCommands(cli.CompletionSubCommand()), 259 - }, 260 - }, 261 - { 262 - name: "float flag", 263 - options: []cli.Option{ 264 - cli.Short("My tool"), 265 - cli.OverrideArgs([]string{"completion"}), 266 - cli.Flag(new(float64), "threshold", flag.NoShortHand, "Minimum threshold"), 267 - cli.SubCommands(cli.CompletionSubCommand()), 268 - }, 269 - }, 270 - { 271 - name: "ip flag", 272 - options: []cli.Option{ 273 - cli.Short("My tool"), 274 - cli.OverrideArgs([]string{"completion"}), 275 - cli.Flag(new(net.IP), "bind", 'b', "Address to bind"), 276 - cli.SubCommands(cli.CompletionSubCommand()), 277 - }, 278 - }, 279 - { 280 - // A subcommand without an explicit Short inherits the package default description. 281 - name: "subcommand with default description", 282 - options: []cli.Option{ 283 - cli.Short("My tool"), 284 - cli.OverrideArgs([]string{"completion"}), 285 - cli.SubCommands( 286 - cli.CompletionSubCommand(), 287 - func() (*cli.Command, error) { 288 - return cli.New("frobnicate", 289 - cli.Run(func(_ context.Context, _ *cli.Command) error { return nil }), 290 - ) 291 - }, 292 - ), 293 - }, 294 - }, 295 - } 296 - 297 - for _, tt := range tests { 298 - t.Run(tt.name, func(t *testing.T) { 299 - snap := snapshot.New( 300 - t, 301 - snapshot.Update(*update), 302 - ) 303 - 304 - stdout := &bytes.Buffer{} 305 - 306 - cmd, err := cli.New("mytool", slices.Concat([]cli.Option{cli.Stdout(stdout)}, tt.options)...) 307 - test.Ok(t, err) 308 - 309 - err = cmd.Execute(t.Context()) 310 - test.Ok(t, err) 311 - 312 - snap.Snap(stdout.String()) 313 - }) 314 - } 315 - }
+1 -1
go.mod
··· 11 11 go.followtheprocess.codes/hue v1.1.0 12 12 go.followtheprocess.codes/snapshot v0.10.1 13 13 go.followtheprocess.codes/test v1.4.0 14 - go.yaml.in/yaml/v4 v4.0.0-rc.4 15 14 ) 16 15 17 16 require ( 18 17 go.followtheprocess.codes/diff v0.2.0 // indirect 18 + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect 19 19 golang.org/x/sys v0.43.0 // indirect 20 20 golang.org/x/term v0.42.0 // indirect 21 21 )
-13
examples/README.md
··· 8 8 - [`./subcommands`](#subcommands) 9 9 - [`./namedargs`](#namedargs) 10 10 - [`./cancel`](#cancel) 11 - - [`./completion`](#completion) 12 11 - [TODO](#todo) 13 12 14 13 ## `./cover` ··· 39 38 40 39 ![cancel](../docs/img/cancel.gif) 41 40 42 - ## `./completion` 43 - 44 - Demonstrates how to add shell completion support to any CLI by wiring in `cli.CompletionSubCommand()`. Running `mytool completion` outputs a [carapace-spec] YAML document describing the full command tree. Redirect it once to register completions with [carapace-bin]: 45 - 46 - ```shell 47 - mytool completion > ~/.config/carapace/specs/mytool.yaml 48 - ``` 49 - 50 - carapace-bin then provides completions across bash, zsh, fish, nushell, PowerShell, and more — no shell-specific scripts required. 51 - 52 41 ### TODO 53 42 54 43 - Replicate one or two well known CLI tools as an example ··· 57 46 58 47 [quickstart]: <https://github.com/FollowTheProcess/cli#quickstart> 59 48 [freeze]: <https://github.com/charmbracelet/freeze> 60 - [carapace-spec]: <https://github.com/carapace-sh/carapace-spec> 61 - [carapace-bin]: <https://github.com/carapace-sh/carapace-bin>
-92
examples/completion/cli.go
··· 1 - package main 2 - 3 - import ( 4 - "context" 5 - "fmt" 6 - "strings" 7 - 8 - "go.followtheprocess.codes/cli" 9 - "go.followtheprocess.codes/cli/flag" 10 - ) 11 - 12 - // BuildCLI constructs the root command with two subcommands and a completion 13 - // subcommand wired in via [cli.CompletionSubCommand]. 14 - // 15 - // Running "mytool completion" outputs a carapace-spec YAML document that 16 - // describes the full command tree. Redirect it once to register completions 17 - // with carapace-bin: 18 - // 19 - // mytool completion > ~/.config/carapace/specs/mytool.yaml 20 - // 21 - // carapace-bin then provides completions across bash, zsh, fish, nushell, 22 - // PowerShell, and more — no shell-specific scripts required. 23 - func BuildCLI() (*cli.Command, error) { 24 - return cli.New( 25 - "mytool", 26 - cli.Short("An example tool with shell completion support"), 27 - cli.Version("dev"), 28 - cli.Example("Say hello", "mytool hello --name Alice"), 29 - cli.Example("Say goodbye loudly", "mytool goodbye --name Bob --shout"), 30 - cli.Example("Register completions (run once)", "mytool completion > ~/.config/carapace/specs/mytool.yaml"), 31 - cli.SubCommands( 32 - buildHelloCommand, 33 - buildGoodbyeCommand, 34 - cli.CompletionSubCommand(), // wire in completion support 35 - ), 36 - ) 37 - } 38 - 39 - type helloOptions struct { 40 - name string 41 - verbosity flag.Count 42 - } 43 - 44 - func buildHelloCommand() (*cli.Command, error) { 45 - var opts helloOptions 46 - 47 - return cli.New( 48 - "hello", 49 - cli.Short("Greet someone"), 50 - cli.Example("Greet a person", "mytool hello --name Alice"), 51 - cli.Example("Greet verbosely", "mytool hello --name Alice -vvv"), 52 - cli.Flag(&opts.name, "name", 'n', "Name of the person to greet", cli.FlagDefault("World")), 53 - cli.Flag(&opts.verbosity, "verbose", 'v', "Increase output verbosity"), 54 - cli.Run(func(_ context.Context, cmd *cli.Command) error { 55 - fmt.Fprintf(cmd.Stdout(), "Hello, %s!\n", opts.name) 56 - 57 - if opts.verbosity > 0 { 58 - fmt.Fprintf(cmd.Stdout(), "(verbosity level: %d)\n", opts.verbosity) 59 - } 60 - 61 - return nil 62 - }), 63 - ) 64 - } 65 - 66 - type goodbyeOptions struct { 67 - name string 68 - shout bool 69 - } 70 - 71 - func buildGoodbyeCommand() (*cli.Command, error) { 72 - var opts goodbyeOptions 73 - 74 - return cli.New( 75 - "goodbye", 76 - cli.Short("Bid someone farewell"), 77 - cli.Example("Say goodbye", "mytool goodbye --name Bob"), 78 - cli.Example("Say it louder", "mytool goodbye --name Bob --shout"), 79 - cli.Flag(&opts.name, "name", 'n', "Name of the person to farewell", cli.FlagDefault("World")), 80 - cli.Flag(&opts.shout, "shout", 's', "Say the farewell in uppercase"), 81 - cli.Run(func(_ context.Context, cmd *cli.Command) error { 82 - msg := fmt.Sprintf("Goodbye, %s!", opts.name) 83 - if opts.shout { 84 - msg = strings.ToUpper(msg) 85 - } 86 - 87 - fmt.Fprintln(cmd.Stdout(), msg) 88 - 89 - return nil 90 - }), 91 - ) 92 - }
-29
examples/completion/main.go
··· 1 - // Package completion demonstrates how to add shell completion support to a CLI 2 - // using [cli.CompletionSubCommand]. 3 - package main 4 - 5 - import ( 6 - "context" 7 - "fmt" 8 - "os" 9 - ) 10 - 11 - func main() { 12 - if err := run(); err != nil { 13 - fmt.Fprintf(os.Stderr, "Error: %s\n", err) 14 - os.Exit(1) 15 - } 16 - } 17 - 18 - func run() error { 19 - cmd, err := BuildCLI() 20 - if err != nil { 21 - return fmt.Errorf("could not build root command: %w", err) 22 - } 23 - 24 - if err := cmd.Execute(context.Background()); err != nil { 25 - return fmt.Errorf("could not execute root command: %w", err) 26 - } 27 - 28 - return nil 29 - }
-14
testdata/snapshots/TestCompletionSpec/bool_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -f, --force: Force deletion 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/count_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -v, --verbose*: Verbosity level 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/duration_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -t, --timeout=: Request timeout 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/float_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - --threshold=: Minimum threshold 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/ip_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -b, --bind=: Address to bind 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/long-only_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - --dry-run: Dry run mode 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-21
testdata/snapshots/TestCompletionSpec/mid-level_command_with_flags_and_subcommands.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: repo 14 - description: Repository commands 15 - flags: 16 - --format=: Output format 17 - commands: 18 - - name: clone 19 - description: Clone a repository 20 - flags: 21 - -b, --branch=: Branch to clone
-19
testdata/snapshots/TestCompletionSpec/mixed_flag_types.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - --dry-run: Dry run, make no changes 9 - --tags=: Tags to apply 10 - --timeout=: Timeout in seconds 11 - -f, --force: Force the operation 12 - -o, --output=: Output file 13 - -v, --verbose*: Verbosity level 14 - persistentflags: 15 - -V, --version: Show version info for mytool 16 - -h, --help: Show help for mytool 17 - commands: 18 - - name: completion 19 - description: Output a carapace-spec YAML document to stdout
-19
testdata/snapshots/TestCompletionSpec/nested_subcommands.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: repo 14 - description: Repository commands 15 - commands: 16 - - name: clone 17 - description: Clone a repository 18 - flags: 19 - -b, --branch=: Branch to clone
-12
testdata/snapshots/TestCompletionSpec/no_user_flags.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout
-21
testdata/snapshots/TestCompletionSpec/root_flags_with_nested_subcommand_flags.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -f, --force: Force the operation 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout 15 - - name: deploy 16 - description: Deploy commands 17 - commands: 18 - - name: prod 19 - description: Deploy to production 20 - flags: 21 - --dry-run: Dry run mode
-14
testdata/snapshots/TestCompletionSpec/slice_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -t, --tags=: Tags to apply 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/string_flag.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -o, --output=: Output file 9 - persistentflags: 10 - -V, --version: Show version info for mytool 11 - -h, --help: Show help for mytool 12 - commands: 13 - - name: completion 14 - description: Output a carapace-spec YAML document to stdout
-14
testdata/snapshots/TestCompletionSpec/subcommand_with_default_description.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: frobnicate 14 - description: A placeholder for something cool
-16
testdata/snapshots/TestCompletionSpec/subcommands_sorted_alphabetically.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: deploy 14 - description: Deploy commands 15 - - name: repo 16 - description: Repository commands
-22
testdata/snapshots/TestCompletionSpec/three_level_nesting.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: repo 14 - description: Repository commands 15 - commands: 16 - - name: pr 17 - description: Pull request commands 18 - commands: 19 - - name: create 20 - description: Create a pull request 21 - flags: 22 - -t, --title=: PR title
-16
testdata/snapshots/TestCompletionSpec/with_subcommands.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - persistentflags: 8 - -V, --version: Show version info for mytool 9 - -h, --help: Show help for mytool 10 - commands: 11 - - name: completion 12 - description: Output a carapace-spec YAML document to stdout 13 - - name: delete 14 - description: Delete a resource 15 - - name: get 16 - description: Get a resource
-15
testdata/snapshots/TestCompletionSpec/yaml_special_chars_in_usage.snap
··· 1 - source: completion_test.go 2 - expression: stdout.String() 3 - --- 4 - | 5 - name: mytool 6 - description: My tool 7 - flags: 8 - -c, --config=: 'Config file (default: "~/.config.yaml")' 9 - -v, --verbose: 'Verbose: show all output' 10 - persistentflags: 11 - -V, --version: Show version info for mytool 12 - -h, --help: Show help for mytool 13 - commands: 14 - - name: completion 15 - description: Output a carapace-spec YAML document to stdout