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
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.
[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 {
*flag.FlagSet
summary string
firstName string
lastName string
jobTitle string
@ -22,12 +23,15 @@ type CreateCommand struct {
func NewCreateCommand() *CreateCommand {
cc := CreateCommand{
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.lastName, "last-name", "", "specify your last name")
cc.StringVar(&cc.jobTitle, "job-title", "", "specify your preferred job title")
cc.StringVar(&cc.filename, "filename", "cv.json", "specify the filename of the created JSON document")
cc.StringVar(&cc.filename, "filepath", "cv.json", "specify the path where the CV JSON document should be created.")
cc.StringVar(&cc.firstName, "first-name", "", "specify your first name.")
cc.StringVar(&cc.jobTitle, "job-title", "", "specify your current job title.")
cc.StringVar(&cc.lastName, "last-name", "", "specify your last name.")
cc.Usage = usageFunc(cc.Name(), cc.summary, cc.FlagSet)
return &cc
}

View file

@ -21,6 +21,7 @@ var templates embed.FS
type GenerateCommand struct {
*flag.FlagSet
summary string
input string
output string
employmentHistory int
@ -30,12 +31,15 @@ type GenerateCommand struct {
func NewGenerateCommand() *GenerateCommand {
gc := GenerateCommand{
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.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.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
}

View file

@ -3,10 +3,12 @@ package cmd
import (
"flag"
"fmt"
"strings"
)
type VersionCommand struct {
*flag.FlagSet
summary string
fullVersion bool
binaryVersion string
buildTime string
@ -21,28 +23,24 @@ func NewVersionCommand(binaryVersion, buildTime, goVersion, gitCommit string) *V
buildTime: buildTime,
goVersion: goVersion,
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
}
func (v *VersionCommand) Run() error {
var version string
var b strings.Builder
if v.fullVersion {
fullVersionFmt := `Spruce
Version: %s
Git commit: %s
Go version: %s
Build date: %s
`
version = fmt.Sprintf(fullVersionFmt, v.binaryVersion, v.gitCommit, v.goVersion, v.buildTime)
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)
} else {
version = v.binaryVersion + "\n"
fmt.Fprintln(&b, v.binaryVersion)
}
fmt.Print(version)
fmt.Print(b.String())
return nil
}

39
main.go
View file

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