Dan Anglin
4f90a4c8bb
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.
98 lines
2.5 KiB
Go
98 lines
2.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"codeflow.dananglin.me.uk/apollo/spruce/internal/pdf"
|
|
)
|
|
|
|
type GenerateCommand struct {
|
|
*flag.FlagSet
|
|
summary string
|
|
input string
|
|
output string
|
|
employmentHistory int
|
|
verbose bool
|
|
}
|
|
|
|
type noInputSpecifiedError struct{}
|
|
|
|
func (e noInputSpecifiedError) Error() string {
|
|
return "no input file specified, please set the --input field"
|
|
}
|
|
|
|
func NewGenerateCommand(name, summary string) *GenerateCommand {
|
|
command := GenerateCommand{
|
|
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
|
summary: summary,
|
|
}
|
|
|
|
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.")
|
|
|
|
command.Usage = usageFunc(command.Name(), command.summary, command.FlagSet)
|
|
|
|
return &command
|
|
}
|
|
|
|
func (c *GenerateCommand) Run() error {
|
|
if c.input == "" {
|
|
return noInputSpecifiedError{}
|
|
}
|
|
|
|
historyLimit := time.Now().AddDate(-1*c.employmentHistory, 0, 0)
|
|
|
|
tempDir, err := os.MkdirTemp("/tmp", "cv-builder-")
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create a temporary directory; %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
err := os.RemoveAll(tempDir)
|
|
if err != nil {
|
|
slog.Warn(fmt.Sprintf("WARN: An error occurred when removing the temporary directory; %v", err))
|
|
}
|
|
}()
|
|
|
|
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"), c.output); err != nil {
|
|
return fmt.Errorf("unable to copy %s to %s; %w", pdfFileName, c.output, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func copyfile(source, destination string) error {
|
|
inputFile, err := os.Open(source)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to open %s; %w", source, err)
|
|
}
|
|
defer inputFile.Close()
|
|
|
|
outputFile, err := os.Create(destination)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to create %s; %w", destination, err)
|
|
}
|
|
defer outputFile.Close()
|
|
|
|
_, err = io.Copy(outputFile, inputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to copy %s to %s; %w", source, destination, err)
|
|
}
|
|
|
|
slog.Info("File successfully copied.", "source", source, "destination", destination)
|
|
|
|
return nil
|
|
}
|