all repos — homestead @ 8abf706d2e9d70a3d11f58a9eb4c01a7e6b436c2

Code for my website

refactor: lint with golangci-lint

Alan Pearce
commit

8abf706d2e9d70a3d11f58a9eb4c01a7e6b436c2

parent

728423eaeb39d3db2fdab31f65a3dc503542762d

A .golangci.yaml
@@ -0,0 +1,23 @@
+--- +# yamllint disable-line rule:line-length +# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json +linters: + fast: false + enable: + - gocritic + - godox + - gosec + - grouper + - lll + - nilerr + - nilnil + - nlreturn + - noctx + - nosprintfhostport + - paralleltest + - prealloc + - reassign + - revive + - sloglint + - unconvert + - wrapcheck
M cmd/dev/main.gocmd/dev/main.go
@@ -48,7 +48,8 @@ err := cmd.Process.Signal(os.Interrupt)
if err != nil { log.Error("signal error:", "error", err) } - return err + + return errors.Wrap(err, "error signalling child process") } stdout, err := cmd.StdoutPipe() if err != nil {
@@ -59,8 +60,18 @@ if err != nil {
return } - go io.Copy(os.Stdout, stdout) - go io.Copy(os.Stderr, stderr) + go func() { + _, err := io.Copy(os.Stdout, stdout) + if err != nil { + log.Error("error copying stdout", "error", err) + } + }() + go func() { + _, err := io.Copy(os.Stderr, stderr) + if err != nil { + log.Error("error copying stderr", "error", err) + } + }() return }
@@ -72,24 +83,27 @@
func NewFileWatcher(pollTime time.Duration) (*FileWatcher, error) { batcher, err := watcher.New(pollTime/5, pollTime, true) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not create file watcher") } + return &FileWatcher{batcher}, nil } func (watcher FileWatcher) WatchAllFiles(from string) error { log.Debug("watching files under", "from", from) - err := filepath.Walk(from, func(path string, info fs.FileInfo, err error) error { + err := filepath.Walk(from, func(path string, _ fs.FileInfo, err error) error { if err != nil { return err } // log.Debug(fmt.Sprintf("adding file %s to watcher", path)) if err = watcher.Add(path); err != nil { - return err + return errors.Wrapf(err, "could not add path %s to watcher", path) } + return nil }) - return err + + return errors.Wrapf(err, "could not walk directory tree %s", from) } func build(ctx context.Context, config DevConfig) error {
@@ -113,6 +127,7 @@ )
if err != nil { return errors.WithMessage(err, "error running build command") } + return nil }
@@ -153,7 +168,7 @@ case <-ctx.Done():
log.Debug("server context done") err := cmd.Process.Signal(os.Interrupt) if err != nil { - return err + return errors.Wrap(err, "could not process signal") } <-done case err := <-cmdErr:
@@ -179,7 +194,6 @@
log.Debug("running with in /tmp", "dir", devConfig.TempDir) ctx, cancel := context.WithCancel(context.Background()) - defer cancel() log.Debug("setting interrupt handler") c := make(chan os.Signal, 1)
@@ -195,9 +209,9 @@ }()
serverChan := make(chan bool, 1) eventsource := eventsource.New(nil, nil) - defer eventsource.Close() srv := http.Server{ - Addr: devConfig.BaseURL.Host, + Addr: devConfig.BaseURL.Host, + ReadHeaderTimeout: 1 * time.Minute, } devCtx, devCancel := context.WithTimeout(ctx, 1*time.Second)
@@ -242,15 +256,15 @@ }()
fw, err := NewFileWatcher(500 * time.Millisecond) if err != nil { - log.Fatal("error creating file watcher", "error", err) + log.Panic("error creating file watcher", "error", err) } err = fw.WatchAllFiles("content") if err != nil { - log.Fatal("could not watch files in content directory", "error", err) + log.Panic("could not watch files in content directory", "error", err) } err = fw.WatchAllFiles("templates") if err != nil { - log.Fatal("could not watch files in templates directory", "error", err) + log.Panic("could not watch files in templates directory", "error", err) } var exitCode int
@@ -283,6 +297,7 @@ if err != nil {
log.Debug("shutdown error", "error", err) } exitCode = 1 + break loop case event := <-fw.Events: log.Debug("event received:", "event", event)
@@ -291,6 +306,7 @@ serverChan <- false
log.Debug("waiting for server shutdown") <-serverErr log.Debug("server shutdown completed") + continue case err = <-serverErr: if err != nil && err != context.Canceled {
@@ -301,18 +317,24 @@ log.Debug("server exit error")
} else { log.Debug("server other error") } + break } log.Debug("no error or server context cancelled") + continue } log.Debug("waiting on server") exitCode = 0 + break } log.Debug("waiting for wg before shutting down") + eventsource.Close() + cancel() wg.Wait() + os.Exit(exitCode) }
M internal/atom/atom.gointernal/atom/atom.go
@@ -4,10 +4,10 @@ import (
"encoding/xml" "time" - . "website/internal/config" + "website/internal/config" ) -func MakeTagURI(config Config, specific string) string { +func MakeTagURI(config config.Config, specific string) string { return "tag:" + config.OriginalDomain + "," + config.DomainStartDate + ":" + specific }
@@ -35,7 +35,7 @@ type FeedEntry struct {
XMLName xml.Name `xml:"entry"` Title string `xml:"title"` Link Link `xml:"link"` - Id string `xml:"id"` + ID string `xml:"id"` Updated time.Time `xml:"updated"` Summary string `xml:"summary,omitempty"` Content FeedContent `xml:"content"`
M internal/builder/builder.gointernal/builder/builder.go
@@ -23,7 +23,9 @@ Development bool `conf:"default:false,flag:dev"`
} func mkdirp(dirs ...string) error { - return os.MkdirAll(path.Join(dirs...), 0755) + err := os.MkdirAll(path.Join(dirs...), 0755) + + return errors.Wrap(err, "could not create directory") } func outputToFile(output io.Reader, filename ...string) error {
@@ -37,6 +39,7 @@
if _, err := file.ReadFrom(output); err != nil { return errors.WithMessage(err, "could not write output file") } + return nil }
M internal/builder/posts.gointernal/builder/posts.go
@@ -73,6 +73,7 @@ var buf bytes.Buffer
if err := markdown.Convert(content, &buf); err != nil { return "", errors.WithMessage(err, "could not convert markdown content") } + return buf.String(), nil }
@@ -121,5 +122,6 @@ }
slices.SortFunc(posts, func(a, b Post) int { return b.Date.Compare(a.Date) }) + return posts, tags, nil }
M internal/builder/template.gointernal/builder/template.go
@@ -18,6 +18,7 @@ "github.com/a-h/htmlformat"
"github.com/antchfx/xmlquery" "github.com/antchfx/xpath" mapset "github.com/deckarep/golang-set/v2" + "github.com/pkg/errors" "golang.org/x/net/html" )
@@ -26,18 +27,19 @@ assetsOnce sync.Once
css string countHTML *goquery.Document liveReloadHTML *goquery.Document - templates map[string]*os.File = make(map[string]*os.File) + templates = make(map[string]*os.File) ) func loadTemplate(path string) (file *os.File, err error) { if templates[path] == nil { file, err = os.OpenFile(path, os.O_RDONLY, 0) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "could not load template at path %s", path) } templates[path] = file } file = templates[path] + return }
@@ -57,7 +59,8 @@ }
func NewDocumentFromReader(r io.Reader) (*QueryDocument, error) { doc, err := goquery.NewDocumentFromReader(r) - return &QueryDocument{doc}, err + + return &QueryDocument{doc}, errors.Wrap(err, "could not create query document") } func (q *QueryDocument) Find(selector string) *QuerySelection {
@@ -66,7 +69,8 @@ }
func NewDocumentNoScript(r io.Reader) (*goquery.Document, error) { root, err := html.ParseWithOptions(r, html.ParseOptionEnableScripting(false)) - return goquery.NewDocumentFromNode(root), err + + return goquery.NewDocumentFromNode(root), errors.Wrap(err, "could not parse HTML") } func (root QuerySelection) setImgURL(pageURL string, pageTitle string) QuerySelection {
@@ -90,6 +94,7 @@ q.Set("t", pageTitle)
output := urlTemplate.String() + "?" + q.Encode() clone.Find("img").SetAttr("src", output) root.AppendSelection(clone.Find("body").Children()) + return root }
@@ -103,7 +108,12 @@ html, err := loadTemplate(filename)
if err != nil { return nil, err } - defer html.Seek(0, io.SeekStart) + defer func() { + _, err := html.Seek(0, io.SeekStart) + if err != nil { + panic("could not reset template file offset: " + err.Error()) + } + }() assetsOnce.Do(func() { var bytes []byte bytes, err = os.ReadFile("templates/style.css")
@@ -133,7 +143,7 @@ }
} }) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not set up layout template") } doc, err := NewDocumentFromReader(html)
@@ -156,6 +166,7 @@ nav.Empty()
for _, link := range config.Menus["main"] { nav.AppendSelection(navLink.Clone().SetAttr("href", link.URL).SetText(link.Name)) } + return doc.Document, nil }
@@ -196,6 +207,7 @@ li := tpl.Clone()
li.Find("a").SetAttr("href", fmt.Sprintf("/tags/%s/", tag)).SetText("#" + tag) tagList.AppendSelection(li) } + return renderHTML(doc), nil }
@@ -288,6 +300,7 @@ doc, err := layout("templates/404.html", config, "404 Not Found", url)
if err != nil { return nil, err } + return renderHTML(doc), nil }
@@ -301,10 +314,15 @@ reader, err := loadTemplate("templates/feed.xml")
if err != nil { return nil, err } - defer reader.Seek(0, io.SeekStart) + defer func() { + _, err := reader.Seek(0, io.SeekStart) + if err != nil { + panic("could not reset reader: " + err.Error()) + } + }() doc, err := xmlquery.Parse(reader) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not parse XML") } feed := doc.SelectElement("feed") feed.SelectElement("title").FirstChild.Data = title
@@ -312,21 +330,18 @@ feed.SelectElement("link").SetAttr("href", config.BaseURL.String())
feed.SelectElement("id").FirstChild.Data = atom.MakeTagURI(config, specific) datetime, err := posts[0].Date.UTC().MarshalText() if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not convert post date to text") } feed.SelectElement("updated").FirstChild.Data = string(datetime) tpl := feed.SelectElement("entry") xmlquery.RemoveFromTree(tpl) for _, post := range posts { - fullURL, err := url.JoinPath(config.BaseURL.String(), post.URL) - if err != nil { - return nil, err - } + fullURL := config.BaseURL.JoinPath(post.URL).String() text, err := xml.MarshalIndent(&atom.FeedEntry{ Title: post.Title, Link: atom.MakeLink(fullURL), - Id: atom.MakeTagURI(config, post.Basename), + ID: atom.MakeTagURI(config, post.Basename), Updated: post.Date.UTC(), Summary: post.Description, Author: config.Title,
@@ -336,7 +351,7 @@ Type: "html",
}, }, " ", " ") if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not marshal xml") } entry, err := xmlquery.ParseWithOptions( strings.NewReader(string(text)),
@@ -349,7 +364,7 @@ },
}, ) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not parse XML") } xmlquery.AddChild(feed, entry.SelectElement("entry")) }
@@ -362,7 +377,12 @@ reader, err := loadTemplate("templates/feed-styles.xsl")
if err != nil { return nil, err } - defer reader.Seek(0, io.SeekStart) + defer func() { + _, err := reader.Seek(0, io.SeekStart) + if err != nil { + panic("could not reset reader: " + err.Error()) + } + }() nsMap := map[string]string{ "xsl": "http://www.w3.org/1999/XSL/Transform", "atom": "http://www.w3.org/2005/Atom",
@@ -370,24 +390,24 @@ "xhtml": "http://www.w3.org/1999/xhtml",
} doc, err := xmlquery.Parse(reader) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not parse XML") } expr, err := xpath.CompileWithNS("//xhtml:style", nsMap) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not parse XML") } style := xmlquery.QuerySelector(doc, expr) xmlquery.AddChild(style, &xmlquery.Node{ Type: xmlquery.TextNode, - Data: string(css), + Data: css, }) + return strings.NewReader(doc.OutputXML(true)), nil } func renderHTML(doc *goquery.Document) io.Reader { r, w := io.Pipe() - // TODO: return errors to main thread go func() { _, err := w.Write([]byte("<!doctype html>\n")) if err != nil {
@@ -398,9 +418,11 @@ err = htmlformat.Nodes(w, []*html.Node{doc.Children().Get(0)})
if err != nil { log.Error("error rendering html", "error", err) w.CloseWithError(err) + return } defer w.Close() }() + return r }
M internal/config/config.gointernal/config/config.go
@@ -25,7 +25,8 @@ }
func (u *URL) UnmarshalText(text []byte) (err error) { u.URL, err = url.Parse(string(text)) - return err + + return errors.Wrapf(err, "could not parse URL %s", string(text)) } type Config struct {
@@ -51,15 +52,15 @@ config := Config{}
log.Debug("reading config.toml") _, err := toml.DecodeFile("config.toml", &config) if err != nil { - var pathError *fs.PathError - var tomlError toml.ParseError - if errors.As(err, &pathError) { - return nil, errors.WithMessage(err, "could not read configuration") - } else if errors.As(err, &tomlError) { - return nil, errors.WithMessage(err, tomlError.ErrorWithUsage()) - } else { - return nil, errors.Wrap(err, "config error") + switch t := err.(type) { + case *fs.PathError: + return nil, errors.WithMessage(t, "could not read configuration") + case *toml.ParseError: + return nil, errors.WithMessage(t, t.ErrorWithUsage()) } + + return nil, errors.Wrap(err, "config error") } + return &config, nil }
M internal/config/cspgenerator.gointernal/config/cspgenerator.go
@@ -9,13 +9,15 @@ "reflect"
"github.com/crewjam/csp" "github.com/fatih/structtag" + "github.com/pkg/errors" ) func GenerateCSP() error { t := reflect.TypeFor[csp.Header]() file, err := os.OpenFile("./csp.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0) if err != nil { - return err + + return errors.Wrap(err, "could not write to output") } defer file.Close()
@@ -29,46 +31,43 @@ )
`) if err != nil { - return err + + return errors.Wrap(err, "could not write to output") } _, err = fmt.Fprintf(file, "type CSP struct {\n") if err != nil { - return err + return errors.Wrap(err, "could not write to output") } for i := 0; i < t.NumField(); i++ { field := t.Field(i) var t reflect.Type - if field.Type.Kind() == reflect.Slice { - t = field.Type - } else { - t = field.Type - } + t = field.Type tags, err := structtag.Parse(string(field.Tag)) if err != nil { - return err + return errors.Wrap(err, "could not write to output") } cspTag, err := tags.Get("csp") if err != nil { - return err + return errors.Wrap(err, "could not get csp tag") } err = tags.Set(&structtag.Tag{ Key: "toml", Name: cspTag.Name, }) if err != nil { - return err + return errors.Wrap(err, "could not set toml tag") } _, err = fmt.Fprintf(file, "\t%-23s %-28s `%s`\n", field.Name, t, tags.String()) if err != nil { - return err + return errors.Wrap(err, "could not write to output") } } _, err = fmt.Fprintln(file, "}") if err != nil { - return err + return errors.Wrap(err, "could not write to output") } _, err = fmt.Fprintln(file, `
@@ -76,7 +75,8 @@ func (c *CSP) String() string {
return csp.Header(*c).String() }`) if err != nil { - return err + return errors.Wrap(err, "could not write to output") } + return nil }
M internal/server/logging.gointernal/server/logging.go
@@ -5,12 +5,12 @@ "net/http"
"website/internal/log" ) -type loggingResponseWriter struct { +type LoggingResponseWriter struct { http.ResponseWriter statusCode int } -func (lrw *loggingResponseWriter) WriteHeader(code int) { +func (lrw *LoggingResponseWriter) WriteHeader(code int) { lrw.statusCode = code // avoids warning: superfluous response.WriteHeader call if lrw.statusCode != http.StatusOK {
@@ -18,8 +18,8 @@ lrw.ResponseWriter.WriteHeader(code)
} } -func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{w, http.StatusOK} +func NewLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter { + return &LoggingResponseWriter{w, http.StatusOK} } type wrappedHandlerOptions struct {
M internal/server/server.gointernal/server/server.go
@@ -21,9 +21,9 @@
var config *cfg.Config var ( - CommitSHA string = "local" - ShortSHA string = "local" - serverHeader string = fmt.Sprintf("website (%s)", ShortSHA) + CommitSHA = "local" + ShortSHA = "local" + serverHeader = fmt.Sprintf("website (%s)", ShortSHA) ) type Config struct {
@@ -83,7 +83,7 @@
top := http.NewServeMux() mux, err := website.NewMux(config, runtimeConfig.Root) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not create website mux") } log.Debug("binding main handler to", "host", runtimeConfig.BaseURL.Hostname()+"/") hostname := runtimeConfig.BaseURL.Hostname()
@@ -95,14 +95,16 @@ newURL := runtimeConfig.BaseURL.JoinPath(r.URL.String())
http.Redirect(w, r, newURL.String(), 301) }) - top.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + top.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) listenAddress := net.JoinHostPort(runtimeConfig.ListenAddress, runtimeConfig.Port) + return &Server{ &http.Server{ - Addr: listenAddress, + Addr: listenAddress, + ReadHeaderTimeout: 1 * time.Minute, Handler: sentryHandler.Handle( serverHeaderHandler( wrapHandlerWithLogging(top, wrappedHandlerOptions{
@@ -116,8 +118,9 @@ }
func (s *Server) Start() error { if err := s.ListenAndServe(); err != http.ErrServerClosed { - return err + return errors.Wrap(err, "error creating/closing server") } + return nil }
M internal/website/filemap.gointernal/website/filemap.go
@@ -24,19 +24,21 @@
func hashFile(filename string) (string, error) { f, err := os.Open(filename) if err != nil { - return "", err + return "", errors.Wrapf(err, "could not open file %s for hashing", filename) } defer f.Close() hash := fnv.New64a() if _, err := io.Copy(hash, f); err != nil { - return "", err + return "", errors.Wrapf(err, "could not hash file %s", filename) } + return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil } func registerFile(urlpath string, filepath string) error { if files[urlpath] != (File{}) { log.Info("registerFile called with duplicate file", "url_path", urlpath) + return nil } hash, err := hashFile(filepath)
@@ -47,6 +49,7 @@ files[urlpath] = File{
filename: filepath, etag: hash, } + return nil }
@@ -62,13 +65,16 @@ }
urlPath, _ := strings.CutSuffix(relPath, "index.html") if !f.IsDir() { log.Debug("registering file", "urlpath", "/"+urlPath) + return registerFile("/"+urlPath, filePath) } + return nil }) if err != nil { - return err + return errors.Wrap(err, "could not walk directory") } + return nil }
M internal/website/mux.gointernal/website/mux.go
@@ -25,6 +25,7 @@ cPath, differs = strings.CutSuffix(path, "index.html")
} else if !strings.HasSuffix(path, "/") && files[path+"/"] != (File{}) { cPath, differs = path+"/", true } + return cPath, differs }
@@ -62,6 +63,7 @@ mux.Handle("/", webHandler(func(w http.ResponseWriter, r *http.Request) *HTTPError {
urlPath, shouldRedirect := canonicalisePath(r.URL.Path) if shouldRedirect { http.Redirect(w, r, urlPath, 302) + return nil } file := GetFile(urlPath)
@@ -79,6 +81,7 @@ w.Header().Add(k, v)
} http.ServeFile(w, r, files[urlPath].filename) + return nil }))