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