Skip to content

Commit ac9f5b9

Browse files
committed
Flesh out README with an example
1 parent 805197b commit ac9f5b9

File tree

3 files changed

+121
-5
lines changed

3 files changed

+121
-5
lines changed

README.md

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,72 @@
11
# serpent
22

3-
`serpent` is a CLI configuration framework based on [cobra](https://github.com/spf13/cobra).
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/coder/serpent.svg)](https://pkg.go.dev/github.com/coder/serpent)
44

5+
`serpent` is a CLI configuration framework based on [cobra](https://github.com/spf13/cobra) and used by [coder/coder](https://github.com/coder/coder).
6+
It's designed for large-scale CLIs with dozens of commands and hundreds
7+
of options. If you're building a small, self-contained tool, go with
8+
cobra.
9+
10+
## Basic Usage
11+
12+
See `example/echo`:
13+
14+
```go
15+
package main
16+
17+
import (
18+
"os"
19+
"strings"
20+
21+
"github.com/coder/serpent"
22+
)
23+
24+
func main() {
25+
var upper bool
26+
cmd := serpent.Cmd{
27+
Use: "echo <text>",
28+
Short: "Prints the given text to the console.",
29+
Options: serpent.OptionSet{
30+
{
31+
Name: "upper",
32+
Value: serpent.BoolOf(&upper),
33+
Flag: "upper",
34+
Description: "Prints the text in upper case.",
35+
},
36+
},
37+
Handler: func(inv *serpent.Invocation) error {
38+
if len(inv.Args) == 0 {
39+
inv.Stderr.Write([]byte("error: missing text\n"))
40+
os.Exit(1)
41+
}
42+
43+
text := inv.Args[0]
44+
if upper {
45+
text = strings.ToUpper(text)
46+
}
47+
48+
inv.Stdout.Write([]byte(text))
49+
return nil
50+
},
51+
}
52+
53+
err := cmd.Invoke().WithOS().Run()
54+
if err != nil {
55+
panic(err)
56+
}
57+
}
58+
```
59+
60+
## Design
61+
This Design section assumes you have a good understanding of how `cobra` works.
62+
63+
### Options
64+
65+
Serpent is designed for high-configurability. To us, that means providing
66+
many ways to configure the same value (env, YAML, flags, etc.) and keeping
67+
the code clean and testable as you scale the number of options.
68+
69+
70+
## More coming...
71+
This README is a stub for now. We'll better explain the design and usage
72+
of `serpent` in the future.

cmd.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ func (c *Cmd) Walk(fn func(*Cmd)) {
7676
}
7777
}
7878

79-
// PrepareAll performs initialization and linting on the command and all its children.
80-
func (c *Cmd) PrepareAll() error {
79+
// init performs initialization and linting on the command and all its children.
80+
func (c *Cmd) init() error {
8181
if c.Use == "" {
82-
return xerrors.New("command must have a Use field so that it has a name")
82+
c.Use = "unnamed"
8383
}
8484
var merr error
8585

@@ -116,7 +116,7 @@ func (c *Cmd) PrepareAll() error {
116116
})
117117
for _, child := range c.Children {
118118
child.Parent = c
119-
err := child.PrepareAll()
119+
err := child.init()
120120
if err != nil {
121121
merr = errors.Join(merr, xerrors.Errorf("command %v: %w", child.Name(), err))
122122
}
@@ -493,6 +493,11 @@ func findArg(want string, args []string, fs *pflag.FlagSet) (int, error) {
493493
//
494494
//nolint:revive
495495
func (inv *Invocation) Run() (err error) {
496+
err = inv.Command.init()
497+
if err != nil {
498+
return xerrors.Errorf("initializing command: %w", err)
499+
}
500+
496501
defer func() {
497502
// Pflag is panicky, so additional context is helpful in tests.
498503
if flag.Lookup("test.v") == nil {

example/echo/main.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"strings"
6+
7+
"github.com/coder/serpent"
8+
)
9+
10+
func main() {
11+
var upper bool
12+
cmd := serpent.Cmd{
13+
Use: "echo <text>",
14+
Short: "Prints the given text to the console.",
15+
Options: serpent.OptionSet{
16+
{
17+
Name: "upper",
18+
Value: serpent.BoolOf(&upper),
19+
Flag: "upper",
20+
Description: "Prints the text in upper case.",
21+
},
22+
},
23+
Handler: func(inv *serpent.Invocation) error {
24+
if len(inv.Args) == 0 {
25+
inv.Stderr.Write([]byte("error: missing text\n"))
26+
os.Exit(1)
27+
}
28+
29+
text := inv.Args[0]
30+
if upper {
31+
text = strings.ToUpper(text)
32+
}
33+
34+
inv.Stdout.Write([]byte(text))
35+
return nil
36+
},
37+
}
38+
39+
err := cmd.Invoke().WithOS().Run()
40+
if err != nil {
41+
panic(err)
42+
}
43+
}

0 commit comments

Comments
 (0)