Movatterモバイル変換


[0]ホーム

URL:


Notice  The highest tagged major version isv4.

ffcli

package
v3.4.0Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2023 License:Apache-2.0Imports:7Imported by:367

Details

Repository

github.com/peterbourgon/ff

Links

README

ffcligo.dev reference

ffcli stands for flags-first command line interface,and provides an opinionated way to build CLIs.

Rationale

Popular CLI frameworks likespf13/cobra,urfave/cli, oralecthomas/kingpin tend to have extremely large APIs, to support alarge number of "table stakes" features.

This package is intended to be a lightweight alternative to those packages. Incontrast to them, the API surface area of package ffcli is very small, with theimmediate goal of being intuitive and productive, and the long-term goal ofsupporting commandline applications that are substantially easier to understandand maintain.

To support these goals, the package is concerned only with the core mechanics ofdefining a command tree, parsing flags, and selecting a command to run. It doesnot intend to be a one-stop-shop for everything your commandline applicationneeds. Features like tab completion or colorized output are orthogonal tocommand tree parsing, and should be easy to provide on top of ffcli.

Finally, this package follows in the philosophy of its parent package ff, or"flags-first". Flags, and more specifically the Go stdlib flag.FlagSet, shouldbe the primary mechanism of getting configuration from the execution environmentinto your program. The affordances provided by package ff, including environmentvariable and config file parsing, are also available in package ffcli. Supportfor other flag packages is a non-goal.

Goals

  • Absolute minimum usable API
  • Prefer using existing language features/patterns/abstractions whenever possible
  • Enable integration-style testing of CLIs with mockable dependencies
  • No global state

Non-goals

  • All conceivably useful features
  • Integration with flag packages other thanpackage flag andff

Usage

The core of the package is theffcli.Command. Here is the simplestpossible example of an ffcli program.

import ("context""os""github.com/peterbourgon/ff/v3/ffcli")func main() {root := &ffcli.Command{Exec: func(ctx context.Context, args []string) error {println("hello world")return nil},}root.ParseAndRun(context.Background(), os.Args[1:])}

Most CLIs use flags and arguments to control behavior. Here is a command whichtakes a string to repeat as an argument, and the number of times to repeat it asa flag.

fs := flag.NewFlagSet("repeat", flag.ExitOnError)n := fs.Int("n", 3, "how many times to repeat")root := &ffcli.Command{ShortUsage: "repeat [-n times] <arg>",ShortHelp:  "Repeatedly print the argument to stdout.",FlagSet:    fs,Exec: func(ctx context.Context, args []string) error {if nargs := len(args); nargs != 1 {return fmt.Errorf("repeat requires exactly 1 argument, but you provided %d", nargs)}for i := 0; i < *n; i++ {fmt.Fprintln(os.Stdout, args[0])}return nil},}if err := root.ParseAndRun(context.Background(), os.Args[1:]); err != nil {log.Fatal(err)}

Each command may have subcommands, allowing you to build a command tree.

var (rootFlagSet   = flag.NewFlagSet("textctl", flag.ExitOnError)verbose       = rootFlagSet.Bool("v", false, "increase log verbosity")repeatFlagSet = flag.NewFlagSet("textctl repeat", flag.ExitOnError)n             = repeatFlagSet.Int("n", 3, "how many times to repeat"))repeat := &ffcli.Command{Name:       "repeat",ShortUsage: "textctl repeat [-n times] <arg>",ShortHelp:  "Repeatedly print the argument to stdout.",FlagSet:    repeatFlagSet,Exec:       func(_ context.Context, args []string) error { ... },}count := &ffcli.Command{Name:       "count",ShortUsage: "textctl count [<arg> ...]",ShortHelp:  "Count the number of bytes in the arguments.",Exec:       func(_ context.Context, args []string) error { ... },}root := &ffcli.Command{ShortUsage:  "textctl [flags] <subcommand>",FlagSet:     rootFlagSet,Subcommands: []*ffcli.Command{repeat, count},}if err := root.ParseAndRun(context.Background(), os.Args[1:]); err != nil {log.Fatal(err)}

ParseAndRun can also be split into distinct Parse and Run phases, allowing youto perform two-phase setup or initialization of e.g. API clients that requireuser-supplied configuration.

Examples

Seethe examples directory. If you'd like an example of a specifictype of program structure, or a CLI that satisfies a specific requirement,pleasefile an issue.

Documentation

Overview

Package ffcli is for building declarative commandline applications.

See the README athttps://github.com/peterbourgon/ff/tree/master/ffclifor more information.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrUnparsed =errors.New("command tree is unparsed, can't run")

ErrUnparsed is returned by Run if Parse hasn't been called first.

Functions

funcDefaultUsageFunc

func DefaultUsageFunc(c *Command)string

DefaultUsageFunc is the default UsageFunc used for all commandsif no custom UsageFunc is provided.

Types

typeCommand

