ff is a flags-first approach to configuration.
The basic idea is that myprogram -h
should always show the complete
configurati
8000
on "surface area" of a program. Therefore, every config parameter
should be defined as a flag. This module provides a simple and robust way to
define those flags, and to parse them from command-line arguments, environment
variables, and/or a config file.
Building a command-line application in the style of kubectl
or docker
?
Command provides a declarative approach that's simpler to write, and
easier to maintain, than many common alternatives.
This module provides a FlagSet that can be used as follows.
fs := ff.NewFlagSet("myprogram")
var (
listenAddr = fs.StringLong("listen", "localhost:8080", "listen address")
refresh = fs.Duration('r', "refresh", 15*time.Second, "refresh interval")
debug = fs.Bool('d', "debug", false, "log debug information")
_ = fs.StringLong("config", "", "config file (optional)")
)
It's also possible to use a standard library flag.FlagSet. Be sure to pass the ContinueOnError error handling strategy, as other options either panic or terminate the program on parse errors -- rude!
fs := flag.NewFlagSet("myprogram", flag.ContinueOnError)
var (
listenAddr = fs.String("listen", "localhost:8080", "listen address")
refresh = fs.Duration("refresh", 15*time.Second, "refresh interval")
debug = fs.Bool("debug", "log debug information")
_ = fs.String("config", "", "config file (optional)")
)
You can also provide your own implementation of the Flags interface.
Call ff.Parse to parse the flag set. Options can be provided to control parsing behavior.
err := ff.Parse(fs, os.Args[1:],
ff.WithEnvVarPrefix("MY_PROGRAM"),
ff.WithConfigFileFlag("config"),
ff.WithConfigFileParser(ff.PlainParser),
)
Here, flags are first set from the provided command-line arguments, from env
vars beginning with MY_PROGRAM
, and, if the user specifies a config file, from
values in that file, as parsed by ff.PlainParser.
Unlike other flag packages, help text is not automatically printed as a side effect of parsing. When a user requests help via e.g. -h or --help, it's reported as a parse error. Callers are responsible for checking parse errors, and printing help text when appropriate. package ffhelp has helpers for producing help text in a standard format, but you can always write your own.
if errors.Is(err, ff.ErrHelp) {
fmt.Fprint(os.Stderr, ffhelp.Flags(fs))
os.Exit(0)
} else if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", )
os.Exit(1)
}
It's possible to take runtime configuration from the environment. The options WithEnvVars and WithEnvVarPrefix enable this feature, and determine how flag names are mapped to environment variable names.
fs := ff.NewFlagSet("myservice")
var (
port = fs.Int('p', "port", 8080, "listen port for server (also via PORT)")
debug = fs.Bool('d', "debug", false, "log debug information (also via DEBUG)")
)
ff.Parse(fs, os.Args[1:], ff.WithEnvVars())
fmt.Printf("port %d, debug %v\n", *port, *debug)
$ env PORT=9090 myservice
port 9090, debug false
$ env PORT=9090 DEBUG=1 myservice --port=1234
port 1234, debug true
It's possible to take runtime configuration from config files. The options WithConfigFile, WithConfigFileFlag, and WithConfigFileParser control how config files are specified and parsed. This module includes support for JSON, YAML, TOML, and .env config files, as well as the simple PlainParser format.
fs := ff.NewFlagSet("myservice")
var (
port = fs.IntLong("port", 8080, "listen port for server")
debug = fs.BoolLong("debug", false, "log debug information")
_ = fs.StringLong("config", "", "config file")
)
ff.Parse(fs, os.Args[1:], ff.WithConfigFileFlag("config"), ff.WithConfigFileParser(ff.PlainParser))
fmt.Printf("port %d, debug %v\n", *port, *debug)
$ printf "port 9090\n" >1.conf ; myservice --config=1.conf
port 9090, debug false
$ printf "port 9090\ndebug\n" >2.conf ; myservice --config=2.conf --port=1234
port 1234, debug true
Command-line args have the highest priority, because they're explicitly given to each running instance of a program by the user. Think of command-line args as the "user" configuration.
Environment variables have the next-highest priority, because they reflect configuration set in the runtime context. Think of env vars as the "session" configuration.
Config files have the lowest priority, because they represent config that's static to the host. Think of config files as the "host" configuration.
Command is a declarative and lightweight alternative to common CLI frameworks like spf13/cobra, urfave/cli, or alecthomas/kingpin.
Those frameworks have relatively large APIs, in order to support a large number of "table stakes" features. In contrast, the command API is quite small, with the immediate goal of being intuitive and productive, and the long-term goal of producing CLI applications that are substantially easier to understand and maintain.
Commands are concerned only with the core mechanics of defining a command tree, parsing flags, and selecting a command to run. They're not intended to be a one-stop-shop for everything a command-line application may need. Features like tab completion, colorized output, etc. are orthogonal to command tree parsing, and can be easily provided by the consumer.
See the examples directory for some CLI tools built with commands.