Skip to content

Commit 6005355

Browse files
authored
Add usage to readme (#5)
* add usage to `README.md` * add comprehensive usage
1 parent 3a251e8 commit 6005355

File tree

7 files changed

+253
-18
lines changed

7 files changed

+253
-18
lines changed

README.md

+101-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,108 @@ go get github.com/broothie/cli@latest
1515

1616
## Documentation
1717

18-
https://pkg.go.dev/github.com/broothie/cli
18+
Detailed documentation can be found at [pkg.go.dev](https://pkg.go.dev/github.com/broothie/cli).
1919

20-
## To Do
20+
## Usage
21+
22+
Using `cli` is as simple as:
23+
24+
```go
25+
// Create and run a command called "fileserver".
26+
// `Run` automatically passes down a `context.Background()` and parses `os.Args[1:]`.
27+
// If an error is returned, and it is either a `cli.ExitError` or an `*exec.ExitError`, the error's exit code will be used.
28+
// For any other errors returned, it exits with code 1.
29+
cli.Run("fileserver", "An HTTP server.",
30+
31+
// Add an optional positional argument called "root" which will default to ".".
32+
cli.AddArg("root", "Directory to serve from", cli.SetArgDefault(".")),
33+
34+
// Add an optional flag called "port" which will default to 3000.
35+
cli.AddFlag("port", "Port to run server.", cli.SetFlagDefault(3000)),
36+
37+
// Register a handler for this command.
38+
// If no handler is registered, it will simply print help and exit.
39+
cli.SetHandler(func(ctx context.Context) error {
40+
// Extract the value of the "root" argument.
41+
root, _ := cli.ArgValue[string](ctx, "root")
42+
43+
// Extract the value of the "port" flag.
44+
port, _ := cli.FlagValue[int](ctx, "port")
45+
46+
addr := fmt.Sprintf(":%d", port)
47+
return http.ListenAndServe(addr, http.FileServer(http.Dir(root)))
48+
}),
49+
)
50+
51+
```
52+
53+
Here's an example using the complete set of options:
54+
55+
```go
56+
cmd, err := cli.NewCommand("git", "Modern version control.",
57+
// Set command version
58+
cli.SetVersion("2.37.0"),
59+
60+
// Add a "--version" flag with a short flag "-V" for printing the command version
61+
cli.AddVersionFlag(cli.AddFlagShort('V')),
62+
63+
// Add a "--help" flag
64+
cli.AddHelpFlag(
65+
66+
// Add a short flag "-h" to help
67+
cli.AddFlagShort('h'),
68+
69+
// Make this flag inherited by sub-commands
70+
cli.SetFlagIsInherited(true),
71+
),
72+
73+
// Add a hidden "--debug" flag
74+
cli.AddFlag("debug", "Enable debugging",
75+
cli.SetFlagDefault(false), // Default parser for flags is cli.StringParser
76+
77+
// Make it hidden
78+
cli.SetFlagIsHidden(true),
79+
),
80+
81+
// Add a sub-command "clone"
82+
cli.AddSubCmd("clone", "Clone a repository.",
83+
84+
// Add a required argument "<url>"
85+
cli.AddArg("url", "Repository to clone.",
86+
87+
// Parse it into a *url.URL
88+
cli.SetArgParser(cli.URLParser),
89+
),
90+
91+
// Add optional argument "<dir?>"
92+
cli.AddArg("dir", "Directory to clone repo into.",
93+
94+
// Set its default value to "."
95+
cli.SetArgDefault("."),
96+
),
97+
98+
// Add a flag "--verbose"
99+
cli.AddFlag("verbose", "Be more verbose.",
100+
101+
// Add a short "-v"
102+
cli.AddFlagShort('v'),
103+
104+
// Make it a boolean that defaults to false
105+
cli.SetFlagDefault(false),
106+
),
107+
),
108+
)
109+
if err != nil {
110+
cli.ExitWithError(err)
111+
}
112+
113+
// Pass in your `context.Context` and args
114+
if err := cmd.Run(context.TODO(), os.Args[1:]); err != nil {
115+
cli.ExitWithError(err)
116+
}
117+
```
118+
119+
## Roadmap
21120

22121
- [ ] Audit bare `err` returns
23122
- [ ] Two types of errors: config and parse

command.go

+1-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"os"
7-
"os/exec"
87
"strings"
98

109
"github.com/bobg/errors"
@@ -53,13 +52,7 @@ func Run(name, description string, options ...option.Option[*Command]) {
5352
}
5453

5554
if err := command.Run(context.Background(), os.Args[1:]); err != nil {
56-
fmt.Println(err)
57-
58-
if exitErr := new(exec.ExitError); errors.As(err, &exitErr) {
59-
os.Exit(exitErr.ExitCode())
60-
} else {
61-
os.Exit(1)
62-
}
55+
ExitWithError(err)
6356
}
6457
}
6558

command_test.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package cli
22

3-
import (
4-
"os"
5-
)
3+
import "os"
64

75
func ExampleNewCommand() {
86
command, _ := NewCommand("server", "An http server.",
9-
AddHelpFlag(AddFlagShort('h')),
107
SetVersion("v0.1.0"),
118
AddVersionFlag(AddFlagShort('V')),
9+
AddHelpFlag(AddFlagShort('h')),
1210
AddFlag("port", "Port to run server on.",
1311
SetFlagDefault(3000),
1412
AddFlagShort('p'),
@@ -34,8 +32,8 @@ func ExampleNewCommand() {
3432
// proxy: Proxy requests to another server.
3533
//
3634
// Flags:
37-
// --help -h Print help. (type: bool, default: "false")
3835
// --version -V Print version. (type: bool, default: "false")
36+
// --help -h Print help. (type: bool, default: "false")
3937
// --port -p Port to run server on. (type: int, default: "3000")
4038
// --auth-required Whether to require authentication. (type: bool, default: "true")
4139
}

error.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
8+
"github.com/bobg/errors"
9+
)
10+
11+
type ExitError struct {
12+
Code int
13+
}
14+
15+
func (e ExitError) Error() string {
16+
return fmt.Sprintf("exit status %d", e.Code)
17+
}
18+
19+
func ExitCode(code int) ExitError {
20+
return ExitError{Code: code}
21+
}
22+
23+
func ExitWithError(err error) {
24+
fmt.Println(err)
25+
26+
if exitErr := new(ExitError); errors.As(err, &exitErr) {
27+
os.Exit(exitErr.Code)
28+
} else if exitErr := new(exec.ExitError); errors.As(err, &exitErr) {
29+
os.Exit(exitErr.ExitCode())
30+
} else {
31+
os.Exit(1)
32+
}
33+
}

examples_test.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
9+
"github.com/broothie/cli"
10+
)
11+
12+
func Example_basic_usage() {
13+
// Create and run a command called "fileserver".
14+
// `Run` automatically passes down a `context.Background()` and parses `os.Args[1:]`.
15+
// If an error is returned, and it is either a `cli.ExitError` or an `*exec.ExitError`, the error's exit code will be used.
16+
// For any other errors returned, it exits with code 1.
17+
cli.Run("fileserver", "An HTTP server.",
18+
19+
// Add an optional positional argument called "root" which will default to ".".
20+
cli.AddArg("root", "Directory to serve from", cli.SetArgDefault(".")),
21+
22+
// Add an optional flag called "port" (usage: --port) which will default to 3000.
23+
cli.AddFlag("port", "Port to run server.", cli.SetFlagDefault(3000)),
24+
25+
// Register a handler for this command.
26+
// If no handler is registered, it will simply print help and exit.
27+
cli.SetHandler(func(ctx context.Context) error {
28+
// Extract the value of the "root" argument.
29+
root, _ := cli.ArgValue[string](ctx, "root")
30+
31+
// Extract the value of the "port" flag.
32+
port, _ := cli.FlagValue[int](ctx, "port")
33+
34+
addr := fmt.Sprintf(":%d", port)
35+
return http.ListenAndServe(addr, http.FileServer(http.Dir(root)))
36+
}),
37+
)
38+
}
39+
40+
func Example_kitchen_sink() {
41+
// Create a new command
42+
cmd, err := cli.NewCommand("git", "Modern version control.",
43+
// Set command version
44+
cli.SetVersion("2.37.0"),
45+
46+
// Add a "--version" flag with a short flag "-V" for printing the command version
47+
cli.AddVersionFlag(cli.AddFlagShort('V')),
48+
49+
// Add a "--help" flag
50+
cli.AddHelpFlag(
51+
52+
// Add a short flag "-h" to help
53+
cli.AddFlagShort('h'),
54+
55+
// Make this flag inherited by sub-commands
56+
cli.SetFlagIsInherited(true),
57+
),
58+
59+
// Add a hidden "--debug" flag
60+
cli.AddFlag("debug", "Enable debugging",
61+
cli.SetFlagDefault(false), // Default parser for flags is cli.StringParser
62+
63+
// Make it hidden
64+
cli.SetFlagIsHidden(true),
65+
),
66+
67+
// Add a sub-command "clone"
68+
cli.AddSubCmd("clone", "Clone a repository.",
69+
70+
// Add a required argument "<url>"
71+
cli.AddArg("url", "Repository to clone.",
72+
73+
// Parse it into a *url.URL
74+
cli.SetArgParser(cli.URLParser),
75+
),
76+
77+
// Add optional argument "<dir?>"
78+
cli.AddArg("dir", "Directory to clone repo into.",
79+
80+
// Set its default value to "."
81+
cli.SetArgDefault("."),
82+
),
83+
84+
// Add a flag "--verbose"
85+
cli.AddFlag("verbose", "Be more verbose.",
86+
87+
// Add a short "-v"
88+
cli.AddFlagShort('v'),
89+
90+
// Make it a boolean that defaults to false
91+
cli.SetFlagDefault(false),
92+
),
93+
),
94+
)
95+
if err != nil {
96+
cli.ExitWithError(err)
97+
}
98+
99+
// Pass in your `context.Context` and args
100+
if err := cmd.Run(context.TODO(), os.Args[1:]); err != nil {
101+
cli.ExitWithError(err)
102+
}
103+
}

help.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,16 @@ func (h helpContext) ArgumentList() string {
7777

7878
func (h helpContext) ArgumentTable() (string, error) {
7979
return tableToString(lo.Map(h.Arguments(), func(argument *Argument, _ int) []string {
80+
valueInfo := fmt.Sprintf("(type: %T)", argument.parser.Type())
81+
if argument.isOptional() {
82+
valueInfo = fmt.Sprintf("(type: %T, default: %q)", argument.parser.Type(), fmt.Sprint(argument.defaultValue))
83+
}
84+
8085
return []string{
8186
"",
8287
argument.inBrackets(),
8388
argument.description,
84-
fmt.Sprintf("(type: %T)", argument.parser.Type()),
89+
valueInfo,
8590
}
8691
}))
8792
}

help_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ func TestCommand_renderHelp(t *testing.T) {
2929
SetFlagDefaultAndParser(CustomType{Field: "field default"}, func(s string) (CustomType, error) { return CustomType{Field: s}, nil }),
3030
),
3131
AddFlag("hidden-flag", "some hidden flag", SetFlagIsHidden(true)),
32+
AddArg("another-arg", "another arg",
33+
SetArgDefault(123),
34+
),
3235
AddArg("some-arg", "some arg",
3336
SetArgParser(TimeLayoutParser(time.RubyDate)),
3437
),
@@ -44,10 +47,11 @@ func TestCommand_renderHelp(t *testing.T) {
4447
test v1.2.3-rc10: test command
4548
4649
Usage:
47-
test [flags] <some-arg>
50+
test [flags] <some-arg> <another-arg?>
4851
4952
Arguments:
50-
<some-arg> some arg (type: time.Time)
53+
<some-arg> some arg (type: time.Time)
54+
<another-arg?> another arg (type: int, default: "123")
5155
5256
Flags:
5357
--help Print help. (type: bool, default: "false")

0 commit comments

Comments
 (0)