Skip to content

Commit 01819a1

Browse files
committed
feat(Command): add status for the helptext
1 parent f42cf82 commit 01819a1

File tree

2 files changed

+103
-20
lines changed

2 files changed

+103
-20
lines changed

cli/helptext.go

+87-20
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,20 @@ const (
2828
)
2929

3030
type helpFields struct {
31-
Indent string
32-
Usage string
33-
Path string
34-
Tagline string
35-
Arguments string
36-
Options string
37-
Synopsis string
38-
Subcommands string
39-
Description string
40-
MoreHelp bool
31+
Indent string
32+
Warning string
33+
Usage string
34+
Path string
35+
Tagline string
36+
Arguments string
37+
Options string
38+
Synopsis string
39+
Subcommands string
40+
ExperimentalSubcommands string
41+
DeprecatedSubcommands string
42+
RemovedSubcommands string
43+
Description string
44+
MoreHelp bool
4145
}
4246

4347
// TrimNewlines removes extra newlines from fields. This makes aligning
@@ -50,12 +54,16 @@ type helpFields struct {
5054
// `
5155
func (f *helpFields) TrimNewlines() {
5256
f.Path = strings.Trim(f.Path, "\n")
57+
f.Warning = strings.Trim(f.Warning, "\n")
5358
f.Usage = strings.Trim(f.Usage, "\n")
5459
f.Tagline = strings.Trim(f.Tagline, "\n")
5560
f.Arguments = strings.Trim(f.Arguments, "\n")
5661
f.Options = strings.Trim(f.Options, "\n")
5762
f.Synopsis = strings.Trim(f.Synopsis, "\n")
5863
f.Subcommands = strings.Trim(f.Subcommands, "\n")
64+
f.ExperimentalSubcommands = strings.Trim(f.ExperimentalSubcommands, "\n")
65+
f.DeprecatedSubcommands = strings.Trim(f.DeprecatedSubcommands, "\n")
66+
f.RemovedSubcommands = strings.Trim(f.RemovedSubcommands, "\n")
5967
f.Description = strings.Trim(f.Description, "\n")
6068
}
6169

@@ -68,15 +76,21 @@ func (f *helpFields) IndentAll() {
6876
return indentString(s, indentStr)
6977
}
7078

79+
f.Warning = indent(f.Warning)
7180
f.Usage = indent(f.Usage)
7281
f.Arguments = indent(f.Arguments)
7382
f.Options = indent(f.Options)
7483
f.Synopsis = indent(f.Synopsis)
7584
f.Subcommands = indent(f.Subcommands)
85+
f.DeprecatedSubcommands = indent(f.DeprecatedSubcommands)
86+
f.ExperimentalSubcommands = indent(f.ExperimentalSubcommands)
87+
f.RemovedSubcommands = indent(f.RemovedSubcommands)
7688
f.Description = indent(f.Description)
7789
}
7890

