mcli

package module
v0.9.5 Latest Latest
Warning

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

Go to latest
Published: Apr 6, 2024 License: MIT Imports: 18 Imported by: 6

README

mcli

GoDoc Go Report Card Coverage Issues GitHub release MIT License

mcli is a minimal but powerful cli library for Go. m stands for minimal and magic.

It is extremely easy to use, it makes you love writing cli programs in Go.

Disclaimer: the original idea is inspired by shafreeck/cortana, which is licensed under the Apache License 2.0.

Features

  • Easy to use, dead simple yet very powerful API to define commands, flags and arguments.
  • Add arbitrary nested sub-command with single line code.
  • Group subcommands into different categories in help.
  • Define command flags and arguments inside the command processor using struct tag.
  • Define global flags apply to all commands, or share common flags between a group of commands.
  • Read environment variables for flags and arguments.
  • Set default value for flags and arguments.
  • Work with time.Duration, slice, map out of box.
  • Mark commands, flags as hidden, hidden commands and flags won't be showed in help, except that when a special flag --mcli-show-hidden is provided.
  • Mark flags, arguments as required, report error when a required flag is not given.
  • Mark flags as deprecated.
  • Automatic suggestions like git.
  • Automatic help generation for commands, flags and arguments.
  • Automatic help flag recognition of -h, --help, etc.
  • Automatic shell completion, it supports bash, zsh, fish, powershell for now.
  • Compatible with the standard library's flag.FlagSet.
  • Optional posix-style single token multiple options command line parsing.
  • Alias command, so you can reorganize commands without breaking them.
  • Flexibility to define your own usage messages.
  • Minimal dependency.
  • Makes you love writing cli programs in Go.

Usage

Use in main function:

func main() {
    var args struct {
        Name string `cli:"-n, --name, Who do you want to say to" default:"tom"`

        // This argument is required.
        Text string `cli:"#R, text, The 'message' you want to send"`

        // This argument reads environment variable and requires the variable must exist,
        // it doesn't accept input from command line.
        APIAccessKey string `cli:"#ER, The access key to your service provider" env:"MY_API_ACCESS_KEY"`
    }
    mcli.Parse(&args)
    fmt.Printf("Say to %s: %s\n", args.Name, args.Text)
}
$ go run say.go -h
Usage:
  say [flags] <text>

Flags:
  -n, --name <string>    Who do you want to say to
                         [default: "tom"]

Arguments:
  text <message> [REQUIRED]    The message you want to send

Environment Variables:
  - MY_API_ACCESS_KEY <string> [REQUIRED]
    The access key to your service provider

$ MY_API_ACCESS_KEY=xxxx go run say.go hello
Say to tom: hello

Use sub-commands:

func main() {
    mcli.Add("cmd1", runCmd1, "An awesome command cmd1")

    mcli.AddGroup("cmd2", "This is a command group called cmd2")
    mcli.Add("cmd2 sub1", runCmd2Sub1, "Do something with cmd2 sub1")
    mcli.Add("cmd2 sub2", runCmd2Sub2, "Brief description about cmd2 sub2")

    // A sub-command can also be added without registering the group.
    mcli.Add("group3 sub1 subsub1", runGroup3Sub1Subsub1, "Blah blah Blah")

    // This is a hidden command, it won't be showed in help,
    // except that when flag "--mcli-show-hidden" is given.
    mcli.AddHidden("secret-cmd", secretCmd, "An secret command won't be showed in help")

    // Enable shell auto-completion, see `program completion -h` for help.
    mcli.AddCompletion()

    mcli.Run()
}

func runCmd1() {
    var args struct {
        Branch    string `cli:"-b, --branch, Select another branch by passing in the branch name"`
        Commit    bool   `cli:"-c, --commit, Open the last commit"`
        NoBrowser bool   `cli:"-n, --no-browser, Print destination URL instead of opening the browser"`
        Projects  bool   `cli:"-p, --projects, Open repository projects"`
        Repo      string `cli:"-R, --repo, Select another repository using the '[HOST/]OWNER/REPO' format"`
        Settings  bool   `cli:"-s, --settings, Open repository settings"`
        Wiki      bool   `cli:"-w, --wiki, Open repository wiki"`

        Location  string `cli:"location, A browser location can be specified using arguments in the following format:\n- by number for issue or pull request, e.g. \"123\"; or\n- by path for opening folders and files, e.g. \"cmd/gh/main.go\""`
    }
    mcli.Parse(&args)

    // Do something
}

