Merge pull request #104736 from lauchokyip/improveFlags

Beautify kubectl help flag commands
This commit is contained in:
Kubernetes Prow Robot 2022-03-24 17:33:50 -07:00 committed by GitHub
commit 750cb93e62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 141 additions and 39 deletions

View File

@ -136,7 +136,7 @@ func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
f.TemplatePrinterFlags.AddFlags(cmd) f.TemplatePrinterFlags.AddFlags(cmd)
if f.OutputFormat != nil { if f.OutputFormat != nil {
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: %s.", strings.Join(f.AllowedFormats(), "|"))) cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf(`Output format. One of: (%s).`, strings.Join(f.AllowedFormats(), ", ")))
if f.OutputFlagSpecified == nil { if f.OutputFlagSpecified == nil {
f.OutputFlagSpecified = func() bool { f.OutputFlagSpecified = func() bool {
return cmd.Flag("output").Changed return cmd.Flag("output").Changed

View File

@ -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().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().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.") cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")

View File

@ -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") 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" usage := "that contains the last-applied-configuration annotations"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)

View File

@ -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().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 return cmd
} }

View File

@ -162,7 +162,7 @@ func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
f.CustomColumnsFlags.AddFlags(cmd) f.CustomColumnsFlags.AddFlags(cmd)
if f.OutputFormat != nil { 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( util.CheckErr(cmd.RegisterFlagCompletionFunc(
"output", "output",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {

View File

@ -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)
}

View File

@ -23,10 +23,10 @@ import (
"text/template" "text/template"
"unicode" "unicode"
"k8s.io/kubectl/pkg/util/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
"k8s.io/kubectl/pkg/util/term"
) )
type FlagExposer interface { type FlagExposer interface {
@ -160,7 +160,7 @@ func (t *templater) cmdGroupsString(c *cobra.Command) string {
cmds := []string{cmdGroup.Message} cmds := []string{cmdGroup.Message}
for _, cmd := range cmdGroup.Commands { for _, cmd := range cmdGroup.Commands {
if cmd.IsAvailableCommand() { 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")) groups = append(groups, strings.Join(cmds, "\n"))
@ -218,34 +218,40 @@ func (t *templater) usageLine(c *cobra.Command) string {
return usage return usage
} }
func flagsUsages(f *flag.FlagSet) string { // flagsUsages will print out the kubectl help flags
x := new(bytes.Buffer) 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) { f.VisitAll(func(flag *flag.Flag) {
if flag.Hidden { if flag.Hidden {
return return
} }
format := "--%s=%s: %s%s\n" printer.PrintHelpFlag(flag)
if flag.Value.Type() == "string" {
format = "--%s='%s': %s%s\n"
}
if len(flag.Shorthand) > 0 {
format = " -%s, " + format
} else {
format = " %s " + format
}
deprecated := ""
if flag.Deprecated != "" {
deprecated = fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
}
fmt.Fprintf(x, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage, deprecated)
}) })
return x.String() return flagBuf.String(), nil
}
// 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"
}
if len(f.Shorthand) > 0 {
format = " -%s, " + format
} else {
format = " %s" + format
}
return format
} }
func rpad(s string, padding int) string { func rpad(s string, padding int) string {

View File

@ -17,11 +17,14 @@ limitations under the License.
package term package term
import ( import (
"errors"
"io" "io"
"os" "os"
wordwrap "github.com/mitchellh/go-wordwrap" wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/moby/term" "github.com/moby/term"
"k8s.io/client-go/tools/remotecommand"
) )
type wordWrapWriter struct { type wordWrapWriter struct {
@ -51,16 +54,7 @@ func NewResponsiveWriter(w io.Writer) io.Writer {
if terminalSize == nil { if terminalSize == nil {
return w return w
} }
limit := getTerminalLimitWidth(terminalSize)
var limit uint
switch {
case terminalSize.Width >= 120:
limit = 120
case terminalSize.Width >= 100:
limit = 100
case terminalSize.Width >= 80:
limit = 80
}
return NewWordWrapWriter(w, limit) return NewWordWrapWriter(w, limit)
} }
@ -74,6 +68,32 @@ func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
} }
} }
func getTerminalLimitWidth(terminalSize *remotecommand.TerminalSize) uint {
var limit uint
switch {
case terminalSize.Width >= 120:
limit = 120
case terminalSize.Width >= 100:
limit = 100
case terminalSize.Width >= 80:
limit = 80
}
return limit
}
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) { func (w wordWrapWriter) Write(p []byte) (nn int, err error) {
if w.limit == 0 { if w.limit == 0 {
return w.writer.Write(p) return w.writer.Write(p)