Spf13 / Cobra and Urfave/CLI are two excellent command line tools for Go:

The name of the star Introduction to the Application project
spf13/cobra 11571 A Commander for modern Go CLI interactions Docker, Kubernetes, Istio, Hugo…
urfave/cli 10501 A simple, fast, and fun package for building command line apps in Go drone, peach, gogs …

The briefs on both projects are interesting and the applications are excellent. Let’s learn together, from docker and drone source code, to understand how to use.

spf13/cobra

Spf13 this dude, feels like an Indian, but very good looking, here is his profile:

Spf13 @golang at @Google • Author, Speaker, Developer • Creator of Hugo, Cobra & SPF13 – Vim • Former Docker & MongoDB

You don’t need to know the hen to eat your eggs, but it’s also important to know which factory the hen belongs to, as is the case with open source projects. Google, Docker and mongodb are all great tech companies, Hugo is a great blogging platform and I use it for my personal blog. I feel cobra has a great background. Now that the small talk is over, let’s get down to business.

Docker help command

A good command line tool starts with a handy help directive to help the user understand the command, which is most important. Here’s docker’s help:

➜  ~ docker

Usage:	docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
      --config string      Location of client config files (default "/Users/tu/.docker")
  -D, --debug              Enable debug mode
  -H, --host list          Daemon socket(s) to connect to
  -l, --log-level string   Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
      --tls                Use TLS; implied by --tlsverify
      --tlscacert string   Trust certs signed only by this CA (default "/Users/tu/.docker/ca.pem")
      --tlscert string     Path to TLS certificate file (default "/Users/tu/.docker/cert.pem")
      --tlskey string      Path to TLS key file (default "/Users/tu/.docker/key.pem")
      --tlsverify          Use TLS and verify the remote
  -v, --version            Print version information and quit

Management Commands:
  builder     Manage builds
  ...
  container   Manage containers
  ...

Commands:
  ...
  ps          List containers
  ...

Run 'docker COMMAND --help' for more information on a command.
Copy the code

The help of Docker is very detailed. In order to avoid being too long, part of the output is omitted here, and the commands focused on analysis and introduction are reserved (the same below), which is very convenient to use. How does this help instruction work?

Start with the main function docker-ce\components\cli\ CMD \docker.go:

func main() {
	// Set terminal emulation based on platform as required.
	stdin, stdout, stderr := term.StdStreams()
	logrus.SetOutput(stderr)

	dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled(), containerizedengine.NewClient)
	cmd := newDockerCommand(dockerCli)

	iferr := cmd.Execute(); err ! = nil {if sterr, ok := err.(cli.StatusError); ok {
			ifsterr.Status ! ="" {
				fmt.Fprintln(stderr, sterr.Status)
			}
			// StatusError should only be used for errors, and all errors should
			// have a non-zero exit status, so never exit with 0
			if sterr.StatusCode == 0 {
				os.Exit(1)
			}
			os.Exit(sterr.StatusCode)
		}
		fmt.Fprintln(stderr, err)
		os.Exit(1)
	}
}
Copy the code

The code is very clear and does three things: 1) reads the command line input 2) parses the lookup command 3) executes the command.

In the newDockerCommand docker-ce\components\cli\ CMD \docker\docker.go, you can know the implementation of the root command:

cmd := &cobra.Command{
		Use:              "docker [OPTIONS] COMMAND [ARG...] ",
		Short:            "A self-sufficient runtime for containers",
		SilenceUsage:     true,
		SilenceErrors:    true,
		TraverseChildren: true,
		Args:             noArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return command.ShowHelp(dockerCli.Err())(cmd, args)
		},
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			// flags must be the top-level command flags, not cmd.Flags()
			opts.Common.SetDefaultOptions(flags)
			dockerPreRun(opts)
			iferr := dockerCli.Initialize(opts); err ! = nil {return err
			}
			return isSupported(cmd, dockerCli)
		},
		Version:               fmt.Sprintf("%s, build %s", cli.Version, cli.GitCommit),
		DisableFlagsInUseLine: true,}Copy the code

The Use and Short output clearly correspond from the code.

Check out the Docker help command at Cobra. go

