cli

package
v1.0.4 Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2024 License: Apache-2.0 Imports: 21 Imported by: 38

Documentation

Overview

Package cli defines an SDK for building performant and consistent CLIs. All commands start with a RootCommand which can then accept one or more nested subcommands. Subcommands can also be RootCommand, which creates nested CLIs (e.g. "my-tool do the-thing").

The CLI provides opinionated, formatted help output including flag structure. It also provides a more integrated experience for defining CLI flags, hiding flags, and generating aliases.

To minimize startup times, things are as lazy-loaded as possible. This means commands are instantiated only when needed. Most applications will create a private global variable that returns the root command:

var rootCmd = func() cli.Command {
  return &cli.RootCommand{
    Name:    "my-tool",
    Version: "1.2.3",
    Commands: map[string]cli.CommandFactory{
      "eat": func() cli.Command {
        return &EatCommand{}
      },
      "sleep": func() cli.Command {
        return &SleepCommand{}
      },
    },
  }
}

This CLI could be invoked via:

$ my-tool eat
$ my-tool sleep

Deeply-nested RootCommand behave like nested CLIs:

var rootCmd = func() cli.Command {
  return &cli.RootCommand{
    Name:    "my-tool",
    Version: "1.2.3",
    Commands: map[string]cli.CommandFactory{
      "transport": func() cli.Command {
        return &cli.RootCommand{
          Name:        "transport",
          Description: "Subcommands for transportation",
          Commands: map[string]cli.CommandFactory{
            "bus": func() cli.Command {
              return &BusCommand{}
            },
            "car": func() cli.Command {
              return &CarCommand{}
            },
            "train": func() cli.Command {
              return &TrainCommand{}
            },
          },
        }
      },
    },
  }
}

This CLI could be invoked via:

$ my-tool transport bus
$ my-tool transport car
$ my-tool transport train
Example (AfterParse)
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/abcxyz/pkg/cli"
)

type StreamCommand struct {
	cli.BaseCommand

	flagOldAddress string
	flagAddress    string
}

func (c *StreamCommand) Desc() string {
	return "Stream a data stream"
}

func (c *StreamCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options]

  Stream a data stream.
`
}

func (c *StreamCommand) Flags() *cli.FlagSet {
	set := c.NewFlagSet()

	f := set.NewSection("SERVER OPTIONS")

	f.StringVar(&cli.StringVar{
		Name:    "server-address",
		Example: "https://my.corp.server:8145",
		Default: "http://localhost:8145",
		EnvVar:  "CLI_SERVER_ADDRESS",
		Target:  &c.flagAddress,
		Usage:   "Endpoint, including protocol and port, the server.",
	})

	// Deprecated - use -server-address instead.
	f.StringVar(&cli.StringVar{
		Name:    "address",
		Default: "http://localhost:8145",
		Target:  &c.flagOldAddress,
		Hidden:  true,
	})

	// Each AfterParse will be invoked after flags have been parsed.
	set.AfterParse(func(existingErr error) error {
		// Example of deferred defaulting. At this point, it is safe to set values
		// of flags to other values.
		if c.flagOldAddress != "" {
			c.Errf("WARNING: -address is deprecated, use -server-address instead")
		}
		if c.flagAddress == "" {
			c.flagAddress = c.flagOldAddress
		}

		return nil
	})

	return set
}

func (c *StreamCommand) Run(ctx context.Context, args []string) error {
	if err := c.Flags().Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	c.Outf("address: %s", c.flagAddress)

	// TODO: implement
	return nil
}

func main() {
	ctx := context.Background()

	rootCmd := func() cli.Command {
		return &cli.RootCommand{
			Name:    "my-tool",
			Version: "1.2.3",
			Commands: map[string]cli.CommandFactory{
				"stream": func() cli.Command {
					return &StreamCommand{}
				},
			},
		}
	}

	cmd := rootCmd()

	// Help output is written to stderr by default. Redirect to stdout so the
	// "Output" assertion works.
	cmd.SetStderr(os.Stdout)

	if err := cmd.Run(ctx, []string{"stream", "-address", "1.2.3"}); err != nil {
		panic(err)
	}

}
Output:

WARNING: -address is deprecated, use -server-address instead
address: http://localhost:8145
Example (CommandGroup)
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/abcxyz/pkg/cli"
)

type EatCommand struct {
	cli.BaseCommand
}

func (c *EatCommand) Desc() string {
	return "Eat some food"
}

func (c *EatCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options]

  The eat command eats food.
`
}

