refactor: add golangci-lint with code refactoring
Add golangci-lint for linting and refactor the code based on the feedback from running it. Changes: - Add configuration for golangci-lint. - Break the large function in create.go into smaller ones. - Rename internal/templateFuncs to internal/templatefuncs to remove upper case letters in the package name. - Add a mage target for lint tests.
This commit is contained in:
parent
4f90a4c8bb
commit
71d62ecaf6
55
.golangci.yml
Normal file
55
.golangci.yml
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 1m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
modules-download-mode: readonly
|
||||
|
||||
output:
|
||||
format: colored-line-number
|
||||
print-issues-lines: true
|
||||
print-linter-name: true
|
||||
uniq-by-line: true
|
||||
sort-results: true
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- exhaustruct
|
||||
- exhaustivestruct # (deprecated)
|
||||
- golint # (deprecated)
|
||||
- ifshort # (deprecated)
|
||||
- scopelint # (deprecated)
|
||||
- maligned # (deprecated)
|
||||
- deadcode # (deprecated)
|
||||
- nosnakecase # (deprecated)
|
||||
- interfacer # (deprecated)
|
||||
- varcheck # (deprecated)
|
||||
- structcheck # (deprecated)
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- $all
|
||||
allow:
|
||||
- $gostd
|
||||
- codeflow.dananglin.me.uk/apollo/spruce
|
||||
lll:
|
||||
line-length: 140
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: cmd/spruce/main.go
|
||||
linters:
|
||||
- gochecknoglobals
|
||||
- path: cmd/spruce-docgen/main.go
|
||||
linters:
|
||||
- gochecknoglobals
|
||||
- cyclop
|
||||
- path: internal/cmd/generate.go
|
||||
linters:
|
||||
- gomnd
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -10,8 +11,6 @@ import (
|
|||
"unicode"
|
||||
)
|
||||
|
||||
var schemaFile = "./schema/cv.schema.json"
|
||||
|
||||
var schemaReferenceTemplate = `= JSON schema reference
|
||||
|
||||
NOTE: This page was auto-generated with spruce-docgen.
|
||||
|
@ -52,7 +51,7 @@ NOTE: This page was auto-generated with spruce-docgen.
|
|||
{{- end }}
|
||||
`
|
||||
|
||||
// schema minimally represents the JSON schema format
|
||||
// schema minimally represents the JSON schema format.
|
||||
type schema struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
|
@ -65,14 +64,14 @@ type schema struct {
|
|||
AdditionalProperties *schema `json:"additionalProperties"`
|
||||
}
|
||||
|
||||
func (s *schema) UnmarshalJSON(b []byte) error {
|
||||
if bytes.Equal(b, []byte("true")) || bytes.Equal(b, []byte("false")) {
|
||||
func (s *schema) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, []byte("true")) || bytes.Equal(data, []byte("false")) {
|
||||
*s = schema{}
|
||||
} else {
|
||||
type rawSchema schema
|
||||
var res rawSchema
|
||||
if err := json.Unmarshal(b, &res); err != nil {
|
||||
return err
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal to rawSchema; %w", err)
|
||||
}
|
||||
|
||||
*s = schema(res)
|
||||
|
@ -82,6 +81,8 @@ func (s *schema) UnmarshalJSON(b []byte) error {
|
|||
}
|
||||
|
||||
func main() {
|
||||
schemaFile := "./schema/cv.schema.json"
|
||||
|
||||
file, err := os.Open(schemaFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -93,7 +94,7 @@ func main() {
|
|||
var data schema
|
||||
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
|
@ -104,7 +105,7 @@ func main() {
|
|||
t := template.Must(template.New("asciidoc").Funcs(funcMap).Parse(schemaReferenceTemplate))
|
||||
|
||||
if err = t.Execute(os.Stdout, data); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,35 +117,39 @@ func title(str string) string {
|
|||
return string(runes)
|
||||
}
|
||||
|
||||
func getType(s schema) string {
|
||||
if s.Type != "" && s.Type != "array" && s.Type != "object" {
|
||||
return s.Type
|
||||
const (
|
||||
typeArray = "array"
|
||||
typeObject = "object"
|
||||
)
|
||||
|
||||
func getType(data schema) string {
|
||||
if data.Type != "" && data.Type != typeArray && data.Type != typeObject {
|
||||
return data.Type
|
||||
}
|
||||
|
||||
if s.Type == "array" {
|
||||
if s.Items == nil {
|
||||
if data.Type == typeArray {
|
||||
switch {
|
||||
case data.Items == nil:
|
||||
return "list(UNKNOWN)"
|
||||
case data.Items.Type != "":
|
||||
return "list(" + data.Items.Type + ")"
|
||||
case data.Items.Ref != "":
|
||||
return "list(<<" + title(refType(data.Items.Ref)) + ">>)"
|
||||
default:
|
||||
return "list(UNKNOWN)"
|
||||
}
|
||||
|
||||
if s.Items.Type != "" {
|
||||
return "list(" + s.Items.Type + ")"
|
||||
}
|
||||
|
||||
if s.Items.Ref != "" {
|
||||
return "list(<<" + title(refType(s.Items.Ref)) + ">>)"
|
||||
}
|
||||
}
|
||||
|
||||
if s.Type == "" && s.Ref != "" {
|
||||
return "<<" + title(refType(s.Ref)) + ">>"
|
||||
if data.Type == "" && data.Ref != "" {
|
||||
return "<<" + title(refType(data.Ref)) + ">>"
|
||||
}
|
||||
|
||||
if s.Type == "object" {
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Type != "" {
|
||||
return "map(" + s.AdditionalProperties.Type + ")"
|
||||
if data.Type == typeObject {
|
||||
if data.AdditionalProperties.Type != "" {
|
||||
return "map(" + data.AdditionalProperties.Type + ")"
|
||||
}
|
||||
|
||||
return "object"
|
||||
return typeObject
|
||||
}
|
||||
|
||||
return "UNKNOWN"
|
||||
|
|
|
@ -87,34 +87,37 @@ func main() {
|
|||
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
|
||||
var builder strings.Builder
|
||||
|
||||
builder.WriteString("SUMMARY:\n spruce - A command-line tool for building CVs\n\n")
|
||||
|
||||
b.WriteString("SUMMARY:\n spruce - A command-line tool for building CVs\n\n")
|
||||
if binaryVersion != "" {
|
||||
b.WriteString("VERSION:\n " + binaryVersion + "\n\n")
|
||||
builder.WriteString("VERSION:\n " + binaryVersion + "\n\n")
|
||||
}
|
||||
|
||||
b.WriteString("USAGE:\n spruce [flags]\n spruce [command]\n\nCOMMANDS:")
|
||||
builder.WriteString("USAGE:\n spruce [flags]\n spruce [command]\n\nCOMMANDS:")
|
||||
|
||||
for _, cmd := range cmds {
|
||||
fmt.Fprintf(&b, "\n %s\t%s", cmd, summaries[cmd])
|
||||
fmt.Fprintf(&builder, "\n %s\t%s", cmd, summaries[cmd])
|
||||
}
|
||||
|
||||
b.WriteString("\n\nFLAGS:\n -help, --help\n print the help message\n")
|
||||
builder.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)
|
||||
fmt.Fprintf(&builder, "\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")
|
||||
builder.WriteString("\nUse \"spruce [command] --help\" for more information about a command.\n")
|
||||
|
||||
w := flag.CommandLine.Output()
|
||||
fmt.Fprint(w, b.String())
|
||||
fmt.Fprint(w, builder.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,18 +14,18 @@ type Runner interface {
|
|||
|
||||
func usageFunc(name, summary string, flagset *flag.FlagSet) func() {
|
||||
return func() {
|
||||
var b strings.Builder
|
||||
var builder strings.Builder
|
||||
|
||||
fmt.Fprintf(&b, "SUMMARY:\n %s - %s\n\nUSAGE:\n spruce %s [flags]\n\nFLAGS:", name, summary, name)
|
||||
fmt.Fprintf(&builder, "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, --%s\n %s", f.Name, f.Name, f.Usage)
|
||||
fmt.Fprintf(&builder, "\n -%s, --%s\n %s", f.Name, f.Name, f.Usage)
|
||||
})
|
||||
|
||||
b.WriteString("\n")
|
||||
builder.WriteString("\n")
|
||||
|
||||
w := flag.CommandLine.Output()
|
||||
|
||||
fmt.Fprint(w, b.String())
|
||||
fmt.Fprint(w, builder.String())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,75 +37,16 @@ func NewCreateCommand(name, summary string) *CreateCommand {
|
|||
}
|
||||
|
||||
func (c *CreateCommand) Run() error {
|
||||
detailLen := 2
|
||||
data := cv.NewCV(c.firstName, c.lastName, c.jobTitle)
|
||||
|
||||
data.Contact = map[string]string{
|
||||
"Email": "",
|
||||
"Phone": "",
|
||||
}
|
||||
|
||||
data.Links = map[string]string{
|
||||
"GitHub": "",
|
||||
"Website": "",
|
||||
}
|
||||
|
||||
data.Summary = make([]string, 2)
|
||||
|
||||
data.Skills = []cv.Skills{
|
||||
{
|
||||
Category: "",
|
||||
Values: make([]string, 1),
|
||||
},
|
||||
{
|
||||
Category: "",
|
||||
Values: make([]string, 1),
|
||||
},
|
||||
}
|
||||
|
||||
data.Employment = []cv.Employment{
|
||||
{
|
||||
Company: "",
|
||||
Location: "",
|
||||
LocationType: "",
|
||||
JobTitle: "",
|
||||
Duration: cv.Duration{
|
||||
Start: cv.Date{
|
||||
Day: int64(time.Now().Day()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Year: int64(time.Now().Year()),
|
||||
},
|
||||
End: &cv.Date{
|
||||
Day: int64(time.Now().Day()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Year: int64(time.Now().Year()),
|
||||
},
|
||||
Present: false,
|
||||
},
|
||||
Details: make([]string, 2),
|
||||
},
|
||||
}
|
||||
|
||||
data.Education = []cv.Education{
|
||||
{
|
||||
School: "",
|
||||
Location: "",
|
||||
Qualification: "",
|
||||
Duration: cv.Duration{
|
||||
Start: cv.Date{
|
||||
Year: int64(time.Now().Year()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Day: int64(time.Now().Day()),
|
||||
},
|
||||
End: &cv.Date{
|
||||
Year: int64(time.Now().Year()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Day: int64(time.Now().Day()),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data.Interests = make([]string, 2)
|
||||
data.Contact = contact()
|
||||
data.Links = links()
|
||||
data.Summary = make([]string, detailLen)
|
||||
data.Skills = skills()
|
||||
data.Employment = employment(detailLen)
|
||||
data.Education = education()
|
||||
data.Interests = make([]string, detailLen)
|
||||
|
||||
file, err := os.Create(c.filename)
|
||||
if err != nil {
|
||||
|
@ -125,3 +66,77 @@ func (c *CreateCommand) Run() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func contact() map[string]string {
|
||||
return map[string]string{
|
||||
"Email": "",
|
||||
"Phone": "",
|
||||
}
|
||||
}
|
||||
|
||||
func links() map[string]string {
|
||||
return map[string]string{
|
||||
"GitHub": "",
|
||||
"Website": "",
|
||||
}
|
||||
}
|
||||
|
||||
func skills() []cv.Skills {
|
||||
return []cv.Skills{
|
||||
{
|
||||
Category: "",
|
||||
Values: make([]string, 1),
|
||||
},
|
||||
{
|
||||
Category: "",
|
||||
Values: make([]string, 1),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func employment(detailLen int) []cv.Employment {
|
||||
return []cv.Employment{
|
||||
{
|
||||
Company: "",
|
||||
Location: "",
|
||||
LocationType: "",
|
||||
JobTitle: "",
|
||||
Duration: cv.Duration{
|
||||
Start: cv.Date{
|
||||
Day: int64(time.Now().Day()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Year: int64(time.Now().Year()),
|
||||
},
|
||||
End: &cv.Date{
|
||||
Day: int64(time.Now().Day()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Year: int64(time.Now().Year()),
|
||||
},
|
||||
Present: false,
|
||||
},
|
||||
Details: make([]string, detailLen),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func education() []cv.Education {
|
||||
return []cv.Education{
|
||||
{
|
||||
School: "",
|
||||
Location: "",
|
||||
Qualification: "",
|
||||
Duration: cv.Duration{
|
||||
Start: cv.Date{
|
||||
Year: int64(time.Now().Year()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Day: int64(time.Now().Day()),
|
||||
},
|
||||
End: &cv.Date{
|
||||
Year: int64(time.Now().Year()),
|
||||
Month: int64(time.Now().Month()),
|
||||
Day: int64(time.Now().Day()),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package cmd
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -34,13 +35,22 @@ func NewVersionCommand(binaryVersion, buildTime, goVersion, gitCommit, name, sum
|
|||
}
|
||||
|
||||
func (c *VersionCommand) Run() error {
|
||||
var b strings.Builder
|
||||
var builder strings.Builder
|
||||
|
||||
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)
|
||||
fmt.Fprintf(
|
||||
&builder,
|
||||
"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, c.binaryVersion)
|
||||
fmt.Fprintln(&builder, c.binaryVersion)
|
||||
}
|
||||
|
||||
fmt.Print(b.String())
|
||||
fmt.Fprint(os.Stdout, builder.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,36 +17,35 @@ func NewCVFromFile(path string) (CV, error) {
|
|||
|
||||
decoder := json.NewDecoder(file)
|
||||
|
||||
var c CV
|
||||
var output CV
|
||||
|
||||
if err = decoder.Decode(&c); err != nil {
|
||||
if err = decoder.Decode(&output); err != nil {
|
||||
return CV{}, fmt.Errorf("unable to decode JSON data; %w", err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// NewCV returns a new value of type CV.
|
||||
func NewCV(firstName, lastName, jobTitle string) CV {
|
||||
cv := CV{
|
||||
output := CV{
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
JobTitle: jobTitle,
|
||||
}
|
||||
|
||||
return cv
|
||||
return output
|
||||
}
|
||||
|
||||
// After returns true if the Duration's end date is set after time t.
|
||||
// After returns true if the Duration's end date is set after the earliest experience date.
|
||||
// An error is returned if the end date is not parsed successfully.
|
||||
func (d Duration) After(t time.Time) (bool, error) {
|
||||
func (d Duration) After(earliestExperienceDate time.Time) (bool, error) {
|
||||
endDate, err := d.End.Parse()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return endDate.After(t), nil
|
||||
|
||||
return endDate.After(earliestExperienceDate), nil
|
||||
}
|
||||
|
||||
// Parse parses Date and returns a value of type time.Time.
|
||||
|
|
|
@ -28,7 +28,7 @@ func Generate(tempDir, input string, historyLimit time.Time, verbose bool) (stri
|
|||
}
|
||||
|
||||
if err := command.Run(); err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("an error occurred when creating the PDF file; %w", err)
|
||||
}
|
||||
|
||||
output := filepath.Join(tempDir, "cv.pdf")
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/spruce/internal/cv"
|
||||
tf "codeflow.dananglin.me.uk/apollo/spruce/internal/templateFuncs"
|
||||
tf "codeflow.dananglin.me.uk/apollo/spruce/internal/templatefuncs"
|
||||
)
|
||||
|
||||
//go:embed templates/tex/*
|
||||
|
@ -20,7 +20,7 @@ var templates embed.FS
|
|||
func tex(input, tempDir string, historyLimit time.Time) (string, error) {
|
||||
slog.Info("Creating the Tex file.")
|
||||
|
||||
c, err := cv.NewCVFromFile(input)
|
||||
data, err := cv.NewCVFromFile(input)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create a new CV value from %s; %w", input, err)
|
||||
}
|
||||
|
@ -41,13 +41,13 @@ func tex(input, tempDir string, historyLimit time.Time) (string, error) {
|
|||
"location": tf.Location,
|
||||
}
|
||||
|
||||
t := template.Must(template.New("cv.tmpl.tex").
|
||||
tmpl := template.Must(template.New("cv.tmpl.tex").
|
||||
Funcs(fmap).
|
||||
Delims("<<", ">>").
|
||||
ParseFS(templates, "templates/tex/*.tmpl.tex"),
|
||||
)
|
||||
|
||||
if err = t.Execute(file, c); err != nil {
|
||||
if err = tmpl.Execute(file, data); err != nil {
|
||||
return "", fmt.Errorf("unable to execute the CV template. %w", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package templateFuncs
|
||||
package templatefuncs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -8,10 +8,10 @@ import (
|
|||
|
||||
// FormatDuration outputs the employment/education
|
||||
// duration as a formatted string.
|
||||
func FormatDuration(d cv.Duration) string {
|
||||
func FormatDuration(duration cv.Duration) string {
|
||||
var start string
|
||||
|
||||
startDate, err := d.Start.Parse()
|
||||
startDate, err := duration.Start.Parse()
|
||||
|
||||
if err != nil {
|
||||
start = "Unknown"
|
||||
|
@ -21,10 +21,10 @@ func FormatDuration(d cv.Duration) string {
|
|||
|
||||
var end string
|
||||
|
||||
if d.Present {
|
||||
if duration.Present {
|
||||
end = "Present"
|
||||
} else {
|
||||
endDate, err := d.End.Parse()
|
||||
endDate, err := duration.End.Parse()
|
||||
if err != nil {
|
||||
end = "Unknown"
|
||||
} else {
|
|
@ -1,4 +1,4 @@
|
|||
package templateFuncs
|
||||
package templatefuncs
|
||||
|
||||
import (
|
||||
"strings"
|
|
@ -1,4 +1,4 @@
|
|||
package templateFuncs
|
||||
package templatefuncs
|
||||
|
||||
// Location returns a string with both the location and the
|
||||
// location type if the location type is not empty.
|
|
@ -1,4 +1,4 @@
|
|||
package templateFuncs
|
||||
package templatefuncs
|
||||
|
||||
// NotLastElement returns true if an element of an array
|
||||
// or a slice is not the last.
|
|
@ -1,4 +1,4 @@
|
|||
package templateFuncs
|
||||
package templatefuncs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
@ -8,13 +8,13 @@ import (
|
|||
|
||||
// WithinTimePeriod returns true if the employment's end date is within
|
||||
// the specified time period.
|
||||
func WithinTimePeriod(t time.Time) func(d cv.Duration) bool {
|
||||
func WithinTimePeriod(date time.Time) func(d cv.Duration) bool {
|
||||
return func(d cv.Duration) bool {
|
||||
if d.Present {
|
||||
return true
|
||||
}
|
||||
|
||||
result, err := d.After(t)
|
||||
result, err := d.After(date)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
|
@ -19,6 +19,11 @@ var (
|
|||
defaultInstallPrefix = "/usr/local"
|
||||
)
|
||||
|
||||
// Lint runs golangci-lint against the code.
|
||||
func Lint() error {
|
||||
return sh.RunV("golangci-lint", "run", "--color", "always")
|
||||
}
|
||||
|
||||
// Build builds the binary.
|
||||
func Build() error {
|
||||
flags := ldflags()
|
||||
|
|
Loading…
Reference in a new issue