ff

package module
v4.0.0-alpha.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2023 License: Apache-2.0 Imports: 15 Imported by: 45

README

ff go.dev reference Latest Release Build Status

ff is a flags-first approach to configuration.

The basic idea is that myprogram -h should always show the complete configuration "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 config files.

Building a command-line application in the style of kubectl or docker? ff.Command is a declarative approach that's simpler to write, and easier to maintain, than many common alternatives.

Usage

Parse a flag.FlagSet

Parse a flag.FlagSet from commandline args, env vars, and/or a config file, by using ff.Parse instead of flag.FlagSet.Parse, and passing relevant options to control parse behavior.

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", false, "log debug information")
	_          = fs.String("config", "", "config file (optional)")
)

ff.Parse(fs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("listen=%s refresh=%s debug=%v\n", *listen, *refresh, *debug)
$ myprogram -listen=localhost:9090
listen=localhost:9090 refresh=15s debug=false

$ env MY_PROGRAM_DEBUG=1 myprogram
listen=localhost:8080 refresh=15s debug=true

$ printf 'refresh 30s \n debug \n' > my.conf
$ myprogram -config=my.conf
listen=localhost:8080 refresh=30s debug=true
Upgrade to an ff.FlagSet

Alternatively, you can use the getopts(3)-inspired [ff.FlagSet][flagset], which provides short (-f) and long (--foo) flag names, more useful flag types, and other niceities.

fs := ff.NewFlagSet("myprogram")
var (
	addrs     = fs.StringSet('a', "addr", "remote address (repeatable)")
	compress  = fs.Bool('c', "compress", "enable compression")
	transform = fs.Bool('t', "transform", "enable transformation")
	loglevel  = fs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = fs.StringLong("config", "", "config file (optional)")
)

ff.Parse(fs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("addrs=%v compress=%v transform=%v loglevel=%v\n", *addrs, *compress, *transform, *loglevel)
$ env MY_PROGRAM_LOG=debug myprogram -afoo -a bar --addr=baz --addr qux -ct
addrs=[foo bar baz qux] compress=true transform=true loglevel=debug
Parent flag sets

ff.FlagSet supports the notion of a parent flag set, which allows a "child" flag set to successfully parse every "parent" flag.

parentfs := ff.NewFlagSet("parentcommand")
var (
	loglevel = parentfs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_        = parentfs.StringLong("config", "", "config file (optional)")
)

childfs := ff.NewFlagSet("childcommand").SetParent(parentfs)
var (
	compress  = childfs.Bool('c', "compress", "enable compression")
	transform = childfs.Bool('t', "transform", "enable transformation")
	refresh   = childfs.DurationLong("refresh", 15*time.Second, "refresh interval")
)

ff.Parse(childfs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("loglevel=%v compress=%v transform=%v refresh=%v\n", *loglevel, *compress, *transform, *refresh)
$ myprogram --log=debug --refresh=1s
loglevel=debug compress=false transform=false refresh=1s

$ printf 'log error \n refresh 5s \n' > my.conf
$ myprogram --config my.conf
loglevel=error compress=false transform=false refresh=5s
Help output

Unlike flag.FlagSet, ff.FlagSet doesn't emit help text to os.Stderr as an invisible side effect of a failed parse. When using an ff.FlagSet, callers are expected to check the error returned by parse, and to emit help text to the user as appropriate. Package ffhelp provides functions that produce help text in a standard format, and tools for creating your own help text format.

parentfs := ff.NewFlagSet("parentcommand")
var (
	loglevel  = parentfs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = parentfs.StringLong("config", "", "config file (optional)")
)

childfs := ff.NewFlagSet("childcommand").SetParent(parentfs)
var (
	compress  = childfs.Bool('c', "compress", "enable compression")
	transform = childfs.Bool('t', "transform", "enable transformation")
	refresh   = childfs.DurationLong("refresh", 15*time.Second, "refresh interval")
)

if err := ff.Parse(childfs, os.Args[1:],
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
); err != nil {
	fmt.Printf("%s\n", ffhelp.Flags(childfs))
	fmt.Printf("err=%v\n", err)
} else {
	fmt.Printf("loglevel=%v compress=%v transform=%v refresh=%v\n", *loglevel, *compress, *transform, *refresh)
}
$ childcommand -h
NAME
  childcommand

FLAGS (childcommand)
  -c, --compress           enable compression
  -t, --transform          enable transformation
      --refresh DURATION   refresh interval (default: 15s)

FLAGS (parentcommand)
  -l, --log STRING         log level: debug, info, error (default: info)
      --config STRING      config file (optional)

err=parse args: flag: help requested

Parse priority

Command-line args have the highest priority, because they're explicitly provided to the program by the user. Think of command-line args as the "user" configuration.

Environment variables have the next-highest priority, because they represent configuration in the runtime environment. 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.

ff.Command

ff.Command is a tool for building larger CLI programs with sub-commands, like docker or kubectl. It's a declarative and lightweight alternative to more common frameworks like spf13/cobra, urfave/cli, or alecthomas/kingpin.

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 added on top.

Here's a simple example of a basic command tree.

// textctl -- root command
textctlFlags := ff.NewFlagSet("textctl")
verbose := textctlFlags.Bool('v', "verbose", "increase log verbosity")
textctlCmd := &ff.Command{
	Name:  "textctl",
	Usage: "textctl [FLAGS] SUBCOMMAND ...",
	Flags: textctlFlags,
}

// textctl repeat -- subcommand
repeatFlags := ff.NewFlagSet("repeat").SetParent(textctlFlags) // <-- set parent flag set
n := repeatFlags.IntShort('n', 3, "how many times to repeat")
repeatCmd := &ff.Command{
	Name:      "repeat",
	Usage:     "textctl repeat [-n TIMES] ARG",
	ShortHelp: "repeatedly print the first argument to stdout",
	Flags:     repeatFlags,
	Exec:      func(ctx context.Context, args []string) error { /* ... */ },
}
textctlCmd.Subcommands = append(textctlCmd.Subcommands, repeatCmd) // <-- append to parent subcommands

// ...

if err := textctlCmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil {
	fmt.Fprintf(os.Stderr, "%s\n", ffhelp.Command(textctlCmd))
	fmt.Fprintf(os.Stderr, "error: %v\n", err)
	os.Exit(1)
}

More sophisticated programs are available in the examples directory.

Documentation

Overview

Package ff provides a flags-first approach to runtime configuration.

The central function is Parse, which populates a flag set from commandline arguments, environment variables, and/or config files. Parse takes either an implementation of the Flags interface, or (for compatibility reasons) a concrete flag.FlagSet. Option values can be used to control parsing behavior.

FlagSet is provided as a standard implementation of the Flags interface, inspired by getopts(3). It supports single (-f) and long (--foo) flag names.

Command is provided as a tool for building CLI applications, like docker or kubectl, in a simple and declarative style. It's intended to be easier to understand and maintain than common alternatives.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrHelp should be returned by flag sets during parse, when the provided
	// args indicate the user has requested help.
	ErrHelp = flag.ErrHelp

	// ErrDuplicateFlag should be returned by flag sets when the user tries to
	// add a flag with the same name as a pre-existing flag.
	ErrDuplicateFlag = errors.New("duplicate flag")

	// ErrNotParsed may be returned by flag set methods which require the flag
	// set to have been successfully parsed, and that condition isn't satisfied.
	ErrNotParsed = errors.New("not parsed")

	// ErrAlreadyParsed may be returned by the parse method of flag sets, if the
	// flag set has already been successfully parsed, and cannot be parsed
	// again.
	ErrAlreadyParsed = errors.New("already parsed")

	// ErrUnknownFlag should be returned by flag sets methods to indicate that a
	// specific or user-requested flag was provided but could not be found.
	ErrUnknownFlag = errors.New("unknown flag")

	// ErrNoExec is returned when a command without an exec function is run.
	ErrNoExec = errors.New("no exec function")
)

Functions

func Parse

func Parse(fs FlagSetAny, args []string, options ...Option) error

Parse the flag set with the provided args. Option values can be used to influence parse behavior. For example, options exist to read flags from environment variables, config files, etc.

Example (Args)
fs := ff.NewFlagSet("myprogram")
var (
	listen  = fs.StringLong("listen", "localhost:8080", "listen address")
	refresh = fs.Duration('r', "refresh", 15*time.Second, "refresh interval")
	debug   = fs.Bool('d', "debug", "log debug information")
)

err := ff.Parse(fs, []string{"--refresh=1s", "-d"})

fmt.Printf("err=%v\n", err)
fmt.Printf("listen=%v\n", *listen)
fmt.Printf("refresh=%v\n", *refresh)
fmt.Printf("debug=%v\n", *debug)
Output:

err=<nil>
listen=localhost:8080
refresh=1s
debug=true
Example (Config)
fs := ff.NewFlagSet("myprogram")
var (
	listen  = fs.StringLong("listen", "localhost:8080", "listen address")
	refresh = fs.Duration('r', "refresh", 15*time.Second, "refresh interval")
	debug   = fs.Bool('d', "debug", "log debug information")
	_       = fs.String('c', "config", "", "path to config file")
)

f, _ := os.CreateTemp("", "ExampleParse_config")
defer func() { f.Close(); os.Remove(f.Name()) }()
fmt.Fprint(f, `
		debug
		listen localhost:9999
	`)

err := ff.Parse(fs, []string{"-c", f.Name()},
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("err=%v\n", err)
fmt.Printf("listen=%v\n", *listen)
fmt.Printf("refresh=%v\n", *refresh)
fmt.Printf("debug=%v\n", *debug)
Output:

err=<nil>
listen=localhost:9999
refresh=15s
debug=true
Example (Env)
fs := ff.NewFlagSet("myprogramenv")
var (
	listen  = fs.StringLong("listen", "localhost:8080", "listen address")
	refresh = fs.Duration('r', "refresh", 15*time.Second, "refresh interval")
	debug   = fs.Bool('d', "debug", "log debug information")
)

defer os.Setenv("MY_PROGRAM_ENV_REFRESH", os.Getenv("MY_PROGRAM_ENV_REFRESH"))
os.Setenv("MY_PROGRAM_ENV_REFRESH", "3s")

err := ff.Parse(fs, []string{},
	ff.WithEnvVarPrefix("MY_PROGRAM_ENV"),
)

fmt.Printf("err=%v\n", err)
fmt.Printf("listen=%v\n", *listen)
fmt.Printf("refresh=%v\n", *refresh)
fmt.Printf("debug=%v\n", *debug)
Output:

err=<nil>
listen=localhost:8080
refresh=3s
debug=false
Example (Flag_set_features)
fs := ff.NewFlagSet("myprogram")
var (
	addrs     = fs.StringSet('a', "addr", "remote address (repeatable)")
	refresh   = fs.DurationLong("refresh", 15*time.Second, "refresh interval")
	compress  = fs.Bool('c', "compress", "enable compression")
	transform = fs.Bool('t', "transform", "enable transformation")
	loglevel  = fs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = fs.StringLong("config", "", "config file (optional)")
)
err := ff.Parse(fs, []string{"-afoo", "-a", "bar", "--log=debug", "-ct"},
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)
fmt.Printf("%s\n", ffhelp.Flags(fs))
fmt.Printf("err=%v\n", err)
fmt.Printf("addrs=%v\n", *addrs)
fmt.Printf("refresh=%v\n", *refresh)
fmt.Printf("compress=%v\n", *compress)
fmt.Printf("transform=%v\n", *transform)
fmt.Printf("loglevel=%v\n", *loglevel)
Output:

NAME
  myprogram

FLAGS
  -a, --addr STRING        remote address (repeatable)
      --refresh DURATION   refresh interval (default: 15s)
  -c, --compress           enable compression
  -t, --transform          enable transformation
  -l, --log STRING         log level: debug, info, error (default: info)
      --config STRING      config file (optional)

err=<nil>
addrs=[foo bar]
refresh=15s
compress=true
transform=true
loglevel=debug
Example (Help)
fs := ff.NewFlagSet("myprogram")
var (
	addrs     = fs.StringSet('a', "addr", "remote address (repeatable)")
	compress  = fs.Bool('c', "compress", "enable compression")
	transform = fs.Bool('t', "transform", "enable transformation")
	loglevel  = fs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_         = fs.StringLong("config", "", "config file (optional)")
)

err := ff.Parse(fs, []string{"-h"},
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

if err != nil {
	fmt.Printf("%s\n", ffhelp.Flags(fs))
	fmt.Printf("err=%v\n", err)
} else {
	fmt.Printf("addrs=%v compress=%v transform=%v loglevel=%v\n", *addrs, *compress, *transform, *loglevel)
}
Output:

NAME
  myprogram

FLAGS
  -a, --addr STRING     remote address (repeatable)
  -c, --compress        enable compression
  -t, --transform       enable transformation
  -l, --log STRING      log level: debug, info, error (default: info)
      --config STRING   config file (optional)

err=parse args: flag: help requested
Example (Parent)
parentfs := ff.NewFlagSet("mycommand")
var (
	loglevel = parentfs.StringEnum('l', "log", "log level: debug, info, error", "info", "debug", "error")
	_        = parentfs.StringLong("config", "", "config file (optional)")
)

childfs := ff.NewFlagSet("subcommand").SetParent(parentfs)
var (
	compress  = childfs.Bool('c', "compress", "enable compression")
	transform = childfs.Bool('t', "transform", "enable transformation")
	refresh   = childfs.DurationLong("refresh", 15*time.Second, "refresh interval")
)

f, _ := os.CreateTemp("", "ExampleParse_parent")
defer func() { f.Close(); os.Remove(f.Name()) }()
fmt.Fprint(f, `
		log error
		compress
		refresh 3s
	`)

err := ff.Parse(childfs, []string{"--config", f.Name(), "--refresh=1s"},
	ff.WithEnvVarPrefix("MY_PROGRAM"),
	ff.WithConfigFileFlag("config"),
	ff.WithConfigFileParser(ff.PlainParser),
)

fmt.Printf("err=%v\n", err)
fmt.Printf("loglevel=%v\n", *loglevel)
fmt.Printf("compress=%v\n", *compress)
fmt.Printf("transform=%v\n", *transform)
fmt.Printf("refresh=%v\n", *refresh)
Output:

err=<nil>
loglevel=error
compress=true
transform=false
refresh=1s
Example (Stdlib)
fs := flag.NewFlagSet("myprogram", flag.ContinueOnError)
var (
	listen  = fs.String("listen", "localhost:8080", "listen address")
	refresh = fs.Duration("refresh", 15*time.Second, "refresh interval")
	debug   = fs.Bool("debug", false, "log debug information")
)

err := ff.Parse(fs, []string{"--debug", "-refresh=2s", "-listen", "localhost:9999"})

fmt.Printf("err=%v\n", err)
fmt.Printf("listen=%v\n", *listen)
fmt.Printf("refresh=%v\n", *refresh)
fmt.Printf("debug=%v\n", *debug)
Output:

err=<nil>
listen=localhost:9999
refresh=2s
debug=true

func PlainParser

func PlainParser(r io.Reader, set func(name, value string) error) error

PlainParser is a parser for config files in an extremely simple format. Each line is tokenized as a single key/value pair. The first space-delimited token in the line is interpreted as the flag name, and the rest of the line is interpreted as the flag value.

Any leading hyphens on the flag name are ignored. Lines with a flag name but no value are interpreted as booleans, and the value is set to true.

Flag values are trimmed of leading and trailing whitespace, but are otherwise unmodified. In particular, values are not quote-unescaped, and control characters like \n are not evaluated and instead passed through as literals.

Comments are supported via "#". End-of-line comments require a space between the end of the line and the "#" character.

An example config file follows.

# this is a full-line comment
timeout 250ms     # this is an end-of-line comment
foo     abc def   # set foo to `abc def`
foo     12345678  # repeated flags result in repeated calls to Set
bar     "abc def" # set bar to `"abc def"`, including quotes
baz     x\ny      # set baz to `x\ny`, passing \n literally
verbose           # equivalent to `verbose true`

Types

type Command

type Command struct {
	// Name of the command, which is used when producing the help text for the
	// command, as well as for subcommand matching.
	//
	// Required.
	Name string

	// Usage is a single line string which should describe the syntax of the
	// command, including flags and arguments. It's typically printed at the top
	// of the help text for the command. For example,
	//
	//    USAGE
	//      cmd [FLAGS] subcmd [FLAGS] <ARG> [<ARG>...]
	//
	// Here, the usage string begins with "cmd [FLAGS] ...".
	//
	// Recommended. If not provided, the help text for the command should not
	// include a usage section.
	Usage string

	// ShortHelp is a single line which should very briefly describe the purpose
	// of the command in prose. It's typically printed next to the command name
	// when it appears as a subcommand in help text. For example,
	//
	//    SUBCOMMANDS
	//      commandname   this is the short help string
	//
	// Recommended.
	ShortHelp string

	// LongHelp is a multi-line string, usually one or more paragraphs of prose,
	// which explain the command in detail. It's typically included in the help
	// output for the command, separate from other sections.
	//
	// Long help should be formatted for user readability. For example, if help
	// output is written to a terminal, long help should include newlines which
	// hard-wrap the string at an appropriate column width for that terminal.
	//
	// Optional.
	LongHelp string

	// Flags is the set of flags associated with, and parsed by, this command.
	//
	// When building a command tree, it's often useful to allow flags defined by
	// parent commands to be specified by any subcommand. A FlagSet supports
	// this behavior via SetParent, see the documentation of that method for
	// details.
	//
	// Optional. If not provided, an empty flag set will be constructed and used
	// so that the -h, --help flag works as expected.
	Flags Flags

	// Subcommands which are available underneath (i.e. after) this command.
	// Selecting a subcommand is done via a case-insensitive comparison of the
	// first post-parse argument to this command, against the name of each
	// subcommand.
	//
	// Optional.
	Subcommands []*Command

	// Exec is invoked by Run (or ParseAndRun) if this command was selected as
	// the terminal command during the parse phase. The args passed to Exec are
	// the args left over after parsing.
	//
	// Optional. If not provided, running this command will result in ErrNoExec.
	Exec func(ctx context.Context, args []string) error
	// contains filtered or unexported fields
}

Command is a declarative structure that combines a main function with a flag set and zero or more subcommands. It's intended to model CLI applications which can be represented as a tree of such commands.

func (*Command) GetParent

func (cmd *Command) GetParent() *Command

GetParent returns the parent command of this command, or nil if a parent hasn't been set. Parents are set during the parse phase, but only for commands which are traversed.

func (*Command) GetSelected

func (cmd *Command) GetSelected() *Command

GetSelected returns the terminal command selected during the parse phase, or nil if the command hasn't been successfully parsed.

func (*Command) Parse

func (cmd *Command) Parse(args []string, options ...Option) error

Parse the args and options against the defined command, which sets relevant flags, traverses the command hierarchy to select a terminal command, and captures the arguments that will be given to that command's exec function. The args should not include the program name: pass os.Args[1:], not os.Args.

func (*Command) ParseAndRun

func (cmd *Command) ParseAndRun(ctx context.Context, args []string, options ...Option) error

ParseAndRun calls Command.Parse and, upon success, Command.Run.

func (*Command) Reset

func (cmd *Command) Reset() error

Reset every command in the command tree to its initial state, including all flag sets. Every flag set must implement Resetter, or else reset will return an error.

func (*Command) Run

func (cmd *Command) Run(ctx context.Context) error

Run the Exec function of the terminal command selected during the parse phase, passing the args left over after parsing. Calling Command.Run without first calling Command.Parse will result in ErrNotParsed.

type ConfigFileParseFunc

type ConfigFileParseFunc func(r io.Reader, set func(name, value string) error) error

ConfigFileParseFunc is a function that consumes the provided reader as a config file, and calls the provided set function for every name=value pair it discovers.

type Flag

type Flag interface {
	// GetFlags should return the set of flags in which this flag is defined.
	// It's primarily used to produce help text for hierarchical commands.
	GetFlags() Flags

	// GetShortName should return the short name for this flag, if one is
	// defined. A short name is always a single valid rune (character) which is
	// typically parsed with a single leading hyphen, e.g. -f.
	GetShortName() (rune, bool)

	// GetLongName should return the long name for this flag, if one is defined.
	// A long name is always a non-empty string which is typically parsed with
	// two leading hyphens, e.g. --foo.
	GetLongName() (string, bool)

	// GetPlaceholder should return a string that can be used as a placeholder
	// for the flag value in help text. For example, a placeholder for a
	// string flag might be STRING. An empty placeholder is valid.
	GetPlaceholder() string

	// GetUsage should return a short description of the flag, which can be
	// included in the help text on the same line as the flag name(s). For
	// example, the usage string for a timeout flag used in an HTTP client might
	// be "timeout for outgoing HTTP requests". An empty usage string is valid,
	// but not recommended.
	GetUsage() string

	// GetDefault should return a string that represents the default value of
	// the flag in the context of help text. An empty string is valid, and
	// doesn't mean that the true default value of the flag is actually an empty
	// string.
	GetDefault() string

	// SetValue should parse the provided string into the appropriate type for
	// the flag, and set the flag to that parsed value.
	SetValue(string) error

	// GetValue should return the current value of the flag as a string. If no
	// value has been set, it should return the default value as a string.
	GetValue() string

	// IsSet should return true if SetValue has been called successfully.
	IsSet() bool
}

Flag describes a single runtime configuration parameter, defined in a set of Flags, and with a value that can be parsed from a string.

Implementations are not expected to be safe for concurrent use.

type FlagConfig

type FlagConfig struct {
	// ShortName is the short form name of the flag, which can be provided as a
	// commandline argument with a single dash - prefix. A rune value of 0 or
	// utf8.RuneError is considered an invalid short name and is ignored.
	//
	// At least one of ShortName and/or LongName is required.
	ShortName rune

	// LongName is the long form name of the flag, which can be provided as a
	// commandline argument with a double-dash -- prefix. Long names must be
	// non-empty, and cannot contain whitespace, control characters, single or
	// double quotes, backticks, or backslashes.
	//
	// At least one of ShortName and/or LongName is required.
	LongName string

	// Usage is a short help message for the flag, typically printed after the
	// flag name(s) on a single line in the help text. For example, a usage
	// string "set the foo parameter" might produce help text as follows.
	//
	//      -f, --foo BAR   set the foo parameter
	//
	// If the usage string contains a `backtick` quoted substring, that
	// substring will be treated as a placeholder, if a placeholder was not
	// otherwise explicitly provided.
	//
	// Recommended.
	Usage string

	// Value is used to parse and store the underlying value for the flag.
	// Package ffval provides helpers and definitions for common value types.
	//
	// Required.
	Value flag.Value

	// Placeholder represents an example value in the help text for the flag,
	// typically printed after the flag name(s). For example, a placeholder of
	// "BAR" might produce help text as follows.
	//
	//      -f, --foo BAR   set the foo parameter
	//
	// Optional. If not provided, a default based on the value type is used.
	Placeholder string

	// NoPlaceholder will force GetPlaceholder to return the empty string. This
	// can be useful for flags that don't need placeholders in their help text,
	// for example boolean flags.
	NoPlaceholder bool

	// NoDefault will force GetDefault to return the empty string. This can be
	// useful for flags whose default values don't need to be communicated in
	// help text. Note this does not affect the actual default value of the
	// flag.
	NoDefault bool
}

FlagConfig collects the required config for a flag in a flag set.

type FlagSet

type FlagSet struct {
	// contains filtered or unexported fields
}

FlagSet is a standard implementation of Flags. It's broadly similar to a flag.FlagSet, but with additional capabilities inspired by getopt(3).

func NewFlagSet

func NewFlagSet(name string) *FlagSet

NewFlagSet returns a new flag set with the given name.

func NewFlagSetFrom

func NewFlagSetFrom(name string, val any) *FlagSet

NewFlagSetFrom is a helper method that calls NewFlagSet with name, and then FlagSet.AddStruct with val, which must be a pointer to a struct. Any error results in a panic.

As a special case, val may also be a pointer to a flag.FlagSet. In this case, the returned ff.FlagSet behaves differently than normal. It acts as a fixed "snapshot" of the flag.FlagSet, and so doesn't allow new flags to be added. To approximate the behavior of the standard library, every flag.FlagSet flag name is treated as a long name, and parsing treats single-hyphen and double-hyphen flag arguments identically: -abc is parsed as --abc rather than -a -b -c. The flag.FlagSet error handling strategy is (effectively) forced to ContinueOnError. The usage function is ignored, and usage is never printed as a side effect of parsing.

func (*FlagSet) AddFlag

func (fs *FlagSet) AddFlag(cfg FlagConfig) (Flag, error)

AddFlag adds a flag to the flag set, as specified by the provided config. An error is returned if the config is invalid, or if a flag is already defined in the flag set with the same short or long name.

This is a fairly low level method. Consumers may prefer type-specific helpers like FlagSet.Bool, FlagSet.StringVar, etc.

func (*FlagSet) AddStruct

func (fs *FlagSet) AddStruct(val any) error

AddStruct adds flags to the flag set from the given val, which must be a pointer to a struct. Each exported field in that struct with a valid `ff:` struct tag corresponds to a unique flag in the flag set. Those fields must be a supported ffval.ValueType or implement flag.Value.

The `ff:` struct tag is a sequence of comma- or pipe-delimited items. An item is either empty (and ignored), a key, or a key/value pair. Key/value pairs are expressed as either key=value (with =) or key:value (with :). Items, keys, and values are trimmed of whitespace before use. Values may be 'single quoted' and will be unquoted before use.

The following is a list of valid keys and their expected values. Any invalid item, key, or value in the `ff:` struct tag will result in an error.

  • s, short, shortname -- value must be a single valid rune
  • l, long, longname -- value must be a valid long name
  • u, usage -- value must be a non-empty string
  • d, def, default -- value must be a non-empty and assignable string
  • p, placeholder -- value must be a non-empty string
  • noplaceholder -- no value
  • nodefault -- no value

See the example for more detail.

Example
var firstFlags struct {
	Alpha   string `ff:"shortname: a, longname: alpha, usage: alpha string,    default: abc   "`
	Beta    int    `ff:"              longname: beta,  usage: 'beta: an int',  placeholder: β "`
	Delta   bool   `ff:"shortname: d,                  usage: 'delta, a bool', nodefault      "`
	Epsilon bool   `ff:"short: e,     long: epsilon,   usage: epsilon bool,    nodefault      "`
}

var secondFlags struct {
	Gamma string          `ff:" short=g | long=gamma |              | usage: gamma string       "`
	Iota  float64         `ff:"         | long=iota  | default=0.43 | usage: 🦊                 "`
	Kappa ffval.StringSet `ff:" short=k | long=kappa |              | usage: kappa (repeatable) "`
}

fs := ff.NewFlagSet("mycommand")
fs.AddStruct(&firstFlags)
fs.AddStruct(&secondFlags)
fmt.Print(ffhelp.Flags(fs))
Output:

NAME
  mycommand

FLAGS
  -a, --alpha STRING   alpha string (default: abc)
      --beta β         beta: an int (default: 0)
  -d                   delta, a bool
  -e, --epsilon        epsilon bool
  -g, --gamma STRING   gamma string
      --iota FLOAT64   🦊 (default: 0.43)
  -k, --kappa STRING   kappa (repeatable)

func (*FlagSet) Bool

func (fs *FlagSet) Bool(short rune, long string, usage string) *bool

Bool defines a new default false bool flag in the flag set, and panics on any error.

func (*FlagSet) BoolConfig

func (fs *FlagSet) BoolConfig(cfg FlagConfig) *bool

BoolConfig defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) BoolDefault

func (fs *FlagSet) BoolDefault(short rune, long string, def bool, usage string) *bool

BoolDefault defines a new bool flag in the flag set, and panics on any error. Bool flags should almost always be default false; prefer Bool to BoolDefault.

func (*FlagSet) BoolLong

func (fs *FlagSet) BoolLong(long string, usage string) *bool

BoolLong defines a new default false bool flag in the flag set, and panics on any error.

func (*FlagSet) BoolLongDefault

func (fs *FlagSet) BoolLongDefault(long string, def bool, usage string) *bool

BoolLongDefault defines a new bool flag in the flag set, and panics on any error. Bool flags should almost always be default false; prefer BoolLong to BoolLongDefault.

func (*FlagSet) BoolShort

func (fs *FlagSet) BoolShort(short rune, usage string) *bool

BoolShort defines a new default false bool flag in the flag set, and panics on any error.

func (*FlagSet) BoolShortDefault

func (fs *FlagSet) BoolShortDefault(short rune, def bool, usage string) *bool

BoolShortDefault defines a new bool flag in the flag set, and panics on any error. Bool flags should almost always be default false; prefer BoolShort to BoolShortDefault.

func (*FlagSet) BoolVar

func (fs *FlagSet) BoolVar(pointer *bool, short rune, long string, usage string) Flag

BoolVar defines a new default false bool flag in the flag set, and panics on any error.

func (*FlagSet) BoolVarDefault

func (fs *FlagSet) BoolVarDefault(pointer *bool, short rune, long string, def bool, usage string) Flag

BoolVarDefault defines a new bool flag in the flag set, and panics on any error. Bool flags should almost always be default false; prefer BoolVar to BoolVarDefault.

func (*FlagSet) Duration

func (fs *FlagSet) Duration(short rune, long string, def time.Duration, usage string) *time.Duration

Duration defines a new flag in the flag set, and panics on any error.

func (*FlagSet) DurationConfig

func (fs *FlagSet) DurationConfig(cfg FlagConfig, def time.Duration) *time.Duration

DurationConfig defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) DurationLong

func (fs *FlagSet) DurationLong(long string, def time.Duration, usage string) *time.Duration

DurationLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) DurationShort

func (fs *FlagSet) DurationShort(short rune, def time.Duration, usage string) *time.Duration

DurationShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) DurationVar

