spruce/generate.go
Dan Anglin cee274318d
feat: add FlagSets to create new subcommands
Create new FlagSets to create new subcommands.

- The version subcommand prints the version and build info.
- The generate subcommand generates the CV PDF documentation.
2023-08-11 18:33:26 +01:00

152 lines
3.6 KiB
Go

package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"text/template"
"time"
"codeflow.dananglin.me.uk/apollo/spruce/internal/cv"
tf "codeflow.dananglin.me.uk/apollo/spruce/internal/templateFuncs"
)
type GenerateCommand struct {
fs *flag.FlagSet
input string
output string
employmentHistory int
}
func NewGenerateCommand() *GenerateCommand {
gc := GenerateCommand{
fs: flag.NewFlagSet("generate", flag.ExitOnError),
}
gc.fs.StringVar(&gc.input, "input", "", "specify the CV JSON file that you want to input to the builder.")
gc.fs.StringVar(&gc.output, "output", "", "specify the name of the output CV file.")
gc.fs.IntVar(&gc.employmentHistory, "employment-history", 10, "show employment history within these number of years.")
return &gc
}
func (g *GenerateCommand) Parse(args []string) error {
return g.fs.Parse(args)
}
func (g *GenerateCommand) Name() string {
return g.fs.Name()
}
func (g *GenerateCommand) Run() error {
historyLimit := time.Now().AddDate(-1*g.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 {
log.Printf("WARN: An error occurred when removing the temporary directory; %v", err)
}
}()
texFile, err := tex(g.input, tempDir, historyLimit)
if err != nil {
return fmt.Errorf("unable to create the tex file; %w", err)
}
if err := pdf(tempDir, texFile, g.output); err != nil {
return fmt.Errorf("unable to create the PDF file; %w", err)
}
return nil
}
// tex generates the CV document as a Tex file.
func tex(input, tempDir string, historyLimit time.Time) (string, error) {
c, err := cv.NewCV(input)
if err != nil {
return "", fmt.Errorf("unable to create a new CV value from %s; %w", input, err)
}
output := filepath.Join(tempDir, "cv.tex")
file, err := os.Create(output)
if err != nil {
return "", fmt.Errorf("unable to create output file %s; %w", output, err)
}
defer file.Close()
fmap := template.FuncMap{
"notLastElement": tf.NotLastElement,
"join": tf.JoinSentences,
"durationToString": tf.FormatDuration,
"withinTimePeriod": tf.WithinTimePeriod(historyLimit),
}
t := template.Must(template.New("cv.tmpl.tex").
Funcs(fmap).
Delims("<<", ">>").
ParseFS(templates, "templates/tex/*.tmpl.tex"),
)
if err = t.Execute(file, c); err != nil {
return "", fmt.Errorf("unable to execute the CV template. %w", err)
}
log.Printf("INFO: Tex file %s was successfully created.", output)
return output, nil
}
// pdf generates the CV document as a PDF file from the tex file.
func pdf(tempDir, texFile, output string) error {
pathArg := "--path=" + tempDir
command := exec.Command("mtxrun", pathArg, "--script", "context", texFile)
command.Stderr = os.Stderr
command.Stdout = os.Stdout
if err := command.Run(); err != nil {
return err
}
if output == "" {
output = "./cv.pdf"
}
if err := copyfile(filepath.Join(tempDir, "cv.pdf"), output); err != nil {
return err
}
return nil
}
func copyfile(input, output string) error {
inputFile, err := os.Open(input)
if err != nil {
return fmt.Errorf("unable to open %s; %w", input, err)
}
defer inputFile.Close()
outputFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("unable to create %s; %w", output, err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return fmt.Errorf("unable to copy %s to %s; %w", input, output, err)
}
return nil
}