Browse code

Update usage and help to (almost) match the existing docker behaviour

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2016/05/17 06:20:29
Showing 9 changed files
... ...
@@ -1,9 +1,12 @@
1 1
 package volume
2 2
 
3 3
 import (
4
+	"fmt"
5
+
4 6
 	"github.com/spf13/cobra"
5 7
 
6 8
 	"github.com/docker/docker/api/client"
9
+	"github.com/docker/docker/cli"
7 10
 )
8 11
 
9 12
 // NewVolumeCommand returns a cobra command for `volume` subcommands
... ...
@@ -11,6 +14,14 @@ func NewVolumeCommand(dockerCli *client.DockerCli) *cobra.Command {
11 11
 	cmd := &cobra.Command{
12 12
 		Use:   "volume",
13 13
 		Short: "Manage Docker volumes",
14
+		// TODO: remove once cobra is patched to handle this
15
+		RunE: func(cmd *cobra.Command, args []string) error {
16
+			fmt.Fprintf(dockerCli.Err(), "\n%s", cmd.UsageString())
17
+			if len(args) > 0 {
18
+				return cli.StatusError{StatusCode: 1}
19
+			}
20
+			return nil
21
+		},
14 22
 	}
15 23
 	cmd.AddCommand(
16 24
 		newCreateCommand(dockerCli),
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"golang.org/x/net/context"
7 7
 
8 8
 	"github.com/docker/docker/api/client"
9
+	"github.com/docker/docker/cli"
9 10
 	"github.com/docker/docker/opts"
10 11
 	runconfigopts "github.com/docker/docker/runconfig/opts"
11 12
 	"github.com/docker/engine-api/types"
... ...
@@ -20,12 +21,18 @@ type createOptions struct {
20 20
 }
21 21
 
22 22
 func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
23
-	var opts createOptions
23
+	opts := createOptions{
24
+		driverOpts: *opts.NewMapOpts(nil, nil),
25
+	}
24 26
 
25 27
 	cmd := &cobra.Command{
26 28
 		Use:   "create",
27 29
 		Short: "Create a volume",
28 30
 		RunE: func(cmd *cobra.Command, args []string) error {
31
+			// TODO: remove once cobra is patched to handle this
32
+			if err := cli.AcceptsNoArgs(args, cmd); err != nil {
33
+				return err
34
+			}
29 35
 			return runCreate(dockerCli, opts)
30 36
 		},
31 37
 	}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"golang.org/x/net/context"
9 9
 
10 10
 	"github.com/docker/docker/api/client"
11
+	"github.com/docker/docker/cli"
11 12
 	"github.com/docker/engine-api/types"
12 13
 	"github.com/docker/engine-api/types/filters"
13 14
 	"github.com/spf13/cobra"
... ...
@@ -34,6 +35,10 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
34 34
 		Aliases: []string{"list"},
35 35
 		Short:   "List volumes",
36 36
 		RunE: func(cmd *cobra.Command, args []string) error {
37
+			// TODO: remove once cobra is patched to handle this
38
+			if err := cli.AcceptsNoArgs(args, cmd); err != nil {
39
+				return err
40
+			}
37 41
 			return runList(dockerCli, opts)
38 42
 		},
39 43
 	}
... ...
@@ -18,17 +18,21 @@ type CobraAdaptor struct {
18 18
 
19 19
 // NewCobraAdaptor returns a new handler
20 20
 func NewCobraAdaptor(clientFlags *cliflags.ClientFlags) CobraAdaptor {
21
-	var rootCmd = &cobra.Command{
22
-		Use: "docker",
23
-	}
24
-	rootCmd.SetUsageTemplate(usageTemplate)
25
-
26 21
 	stdin, stdout, stderr := term.StdStreams()
27 22
 	dockerCli := client.NewDockerCli(stdin, stdout, stderr, clientFlags)
28 23
 
24
+	var rootCmd = &cobra.Command{
25
+		Use:           "docker",
26
+		SilenceUsage:  true,
27
+		SilenceErrors: true,
28
+	}
29
+	rootCmd.SetUsageTemplate(usageTemplate)
30
+	rootCmd.SetHelpTemplate(helpTemplate)
31
+	rootCmd.SetOutput(stdout)
29 32
 	rootCmd.AddCommand(
30 33
 		volume.NewVolumeCommand(dockerCli),
31 34
 	)
35
+
32 36
 	return CobraAdaptor{
33 37
 		rootCmd:   rootCmd,
34 38
 		dockerCli: dockerCli,
... ...
@@ -64,20 +68,24 @@ func (c CobraAdaptor) Command(name string) func(...string) error {
64 64
 	return nil
65 65
 }
66 66
 
67
-var usageTemplate = `Usage:  {{if .Runnable}}{{if .HasFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND {{end}}{{if gt .Aliases 0}}
67
+var usageTemplate = `Usage:	{{if not .HasSubCommands}}{{if .HasLocalFlags}}{{appendIfNotPresent .UseLine "[OPTIONS]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasSubCommands}}{{ .CommandPath}} COMMAND{{end}}
68
+
69
+{{with or .Long .Short }}{{. | trim}}{{end}}{{if gt .Aliases 0}}
68 70
 
69 71
 Aliases:
70
-  {{.NameAndAliases}}
71
-{{end}}{{if .HasExample}}
72
+  {{.NameAndAliases}}{{end}}{{if .HasExample}}
72 73
 
73 74
 Examples:
74
-{{ .Example }}{{end}}{{ if .HasLocalFlags}}
75
+{{ .Example }}{{end}}{{if .HasFlags}}
75 76
 
76 77
 Options:
77
-{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}
78
+{{.Flags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableSubCommands}}
78 79
 
79 80
 Commands:{{range .Commands}}{{if .IsAvailableCommand}}
80 81
   {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
81 82
 
82 83
 Run '{{.CommandPath}} COMMAND --help' for more information on a command.{{end}}
83 84
 `
85
+
86
+var helpTemplate = `
87
+{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
... ...
@@ -21,3 +21,17 @@ func MinRequiredArgs(args []string, min int, cmd *cobra.Command) error {
21 21
 		cmd.Short,
22 22
 	)
23 23
 }
24
+
25
+// AcceptsNoArgs returns an error message if there are args
26
+func AcceptsNoArgs(args []string, cmd *cobra.Command) error {
27
+	if len(args) == 0 {
28
+		return nil
29
+	}
30
+
31
+	return fmt.Errorf(
32
+		"\"%s\" accepts no argument(s).\n\nUsage:  %s\n\n%s",
33
+		cmd.CommandPath(),
34
+		cmd.UseLine(),
35
+		cmd.Short,
36
+	)
37
+}
... ...
@@ -48,7 +48,6 @@ var DockerCommandUsage = []Command{
48 48
 	{"unpause", "Unpause all processes within a container"},
49 49
 	{"update", "Update configuration of one or more containers"},
50 50
 	{"version", "Show the Docker version information"},
51
-	{"volume", "Manage Docker volumes"},
52 51
 	{"wait", "Block until a container stops, then print its exit code"},
53 52
 }
54 53
 
... ...
@@ -249,7 +249,8 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
249 249
 		// If a line starts with 4 spaces then assume someone
250 250
 		// added a multi-line description for an option and we need
251 251
 		// to flag it
252
-		if strings.HasPrefix(line, "    ") {
252
+		if strings.HasPrefix(line, "    ") &&
253
+			!strings.HasPrefix(strings.TrimLeft(line, " "), "--") {
253 254
 			return fmt.Errorf("Help for %q should not have a multi-line option", cmd)
254 255
 		}
255 256
 
... ...
@@ -260,7 +261,7 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
260 260
 
261 261
 		// Options should NOT end with a space
262 262
 		if strings.HasSuffix(line, " ") {
263
-			return fmt.Errorf("Help for %q should not end with a space", cmd)
263
+			return fmt.Errorf("Help for %q should not end with a space: %s", cmd, line)
264 264
 		}
265 265
 
266 266
 	}
... ...
@@ -326,8 +327,8 @@ func testCommand(cmd string, newEnvs []string, scanForHome bool, home string) er
326 326
 			return fmt.Errorf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q\n", args, out, stderr, ec, err)
327 327
 		}
328 328
 		// Should have just short usage
329
-		if !strings.Contains(stderr, "\nUsage:\t") {
330
-			return fmt.Errorf("Missing short usage on %q\n", args)
329
+		if !strings.Contains(stderr, "\nUsage:") {
330
+			return fmt.Errorf("Missing short usage on %q\n:%#v", args, stderr)
331 331
 		}
332 332
 		// But shouldn't have full usage
333 333
 		if strings.Contains(stderr, "--help=false") {
... ...
@@ -25,7 +25,7 @@ func (s *DockerSuite) TestVolumeCliCreateOptionConflict(c *check.C) {
25 25
 	c.Assert(err, check.NotNil, check.Commentf("volume create exception name already in use with another driver"))
26 26
 	c.Assert(out, checker.Contains, "A volume named test already exists")
27 27
 
28
-	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Driver }}'", "test")
28
+	out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Driver }}", "test")
29 29
 	_, _, err = dockerCmdWithError("volume", "create", "--name", "test", "--driver", strings.TrimSpace(out))
30 30
 	c.Assert(err, check.IsNil)
31 31
 }
... ...
@@ -39,11 +39,11 @@ func (s *DockerSuite) TestVolumeCliInspect(c *check.C) {
39 39
 
40 40
 	out, _ := dockerCmd(c, "volume", "create")
41 41
 	name := strings.TrimSpace(out)
42
-	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", name)
42
+	out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Name }}", name)
43 43
 	c.Assert(strings.TrimSpace(out), check.Equals, name)
44 44
 
45 45
 	dockerCmd(c, "volume", "create", "--name", "test")
46
-	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Name }}'", "test")
46
+	out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Name }}", "test")
47 47
 	c.Assert(strings.TrimSpace(out), check.Equals, "test")
