|
| 1 | +// Copyright 2025 The etcd Authors |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +// copied from https://github.com/rkt/rkt/blob/master/rkt/help.go |
| 16 | + |
| 17 | +package util |
| 18 | + |
| 19 | +import ( |
| 20 | + "fmt" |
| 21 | + "io" |
| 22 | + "os" |
| 23 | + "strings" |
| 24 | + "text/tabwriter" |
| 25 | + "text/template" |
| 26 | + |
| 27 | + "github.com/spf13/cobra" |
| 28 | + "github.com/spf13/pflag" |
| 29 | +) |
| 30 | + |
| 31 | +var ( |
| 32 | + commandUsageTemplate *template.Template |
| 33 | + templFuncs = template.FuncMap{ |
| 34 | + "descToLines": func(s string) []string { |
| 35 | + // trim leading/trailing whitespace and split into slice of lines |
| 36 | + return strings.Split(strings.Trim(s, "\n\t "), "\n") |
| 37 | + }, |
| 38 | + "cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) string { |
| 39 | + parts := []string{cmd.Name()} |
| 40 | + for cmd.HasParent() && cmd.Parent().Name() != startCmd.Name() { |
| 41 | + cmd = cmd.Parent() |
| 42 | + parts = append([]string{cmd.Name()}, parts...) |
| 43 | + } |
| 44 | + return strings.Join(parts, " ") |
| 45 | + }, |
| 46 | + "indent": func(s string) string { |
| 47 | + pad := strings.Repeat(" ", 2) |
| 48 | + return pad + strings.Replace(s, "\n", "\n"+pad, -1) |
| 49 | + }, |
| 50 | + } |
| 51 | +) |
| 52 | + |
| 53 | +func init() { |
| 54 | + commandUsage := ` |
| 55 | +{{ $cmd := .Cmd }}\ |
| 56 | +{{ $cmdname := cmdName .Cmd .Cmd.Root }}\ |
| 57 | +NAME: |
| 58 | +{{if not .Cmd.HasParent}}\ |
| 59 | +{{printf "%s - %s" .Cmd.Name .Cmd.Short | indent}} |
| 60 | +{{else}}\ |
| 61 | +{{printf "%s - %s" $cmdname .Cmd.Short | indent}} |
| 62 | +{{end}}\ |
| 63 | +
|
| 64 | +USAGE: |
| 65 | +{{printf "%s" .Cmd.UseLine | indent}} |
| 66 | +{{ if not .Cmd.HasParent }}\ |
| 67 | +
|
| 68 | +VERSION: |
| 69 | +{{printf "%s" .Version | indent}} |
| 70 | +{{end}}\ |
| 71 | +{{if .Cmd.HasSubCommands}}\ |
| 72 | +
|
| 73 | +API VERSION: |
| 74 | +{{.APIVersion | indent}} |
| 75 | +{{end}}\ |
| 76 | +{{if .Cmd.HasExample}}\ |
| 77 | +
|
| 78 | +Examples: |
| 79 | +{{.Cmd.Example}} |
| 80 | +{{end}}\ |
| 81 | +{{if .Cmd.HasSubCommands}}\ |
| 82 | +
|
| 83 | +COMMANDS: |
| 84 | +{{range .SubCommands}}\ |
| 85 | +{{ $cmdname := cmdName . $cmd }}\ |
| 86 | +{{ if .Runnable }}\ |
| 87 | +{{printf "%s\t%s" $cmdname .Short | indent}} |
| 88 | +{{end}}\ |
| 89 | +{{end}}\ |
| 90 | +{{end}}\ |
| 91 | +{{ if .Cmd.Long }}\ |
| 92 | +
|
| 93 | +DESCRIPTION: |
| 94 | +{{range $line := descToLines .Cmd.Long}}{{printf "%s" $line | indent}} |
| 95 | +{{end}}\ |
| 96 | +{{end}}\ |
| 97 | +{{if .Cmd.HasLocalFlags}}\ |
| 98 | +
|
| 99 | +OPTIONS: |
| 100 | +{{.LocalFlags}}\ |
| 101 | +{{end}}\ |
| 102 | +{{if .Cmd.HasInheritedFlags}}\ |
| 103 | +
|
| 104 | +GLOBAL OPTIONS: |
| 105 | +{{.GlobalFlags}}\ |
| 106 | +{{end}} |
| 107 | +`[1:] |
| 108 | + |
| 109 | + commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.ReplaceAll(commandUsage, "\\\n", ""))) |
| 110 | +} |
| 111 | + |
| 112 | +func etcdFlagUsages(flagSet *pflag.FlagSet) string { |
| 113 | + x := new(strings.Builder) |
| 114 | + |
| 115 | + flagSet.VisitAll(func(flag *pflag.Flag) { |
| 116 | + if len(flag.Deprecated) > 0 { |
| 117 | + return |
| 118 | + } |
| 119 | + var format string |
| 120 | + if len(flag.Shorthand) > 0 { |
| 121 | + format = " -%s, --%s" |
| 122 | + } else { |
| 123 | + format = " %s --%s" |
| 124 | + } |
| 125 | + if len(flag.NoOptDefVal) > 0 { |
| 126 | + format = format + "[" |
| 127 | + } |
| 128 | + if flag.Value.Type() == "string" { |
| 129 | + // put quotes on the value |
| 130 | + format = format + "=%q" |
| 131 | + } else { |
| 132 | + format = format + "=%s" |
| 133 | + } |
| 134 | + if len(flag.NoOptDefVal) > 0 { |
| 135 | + format = format + "]" |
| 136 | + } |
| 137 | + format = format + "\t%s\n" |
| 138 | + shorthand := flag.Shorthand |
| 139 | + fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, flag.Usage) |
| 140 | + }) |
| 141 | + |
| 142 | + return x.String() |
| 143 | +} |
| 144 | + |
| 145 | +func getSubCommands(cmd *cobra.Command) []*cobra.Command { |
| 146 | + var subCommands []*cobra.Command |
| 147 | + for _, subCmd := range cmd.Commands() { |
| 148 | + subCommands = append(subCommands, subCmd) |
| 149 | + subCommands = append(subCommands, getSubCommands(subCmd)...) |
| 150 | + } |
| 151 | + return subCommands |
| 152 | +} |
| 153 | + |
| 154 | +func UsageFunc(cmd *cobra.Command, version, APIVersion string) error { |
| 155 | + subCommands := getSubCommands(cmd) |
| 156 | + tabOut := getTabOutWithWriter(os.Stdout) |
| 157 | + commandUsageTemplate.Execute(tabOut, struct { |
| 158 | + Cmd *cobra.Command |
| 159 | + LocalFlags string |
| 160 | + GlobalFlags string |
| 161 | + SubCommands []*cobra.Command |
| 162 | + Version string |
| 163 | + APIVersion string |
| 164 | + }{ |
| 165 | + cmd, |
| 166 | + etcdFlagUsages(cmd.LocalFlags()), |
| 167 | + etcdFlagUsages(cmd.InheritedFlags()), |
| 168 | + subCommands, |
| 169 | + version, |
| 170 | + APIVersion, |
| 171 | + }) |
| 172 | + tabOut.Flush() |
| 173 | + return nil |
| 174 | +} |
| 175 | + |
| 176 | +func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer { |
| 177 | + aTabOut := new(tabwriter.Writer) |
| 178 | + aTabOut.Init(writer, 0, 8, 1, '\t', 0) |
| 179 | + return aTabOut |
| 180 | +} |
0 commit comments