func (c *EatCommand) Flags() *cli.FlagSet {
	return c.NewFlagSet()
}

func (c *EatCommand) Run(ctx context.Context, args []string) error {
	if err := c.Flags().Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	// TODO: implement
	return nil
}

type DrinkCommand struct {
	cli.BaseCommand
}

func (c *DrinkCommand) Desc() string {
	return "Drink some water"
}

func (c *DrinkCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options]

  The drink command drinks water.
`
}

func (c *DrinkCommand) Flags() *cli.FlagSet {
	return c.NewFlagSet()
}

func (c *DrinkCommand) Run(ctx context.Context, args []string) error {
	if err := c.Flags().Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	// TODO: implement
	return nil
}

func main() {
	ctx := context.Background()

	rootCmd := func() cli.Command {
		return &cli.RootCommand{
			Name:    "my-tool",
			Version: "1.2.3",
			Commands: map[string]cli.CommandFactory{
				"eat": func() cli.Command {
					return &EatCommand{}
				},
				"drink": func() cli.Command {
					return &DrinkCommand{}
				},
			},
		}
	}

	cmd := rootCmd()

	// Help output is written to stderr by default. Redirect to stdout so the
	// "Output" assertion works.
	cmd.SetStderr(os.Stdout)

	cmd.Outf("\nTop-level help:")
	if err := cmd.Run(ctx, []string{"-h"}); err != nil {
		panic(err)
	}

	cmd.Outf("\nCommand-level help:")
	if err := cmd.Run(ctx, []string{"eat", "-h"}); err != nil {
		panic(err)
	}

}
Output:

Top-level help:
Usage: my-tool COMMAND

  drink    Drink some water
  eat      Eat some food

Command-level help:
Usage: my-tool eat [options]

  The eat command eats food.
Example (CommandWithFlags)
package main

import (
	"context"
	"fmt"
	"os"
	"strconv"

	"github.com/abcxyz/pkg/cli"
)

type CountCommand struct {
	cli.BaseCommand

	flagStep int64
}

func (c *CountCommand) Desc() string {
	return "Counts from 0 up to a number"
}

func (c *CountCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options] MAX

  The count command prints out a list of numbers starting from 0 up to and
  including the provided MAX.

      $ {{ COMMAND }} 50

  The value for MAX must be a positive integer.
`
}

func (c *CountCommand) Flags() *cli.FlagSet {
	set := c.NewFlagSet()

	f := set.NewSection("NUMBER OPTIONS")

	f.Int64Var(&cli.Int64Var{
		Name:    "step",
		Aliases: []string{"s"},
		Example: "1",
		Default: 1,
		Target:  &c.flagStep,
		Usage:   "Numeric value by which to increment between each number.",
	})

	return set
}

func (c *CountCommand) Run(ctx context.Context, args []string) error {
	f := c.Flags()
	if err := f.Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	args = f.Args()
	if len(args) != 1 {
		return fmt.Errorf("expected 1 argument, got %q", args)
	}

	maxStr := args[0]
	max, err := strconv.ParseInt(maxStr, 10, 64)
	if err != nil {
		return fmt.Errorf("failed to parse max: %w", err)
	}

	for i := int64(0); i <= max; i += c.flagStep {
		c.Outf("%d", i)
	}

	return nil
}

func main() {
	ctx := context.Background()

	// Create the command.
	rootCmd := func() cli.Command {
		return &cli.RootCommand{
			Name:    "my-tool",
			Version: "1.2.3",
			Commands: map[string]cli.CommandFactory{
				"count": func() cli.Command {
					return &CountCommand{}
				},
			},
		}
	}

	cmd := rootCmd()

	// Help output is written to stderr by default. Redirect to stdout so the
	// "Output" assertion works.
	cmd.SetStderr(os.Stdout)

	cmd.Outf("\nUp to 3:")
	if err := cmd.Run(ctx, []string{"count", "3"}); err != nil {
		panic(err)
	}

	cmd.Outf("\nUp to 10, stepping 2")
	if err := cmd.Run(ctx, []string{"count", "-step=2", "10"}); err != nil {
		panic(err)
	}

}
Output:


