fix: customised usage messages

Add functionality to display the default help message when running
spruce without any arguments or when the help flag is used.

Customise the help message for the subcommands.

Additional changes:

- Refactor: move the Runner interface to the internal cmd package
- Fix: Add a summary for each of the subcommands.
- Refactor: Use string builder to replace string literals.
- Perf: Use a switch statement to only create the subcommand that the
  user calls.
This commit is contained in:
Dan Anglin 2023-08-13 17:45:33 +01:00
parent 050748d8cd
commit 90638f5569
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
6 changed files with 118 additions and 35 deletions

View file

@ -89,6 +89,29 @@ go install .
=== Verifying the installation === Verifying the installation
Run `spruce` to verify your installation. You should see the usage printed onto your screen.
[source,console]
----
$ spruce
A tool for building CVs
Usage:
spruce [flags]
spruce [command]
Available Commands:
create create a new CV JSON file
generate generate a PDF file from an existing CV JSON file
version print the application's build information
Flags:
-h, --help
print the help message for spruce
Use "spruce [command] --help" for more information about a command.
----
If you have installed spruce with Mage, you can get the build information to confirm that you have installed the correct version. If you have installed spruce with Mage, you can get the build information to confirm that you have installed the correct version.
[source,console] [source,console]
---- ----

51
internal/cmd/cmd.go Normal file
View file

@ -0,0 +1,51 @@
package cmd
import (
"flag"
"fmt"
"strings"
)
type Runner interface {
Parse([]string) error
Name() string
Run() error
}
func SpruceUsage() {
usage := `A tool for building CVs
Usage:
spruce [flags]
spruce [command]
Available Commands:
create create a new CV JSON file
generate generate a PDF file from an existing CV JSON file
version print the application's build information
Flags:
-h, --help
print the help message for spruce
Use "spruce [command] --help" for more information about a command.
`
fmt.Print(usage)
}
func usageFunc(name, summary string, flagset *flag.FlagSet) func() {
return func() {
var b strings.Builder
w := flag.CommandLine.Output()
fmt.Fprintf(&b, "%s\n\nUsage:\n spruce %s [flags]\n\nFlags:", summary, name)
flagset.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(&b, "\n --%s\n %s\n", f.Name, f.Usage)
})
fmt.Fprint(w, b.String())
}
}

View file