func (fs *FlagSet) DurationVar(pointer *time.Duration, short rune, long string, def time.Duration, usage string) Flag

DurationVar defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Float64

func (fs *FlagSet) Float64(short rune, long string, def float64, usage string) *float64

Float64 defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Float64Config

func (fs *FlagSet) Float64Config(cfg FlagConfig, def float64) *float64

Float64Config defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) Float64Long

func (fs *FlagSet) Float64Long(long string, def float64, usage string) *float64

Float64Long defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Float64Short

func (fs *FlagSet) Float64Short(short rune, def float64, usage string) *float64

Float64Short defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Float64Var

func (fs *FlagSet) Float64Var(pointer *float64, short rune, long string, def float64, usage string) Flag

Float64Var defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Func

func (fs *FlagSet) Func(short rune, long string, fn func(string) error, usage string)

Func defines a new flag in the flag set, and panics on any error.

func (*FlagSet) FuncLong

func (fs *FlagSet) FuncLong(long string, fn func(string) error, usage string)

FuncLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) FuncShort

func (fs *FlagSet) FuncShort(short rune, fn func(string) error, usage string)

FuncShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) GetArgs

func (fs *FlagSet) GetArgs() []string

GetArgs returns the args left over after a successful parse.

func (*FlagSet) GetFlag