Up to 3:
0
1
2
3

Up to 10, stepping 2
0
2
4
6
8
10
Example (Completions)
package main

import (
	"context"
	"strconv"
	"time"

	"github.com/posener/complete/v2"
	"github.com/posener/complete/v2/predict"

	"github.com/abcxyz/pkg/cli"
)

type SingCommand struct {
	cli.BaseCommand

	flagSong string
	flagFade time.Duration
	flagNow  int64
}

func (c *SingCommand) Desc() string {
	return "Sings a song"
}

func (c *SingCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options] PATH

  Sings the given song at the audio file.
`
}

// PredictArgs is an optional interface which will predict argument values. If
// omitted, no arguments are suggested.
func (c *SingCommand) PredictArgs() complete.Predictor {
	return predict.Files("*.mp3")
}

func (c *SingCommand) Flags() *cli.FlagSet {
	set := c.NewFlagSet()

	f := set.NewSection("SONG OPTIONS")

	f.StringVar(&cli.StringVar{
		Name:    "song",
		Aliases: []string{"s"},
		Example: "Itsy Bitsy Spider",
		Target:  &c.flagSong,
		Predict: predict.Set{"Happy Birthday", "Twinkly Twinkle Little Star"},
		Usage:   "Name of the song to play.",
	})

	f.DurationVar(&cli.DurationVar{
		Name:    "fade",
		Example: "5s",
		Default: 5 * time.Second,
		Target:  &c.flagFade,
		Predict: predict.Set{"1s", "5s", "10s"},
		Usage:   "Duration to fade audio tracks.",
	})

	f.Int64Var(&cli.Int64Var{
		Name:    "now",
		Example: "10404929",
		Default: time.Now().Unix(),
		Target:  &c.flagNow,
		Predict: complete.PredictFunc(func(prefix string) []string {
			return []string{strconv.FormatInt(time.Now().Unix(), 10)}
		}),
		Usage: "Curring timestamp, in unix seconds.",
	})

	return set
}

func (c *SingCommand) Run(ctx context.Context, args []string) error {
	return nil
}

func main() {
	// The example is above, demonstrating various ways to define completions. To
	// see even more ways, look at the examples at
	// github.com/posener/complete/tree/master.
	//
	// Since completions are a shell function, users must add something to their
	// shell configuration (e.g. .bashrc, .zshrc). The easiest method to install
	// completions is to allow the binary to do it. It will detect the shell and
	// insert the correct code into the user's shell profile. Instruct users to
	// run the following command:
	//
	//     COMP_INSTALL=1 COMP_YES=1 my-cli
	//
	// This will automatically install shell completions. To uninstall
	// completions, instruct users to run:
	//
	//     COMP_UNINSTALL=1 COMP_YES=1 my-cli
	//
	// This will automatically uninstall the completions.
	//
	// If users want to install the completions manually, you will need to provide
	// them shell-specific instructions. The setup usually requires adding the
	// following lines:
	//
	//     autoload -U +X bashcompinit && bashcompinit
	//     complete -o nospace -C /full/path/to/my-cli my-cli
	//
	// Of note:
	//
	//   1. You must use the full filepath to the CLI binary
	//   2. The argument to the CLI binary is itself
}
Output:

Example (PersistentFlags)
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/abcxyz/pkg/cli"
)

// ServerFlags represent the shared flags among all server commands. Embed this
// struct into any commands that interact with a server.
type ServerFlags struct {
	flagAddress       string
	flagTLSSkipVerify bool
}

func (sf *ServerFlags) Register(set *cli.FlagSet) {
	f := set.NewSection("SERVER OPTIONS")

	f.StringVar(&cli.StringVar{
		Name:    "server-address",
		Example: "https://my.corp.server:8145",
		Default: "http://localhost:8145",
		EnvVar:  "CLI_SERVER_ADDRESS",
		Target:  &sf.flagAddress,
		Usage:   "Endpoint, including protocol and port, the server.",
	})

	f.BoolVar(&cli.BoolVar{
		Name:    "insecure",
		Default: false,
		EnvVar:  "CLI_SERVER_TLS_SKIP_VERIFY",
		Target:  &sf.flagTLSSkipVerify,
		Usage:   "Skip TLS verification. This is bad, please don't do it.",
	})
}

type UploadCommand struct {
	cli.BaseCommand
	serverFlags ServerFlags
}

func (c *UploadCommand) Desc() string {
	return "Upload a file"
}

func (c *UploadCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options]

  Upload a file to the server.
`
}