type Cmd2CommonArgs struct {
    Repo string `cli:"-R, --repo, Select another repository using the '[HOST/]OWNER/REPO' format"`
}

func runCmd2Sub1() {
    // Note that the flag/argument description can be seperated either
    // by a comma or spaces, and can be mixed.
    var args struct {
        Body     string `cli:"-b, --body        Supply a body. Will prompt for one otherwise."`
        BodyFile string `cli:"-F, --body-file   Read body text from 'file' (use \"-\" to read from standard input)"`
        Editor   bool   `cli:"-e, --editor,     Add body using editor"`
        Web      bool   `cli:"-w, --web,        Add body in browser"`

        // Can embed other structs.
        Cmd2CommonArgs
    }
    mcli.Parse(&args)

    // Do something
}

Also, there are some sophisticated examples:

  • github-cli mimics Github's cli command gh
  • lego mimics Lego's command lego

API

Use the default App:

  • SetOptions updates options of the default application.
  • SetGlobalFlags sets global flags, global flags are available to all commands.
  • Add adds a command.
  • AddRoot adds a root command. A root command is executed when no sub command is specified.
  • AddAlias adds an alias name for a command.
  • AddHidden adds a hidden command.
  • AddGroup adds a group explicitly. A group is a common prefix for some commands. It's not required to add group before adding sub commands, but user can use this function to add a description to a group, which will be showed in help.
  • AddHelp enables the "help" command.
  • AddCompletion enables the "completion" command to generate autocomplete scripts.
  • Parse parses the command line for flags and arguments.
  • Run runs the program, it will parse the command line, search for a registered command and run it.
  • PrintHelp prints usage doc of the current command to stderr.

Create a new App instance:

  • NewApp creates a new cli applcation instance.
Custom options

App:

  • App.Options specifies optional options for an application.

CmdOpt:

  • WithCategory groups commands into different categories in help.
  • WithLongDesc specifies a long description of a command, which will be showed in the command's help.
  • EnableFlagCompletion enables flag completion for a command.

ParseOpt:

  • WithArgs tells Parse to parse from the given args, instead of parsing from the command line arguments.
  • WithErrorHandling tells Parse to use the given ErrorHandling. By default, the program exits when an error happens.
  • WithName specifies the command name to use when printing usage doc.
  • DisableGlobalFlags tells Parse to don't parse and print global flags in help.
  • ReplaceUsage tells Parse to use a custom usage function instead of the default.
  • WithExamples specifies examples for a command. Examples will be showed after flags in the help.
  • WithFooter adds a footer message after the default help, this option overrides the App's setting Options.HelpFooter for this parsing call.
  • WithArgCompFuncs specifies functions to suggest flag values and positional arguments programmatically.

Tag syntax

Struct tag is a powerful feature in Go, mcli uses struct tag to define flags and arguments.

  • tag cli defines the name and description for flags and arguments
  • tag env optionally tells Parse to lookup environment variables when user doesn't provide a value on the command line
  • tag default optionally provides a default value to a flag or argument, which will be used when the value is not available from both command line and env

The syntax is

/* cli tag, only Name is required.
 * Short name and long name are both optional, but at least one must be given.
 * See below for details about modifiers.
 * e.g.
 * - `cli:"-c, Open the last commit"`
 * - `cli:"#R, -b, --branch, Select another branch by passing in the branch name"`
 * - `cli:"--an-obvious-flag-dont-need-description"`
 * - `cli:"#ER, AWS Secret Access Key" env:"AWS_SECRET_ACCESS_KEY"`
 */
CliTag       <-  ( Modifiers ',' Space? )? Name ( ( ',' | Space ) Description )?
Modifiers    <-  '#' [DHRE]+
Name         <-  ( ShortName LongName? ) | LongName
Description  <-  ( ![\r\n] . )*

/* env tag, optional.
 * Multiple environment names can be specified, the first non-empty value takes effect.
 * e.g.
 * - `env:"SOME_ENV"`
 * - `env:"ANOTHER_ENV_1, ANOTHER_ENV_2"`
 */