type Command struct {// Name of the command. Used for sub-command matching, and as a replacement// for Usage, if no Usage string is provided. Required for sub-commands,// optional for the root command.Namestring// ShortUsage string for this command. Consumed by the DefaultUsageFunc and// printed at the top of the help output. Recommended but not required.// Should be one line of the form////     cmd [flags] subcmd [flags] <required> [<optional> ...]//// If it's not provided, the DefaultUsageFunc will use Name instead.// Optional, but recommended.ShortUsagestring// ShortHelp is printed next to the command name when it appears as a// sub-command, in the help output of its parent command. Optional, but// recommended.ShortHelpstring// LongHelp is consumed by the DefaultUsageFunc and printed in the help// output, after ShortUsage and before flags. Typically a paragraph or more// of prose-like text, providing more explicit context and guidance than// what is implied by flags and arguments. Optional.LongHelpstring// UsageFunc generates a complete usage output, written to the io.Writer// returned by FlagSet.Output() when the -h flag is passed. The function is// invoked with its corresponding command, and its output should reflect the// command's short usage, short help, and long help strings, subcommands,// and available flags. Optional; if not provided, a suitable, compact// default is used.UsageFunc func(c *Command)string// FlagSet associated with this command. Optional, but if none is provided,// an empty FlagSet will be defined and attached during the parse phase, so// that the -h flag works as expected.FlagSet *flag.FlagSet// Options provided to ff.Parse when parsing arguments for this command.// Optional.Options []ff.Option// Subcommands accessible underneath (i.e. after) this command. Optional.Subcommands []*Command// Exec is invoked if this command has been determined to be the terminal// command selected by the arguments provided to Parse or ParseAndRun. The// args passed to Exec are the args left over after flags parsing. Optional.//// If Exec returns flag.ErrHelp, then Run (or ParseAndRun) will behave as if// -h were passed and emit the complete usage output.//// If Exec is nil, and this command is identified as the terminal command,// then Parse, Run, and ParseAndRun will all return NoExecError. Callers may// check for this error and print e.g. help or usage text to the user, in// effect treating some commands as just collections of subcommands, rather// than being invocable themselves.Exec func(ctxcontext.Context, args []string)error// contains filtered or unexported fields}

Command combines a main function with a flag.FlagSet, and zero or moresub-commands. A commandline program can be represented as a declarative treeof commands.

func (*Command)Parse

func (c *Command) Parse(args []string)error

Parse the commandline arguments for this command and all sub-commandsrecursively, defining flags along the way. If Parse returns without an error,the terminal command has been successfully identified, and may be invoked bycalling Run.

If the terminal command identified by Parse doesn't define an Exec function,then Parse will return NoExecError.

Example (Then_Run)
package mainimport ("context""flag""fmt""log""github.com/peterbourgon/ff/v3/ffcli")func main() {// Assume our CLI will use some client that requires a token.type FooClient struct {token string}// That client would have a constructor.NewFooClient := func(token string) (*FooClient, error) {if token == "" {return nil, fmt.Errorf("token required")}return &FooClient{token: token}, nil}// We define the token in the root command's FlagSet.var (rootFlagSet = flag.NewFlagSet("mycommand", flag.ExitOnError)token       = rootFlagSet.String("token", "", "API token"))// Create a placeholder client, initially nil.var client *FooClient// Commands can reference and use it, because by the time their Exec// function is invoked, the client will be constructed.foo := &ffcli.Command{Name: "foo",Exec: func(context.Context, []string) error {fmt.Printf("subcommand foo can use the client: %v", client)return nil},}root := &ffcli.Command{FlagSet:     rootFlagSet,Subcommands: []*ffcli.Command{foo},}// Call Parse first, to populate flags and select a terminal command.if err := root.Parse([]string{"-token", "SECRETKEY", "foo"}); err != nil {log.Fatalf("Parse failure: %v", err)}// After a successful Parse, we can construct a FooClient with the token.var err errorclient, err = NewFooClient(*token)if err != nil {log.Fatalf("error constructing FooClient: %v", err)}// Then call Run, which will select the foo subcommand and invoke it.if err := root.Run(context.Background()); err != nil {log.Fatalf("Run failure: %v", err)}}
Output:subcommand foo can use the client: &{SECRETKEY}

func (*Command)ParseAndRun

func (c *Command) ParseAndRun(ctxcontext.Context, args []string)error

ParseAndRun is a helper function that calls Parse and then Run in a singleinvocation. It's useful for simple command trees that don't need two-phasesetup.

func (*Command)Run

func (c *Command) Run(ctxcontext.Context) (errerror)

Run selects the terminal command in a command tree previously identified by asuccessful call to Parse, and calls that command's Exec function with theappropriate subset of commandline args.

If the terminal command previously identified by Parse doesn't define an Execfunction, then Run will return NoExecError.

typeNoExecError

type NoExecError struct {Command *Command}

NoExecError is returned if the terminal command selected during the parsephase doesn't define an Exec function.

func (NoExecError)Error

func (eNoExecError) Error()string

Error implements the error interface.

Source Files

View all Source files

Directories

PathSynopsis
examples
textctlcommand

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f orF : Jump to
y orY : Canonical URL
go.dev uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic.Learn more.

[8]ページ先頭

©2009-2025 Movatter.jp