33[ ![ GoDoc] ( https://godoc.org/github.com/pressly/cli?status.svg )] ( https://pkg.go.dev/github.com/pressly/cli#pkg-index )
44[ ![ CI] ( https://github.com/pressly/cli/actions/workflows/ci.yaml/badge.svg )] ( https://github.com/pressly/cli/actions/workflows/ci.yaml )
55
6- A Go package for building CLI applications. Extends the standard library's ` flag ` package to support
7- [ flags anywhere] ( https://mfridman.com/blog/2024/allowing-flags-anywhere-on-the-cli/ ) in command
8- arguments.
9-
10- ## Features
11-
12- The ** bare minimum** to build a CLI application while leveraging the standard library's ` flag `
13- package.
14-
15- - Nested subcommands for organizing complex CLIs
16- - Flexible flag parsing, allowing flags anywhere
17- - Subcommands inherit flags from parent commands
18- - Type-safe flag access
19- - Automatic generation of help text and usage information
20- - Suggestions for misspelled or incomplete commands
21-
22- ### But why?
23-
24- This package is intentionally minimal. It aims to be a building block for CLI applications that want
25- to leverage the standard library's ` flag ` package while providing a bit more structure and
26- flexibility.
27-
28- - Build maintainable command-line tools quickly
29- - Focus on application logic rather than framework complexity
30- - Extend functionality ** only when needed**
31-
32- Sometimes less is more. While other frameworks offer extensive features, this package focuses on
33- core functionality.
6+ An intentionally minimal Go package for building CLI applications. Extends the standard library's
7+ ` flag ` package to support [ flags
8+ anywhere] ( https://mfridman.com/blog/2024/allowing-flags-anywhere-on-the-cli/ ) in command arguments,
9+ adds nested subcommands, and gets out of the way.
3410
3511## Installation
3612
3713``` bash
3814go get github.com/pressly/cli@latest
3915```
4016
41- Required go version: 1.21 or higher
17+ Requires Go 1.21 or higher.
4218
4319## Quick Start
4420
45- Here's a simple example of a CLI application that echoes back the input with a required ` -c ` flag to
46- capitalize the output:
47-
4821``` go
4922root := &cli.Command {
50- Name : " echo" ,
51- Usage : " echo [flags] <text>..." ,
52- ShortHelp : " echo is a simple command that prints the provided text" ,
53- Flags : cli.FlagsFunc (func (f *flag.FlagSet ) {
54- // Add a flag to capitalize the input
55- f.Bool (" c" , false , " capitalize the input" )
56- }),
57- FlagsMetadata : []cli.FlagMetadata {
58- {Name: " c" , Required: true },
59- },
23+ Name : " greet" ,
24+ ShortHelp : " Print a greeting" ,
6025 Exec : func (ctx context.Context , s *cli.State ) error {
61- if len (s.Args ) == 0 {
62- return errors.New (" must provide text to echo, see --help" )
63- }
64- output := strings.Join (s.Args , " " )
65- // If -c flag is set, capitalize the output
66- if cli.GetFlag [bool ](s, " c" ) {
67- output = strings.ToUpper (output)
68- }
69- fmt.Fprintln (s.Stdout , output)
26+ fmt.Fprintln (s.Stdout , " hello, world!" )
7027 return nil
7128 },
7229}
73- if err := cli.Parse (root, os.Args [1 :]); err != nil {
74- if errors.Is (err, flag.ErrHelp ) {
75- fmt.Fprintf (os.Stdout , " %s \n " , cli.DefaultUsage (root))
76- return
77- }
78- fmt.Fprintf (os.Stderr , " error: %v \n " , err)
79- os.Exit (1 )
80- }
81- if err := cli.Run (context.Background (), root, nil ); err != nil {
30+ if err := cli.ParseAndRun (ctx, root, os.Args [1 :], nil ); err != nil {
8231 fmt.Fprintf (os.Stderr , " error: %v \n " , err)
8332 os.Exit (1 )
8433}
8534```
8635
87- ## Command Structure
88-
89- Each command is represented by a ` Command ` struct:
90-
91- ``` go
92- type Command struct {
93- Name string // Required
94- Usage string
95- ShortHelp string
96- UsageFunc func (*Command) string
97- Flags *flag.FlagSet
98- FlagsMetadata []FlagMetadata
99- SubCommands []*Command
100- Exec func (ctx context.Context , s *State) error
101- }
102- ```
103-
104- The ` Name ` field is the command's name and is ** required** .
105-
106- The ` Usage ` and ` ShortHelp ` fields are used to generate help text. Nice-to-have but not required.
107-
108- The ` Flags ` field is a ` *flag.FlagSet ` that defines the command's flags.
109-
110- > [ !TIP]
111- >
112- > There's a convenience function ` FlagsFunc ` that allows you to define flags inline:
113-
114- ``` go
115- root := &cli.Command {
116- Flags : cli.FlagsFunc (func (f *flag.FlagSet ) {
117- fs.Bool (" verbose" , false , " enable verbose output" )
118- fs.String (" output" , " " , " output file" )
119- fs.Int (" count" , 0 , " number of items" )
120- }),
121- FlagsMetadata : []cli.FlagMetadata {
122- {Name: " c" , Required: true },
123- },
124- }
125- ```
126-
127- The optional ` FlagsMetadata ` field is a way to extend defined flags. The ` flag ` package alone is a
128- bit limiting, so we add this to provide the most common features, such as handling of required
129- flags.
130-
131- The ` SubCommands ` field is a list of ` *Command ` structs that represent subcommands. This allows you
132- to organize CLI applications into a hierarchy of commands. Each subcommand can have its own flags
133- and business logic.
134-
135- The ` Exec ` field is a function that is called when the command is executed. This is where you put
136- business logic.
36+ ` ParseAndRun ` parses the command hierarchy, handles ` --help ` automatically, and executes the
37+ resolved command. For applications that need work between parsing and execution, use ` Parse ` and
38+ ` Run ` separately. See the [ examples] ( examples/ ) directory for more complete applications.
13739
138- ## Flag Access
40+ ## Flags
13941
140- Flags can be accessed using the type-safe ` GetFlag ` function, called inside the ` Exec ` function:
42+ ` FlagsFunc ` is a convenience for defining flags inline. Use ` FlagsMetadata ` to extend the standard
43+ ` flag ` package with features like required flag enforcement:
14144
14245``` go
143- // Access boolean flag
144- verbose := cli.GetFlag [bool ](state, " verbose" )
145- // Access string flag
146- output := cli.GetFlag [string ](state, " output" )
147- // Access integer flag
148- count := cli.GetFlag [int ](state, " count" )
46+ Flags: cli.FlagsFunc (func (f *flag.FlagSet ) {
47+ f.Bool (" verbose" , false , " enable verbose output" )
48+ f.String (" output" , " " , " output file" )
49+ }),
50+ FlagsMetadata: []cli.FlagMetadata {
51+ {Name: " output" , Required: true },
52+ },
14953```
15054
151- ### State Inheritance
152-
153- Child commands automatically inherit their parent command's flags:
55+ Access flags inside ` Exec ` with the type-safe ` GetFlag ` function:
15456
15557``` go
156- // Parent command with a verbose flag
157- root := cli.Command {
158- Name : " root" ,
159- Flags : cli.FlagsFunc (func (f *flag.FlagSet ) {
160- f.Bool (" verbose" , false , " enable verbose mode" )
161- }),
162- }
163-
164- // Child command that can access parent's verbose flag
165- sub := cli.Command {
166- Name : " sub" ,
167- Exec : func (ctx context.Context , s *cli.State ) error {
168- verbose := cli.GetFlag [bool ](s, " verbose" )
169- if verbose {
170- fmt.Println (" Verbose mode enabled" )
171- }
172- return nil
173- },
174- }
58+ verbose := cli.GetFlag [bool ](s, " verbose" )
59+ output := cli.GetFlag [string ](s, " output" )
17560```
17661
177- ## Help System
62+ Child commands automatically inherit flags from parent commands, so a ` --verbose ` flag on the root
63+ is accessible from any subcommand via ` GetFlag ` .
17864
179- Help text is automatically generated, but you can customize it by setting the ` UsageFunc ` field.
65+ ## Subcommands
18066
181- There is a ` DefaultUsage ` function that generates a default help text for a command, which is useful
182- to display when ` flag.ErrHelp ` is returned from ` Parse ` :
67+ Commands can have nested subcommands, each with their own flags and ` Exec ` function:
18368
18469``` go
185- if err := cli.Parse (root, os.Args [1 :]); err != nil {
186- if errors.Is (err, flag.ErrHelp ) {
187- fmt.Fprintf (os.Stdout , " %s \n " , cli.DefaultUsage (root)) // Display help text and exit
188- return
189- }
190- fmt.Fprintf (os.Stderr , " error: %v \n " , err)
191- os.Exit (1 )
70+ root := &cli.Command {
71+ Name : " todo" ,
72+ Usage : " todo <command> [flags]" ,
73+ ShortHelp : " A simple CLI for managing your tasks" ,
74+ SubCommands : []*cli.Command {
75+ {
76+ Name: " list" ,
77+ ShortHelp: " List all tasks" ,
78+ Exec: func (ctx context.Context , s *cli.State ) error {
79+ // ...
80+ return nil
81+ },
82+ },
83+ },
19284}
19385```
19486
195- ## Usage Syntax Conventions
196-
197- When reading command usage strings, the following syntax is used:
87+ For a more complete example with deeply nested subcommands, see the [ todo
88+ example] ( examples/cmd/task/ ) .
19889
199- | Syntax | Description |
200- | ------------- | -------------------------- |
201- | ` <required> ` | Required argument |
202- | ` [optional] ` | Optional argument |
203- | ` <arg>... ` | One or more arguments |
204- | ` [arg]... ` | Zero or more arguments |
205- | ` (a\|b) ` | Must choose one of a or b |
206- | ` [-f <file>] ` | Flag with value (optional) |
207- | ` -f <file> ` | Flag with value (required) |
90+ ## Help
20891
209- Examples:
92+ Help text is generated automatically and displayed when ` --help ` is passed. To customize it, set the
93+ ` UsageFunc ` field on a command.
21094
211- ``` bash
212- # Multiple source files, one destination
213- mv < source> ... < dest>
214-
215- # Required flag with value, optional config
216- build -t < tag> [config]...
217-
218- # Subcommands with own flags
219- docker (run| build) [--file < dockerfile> ] < image>
95+ ## Usage Syntax
22096
221- # Multiple flag values
222- find [--exclude < pattern> ]... < path>
223-
224- # Choice between options, required path
225- chmod (u+x| a+r) < file> ...
226-
227- # Flag groups with value
228- kubectl [-n < namespace> ] (get| delete) (pod| service) < name>
229- ```
97+ See [ docs/usage-syntax.md] ( docs/usage-syntax.md ) for conventions used in usage strings.
23098
23199## Status
232100
@@ -238,9 +106,9 @@ issue if you encounter any problems or have suggestions for improvement.
238106There are many great CLI libraries out there, but I always felt [ they were too heavy for my
239107needs] ( https://mfridman.com/blog/2021/a-simpler-building-block-for-go-clis/ ) .
240108
241- I was inspired by Peter Bourgon's [ ff] ( https://github.com/peterbourgon/ff ) library, specifically the
242- ` v3 ` branch, which was soooo close to what I wanted. But the ` v4 ` branch took a different direction
243- and I wanted to keep the simplicity of ` v3 ` . This library aims to pick up where ` v3 ` left off .
109+ Inspired by Peter Bourgon's [ ff] ( https://github.com/peterbourgon/ff ) library, specifically the ` v3 `
110+ branch, which was so close to what I wanted. The ` v4 ` branch took a different direction, and I wanted
111+ to keep the simplicity of ` v3 ` . This library carries that idea forward .
244112
245113## License
246114
0 commit comments