var helpCommand = &cobra.Command{
	Use:               "help [command]",
	Short:             "Help about the command",
	PersistentPreRun:  func(cmd *cobra.Command, args []string) {},
	PersistentPostRun: func(cmd *cobra.Command, args []string) {},
	RunE: func(c *cobra.Command, args []string) error {
		cmd, args, e := c.Root().Find(args)
		ifcmd == nil || e ! = nil || len(args) > 0 {return errors.Errorf("unknown help topic: %v", strings.Join(args, ""))}helpFunc := cmd.HelpFunc()
		helpFunc(cmd, args)
		return nil
	},
}
Copy the code

And the template output for Docker Help

var usageTemplate = `Usage:

....

var helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

Copy the code

So, we have an overview of the output of the Docker command, and an overview of how COBRA is used.

Docker command registration

Continue to see how docker subcommands are registered. Line 62 of docker.go, where all commands are registered:

commands.AddCommands(cmd, dockerCli)
Copy the code

For implementation in command\commands\commands. Go:

// AddCommands adds all the commands from cli/command to the root command
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
	cmd.AddCommand(
		// checkpoint
		checkpoint.NewCheckpointCommand(dockerCli),

		// config
		config.NewConfigCommand(dockerCli),

		// container
		container.NewContainerCommand(dockerCli),
		container.NewRunCommand(dockerCli),

		...

	)
	if runtime.GOOS == "linux" {
		// engine
		cmd.AddCommand(engine.NewEngineCommand(dockerCli))
	}
}
Copy the code

Go to docker-ce\ Components \cli\ Command \container\cmd.go

// NewContainerCommand returns a cobra command for `container` subcommands
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "container",
		Short: "Manage containers",
		Args:  cli.NoArgs,
		RunE:  command.ShowHelp(dockerCli.Err()),
	}
	cmd.AddCommand(
		NewAttachCommand(dockerCli),
		NewCommitCommand(dockerCli),
		NewCopyCommand(dockerCli),
		NewCreateCommand(dockerCli),
		NewDiffCommand(dockerCli),
		NewExecCommand(dockerCli),
		NewExportCommand(dockerCli),
		NewKillCommand(dockerCli),
		NewLogsCommand(dockerCli),
		NewPauseCommand(dockerCli),
		....
	)
	return cmd
}
Copy the code

It can be clearly seen that docker Contianer subcommands are also registered through AddCommand interface.

Docker ps command

Docker ps, this is a very frequently used command, help is as follows:

➜  ~ docker ps --help

Usage:	docker ps [OPTIONS]

List containers

Options:
  -a, --all             Show all containers (default shows just running)
  -f. --filter filter Filter output based on conditions provided --format string Pretty-print containers using a Go template -n, --last int Show n last created containers (includes all states) (default -1)-l, --latest          Show the latest created container (includes all states)
      --no-trunc        Don't truncate output -q, --quiet Only display numeric IDs -s, --size Display total file sizesCopy the code

Docker ps is the alias of docker contianer ls.

➜ ~ docker container --help Usage: Docker container COMMAND Manage containers Commands:... ls List containers ... Run'docker container COMMAND --help' for more information on a command.
Copy the code

It can be seen that docker PS and Docker Container LS both function as List containers. Once you know this, look at the code: docker-ce\ Components \cli\ Command \container\ls.go:


// NewPsCommand creates a new cobra.Command for `docker ps`
func NewPsCommand(dockerCli command.Cli) *cobra.Command {
	options := psOptions{filter: opts.NewFilterOpt()}

	cmd := &cobra.Command{
		Use:   "ps [OPTIONS]",
		Short: "List containers",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runPs(dockerCli, &options)
		},
	}

	flags := cmd.Flags()

	flags.BoolVarP(&options.quiet, "quiet"."q".false."Only display numeric IDs")
	flags.BoolVarP(&options.size, "size"."s".false."Display total file sizes")
	flags.BoolVarP(&options.all, "all"."a".false."Show all containers (default shows just running)")
	flags.BoolVar(&options.noTrunc, "no-trunc".false."Don't truncate output")
	flags.BoolVarP(&options.nLatest, "latest"."l".false."Show the latest created container (includes all states)")
	flags.IntVarP(&options.last, "last"."n", 1,"Show n last created containers (includes all states)")
	flags.StringVarP(&options.format, "format"."".""."Pretty-print containers using a Go template")
	flags.VarP(&options.filter, "filter"."f"."Filter output based on conditions provided")

	return cmd
}

Copy the code

Docker ps –help output: Options and Flags Continue to see the BoolVarP definition at spf13/pflag/bool. Go

// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
	flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage)
	flag.NoOptDefVal = "true"
}
Copy the code

Along with the comment, it should be clear that option names are used — shorthand names are used. Docker pS-A is the same as Docker ps –all.

Spf13 /pflag is also a library of SPF13, dealing mainly with the number of parameters and so on. Because GO is strongly typed, user input should be legally processed into the data type corresponding to GO.

There is also a new command in ls.go that names ps as an alias for container ls.

func newListCommand(dockerCli command.Cli) *cobra.Command {
	cmd := *NewPsCommand(dockerCli)
	cmd.Aliases = []string{"ps"."list"}
	cmd.Use = "ls [OPTIONS]"
	return &cmd
}
Copy the code

So much for the brief introduction of COBRA, the docker version of this trial is as follows:

➜  ~ docker --version
Docker version 18.09.2, build 6247962
Copy the code

A quick summary of cobra use:

  • Command mode
  • Perfect help command and help option
  • Support options and shorthand options
  • Command Support Alias

urfave/cli

Drone help command

First look at drone’s help information:

➜  ~ drone --help
NAME:
   drone - command line utility

USAGE:
   drone [global options] command [commandoptions] [arguments...] VERSION: 1.0.7 COMMANDS:... repo manage repositories ... GLOBAL OPTIONS: -t value, --token value server auth token [$DRONE_TOKEN]
   -s value, --server value  server address [$DRONE_SERVER]
   --autoscaler value        autoscaler address [$DRONE_AUTOSCALER]
   --help, -h                show help
   --version, -v             print the version
Copy the code

Then the Drone repo subcommand:

➜  ~ drone repo
NAME:
   drone repo - manage repositories

USAGE:
   drone repo command [commandoptions] [arguments...]  COMMANDS: ls list all repos info show repository detailsenable   enable a repository
     update   update a repository
     disable  disable a repository
     repair   repair repository webhooks
     chown    assume ownership of a repository
     sync     synchronize the repository list

OPTIONS:
   --help, -h  show help
Copy the code

Drone repo ls secondary subcommand

➜  rone repo ls --help
NAME:
   drone repo ls - list all repos

USAGE:
   drone repo ls [command options]

OPTIONS:
   --format value  format output (default: "{{ .Slug }}")
   --org value     filter by organization

Copy the code

Drone command implementation

Drone -cli\drone\main.go is configured with a level of subcommand:

app.Commands = []cli.Command{
		build.Command,
		cron.Command,
		log.Command,
		encrypt.Command,
		exec.Command,
		info.Command,
		repo.Command,
	    ...
	}
Copy the code

Parsing execution of commands:

iferr := app.Run(os.Args); err ! = nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }Copy the code

Continue to check out repo\ rebo.go:

// Command exports the repository command.
var Command = cli.Command{
	Name:  "repo",
	Usage: "manage repositories",
	Subcommands: []cli.Command{
		repoListCmd,
		repoInfoCmd,
		repoAddCmd,
		repoUpdateCmd,
		repoRemoveCmd,
		repoRepairCmd,
		repoChownCmd,
		repoSyncCmd,
	},
}
Copy the code

You can see the drone repo subcommand registry.

var repoListCmd = cli.Command{
	Name:      "ls",
	Usage:     "list all repos",
	ArgsUsage: "",
	Action:    repoList,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "format",
			Usage: "format output",
			Value: tmplRepoList,
		},
		cli.StringFlag{
			Name:  "org",
			Usage: "filter by organization",,}}}Copy the code

Finally, take a look at how commands are found, mainly the following two functions.

Find commands by name:

// Command returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
	for _, c := range a.Commands {
		if c.HasName(name) {
			return &c
		}
	}

	return nil
}

Copy the code

Check whether the command names are consistent:

// HasName returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
	for _, n := range c.Names() {
		if n == name {
			return true}}return false
}
Copy the code

The drone version of this experiment is:

➜  ~ drone --version
drone version 1.0.7
Copy the code

A quick summary of cobra use:

  • The implementation looks simpler
  • There are also complete help commands and help options

conclusion

Spf13 / COBRA and URfave/CLI are great, urfave/ CLI is a little more concise; Spf13 / COBRA supports generator to assist in project generation and is a little more powerful. If you are interested in Go, I recommend you to know about it.

Refer to the link

★ What is the essential difference between urfave/cli и spf13/cobra?

Golang uses Cobra

Python command line tools