// Package render wires the HTML template engine: an embed.FS of templates
// (self-contained binary — no template files to ship beside it), a Render
// helper, and the FuncMap seam the template/format port fills in.
//
// The templates themselves are owned elsewhere (the Jinja -> html/template
// port); this package only establishes HOW they are parsed and executed:
//
// - Files live in internal/render/templates/*.tmpl and are named by base
// filename (html/template's ParseFS convention), e.g. "city.html.tmpl".
// Jinja's {% extends %}/{% block %} maps to {{define}}/{{template}} with a
// shared base layout; each page template {{define}}s the blocks the base
// references and is executed by its own file name.
// - Formatting helpers arrive via the FuncMap passed to New. html/template
// FuncMaps are engine-global, so anything unit-scoped (the Python used a
// ContextVar the handlers set per request — format.py's unit_scope) must
// NOT be a bare FuncMap closure over mutable state; either pre-format in
// the handler (content.py already did for most values) or pass a
// formatter value in the render data and call its methods.
//
// It also owns the response-side ETag behavior ported from content.py's
// _respond_html and app.py's _page: a weak SHA-1 ETag over the rendered
// bytes, and the two If-None-Match comparison flavors the Python had.
package render
import (
"bytes"
"crypto/sha1"
"embed"
"encoding/hex"
"fmt"
"html/template"
"io"
"net/http"
"strings"
)
//go:embed templates
var templatesFS embed.FS
// Engine holds the parsed template set. Templates only change on a redeploy
// (a fresh process), so everything is parsed exactly once, at New — the Go
// analogue of the Jinja environment's auto_reload=False.
type Engine struct {
t *template.Template
}
// New parses every embedded template with the given FuncMap. Call it once at
// boot with the full FuncMap — the function NAMES must all be registered
// before parsing, because html/template resolves them at parse time.
//
// The FuncMap keys the templates rely on (the Jinja globals/filters
// content.py registered; implementations come from the format port):
//
// "temp" func(f *float64) template.HTML — N°F
// "temp_bare" func(f *float64) template.HTML — bare-degree variant
// "temp_class" func(f *float64) string — diverging-palette tier name
// "ordinal" func(pct any) string — percentile -> "66th", floored into 1..99
// "head_verify" func() template.HTML — search-console tags
// "comment" func(s string) template.HTML — a visible that
// survives parsing (html/template strips literal HTML
// comments; wrapping the string template.HTML inserts it as
// trusted content instead of markup the parser interprets)
// "jscomment" func(s string) template.JS — a visible "// ..." line
// that survives inside