func (fs *FlagSet) GetFlag(name string) (Flag, bool)

GetFlag returns the first flag known to the flag set that matches the given name. This includes all parent flags, if a parent has been set. The name is compared against each flag's long name, and, if the name is a single rune, it's also compared against each flag's short name.

func (*FlagSet) GetName

func (fs *FlagSet) GetName() string

GetName returns the name of the flag set provided during construction.

func (*FlagSet) Int

func (fs *FlagSet) Int(short rune, long string, def int, usage string) *int

Int defines a new flag in the flag set, and panics on any error.

func (*FlagSet) IntConfig

func (fs *FlagSet) IntConfig(cfg FlagConfig, def int) *int

IntConfig defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) IntLong

func (fs *FlagSet) IntLong(long string, def int, usage string) *int

IntLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) IntShort

func (fs *FlagSet) IntShort(short rune, def int, usage string) *int

IntShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) IntVar

func (fs *FlagSet) IntVar(pointer *int, short rune, long string, def int, usage string) Flag

IntVar defines a new flag in the flag set, and panics on any error.

func (*FlagSet) IsParsed

func (fs *FlagSet) IsParsed() bool

IsParsed returns true if the flag set has been successfully parsed.

func (*FlagSet) Parse

func (fs *FlagSet) Parse(args []string) error

