Skip to content

Commit

Permalink
Merge pull request #39 from evilmarty/usage
Browse files Browse the repository at this point in the history
Fix up usage formatting and include aliases
  • Loading branch information
evilmarty authored Jul 5, 2024
2 parents 16bcaeb + 4dee9de commit 9e6b1e3
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 63 deletions.
2 changes: 2 additions & 0 deletions examples/ilc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ commands:
Los Angeles International Airport: lax
Munich International Airport: muc
calendar:
aliases:
- cal
description: Display a calendar for the month
inputs:
month:
Expand Down
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ var (

func main() {
mainFlagSet.Usage = func() {
u := NewUsage(os.Args[0:1], "ILC", "")
fmt.Printf("%s", u.String())
u := NewUsage(mainFlagSet.Output())
u.Entrypoint = os.Args[0:1]
u.Print()
os.Exit(0)
}
mainFlagSet.BoolFunc("version", "Displays the version", func(_ string) error {
Expand Down
11 changes: 7 additions & 4 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ type Runner struct {
}

func (r *Runner) printUsage(cs CommandSet) {
entrypoint := append([]string{}, r.Entrypoint...)
entrypoint = append(entrypoint, cs.String())
u := NewUsage(entrypoint, "ILC", cs.Description()).ImportCommandSet(cs)
fmt.Fprint(r.Stderr, u.String())
u := NewUsage(mainFlagSet.Output()).ImportCommandSet(cs)
u.Entrypoint = append([]string{}, r.Entrypoint...)
if s := cs.String(); s != "" {
u.Entrypoint = append(u.Entrypoint, s)
}
u.Description = cs.Description()
u.Print()
os.Exit(0)
}

Expand Down
89 changes: 52 additions & 37 deletions usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,44 @@ import (
"fmt"
"io"
"strings"

"github.com/muesli/termenv"
)

type Usage struct {
Title string
Description string
commandNames []string
commandDescs []string
inputNames []string
inputDescs []string
flagNames []string
flagDescs []string
Entrypoint []string
Title string
Description string
commands [][]string
inputs [][]string
flags [][]string
Entrypoint []string
output *termenv.Output
}

func (u Usage) printSection(b io.Writer, header, content string) {
o := u.output
content = strings.ReplaceAll(content, "\n", "\n ")
content = strings.TrimSuffix(content, " ")
fmt.Fprintf(b, "%s\n %s\n", header, content)
fmt.Fprintf(b, "%s\n %s\n",
o.String(header).Bold(),
content,
)
}

func (u Usage) printInstructions(b io.Writer, names, descs []string, header, prefix string) {
var s strings.Builder
func (u Usage) printInstructions(b io.Writer, entries [][]string, header, prefix string) {
// Each entry in entries begins with the description followed by additional names
s := strings.Builder{}
col := 15
for _, name := range names {
for i, entry := range entries {
name := strings.Join(entry[1:], ", ")
col = max(col, len([]rune(name)))
entries[i] = []string{entry[0], name}
}
col = col + 5
format := fmt.Sprintf("%%-%ds %%s\n", col)
for i, name := range names {
desc := descs[i]
name = fmt.Sprintf("%s%s", prefix, name)
for _, entry := range entries {
desc := entry[0]
name := fmt.Sprintf("%s%s", prefix, entry[1])
fmt.Fprintf(&s, format, name, desc)
}
u.printSection(b, header, s.String())
Expand All @@ -46,60 +53,70 @@ func (u Usage) usage() string {
if len(u.Entrypoint) > 0 {
params = append(params, u.Entrypoint[0])
}
if len(u.flagNames) > 0 {
if len(u.flags) > 0 {
params = append(params, "[flags]")
}
if len(u.Entrypoint) > 1 {
params = append(params, u.Entrypoint[1:]...)
} else {
params = append(params, "<config>")
}
if len(u.commandNames) > 0 {
if len(u.commands) > 0 {
params = append(params, "<commands>")
}
if len(u.inputNames) > 0 {
if len(u.inputs) > 0 {
params = append(params, "[inputs]")
}
return strings.Join(params, " ")
}

func (u Usage) String() string {
var b strings.Builder
b := strings.Builder{}
o := u.output
fmt.Fprintf(&b, "\n")
if s := u.Title; s != "" {
fmt.Fprintf(&b, "%s\n\n", s)
fmt.Fprintf(&b, "%s\n\n",
o.String(s).Underline(),
)
}
if s := u.Description; s != "" {
fmt.Fprintf(&b, "%s\n\n", s)
}
if b.Len() > 0 {
fmt.Fprintf(&b, "\n")
}
if s := u.usage(); s != "" {
u.printSection(&b, "USAGE", s)
b.WriteString("\n")
}
if len(u.commandNames) > 0 {
u.printInstructions(&b, u.commandNames, u.commandDescs, "COMMANDS", "")
if len(u.commands) > 0 {
u.printInstructions(&b, u.commands, "COMMANDS", "")
}
if len(u.inputNames) > 0 {
u.printInstructions(&b, u.inputNames, u.inputDescs, "INPUTS", "-")
if len(u.inputs) > 0 {
u.printInstructions(&b, u.inputs, "INPUTS", "-")
}
if len(u.flagNames) > 0 {
u.printInstructions(&b, u.flagNames, u.flagDescs, "FLAGS", "-")
if len(u.flags) > 0 {
u.printInstructions(&b, u.flags, "FLAGS", "-")
}
b.WriteString("\n")
return b.String()
}

func (u Usage) Print() error {
_, err := u.output.WriteString(u.String())
return err
}

func (u Usage) ImportCommands(commands []ConfigCommand) Usage {
for _, command := range commands {
u.commandNames = append(u.commandNames, command.Name)
u.commandDescs = append(u.commandDescs, command.Description)
u.commands = append(u.commands, append([]string{command.Description, command.Name}, command.Aliases...))
}
return u
}

func (u Usage) ImportInputs(inputs []ConfigInput) Usage {
for _, input := range inputs {
u.inputNames = append(u.inputNames, input.Name)
u.inputDescs = append(u.inputDescs, input.Description)
u.inputs = append(u.inputs, []string{input.Description, input.Name})
}
return u
}
Expand All @@ -108,15 +125,13 @@ func (u Usage) ImportCommandSet(cs CommandSet) Usage {
return u.ImportCommands(cs.Subcommands()).ImportInputs(cs.Inputs())
}

func NewUsage(entrypoint []string, title, desc string) Usage {
func NewUsage(tty io.Writer) Usage {
u := Usage{
Title: title,
Description: desc,
Entrypoint: entrypoint,
Title: "ILC",
output: termenv.NewOutput(tty),
}
mainFlagSet.VisitAll(func(f *flag.Flag) {
u.flagNames = append(u.flagNames, f.Name)
u.flagDescs = append(u.flagDescs, f.Usage)
u.flags = append(u.flags, []string{f.Usage, f.Name})
})
return u
}
60 changes: 40 additions & 20 deletions usage_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package main

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestUsage_EmptyCommands(t *testing.T) {
u := usageFixture()
u.commandNames = []string{}
expected := `test
u.commands = [][]string{}
expected := `
test
this is a fixture
USAGE
ilc config.yaml subcommand [inputs]
Expand All @@ -27,16 +30,18 @@ INPUTS

func TestUsage_EmptyInputs(t *testing.T) {
u := usageFixture()
u.inputNames = []string{}
expected := `test
u.inputs = [][]string{}
expected := `
test
this is a fixture
USAGE
ilc config.yaml subcommand <commands>
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
Expand All @@ -47,15 +52,17 @@ COMMANDS
func TestUsage_Entrypoint_Empty(t *testing.T) {
u := usageFixture()
u.Entrypoint = []string{}
expected := `test
expected := `
test
this is a fixture
USAGE
<config> <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -70,15 +77,17 @@ INPUTS
func TestUsage_Entrypoint_One(t *testing.T) {
u := usageFixture()
u.Entrypoint = []string{"ilc"}
expected := `test
expected := `
test
this is a fixture
USAGE
ilc <config> <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -93,15 +102,17 @@ INPUTS
func TestUsage_Entrypoint_Two(t *testing.T) {
u := usageFixture()
u.Entrypoint = []string{"ilc", "config.yaml"}
expected := `test
expected := `
test
this is a fixture
USAGE
ilc config.yaml <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -116,15 +127,17 @@ INPUTS
func TestUsage_Entrypoint_Many(t *testing.T) {
u := usageFixture()
u.Entrypoint = []string{"ilc", "config.yaml", "command", "subcommand"}
expected := `test
expected := `
test
this is a fixture
USAGE
ilc config.yaml command subcommand <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -139,13 +152,15 @@ INPUTS
func TestUsage_Title_Blank(t *testing.T) {
u := usageFixture()
u.Title = ""
expected := `this is a fixture
expected := `
this is a fixture
USAGE
ilc config.yaml subcommand <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -160,13 +175,15 @@ INPUTS
func TestUsage_Description_Blank(t *testing.T) {
u := usageFixture()
u.Description = ""
expected := `test
expected := `
test
USAGE
ilc config.yaml subcommand <commands> [inputs]
COMMANDS
a a subcommand
a, aa a subcommand
b b subcommand
INPUTS
Expand All @@ -180,13 +197,16 @@ INPUTS

func usageFixture() Usage {
commands := []ConfigCommand{
{Name: "a", Description: "a subcommand"},
{Name: "a", Description: "a subcommand", Aliases: []string{"aa"}},
{Name: "b", Description: "b subcommand"},
}
inputs := []ConfigInput{
{Name: "c", Description: "c input"},
{Name: "d", Description: "d input"},
}
entrypoint := []string{"ilc", "config.yaml", "subcommand"}
return NewUsage(entrypoint, "test", "this is a fixture").ImportCommands(commands).ImportInputs(inputs)
u := NewUsage(os.Stdout).ImportCommands(commands).ImportInputs(inputs)
u.Entrypoint = []string{"ilc", "config.yaml", "subcommand"}
u.Title = "test"
u.Description = "this is a fixture"
return u
}

0 comments on commit 9e6b1e3

Please sign in to comment.