EnvTag  <-  ( EnvName ',' Space? )* EnvName

/* default value tag, optional.
 * e.g.
 * - `default:"1.5s"` // duration
 * - `default:"true"` // bool
 */
DefaultValueTag  <-  ( ![\r\n] . )*

Modifiers

Modifier represents an option to a flag, it sets the flag to be deprecated, hidden, or required. In a cli tag, modifiers appears as the first segment, starting with a # character.

Fow now the following modifiers are available:

  • D - marks a flag or argument as deprecated, "DEPRECATED" will be showed in help.
  • R - marks a flag or argument as required, "REQUIRED" will be showed in help.
  • H - marks a flag as hidden, see below for more about hidden flags.
  • E - marks an argument read from environment variables, but not command line, environment variables will be showed in a separate section in help.

Hidden flags won't be showed in help, except that when a special flag "--mcli-show-hidden" is provided.

Modifier H shall not be used for an argument, else it panics. An argument must be showed in help to tell user how to use the program correctly.

Modifier E is useful when you want to read an environment variable, but don't want user to provide from command line (e.g. password or other secrets). Using together with R also ensures that the env variable must exist.

Some modifiers cannot be used together, else it panics, e.g.

  • H & R - a required flag must appear in help to tell user to set it.
  • D & R - a required flag must not be deprecated, it does not make sense, but makes user confused.

Compatibility with package flag

Parse returns a *flag.FlagSet if success, all defined flags are available with the flag set, including both short and long names.

Note that the package flag requires command line flags must present before arguments, this package does not have this requirement. Positional arguments can present either before flags or after flags, even both before and after flags, in which case, the args will be reordered and all arguments can be accessed by calling flagSet.Args() and flagSet.Arg(i).

If there is slice or map arguments, it will match all following arguments.

Shell completion

mcli supports auto shell completion for bash, zsh, fish, and powershell. Use AddCompletion to enable the feature, run program help completion [bash|zsh|fish|powershell] for usage guide.

Also check AddCompletion, EnableFlagCompletion, and Options.EnableFlagCompletionForAllCommands for detail docs about command flag completion.

User can use WithArgCompFuncs to specify functions to suggest flag values and positional arguments programmatically, already provided flags and arguments can be accessed in the functions.

Changelog

See CHANGELOG for detailed change history.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Add

func Add(name string, cmd any, description string, opts ...CmdOpt)

Add adds a command.

Param cmd must be type of one of the following:

  • `func()`, user should call `mcli.Parse` inside the function
  • `func(ctx *mcli.Context)`, user should call `ctx.Parse` inside the function
  • a Command created by NewCommand

func AddAlias added in v0.4.0

func AddAlias(aliasName, target string, opts ...CmdOpt)

AddAlias adds an alias name for a command.

func AddCompletion added in v0.8.0

func AddCompletion()

AddCompletion enables the "completion" command to generate auto-completion script. If you want a different name other than "completion", use AddCompletionWithName.

Note: by default this command only enables command completion, to enable flag completion, user should either set `App.Options.EnableFlagCompletionForAllCommands` to enable flag completion for the whole application, or provide command option `EnableFlagCompletion` when adding a command to enable for a specific command.

func AddCompletionWithName added in v0.8.0

func AddCompletionWithName(name string)

AddCompletionWithName enables the completion command with custom command name.

Note: by default this command only enables command completion, to enable flag completion, user should either set `App.Options.EnableFlagCompletionForAllCommands` to enable flag completion for the whole application, or provide command option `EnableFlagCompletion` when adding a command to enable for a specific command.

func AddGroup

func AddGroup(name string, description string, opts ...CmdOpt)

AddGroup adds a group explicitly. A group is a common prefix for some commands. It's not required to add group before adding sub commands, but user can use this function to add a description to a group, which will be showed in help.

func AddHelp

func AddHelp()

AddHelp enables the "help" command to print help about any command.

func AddHidden

func AddHidden(name string, cmd any, description string, opts ...CmdOpt)

AddHidden adds a hidden command. f must be a function of signature `func()` or `func(*Context)`, else it panics.