Parse the provided args against the flag set, assigning flag values as appropriate. Args are matched to flags defined in this flag set, and, if a parent is set, all parent flag sets, recursively. If a specified flag can't be found, parse fails with ErrUnknownFlag. After a successful parse, subsequent calls to parse fail with ErrAlreadyParsed, until and unless the flag set is reset.

func (*FlagSet) Reset

func (fs *FlagSet) Reset() error

Reset the flag set, and all of the flags defined in the flag set, to their initial state. After a successful reset, the flag set may be parsed as if it were newly constructed.

func (*FlagSet) SetParent

func (fs *FlagSet) SetParent(parent *FlagSet) *FlagSet

SetParent assigns a parent flag set to this one, making all parent flags available, recursively, to the receiver. For example, Parse will match against any parent flag, WalkFlags will traverse all parent flags, etc.

This method returns its receiver to allow for builder-style initialization.

func (*FlagSet) String

func (fs *FlagSet) String(short rune, long string, def string, usage string) *string

String defines a new flag in the flag set, and panics on any error.

func (*FlagSet) StringConfig

func (fs *FlagSet) StringConfig(cfg FlagConfig, def string) *string

StringConfig defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) StringEnum

func (fs *FlagSet) StringEnum(short rune, long string, usage string, valid ...string) *string

