cli

package module
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2023 License: MIT Imports: 14 Imported by: 3

README

cli

Go Reference

Package cli makes it easy to create CLIs by defining options using struct tags.

⚠ This package is under active development and is subject to major breaking changes prior to the 1.0 release.

Example

package main

import (
	"fmt"

	"github.com/isobit/cli"
)

type App struct {
	Excited  bool   `cli:"help='when true, use exclamation point'"`
	Greeting string `cli:"env=GREETING,help=the greeting to use"`
	Name     string `cli:"required,short=n,help=your name"`
}

func (app *App) Run() error {
	punctuation := "."
	if app.Excited {
		punctuation = "!"
	}
	fmt.Printf("%s, %s%s\n", app.Greeting, app.Name, punctuation)
	return nil
}

func main() {
	cli.New("greet", &App{
		Greeting: "Hey",
	}).
		Parse().
		RunFatal()
}
$ greet --help
USAGE:
    greet [OPTIONS]

OPTIONS:
    -h, --help                    show usage help
    --excited                     when true, use exclamation point
    --greeting <VALUE>  GREETING  the greeting to use  (default: Hey)
    -n, --name <VALUE>            your name  (required)

$ GREETING="Hello" greet -n world --excited
Hello, world!

Struct Tags

The parsing behavior for config fields can be controlled by adding a struct tag that cli understands. Command struct tags look like cli:"key1,key2=value,key3='blah'"; for example:

struct Example {
	Foo string `cli:"required,placeholder=quux,short=f,env=FOO,help='hello, world'"`
}
Tag Value Description
- No Ignore field (similar to encoding/json)
required No Error if the field is not set at least once
help Yes Custom help text
placeholder Yes Custom value placeholder in help text
name Yes Explicit flag name (by default names are derived from the struct field name)
short Yes Single character short name alias
env Yes Environment variable to use as a default value
hidden No Don't show field in help text
append No Change flag setting behavior to append to value when specified multiple times (must be a slice type)
args No Set this field to the remaining non-flag args instead of recursively parsing them as subcommands.

Tags are parsed according to this ABNF:

tags = "cli:" DQUOTE *(tag ",") tag DQUOTE
tag = key [ "=" value ]
key = *<anything except "=">
value = *<anything except ","> / "'" *<anything except "'"> "'"

Field Types and Value Parsing

Primitive types (e.g. int and string), and pointers to primitive types (e.g. *int and *string) are handled natively by cli. In the case of pointers, if the default value is a nil pointer, and a value is passed, cli will construct a new value of the inner type and set the struct field to be a pointer to the newly constructed value.

There is no special parsing for string fields, they are set directly from input.

The following primitives are parsed by fmt.Sscanf using the %v directive:

  • bool
  • int, int8, int16, int32, int64
  • uint8, uint16, uint32, uint64
  • float32, float64

Additionally, time.Duration fields are automatically parsed using time.ParseDuration.

All other types are parsed using the first method below that is implemented with the type itself or a pointer to the type as the receiver:

  • Set(s string) error (similar to flag.Value)
  • UnmarshalText(text []byte) error (encoding.TextUnmarshaler)
  • UnmarshalBinary(data []byte) error (encoding.BinaryUnmarshaler)

Many standard library types already implement one of these methods. For example, time.Time implements encoding.TextUnmarshaler for parsing RFC 3339 timestamps.

Custom types can be used so long as they implement one of the above methods. Here is an example which parses a string slice from a comma-delimited flag value string:

type App struct {
	Foos Foos
}

type Foos []string

func (foos *Foos) UnmarshalText(text []byte) error {
	s := string(text)
	*foos = append(*foos, strings.Split(s, ",")...)
	return nil
}

Default values for custom types are represented in help text using fmt.Sprintf("%v", value). This can be overridden by defining a String() string method with the type itself or a pointer to the type as the receiver.

Contexts and Signal Handling

Here is an example of a "sleep" program which sleeps for the specified duration, but can be cancelled by a SIGINT or SIGTERM:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/isobit/cli"
)

type SleepCommand struct {
	Duration time.Duration `cli:"short=d,required"`
}

func (cmd *Sleep) Run(ctx context.Context) error {
	fmt.Printf("sleeping for %s\n", cmd.Duration)
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-time.After(cmd.Duration):
		fmt.Println("done")
	}
	return nil
}