A hidden command won't be showed in help, except that when a special flag "--mcli-show-hidden" is provided.

See Add for valid types of cmd.

func AddRoot added in v0.7.0

func AddRoot(cmd any, opts ...CmdOpt)

AddRoot adds a root command processor. When no sub command specified, a root command will be executed.

See Add for valid types of cmd.

func MustParse added in v0.9.4

func MustParse(args any, opts ...ParseOpt) *flag.FlagSet

MustParse is a helper that wraps Parse and panics if Parse returns an error.

func Parse

func Parse(args any, opts ...ParseOpt) (fs *flag.FlagSet, err error)

Parse parses the command line for flags and arguments. `args` must be a pointer to a struct, else it panics.

By default, it prints help and exits the program if an error occurs when parsing, instead of returning the error, which is the same behavior with package "flag". Generally, user can safely ignore the return value of this function, except that an option `WithErrorHandling(flag.ContinueOnError)` is explicitly passed to it if you want to inspect the error.

See Context.Parse if you use an application created by NewApp instead of the default application.

func PrintHelp

func PrintHelp()

PrintHelp prints usage doc of the current command to stderr.

func Run

func Run(args ...string)

Run runs the program, it parses the command line and searches for a registered command, it runs the command if a command is found, else it will report an error and exit the program.

Optionally you may specify args to parse, by default it parses the command line arguments os.Args[1:].

func SetGlobalFlags added in v0.2.0

func SetGlobalFlags(v any)

SetGlobalFlags sets global flags, global flags are available to all commands. DisableGlobalFlags may be used to disable global flags for a specific command when calling Parse.

func SetOptions added in v0.4.0

func SetOptions(options Options)

SetOptions updates options of the default application.

Types

type App added in v0.3.0

type App struct {
	// Description optionally provides a description of the program.
	Description string

	// Options specifies optional options to custom the behavior
	// of an App.
	Options
	// contains filtered or unexported fields
}

App holds the state of a cli application.

func NewApp added in v0.3.0

func NewApp() *App

NewApp creates a new cli application instance.

Typically, there is no need to manually create an application, using the package-level functions with the default application is preferred.

func (*App) Add added in v0.3.0

func (p *App) Add(name string, cmd any, description string, opts ...CmdOpt)

Add adds a command.

Param cmd must be type of one of the following:

  • `func()`, user should call `mcli.Parse` inside the function
  • `func(ctx *mcli.Context)`, user should call `ctx.Parse` inside the function
  • a Command created by NewCommand

func (*App) AddAlias added in v0.4.0

func (p *App) AddAlias(aliasName, target string, opts ...CmdOpt)

AddAlias adds an alias name for a command.

func (*App) AddCompletion added in v0.8.0

func (p *App) AddCompletion()

AddCompletion enables the "completion" command to generate auto-completion script. If you want a different name other than "completion", use AddCompletionWithName.

Note: by default this command only enables command completion, to enable flag completion, user should either set `App.Options.EnableFlagCompletionForAllCommands` to enable flag completion for the whole application, or provide command option `EnableFlagCompletion` when adding a command to enable for a specific command.

func (*App) AddCompletionWithName added in v0.8.0

func (p *App) AddCompletionWithName(name string)

AddCompletionWithName enables the completion command with custom command name.

Note: by default this command only enables command completion, to enable flag completion, user should either set `App.Options.EnableFlagCompletionForAllCommands` to enable flag completion for the whole application, or provide command option `EnableFlagCompletion` when adding a command to enable for a specific command.

func (*App) AddGroup added in v0.3.0

func (p *App) AddGroup(name string, description string, opts ...CmdOpt)

AddGroup adds a group explicitly. A group is a common prefix for some commands. It's not required to add group before adding sub commands, but user can use this function to add a description to a group, which will be showed in help.

func (*App) AddHelp added in v0.3.0

func (p *App) AddHelp()

AddHelp enables the "help" command to print help about any command.

func (*App) AddHidden added in v0.3.0

func (p *App) AddHidden(name string, cmd any, description string, opts ...CmdOpt)

AddHidden adds a hidden command.

A hidden command won't be showed in help, except that when a special flag "--mcli-show-hidden" is provided.

See App.Add for valid types of cmd.

func (*App) AddRoot added in v0.7.0

func (p *App) AddRoot(cmd any, opts ...CmdOpt)

AddRoot adds a root command. When no sub command specified, a root command will be executed.

See App.Add for valid types of cmd.

func (*App) Run added in v0.3.0

func (p *App) Run(args ...string)

Run is the entry point to an application, it parses the command line and searches for a registered command, it runs the command if a command is found, else it will report an error and exit the program.

Optionally you may specify args to parse, by default it parses the command line arguments os.Args[1:].

func (*App) SetGlobalFlags added in v0.3.0

func (p *App) SetGlobalFlags(v any)

SetGlobalFlags sets global flags, global flags are available to all commands. DisableGlobalFlags may be used to disable global flags for a specific command when calling Parse.

type ArgCompletionContext added in v0.9.0

type ArgCompletionContext interface {
	context.Context

	GlobalFlags() any
	CommandArgs() any
	FlagSet() *flag.FlagSet
	ArgPrefix() string
}

ArgCompletionContext provides essential information to do suggestion for flag value and positional argument completion.

type ArgCompletionFunc added in v0.9.0

type ArgCompletionFunc func(ctx ArgCompletionContext) []CompletionItem

ArgCompletionFunc is a function to do completion for flag value or positional argument.

type CmdOpt added in v0.8.1

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

CmdOpt specifies options to customize the behavior of a Command.

func EnableFlagCompletion added in v0.8.3

func EnableFlagCompletion() CmdOpt

EnableFlagCompletion enables flag completion for a command. By default, flag completion is disabled to avoid unexpectedly running the user command when doing flag completion, in case that the user does not call `Parse` in the command.

func WithCategory added in v0.9.0

func WithCategory(category string) CmdOpt

WithCategory groups commands into different categories in help.

func WithLongDesc added in v0.8.1

func WithLongDesc(long string) CmdOpt

WithLongDesc specifies a long description of a command, which will be showed in the command's help.

type Command

type Command struct {
	Name        string
	AliasOf     string
	Description string
	Hidden      bool
	// contains filtered or unexported fields
}

Command holds the information of a command.

func NewCommand added in v0.8.3

func NewCommand[T any](f func(ctx *Context, args *T), opts ...ParseOpt) *Command

NewCommand accepts a typed function and returns a Command. The type parameter T must be a struct, else it panics. When the command is matched, mcli will parse "args" and pass it to f, thus user must not call "Parse" again in f, else it panics. If option `WithErrorHandling(flag.ContinueOnError)` is used, user can use Context.ArgsError to check error that occurred during parsing flags and arguments. In case you want to get the parsed flag.FlagSet, check Context.FlagSet.

type CompletionItem added in v0.9.0

type CompletionItem struct {
	Value       string
	Description string
}

type Context added in v0.5.0

type Context struct {
	context.Context
	Command *Command
	// contains filtered or unexported fields
}

Context holds context-specific information, which is passed to a Command when executing it. Context embeds context.Context, it can be passed to functions which take context.Context as parameter.

func (*Context) ArgsError added in v0.8.3

func (ctx *Context) ArgsError() error

ArgsError returns the error of parsing arguments. If no error occurs, it returns nil.

func (*Context) FlagSet added in v0.8.3

func (ctx *Context) FlagSet() *flag.FlagSet

FlagSet returns the flag.FlagSet parsed from arguments. This is for compatibility to work with standard library, in most cases, using the strongly-typed parsing result is more convenient.

func (*Context) MustParse added in v0.9.4

func (ctx *Context) MustParse(args any, opts ...ParseOpt) *flag.FlagSet

MustParse is a helper that wraps Parse and panics if Parse returns an error.

func (*Context) Parse added in v0.5.0

func (ctx *Context) Parse(args any, opts ...ParseOpt) (*flag.FlagSet, error)

Parse parses the command line for flags and arguments. `args` must be a pointer to a struct, else it panics.

By default, it prints help and exits the program if an error occurs when parsing, instead of returning the error, which is the same behavior with package "flag". Generally, user can safely ignore the return value of this function, except that an option `WithErrorHandling(flag.ContinueOnError)` is explicitly passed to it if you want to inspect the error.

Note:

  1. if you enable flag completion for a command, you must call this in the command function to make the completion work correctly
  2. if the command is created by NewCommand, mcli automatically calls this to parse flags and arguments, then pass args to user command, you must not call this again, else it panics

func (*Context) PrintHelp added in v0.5.0

func (ctx *Context) PrintHelp()

PrintHelp prints usage doc of the current command to stderr.

type Modifier

type Modifier byte

Modifier represents an option to a flag, it sets the flag to be deprecated, hidden, or required. In a `cli` tag, modifiers appears as the first segment, starting with a `#` character.

Fow now the following modifiers are available:

D - marks a flag or argument as deprecated, "DEPRECATED" will be showed in help.
R - marks a flag or argument as required, "REQUIRED" will be showed in help.
H - marks a flag as hidden, see below for more about hidden flags.
E - marks an argument read from environment variables, but not command line,
    environment variables will be showed in a separate section in help.

Hidden flags won't be showed in help, except that when a special flag "--mcli-show-hidden" is provided.

Modifier `H` shall not be used for an argument, else it panics. An argument must be showed in help to tell user how to use the program correctly.

Modifier `E` is useful when you want to read an environment variable, but don't want user to provide from command line (e.g. password or other secrets). Using together with `R` also ensures that the env variable must exist.

Some modifiers cannot be used together, else it panics, e.g.

H & R - a required flag must appear in help to tell user to set it
D & R - a required flag must not be deprecated, it does not make sense,
        but makes user confused

type Options added in v0.4.0

type Options struct {
	// KeepCommandOrder makes Parse to print commands in the order of
	// adding the commands.
	// By default, it prints commands in lexicographic order.
	KeepCommandOrder bool

	// AllowPosixSTMO enables using the posix-style single token to specify
	// multiple boolean options. e.g. `-abc` is equivalent to `-a -b -c`.
	AllowPosixSTMO bool

	// EnableFlagCompletionForAllCommands enables flag completion for
	// all commands of an application.
	// By default, flag completion is disabled to avoid unexpectedly running
	// the user command when doing flag completion, in case that the
	// command does not call `Parse`.
	//
	// Note that when flag completion is enabled, the command functions
	// must call `Parse` to parse flags and arguments, either by creating
	// Command by NewCommand or manually call `Parse` in functions
	// with signature `func()` or `func(*mcli.Context)`.
	EnableFlagCompletionForAllCommands bool

	// HelpFooter optionally adds a footer message to help output.
	// If Parse is called with option `WithFooter`, the option function's
	// output overrides this setting.
	HelpFooter string
}

Options specifies optional options for an App.

type ParseOpt

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

ParseOpt specifies options to customize the behavior of Parse.

func DisableGlobalFlags added in v0.2.0

func DisableGlobalFlags() ParseOpt

DisableGlobalFlags tells Parse to don't parse and print global flags in help.

func ReplaceUsage added in v0.3.0

func ReplaceUsage(f func() string) ParseOpt

ReplaceUsage specifies a function to generate a usage help to replace the default help.

func WithArgCompFuncs added in v0.9.0

func WithArgCompFuncs(funcMap map[string]ArgCompletionFunc) ParseOpt

WithArgCompFuncs specifies completion functions to complete flag values or positional arguments. Key of funcMap should be a flag name in form "-flag" or a positional arg name "arg1".

func WithArgs

func WithArgs(args []string) ParseOpt

WithArgs tells Parse to parse from the given args, instead of parsing from the command line arguments.

func WithErrorHandling

func WithErrorHandling(h flag.ErrorHandling) ParseOpt

WithErrorHandling tells Parse to use the given ErrorHandling. By default, Parse exits the program when an error happens.

func WithExamples added in v0.8.1

func WithExamples(examples string) ParseOpt

WithExamples specifies examples for a command. Examples will be showed after flags in the command's help.

func WithFooter added in v0.3.0

func WithFooter(f func() string) ParseOpt

WithFooter specifies a function to generate extra help text to print after the default help. If this option is provided, the option function's output overrides the App's optional help-footer setting.

func WithName

func WithName(name string) ParseOpt

WithName specifies the command name to use when printing usage doc.

Directories

Path Synopsis
examples
say

Jump to

Keyboard shortcuts

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