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