@ -13,6 +13,7 @@ import (
type CreateCommand struct { type CreateCommand struct {
*flag.FlagSet *flag.FlagSet
summary string
firstName string firstName string
lastName string lastName string
jobTitle string jobTitle string
@ -22,12 +23,15 @@ type CreateCommand struct {
func NewCreateCommand() *CreateCommand { func NewCreateCommand() *CreateCommand {
cc := CreateCommand{ cc := CreateCommand{
FlagSet: flag.NewFlagSet("create", flag.ExitOnError), FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
summary: "Create a new CV JSON file.",
} }
cc.StringVar(&cc.firstName, "first-name", "", "specify your first name") cc.StringVar(&cc.filename, "filepath", "cv.json", "specify the path where the CV JSON document should be created.")
cc.StringVar(&cc.lastName, "last-name", "", "specify your last name") cc.StringVar(&cc.firstName, "first-name", "", "specify your first name.")
cc.StringVar(&cc.jobTitle, "job-title", "", "specify your preferred job title") cc.StringVar(&cc.jobTitle, "job-title", "", "specify your current job title.")
cc.StringVar(&cc.filename, "filename", "cv.json", "specify the filename of the created JSON document") cc.StringVar(&cc.lastName, "last-name", "", "specify your last name.")
cc.Usage = usageFunc(cc.Name(), cc.summary, cc.FlagSet)
return &cc return &cc
} }

View file

@ -21,6 +21,7 @@ var templates embed.FS
type GenerateCommand struct { type GenerateCommand struct {
*flag.FlagSet *flag.FlagSet
summary string
input string input string
output string output string
employmentHistory int employmentHistory int
@ -30,12 +31,15 @@ type GenerateCommand struct {
func NewGenerateCommand() *GenerateCommand { func NewGenerateCommand() *GenerateCommand {
gc := GenerateCommand{ gc := GenerateCommand{
FlagSet: flag.NewFlagSet("generate", flag.ExitOnError), FlagSet: flag.NewFlagSet("generate", flag.ExitOnError),
summary: "Generate a PDF file from an existing CV JSON file.",
} }
gc.StringVar(&gc.input, "input", "", "specify the CV JSON file that you want to input to the builder.") gc.StringVar(&gc.input, "input", "", "specify the CV JSON file that you want to input to the builder.")
gc.StringVar(&gc.output, "output", "", "specify the name of the output CV file.") gc.StringVar(&gc.output, "output", "", "specify the name of the output CV file.")
gc.IntVar(&gc.employmentHistory, "employment-history", 10, "show employment history within these number of years.") gc.IntVar(&gc.employmentHistory, "employment-history", 10, "show employment history within these number of years.")
gc.BoolVar(&gc.verbose, "verbose", false, "set to true to enable verbose logging") gc.BoolVar(&gc.verbose, "verbose", false, "set to true to enable verbose logging.")
gc.Usage = usageFunc(gc.Name(), gc.summary, gc.FlagSet)
return &gc return &gc
} }

View file

@ -3,10 +3,12 @@ package cmd
import ( import (
"flag" "flag"
"fmt" "fmt"
"strings"
) )
type VersionCommand struct { type VersionCommand struct {
*flag.FlagSet *flag.FlagSet
summary string
fullVersion bool fullVersion bool
binaryVersion string binaryVersion string
buildTime string buildTime string
@ -21,28 +23,24 @@ func NewVersionCommand(binaryVersion, buildTime, goVersion, gitCommit string) *V
buildTime: buildTime, buildTime: buildTime,
goVersion: goVersion, goVersion: goVersion,
gitCommit: gitCommit, gitCommit: gitCommit,
summary: "Print the application's build information.",
} }
vc.BoolVar(&vc.fullVersion, "full", false, "prints the full version") vc.BoolVar(&vc.fullVersion, "full", false, "prints the full build information")
vc.Usage = usageFunc(vc.Name(), vc.summary, vc.FlagSet)
return &vc return &vc
} }
func (v *VersionCommand) Run() error { func (v *VersionCommand) Run() error {
var version string var b strings.Builder
if v.fullVersion { if v.fullVersion {
fullVersionFmt := `Spruce fmt.Fprintf(&b, "Spruce\n Version: %s\n Git commit: %s\n Go version: %s\n Build date: %s\n", v.binaryVersion, v.gitCommit, v.goVersion, v.buildTime)
Version: %s
Git commit: %s
Go version: %s
Build date: %s
`
version = fmt.Sprintf(fullVersionFmt, v.binaryVersion, v.gitCommit, v.goVersion, v.buildTime)
} else { } else {
version = v.binaryVersion + "\n" fmt.Fprintln(&b, v.binaryVersion)
} }
fmt.Print(version) fmt.Print(b.String())
return nil return nil
} }

39
main.go
View file

@ -8,12 +8,6 @@ import (
"codeflow.dananglin.me.uk/apollo/spruce/internal/cmd" "codeflow.dananglin.me.uk/apollo/spruce/internal/cmd"
) )
type Runner interface {
Parse([]string) error
Name() string
Run() error
}
var ( var (
binaryVersion string binaryVersion string
buildTime string buildTime string
@ -22,6 +16,13 @@ var (
) )
func main() { func main() {
args := os.Args[1:]
if len(args) < 1 || args[0] == "--help" || args[0] == "-h" || args[0] == "help" || args[0] == "-help" {
cmd.SpruceUsage()
os.Exit(0)
}
logOptions := slog.HandlerOptions{ logOptions := slog.HandlerOptions{
AddSource: false, AddSource: false,
} }
@ -30,23 +31,25 @@ func main() {
slog.SetDefault(logger) slog.SetDefault(logger)
commandMap := map[string]Runner{ subcommand := args[0]
"version": cmd.NewVersionCommand(
var runner cmd.Runner
switch subcommand {
case "version":
runner = cmd.NewVersionCommand(
binaryVersion, binaryVersion,
buildTime, buildTime,
goVersion, goVersion,
gitCommit, gitCommit,
), )
"generate": cmd.NewGenerateCommand(), case "generate":
"create": cmd.NewCreateCommand(), runner = cmd.NewGenerateCommand()
} case "create":
runner = cmd.NewCreateCommand()
subcommand := os.Args[1] default:
runner, ok := commandMap[subcommand]
if !ok {
slog.Error("unknown subcommand", "subcommand", subcommand) slog.Error("unknown subcommand", "subcommand", subcommand)
cmd.SpruceUsage()
os.Exit(1) os.Exit(1)
} }