github.com/projectatomic/docker@v1.8.2/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  }