Skip to content

Commit

Permalink
Some docs and README improvements (#80)
Browse files Browse the repository at this point in the history
* Some README tweaks

* Add a README under ./examples

* Use links rather than files

* Fix the links

* Add TODO around examples
  • Loading branch information
FollowTheProcess authored Aug 11, 2024
1 parent ede821c commit fa0eb56
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 19 deletions.
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Tiny, simple, but powerful CLI framework for modern Go 🚀

<p align="center">
<img src="./docs/img/demo.png" alt="demo">
<img src="https://github.com/FollowTheProcess/cli/raw/main/docs/img/demo.png" alt="demo">
</p>

> [!WARNING]
Expand Down Expand Up @@ -77,25 +77,29 @@ func runQuickstart(count *int) func(cmd *cli.Command, args []string) error {

Will get you the following:

![quickstart](./docs/img/quickstart.gif)
![quickstart](https://github.com/FollowTheProcess/cli/raw/main/docs/img/quickstart.gif)

> [!TIP]
> See more examples under [`./examples`](https://github.com/FollowTheProcess/cli/tree/main/examples)
### Core Principles

#### 😱 Well behaved libraries don't panic

`cli` validates heavily and returns errors for you to handle. By contrast [spf13/cobra] (and [spf13/pflag]) panic in a number of conditions including:

- Duplicate subcommand being added
- Duplicate subcommand added
- Command adding itself as a subcommand
- Duplicate flag added
- Invalid shorthand flag letter

The design of `cli` is such that commands are instantiated with `cli.New` and a number of [functional options]. These options are in charge of configuring your command and each will perform validation prior to applying the setting.

These errors are joined and bubbled up to you in one go via `cli.New` so you don't have to play error whack-a-mole.
These errors are joined and bubbled up to you in one go via `cli.New` so you don't have to play error whack-a-mole, and more importantly your application won't panic!

#### 🧘🏻 Keep it Simple

`cli` has a tiny public interface and gives you only what you need to build amazing CLI apps, no more confusing options and hundreds of struct fields.
`cli` has an intentionally tiny public interface and gives you only what you need to build amazing CLI apps, no more confusing options and hundreds of struct fields.

There is one and only one way to do things (and that is *usually* to use an option in `cli.New`)

Expand All @@ -112,7 +116,7 @@ var force bool
cli.New("demo", cli.Flag(&force, "force", 'f', false, "Force something"))
```

Note the type `bool` is inferred by `cli.Flag`. No more `flag.BoolStringSliceVarP` 🎉
Note the type `bool` is inferred by `cli.Flag`. This will work with any type allowed by the `Flaggable` generic constraint so you'll get compile time feedback if you've got it wrong. No more `flag.BoolStringSliceVarP` 🎉

#### 🥹 A Beautiful API

Expand All @@ -134,7 +138,7 @@ cmd, err := cli.New(

#### 🔐 Immutable State

Typically, these sorts of things are implemented with a big struct with lots of fields. `cli` is no different in this regard.
Typically, commands are implemented as a big struct with lots of fields. `cli` is no different in this regard.

What *is* different though is that this large struct can **only** be configured with `cli.New`. Once you've built your command, it can't be modified.

Expand All @@ -149,7 +153,7 @@ Consider the following example of a bad shorthand value:
```go
var delete bool

// Note: bad shorthand, it's two letters
// Note: "de" is a bad shorthand, it's two letters
cli.New("demo", cli.Flag(&delete, "delete", "de", false, "Delete something"))
```

Expand All @@ -169,12 +173,6 @@ var delete bool
cli.New("demo", cli.Flag(&delete, "delete", cli.NoShortHand, false, "Delete something"))
```

### Credits

This package was created with [copier] and the [FollowTheProcess/go_copier] project template.

[copier]: https://copier.readthedocs.io/en/stable/
[FollowTheProcess/go_copier]: https://github.com/FollowTheProcess/go_copier
[spf13/cobra]: https://github.com/spf13/cobra
[spf13/pflag]: https://github.com/spf13/pflag
[urfave/cli]: https://github.com/urfave/cli
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Things I want to do to make this library as good as it can be and a better, simp
- [ ] Figure out if/how we support setting custom flag types e.g. implement `Value`. Try this out in an example, does it work with `Flaggable`, how do we make it work?
- [x] How to handle conflicting flags e.g. `--version` has a short of `-v`, what if we want `-v` to mean verbosity, should be an error if there's a conflicting flag
- [ ] Cleverly look at what `cli.Allow` is set to to dynamically render the usage info
- [ ] Use the default values in the flag usages somehow, maybe if the default is not the zero value for the type?
- [ ] Add example tests for some of the public functions

## Ideas

Expand Down
8 changes: 4 additions & 4 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,20 +241,20 @@ func (c *Command) Execute() error {
return cmd.run(cmd, argsWithoutFlags)
}

// This basically only happens when we have subcommands defined but pass no args to the root command
// in which case we'll just show the help text and error
// The only way we get here is if the command has subcommands defined but got no arguments given to it
// so just show the usage and error
if err := defaultHelp(cmd); err != nil {
return err
}
return errors.New("invalid arguments")
return fmt.Errorf("command %q expected arguments (subcommands) but got none", cmd.name)
}

// Flags returns the set of flags for the command.
func (c *Command) flagSet() *flag.Set {
if c == nil {
// Only thing to do really, slightly more helpful than a generic
// nil pointer dereference
panic("Flags called on a nil Command")
panic("flagSet called on a nil Command")
}
if c.flags == nil {
return flag.NewSet()
Expand Down
17 changes: 17 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Examples

This directory contains a bunch of example programs built with `cli` to show you how the library works and how you might implement common patterns.

- [Examples](#examples)
- [`./quickstart`](#quickstart)
- [`./subcommands`](#subcommands)

## `./quickstart`

Implements the [quickstart] command from the main project README

## `./subcommands`

A CLI with multiple subcommands, each with their own flags and expected arguments. Shows how to easily store parsed flag values in an options struct and pass them around your program.

[quickstart]: https://github.com/FollowTheProcess/cli#quickstart

0 comments on commit fa0eb56

Please sign in to comment.