cli

package module
v0.0.0-...-c354aca Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2023 License: MIT Imports: 15 Imported by: 3

README

Documentation Go workflow CircleCI codecov Go Report Card GitHub tag (latest SemVer)

cli

This is a lightweight yet extensible library for creating convenient command-line applications in Go. It follows the general principle of being dead simple, efficient, and highly customizable to fit your needs.

Many cool features are not supported by the library, but they can be easily implemented if needed. For example GNU grouping of oneletter options are not supported, but if you want, just set your custom Command.ParseFlag handler.

Usage

Hello World
func main() {
    app := &cli.Command{
        Name: "hello",
        Action: hello, // this is how the library connects to your business logic
    }

    cli.RunAndExit(app, os.Args, nil)
}

func hello(c *cli.Command) error {
    fmt.Println("Hello world")

    return nil
}
Flags

Flags are much similar to commands, they have Action property too which does all the job. The default action is to parse the value and assign it to (&Flag{}).Value.

If flag is mentioned multiple times Action will be called for each occurance. Flag value can be taken from env variables, which uses the same approach with the same Action. But it's called before that command arguments are parsed.

func main() {
    app := &cli.Command{
        Name: "hello",
        Action: hello,
        EnvPrefix: "HELLO_",
        Flags: []*cli.Flag{
            // much less boilerplate than &cli.StringFlag{Name: ...}
            // this was one of the main reasons I started this lib
            cli.NewFlag("name", "world", "name to say hello to"),

            // supported flag value types are string, some ints, time.Duration
            cli.NewFlag("full-flag-name,flag,f", 3, "alias names are added with comma"),

            // action can be passed instead of value
            cli.NewFlag("json,j", jsonFlagAction, "json encoded value"),

            // stdlib flag.Value is also supported

            // must be added explicitly
            cli.HelpFlag,
            cli.EnvFlag,
            cli.FlagfileFlag, // configs without configs
        },
    }

    cli.RunAndExit(app, os.Args, os.Environ()) // env as well as args are passed here
}

func hello(c *cli.Command) error {
    fmt.Println("Hello", c.String("name"))

    return nil
}

// f is a Flag we are working on now.
// arg is an arg that triggered the flag.
// args is the rest of arguments.
// Command can be received as f.CurrentCommand.(*cli.Command).
// Conciseness is sacrificed here in favor of extracting flags into its own package.
// Rest of unparsed args must be returned back.
func jsonFlagAction(f *cli.Flag, arg string, args []string) ([]string, error) {
    key, val, args, err := flag.ParseArg(arg, args, true, false)
                           // last two booleans:
                           // eat next arg from args if value is not present in arg
                           // allow no value at all. Like bool flags.
    if err != nil {
        return args, err
    }

    // key
    // We may slightly change parsing behaviour depending on a flag name was used.

    // default value if any now in f.Value

    err = json.Unmarshal([]byte(val), &f.Value)
    if err != nil {
        return nil, errors.Wrap(err, "parse value as json")
    }

    // You can actually return more args than if was.
    // --flagfile is working just like that.
    return args, nil
}

func hello(c *cli.Command) error {
    fmt.Println("Hello object", c.Flag("flag").Value)

    return nil
}
Arguments

Command do not accept arguments by default. This saved me multiple times from doing something I wasn't going to ask for. For example

command --flag= value           # flag="", args=["value"]
VAR="a b" command --flag=$VAR   # flag=a, args=["b"]
command -f --another value      # if f is a string flag it would be f="--another", args=["value"]

You'll get error in all the previous cases if you wasn't expecting arguments.

So if you need arguments, do it explicit.

func main() {
    app := &cli.Command{
        Name: "hello",
        Args: cli.Args{}, // non-nil value must be here
        Action: hello,
    }

    cli.RunAndExit(app, os.Args, nil)
}

func hello(c *cli.Command) error {
    fmt.Println("Hello ", c.Args.Get(0)) // Get is, by the way, a lazy c.Args[0] if you don't want to check for len(c.Args)

    return nil
}
Subcommands
func main() {
    app := &cli.Command{
        Name:        "todo",
        Description: "todo list. add, list and remove jobs to do",
        // Command may not have action on their own. Help would be printed by default.
        Flags: []*cli.Flag{
            cli.NewFlag("storage", "todo.json", "file to store tasks"), // global flag
            cli.HelpFlag,
        },
        Commands: []*cli.Command{{
            Name: "add",
            Action: add,
            Args: cli.Args{}, // we'll take job description from args
        }, {
            Name: "remove,rm",
            Action: remove,
            Flags: []*cli.Flag{
                cli.NewFlag("job", -1, "job id to remove"), // local flag
            },
        }},
    }

    cli.RunAndExit(app, os.Args, nil)
}
Flag values from the environment
func main() {
    app := &cli.Command{
        Name: "hello",
        Action: hello,
        EnvPrefix: "HELLO_",
        Flags: []*cli.Flag{
            cli.NewFlag("flag,f", "", "flag description"),
        },
        Commands: []*cli.Command{{
            Name: "subcommand",
            // EnvPrefix could be overwritten here for nested flags
            // The parent is used if empty
            Action: subcommand,
            Flags: []*cli.Flag{
                cli.NewFlag("another,a", 1, "another description"),
            },
        }},
    }

    cli.RunAndExit(app, os.Args, os.Environ()) // ENV VARIABLE COME FROM HERE, not from os package. Explicitness.
}