func main() {
	cli.New("sleep", &SleepCommand{}).
		Parse().
		RunFatalWithSigCancel()
}

Command Line Syntax

Arguments are parsed using a more GNU-like modification of the algorithm used in the standard flag package; notably, single dashes are treated differently from double dashes. The following forms are permitted:

--flag    // long boolean flag (no argument)
-f        // single short flag (no argument)

--flag=x  // long flag with argument
--flag x  // long flag with argument
-f=x      // single short flag with argument
-f x      // single short flag with argument

-abc      // multiple short boolean flags (a, b, and c)

-abf x    // multiple short boolean flags (a, b) combined 
          // with single short flag with argument (f)

-abf=x    // multiple short boolean flags (a, b) combined 
          // with single short flag with argument (f)

Flag parsing for each command stops just before the first non-flag argument (- is a non-flag argument) or after the terminator --. If the command has a field with the cli:"args" tag, its value is set to a string slice containing the remaining arguments. Otherwise, if the first non-flag argument is a subcommand, the remaining arguments are further parsed by that subcommand, recursively.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrHelp = fmt.Errorf("cli: help requested")

Functions

This section is empty.

Types

type Beforer

type Beforer interface {
	Before() error
}

type CLI added in v0.7.0

type CLI struct {
	// HelpWriter is used to print help output when calling ParseResult.Run
	// (and other similar methods).
	HelpWriter io.Writer

	// ErrWriter is used to print errors when calling ParseResult.Run (and
	// other similar methods).
	ErrWriter io.Writer

	// LookupEnv is called during parsing for any fields which define an env
	// var key, but are not set by argument.
	LookupEnv LookupEnvFunc

	// Setter can be used to define custom setters for arbitrary field types,
	// or to override the default field setters.
	//
	// Here is an example which uses a custom layout for parsing any time.Time
	// fields:
	//
	//  type customTime time.Time
	//  func (t *customTime) Set(s string) error {
	//  	parsed, err := time.Parse("2006-01-02 15:04", s)
	//  	if err != nil {
	//  		return err
	//  	}
	//  	*ts.value = parsed
	//  	return nil
	//  }
	//  cli := cli.NewCLI()
	//  cli.Setter = func(i interface{}) cli.Setter {
	//  	switch v := i.(type) {
	//  	case *time.Time:
	//  		return (*customTime)(v)
	//  	default:
	//  		// return nil to fall back on default behavior
	//  		return nil
	//  	}
	//  }
	Setter SetterFunc
}

CLI defines functionality which is global to all commands which it constructs. The top-level New and Build methods use a CLI with good defaults for most cases, but custom CLI structs can be used to modify behavior.

func NewCLI added in v0.9.0

func NewCLI() *CLI

func (*CLI) Build added in v0.7.0

func (cli *CLI) Build(name string, config interface{}, opts ...CommandOption) (*Command, error)

func (*CLI) New added in v0.7.0

func (cli *CLI) New(name string, config interface{}, opts ...CommandOption) *Command

type Command

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

func Build

func Build(name string, config interface{}, opts ...CommandOption) (*Command, error)

Build is like New, but it returns any errors instead of calling panic, at the expense of being harder to chain.

func New

func New(name string, config interface{}, opts ...CommandOption) *Command

New creates a new Command with the provided name and config. The config must be a pointer to a configuration struct. Default values can be specified by simply setting them on the config struct.

Command options (e.g. help text and subcommands) can be passed as additonal CommandOption arguments, or set using chained method calls. Note that *Command implements CommandOption, so subcommands can be registered by simply passing them as arugments.

New returns an Command pointer for further method chaining. If an error is encountered while building the options, such as a struct field having an unsupported type, New will panic. If you would like to have errors returned for handling, use Build instead.

func (*Command) AddCommand

func (cmd *Command) AddCommand(subCmd *Command) *Command

AddCommand registers another Command instance as a subcommand of this Command instance.

func (*Command) Apply added in v0.8.0

func (cmd *Command) Apply(parent *Command)

func (*Command) HelpString

func (cmd *Command) HelpString() string

func (*Command) Parse

func (cmd *Command) Parse() ParseResult

Parse is a convenience method for calling ParseArgs(os.Args[1:])

func (*Command) ParseArgs

func (cmd *Command) ParseArgs(args []string) ParseResult

