coder/cli/usage.go

141 lines
3.4 KiB
Go

package cli
import (
_ "embed"
"fmt"
"io"
"sort"
"strings"
"text/template"
"github.com/mitchellh/go-wordwrap"
"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/cliui"
)
//go:embed usage.tpl
var usageTemplateRaw string
type optionGroup struct {
Name string
Description string
Options clibase.OptionSet
}
const envPrefix = "CODER_"
var usageTemplate = template.Must(
template.New("usage").Funcs(
template.FuncMap{
"wordWrap": func(s string, width uint) string {
return wordwrap.WrapString(s, width)
},
"trimNewline": func(s string) string {
return strings.TrimSuffix(s, "\n")
},
"indent": func(s string, tabs int) string {
var sb strings.Builder
for _, line := range strings.Split(s, "\n") {
// Remove existing indent, if any.
_, _ = sb.WriteString(strings.Repeat("\t", tabs))
_, _ = sb.WriteString(line)
_, _ = sb.WriteString("\n")
}
return sb.String()
},
"envName": func(opt clibase.Option) string {
if opt.Env == "" {
return ""
}
return envPrefix + opt.Env
},
"flagName": func(opt clibase.Option) string {
return opt.Flag
},
"prettyHeader": func(s string) string {
return cliui.Styles.Bold.Render(s)
},
"isEnterprise": func(opt clibase.Option) bool {
return opt.Annotations.IsSet("enterprise")
},
"isDeprecated": func(opt clibase.Option) bool {
return len(opt.UseInstead) > 0
},
"formatGroupDescription": func(s string) string {
s = strings.ReplaceAll(s, "\n", "")
s = "\n" + s + "\n"
s = wordwrap.WrapString(s, 60)
return s
},
"optionGroups": func(cmd *clibase.Cmd) []optionGroup {
groups := []optionGroup{{
// Default group.
Name: "",
Description: "",
}}
enterpriseGroup := optionGroup{
Name: "Enterprise",
Description: `These options are only available in the Enterprise Edition.`,
}
// Sort options lexicographically.
sort.Slice(cmd.Options, func(i, j int) bool {
return cmd.Options[i].Name < cmd.Options[j].Name
})
optionLoop:
for _, opt := range cmd.Options {
if opt.Hidden {
continue
}
// Enterprise options are always grouped separately.
if opt.Annotations.IsSet("enterprise") {
enterpriseGroup.Options = append(enterpriseGroup.Options, opt)
continue
}
if len(opt.Group.Ancestry()) == 0 {
// Just add option to default group.
groups[0].Options = append(groups[0].Options, opt)
continue
}
groupName := opt.Group.FullName()
for i, foundGroup := range groups {
if foundGroup.Name != groupName {
continue
}
groups[i].Options = append(groups[i].Options, opt)
continue optionLoop
}
groups = append(groups, optionGroup{
Name: groupName,
Description: opt.Group.Description,
Options: clibase.OptionSet{opt},
})
}
sort.Slice(groups, func(i, j int) bool {
// Sort groups lexicographically.
return groups[i].Name < groups[j].Name
})
// Always show enterprise group last.
return append(groups, enterpriseGroup)
},
},
).Parse(usageTemplateRaw),
)
// usageFn returns a function that generates usage (help)
// output for a given command.
func usageFn(output io.Writer, cmd *clibase.Cmd) func() {
return func() {
err := usageTemplate.Execute(output, cmd)
if err != nil {
_, _ = fmt.Fprintf(output, "execute template: %v", err)
}
}
}