Refactor plugins' vendor location on --help

- The placement of the vendor is now in the end of the line.
- A '*' is now added as suffix of plugins' top level commands.

Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
This commit is contained in:
Ulysses Souza 2019-02-15 17:27:22 +01:00
parent db166da03a
commit 92013600f9
4 changed files with 46 additions and 24 deletions

View File

@ -16,6 +16,11 @@ const (
// that plugin. // that plugin.
CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor" CommandAnnotationPluginVendor = "com.docker.cli.plugin.vendor"
// CommandAnnotationPluginVersion is added to every stub command
// added by AddPluginCommandStubs and contains the version of
// that plugin.
CommandAnnotationPluginVersion = "com.docker.cli.plugin.version"
// CommandAnnotationPluginInvalid is added to any stub command // CommandAnnotationPluginInvalid is added to any stub command
// added by AddPluginCommandStubs for an invalid command (that // added by AddPluginCommandStubs for an invalid command (that
// is, one which failed it's candidate test) and contains the // is, one which failed it's candidate test) and contains the
@ -37,8 +42,9 @@ func AddPluginCommandStubs(dockerCli command.Cli, cmd *cobra.Command) error {
vendor = "unknown" vendor = "unknown"
} }
annotations := map[string]string{ annotations := map[string]string{
CommandAnnotationPlugin: "true", CommandAnnotationPlugin: "true",
CommandAnnotationPluginVendor: vendor, CommandAnnotationPluginVendor: vendor,
CommandAnnotationPluginVersion: p.Version,
} }
if p.Err != nil { if p.Err != nil {
annotations[CommandAnnotationPluginInvalid] = p.Err.Error() annotations[CommandAnnotationPluginInvalid] = p.Err.Error()

View File

@ -22,6 +22,7 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *p
flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files") flags.StringVar(&opts.ConfigDir, "config", cliconfig.Dir(), "Location of client config files")
opts.Common.InstallFlags(flags) opts.Common.InstallFlags(flags)
cobra.AddTemplateFunc("add", func(a, b int) int { return a + b })
cobra.AddTemplateFunc("hasSubCommands", hasSubCommands) cobra.AddTemplateFunc("hasSubCommands", hasSubCommands)
cobra.AddTemplateFunc("hasManagementSubCommands", hasManagementSubCommands) cobra.AddTemplateFunc("hasManagementSubCommands", hasManagementSubCommands)
cobra.AddTemplateFunc("hasInvalidPlugins", hasInvalidPlugins) cobra.AddTemplateFunc("hasInvalidPlugins", hasInvalidPlugins)
@ -29,9 +30,10 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *p
cobra.AddTemplateFunc("managementSubCommands", managementSubCommands) cobra.AddTemplateFunc("managementSubCommands", managementSubCommands)
cobra.AddTemplateFunc("invalidPlugins", invalidPlugins) cobra.AddTemplateFunc("invalidPlugins", invalidPlugins)
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages) cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
cobra.AddTemplateFunc("commandVendor", commandVendor) cobra.AddTemplateFunc("vendorAndVersion", vendorAndVersion)
cobra.AddTemplateFunc("isFirstLevelCommand", isFirstLevelCommand) // is it an immediate sub-command of the root
cobra.AddTemplateFunc("invalidPluginReason", invalidPluginReason) cobra.AddTemplateFunc("invalidPluginReason", invalidPluginReason)
cobra.AddTemplateFunc("isPlugin", isPlugin)
cobra.AddTemplateFunc("decoratedName", decoratedName)
rootCmd.SetUsageTemplate(usageTemplate) rootCmd.SetUsageTemplate(usageTemplate)
rootCmd.SetHelpTemplate(helpTemplate) rootCmd.SetHelpTemplate(helpTemplate)
@ -155,19 +157,23 @@ func wrappedFlagUsages(cmd *cobra.Command) string {
return cmd.Flags().FlagUsagesWrapped(width - 1) return cmd.Flags().FlagUsagesWrapped(width - 1)
} }
func isFirstLevelCommand(cmd *cobra.Command) bool { func decoratedName(cmd *cobra.Command) string {
return cmd.Parent() == cmd.Root() decoration := " "
if isPlugin(cmd) {
decoration = "*"
}
return cmd.Name() + decoration
} }
func commandVendor(cmd *cobra.Command) string { func vendorAndVersion(cmd *cobra.Command) string {
width := 13 if vendor, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok { version := ""
if len(v) > width-2 { if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" {
v = v[:width-3] + "…" version = ", " + v
} }
return fmt.Sprintf("%-*s", width, "("+v+")") return fmt.Sprintf("(%s%s)", vendor, version)
} }
return strings.Repeat(" ", width) return ""
} }
func managementSubCommands(cmd *cobra.Command) []*cobra.Command { func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
@ -230,7 +236,7 @@ Options:
Management Commands: Management Commands:
{{- range managementSubCommands . }} {{- range managementSubCommands . }}
{{rpad .Name .NamePadding }} {{ if isFirstLevelCommand .}}{{commandVendor .}} {{ end}}{{.Short}} {{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}{{ if isPlugin .}} {{vendorAndVersion .}}{{ end}}
{{- end}} {{- end}}
{{- end}} {{- end}}
@ -239,7 +245,7 @@ Management Commands:
Commands: Commands:
{{- range operationSubCommands . }} {{- range operationSubCommands . }}
{{rpad .Name .NamePadding }} {{ if isFirstLevelCommand .}}{{commandVendor .}} {{ end}}{{.Short}} {{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}{{ if isPlugin .}} {{vendorAndVersion .}}{{ end}}
{{- end}} {{- end}}
{{- end}} {{- end}}

View File

@ -32,28 +32,29 @@ func TestVisitAll(t *testing.T) {
assert.DeepEqual(t, expected, visited) assert.DeepEqual(t, expected, visited)
} }
func TestCommandVendor(t *testing.T) { func TestVendorAndVersion(t *testing.T) {
// Non plugin. // Non plugin.
assert.Equal(t, commandVendor(&cobra.Command{Use: "test"}), " ") assert.Equal(t, vendorAndVersion(&cobra.Command{Use: "test"}), "")
// Plugins with various lengths of vendor. // Plugins with various lengths of vendor.
for _, tc := range []struct { for _, tc := range []struct {
vendor string vendor string
version string
expected string expected string
}{ }{
{vendor: "vendor", expected: "(vendor) "}, {vendor: "vendor", expected: "(vendor)"},
{vendor: "vendor12345", expected: "(vendor12345)"}, {vendor: "vendor", version: "testing", expected: "(vendor, testing)"},
{vendor: "vendor123456", expected: "(vendor1234…)"},
{vendor: "vendor1234567", expected: "(vendor1234…)"},
} { } {
t.Run(tc.vendor, func(t *testing.T) { t.Run(tc.vendor, func(t *testing.T) {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "test", Use: "test",
Annotations: map[string]string{ Annotations: map[string]string{
pluginmanager.CommandAnnotationPluginVendor: tc.vendor, pluginmanager.CommandAnnotationPlugin: "true",
pluginmanager.CommandAnnotationPluginVendor: tc.vendor,
pluginmanager.CommandAnnotationPluginVersion: tc.version,
}, },
} }
assert.Equal(t, commandVendor(cmd), tc.expected) assert.Equal(t, vendorAndVersion(cmd), tc.expected)
}) })
} }
} }
@ -76,3 +77,12 @@ func TestInvalidPlugin(t *testing.T) {
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{})) assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
} }
func TestDecoratedName(t *testing.T) {
root := &cobra.Command{Use: "root"}
topLevelCommand := &cobra.Command{Use: "pluginTopLevelCommand"}
root.AddCommand(topLevelCommand)
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand ")
topLevelCommand.Annotations = map[string]string{pluginmanager.CommandAnnotationPlugin: "true"}
assert.Equal(t, decoratedName(topLevelCommand), "pluginTopLevelCommand*")
}

View File

@ -34,7 +34,7 @@ func TestGlobalHelp(t *testing.T) {
// - The `badmeta` plugin under the "Invalid Plugins" heading. // - The `badmeta` plugin under the "Invalid Plugins" heading.
// //
// Regexps are needed because the width depends on `unix.TIOCGWINSZ` or similar. // Regexps are needed because the width depends on `unix.TIOCGWINSZ` or similar.
helloworldre := regexp.MustCompile(`^ helloworld\s+\(Docker Inc\.\)\s+A basic Hello World plugin for tests$`) helloworldre := regexp.MustCompile(`^ helloworld\*\s+A basic Hello World plugin for tests \(Docker Inc\., testing\)$`)
badmetare := regexp.MustCompile(`^ badmeta\s+invalid metadata: invalid character 'i' looking for beginning of object key string$`) badmetare := regexp.MustCompile(`^ badmeta\s+invalid metadata: invalid character 'i' looking for beginning of object key string$`)
var helloworldcount, badmetacount int var helloworldcount, badmetacount int
for _, expected := range []*regexp.Regexp{ for _, expected := range []*regexp.Regexp{