StringEnum defines a new enum in the flag set, and panics on any error. The default is the first valid value. At least one valid value is required.

func (*FlagSet) StringEnumLong

func (fs *FlagSet) StringEnumLong(long string, usage string, valid ...string) *string

StringEnumLong defines a new enum in the flag set, and panics on any error. The default is the first valid value. At least one valid value is required.

func (*FlagSet) StringEnumShort

func (fs *FlagSet) StringEnumShort(short rune, usage string, valid ...string) *string

StringEnumShort defines a new enum in the flag set, and panics on any error. The default is the first valid value. At least one valid value is required.

func (*FlagSet) StringEnumVar

func (fs *FlagSet) StringEnumVar(pointer *string, short rune, long string, usage string, valid ...string) Flag

StringEnumVar defines a new enum in the flag set, and panics on any error. The default is the first valid value. At least one valid value is required.

func (*FlagSet) StringList

func (fs *FlagSet) StringList(short rune, long string, usage string) *[]string

StringList defines a new flag in the flag set, and panics on any error. See FlagSet.StringListVar for more details.

func (*FlagSet) StringListLong

func (fs *FlagSet) StringListLong(long string, usage string) *[]string

StringListLong defines a new flag in the flag set, and panics on any error. See FlagSet.StringListVar for more details.

