notes-on-water had become a kitchen sink: every feature from the last eight loops on one page, which demonstrates the engine and misrepresents the site. Now each lands where a real site would put it. The text marks, the abbreviation, authored <kbd> and the margin note go to writing/typography, which is already the page about what the engine does to words. Icons and the grouped panels go to pages/colophon, which is the page about the theme — install instructions are what tabs are actually for. about gets a lone expando, the-flood gets a chapter note, and notes-on-water keeps what it is really for: the include and its namespaced footnotes, the gauge table, the glossary and a contents list over its own headings. Seven bundles carry features now instead of two, and the demo cases moved with them, so each still names the page it proves.
22 KiB
Surface
Generated by scripts/surface.sh — do not edit. Regenerate with make surface.
Every top-level declaration in the engine, with its line. Read this before opening a source
file: it answers "where does X live" and "what is in this package" without the bodies. What each
file is for lives in state.md; why it is that way lives in decisions.md.
cmd/khosra — 305 lines + 63 test
check.go 45 · main.go 191 · new.go 42 · wire.go 27
- check.go:16 func runCheck(args []string)
- main.go:24 func main()
- main.go:45 func runServe()
- main.go:106 func pollInterval(dev bool, chosen time.Duration) time.Duration
- main.go:119 func watching(fsys fs.FS, every time.Duration, renderer *render.Renderer, rebuild func() int)
- main.go:136 func rebuilder(fsys fs.FS, cache string, reveal bool, live *atomic.Pointer[content.Site]) func() int
- main.go:162 func given(name string) bool
- main.go:174 func defaultCache() string
- main.go:184 func fatal(msg string, err error)
- new.go:12 func runNew(args []string)
- wire.go:17 func extenders(partial render.Partial) []goldmark.Extender
internal/content — 1042 lines + 537 test
clock.go 12 · content.go 450 · doc.go 5 · extras.go 92 · settings.go 59 · site.go 424
- clock.go:9 var now = time.Now
- clock.go:12 func Now() time.Time { return now() }
- content.go:20 const DefaultLang = "en"
- content.go:27 type Bundle struct
- content.go:70 func OpenSite(dir string) (fs.FS, error)
- content.go:82 type Problem struct
- content.go:94 func Scan(fsys fs.FS) ([]Bundle, error)
- content.go:103 func ScanReport(fsys fs.FS) ([]Bundle, []Problem, error)
- content.go:142 func Parse(name string, data []byte) (Bundle, error)
- content.go:182 func (b Bundle) Published(at time.Time) bool
- content.go:191 func (b Bundle) Assets() (string, bool)
- content.go:201 func stringList(v any) []string
- content.go:223 func asTime(v any) time.Time
- content.go:239 func asInt(v any) int
- content.go:252 func terms(v any) []string
- content.go:276 func TagSlug(tag string) string
- content.go:285 func Normalise(s string) string { return norm.NFC.String(s) }
- content.go:291 func splitName(name string) (key, lang string, ok bool)
- content.go:313 func isLangTag(s string) bool
- content.go:331 func isPartial(base string) bool
- content.go:343 func skipDir(base string) bool
- content.go:351 func splitFrontmatter(data []byte) (front, body []byte)
- content.go:366 func trimLeadingFence(data []byte, fence string) ([]byte, bool)
- content.go:383 func dropCollisions(all []Bundle) ([]Bundle, []Problem)
- content.go:405 const PerPage = 10
- content.go:411 func URL(key, lang string) string
- content.go:424 func TagURL(section, slug, lang string, page int) string
- content.go:434 const DerivedPrefix = "/derived/"
- content.go:437 func DerivedURL(name string) string { return DerivedPrefix + name }
- content.go:440 const TagsSegment = "tags"
- content.go:444 func PageURL(key, lang string, page int) string
- extras.go:14 const ExtrasDir = "extras"
- extras.go:17 type Entry struct
- extras.go:33 func Extras(fsys fs.FS, b Bundle) []Entry
- extras.go:67 func KindOf(name string) string
- extras.go:86 func ExtrasURL(route, lang, entry string) string
- settings.go:13 const SettingsFile = "site.yaml"
- settings.go:20 type Settings struct
- settings.go:33 func LoadSettings(fsys fs.FS) (Settings, error)
- settings.go:54 func Absolute(base, path string) string
- site.go:11 type Site struct
- site.go:28 func NewSite(bundles []Bundle) *Site
- site.go:45 func (s *Site) Reveal() { s.reveal = true }
- site.go:52 func (s *Site) visible(b Bundle) bool
- site.go:58 func (s *Site) Problems() []Problem { return s.problems }
- site.go:61 func (s *Site) note(path, detail string)
- site.go:72 func (s *Site) indexRoutes(bundles []Bundle)
- site.go:111 func (s *Site) KeyFor(route string) (string, bool)
- site.go:122 func (s *Site) RouteOf(key string) string
- site.go:130 func sorted(set map[string]bool) []string
- site.go:143 func (s *Site) indexAliases(bundles []Bundle)
- site.go:175 func (s *Site) Alias(alias string) (string, bool)
- site.go:184 func (s *Site) Lookup(key, lang string) (b Bundle, served string, ok bool)
- site.go:202 func (s *Site) Variants(key string) []string
- site.go:216 func (s *Site) HasLang(lang string) bool
- site.go:226 func (s *Site) Len() int { return len(s.byKeyLang) }
- site.go:232 type Query struct
- site.go:243 func (s *Site) Run(q Query) []Bundle
- site.go:268 func (s *Site) keys() []string
- site.go:284 type Sequence struct
- site.go:304 func (s *Site) Sequence(key, lang string) (Sequence, bool)
- site.go:325 func (s *Site) parent(key string) (string, bool)
- site.go:335 func (s *Site) has(key string) bool
- site.go:344 func (s *Site) members(series, lang string) []Bundle
- site.go:369 func (b Bundle) hasTag(slug string) bool
- site.go:382 func (b Bundle) Section() string
- site.go:394 func (s *Site) Everything() []Bundle
- site.go:407 func (s *Site) Sections() []string
internal/ext/check — 231 lines + 168 test
check.go 223 · doc.go 8
- check.go:16 type Finding struct
- check.go:30 func Run(fsys fs.FS, bundles []content.Bundle, site *content.Site, problems []content.Problem) []Finding
- check.go:53 func Fatal(found []Finding) bool
- check.go:63 func inspect(fsys fs.FS, b content.Bundle, site *content.Site) []Finding
- check.go:85 var figureCall = regexp.MustCompile(
(?m)^\s* figure\{([^}]*)\}\s*$) - check.go:89 var altArg = regexp.MustCompile(
alt=("[^"]+"|[^"\s}]+)) - check.go:95 func checkFigures(b content.Bundle) []Finding
- check.go:106 var internalLink = regexp.MustCompile(
\]\((/[^)\s"]*)) - check.go:112 func checkLinks(fsys fs.FS, b content.Bundle, site *content.Site) []Finding
- check.go:129 func engineOwned(target string) bool
- check.go:146 func resolves(fsys fs.FS, target string, from content.Bundle, site *content.Site) bool
- check.go:172 func asset(fsys fs.FS, trimmed string, site *content.Site) bool
- check.go:200 func mixedOrdering(bundles []content.Bundle, site *content.Site) []Finding
internal/ext/notation — 411 lines + 149 test
abbr.go 246 · doc.go 8 · notation.go 157
- abbr.go:17 var defined = parser.NewContextKey()
- abbr.go:21 var definitionKind = ast.NewNodeKind("AbbrDefinition")
- abbr.go:23 type definition struct{ ast.BaseBlock }
- abbr.go:25 func (n *definition) Kind() ast.NodeKind { return definitionKind }
- abbr.go:27 func (n *definition) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- abbr.go:30 var abbrKind = ast.NewNodeKind("Abbr")
- abbr.go:32 type abbr struct
- abbr.go:37 func (n *abbr) Kind() ast.NodeKind { return abbrKind }
- abbr.go:39 func (n *abbr) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- abbr.go:45 type definitionParser struct{}
- abbr.go:47 func (definitionParser) Trigger() []byte { return []byte{'*'} }
- abbr.go:49 func (definitionParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State)
- abbr.go:65 func (definitionParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State
- abbr.go:69 func (definitionParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {}
- abbr.go:71 func (definitionParser) CanInterruptParagraph() bool { return true }
- abbr.go:73 func (definitionParser) CanAcceptIndentedLine() bool { return false }
- abbr.go:77 func parseDefinition(line string) (term, expansion string, ok bool)
- abbr.go:101 type expand struct{}
- abbr.go:103 func (expand) Transform(doc *ast.Document, reader text.Reader, pc parser.Context)
- abbr.go:136 func replace(node *ast.Text, source []byte, terms map[string]string, ordered []string)
- abbr.go:172 func slice(seg text.Segment, from, to int) text.Segment
- abbr.go:180 func firstMatch(body string, ordered []string) (string, int)
- abbr.go:193 func earliest(body, term string) int
- abbr.go:209 func whole(body string, index, length int) bool
- abbr.go:226 func renderDefinition(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
- abbr.go:232 func renderAbbr(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
- abbr.go:243 func (nodeRenderer) registerAbbr(reg renderer.NodeRendererFuncRegisterer)
- notation.go:17 var marks = []mark{
- notation.go:27 type mark struct
- notation.go:33 func New() goldmark.Extender { return extension{} }
- notation.go:35 type extension struct{}
- notation.go:41 func (extension) Extend(md goldmark.Markdown)
- notation.go:56 var kind = ast.NewNodeKind("Notation")
- notation.go:59 type node struct
- notation.go:64 func (n *node) Kind() ast.NodeKind { return kind }
- notation.go:66 func (n *node) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- notation.go:70 type processor struct{ tag string }
- notation.go:72 func (p processor) IsDelimiter(b byte) bool { return b == '~' || b == '=' }
- notation.go:74 func (p processor) CanOpenCloser(opener, closer *parser.Delimiter) bool
- notation.go:78 func (p processor) OnMatch(consumes int) ast.Node { return &node{tag p.tag} }
- notation.go:81 type inlineParser struct{ m mark }
- notation.go:83 func (p inlineParser) Trigger() []byte { return []byte{p.m.char} }
- notation.go:85 func (p inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node
- notation.go:94 func (p inlineParser) doubled(block text.Reader, line []byte, segment text.Segment, pc parser.Context) ast.Node
- notation.go:113 func (p inlineParser) single(block text.Reader, line []byte, segment text.Segment) ast.Node
- notation.go:142 type nodeRenderer struct{}
- notation.go:144 func (r nodeRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
- notation.go:149 func render(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
internal/ext/scaffold — 102 lines + 89 test
doc.go 8 · scaffold.go 94
- scaffold.go:23 func New(siteDir, key, lang, title string) (string, error)
- scaffold.go:69 func frontmatter(title string) string
- scaffold.go:76 func titleFrom(key string) string
- scaffold.go:85 func mkdirAll(root *os.Root, dir string) error
internal/ext/shortcodes — 948 lines + 545 test
containers.go 117 · doc.go 7 · icons.go 125 · images.go 250 · shortcodes.go 371 · toc.go 78
- containers.go:19 var containerKind = ast.NewNodeKind("ShortcodeContainer")
- containers.go:21 type container struct
- containers.go:30 func (n *container) Kind() ast.NodeKind { return containerKind }
- containers.go:32 func (n *container) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- containers.go:35 type containers struct{}
- containers.go:37 func (containers) Trigger() []byte { return []byte{' '} }
- containers.go:39 func (containers) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State)
- containers.go:53 func (containers) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State
- containers.go:62 func (containers) Close(node ast.Node, reader text.Reader, pc parser.Context) {}
- containers.go:64 func (containers) CanInterruptParagraph() bool { return true }
- containers.go:66 func (containers) CanAcceptIndentedLine() bool { return false }
- containers.go:72 type bodies struct
- containers.go:76 func (b bodies) Transform(doc *ast.Document, reader text.Reader, pc parser.Context)
- containers.go:100 func (f fragments) renderContainer(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
- icons.go:19 const iconFragment = "icon"
- icons.go:27 type icons struct{}
- icons.go:29 func (icons) Trigger() []byte { return []byte{' '} }
- icons.go:31 func (icons) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node
- icons.go:49 var iconKind = ast.NewNodeKind("ShortcodeIcon")
- icons.go:51 type iconNode struct
- icons.go:57 func (n *iconNode) Kind() ast.NodeKind { return iconKind }
- icons.go:59 func (n *iconNode) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- icons.go:63 func (f fragments) renderIcon(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
- icons.go:83 func iconName(line []byte) (string, int, bool)
- icons.go:113 func isNameByte(c byte, first bool) bool
- icons.go:125 func isWordRune(r rune) bool { return unicode.IsLetter(r) || unicode.IsDigit(r) }
- images.go:31 var widths = []int{480, 960, 1440}
- images.go:38 func Derive(siteFS fs.FS, cacheDir string) (int, error)
- images.go:76 func derive(data []byte, name, cacheDir string) (int, error)
- images.go:103 func scale(src image.Image, width int) image.Image
- images.go:115 func write(file string, img image.Image, source string) error
- images.go:133 func encode(w *os.File, img image.Image, source string) error
- images.go:149 var
- images.go:159 func picture(origin render.Origin, file string) (render.Picture, bool)
- images.go:214 func derivedName(data []byte, source string, width int) string
- images.go:224 func lossless(source string) bool
- images.go:234 func showable(name string) bool
- images.go:244 func derivable(name string) bool
- shortcodes.go:28 const
- shortcodes.go:37 func New(partial render.Partial) goldmark.Extender
- shortcodes.go:41 type extension struct
- shortcodes.go:50 func (e extension) Extend(md goldmark.Markdown)
- shortcodes.go:69 var nested = parser.NewContextKey()
- shortcodes.go:74 var includedAs = parser.NewContextKey()
- shortcodes.go:76 const footnoteKey = "khosra footnote-prefix"
- shortcodes.go:84 func FootnotePrefix(n ast.Node) []byte
- shortcodes.go:102 type includes struct
- shortcodes.go:106 func (in includes) Transform(doc *ast.Document, reader text.Reader, pc parser.Context)
- shortcodes.go:130 func (in includes) convert(name string, pc parser.Context) ([]byte, error)
- shortcodes.go:161 func pending(doc *ast.Document) []*node
- shortcodes.go:179 var kind = ast.NewNodeKind("Shortcode")
- shortcodes.go:182 type node struct
- shortcodes.go:198 func (n *node) Kind() ast.NodeKind { return kind }
- shortcodes.go:200 func (n *node) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
- shortcodes.go:203 type blocks struct{}
- shortcodes.go:205 func (blocks) Trigger() []byte { return []byte{' '} }
- shortcodes.go:207 func (blocks) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State)
- shortcodes.go:238 func gallery(pc parser.Context) []render.Picture
- shortcodes.go:265 func (blocks) Continue(n ast.Node, reader text.Reader, pc parser.Context) parser.State
- shortcodes.go:269 func (blocks) Close(n ast.Node, reader text.Reader, pc parser.Context) {}
- shortcodes.go:271 func (blocks) CanInterruptParagraph() bool { return true }
- shortcodes.go:273 func (blocks) CanAcceptIndentedLine() bool { return false }
- shortcodes.go:276 type fragments struct
- shortcodes.go:280 func (f fragments) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
- shortcodes.go:290 func (f fragments) render(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error)
- shortcodes.go:318 func parse(line, prefix string) (name string, args map[string]string, ok bool)
- shortcodes.go:353 func argument(s string) (key, value, rest string, ok bool)
- toc.go:15 const tocName = "toc"
- toc.go:22 type tables struct{}
- toc.go:24 func (tables) Transform(doc *ast.Document, reader text.Reader, pc parser.Context)
- toc.go:52 func headingText(heading *ast.Heading, source []byte) string
- toc.go:67 func headingID(heading *ast.Heading) string
- toc.go:78 var _ parser.ASTTransformer = tables{}
internal/ext/watch — 133 lines + 114 test
doc.go 8 · watch.go 125
- watch.go:16 type Changed func()
- watch.go:32 func Watch(fsys fs.FS, every, settle time.Duration, stop <-chan struct{}, onChange Changed)
- watch.go:76 func Fingerprint(fsys fs.FS) string
- watch.go:90 func record(sum hash.Hash, p string, d fs.DirEntry, err error) error
- watch.go:113 func dropping(name string) bool
internal/render — 723 lines + 489 test
chrome.go 110 · render.go 483 · view.go 130
- chrome.go:19 var chrome = map[string]map[string]string{
- chrome.go:33 var months = map[string][]string{
- chrome.go:41 var digits = map[string][]rune{
- chrome.go:47 var funcs = template.FuncMap{"t" text, "num" numerals, "day" day}
- chrome.go:53 func text(lang, key string, args ...string) string
- chrome.go:73 func numerals(lang string, n int) string
- chrome.go:82 func day(lang string, t time.Time) string
- chrome.go:98 func localiseDigits(lang, s string) string
- render.go:26 var themeFS embed.FS
- render.go:30 type Renderer struct
- render.go:49 type parsedTheme struct
- render.go:63 type Partial func(name string, data Fragment) ([]byte, error)
- render.go:66 type Fragment struct
- render.go:80 type Heading struct
- render.go:86 type Picture struct
- render.go:103 type Origin struct
- render.go:112 var originKey = parser.NewContextKey()
- render.go:115 func OriginFrom(pc parser.Context) (Origin, bool)
- render.go:122 func WithOrigin(pc parser.Context, origin Origin)
- render.go:135 func New(siteFS fs.FS, settings content.Settings, extend func(Partial) []goldmark.Extender) (*Renderer, error)
- render.go:166 func parseTheme(siteFS fs.FS) (*parsedTheme, error)
- render.go:194 func (r *Renderer) head(title, lang, canonical string) head
- render.go:211 func (r *Renderer) absolute(path string) string
- render.go:219 func (r *Renderer) Navigation(sections func() []string) { r.sections = sections }
- render.go:231 func (r *Renderer) Refresh() error
- render.go:242 func (r *Renderer) Partial(name string, data Fragment) ([]byte, error)
- render.go:261 func parseSet(siteFS fs.FS, names ...string) (*template.Template, error)
- render.go:283 func readStyle(siteFS fs.FS) (template.CSS, error)
- render.go:301 func (r *Renderer) Extras(b content.Bundle, served string, entries []content.Entry, selected *Selected) ([]byte, error)
- render.go:322 func (r *Renderer) RenderText(kind string, data []byte) (template.HTML, error)
- render.go:340 func (r *Renderer) Bundle(b content.Bundle, served string, variants []string, seq *content.Sequence) ([]byte, error)
- render.go:377 func (r *Renderer) Listing(section, lang string, all []content.Bundle, page int) ([]byte, error)
- render.go:396 func (r *Renderer) Tag(section, slug, lang string, all []content.Bundle, page int) ([]byte, error)
- render.go:423 func (r *Renderer) sequence(seq *content.Sequence, lang string) *Sequence
- render.go:450 func (r *Renderer) item(b content.Bundle, lang string) Item
- render.go:455 func (r *Renderer) paginate(title, lang, canonical string, all []content.Bundle, page int, url func(int) string) (List, []content.Bundle)
- render.go:477 func (r *Renderer) execute(set *template.Template, data any, what string) ([]byte, error)
- view.go:16 type head struct
- view.go:36 type Page struct
- view.go:55 type Sequence struct
- view.go:71 type Extras struct
- view.go:82 type Selected struct
- view.go:91 type List struct
- view.go:105 type Group struct
- view.go:111 type Item struct
- view.go:122 type Alternate struct
internal/web — 734 lines + 1622 test
asset.go 58 · discover.go 71 · extras.go 93 · feed.go 125 · resolve.go 170 · web.go 217
- asset.go:22 func serveAsset(w http.ResponseWriter, req *http.Request, site *content.Site, siteFS fs.FS, res resolution) bool
- discover.go:14 const
- discover.go:24 func serveRobots(w http.ResponseWriter, req *http.Request, siteFS fs.FS, base string)
- discover.go:45 func serveSitemap(w http.ResponseWriter, req *http.Request, site *content.Site, base string)
- discover.go:67 func xmlEscape(s string) string
- extras.go:18 func serveExtras(w http.ResponseWriter, req *http.Request, site *content.Site, r *render.Renderer,
- extras.go:69 func renderExtras(w http.ResponseWriter, r *render.Renderer, b content.Bundle, served string,
- extras.go:85 func find(entries []content.Entry, want string) (content.Entry, bool)
- feed.go:13 const feedFile = "feed.xml"
- feed.go:17 const feedMax = 20
- feed.go:24 type atom struct
- feed.go:34 type atomLink struct
- feed.go:40 type atomEntry struct
- feed.go:52 func serveFeed(w http.ResponseWriter, req *http.Request, site *content.Site, res resolution, settings content.Settings) bool
- feed.go:102 func dated(all []content.Bundle) []content.Bundle
- feed.go:113 func feedTitle(settings content.Settings, res resolution) string
- resolve.go:12 type resolution struct
- resolve.go:40 func resolve(path string, site *content.Site) (resolution, bool)
- resolve.go:102 func cutLang(key string, site *content.Site) (lang, rest, redirect string)
- resolve.go:117 func cutExtras(key string) (bundle, entry string, ok bool)
- resolve.go:131 func cutFeed(key string) (rest string, ok bool)
- resolve.go:142 func cutTag(key string) (tag, section string, ok bool)
- resolve.go:154 func cutPage(key string) (rest string, page int, ok bool)
- web.go:19 type Current func() *content.Site
- web.go:22 func Fixed(site *content.Site) Current { return func() *content.Site { return site } }
- web.go:27 func Handler(current Current, r *render.Renderer, siteFS, derivedFS fs.FS, settings content.Settings) http.Handler
- web.go:60 func serveStatic(sub fs.FS) http.Handler
- web.go:76 func serveListing(w http.ResponseWriter, req *http.Request, site *content.Site, r *render.Renderer, res resolution) bool
- web.go:100 func serveTags(w http.ResponseWriter, req *http.Request, site *content.Site, r *render.Renderer, res resolution) bool
- web.go:125 func write(w http.ResponseWriter, out []byte, what string)
- web.go:134 func writeAs(w http.ResponseWriter, contentType string, out []byte, what string)
- web.go:142 func serve(w http.ResponseWriter, req *http.Request, site *content.Site, r *render.Renderer, siteFS fs.FS, settings content.Settings)
- web.go:173 func serveBundle(w http.ResponseWriter, req *http.Request, site *content.Site, r *render.Renderer,