Repos / s4g / b4e42aac1d
commit b4e42aac1dad3f3ba99ee9dd75562fc54baa1850
Author: Nhân <hi@imnhan.com>
Date:   Sun Jul 16 23:19:43 2023 +0700

    error message in html

diff --git a/Makefile b/Makefile
index 1171c44..6f30ab5 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,7 @@ build:
 	go build -o dist/
 
 watch:
-	find . -name '*.go' -or -name '*.js' -or -name 'livereload.html' \
-	| entr -rc go run .
+	fd -E docs -E theme | entr -rc go run .
 
 watch-theme:
 	find theme/* | entr -c rsync -av theme/ docs/_theme/
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..8999864
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"fmt"
+	"html/template"
+)
+
+type SiteMetadataErr struct {
+	Field string
+	Msg   string
+}
+
+func (e *SiteMetadataErr) Error() string {
+	return fmt.Sprintf("SiteMetadataErr - %s: %s", e.Field, e.Msg)
+}
+
+func (e *SiteMetadataErr) Html() template.HTML {
+	return template.HTML(fmt.Sprintf(
+		"<p>In file <b>%s</b>, field <b>%s</b>: %s </p>",
+		SiteFileName, e.Field, e.Msg,
+	))
+}
diff --git a/livereload/error.go b/livereload/error.go
new file mode 100644
index 0000000..b8ed95d
--- /dev/null
+++ b/livereload/error.go
@@ -0,0 +1,26 @@
+package livereload
+
+import (
+	"bytes"
+	_ "embed"
+	"html/template"
+	"net/http"
+)
+
+//go:embed error.html
+var errorTmpl string
+
+var errTmpl = template.Must(template.New("error").Parse(errorTmpl))
+
+// Error that has a user-friendly HTML representation.
+type htmlErr interface {
+	error
+	Html() template.HTML
+}
+
+func serveError(w http.ResponseWriter, r *http.Request, err htmlErr) {
+	var buf bytes.Buffer
+	errTmpl.Execute(&buf, err)
+	body := withLiveReload(buf.Bytes())
+	w.Write(body)
+}
diff --git a/livereload/error.html b/livereload/error.html
index c2d392e..e59eb99 100644
--- a/livereload/error.html
+++ b/livereload/error.html
@@ -1,15 +1,15 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head>
     <meta charset="utf-8" />
-    <title>Error</title>
+    <title>{{.Error}}</title>
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   </head>
   <body>
-    <b>Error:</b> {{.}}
+    <h1>Error</h1>
+    {{.Html}}
     <style>
-      body {
-        background-color: pink;
+      h1 {
         color: red;
       }
     </style>
diff --git a/livereload/livereload.go b/livereload/livereload.go
index 898e6dd..b51105a 100644
--- a/livereload/livereload.go
+++ b/livereload/livereload.go
@@ -7,7 +7,6 @@
 	"net/http"
 	"strings"
 	"sync"
-	"text/template"
 
 	"go.imnhan.com/webmaker2000/writablefs"
 )
@@ -18,9 +17,6 @@
 //go:embed livereload.html
 var lrScript []byte
 
-//go:embed error.html
-var errorTmpl string
-
 var pleaseReload = []byte("1")
 var dontReload = []byte("0")
 
@@ -90,7 +86,7 @@ func Middleware(mux *http.ServeMux, root string, fsys writablefs.FS, f http.Hand
 		err = state.err
 		state.errMut.RUnlock()
 		if err != nil {
-			serveError(w, r, err)
+			serveError(w, r, err.(htmlErr))
 			return
 		}
 
@@ -152,12 +148,3 @@ func withLiveReload(original []byte) []byte {
 	copy(result[bodyEndPos+len(lrScript):], original[bodyEndPos:])
 	return result
 }
-
-var errTmpl = template.Must(template.New("error").Parse(errorTmpl))
-
-func serveError(w http.ResponseWriter, r *http.Request, err error) {
-	var buf bytes.Buffer
-	errTmpl.Execute(&buf, err.Error())
-	body := withLiveReload(buf.Bytes())
-	w.Write(body)
-}
diff --git a/main.go b/main.go
index 7eb15a6..6060cef 100644
--- a/main.go
+++ b/main.go
@@ -190,8 +190,10 @@ func regenerate(fsys writablefs.FS) (site *SiteMetadata, err error) {
 	for _, link := range site.NavbarLinks {
 		a, ok := articles[link]
 		if !ok {
-			return nil,
-				fmt.Errorf("%s: NavbarLinks: %s not found", FeedPath, link)
+			return nil, &SiteMetadataErr{
+				Field: "NavbarLinks",
+				Msg:   fmt.Sprintf(`"%s" does not exist`, link),
+			}
 		}
 		articlesInNav = append(articlesInNav, a)
 	}
@@ -214,7 +216,10 @@ func regenerate(fsys writablefs.FS) (site *SiteMetadata, err error) {
 
 	for _, a := range articles {
 		fmt.Println(">", a.Path, "-", a.Title)
-		a.WriteHtmlFile(site, articlesInNav, articlesInFeed, startYear)
+		err := a.WriteHtmlFile(site, articlesInNav, articlesInFeed, startYear)
+		if err != nil {
+			return nil, fmt.Errorf("Article %s: %w", a.Path, err)
+		}
 		generatedFiles[a.OutputPath] = true
 	}
 	fmt.Printf("Processed %d articles\n", len(articles))
@@ -282,15 +287,19 @@ func (a *Article) WriteHtmlFile(
 	articlesInNav []Article,
 	articlesInFeed []Article,
 	startYear int,
-) {
-	// First generate the main content in html
+) error {
 	contentHtml := djot.ToHtml(a.DjotBody)
 
-	// Then insert that content into the main template
-	var buf bytes.Buffer
+	tmpl, err := template.ParseFS(a.Fs, a.TemplatePaths...)
 	// TODO: should probably reuse the template object for common cases
-	tmpl := template.Must(template.ParseFS(a.Fs, a.TemplatePaths...))
-	err := tmpl.Execute(&buf, struct {
+	if err != nil {
+		return fmt.Errorf(
+			"Failed to parse templates (%v): %w", a.Templates, err,
+		)
+	}
+
+	var buf bytes.Buffer
+	err = tmpl.Execute(&buf, struct {
 		Site           *SiteMetadata
 		Content        template.HTML
 		Title          string
@@ -312,16 +321,17 @@ func (a *Article) WriteHtmlFile(
 		StartYear:      startYear,
 	})
 	if err != nil {
-		fmt.Println("Error in WriteHtmlFile:", err)
-		return
+		return fmt.Errorf("Failed to execute templates (%v): %w", a.Templates, err)
 	}
 	fullHtml := buf.Bytes()
 
 	// Now write into an html with the same name as the original djot file
 	err = a.Fs.WriteFile(a.OutputPath, fullHtml)
 	if err != nil {
-		panic(err)
+		return fmt.Errorf("Failed to write to %s: %w", a.OutputPath, err)
 	}
+
+	return nil
 }
 
 func findArticles(fsys writablefs.FS, site *SiteMetadata) map[string]Article {