func (*FlagSet) StringListShort

func (fs *FlagSet) StringListShort(short rune, usage string) *[]string

StringListShort defines a new flag in the flag set, and panics on any error. See FlagSet.StringListVar for more details.

func (*FlagSet) StringListVar

func (fs *FlagSet) StringListVar(pointer *[]string, short rune, long string, usage string) Flag

StringListVar defines a new flag in the flag set, and panics on any error.

The flag represents a list of strings, where each call to Set adds a new value to the list. Duplicate values are permitted.

func (*FlagSet) StringLong

func (fs *FlagSet) StringLong(long string, def string, usage string) *string

StringLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) StringSet

func (fs *FlagSet) StringSet(short rune, long string, usage string) *[]string

StringSet defines a new flag in the flag set, and panics on any error. See FlagSet.StringSetVar for more details.

func (*FlagSet) StringSetLong

func (fs *FlagSet) StringSetLong(long string, usage string) *[]string

StringSetLong defines a new flag in the flag set, and panics on any error. See FlagSet.StringSetVar for more details.

func (*FlagSet) StringSetShort

func (fs *FlagSet) StringSetShort(short rune, usage string) *[]string

StringSetShort defines a new flag in the flag set, and panics on any error. See FlagSet.StringSetVar for more details.

func (*FlagSet) StringSetVar

