content: Markdown frontmatter -> metadata + 9 tests (469/469)
Some checks are pending
Test, Build, and Deploy / test-build-deploy (push) Waiting to run
Some checks are pending
Test, Build, and Deploy / test-build-deploy (push) Waiting to run
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
;; content-on-sx — Markdown import adapter (markdown text -> block document).
|
||||
;;
|
||||
;; A line-based parser, the inverse of markdown.sx's asMarkdown. Confined to the
|
||||
;; adapter boundary: the core knows nothing about Markdown. Handles ATX headings
|
||||
;; (#..######), fenced code (```lang), blockquotes (> ), unordered (- / * ) and
|
||||
;; ordered (1. ) lists, thematic breaks (--- / ***), pipe tables (header row +
|
||||
;; --- separator row + body rows), and paragraphs (consecutive plain lines joined
|
||||
;; with a space). Block ids are assigned sequentially b0,b1…
|
||||
;; adapter boundary: the core knows nothing about Markdown. Handles a leading
|
||||
;; --- frontmatter block (key: value -> doc metadata), ATX headings (#..######),
|
||||
;; fenced code (```lang), blockquotes (> ), unordered (- / * ) and ordered (1. )
|
||||
;; lists, thematic breaks (--- / ***), pipe tables (header + --- separator +
|
||||
;; body), and paragraphs (consecutive plain lines joined with a space). Block ids
|
||||
;; are assigned sequentially b0,b1…
|
||||
;;
|
||||
;; Requires (loaded by harness): block.sx, doc.sx, table.sx (mk-table); and
|
||||
;; markdown.sx for the adapter's export side.
|
||||
;; Requires (loaded by harness): block.sx, doc.sx, table.sx (mk-table),
|
||||
;; meta.sx (doc-with-meta); markdown.sx for the adapter's export side.
|
||||
|
||||
(define md/-id (fn (i) (str "b" i)))
|
||||
(define md/-blank? (fn (s) (= s "")))
|
||||
@@ -35,6 +36,18 @@
|
||||
md/-drop
|
||||
(fn (s prefix) (substring s (string-length prefix) (string-length s))))
|
||||
|
||||
(define
|
||||
md/-drop-n
|
||||
(fn
|
||||
(xs n)
|
||||
(if
|
||||
(= n 0)
|
||||
xs
|
||||
(if
|
||||
(= (len xs) 0)
|
||||
xs
|
||||
(md/-drop-n (rest xs) (- n 1))))))
|
||||
|
||||
(define
|
||||
md/-join-with
|
||||
(fn
|
||||
@@ -365,7 +378,72 @@
|
||||
md/parse
|
||||
(fn (text) (md/-walk (split text (str "\n")) 0 (list))))
|
||||
|
||||
;; ── frontmatter (leading --- key: value --- block) ──
|
||||
(define
|
||||
md/-frontmatter?
|
||||
(fn (lines) (and (> (len lines) 0) (= (first lines) "---"))))
|
||||
(define
|
||||
md/-fm-end
|
||||
(fn
|
||||
(lines i)
|
||||
(cond
|
||||
((>= i (len lines)) -1)
|
||||
((= (nth lines i) "---") i)
|
||||
(else (md/-fm-end lines (+ i 1))))))
|
||||
(define
|
||||
md/-fm-add
|
||||
(fn
|
||||
(acc line)
|
||||
(let
|
||||
((parts (split line ":")))
|
||||
(if
|
||||
(< (len parts) 2)
|
||||
acc
|
||||
(let
|
||||
((key (trim (first parts)))
|
||||
(val (trim (md/-join-with ":" (rest parts)))))
|
||||
(cond
|
||||
((= key "title") (assoc acc :title val))
|
||||
((= key "slug") (assoc acc :slug val))
|
||||
((= key "tags")
|
||||
(assoc acc :tags (map (fn (t) (trim t)) (split val ","))))
|
||||
(else acc)))))))
|
||||
(define
|
||||
md/-fm-pairs
|
||||
(fn
|
||||
(lines start end acc)
|
||||
(if
|
||||
(>= start end)
|
||||
acc
|
||||
(md/-fm-pairs
|
||||
lines
|
||||
(+ start 1)
|
||||
end
|
||||
(md/-fm-add acc (nth lines start))))))
|
||||
|
||||
;; ── adapter ──
|
||||
(define md/import (fn (text doc-id) (doc-new doc-id (md/parse text))))
|
||||
(define
|
||||
md/import
|
||||
(fn
|
||||
(text doc-id)
|
||||
(let
|
||||
((lines (split text (str "\n"))))
|
||||
(if
|
||||
(md/-frontmatter? lines)
|
||||
(let
|
||||
((end (md/-fm-end lines 1)))
|
||||
(if
|
||||
(= end -1)
|
||||
(doc-new doc-id (md/-walk lines 0 (list)))
|
||||
(doc-with-meta
|
||||
(doc-new
|
||||
doc-id
|
||||
(md/-walk
|
||||
(md/-drop-n lines (+ end 1))
|
||||
0
|
||||
(list)))
|
||||
(md/-fm-pairs lines 1 end {}))))
|
||||
(doc-new doc-id (md/-walk lines 0 (list)))))))
|
||||
|
||||
(define content/from-markdown md/import)
|
||||
(define markdown-adapter {:export (fn (doc) (asMarkdown doc)) :import md/import})
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
"crdt": {"pass": 34, "fail": 0},
|
||||
"crdt-store": {"pass": 14, "fail": 0},
|
||||
"sync": {"pass": 14, "fail": 0},
|
||||
"md-import": {"pass": 29, "fail": 0},
|
||||
"md-import": {"pass": 38, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 460,
|
||||
"total_pass": 469,
|
||||
"total_fail": 0,
|
||||
"total": 460
|
||||
"total": 469
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| crdt | 34 | 0 | 34 |
|
||||
| crdt-store | 14 | 0 | 14 |
|
||||
| sync | 14 | 0 | 14 |
|
||||
| md-import | 29 | 0 | 29 |
|
||||
| md-import | 38 | 0 | 38 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **460** | **0** | **460** |
|
||||
| **Total** | **469** | **0** | **469** |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
;; Extension — Markdown import adapter (markdown text -> blocks), inverse of
|
||||
;; asMarkdown. Round-trips canonical Markdown.
|
||||
;; asMarkdown. Round-trips canonical Markdown; parses frontmatter + tables.
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
@@ -130,6 +130,43 @@
|
||||
(doc-types dmix)
|
||||
(list "heading" "table" "text"))
|
||||
|
||||
;; ── frontmatter ──
|
||||
(define
|
||||
dfm
|
||||
(md/import
|
||||
(str
|
||||
"---"
|
||||
nl
|
||||
"title: My Post"
|
||||
nl
|
||||
"slug: my-post"
|
||||
nl
|
||||
"tags: a, b, c"
|
||||
nl
|
||||
"---"
|
||||
nl
|
||||
"# Hi"
|
||||
nl
|
||||
nl
|
||||
"body")
|
||||
"d"))
|
||||
(content-test "fm title" (doc-title dfm) "My Post")
|
||||
(content-test "fm slug" (doc-slug dfm) "my-post")
|
||||
(content-test "fm tags" (doc-tags dfm) (list "a" "b" "c"))
|
||||
(content-test "fm body types" (doc-types dfm) (list "heading" "text"))
|
||||
(content-test
|
||||
"fm body content"
|
||||
(str (blk-send (doc-find dfm "b0") "text"))
|
||||
"Hi")
|
||||
(content-test "no fm title nil" (doc-title (md/import "# Hi" "d")) nil)
|
||||
(content-test
|
||||
"hr not frontmatter"
|
||||
(doc-types (md/import (str "text" nl nl "---") "d"))
|
||||
(list "text" "divider"))
|
||||
(define dfmo (md/import (str "---" nl "title: T" nl "---") "d"))
|
||||
(content-test "fm only title" (doc-title dfmo) "T")
|
||||
(content-test "fm only empty body" (doc-ids dfmo) (list))
|
||||
|
||||
;; ── round-trip: import . export == identity (canonical markdown) ──
|
||||
(define
|
||||
src
|
||||
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **460/460** (Phases 1–4 COMPLETE + 13 extensions: HTML/SX escaping, Markdown render + import incl. tables, CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees, doc stats, table block, HTML page wrapper)
|
||||
`bash lib/content/conformance.sh` → **469/469** (Phases 1–4 COMPLETE + 13 extensions: HTML/SX escaping, Markdown render + import incl. tables & frontmatter, CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees, doc stats, table block, HTML page wrapper)
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -81,7 +81,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
- [x] Markdown render mode (`asMarkdown:` / `content/render doc "md"`)
|
||||
- [x] durable CRDT replication (`crdt-store.sx`: ops on persist, replay + converge)
|
||||
- [x] document validation (`validate.sx`: ids, per-type fields, duplicate ids; tree-aware — descends into sections, tree-wide dup ids, section field check)
|
||||
- [x] Markdown import adapter (`md-import.sx`: text → blocks, round-trips export; incl. pipe tables)
|
||||
- [x] Markdown import adapter (`md-import.sx`: text → blocks, round-trips export; incl. pipe tables + frontmatter → metadata)
|
||||
- [x] snapshot cache over replay (`snapshot.sx`: cache-not-primary, transparent)
|
||||
- [x] document metadata (`meta.sx`: title/slug/tags + Ghost title plumbing)
|
||||
- [x] plain-text render + excerpt (`text.sx`: asText, content/excerpt)
|
||||
@@ -92,6 +92,11 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Extension: Markdown frontmatter. `md/import` parses a leading
|
||||
`---` / `key: value` / `---` block into document metadata (title, slug,
|
||||
comma-separated tags via `doc-with-meta`) before parsing the body; a `---`
|
||||
elsewhere stays a divider. Ties the Markdown importer to the metadata layer the
|
||||
way real blog posts work. +9 tests; suite 469/469.
|
||||
- 2026-06-07 — Extension: Markdown table import. `md-import.sx` now recognizes a
|
||||
`| … |` header row followed by a `| --- |` separator and parses a `CtTable`
|
||||
(cells trimmed, mixed with other blocks via blank-line separation), completing
|
||||
|
||||
Reference in New Issue
Block a user