Skip to content

Commit

Permalink
Flesh out README with an example
Browse files Browse the repository at this point in the history
  • Loading branch information
ammario committed Feb 21, 2024
1 parent 805197b commit ac9f5b9
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 5 deletions.
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,72 @@
# serpent

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

`serpent` is a CLI configuration framework based on [cobra](https://github.com/spf13/cobra) and used by [coder/coder](https://github.com/coder/coder).
It's designed for large-scale CLIs with dozens of commands and hundreds
of options. If you're building a small, self-contained tool, go with
cobra.

## Basic Usage

See `example/echo`:

```go
package main

import (
"os"
"strings"

"github.com/coder/serpent"
)

func main() {
var upper bool
cmd := serpent.Cmd{
Use: "echo <text>",
Short: "Prints the given text to the console.",
Options: serpent.OptionSet{
{
Name: "upper",
Value: serpent.BoolOf(&upper),
Flag: "upper",
Description: "Prints the text in upper case.",
},
},
Handler: func(inv *serpent.Invocation) error {
if len(inv.Args) == 0 {
inv.Stderr.Write([]byte("error: missing text\n"))
os.Exit(1)
}

text := inv.Args[0]
if upper {
text = strings.ToUpper(text)
}

inv.Stdout.Write([]byte(text))
return nil
},
}

err := cmd.Invoke().WithOS().Run()
if err != nil {
panic(err)
}
}
```

## Design
This Design section assumes you have a good understanding of how `cobra` works.

### Options

Serpent is designed for high-configurability. To us, that means providing
many ways to configure the same value (env, YAML, flags, etc.) and keeping
the code clean and testable as you scale the number of options.


## More coming...
This README is a stub for now. We'll better explain the design and usage
of `serpent` in the future.
13 changes: 9 additions & 4 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ func (c *Cmd) Walk(fn func(*Cmd)) {
}
}

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

Expand Down Expand Up @@ -116,7 +116,7 @@ func (c *Cmd) PrepareAll() error {
})
for _, child := range c.Children {
child.Parent = c
err := child.PrepareAll()
err := child.init()
if err != nil {
merr = errors.Join(merr, xerrors.Errorf("command %v: %w", child.Name(), err))
}
Expand Down Expand Up @@ -493,6 +493,11 @@ func findArg(want string, args []string, fs *pflag.FlagSet) (int, error) {
//
//nolint:revive
func (inv *Invocation) Run() (err error) {
err = inv.Command.init()
if err != nil {
return xerrors.Errorf("initializing command: %w", err)
}

defer func() {
// Pflag is panicky, so additional context is helpful in tests.
if flag.Lookup("test.v") == nil {
Expand Down
43 changes: 43 additions & 0 deletions example/echo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"os"
"strings"

"github.com/coder/serpent"
)

func main() {
var upper bool
cmd := serpent.Cmd{
Use: "echo <text>",
Short: "Prints the given text to the console.",
Options: serpent.OptionSet{
{
Name: "upper",
Value: serpent.BoolOf(&upper),
Flag: "upper",
Description: "Prints the text in upper case.",
},
},
Handler: func(inv *serpent.Invocation) error {
if len(inv.Args) == 0 {
inv.Stderr.Write([]byte("error: missing text\n"))

Check failure on line 25 in example/echo/main.go

View workflow job for this annotation

GitHub Actions / make

Error return value of `inv.Stderr.Write` is not checked (errcheck)
os.Exit(1)
}

text := inv.Args[0]
if upper {
text = strings.ToUpper(text)
}

inv.Stdout.Write([]byte(text))

Check failure on line 34 in example/echo/main.go

View workflow job for this annotation

GitHub Actions / make

Error return value of `inv.Stdout.Write` is not checked (errcheck)
return nil
},
}

err := cmd.Invoke().WithOS().Run()
if err != nil {
panic(err)
}
}

0 comments on commit ac9f5b9

Please # to comment.