func (fs *FlagSet) StringSetVar(pointer *[]string, short rune, long string, usage string) Flag

StringSetVar defines a new flag in the flag set, and panics on any error.

The flag represents a unique list of strings, where each call to Set adds a new value to the list. Duplicate values are silently dropped.

func (*FlagSet) StringShort

func (fs *FlagSet) StringShort(short rune, def string, usage string) *string

StringShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) StringVar

func (fs *FlagSet) StringVar(pointer *string, short rune, long string, def string, usage string) Flag

StringVar defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Uint

func (fs *FlagSet) Uint(short rune, long string, def uint, usage string) *uint

Uint defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Uint64

func (fs *FlagSet) Uint64(short rune, long string, def uint64, usage string) *uint64

Uint64 defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Uint64Config

func (fs *FlagSet) Uint64Config(cfg FlagConfig, def uint64) *uint64

Uint64Config defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) Uint64Long

func (fs *FlagSet) Uint64Long(long string, def uint64, usage string) *uint64

Uint64Long defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Uint64Short

func (fs *FlagSet) Uint64Short(short rune, def uint64, usage string) *uint64

Uint64Short defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Uint64Var

func (fs *FlagSet) Uint64Var(pointer *uint64, short rune, long string, def uint64, usage string) Flag

Uint64Var defines a new flag in the flag set, and panics on any error.

func (*FlagSet) UintConfig

func (fs *FlagSet) UintConfig(cfg FlagConfig, def uint) *uint

UintConfig defines a new flag in the flag set, and panics on any error. The value field of the provided config is overwritten.

func (*FlagSet) UintLong

func (fs *FlagSet) UintLong(long string, def uint, usage string) *uint

UintLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) UintShort

func (fs *FlagSet) UintShort(short rune, def uint, usage string) *uint

UintShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) UintVar

func (fs *FlagSet) UintVar(pointer *uint, short rune, long string, def uint, usage string) Flag

UintVar defines a new flag in the flag set, and panics on any error.

func (*FlagSet) Value

func (fs *FlagSet) Value(short rune, long string, value flag.Value, usage string) Flag

Value defines a new flag in the flag set, and panics on any error.

func (*FlagSet) ValueLong

func (fs *FlagSet) ValueLong(long string, value flag.Value, usage string) Flag

ValueLong defines a new flag in the flag set, and panics on any error.

func (*FlagSet) ValueShort

func (fs *FlagSet) ValueShort(short rune, value flag.Value, usage string) Flag

ValueShort defines a new flag in the flag set, and panics on any error.

func (*FlagSet) WalkFlags

func (fs *FlagSet) WalkFlags(fn func(Flag) error) error

WalkFlags calls fn for every flag known to the flag set. This includes all parent flags, if a parent has been set.

type FlagSetAny

type FlagSetAny any

FlagSetAny must be either a Flags interface, or a concrete *flag.FlagSet. Any other value will produce a runtime error.