Usage

HELLO_FLAG=value hello
HELLO_FLAG=v2 HELLO_ANOTHER=4 hello subcommand
Order of precedence
  • --flag=first
  • ENV_FLAG=second
  • cli.NewFlag("flag", "the_last", "help")

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoSuchCommand  = stderrors.New("no such command")
	ErrNoArgsExpected = stderrors.New("no args expected")
)
View Source
var (
	ErrExit       = stderrors.New("exit") // to stop command execution from flag or before handler
	ErrNoSuchFlag = stderrors.New("no such flag")
)
View Source
var CompleteCmd = &Command{
	Name:        "_complete",
	Usage:       "[bash|zsh]",
	Description: "print shell completion script",
	Action:      completeAuto,
	Hidden:      true,
	Commands: []*Command{{
		Name:   "bash",
		Action: completeBash,
	}, {
		Name:   "zsh",
		Action: completeZsh,
	}},
}
View Source
var DefaultFlags = []*Flag{
	FlagfileFlag,
	EnvfileFlag,
	HelpFlag,
}
View Source
var EnvCmd = &Command{
	Name:   "env",
	Action: envAction,
}
View Source
var EnvfileFlag = &Flag{
	Name:        "envfile",
	Description: "load env variables from file",
	Action:      envfile,
}
View Source
var ErrCouldNotDetermineShell = errors.New("couldn't determine the shell")
View Source
var FlagfileFlag = &Flag{
	Name:        "flagfile,ff",
	Description: "load flags from file",
	Action:      flagfile,
}

FlagfileFlag replaces this flag occurrence with the given file content split on spaces. # comments are also supported.

View Source
var HelpFlag = &Flag{
	Name:        "help,h",
	Usage:       "=[hidden]",
	Description: "print command help end exit",
	Action:      defaultHelp,
}

Functions

func DefaultComplete

func DefaultComplete(c *Command) (err error)

func DefaultParseEnv

func DefaultParseEnv(c *Command, env []string) (rest []string, err error)

func DefaultParseFlag

func DefaultParseFlag(c *Command, arg string, args []string) (nextArgs []string, err error)

func FullName

func FullName(c *Command) (r []string)

func GetEnvPrefix

func GetEnvPrefix(c *Command) string

func MainName

func MainName(n string) string

func ParseEnv

func ParseEnv(c *Command, env []string) ([]string, error)

func ParseFlag

func ParseFlag(c *Command, arg string, more []string) ([]string, error)

func Run

func Run(c *Command, args, env []string) (err error)

func RunAndExit

func RunAndExit(c *Command, args, env []string)

Types

type Action

type Action func(c *Command) error

func Chain

func Chain(a ...Action) Action

type Args

type Args []string

func (Args) First

func (a Args) First() string

func (Args) Get

func (a Args) Get(i int) string

func (Args) Last

func (a Args) Last() string

func (Args) Len

func (a Args) Len() int

func (Args) Pop

func (a Args) Pop() (string, Args)

func (Args) String

func (a Args) String() string

func (Args) Tail

func (a Args) Tail() Args

type Command

type Command struct {
	Parent *Command

	OSArgs []string
	OSEnv  []string

	Arg0 string   // command name
	Args Args     // must be initialized to cli.Args{} if arguments expected
	Env  []string // env vars not used for local flags

	Chosen *Command // chosen command

	Name        string // comma separated list of aliases
	Group       string
	Usage       string // [flags] {args...}
	Description string // short textual description of the command
	Help        string // full description

	Before Action
	After  Action
	Action Action

	Flags    []*Flag
	Commands []*Command

	// Hide from help.
	Hidden bool

	// EnvPrefix used to capture flag values from env vars.
	// No capturing is done if empty.
	// Args have precedence over env vars.
	// Env vars have precedence over default values.
	// Inherited by subcommands.
	EnvPrefix string

	// ParseEnv and ParseFlag override default behaviour.
	// Both are inherited by subcommands.
	ParseEnv  func(c *Command, env []string) ([]string, error)
	ParseFlag func(c *Command, arg string, args []string) ([]string, error)

	Stdout io.Writer // set to os.Stdout if nil
	Stderr io.Writer // the same as Stdout
}

func Version

func Version(ver, commit, date string) *Command

func (*Command) Bool

func (c *Command) Bool(n string) bool

func (*Command) Command

func (c *Command) Command(n string) *Command

func (*Command) Duration

func (c *Command) Duration(n string) time.Duration

func (*Command) Flag

func (c *Command) Flag(n string) *Flag

func (*Command) Float32

func (c *Command) Float32(n string) float32

func (*Command) Float64

func (c *Command) Float64(n string) float64

func (*Command) Getenv

func (c *Command) Getenv(key string) (val string)

func (*Command) Int

func (c *Command) Int(n string) int

func (*Command) Int64

func (c *Command) Int64(n string) int64

func (*Command) LookupEnv

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

func (*Command) MainName

func (c *Command) MainName() string

func (*Command) String

func (c *Command) String(n string) string

func (*Command) Uint

func (c *Command) Uint(n string) uint

func (*Command) Uint64

func (c *Command) Uint64(n string) uint64

type Flag

type Flag = flag.Flag

func NewFlag

func NewFlag(name string, val interface{}, help string, opts ...flag.Option) (f *Flag)

type FlagAction

type FlagAction = flag.Action

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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