Skip to content

Commit

Permalink
Introduce a template for usage output
Browse files Browse the repository at this point in the history
This adds a text/template for the generation of the usage line.
  • Loading branch information
daenney committed Oct 12, 2022
1 parent dbc2ba5 commit 4d56a6d
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 25 deletions.
10 changes: 10 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"reflect"
"strings"
"text/template"

scalar "github.com/alexflint/go-scalar"
)
Expand Down Expand Up @@ -127,6 +128,8 @@ type Parser struct {

// the following field changes during processing of command line arguments
lastCmd *command

usageTemplate *template.Template
}

// Versioned is the interface that the destination struct should implement to
Expand Down Expand Up @@ -193,6 +196,13 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
config: config,
}

// build the usage template
tmpl, err := template.New("usage").Parse(usageTemplate)
if err != nil {
return nil, err
}
p.usageTemplate = tmpl

// make a list of roots
for _, dest := range dests {
p.roots = append(p.roots, reflect.ValueOf(dest))
Expand Down
63 changes: 38 additions & 25 deletions usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
)

// the width of the left column
const colWidth = 25

const (
usageTemplate = "{{if ne .Version \"\"}}{{.Version}}\n{{end}}Usage: {{.Usage}}"
helpTemplate = ""
)

// to allow monkey patching in tests
var (
stdout io.Writer = os.Stdout
Expand Down Expand Up @@ -67,8 +73,7 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
return nil
}

// writeUsageForSubcommand writes usage information for the given subcommand
func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
func (p *Parser) buildUsageForSubcommand(cmd *command) string {
var positionals, longOptions, shortOptions []*spec
for _, spec := range cmd.specs {
switch {
Expand All @@ -81,46 +86,43 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
}
}

if p.version != "" {
fmt.Fprintln(w, p.version)
}

// make a list of ancestor commands so that we print with full context
var ancestors []string
ancestor := cmd
for ancestor != nil {
ancestors = append(ancestors, ancestor.name)
ancestor = ancestor.parent
}
sort.SliceStable(ancestors, func(i, j int) bool {
return i > j
})

res := strings.Builder{}
// print the beginning of the usage string
fmt.Fprint(w, "Usage:")
for i := len(ancestors) - 1; i >= 0; i-- {
fmt.Fprint(w, " "+ancestors[i])
}
res.WriteString(strings.Join(ancestors, " "))

// write the option component of the usage message
for _, spec := range shortOptions {
// prefix with a space
fmt.Fprint(w, " ")
res.WriteString(" ")
if !spec.required {
fmt.Fprint(w, "[")
res.WriteString("[")
}
fmt.Fprint(w, synopsis(spec, "-"+spec.short))
res.WriteString(synopsis(spec, "-"+spec.short))
if !spec.required {
fmt.Fprint(w, "]")
res.WriteString("]")
}
}

for _, spec := range longOptions {
// prefix with a space
fmt.Fprint(w, " ")
res.WriteString(" ")
if !spec.required {
fmt.Fprint(w, "[")
res.WriteString("[")
}
fmt.Fprint(w, synopsis(spec, "--"+spec.long))
res.WriteString(synopsis(spec, "--"+spec.long))
if !spec.required {
fmt.Fprint(w, "]")
res.WriteString("]")
}
}

Expand All @@ -138,25 +140,36 @@ func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
// REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]]
var closeBrackets int
for _, spec := range positionals {
fmt.Fprint(w, " ")
res.WriteString(" ")
if !spec.required {
fmt.Fprint(w, "[")
res.WriteString("[")
closeBrackets += 1
}
if spec.cardinality == multiple {
fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
res.WriteString(fmt.Sprintf("%s [%s ...]", spec.placeholder, spec.placeholder))
} else {
fmt.Fprint(w, spec.placeholder)
res.WriteString(spec.placeholder)
}
}
fmt.Fprint(w, strings.Repeat("]", closeBrackets))
res.WriteString(strings.Repeat("]", closeBrackets))

// if the program supports subcommands, give a hint to the user about their existence
if len(cmd.subcommands) > 0 {
fmt.Fprint(w, " <command> [<args>]")
res.WriteString(" <command> [<args>]")
}

fmt.Fprint(w, "\n")
res.WriteString("\n")
return res.String()
}

// writeUsageForSubcommand writes usage information for the given subcommand
func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
type tpldata struct {
Version string
Usage string
}

p.usageTemplate.Execute(w, tpldata{Version: p.version, Usage: p.buildUsageForSubcommand(cmd)})
}

func printTwoCols(w io.Writer, left, help string, defaultVal string, envVal string) {
Expand Down

0 comments on commit 4d56a6d

Please sign in to comment.