fix: use default FlagSet for spruce help message
Use the default FlagSet to parse all the arguments and to set the default help message for spruce. Arguments set after the subcommand are still parsed by the subcommand's FlagSet. The summaries for all subcommand are defined in one place in the main function for consistency. The internal/cmd.SpruceUsage function is replaced with the spruceUsageFunc function in the main package which returns the usage function which is set as the default usage function. The format of the help message for spruce and the subcommands have been updated with the inspiration of the help message from gopass.
This commit is contained in:
parent
8a530551c2
commit
4f90a4c8bb
|
@ -1,13 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/spruce/internal/cmd"
|
||||
)
|
||||
|
||||
const (
|
||||
create string = "create"
|
||||
generate string = "generate"
|
||||
version string = "version"
|
||||
)
|
||||
|
||||
var (
|
||||
binaryVersion string
|
||||
buildTime string
|
||||
|
@ -16,13 +25,24 @@ var (
|
|||
)
|
||||
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
summaries := map[string]string{
|
||||
create: "creates a new CV JSON file",
|
||||
generate: "generates a PDF file from an existing CV JSON file",
|
||||
version: "print the application's version and build information",
|
||||
}
|
||||
|
||||
if len(args) < 1 || args[0] == "--help" || args[0] == "-h" || args[0] == "help" || args[0] == "-help" {
|
||||
cmd.SpruceUsage()
|
||||
flag.Usage = spruceUsageFunc(summaries)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
subcommand := flag.Arg(0)
|
||||
args := flag.Args()[1:]
|
||||
|
||||
logOptions := slog.HandlerOptions{
|
||||
AddSource: false,
|
||||
}
|
||||
|
@ -31,35 +51,70 @@ func main() {
|
|||
|
||||
slog.SetDefault(logger)
|
||||
|
||||
subcommand := args[0]
|
||||
|
||||
var runner cmd.Runner
|
||||
|
||||
switch subcommand {
|
||||
case "version":
|
||||
case create:
|
||||
runner = cmd.NewCreateCommand(create, summaries[create])
|
||||
case generate:
|
||||
runner = cmd.NewGenerateCommand(generate, summaries[generate])
|
||||
case version:
|
||||
runner = cmd.NewVersionCommand(
|
||||
binaryVersion,
|
||||
buildTime,
|
||||
goVersion,
|
||||
gitCommit,
|
||||
version,
|
||||
summaries[version],
|
||||
)
|
||||
case "generate":
|
||||
runner = cmd.NewGenerateCommand()
|
||||
case "create":
|
||||
runner = cmd.NewCreateCommand()
|
||||
default:
|
||||
slog.Error("unknown subcommand", "subcommand", subcommand)
|
||||
cmd.SpruceUsage()
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := runner.Parse(os.Args[2:]); err != nil {
|
||||
if err := runner.Parse(args); err != nil {
|
||||
slog.Error(fmt.Sprintf("unable to parse the command line flags; %v.", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := runner.Run(); err != nil {
|
||||
slog.Error(fmt.Sprintf("unable to run '%s'; %v.", runner.Name(), err))
|
||||
slog.Error(fmt.Sprintf("unable to run %q; %v.", runner.Name(), err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func spruceUsageFunc(summaries map[string]string) func() {
|
||||
cmds := make([]string, len(summaries))
|
||||
ind := 0
|
||||
for k := range summaries {
|
||||
cmds[ind] = k
|
||||
ind++
|
||||
}
|
||||
slices.Sort(cmds)
|
||||
|
||||
return func() {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("SUMMARY:\n spruce - A command-line tool for building CVs\n\n")
|
||||
if binaryVersion != "" {
|
||||
b.WriteString("VERSION:\n " + binaryVersion + "\n\n")
|
||||
}
|
||||
|
||||
b.WriteString("USAGE:\n spruce [flags]\n spruce [command]\n\nCOMMANDS:")
|
||||
|
||||
for _, cmd := range cmds {
|
||||
fmt.Fprintf(&b, "\n %s\t%s", cmd, summaries[cmd])
|
||||
}
|
||||
|
||||
b.WriteString("\n\nFLAGS:\n -help, --help\n print the help message\n")
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
fmt.Fprintf(&b, "\n -%s, --%s\n %s\n", f.Name, f.Name, f.Usage)
|
||||
})
|
||||
|
||||
b.WriteString("\nUse \"spruce [command] --help\" for more information about a command.\n")
|
||||
|
||||
w := flag.CommandLine.Output()
|
||||
fmt.Fprint(w, b.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,40 +12,20 @@ type Runner interface {
|
|||
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)
|
||||
fmt.Fprintf(&b, "SUMMARY:\n %s - %s\n\nUSAGE:\n spruce %s [flags]\n\nFLAGS:", name, summary, name)
|
||||
|
||||
flagset.VisitAll(func(f *flag.Flag) {
|
||||
fmt.Fprintf(&b, "\n --%s\n %s\n", f.Name, f.Usage)
|
||||
fmt.Fprintf(&b, "\n -%s, --%s\n %s", f.Name, f.Name, f.Usage)
|
||||
})
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
w := flag.CommandLine.Output()
|
||||
|
||||
fmt.Fprint(w, b.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,20 +20,20 @@ type CreateCommand struct {
|
|||
filename string
|
||||
}
|
||||
|
||||
func NewCreateCommand() *CreateCommand {
|
||||
cc := CreateCommand{
|
||||
FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
|
||||
summary: "Create a new CV JSON file.",
|
||||
func NewCreateCommand(name, summary string) *CreateCommand {
|
||||
command := CreateCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
summary: summary,
|
||||
}
|
||||
|
||||
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.")
|
||||
command.StringVar(&command.filename, "filepath", "cv.json", "specify the output path of the CV JSON file.")
|
||||
command.StringVar(&command.firstName, "first-name", "", "specify your first name.")
|
||||
command.StringVar(&command.jobTitle, "job-title", "", "specify your current job title.")
|
||||
command.StringVar(&command.lastName, "last-name", "", "specify your last name.")
|
||||
|
||||
cc.Usage = usageFunc(cc.Name(), cc.summary, cc.FlagSet)
|
||||
command.Usage = usageFunc(command.Name(), command.summary, command.FlagSet)
|
||||
|
||||
return &cc
|
||||
return &command
|
||||
}
|
||||
|
||||
func (c *CreateCommand) Run() error {
|
||||
|
|
|
@ -27,28 +27,28 @@ func (e noInputSpecifiedError) Error() string {
|
|||
return "no input file specified, please set the --input field"
|
||||
}
|
||||
|
||||
func NewGenerateCommand() *GenerateCommand {
|
||||
gc := GenerateCommand{
|
||||
FlagSet: flag.NewFlagSet("generate", flag.ExitOnError),
|
||||
summary: "Generate a PDF file from an existing CV JSON file.",
|
||||
func NewGenerateCommand(name, summary string) *GenerateCommand {
|
||||
command := GenerateCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
summary: summary,
|
||||
}
|
||||
|
||||
gc.StringVar(&gc.input, "input", "", "specify the CV JSON file that you want to input to the builder.")
|
||||
gc.StringVar(&gc.output, "output", "cv.pdf", "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.")
|
||||
command.StringVar(&command.input, "input", "", "specify the CV JSON file that you want to input to the builder.")
|
||||
command.StringVar(&command.output, "output", "cv.pdf", "specify the name of the output CV file.")
|
||||
command.IntVar(&command.employmentHistory, "employment-history", 10, "show employment history within these number of years.")
|
||||
command.BoolVar(&command.verbose, "verbose", false, "set to true to enable verbose logging.")
|
||||
|
||||
gc.Usage = usageFunc(gc.Name(), gc.summary, gc.FlagSet)
|
||||
command.Usage = usageFunc(command.Name(), command.summary, command.FlagSet)
|
||||
|
||||
return &gc
|
||||
return &command
|
||||
}
|
||||
|
||||
func (g *GenerateCommand) Run() error {
|
||||
if g.input == "" {
|
||||
func (c *GenerateCommand) Run() error {
|
||||
if c.input == "" {
|
||||
return noInputSpecifiedError{}
|
||||
}
|
||||
|
||||
historyLimit := time.Now().AddDate(-1*g.employmentHistory, 0, 0)
|
||||
historyLimit := time.Now().AddDate(-1*c.employmentHistory, 0, 0)
|
||||
|
||||
tempDir, err := os.MkdirTemp("/tmp", "cv-builder-")
|
||||
if err != nil {
|
||||
|
@ -62,13 +62,13 @@ func (g *GenerateCommand) Run() error {
|
|||
}
|
||||
}()
|
||||
|
||||
pdfFileName, err := pdf.Generate(tempDir, g.input, historyLimit, g.verbose)
|
||||
pdfFileName, err := pdf.Generate(tempDir, c.input, historyLimit, c.verbose)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the PDF file; %w", err)
|
||||
}
|
||||
|
||||
if err := copyfile(filepath.Join(tempDir, "cv.pdf"), g.output); err != nil {
|
||||
return fmt.Errorf("unable to copy %s to %s; %w", pdfFileName, g.output, err)
|
||||
if err := copyfile(filepath.Join(tempDir, "cv.pdf"), c.output); err != nil {
|
||||
return fmt.Errorf("unable to copy %s to %s; %w", pdfFileName, c.output, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -16,29 +16,29 @@ type VersionCommand struct {
|
|||
gitCommit string
|
||||
}
|
||||
|
||||
func NewVersionCommand(binaryVersion, buildTime, goVersion, gitCommit string) *VersionCommand {
|
||||
vc := VersionCommand{
|
||||
FlagSet: flag.NewFlagSet("version", flag.ExitOnError),
|
||||
func NewVersionCommand(binaryVersion, buildTime, goVersion, gitCommit, name, summary string) *VersionCommand {
|
||||
command := VersionCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
binaryVersion: binaryVersion,
|
||||
buildTime: buildTime,
|
||||
goVersion: goVersion,
|
||||
gitCommit: gitCommit,
|
||||
summary: "Print the application's build information.",
|
||||
summary: summary,
|
||||
}
|
||||
|
||||
vc.BoolVar(&vc.fullVersion, "full", false, "prints the full build information")
|
||||
command.BoolVar(&command.fullVersion, "full", false, "prints the full build information")
|
||||
|
||||
vc.Usage = usageFunc(vc.Name(), vc.summary, vc.FlagSet)
|
||||
command.Usage = usageFunc(command.Name(), command.summary, command.FlagSet)
|
||||
|
||||
return &vc
|
||||
return &command
|
||||
}
|
||||
|
||||
func (v *VersionCommand) Run() error {
|
||||
func (c *VersionCommand) Run() error {
|
||||
var b strings.Builder
|
||||
if v.fullVersion {
|
||||
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)
|
||||
if c.fullVersion {
|
||||
fmt.Fprintf(&b, "Spruce\n Version: %s\n Git commit: %s\n Go version: %s\n Build date: %s\n", c.binaryVersion, c.gitCommit, c.goVersion, c.buildTime)
|
||||
} else {
|
||||
fmt.Fprintln(&b, v.binaryVersion)
|
||||
fmt.Fprintln(&b, c.binaryVersion)
|
||||
}
|
||||
|
||||
fmt.Print(b.String())
|
||||
|
|
Loading…
Reference in a new issue