79-
const longHelpFormat = `USAGE
91+
const longHelpFormat = `{{if .Warning}}WARNING: {{.Warning}}
92+
93+
{{end}}USAGE
8094
{{.Usage}}
8195
8296
{{if .Synopsis}}SYNOPSIS
@@ -99,9 +113,21 @@ const longHelpFormat = `USAGE
99113
100114
{{.Indent}}For more information about each command, use:
101115
{{.Indent}}'{{.Path}} <subcmd> --help'
116+
117+
{{end}}{{if .ExperimentalSubcommands}}EXPERIMENTAL SUBCOMMANDS
118+
{{.ExperimentalSubcommands}}
119+
120+
{{end}}{{if .DeprecatedSubcommands}}DEPRECATED SUBCOMMANDS
121+
{{.DeprecatedSubcommands}}
122+
123+
{{end}}{{if .RemovedSubcommands}}REMOVED SUBCOMMANDS
124+
{{.RemovedSubcommands}}
125+
102126
{{end}}
103127
`
104-
const shortHelpFormat = `USAGE
128+
const shortHelpFormat = `{{if .Warning}}WARNING: {{.Warning}}
129+
130+
{{end}}USAGE
105131
{{.Usage}}
106132
{{if .Synopsis}}
107133
{{.Synopsis}}
@@ -113,6 +139,16 @@ SUBCOMMANDS
113139
{{end}}{{if .MoreHelp}}
114140
{{.Indent}}For more information about each command, use:
115141
{{.Indent}}'{{.Path}} <subcmd> --help'
142+
143+
{{end}}{{if .ExperimentalSubcommands}}EXPERIMENTAL SUBCOMMANDS
144+
{{.ExperimentalSubcommands}}
145+
146+
{{end}}{{if .DeprecatedSubcommands}}DEPRECATED SUBCOMMANDS
147+
{{.DeprecatedSubcommands}}
148+
149+
{{end}}{{if .RemovedSubcommands}}REMOVED SUBCOMMANDS
150+
{{.RemovedSubcommands}}
151+
116152
{{end}}
117153
`
118154

@@ -188,6 +224,7 @@ func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer)
188224
}
189225

190226
// autogen fields that are empty
227+
fields.Warning = generateWarningText(cmd)
191228
if len(cmd.Helptext.Usage) > 0 {
192229
fields.Usage = cmd.Helptext.Usage
193230
} else {
@@ -200,7 +237,10 @@ func LongHelp(rootName string, root *cmds.Command, path []string, out io.Writer)
200237
fields.Options = strings.Join(optionText(width, cmd), "\n")
201238
}
202239
if len(fields.Subcommands) == 0 {
203-
fields.Subcommands = strings.Join(subcommandText(width, cmd, rootName, path), "\n")
240+
fields.Subcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Active), "\n")
241+
fields.ExperimentalSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Experimental), "\n")
242+
fields.DeprecatedSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Deprecated), "\n")
243+
fields.RemovedSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Removed), "\n")
204244
}
205245
if len(fields.Synopsis) == 0 {
206246
fields.Synopsis = generateSynopsis(width, cmd, pathStr)
@@ -245,13 +285,17 @@ func ShortHelp(rootName string, root *cmds.Command, path []string, out io.Writer
245285
width := getTerminalWidth(out) - len(indentStr)
246286

247287
// autogen fields that are empty
288+
fields.Warning = generateWarningText(cmd)
248289
if len(cmd.Helptext.Usage) > 0 {
249290
fields.Usage = cmd.Helptext.Usage
250291
} else {
251292
fields.Usage = commandUsageText(width, cmd, rootName, path)
252293
}
253294
if len(fields.Subcommands) == 0 {
254-
fields.Subcommands = strings.Join(subcommandText(width, cmd, rootName, path), "\n")
295+
fields.Subcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Active), "\n")
296+
fields.ExperimentalSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Experimental), "\n")
297+
fields.DeprecatedSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Deprecated), "\n")
298+
fields.RemovedSubcommands = strings.Join(subcommandText(width, cmd, rootName, path, cmds.Removed), "\n")
255299
}
256300
if len(fields.Synopsis) == 0 {
257301
fields.Synopsis = generateSynopsis(width, cmd, pathStr)
@@ -409,24 +453,28 @@ func optionText(width int, cmd ...*cmds.Command) []string {
409453
return lines
410454
}
411455

412-
func subcommandText(width int, cmd *cmds.Command, rootName string, path []string) []string {
456+
func subcommandText(width int, cmd *cmds.Command, rootName string, path []string, status cmds.Status) []string {
413457
prefix := fmt.Sprintf("%v %v", rootName, strings.Join(path, " "))
414458
if len(path) > 0 {
415459
prefix += " "
416460
}
417461

462+
subCmds := make(map[string]*cmds.Command, len(cmd.Subcommands))
418463
// Sorting fixes changing order bug #2981.
419464
sortedNames := make([]string, 0)
420-
for name := range cmd.Subcommands {
421-
sortedNames = append(sortedNames, name)
465+
for name, c := range cmd.Subcommands {
466+
if c.Status == status {
467+
sortedNames = append(sortedNames, name)
468+
subCmds[name] = c
469+
}
422470
}
423471
sort.Strings(sortedNames)
424472

425-
subcmds := make([]*cmds.Command, len(cmd.Subcommands))
426-
lines := make([]string, len(cmd.Subcommands))
473+
subcmds := make([]*cmds.Command, len(subCmds))
474+
lines := make([]string, len(subCmds))
427475

428476
for i, name := range sortedNames {
429-
sub := cmd.Subcommands[name]
477+
sub := subCmds[name]
430478
usage := usageText(sub)
431479
if len(usage) > 0 {
432480
usage = " " + usage
@@ -445,6 +493,25 @@ func subcommandText(width int, cmd *cmds.Command, rootName string, path []string
445493
return lines
446494
}
447495

496+
// Text printed after 'Warning: ' tag at the start of the command.
497+
// FIXME: We probably want to print more than its status in caps, maybe what
498+
// is the contract behind it, e.g., 'WARNING: DEPRECATED - This command
499+
// might be removed without prior notice'.
500+
func generateWarningText(cmd *cmds.Command) string {
501+
switch cmd.Status {
502+
case cmds.Active:
503+
return "" // We don't print a warning for a normal active command.
504+
case cmds.Deprecated:
505+
return "DEPRECATED"
506+
case cmds.Experimental:
507+
return "EXPERIMENTAL"
508+
case cmds.Removed:
509+
return "REMOVED"
510+
default:
511+
panic("unknown command status")
512+
}
513+
}
514+
448515
func commandUsageText(width int, cmd *cmds.Command, rootName string, path []string) string {
449516
text := fmt.Sprintf("%v %v", rootName, strings.Join(path, " "))
450517
argUsage := usageText(cmd)

command.go

+16
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,26 @@ type Command struct {
9797
// NoLocal denotes that a command cannot be executed in a local environment
9898
NoLocal bool
9999

100+
// Status of the command showed in the help.
101+
Status Status
102+
100103
// Extra contains a set of other command-specific parameters
101104
Extra *Extra
102105
}
103106

107+
// Status indicates whether this command is active/deprecated/experimental/etc
108+
// which is signaled in the help text produced.
109+
type Status int
110+
111+
const (
112+
// Active command, doesn't produce any special indication.
113+
Active Status = iota
114+
// Other commands will be shown their statuses in the help text.
115+
Experimental
116+
Deprecated
117+
Removed
118+
)
119+
104120
// Extra is a set of tag information for a command
105121
type Extra struct {
106122
m map[interface{}]interface{}

0 commit comments

Comments
 (0)