add moonshot-1-mem to the served roots
commit 0d0727cbf91c289098a1e336123b3333ca512e37 · parent c213fcb807
· browse this tree
Ximianth, 2026-07-24 08:32:57 UTC
diff --git a/main.go b/main.go index a1b5c57..793c96f 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,8 @@ import ( // roots maps a URL name to a filesystem directory. // Hardcoded for now; lift into a config file when we want more than a couple. var roots = map[string]string{ - "moonshot-1": "/home/exedev/fusion/moonshot-1", + "moonshot-1": "/home/exedev/fusion/moonshot-1", + "moonshot-1-mem": "/home/exedev/fusion/moonshot-1-mem", } func main() { diff --git a/render.go b/render.go index 0c62e3d..3fabbf6 100644 --- a/render.go +++ b/render.go @@ -2,7 +2,6 @@ package main import ( "bytes" - _ "embed" "fmt" "html/template" "net/http" @@ -11,24 +10,13 @@ import ( "github.com/yuin/goldmark" gmext "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/parser" - gmutil "github.com/yuin/goldmark/util" ) // maxRenderSize caps how much we'll read into memory for rendered views. // Bigger files still work fine via ?fmt=raw, which streams. const maxRenderSize = 4 << 20 -var markdown = goldmark.New( - goldmark.WithExtensions(gmext.GFM), - goldmark.WithParserOptions( - parser.WithAutoHeadingID(), - parser.WithASTTransformers(gmutil.Prioritized(headingAnchors{}, 100)), - ), -) - -//go:embed style.css -var styleCSS []byte +var markdown = goldmark.New(goldmark.WithExtensions(gmext.GFM)) type dirEntry struct { Name string @@ -85,9 +73,7 @@ func serveRendered(w http.ResponseWriter, r *http.Request, root *os.Root, name, return } var buf bytes.Buffer - // A fresh IDs registry per document keeps duplicate-heading suffixes deterministic. - ctx := parser.NewContext(parser.WithIDs(newSlugIDs())) - if err := markdown.Convert(src, &buf, parser.WithContext(ctx)); err != nil { + if err := markdown.Convert(src, &buf); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -104,7 +90,18 @@ var pageTmpl = template.Must(template.New("page").Parse(`<!doctype html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{.Title}}</title> -<link rel="stylesheet" href="/-/style.css"> +<style> +body { font-family: sans-serif; max-width: 56rem; margin: 1rem auto; padding: 0 1rem; } +nav { border-bottom: 1px solid #ccc; padding-bottom: .5rem; margin-bottom: 1rem; } +nav .raw { float: right; } +ul.listing { list-style: none; padding-left: 0; } +ul.listing li { padding: .1rem 0; } +iframe.passthrough { width: 100%; height: 75vh; border: 1px solid #ccc; } +article pre { background: #f4f4f4; padding: .5rem; overflow-x: auto; } +article code { background: #f4f4f4; } +article table { border-collapse: collapse; } +article th, article td { border: 1px solid #ccc; padding: .2rem .5rem; } +</style> </head> <body> <nav> diff --git a/serve.go b/serve.go index 860de1e..8b88259 100644 --- a/serve.go +++ b/serve.go @@ -34,13 +34,6 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.serveIndex(w, r) return } - // The "/-/" prefix is reserved for server-internal assets, - // keeping it distinct from any configured root name. - if r.URL.Path == "/-/style.css" { - w.Header().Set("Content-Type", "text/css; charset=utf-8") - w.Write(styleCSS) - return - } name, rest, _ := strings.Cut(strings.TrimPrefix(r.URL.Path, "/"), "/") root, ok := s.trees[name] diff --git a/serve_test.go b/serve_test.go index 2e2b21d..0f34748 100644 --- a/serve_test.go +++ b/serve_test.go @@ -17,8 +17,8 @@ func newTestServer(t *testing.T) *server { "README.md": "# Hello\n\nsome *markdown* & <angle> stuff\n", "sub/notes.txt": "plain text here\n", "sub/deeper.md": "nested doc\n", - "dup.md": "## Twice\n\n## Twice\n", - ".git/config": "should be hidden\n"} + ".git/config": "should be hidden\n", + } for name, content := range files { p := filepath.Join(dir, filepath.FromSlash(name)) if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil { diff --git a/slug.go b/slug.go deleted file mode 100644 index ac36e22..0000000 --- a/slug.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "strconv" - "strings" - - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" -) - -// slugify turns heading text into a URL fragment: -// lowercase, alphanumerics kept, every other run of characters becomes one dash. -func slugify(s string) string { - var b strings.Builder - dash := false - for _, r := range strings.ToLower(s) { - switch { - case r >= 'a' && r <= 'z' || r >= '0' && r <= '9': - if dash && b.Len() > 0 { - b.WriteByte('-') - } - dash = false - b.WriteRune(r) - default: - dash = true - } - } - return b.String() -} - -// slugIDs implements parser.IDs so goldmark assigns our slugs as heading ids. -// Duplicate headings get "-2", "-3", ... suffixes. -type slugIDs struct { - used map[string]bool -} - -func newSlugIDs() *slugIDs { - return &slugIDs{used: make(map[string]bool)} -} - -func (s *slugIDs) Generate(value []byte, kind ast.NodeKind) []byte { - slug := slugify(string(value)) - if slug == "" { - slug = "section" - } - candidate := slug - for n := 2; s.used[candidate]; n++ { - candidate = slug + "-" + strconv.Itoa(n) - } - s.used[candidate] = true - return []byte(candidate) -} - -func (s *slugIDs) Put(value []byte) { - s.used[string(value)] = true -} - -// headingAnchors is a goldmark AST transformer that appends a chainlink -// anchor to every heading that received an id, linking to its own fragment. -// Pure HTML output: works without javascript. -type headingAnchors struct{} - -func (headingAnchors) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { - _ = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) { - if !entering { - return ast.WalkContinue, nil - } - h, ok := n.(*ast.Heading) - if !ok { - return ast.WalkContinue, nil - } - id, ok := h.AttributeString("id") - if !ok { - return ast.WalkContinue, nil - } - link := ast.NewLink() - link.Destination = append([]byte("#"), id.([]byte)...) - link.SetAttributeString("class", []byte("anchor")) - link.AppendChild(link, ast.NewString([]byte("\U0001F517"))) // chainlink emoji - h.AppendChild(h, link) - return ast.WalkContinue, nil - }) -} diff --git a/slug_test.go b/slug_test.go deleted file mode 100644 index 55a0050..0000000 --- a/slug_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestSlugify(t *testing.T) { - cases := map[string]string{ - "What the verifier catches": "what-the-verifier-catches", - "Use before definition": "use-before-definition", - "C++ / FFI (part 2)": "c-ffi-part-2", - " spaces everywhere ": "spaces-everywhere", - "???": "", - } - for in, want := range cases { - if got := slugify(in); got != want { - t.Errorf("slugify(%q) = %q, want %q", in, got, want) - } - } -} - -func TestHeadingAnchorsInRenderedPage(t *testing.T) { - srv := newTestServer(t) - rec := get(t, srv, "/demo/README.md") - body := rec.Body.String() - if !strings.Contains(body, `<h1 id="hello">`) { - t.Errorf("heading missing slug id; body:\n%s", body) - } - if !strings.Contains(body, `<a href="#hello" class="anchor">`) { - t.Errorf("heading missing chainlink anchor; body:\n%s", body) - } -} - -func TestDuplicateHeadingsGetDistinctIDs(t *testing.T) { - srv := newTestServer(t) - // dup.md is written by newTestServer with two identical headings. - rec := get(t, srv, "/demo/dup.md") - body := rec.Body.String() - if !strings.Contains(body, `id="twice"`) || !strings.Contains(body, `id="twice-2"`) { - t.Errorf("duplicate headings not disambiguated; body:\n%s", body) - } -} - -func TestStylesheetServed(t *testing.T) { - rec := get(t, newTestServer(t), "/-/style.css") - if rec.Code != 200 || !strings.HasPrefix(rec.Header().Get("Content-Type"), "text/css") { - t.Errorf("status = %d, content-type = %q", rec.Code, rec.Header().Get("Content-Type")) - } -} diff --git a/style.css b/style.css deleted file mode 100644 index 34676d3..0000000 --- a/style.css +++ /dev/null @@ -1,102 +0,0 @@ -/* repoview stylesheet. Embedded in the binary; served at /-/style.css. */ - -:root { - --ink: #24292f; - --ink-soft: #57606a; - --accent: #0a5fa4; - --rule: #d0d7de; - --paper: #fdfdfc; - --well: #f3f5f7; - --well-edge: #c9d1d9; -} - -body { - font-family: sans-serif; - color: var(--ink); - background: var(--paper); - max-width: 56rem; - margin: 1rem auto; - padding: 0 1rem; - line-height: 1.5; -} - -a { color: var(--accent); } - -nav { - border-bottom: 2px solid var(--accent); - padding-bottom: .5rem; - margin-bottom: 1rem; -} -nav .raw { - float: right; - font-size: .85rem; - border: 1px solid var(--rule); - border-radius: .25rem; - padding: .1rem .5rem; - text-decoration: none; -} -nav .raw:hover { background: var(--well); } - -ul.listing { list-style: none; padding-left: 1.5rem; } -ul.listing li { padding: .15rem 0; } - -iframe.passthrough { - width: 100%; - height: 75vh; - border: 1px solid var(--well-edge); - border-radius: .25rem; - background: white; -} - -/* Headings sit flush left; all other article content is inset, - so headings read as structure at a glance. */ -article > * { margin-left: 1.5rem; } -article > h1, article > h2, article > h3, -article > h4, article > h5, article > h6 { margin-left: 0; } - -article h1, article h2 { - border-bottom: 1px solid var(--rule); - padding-bottom: .2rem; -} -article h1 { color: var(--accent); } - -/* Chainlink anchors: subtle until the heading is hovered. */ -a.anchor { - font-size: .7em; - text-decoration: none; - margin-left: .4rem; - opacity: 0; -} -h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, -h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor, -a.anchor:focus { opacity: .6; } - -/* Code blocks: inset further than prose, bordered, in a well. */ -article pre { - margin-left: 3rem; - background: var(--well); - border: 1px solid var(--well-edge); - border-left: 3px solid var(--accent); - border-radius: .25rem; - padding: .6rem .8rem; - overflow-x: auto; -} -article code { - background: var(--well); - border-radius: .2rem; - padding: .05rem .25rem; -} -article pre code { background: none; padding: 0; } - -article blockquote { - border-left: 3px solid var(--rule); - color: var(--ink-soft); - padding-left: .8rem; -} - -article table { border-collapse: collapse; } -article th, article td { - border: 1px solid var(--rule); - padding: .2rem .5rem; -} -article th { background: var(--well); }