cli.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package cli
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strings"
  8. flag "github.com/docker/docker/pkg/mflag"
  9. )
  10. // Cli represents a command line interface.
  11. type Cli struct {
  12. Stderr io.Writer
  13. handlers []Handler
  14. Usage func()
  15. }
  16. // Handler holds the different commands Cli will call
  17. // It should have methods with names starting with `Cmd` like:
  18. // func (h myHandler) CmdFoo(args ...string) error
  19. type Handler interface {
  20. Command(name string) func(...string) error
  21. }
  22. // Initializer can be optionally implemented by a Handler to
  23. // initialize before each call to one of its commands.
  24. type Initializer interface {
  25. Initialize() error
  26. }
  27. // New instantiates a ready-to-use Cli.
  28. func New(handlers ...Handler) *Cli {
  29. // make the generic Cli object the first cli handler
  30. // in order to handle `docker help` appropriately
  31. cli := new(Cli)
  32. cli.handlers = append([]Handler{cli}, handlers...)
  33. return cli
  34. }
  35. // initErr is an error returned upon initialization of a handler implementing Initializer.
  36. type initErr struct{ error }
  37. func (err initErr) Error() string {
  38. return err.Error()
  39. }
  40. func (cli *Cli) command(args ...string) (func(...string) error, error) {
  41. for _, c := range cli.handlers {
  42. if c == nil {
  43. continue
  44. }
  45. if cmd := c.Command(strings.Join(args, " ")); cmd != nil {
  46. if ci, ok := c.(Initializer); ok {
  47. if err := ci.Initialize(); err != nil {
  48. return nil, initErr{err}
  49. }
  50. }
  51. return cmd, nil
  52. }
  53. }
  54. return nil, errors.New("command not found")
  55. }
  56. // Run executes the specified command.
  57. func (cli *Cli) Run(args ...string) error {
  58. if len(args) > 1 {
  59. command, err := cli.command(args[:2]...)
  60. switch err := err.(type) {
  61. case nil:
  62. return command(args[2:]...)
  63. case initErr:
  64. return err.error
  65. }
  66. }
  67. if len(args) > 0 {
  68. command, err := cli.command(args[0])
  69. switch err := err.(type) {
  70. case nil:
  71. return command(args[1:]...)
  72. case initErr:
  73. return err.error
  74. }
  75. cli.noSuchCommand(args[0])
  76. }
  77. return cli.CmdHelp()
  78. }
  79. func (cli *Cli) noSuchCommand(command string) {
  80. if cli.Stderr == nil {
  81. cli.Stderr = os.Stderr
  82. }
  83. fmt.Fprintf(cli.Stderr, "docker: '%s' is not a docker command.\nSee 'docker --help'.\n", command)
  84. os.Exit(1)
  85. }
  86. // Command returns a command handler, or nil if the command does not exist
  87. func (cli *Cli) Command(name string) func(...string) error {
  88. return map[string]func(...string) error{
  89. "help": cli.CmdHelp,
  90. }[name]
  91. }
  92. // CmdHelp displays information on a Docker command.
  93. //
  94. // If more than one command is specified, information is only shown for the first command.
  95. //
  96. // Usage: docker help COMMAND or docker COMMAND --help
  97. func (cli *Cli) CmdHelp(args ...string) error {
  98. if len(args) > 1 {
  99. command, err := cli.command(args[:2]...)
  100. switch err := err.(type) {
  101. case nil:
  102. command("--help")
  103. return nil
  104. case initErr:
  105. return err.error
  106. }
  107. }
  108. if len(args) > 0 {
  109. command, err := cli.command(args[0])
  110. switch err := err.(type) {
  111. case nil:
  112. command("--help")
  113. return nil
  114. case initErr:
  115. return err.error
  116. }
  117. cli.noSuchCommand(args[0])
  118. }
  119. if cli.Usage == nil {
  120. flag.Usage()
  121. } else {
  122. cli.Usage()
  123. }
  124. return nil
  125. }
  126. // Subcmd is a subcommand of the main "docker" command.
  127. // A subcommand represents an action that can be performed
  128. // from the Docker command line client.
  129. //
  130. // To see all available subcommands, run "docker --help".
  131. func Subcmd(name string, synopses []string, description string, exitOnError bool) *flag.FlagSet {
  132. var errorHandling flag.ErrorHandling
  133. if exitOnError {
  134. errorHandling = flag.ExitOnError
  135. } else {
  136. errorHandling = flag.ContinueOnError
  137. }
  138. flags := flag.NewFlagSet(name, errorHandling)
  139. flags.Usage = func() {
  140. flags.ShortUsage()
  141. flags.PrintDefaults()
  142. }
  143. flags.ShortUsage = func() {
  144. options := ""
  145. if flags.FlagCountUndeprecated() > 0 {
  146. options = " [OPTIONS]"
  147. }
  148. if len(synopses) == 0 {
  149. synopses = []string{""}
  150. }
  151. // Allow for multiple command usage synopses.
  152. for i, synopsis := range synopses {
  153. lead := "\t"
  154. if i == 0 {
  155. // First line needs the word 'Usage'.
  156. lead = "Usage:\t"
  157. }
  158. if synopsis != "" {
  159. synopsis = " " + synopsis
  160. }
  161. fmt.Fprintf(flags.Out(), "\n%sdocker %s%s%s", lead, name, options, synopsis)
  162. }
  163. fmt.Fprintf(flags.Out(), "\n\n%s\n", description)
  164. }
  165. return flags
  166. }
  167. // StatusError reports an unsuccessful exit by a command.
  168. type StatusError struct {
  169. Status string
  170. StatusCode int
  171. }
  172. func (e StatusError) Error() string {
  173. return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
  174. }