func (c *UploadCommand) Flags() *cli.FlagSet {
	set := c.NewFlagSet()
	c.serverFlags.Register(set)
	return set
}

func (c *UploadCommand) Run(ctx context.Context, args []string) error {
	if err := c.Flags().Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	_ = c.serverFlags.flagAddress
	_ = c.serverFlags.flagTLSSkipVerify

	// TODO: implement
	return nil
}

type DownloadCommand struct {
	cli.BaseCommand
	serverFlags ServerFlags
}

func (c *DownloadCommand) Desc() string {
	return "Download a file"
}

func (c *DownloadCommand) Help() string {
	return `
Usage: {{ COMMAND }} [options]

  Download a file from the server.
`
}

func (c *DownloadCommand) Flags() *cli.FlagSet {
	set := c.NewFlagSet()
	c.serverFlags.Register(set)
	return set
}

func (c *DownloadCommand) Run(ctx context.Context, args []string) error {
	if err := c.Flags().Parse(args); err != nil {
		return fmt.Errorf("failed to parse flags: %w", err)
	}

	_ = c.serverFlags.flagAddress
	_ = c.serverFlags.flagTLSSkipVerify

	// TODO: implement
	return nil
}

func main() {
	ctx := context.Background()

	rootCmd := func() cli.Command {
		return &cli.RootCommand{
			Name:    "my-tool",
			Version: "1.2.3",
			Commands: map[string]cli.CommandFactory{
				"download": func() cli.Command {
					return &DownloadCommand{}
				},
				"upload": func() cli.Command {
					return &UploadCommand{}
				},
			},
		}
	}

	cmd := rootCmd()

	// Help output is written to stderr by default. Redirect to stdout so the
	// "Output" assertion works.
	cmd.SetStderr(os.Stdout)

	cmd.Outf("\nTop-level help:")
	if err := cmd.Run(ctx, []string{"-h"}); err != nil {
		panic(err)
	}

	cmd.Outf("\nCommand-level help:")
	if err := cmd.Run(ctx, []string{"download", "-h"}); err != nil {
		panic(err)
	}

}
Output:

Top-level help:
Usage: my-tool COMMAND

  download    Download a file
  upload      Upload a file

Command-level help:
Usage: my-tool download [options]

  Download a file from the server.

SERVER OPTIONS

    -insecure
        Skip TLS verification. This is bad, please don't do it. The default
        value is "false". This option can also be specified with the
        CLI_SERVER_TLS_SKIP_VERIFY environment variable.

    -server-address="https://my.corp.server:8145"
        Endpoint, including protocol and port, the server. The default value
        is "http://localhost:8145". This option can also be specified with the
        CLI_SERVER_ADDRESS environment variable.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Flag

func Flag[T any](f *FlagSection, i *Var[T])

Flag is a lower-level API for creating a flag on a flag section. Callers should use this for defining new flags as it sets defaults and provides more granular usage details.

It panics if any of the target, parser, or printer are nil.

Types

type AfterParseFunc added in v0.6.0

type AfterParseFunc func(existingErr error) error

AfterParseFunc is the type signature for functions that are called after flags have been parsed.

type ArgPredictor added in v0.4.0

type ArgPredictor interface {
	PredictArgs() complete.Predictor
}

ArgPredictor is an optional interface that Command can implement to declare predictions for their arguments. By default, commands predict nothing for arguments.

type BaseCommand

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

BaseCommand is the default command structure. All commands should embed this structure.

func (*BaseCommand) Errf added in v0.4.0

func (c *BaseCommand) Errf(format string, a ...any)

Errf is a shortcut to write to BaseCommand.Stderr.

func (*BaseCommand) ExecutablePath added in v0.5.0

func (c *BaseCommand) ExecutablePath() (string, error)

ExecutablePath returns the absolute path of the CLI executable binary. All symlinks are resolved to their real values.

func (*BaseCommand) Flags

func (c *BaseCommand) Flags() *FlagSet

Flags returns the base command flags, which is always nil.

