From 4d641c491171586b5c033cfd81566049e6059470 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Mon, 10 Oct 2016 15:39:16 -0300 Subject: [PATCH] Use responsive writer in help --- pkg/kubectl/cmd/cmd.go | 27 --- pkg/kubectl/cmd/templates/command_groups.go | 61 +++++++ pkg/kubectl/cmd/templates/templater.go | 185 ++++++++++---------- pkg/kubectl/cmd/templates/templates.go | 119 +++++++------ 4 files changed, 211 insertions(+), 181 deletions(-) create mode 100644 pkg/kubectl/cmd/templates/command_groups.go diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index 3c70434df13..d1babebef5c 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -195,33 +195,6 @@ __custom_func() { * serviceaccounts (aka 'sa') * services (aka 'svc') ` - usage_template = `{{if gt .Aliases 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}} - -Available Sub-commands:{{range .Commands}}{{if .IsAvailableCommand}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsHelpCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}} - -Usage:{{if .Runnable}} - {{if .HasFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{ if .HasSubCommands }} - {{ .CommandPath}} [command] - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -` - help_template = `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` ) // NewKubectlCommand creates the `kubectl` command and its nested children. diff --git a/pkg/kubectl/cmd/templates/command_groups.go b/pkg/kubectl/cmd/templates/command_groups.go new file mode 100644 index 00000000000..42baa85a607 --- /dev/null +++ b/pkg/kubectl/cmd/templates/command_groups.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package templates + +import ( + "github.com/spf13/cobra" +) + +type CommandGroup struct { + Message string + Commands []*cobra.Command +} + +type CommandGroups []CommandGroup + +func (g CommandGroups) Add(c *cobra.Command) { + for _, group := range g { + for _, command := range group.Commands { + c.AddCommand(command) + } + } +} + +func (g CommandGroups) Has(c *cobra.Command) bool { + for _, group := range g { + for _, command := range group.Commands { + if command == c { + return true + } + } + } + return false +} + +func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups { + group := CommandGroup{Message: message} + for _, c := range cmds { + // Don't show commands that have no short description + if !g.Has(c) && len(c.Short) != 0 { + group.Commands = append(group.Commands, c) + } + } + if len(group.Commands) == 0 { + return g + } + return append(g, group) +} diff --git a/pkg/kubectl/cmd/templates/templater.go b/pkg/kubectl/cmd/templates/templater.go index b51f45a1630..30c128ef3b2 100644 --- a/pkg/kubectl/cmd/templates/templater.go +++ b/pkg/kubectl/cmd/templates/templater.go @@ -23,73 +23,12 @@ import ( "text/template" "unicode" + "k8s.io/kubernetes/pkg/util/term" + "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) -// Content of this package was borrowed from openshift/origin. - -type CommandGroup struct { - Message string - Commands []*cobra.Command -} - -type CommandGroups []CommandGroup - -func (g CommandGroups) Add(c *cobra.Command) { - for _, group := range g { - for _, command := range group.Commands { - c.AddCommand(command) - } - } -} - -func (g CommandGroups) Has(c *cobra.Command) bool { - for _, group := range g { - for _, command := range group.Commands { - if command == c { - return true - } - } - } - return false -} - -func AddAdditionalCommands(g CommandGroups, message string, cmds []*cobra.Command) CommandGroups { - group := CommandGroup{Message: message} - for _, c := range cmds { - // Don't show commands that has no short description - if !g.Has(c) && len(c.Short) != 0 { - group.Commands = append(group.Commands, c) - } - } - if len(group.Commands) == 0 { - return g - } - return append(g, group) -} - -func filter(cmds []*cobra.Command, names ...string) []*cobra.Command { - out := []*cobra.Command{} - for _, c := range cmds { - if c.Hidden { - continue - } - skip := false - for _, name := range names { - if name == c.Name() { - skip = true - break - } - } - if skip { - continue - } - out = append(out, c) - } - return out -} - type FlagExposer interface { ExposeFlags(cmd *cobra.Command, flags ...string) FlagExposer } @@ -98,27 +37,30 @@ func ActsAsRootCommand(cmd *cobra.Command, filters []string, groups ...CommandGr if cmd == nil { panic("nil root command") } - cmd.SetHelpTemplate(MainHelpTemplate()) templater := &templater{ RootCmd: cmd, UsageTemplate: MainUsageTemplate(), + HelpTemplate: MainHelpTemplate(), CommandGroups: groups, Filtered: filters, } cmd.SetUsageFunc(templater.UsageFunc()) + cmd.SetHelpFunc(templater.HelpFunc()) return templater } func UseOptionsTemplates(cmd *cobra.Command) { - cmd.SetHelpTemplate(OptionsHelpTemplate()) templater := &templater{ UsageTemplate: OptionsUsageTemplate(), + HelpTemplate: OptionsHelpTemplate(), } cmd.SetUsageFunc(templater.UsageFunc()) + cmd.SetHelpFunc(templater.HelpFunc()) } type templater struct { UsageTemplate string + HelpTemplate string RootCmd *cobra.Command CommandGroups Filtered []string @@ -129,42 +71,58 @@ func (templater *templater) ExposeFlags(cmd *cobra.Command, flags ...string) Fla return templater } +func (templater *templater) HelpFunc() func(*cobra.Command, []string) { + return func(c *cobra.Command, s []string) { + t := template.New("help") + t.Funcs(templater.templateFuncs()) + template.Must(t.Parse(templater.HelpTemplate)) + out := term.NewResponsiveWriter(c.OutOrStdout()) + err := t.Execute(out, c) + if err != nil { + c.Println(err) + } + } +} + func (templater *templater) UsageFunc(exposedFlags ...string) func(*cobra.Command) error { return func(c *cobra.Command) error { - t := template.New("custom") + t := template.New("usage") + t.Funcs(templater.templateFuncs(exposedFlags...)) + template.Must(t.Parse(templater.UsageTemplate)) + out := term.NewResponsiveWriter(c.OutOrStderr()) + return t.Execute(out, c) + } +} - t.Funcs(template.FuncMap{ - "trim": strings.TrimSpace, - "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }, - "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }, - "gt": cobra.Gt, - "eq": cobra.Eq, - "rpad": rpad, - "appendIfNotPresent": appendIfNotPresent, - "flagsNotIntersected": flagsNotIntersected, - "visibleFlags": visibleFlags, - "flagsUsages": flagsUsages, - "indentLines": indentLines, - "cmdGroups": templater.cmdGroups, - "rootCmd": templater.rootCmdName, - "isRootCmd": templater.isRootCmd, - "optionsCmdFor": templater.optionsCmdFor, - "usageLine": templater.usageLine, - "exposed": func(c *cobra.Command) *flag.FlagSet { - exposed := flag.NewFlagSet("exposed", flag.ContinueOnError) - if len(exposedFlags) > 0 { - for _, name := range exposedFlags { - if flag := c.Flags().Lookup(name); flag != nil { - exposed.AddFlag(flag) - } +func (templater *templater) templateFuncs(exposedFlags ...string) template.FuncMap { + return template.FuncMap{ + "trim": strings.TrimSpace, + "trimRight": func(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) }, + "trimLeft": func(s string) string { return strings.TrimLeftFunc(s, unicode.IsSpace) }, + "gt": cobra.Gt, + "eq": cobra.Eq, + "rpad": rpad, + "appendIfNotPresent": appendIfNotPresent, + "flagsNotIntersected": flagsNotIntersected, + "visibleFlags": visibleFlags, + "flagsUsages": flagsUsages, + "cmdGroups": templater.cmdGroups, + "cmdGroupsString": templater.cmdGroupsString, + "rootCmd": templater.rootCmdName, + "isRootCmd": templater.isRootCmd, + "optionsCmdFor": templater.optionsCmdFor, + "usageLine": templater.usageLine, + "exposed": func(c *cobra.Command) *flag.FlagSet { + exposed := flag.NewFlagSet("exposed", flag.ContinueOnError) + if len(exposedFlags) > 0 { + for _, name := range exposedFlags { + if flag := c.Flags().Lookup(name); flag != nil { + exposed.AddFlag(flag) } } - return exposed - }, - }) - - template.Must(t.Parse(templater.UsageTemplate)) - return t.Execute(c.OutOrStdout(), c) + } + return exposed + }, } } @@ -182,6 +140,20 @@ func (templater *templater) cmdGroups(c *cobra.Command, all []*cobra.Command) [] } } +func (t *templater) cmdGroupsString(c *cobra.Command) string { + groups := []string{} + for _, cmdGroup := range t.cmdGroups(c, c.Commands()) { + cmds := []string{cmdGroup.Message} + for _, cmd := range cmdGroup.Commands { + if cmd.Runnable() { + cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short) + } + } + groups = append(groups, strings.Join(cmds, "\n")) + } + return strings.Join(groups, "\n\n") +} + func (t *templater) rootCmdName(c *cobra.Command) string { return t.rootCmd(c).CommandPath() } @@ -298,3 +270,24 @@ func visibleFlags(l *flag.FlagSet) *flag.FlagSet { }) return f } + +func filter(cmds []*cobra.Command, names ...string) []*cobra.Command { + out := []*cobra.Command{} + for _, c := range cmds { + if c.Hidden { + continue + } + skip := false + for _, name := range names { + if name == c.Name() { + skip = true + break + } + } + if skip { + continue + } + out = append(out, c) + } + return out +} diff --git a/pkg/kubectl/cmd/templates/templates.go b/pkg/kubectl/cmd/templates/templates.go index 45a4aa3fd7a..c40908f9f60 100644 --- a/pkg/kubectl/cmd/templates/templates.go +++ b/pkg/kubectl/cmd/templates/templates.go @@ -16,84 +16,87 @@ limitations under the License. package templates -import "strings" - -func MainHelpTemplate() string { - return decorate(mainHelpTemplate, false) -} - -func MainUsageTemplate() string { - return decorate(mainUsageTemplate, true) + "\n" -} - -func OptionsHelpTemplate() string { - return decorate(optionsHelpTemplate, false) -} - -func OptionsUsageTemplate() string { - return decorate(optionsUsageTemplate, false) -} - -func decorate(template string, trim bool) string { - if trim && len(strings.Trim(template, " ")) > 0 { - template = strings.Trim(template, "\n") - } - return template -} +import ( + "strings" + "unicode" +) const ( - vars = `{{$isRootCmd := isRootCmd .}}` + + // SectionVars is the help template section that declares variables to be used in the template. + SectionVars = `{{$isRootCmd := isRootCmd .}}` + `{{$rootCmd := rootCmd .}}` + `{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` + `{{$explicitlyExposedFlags := exposed .}}` + `{{$optionsCmdFor := optionsCmdFor .}}` + `{{$usageLine := usageLine .}}` - mainHelpTemplate = `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` + // SectionAliases is the help template section that displays command aliases. + SectionAliases = `{{if gt .Aliases 0}}Aliases: +{{.NameAndAliases}} - mainUsageTemplate = vars + - // ALIASES - `{{if gt .Aliases 0}} +{{end}}` -Aliases: -{{.NameAndAliases}}{{end}}` + + // SectionExamples is the help template section that displays command examples. + SectionExamples = `{{if .HasExample}}Examples: +{{trimRight .Example}} - // EXAMPLES - `{{if .HasExample}} +{{end}}` -Examples: -{{ .Example}}{{end}}` + + // SectionSubcommands is the help template section that displays the command's subcommands. + SectionSubcommands = `{{if .HasAvailableSubCommands}}{{cmdGroupsString .}} - // SUBCOMMANDS - `{{ if .HasAvailableSubCommands}} -{{range cmdGroups . .Commands}} -{{.Message}} -{{range .Commands}}{{if .Runnable}} {{rpad .Name .NamePadding }} {{.Short}} -{{end}}{{end}}{{end}}{{end}}` + +{{end}}` - // VISIBLE FLAGS - `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}} + // SectionFlags is the help template section that displays the command's flags. + SectionFlags = `{{ if or $visibleFlags.HasFlags $explicitlyExposedFlags.HasFlags}}Options: +{{ if $visibleFlags.HasFlags}}{{trimRight (flagsUsages $visibleFlags)}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{trimRight (flagsUsages $explicitlyExposedFlags)}}{{end}} -Options: -{{ if $visibleFlags.HasFlags}}{{flagsUsages $visibleFlags}}{{end}}{{ if $explicitlyExposedFlags.HasFlags}}{{flagsUsages $explicitlyExposedFlags}}{{end}}{{end}}` + +{{end}}` - // USAGE LINE - `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}} -Usage: + // SectionUsage is the help template section that displays the command's usage. + SectionUsage = `{{if and .Runnable (ne .UseLine "") (ne .UseLine $rootCmd)}}Usage: {{$usageLine}} -{{end}}` + - // TIPS: --help - `{{ if .HasSubCommands }} -Use "{{$rootCmd}} --help" for more information about a given command.{{end}}` + +{{end}}` - // TIPS: global options - `{{ if $optionsCmdFor}} -Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands).{{end}}` + // SectionTipsHelp is the help template section that displays the '--help' hint. + SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} --help" for more information about a given command. +{{end}}` - optionsHelpTemplate = `` + // SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global flags. + SectionTipsGlobalOptions = `{{if $optionsCmdFor}}Use "{{$optionsCmdFor}}" for a list of global command-line options (applies to all commands). +{{end}}` +) - optionsUsageTemplate = `{{ if .HasInheritedFlags}}The following options can be passed to any command: +// MainHelpTemplate if the template for 'help' used by most commands. +func MainHelpTemplate() string { + return `{{with or .Long .Short }}{{. | trim}}{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` +} + +// MainUsageTemplate if the template for 'usage' used by most commands. +func MainUsageTemplate() string { + sections := []string{ + "\n\n", + SectionVars, + SectionAliases, + SectionExamples, + SectionSubcommands, + SectionFlags, + SectionUsage, + SectionTipsHelp, + SectionTipsGlobalOptions, + } + return strings.TrimRightFunc(strings.Join(sections, ""), unicode.IsSpace) +} + +// OptionsHelpTemplate if the template for 'help' used by the 'options' command. +func OptionsHelpTemplate() string { + return "" +} + +// OptionsUsageTemplate if the template for 'usage' used by the 'options' command. +func OptionsUsageTemplate() string { + return `{{ if .HasInheritedFlags}}The following options can be passed to any command: {{flagsUsages .InheritedFlags}}{{end}}` -) +}