...

Text file src/github.com/peterbourgon/ff/v3/ffcli/README.md

Documentation: github.com/peterbourgon/ff/v3/ffcli

     1# ffcli [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/peterbourgon/ff/v3/ffcli)
     2
     3ffcli stands for flags-first command line interface,
     4and provides an opinionated way to build CLIs.
     5
     6## Rationale
     7
     8Popular CLI frameworks like [spf13/cobra][cobra], [urfave/cli][urfave], or
     9[alecthomas/kingpin][kingpin] tend to have extremely large APIs, to support a
    10large number of "table stakes" features.
    11
    12[cobra]: https://github.com/spf13/cobra
    13[urfave]: https://github.com/urfave/cli
    14[kingpin]: https://github.com/alecthomas/kingpin
    15
    16This package is intended to be a lightweight alternative to those packages. In
    17contrast to them, the API surface area of package ffcli is very small, with the
    18immediate goal of being intuitive and productive, and the long-term goal of
    19supporting commandline applications that are substantially easier to understand
    20and maintain.
    21
    22To support these goals, the package is concerned only with the core mechanics of
    23defining a command tree, parsing flags, and selecting a command to run. It does
    24not intend to be a one-stop-shop for everything your commandline application
    25needs. Features like tab completion or colorized output are orthogonal to
    26command tree parsing, and should be easy to provide on top of ffcli.
    27
    28Finally, this package follows in the philosophy of its parent package ff, or
    29"flags-first". Flags, and more specifically the Go stdlib flag.FlagSet, should
    30be the primary mechanism of getting configuration from the execution environment
    31into your program. The affordances provided by package ff, including environment
    32variable and config file parsing, are also available in package ffcli. Support
    33for other flag packages is a non-goal.
    34
    35
    36## Goals
    37
    38- Absolute minimum usable API
    39- Prefer using existing language features/patterns/abstractions whenever possible
    40- Enable integration-style testing of CLIs with mockable dependencies
    41- No global state
    42
    43## Non-goals
    44
    45- All conceivably useful features
    46- Integration with flag packages other than [package flag][flag] and [ff][ff]
    47
    48[flag]: https://golang.org/pkg/flag
    49[ff]: https://github.com/peterbourgon/ff
    50
    51## Usage
    52
    53The core of the package is the [ffcli.Command][command]. Here is the simplest
    54possible example of an ffcli program.
    55
    56[command]: https://godoc.org/github.com/peterbourgon/ff/ffcli#Command
    57
    58```go
    59import (
    60	"context"
    61	"os"
    62
    63	"github.com/peterbourgon/ff/v3/ffcli"
    64)
    65
    66func main() {
    67	root := &ffcli.Command{
    68		Exec: func(ctx context.Context, args []string) error {
    69			println("hello world")
    70			return nil
    71		},
    72	}
    73
    74	root.ParseAndRun(context.Background(), os.Args[1:])
    75}
    76```
    77
    78Most CLIs use flags and arguments to control behavior. Here is a command which
    79takes a string to repeat as an argument, and the number of times to repeat it as
    80a flag.
    81
    82```go
    83fs := flag.NewFlagSet("repeat", flag.ExitOnError)
    84n := fs.Int("n", 3, "how many times to repeat")
    85
    86root := &ffcli.Command{
    87	ShortUsage: "repeat [-n times] <arg>",
    88	ShortHelp:  "Repeatedly print the argument to stdout.",
    89	FlagSet:    fs,
    90	Exec: func(ctx context.Context, args []string) error {
    91		if nargs := len(args); nargs != 1 {
    92			return fmt.Errorf("repeat requires exactly 1 argument, but you provided %d", nargs)
    93		}
    94		for i := 0; i < *n; i++ {
    95			fmt.Fprintln(os.Stdout, args[0])
    96		}
    97		return nil
    98	},
    99}
   100
   101if err := root.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
   102	log.Fatal(err)
   103}
   104```
   105
   106Each command may have subcommands, allowing you to build a command tree.
   107
   108```go
   109var (
   110	rootFlagSet   = flag.NewFlagSet("textctl", flag.ExitOnError)
   111	verbose       = rootFlagSet.Bool("v", false, "increase log verbosity")
   112	repeatFlagSet = flag.NewFlagSet("textctl repeat", flag.ExitOnError)
   113	n             = repeatFlagSet.Int("n", 3, "how many times to repeat")
   114)
   115
   116repeat := &ffcli.Command{
   117	Name:       "repeat",
   118	ShortUsage: "textctl repeat [-n times] <arg>",
   119	ShortHelp:  "Repeatedly print the argument to stdout.",
   120	FlagSet:    repeatFlagSet,
   121	Exec:       func(_ context.Context, args []string) error { ... },
   122}
   123
   124count := &ffcli.Command{
   125	Name:       "count",
   126	ShortUsage: "textctl count [<arg> ...]",
   127	ShortHelp:  "Count the number of bytes in the arguments.",
   128	Exec:       func(_ context.Context, args []string) error { ... },
   129}
   130
   131root := &ffcli.Command{
   132	ShortUsage:  "textctl [flags] <subcommand>",
   133	FlagSet:     rootFlagSet,
   134	Subcommands: []*ffcli.Command{repeat, count},
   135}
   136
   137if err := root.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
   138	log.Fatal(err)
   139}
   140```
   141
   142ParseAndRun can also be split into distinct Parse and Run phases, allowing you
   143to perform two-phase setup or initialization of e.g. API clients that require
   144user-supplied configuration.
   145
   146## Examples
   147
   148See [the examples directory][examples]. If you'd like an example of a specific
   149type of program structure, or a CLI that satisfies a specific requirement,
   150please [file an issue][issue].
   151
   152[examples]: https://github.com/peterbourgon/ff/tree/master/ffcli/examples
   153[issue]: https://github.com/peterbourgon/ff/issues/new

View as plain text