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  }