ParseArgs parses the passed-in args slice, along with environment variables, into the config fields, and returns a ParseResult which can be used for further method chaining.

If there are args remaining after parsing this Command's fields, subcommands will be recursively parsed until a concrete result is returned

If a Before method is implemented on the config, this method will call it before calling Run or recursing into any subcommand parsing.

func (*Command) SetDescription added in v0.8.0

func (cmd *Command) SetDescription(description string) *Command

func (*Command) SetHelp

func (cmd *Command) SetHelp(help string) *Command

func (*Command) WriteHelp

func (cmd *Command) WriteHelp(w io.Writer)

type CommandOption added in v0.8.0

type CommandOption interface {
	Apply(cmd *Command)
}

func WithDescription added in v0.8.0

func WithDescription(description string) CommandOption

func WithHelp added in v0.8.0

func WithHelp(help string) CommandOption

type ContextRunner added in v0.7.0

type ContextRunner interface {
	Run(context.Context) error
}

type ExitCoder added in v0.7.0

type ExitCoder interface {
	ExitCode() int
}

type LookupEnvFunc added in v0.6.0

type LookupEnvFunc func(key string) (val string, ok bool, err error)

type ParseResult added in v0.7.0

type ParseResult struct {
	Err     error
	Command *Command
	// contains filtered or unexported fields
}

ParseResult contains information about the results of command argument parsing.

func (ParseResult) Run added in v0.7.0

func (r ParseResult) Run() error

Run calls the Run method of the Command config for the parsed command or, if an error occurred during parsing, prints the help text and returns that error instead. If help was requested, the error will flag.ErrHelp. If the underlying command Run method accepts a context, context.Background() will be passed.

func (ParseResult) RunFatal added in v0.7.0

func (r ParseResult) RunFatal()

RunFatal is like Run, except it automatically handles printing out any errors returned by the Run method of the underlying Command config, and exits with an appropriate status code.

If no error occurs, the exit code will be 0. If an error is returned and it implements the ExitCoder interface, the result of ExitCode() will be used as the exit code. If an error is returned that does not implement ExitCoder, the exit code will be 1.

func (ParseResult) RunFatalWithContext added in v0.7.0

func (r ParseResult) RunFatalWithContext(ctx context.Context)

RunFatalWithContext is like RunFatal, but it accepts an explicit context which will be passed to the command's Run method if it accepts one.

func (ParseResult) RunFatalWithSigCancel added in v0.7.0

func (r ParseResult) RunFatalWithSigCancel()

RunFatalWithSigCancel is like RunFatal, but it automatically registers a signal handler for SIGINT and SIGTERM that will cancel the context that is passed to the command's Run method, if it accepts one.

func (ParseResult) RunWithContext added in v0.7.0

func (r ParseResult) RunWithContext(ctx context.Context) error

RunWithContext is like Run, but it accepts an explicit context which will be passed to the command's Run method, if it accepts one.

func (ParseResult) RunWithSigCancel added in v0.7.0

func (r ParseResult) RunWithSigCancel() error

RunWithSigCancel is like Run, but it automatically registers a signal handler for SIGINT and SIGTERM that will cancel the context that is passed to the command's Run method, if it accepts one.

type Runner

type Runner interface {
	Run() error
}

type Setter added in v0.6.2

type Setter interface {
	Set(s string) error
}

type SetterFunc added in v0.6.2

type SetterFunc func(interface{}) Setter

type Setuper added in v0.6.0

type Setuper interface {
	SetupCommand(cmd *Command)
}

type UsageErrorWrapper added in v0.9.0

type UsageErrorWrapper struct {
	Err error
}

UsageErrorWrapper wraps another error to indicate that the error was due to incorrect usage. When this error is handled, help text should be printed in addition to the error message.

func UsageError added in v0.9.0

func UsageError(err error) UsageErrorWrapper

UsageError wraps the given error as a UsageErrorWrapper.

func UsageErrorf added in v0.9.0

func UsageErrorf(format string, v ...interface{}) UsageErrorWrapper

UsageErrorf is a convenience method for wrapping the result of fmt.Errorf as a UsageErrorWrapper.

func (UsageErrorWrapper) Error added in v0.9.0

func (w UsageErrorWrapper) Error() string

func (UsageErrorWrapper) Unwrap added in v0.9.0

func (w UsageErrorWrapper) Unwrap() error

Jump to

Keyboard shortcuts

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