func (*BaseCommand) GetEnv added in v0.6.0

func (c *BaseCommand) GetEnv(key string) string

GetEnv returns the value from the environment at the given key.

func (*BaseCommand) Hidden

func (c *BaseCommand) Hidden() bool

Hidden indicates whether the command is hidden. The default is unhidden.

func (*BaseCommand) LookupEnv added in v0.6.0

func (c *BaseCommand) LookupEnv(key string) (string, bool)

LookupEnv returns the value from the environment at the given key. The second return value indicates whether the value was set.

func (*BaseCommand) NewFlagSet added in v0.6.0

func (c *BaseCommand) NewFlagSet(opts ...Option) *FlagSet

NewFlagSet creates a new flag set that inherits properties from the command.

func (*BaseCommand) Outf added in v0.4.0

func (c *BaseCommand) Outf(format string, a ...any)

Outf is a shortcut to write to BaseCommand.Stdout.

func (*BaseCommand) Pipe

func (c *BaseCommand) Pipe() (stdin, stdout, stderr *bytes.Buffer)

Pipe creates new unqiue stdin, stdout, and stderr buffers, sets them on the command, and returns them. This is most useful for testing where callers want to simulate inputs or assert certain command outputs.

func (*BaseCommand) Prompt

func (c *BaseCommand) Prompt(ctx context.Context, msg string, args ...any) (string, error)

Prompt asks for user input and reads it from [Stdin] until it encounters a newline character. If there's an input stream (e.g. a pipe), it will read the pipe. The result will not include the trailing newline or carriage return. For more information about the conditions under which the prompt is displayed, see [PromptTo].

func (*BaseCommand) PromptAll added in v0.6.1

func (c *BaseCommand) PromptAll(ctx context.Context, msg string, args ...any) (string, error)

PromptAll asks for user input and reads from [Stdin] until it encounters an EOF. If there's an input stream (e.g. a pipe), it will read the pipe. For more information about the conditions under which the prompt is displayed, see [PromptTo].

func (*BaseCommand) PromptTo added in v0.6.1

func (c *BaseCommand) PromptTo(ctx context.Context, splitFunc bufio.SplitFunc, msg string, args ...any) (string, error)

PromptTo provides a mechanism for asking for user input. It reads from [Stdin], using the provided scanner split function. If there's an input stream (e.g. a pipe), it will read the pipe.

The prompt will be printed to c.Stdout() if any of these cases is true:

  • the terminal is a TTY (for real user interaction)
  • c.StdIn(), c.Stdout(), and c.Stderr() came from io.Pipe() (for unit-testing back-and-forth dialog)

It will fail if stdin pipe and the terminal is not a tty. If the context is canceled, [Stdin] could be in a partially-read state.

func (*BaseCommand) SetLookupEnv added in v0.6.0

func (c *BaseCommand) SetLookupEnv(fn LookupEnvFunc)

SetLookupEnv sets the CLIs environment lookup logic to use the provided function.

func (*BaseCommand) SetStderr

func (c *BaseCommand) SetStderr(w io.Writer)

SetStdout sets the standard error.

func (*BaseCommand) SetStdin

func (c *BaseCommand) SetStdin(r io.Reader)

SetStdout sets the standard input.

func (*BaseCommand) SetStdout

func (c *BaseCommand) SetStdout(w io.Writer)

SetStdout sets the standard out.

func (*BaseCommand) Stderr

func (c *BaseCommand) Stderr() io.Writer

Stderr returns the stderr stream.

func (*BaseCommand) Stdin

func (c *BaseCommand) Stdin() io.Reader

Stdin returns the stdin stream.

func (*BaseCommand) Stdout

func (c *BaseCommand) Stdout() io.Writer

Stdout returns the stdout stream.

func (*BaseCommand) WorkingDir added in v0.5.0

func (c *BaseCommand) WorkingDir() (string, error)

WorkingDir returns the absolute path of current working directory from where the command was started. All symlinks are resolved to their real paths.

type BoolVar

type BoolVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default bool
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *bool
}

type Command