48 48
 }
49 49
 
... ...
@@ -212,7 +212,7 @@ func (s *DockerSuite) TestVolumeCliRm(c *check.C) {
212 212
 func (s *DockerSuite) TestVolumeCliNoArgs(c *check.C) {
213 213
 	out, _ := dockerCmd(c, "volume")
214 214
 	// no args should produce the cmd usage output
215
-	usage := "Usage:	docker volume [OPTIONS] [COMMAND]"
215
+	usage := "Usage:	docker volume COMMAND"
216 216
 	c.Assert(out, checker.Contains, usage)
217 217
 
218 218
 	// invalid arg should error and show the command usage on stderr
... ...
@@ -224,7 +224,7 @@ func (s *DockerSuite) TestVolumeCliNoArgs(c *check.C) {
224 224
 	_, stderr, _, err = runCommandWithStdoutStderr(exec.Command(dockerBinary, "volume", "--no-such-flag"))
225 225
 	c.Assert(err, check.NotNil, check.Commentf(stderr))
226 226
 	c.Assert(stderr, checker.Contains, usage)
227
-	c.Assert(stderr, checker.Contains, "flag provided but not defined: --no-such-flag")
227
+	c.Assert(stderr, checker.Contains, "unknown flag: --no-such-flag")
228 228
 }
229 229
 
230 230
 func (s *DockerSuite) TestVolumeCliInspectTmplError(c *check.C) {
... ...
@@ -268,7 +268,7 @@ func (s *DockerSuite) TestVolumeCliCreateLabel(c *check.C) {
268 268
 	out, _, err := dockerCmdWithError("volume", "create", "--label", testLabel+"="+testValue, "--name", testVol)
269 269
 	c.Assert(err, check.IsNil)
270 270
 
271
-	out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+testLabel+" }}'", testVol)
271
+	out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Labels."+testLabel+" }}", testVol)
272 272
 	c.Assert(strings.TrimSpace(out), check.Equals, testValue)
273 273
 }
274 274
 
... ...
@@ -295,7 +295,7 @@ func (s *DockerSuite) TestVolumeCliCreateLabelMultiple(c *check.C) {
295 295
 	c.Assert(err, check.IsNil)
296 296
 
297 297
 	for k, v := range testLabels {
298
-		out, _ = dockerCmd(c, "volume", "inspect", "--format='{{ .Labels."+k+" }}'", testVol)
298
+		out, _ = dockerCmd(c, "volume", "inspect", "--format={{ .Labels."+k+" }}", testVol)
299 299
 		c.Assert(strings.TrimSpace(out), check.Equals, v)
300 300
 	}
301 301
 }
... ...
@@ -100,6 +100,11 @@ func (opts *ListOpts) Len() int {
100 100
 	return len((*opts.values))
101 101
 }
102 102
 
103
+// Type returns a string name for this Option type
104
+func (opts *ListOpts) Type() string {
105
+	return "list"
106
+}
107
+
103 108
 // NamedOption is an interface that list and map options
104 109
 // with names implement.
105 110
 type NamedOption interface {