diff --git a/go.mod b/go.mod index e06ab3c3e..5a842fada 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.0.0-20220324210930-df53a95c65aa k8s.io/apimachinery v0.0.0-20220324210734-b68ae5efb0e8 - k8s.io/cli-runtime v0.0.0-20220324213237-c15667ffec05 + k8s.io/cli-runtime v0.0.0-20220325012647-524d19e1c796 k8s.io/client-go v0.0.0-20220324211221-af0252eed9c0 k8s.io/component-base v0.0.0-20220324211736-57e5b231b63e k8s.io/component-helpers v0.0.0-20220324211837-3d0aa6c934c2 @@ -50,7 +50,7 @@ require ( replace ( k8s.io/api => k8s.io/api v0.0.0-20220324210930-df53a95c65aa k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20220324210734-b68ae5efb0e8 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20220324213237-c15667ffec05 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20220325012647-524d19e1c796 k8s.io/client-go => k8s.io/client-go v0.0.0-20220324211221-af0252eed9c0 k8s.io/code-generator => k8s.io/code-generator v0.0.0-20220324210542-671bd3e7b101 k8s.io/component-base => k8s.io/component-base v0.0.0-20220324211736-57e5b231b63e diff --git a/go.sum b/go.sum index f3982bb99..21df7ebd7 100644 --- a/go.sum +++ b/go.sum @@ -852,8 +852,8 @@ k8s.io/api v0.0.0-20220324210930-df53a95c65aa h1:NzCktMsy22Y/ddoBN/kf96kFaklITIH k8s.io/api v0.0.0-20220324210930-df53a95c65aa/go.mod h1:Ea6ReZGsqdWOZc66t7sHO58lTcUGyQcMJ3UfsqOnKfM= k8s.io/apimachinery v0.0.0-20220324210734-b68ae5efb0e8 h1:LULUz36bFy2WqAjJVZAbn4yVJ92Xn7oBaUCF6BR8HDk= k8s.io/apimachinery v0.0.0-20220324210734-b68ae5efb0e8/go.mod h1:V4ECjDypP1xQpnL3N9yFzlbGZgd8tLKouJnRyAn/Zyw= -k8s.io/cli-runtime v0.0.0-20220324213237-c15667ffec05 h1:XElf3z4AiZ6ZBDGs+hR/xTWH+4QRXbIMzL2dwaTmV54= -k8s.io/cli-runtime v0.0.0-20220324213237-c15667ffec05/go.mod h1:O2DkSiS1T4zJvcVaGWoTeMtiuEeAuKgE0vJ5yNMJJRo= +k8s.io/cli-runtime v0.0.0-20220325012647-524d19e1c796 h1:Y7FlIv0av0nqBIfnCIPkqqRw4absoKoxg0rFQlC4k2w= +k8s.io/cli-runtime v0.0.0-20220325012647-524d19e1c796/go.mod h1:O2DkSiS1T4zJvcVaGWoTeMtiuEeAuKgE0vJ5yNMJJRo= k8s.io/client-go v0.0.0-20220324211221-af0252eed9c0 h1:1CEitsCQXwuKQ22HYRYCgZPi0+3RXCQsiQlOAkkGAAI= k8s.io/client-go v0.0.0-20220324211221-af0252eed9c0/go.mod h1:sVLZTMIBHUCD7MFbyyUPejZNIlOLYZ2aZcZuP0z1aI0= k8s.io/code-generator v0.0.0-20220324210542-671bd3e7b101/go.mod h1:PF9AME2o5S390iHxUI2/febDrY7GRb+AECnBzIWxIO0= diff --git a/pkg/cmd/apiresources/apiresources.go b/pkg/cmd/apiresources/apiresources.go index 94ce9d32e..5a099c7a4 100644 --- a/pkg/cmd/apiresources/apiresources.go +++ b/pkg/cmd/apiresources/apiresources.go @@ -105,7 +105,7 @@ func NewCmdAPIResources(f cmdutil.Factory, ioStreams genericclioptions.IOStreams } cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).") - cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "Output format. One of: wide|name.") + cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`) cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.") cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.") diff --git a/pkg/cmd/apply/apply_view_last_applied.go b/pkg/cmd/apply/apply_view_last_applied.go index c0606f408..64b0959d3 100644 --- a/pkg/cmd/apply/apply_view_last_applied.go +++ b/pkg/cmd/apply/apply_view_last_applied.go @@ -85,7 +85,7 @@ func NewCmdApplyViewLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I }, } - cmd.Flags().StringVarP(&options.OutputFormat, "output", "o", options.OutputFormat, "Output format. Must be one of yaml|json") + cmd.Flags().StringVarP(&options.OutputFormat, "output", "o", options.OutputFormat, `Output format. Must be one of (yaml, json)`) cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all resources in the namespace of the specified resource types") usage := "that contains the last-applied-configuration annotations" cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) diff --git a/pkg/cmd/config/get_contexts.go b/pkg/cmd/config/get_contexts.go index a8fdb7538..033a179ae 100644 --- a/pkg/cmd/config/get_contexts.go +++ b/pkg/cmd/config/get_contexts.go @@ -80,7 +80,7 @@ func NewCmdConfigGetContexts(streams genericclioptions.IOStreams, configAccess c } cmd.Flags().Bool("no-headers", false, "When using the default or custom-column output format, don't print headers (default print headers).") - cmd.Flags().StringP("output", "o", "", "Output format. One of: name") + cmd.Flags().StringP("output", "o", "", `Output format. One of: (name).`) return cmd } diff --git a/pkg/cmd/get/get_flags.go b/pkg/cmd/get/get_flags.go index b53abf17b..bbe2e6667 100644 --- a/pkg/cmd/get/get_flags.go +++ b/pkg/cmd/get/get_flags.go @@ -162,7 +162,7 @@ func (f *PrintFlags) AddFlags(cmd *cobra.Command) { f.CustomColumnsFlags.AddFlags(cmd) if f.OutputFormat != nil { - cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: %s See custom columns [https://kubernetes.io/docs/reference/kubectl/overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [https://kubernetes.io/docs/reference/kubectl/jsonpath/].", strings.Join(f.AllowedFormats(), "|"))) + cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf(`Output format. One of: (%s). See custom columns [https://kubernetes.io/docs/reference/kubectl/overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [https://kubernetes.io/docs/reference/kubectl/jsonpath/].`, strings.Join(f.AllowedFormats(), ", "))) util.CheckErr(cmd.RegisterFlagCompletionFunc( "output", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/pkg/util/templates/help_flags_printer.go b/pkg/util/templates/help_flags_printer.go new file mode 100644 index 000000000..fdfdf08ee --- /dev/null +++ b/pkg/util/templates/help_flags_printer.go @@ -0,0 +1,76 @@ +/* +Copyright 2022 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 ( + "bytes" + "fmt" + "io" + "strings" + + "github.com/mitchellh/go-wordwrap" + flag "github.com/spf13/pflag" +) + +const offset = 10 + +// HelpFlagPrinter is a printer that +// processes the help flag and print +// it to i/o writer +type HelpFlagPrinter struct { + wrapLimit uint + out io.Writer +} + +// NewHelpFlagPrinter will initialize a HelpFlagPrinter given the +// i/o writer +func NewHelpFlagPrinter(out io.Writer, wrapLimit uint) *HelpFlagPrinter { + return &HelpFlagPrinter{ + wrapLimit: wrapLimit, + out: out, + } +} + +// PrintHelpFlag will beautify the help flags and print it out to p.out +func (p *HelpFlagPrinter) PrintHelpFlag(flag *flag.Flag) { + formatBuf := new(bytes.Buffer) + writeFlag(formatBuf, flag) + + wrappedStr := formatBuf.String() + flagAndUsage := strings.Split(formatBuf.String(), "\n") + flagStr := flagAndUsage[0] + + // if the flag usage is longer than one line, wrap it again + if len(flagAndUsage) > 1 { + nextLines := strings.Join(flagAndUsage[1:], " ") + wrappedUsages := wordwrap.WrapString(nextLines, p.wrapLimit-offset) + wrappedStr = flagStr + "\n" + wrappedUsages + } + appendTabStr := strings.ReplaceAll(wrappedStr, "\n", "\n\t") + + fmt.Fprintf(p.out, appendTabStr+"\n\n") +} + +// writeFlag will output the help flag based +// on the format provided by getFlagFormat to i/o writer +func writeFlag(out io.Writer, f *flag.Flag) { + deprecated := "" + if f.Deprecated != "" { + deprecated = fmt.Sprintf(" (DEPRECATED: %s)", f.Deprecated) + } + fmt.Fprintf(out, getFlagFormat(f), f.Shorthand, f.Name, f.DefValue, f.Usage, deprecated) +} diff --git a/pkg/util/templates/templater.go b/pkg/util/templates/templater.go index b55adc6b9..3ef297c85 100644 --- a/pkg/util/templates/templater.go +++ b/pkg/util/templates/templater.go @@ -23,10 +23,10 @@ import ( "text/template" "unicode" - "k8s.io/kubectl/pkg/util/term" - "github.com/spf13/cobra" flag "github.com/spf13/pflag" + + "k8s.io/kubectl/pkg/util/term" ) type FlagExposer interface { @@ -160,7 +160,7 @@ func (t *templater) cmdGroupsString(c *cobra.Command) string { cmds := []string{cmdGroup.Message} for _, cmd := range cmdGroup.Commands { if cmd.IsAvailableCommand() { - cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short) + cmds = append(cmds, " "+rpad(cmd.Name(), cmd.NamePadding())+" "+cmd.Short) } } groups = append(groups, strings.Join(cmds, "\n")) @@ -218,34 +218,40 @@ func (t *templater) usageLine(c *cobra.Command) string { return usage } -func flagsUsages(f *flag.FlagSet) string { - x := new(bytes.Buffer) +// flagsUsages will print out the kubectl help flags +func flagsUsages(f *flag.FlagSet) (string, error) { + flagBuf := new(bytes.Buffer) + wrapLimit, err := term.GetWordWrapperLimit() + if err != nil { + return "", err + } + printer := NewHelpFlagPrinter(flagBuf, wrapLimit) f.VisitAll(func(flag *flag.Flag) { if flag.Hidden { return } - format := "--%s=%s: %s%s\n" - - if flag.Value.Type() == "string" { - format = "--%s='%s': %s%s\n" - } + printer.PrintHelpFlag(flag) + }) - if len(flag.Shorthand) > 0 { - format = " -%s, " + format - } else { - format = " %s " + format - } + return flagBuf.String(), nil +} - deprecated := "" - if flag.Deprecated != "" { - deprecated = fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated) - } +// getFlagFormat will output the flag format +func getFlagFormat(f *flag.Flag) string { + var format string + format = "--%s=%s:\n%s%s" + if f.Value.Type() == "string" { + format = "--%s='%s':\n%s%s" + } - fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage, deprecated) - }) + if len(f.Shorthand) > 0 { + format = " -%s, " + format + } else { + format = " %s" + format + } - return x.String() + return format } func rpad(s string, padding int) string { diff --git a/pkg/util/term/term_writer.go b/pkg/util/term/term_writer.go index 1733764fc..ea254bb68 100644 --- a/pkg/util/term/term_writer.go +++ b/pkg/util/term/term_writer.go @@ -17,11 +17,14 @@ limitations under the License. package term import ( + "errors" "io" "os" wordwrap "github.com/mitchellh/go-wordwrap" "github.com/moby/term" + + "k8s.io/client-go/tools/remotecommand" ) type wordWrapWriter struct { @@ -51,7 +54,21 @@ func NewResponsiveWriter(w io.Writer) io.Writer { if terminalSize == nil { return w } + limit := getTerminalLimitWidth(terminalSize) + + return NewWordWrapWriter(w, limit) +} + +// NewWordWrapWriter is a Writer that supports a limit of characters on every line +// and does auto word wrapping that respects that limit. +func NewWordWrapWriter(w io.Writer, limit uint) io.Writer { + return &wordWrapWriter{ + limit: limit, + writer: w, + } +} +func getTerminalLimitWidth(terminalSize *remotecommand.TerminalSize) uint { var limit uint switch { case terminalSize.Width >= 120: @@ -61,17 +78,20 @@ func NewResponsiveWriter(w io.Writer) io.Writer { case terminalSize.Width >= 80: limit = 80 } - - return NewWordWrapWriter(w, limit) + return limit } -// NewWordWrapWriter is a Writer that supports a limit of characters on every line -// and does auto word wrapping that respects that limit. -func NewWordWrapWriter(w io.Writer, limit uint) io.Writer { - return &wordWrapWriter{ - limit: limit, - writer: w, +func GetWordWrapperLimit() (uint, error) { + stdout := os.Stdout + fd := stdout.Fd() + if !term.IsTerminal(fd) { + return 0, errors.New("file descriptor is not a terminal") + } + terminalSize := GetSize(fd) + if terminalSize == nil { + return 0, errors.New("terminal size is nil") } + return getTerminalLimitWidth(terminalSize), nil } func (w wordWrapWriter) Write(p []byte) (nn int, err error) {