type Command interface {
	// Desc provides a short, one-line description of the command. It must be
	// shorter than 50 characters and should not end with a period or punctuation.
	Desc() string

	// Help is the long-form help output. It should include usage instructions and
	// flag information.
	//
	// Callers can insert the literal string "{{ COMMAND }}" which will be
	// replaced with the actual subcommand structure.
	Help() string

	// Flags returns the list of flags that are defined on the command.
	Flags() *FlagSet

	// Hidden indicates whether the command is hidden from help output.
	Hidden() bool

	// Run executes the command.
	Run(ctx context.Context, args []string) error

	// Stdout returns the stdout stream. SetStdout sets the stdout stream.
	Stdout() io.Writer
	SetStdout(w io.Writer)

	// Outf is a shortcut to write to [Command.Stdout]. It automatically appends a
	// trailing newline if one is not present.
	Outf(format string, a ...any)

	// Stderr returns the stderr stream. SetStderr sets the stderr stream.
	Stderr() io.Writer
	SetStderr(w io.Writer)

	// Errf is a shortcut to write to [Command.Stderr]. It automatically appends a
	// trailing newline if one is not present.
	Errf(format string, a ...any)

	// Stdin returns the stdin stream. SetStdin sets the stdin stream.
	Stdin() io.Reader
	SetStdin(r io.Reader)
}

Command is the interface for a command or subcommand. Most of these functions have default implementations on BaseCommand.

type CommandFactory

type CommandFactory func() Command

CommandFactory returns a new instance of a command. This returns a function instead of allocations because we want the CLI to load as fast as possible, so we lazy load as much as possible.

type DurationVar

type DurationVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default time.Duration
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *time.Duration
}

type FlagSection

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

FlagSection represents a group section of flags. The flags are actually "flat" in memory, but maintain a structure for better help output and alias matching.

func (*FlagSection) BoolVar

func (f *FlagSection) BoolVar(i *BoolVar)

BoolVar creates a new boolean variable (true/false). By convention, the default value should always be false. For example:

Bad: -enable-cookies (default: true)
Good: -disable-cookies (default: false)

Consider naming your flags to match this convention.

func (*FlagSection) DurationVar

func (f *FlagSection) DurationVar(i *DurationVar)

func (*FlagSection) Float64Var

func (f *FlagSection) Float64Var(i *Float64Var)

func (*FlagSection) Int64Var

func (f *FlagSection) Int64Var(i *Int64Var)

func (*FlagSection) IntVar

func (f *FlagSection) IntVar(i *IntVar)

func (*FlagSection) LogLevelVar added in v0.7.0

func (f *FlagSection) LogLevelVar(i *LogLevelVar)

func (*FlagSection) StringMapVar

func (f *FlagSection) StringMapVar(i *StringMapVar)

func (*FlagSection) StringSliceVar

func (f *FlagSection) StringSliceVar(i *StringSliceVar)

func (*FlagSection) StringVar

func (f *FlagSection) StringVar(i *StringVar)

func (*FlagSection) TimeVar added in v0.4.0

func (f *FlagSection) TimeVar(layout string, i *TimeVar)

func (*FlagSection) Uint64Var

func (f *FlagSection) Uint64Var(i *Uint64Var)

func (*FlagSection) UintVar

func (f *FlagSection) UintVar(i *UintVar)

type FlagSet

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

FlagSet is the root flag set for creating and managing flag sections.

func NewFlagSet

func NewFlagSet(opts ...Option) *FlagSet

NewFlagSet creates a new root flag set.

func (*FlagSet) AfterParse added in v0.6.0

func (f *FlagSet) AfterParse(fn AfterParseFunc)

AfterParse defines a post-parse function. This can be used to set flag defaults that should not be set until after parsing, such as defaulting flag values to the value of other flags. These functions are called after flags have been parsed by the flag library, but before [Parse] returns.

Example (CheckIfError)
set := NewFlagSet()

set.AfterParse(func(existingErr error) error {
	// Do not run this function if flag parsing or other AfterParse functions
	// have failed.
	if existingErr != nil {
		return nil //nolint:nilerr
	}

	// Logic
	return nil
})
Output:

Example (DeferredDefault)
set := NewFlagSet()
f := set.NewSection("FLAGS")

// This is an old flag that we will remove in the future. We want "-address"
// to default to the value of this flag. However, the value of this flag is
// not known until after parsing, so we can't set `Default` on the address
// flag to this flag, since that's resolved at compile time. Instead, we need
// to use the `AfterParse` function to set the defaults.
var host string
f.StringVar(&StringVar{
	Name:   "host",
	Target: &host,
	Hidden: true,
})