The intent is to make the signature of functions like Parse more intuitive.

type Flags

type Flags interface {
	// GetName should return the name of the flag set.
	GetName() string

	// Parse should parse the provided args against the flag set, setting flags
	// as appropriate, and saving leftover args to be returned by GetArgs. The
	// provided args shouldn't include the program name: callers should pass
	// os.Args[1:], not os.Args.
	Parse(args []string) error

	// IsParsed should return true if the flag set was successfully parsed.
	IsParsed() bool

	// WalkFlags should call the given fn for each flag known to the flag set.
	// Note that this may include flags that are actually defined in different
	// "parent" flag sets. If fn returns an error, WalkFlags should immediately
	// return that error.
	WalkFlags(fn func(Flag) error) error

	// GetFlag should find and return the first flag known to the flag set with
	// the given name. The name should always be compared against valid flag
	// long names. If name is a single valid rune, it should also be compared
	// against valid flag short names. Note that this may return a flag that is
	// actually defined in a different "parent" flag set.
	GetFlag(name string) (Flag, bool)

	// GetArgs should return the args left over after a successful call to
	// parse. If parse has not yet been called successfully, it should return an
	// empty (or nil) slice.
	GetArgs() []string
}

Flags describes a collection of flags, typically associated with a specific command (or sub-command) executed by an end user.

Any valid Flags can be provided to Parse, or used as the Flags field in a Command. This allows consumers to use their own flag set implementation(s) while still taking advantage of the primary features of the module.

Implementations are not expected to be safe for concurrent use.

type Option

type Option func(*ParseContext)

Option controls some aspect of parsing behavior.

func WithConfigAllowMissingFile

func WithConfigAllowMissingFile() Option

WithConfigAllowMissingFile tells Parse to ignore config files that are specified but don't exist.

By default, missing config files result in a parse error.

func WithConfigFile

func WithConfigFile(filename string) Option

WithConfigFile tells Parse to read the provided filename as a config file. Requires WithConfigFileParser, and overrides WithConfigFileFlag.

Because config files should generally be user-specifiable, this option should rarely be used; prefer WithConfigFileFlag.

func WithConfigFileFlag

func WithConfigFileFlag(flagname string) Option

WithConfigFileFlag tells Parse to treat the flag with the given name as a config file. The flag name must be defined in the flag set consumed by parse. Requires WithConfigFileParser, and is overridden by WithConfigFile.

To specify a default config file, provide it as the default value of the corresponding flag.

func WithConfigFileParser

func WithConfigFileParser(pf ConfigFileParseFunc) Option

WithConfigFileParser tells Parse how to interpret a config file. This option must be explicitly provided in order to parse config files.

By default, no config file parser is defined, and config files are ignored.

func WithConfigIgnoreUndefinedFlags

func WithConfigIgnoreUndefinedFlags() Option

WithConfigIgnoreUndefinedFlags tells Parse to ignore flags in config files which are not defined in the parsed flag set. This option only applies to flags in config files.

By default, undefined flags in config files result in a parse error.

func WithEnvVarPrefix

func WithEnvVarPrefix(prefix string) Option

WithEnvVarPrefix is like WithEnvVars, but only considers environment variables beginning with the given prefix followed by an underscore. That prefix (and underscore) are removed before matching the env var key to a flag name. For example, the env var prefix `MYPROG` would mean that the env var `MYPROG_FOO` matches a flag named `foo`.

By default, flags are not parsed from environment variables at all.

func WithEnvVarSplit

func WithEnvVarSplit(delimiter string) Option

WithEnvVarSplit tells Parse to split environment variable values on the given delimiter, and to set the flag multiple times, once for each delimited token. Values produced in this way are not trimmed of whitespace.

For example, the env var `FOO=a,b,c` would by default set a flag named `foo` one time, with the value `a,b,c`. Providing WithEnvVarSplit with a comma delimiter would set `foo` multiple times, with the values `a`, `b`, and `c`.

By default, no splitting of environment variable values occurs.

func WithEnvVars

func WithEnvVars() Option

WithEnvVars tells Parse to set flags from environment variables. Flags are matched to environment variables by capitalizing the flag name, and replacing separator characters like periods or hyphens with underscores.

By default, flags are not parsed from environment variables at all.

func WithFilesystem

func WithFilesystem(fs iofs.FS) Option

WithFilesystem tells Parse to use the provided filesystem when accessing files on disk, typically when reading a config file.

By default, the host filesystem is used, via os.Open.

type ParseContext

type ParseContext struct {
	// contains filtered or unexported fields
}

ParseContext receives and maintains parse options.

type Resetter

type Resetter interface {
	// Reset should revert the flag set to its initial state, including all
	// flags defined in the flag set. If reset returns successfully, the flag
	// set should be as if it were newly constructed: IsParsed should return
	// false, GetArgs should return an empty slice, etc.
	Reset() error
}

Resetter may optionally be implemented by Flags.

Directories

Path Synopsis
examples
Package ffenv provides an .env config file parser.
Package ffenv provides an .env config file parser.
Package ffhelp provides tools to produce help text for flags and commands.
Package ffhelp provides tools to produce help text for flags and commands.
Package ffjson provides a JSON config file parser.
Package ffjson provides a JSON config file parser.
Package fftest provides tools for testing flag sets.
Package fftest provides tools for testing flag sets.
Package fftoml provides a TOML config file parser.
Package fftoml provides a TOML config file parser.
Package ffval provides common flag value types and helpers.
Package ffval provides common flag value types and helpers.
Package ffyaml provides a YAML config file parser.
Package ffyaml provides a YAML config file parser.
internal
ffdata
Package ffdata provides data-related helpers for ff packages.
Package ffdata provides data-related helpers for ff packages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL