blob: 56459ed70561ef7a6e3d5b09dd482e3630f76892 [file] [log] [blame]
package cli
import (
"io"
"os"
"sync"
)
// CLI contains the state necessary to run subcommands and parse the
// command line arguments.
type CLI struct {
// Args is the list of command-line arguments received excluding
// the name of the app. For example, if the command "./cli foo bar"
// was invoked, then Args should be []string{"foo", "bar"}.
Args []string
// Commands is a mapping of subcommand names to a factory function
// for creating that Command implementation.
Commands map[string]CommandFactory
// Name defines the name of the CLI.
Name string
// Version of the CLI.
Version string
// HelpFunc and HelpWriter are used to output help information, if
// requested.
//
// HelpFunc is the function called to generate the generic help
// text that is shown if help must be shown for the CLI that doesn't
// pertain to a specific command.
//
// HelpWriter is the Writer where the help text is outputted to. If
// not specified, it will default to Stderr.
HelpFunc HelpFunc
HelpWriter io.Writer
once sync.Once
isHelp bool
subcommand string
subcommandArgs []string
isVersion bool
}
// NewClI returns a new CLI instance with sensible defaults.
func NewCLI(app, version string) *CLI {
return &CLI{
Name: app,
Version: version,
HelpFunc: BasicHelpFunc(app),
}
}
// IsHelp returns whether or not the help flag is present within the
// arguments.
func (c *CLI) IsHelp() bool {
c.once.Do(c.init)
return c.isHelp
}
// IsVersion returns whether or not the version flag is present within the
// arguments.
func (c *CLI) IsVersion() bool {
c.once.Do(c.init)
return c.isVersion
}
// Run runs the actual CLI based on the arguments given.
func (c *CLI) Run() (int, error) {
c.once.Do(c.init)
// Just show the version and exit if instructed.
if c.IsVersion() && c.Version != "" {
c.HelpWriter.Write([]byte(c.Version + "\n"))
return 1, nil
}
// Attempt to get the factory function for creating the command
// implementation. If the command is invalid or blank, it is an error.
commandFunc, ok := c.Commands[c.Subcommand()]
if !ok || c.Subcommand() == "" {
c.HelpWriter.Write([]byte(c.HelpFunc(c.Commands) + "\n"))
return 1, nil
}
command, err := commandFunc()
if err != nil {
return 0, err
}
// If we've been instructed to just print the help, then print it
if c.IsHelp() {
c.HelpWriter.Write([]byte(command.Help() + "\n"))
return 1, nil
}
return command.Run(c.SubcommandArgs()), nil
}
// Subcommand returns the subcommand that the CLI would execute. For
// example, a CLI from "--version version --help" would return a Subcommand
// of "version"
func (c *CLI) Subcommand() string {
c.once.Do(c.init)
return c.subcommand
}
// SubcommandArgs returns the arguments that will be passed to the
// subcommand.
func (c *CLI) SubcommandArgs() []string {
c.once.Do(c.init)
return c.subcommandArgs
}
func (c *CLI) init() {
if c.HelpFunc == nil {
c.HelpFunc = BasicHelpFunc("app")
if c.Name != "" {
c.HelpFunc = BasicHelpFunc(c.Name)
}
}
if c.HelpWriter == nil {
c.HelpWriter = os.Stderr
}
c.processArgs()
}
func (c *CLI) processArgs() {
for i, arg := range c.Args {
if c.subcommand == "" {
// Check for version and help flags if not in a subcommand
if arg == "-v" || arg == "-version" || arg == "--version" {
c.isVersion = true
continue
}
if arg == "-h" || arg == "-help" || arg == "--help" {
c.isHelp = true
continue
}
}
// If we didn't find a subcommand yet and this is the first non-flag
// argument, then this is our subcommand. j
if c.subcommand == "" && arg[0] != '-' {
c.subcommand = arg
// The remaining args the subcommand arguments
c.subcommandArgs = c.Args[i+1:]
}
}
}