var address string
f.StringVar(&StringVar{
	Name:   "address",
	Target: &address,
})

set.AfterParse(func(existingErr error) error {
	if address == "" {
		address = host
	}
	return nil
})
Output:

Example (DeferredDefaultArgs)
set := NewFlagSet()
f := set.NewSection("FLAGS")

// The default value should be the first argument. Setting this to default to
// `os.Args[1]` will not work, because arguments can shift after flag parsing.
// Instead, we need to use the `AfterParse` function to set the default.
var address string
f.StringVar(&StringVar{
	Name:   "address",
	Target: &address,
})

set.AfterParse(func(existingErr error) error {
	if address == "" {
		address = set.Arg(1)
	}
	return nil
})
Output:

Example (Validation)
set := NewFlagSet()
f := set.NewSection("FLAGS")

var address string
f.StringVar(&StringVar{
	Name:   "address",
	Target: &address,
})

var protocol string
f.StringVar(&StringVar{
	Name:   "protocol",
	Target: &protocol,
})

set.AfterParse(func(existingErr error) error {
	var merr error
	if address == "" {
		return fmt.Errorf("-address is required")
	}
	if address == "" {
		return fmt.Errorf("-protocol is required")
	}
	return merr
})
Output:

func (*FlagSet) Arg added in v0.6.0

func (f *FlagSet) Arg(i int) string

Arg implements flag.FlagSet#Arg.

func (*FlagSet) Args

func (f *FlagSet) Args() []string

Args implements flag.FlagSet#Args.

func (*FlagSet) GetEnv added in v0.6.0

func (f *FlagSet) GetEnv(k string) string

GetEnv is a convenience function for looking up an environment variable. By default, it is the same as os.GetEnv, but the lookup function can be overridden.

func (*FlagSet) Help

func (f *FlagSet) Help() string

Help returns formatted help output.

func (*FlagSet) Lookup added in v0.6.0

func (f *FlagSet) Lookup(name string) *flag.Flag

Lookup implements flag.FlagSet#Lookup.

func (*FlagSet) LookupEnv added in v0.6.0

func (f *FlagSet) LookupEnv(k string) (string, bool)

LookupEnv is a convenience function for looking up an environment variable. By default, it is the same as os.LookupEnv, but the lookup function can be overridden.

func (*FlagSet) NewSection

func (f *FlagSet) NewSection(name string) *FlagSection

NewSection creates a new flag section. By convention, section names should be all capital letters (e.g. "MY SECTION"), but this is not strictly enforced.

func (*FlagSet) Parse

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

Args implements flag.FlagSet#Parse.

func (*FlagSet) Parsed

func (f *FlagSet) Parsed() bool

Args implements flag.FlagSet#Parsed.

func (*FlagSet) Visit

func (f *FlagSet) Visit(fn func(*flag.Flag))

Args implements flag.FlagSet#Visit.

func (*FlagSet) VisitAll

func (f *FlagSet) VisitAll(fn func(*flag.Flag))

Args implements flag.FlagSet#VisitAll.

type Float64Var

type Float64Var struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default float64
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *float64
}

type Int64Var

type Int64Var struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default int64
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *int64
}

type IntVar

type IntVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default int
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *int
}

type LogLevelVar added in v0.7.0

type LogLevelVar struct {
	Logger *slog.Logger
}

type LookupEnvFunc

type LookupEnvFunc = func(string) (string, bool)

LookupEnvFunc is the signature of a function for looking up environment variables. It makes that of os.LookupEnv.

func MapLookuper

func MapLookuper(m map[string]string) LookupEnvFunc

MapLookuper returns a LookupEnvFunc that reads from a map instead of the environment. This is mostly used for testing.

func MultiLookuper added in v0.6.0

func MultiLookuper(fns ...LookupEnvFunc) LookupEnvFunc

MultiLookuper accepts multiple LookupEnvFunc. It runs them in order on the environment key, returning the first entry that reports found.

type Option

type Option func(fs *FlagSet) *FlagSet

Option is an option to the flagset.

func WithLookupEnv

func WithLookupEnv(fn LookupEnvFunc) Option

WithLookupEnv defines a custom function for looking up environment variables. This is mostly useful for testing.

