package cli import ( "errors" "fmt" "io" "os" "strings" flag "github.com/docker/docker/pkg/mflag" ) // Cli represents a command line interface. type Cli struct { Stderr io.Writer handlers []Handler Usage func() } // Handler holds the different commands Cli will call // It should have methods with names starting with `Cmd` like: // func (h myHandler) CmdFoo(args ...string) error type Handler interface { Command(name string) func(...string) error } // Initializer can be optionally implemented by a Handler to // initialize before each call to one of its commands. type Initializer interface { Initialize() error } // New instantiates a ready-to-use Cli. func New(handlers ...Handler) *Cli { // make the generic Cli object the first cli handler // in order to handle `docker help` appropriately cli := new(Cli) cli.handlers = append([]Handler{cli}, handlers...) return cli } var errCommandNotFound = errors.New("command not found") func (cli *Cli) command(args ...string) (func(...string) error, error) { for _, c := range cli.handlers { if c == nil { continue } if cmd := c.Command(strings.Join(args, " ")); cmd != nil { if ci, ok := c.(Initializer); ok { if err := ci.Initialize(); err != nil { return nil, err } } return cmd, nil } } return nil, errCommandNotFound } // Run executes the specified command. func (cli *Cli) Run(args ...string) error { if len(args) > 1 { command, err := cli.command(args[:2]...) if err == nil { return command(args[2:]...) } if err != errCommandNotFound { return err } } if len(args) > 0 { command, err := cli.command(args[0]) if err != nil { if err == errCommandNotFound { cli.noSuchCommand(args[0]) return nil } return err } return command(args[1:]...) } return cli.CmdHelp() } func (cli *Cli) noSuchCommand(command string) { if cli.Stderr == nil { cli.Stderr = os.Stderr } fmt.Fprintf(cli.Stderr, "docker: '%s' is not a docker command.\nSee 'docker --help'.\n", command) os.Exit(1) } // Command returns a command handler, or nil if the command does not exist func (cli *Cli) Command(name string) func(...string) error { return map[string]func(...string) error{ "help": cli.CmdHelp, }[name] } // CmdHelp displays information on a Docker command. // // If more than one command is specified, information is only shown for the first command. // // Usage: docker help COMMAND or docker COMMAND --help func (cli *Cli) CmdHelp(args ...string) error { if len(args) > 1 { command, err := cli.command(args[:2]...) if err == nil { command("--help") return nil } if err != errCommandNotFound { return err } } if len(args) > 0 { command, err := cli.command(args[0]) if err != nil { if err == errCommandNotFound { cli.noSuchCommand(args[0]) return nil } return err } command("--help") return nil } if cli.Usage == nil { flag.Usage() } else { cli.Usage() } return nil } // Subcmd is a subcommand of the main "docker" command. // A subcommand represents an action that can be performed // from the Docker command line client. // // To see all available subcommands, run "docker --help". func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet { var errorHandling flag.ErrorHandling if exitOnError { errorHandling = flag.ExitOnError } else { errorHandling = flag.ContinueOnError } flags := flag.NewFlagSet(name, errorHandling) flags.Usage = func() { flags.ShortUsage() flags.PrintDefaults() } flags.ShortUsage = func() { if len(synopses) == 0 { synopses = []string{""} } // Allow for multiple command usage synopses. for i, synopsis := range synopses { lead := "\t" if i == 0 { // First line needs the word 'Usage'. lead = "Usage:\t" } if synopsis != "" { synopsis = " " + synopsis } fmt.Fprintf(flags.Out(), "\n%sdocker %s%s", lead, name, synopsis) } fmt.Fprintf(flags.Out(), "\n\n%s\n", description) } return flags } // StatusError reports an unsuccessful exit by a command. type StatusError struct { Status string StatusCode int } func (e StatusError) Error() string { return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) }