To bind to a CLI's lookup function:

func (c *CountCommand) Flags() *cli.FlagSet {
	set := cli.NewFlagSet(cli.WithLookupEnv(c.LookupEnv))
}

Alternatively use BaseCommand.NewFlagSet.

type ParserFunc

type ParserFunc[T any] func(val string) (T, error)

ParserFunc is a function that parses a value into T, or returns an error.

type PrinterFunc

type PrinterFunc[T any] func(cur T) string

PrinterFunc is a function that pretty-prints T.

type RootCommand

type RootCommand struct {
	BaseCommand

	// Name is the name of the command or subcommand. For top-level commands, this
	// should be the binary name. For subcommands, this should be the name of the
	// subcommand.
	Name string

	// Description is the human-friendly description of the command.
	Description string

	// Hide marks the entire subcommand as hidden. It will not be shown in help
	// output.
	Hide bool

	// Version defines the version information for the command. This can be
	// omitted for subcommands as it will be inherited from the parent.
	Version string

	// Commands is the list of sub commands.
	Commands map[string]CommandFactory
}

RootCommand represents a command root for a parent or collection of subcommands.

func (*RootCommand) Desc

func (r *RootCommand) Desc() string

Desc is the root command description. It is used to satisfy the Command interface.

func (*RootCommand) Help

func (r *RootCommand) Help() string

Help compiles structured help information. It is used to satisfy the Command interface.

func (*RootCommand) Hidden

func (r *RootCommand) Hidden() bool

Hidden determines whether the command group is hidden. It is used to satisfy the Command interface.

func (*RootCommand) Run

func (r *RootCommand) Run(ctx context.Context, args []string) error

Run executes the command and prints help output or delegates to a subcommand.

type SetterFunc

type SetterFunc[T any] func(cur *T, val T)

SetterFunc is a function that sets *T to T.

type StringMapVar

type StringMapVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default map[string]string
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *map[string]string
}

type StringSliceVar

type StringSliceVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default []string
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *[]string
}

type StringVar

type StringVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default string
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *string
}

type TimeVar added in v0.4.0

type TimeVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default time.Time
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *time.Time
}

type Uint64Var

type Uint64Var struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default uint64
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *uint64
}

type UintVar

type UintVar struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default uint
	Hidden  bool
	EnvVar  string
	Predict complete.Predictor
	Target  *uint
}

type Value

type Value interface {
	flag.Value

	// Get returns the value. Even though we know the concrete type with generics,
	// this returns [any] to match the standard library.
	Get() any

	// Aliases returns any defined aliases of the flag.
	Aliases() []string

	// Example returns an example input for the flag. For example, if the flag was
	// accepting a URL, this could be "https://example.com". This is largely meant
	// as a hint to the CLI user and only affects help output.
	//
	// If there is a default value, the example value should be different from the
	// default value.
	Example() string

	// Hidden returns true if the flag is hidden, false otherwise.
	Hidden() bool

	// IsBoolFlag returns true if the flag accepts no arguments, false otherwise.
	IsBoolFlag() bool

	// Predictor returns a completion predictor. All flags have a default
	// predictor, but they can be further customized by the developer when
	// instantiating the flag.
	Predictor() complete.Predictor
}

Value is an extension of flag.Value which adds additional fields for setting examples and defining aliases. All flags with this package must statisfy this interface.

type Var

type Var[T any] struct {
	Name    string
	Aliases []string
	Usage   string
	Example string
	Default T
	Hidden  bool
	IsBool  bool
	EnvVar  string
	Target  *T

	// Parser and Printer are the generic functions for converting string values
	// to/from the target value. These are populated by the individual flag
	// helpers.
	Parser  ParserFunc[T]
	Printer PrinterFunc[T]

	// Predict is the completion predictor. If no predictor is defined, it
	// defaults to predicting something (waiting for a value) for all flags except
	// boolean flags (which have no value). Callers are encouraged to customize
	// the predictions.
	Predict complete.Predictor

	// Setter defines the function that sets the variable into the target. If nil,
	// it uses a default setter which overwrites the entire value of the Target.
	// Implementations that do special processing (such as appending to a slice),
	// may override this to customize the behavior.
	Setter SetterFunc[T]
}

Jump to

Keyboard shortcuts

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