content: Markdown table import + 5 tests (round-trip, 460/460)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 02:37:02 +00:00
parent 950ca71a48
commit 7610da1d6d
5 changed files with 166 additions and 11 deletions

View File

@@ -3,11 +3,12 @@
;; A line-based parser, the inverse of markdown.sx's asMarkdown. Confined to the ;; 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 ;; adapter boundary: the core knows nothing about Markdown. Handles ATX headings
;; (#..######), fenced code (```lang), blockquotes (> ), unordered (- / * ) and ;; (#..######), fenced code (```lang), blockquotes (> ), unordered (- / * ) and
;; ordered (1. ) lists, thematic breaks (--- / ***), and paragraphs (consecutive ;; ordered (1. ) lists, thematic breaks (--- / ***), pipe tables (header row +
;; plain lines joined with a space). Block ids are assigned sequentially b0,b1… ;; --- separator row + body rows), and paragraphs (consecutive plain lines joined
;; with a space). Block ids are assigned sequentially b0,b1…
;; ;;
;; Requires (loaded by harness): block.sx, doc.sx (and markdown.sx for the ;; Requires (loaded by harness): block.sx, doc.sx, table.sx (mk-table); and
;; adapter's export side). ;; markdown.sx for the adapter's export side.
(define md/-id (fn (i) (str "b" i))) (define md/-id (fn (i) (str "b" i)))
(define md/-blank? (fn (s) (= s ""))) (define md/-blank? (fn (s) (= s "")))
@@ -118,6 +119,70 @@
md/-drop-ul md/-drop-ul
(fn (line) (substring line 2 (string-length line)))) (fn (line) (substring line 2 (string-length line))))
;; ── table detection ──
(define md/-pipe-row? (fn (line) (ct-starts-with? (trim line) "|")))
(define md/-sep-char? (fn (ch) (ct-in? ch (list "-" ":" "|" " "))))
(define
md/-all-sep?
(fn
(s i)
(if
(>= i (string-length s))
true
(if
(md/-sep-char? (substring s i (+ i 1)))
(md/-all-sep? s (+ i 1))
false))))
(define
md/-has-dash?
(fn
(s i)
(if
(>= i (string-length s))
false
(if
(= (substring s i (+ i 1)) "-")
true
(md/-has-dash? s (+ i 1))))))
(define
md/-sep-row?
(fn
(line)
(and
(md/-pipe-row? line)
(md/-all-sep? (trim line) 0)
(md/-has-dash? line 0))))
(define
md/-table-start?
(fn
(lines)
(and
(md/-pipe-row? (first lines))
(> (len lines) 1)
(md/-sep-row? (nth lines 1)))))
(define
md/-strip-pipes
(fn
(s0)
(let
((s (trim s0)))
(let
((a (if (ct-starts-with? s "|") (substring s 1 (string-length s)) s)))
(if
(and
(> (string-length a) 0)
(=
(substring
a
(- (string-length a) 1)
(string-length a))
"|"))
(substring a 0 (- (string-length a) 1))
a)))))
(define
md/-cells
(fn (line) (map (fn (c) (trim c)) (split (md/-strip-pipes line) "|"))))
(define (define
md/-plain? md/-plain?
(fn (fn
@@ -168,6 +233,41 @@
(else (else
(md/-code-collect (rest lines) lang (cons (first lines) body) i acc))))) (md/-code-collect (rest lines) lang (cons (first lines) body) i acc)))))
(define
md/-table-body
(fn
(lines headers rows i acc)
(if
(= (len lines) 0)
(md/-walk
lines
(+ i 1)
(cons (mk-table (md/-id i) headers (reverse rows)) acc))
(let
((line (first lines)))
(if
(md/-pipe-row? line)
(md/-table-body
(rest lines)
headers
(cons (md/-cells line) rows)
i
acc)
(md/-walk
lines
(+ i 1)
(cons (mk-table (md/-id i) headers (reverse rows)) acc)))))))
(define
md/-table
(fn
(lines i acc)
(md/-table-body
(rest (rest lines))
(md/-cells (first lines))
(list)
i
acc)))
(define (define
md/-list-collect md/-list-collect
(fn (fn
@@ -256,6 +356,7 @@
(rest lines) (rest lines)
(+ i 1) (+ i 1)
(cons (mk-divider (md/-id i)) acc))) (cons (mk-divider (md/-id i)) acc)))
((md/-table-start? lines) (md/-table lines i acc))
((md/-ul? line) (md/-list-collect lines (list) i acc false)) ((md/-ul? line) (md/-list-collect lines (list) i acc false))
((md/-ol? line) (md/-list-collect lines (list) i acc true)) ((md/-ol? line) (md/-list-collect lines (list) i acc true))
(else (md/-para-collect lines (list) i acc))))))) (else (md/-para-collect lines (list) i acc)))))))

View File

@@ -17,10 +17,10 @@
"crdt": {"pass": 34, "fail": 0}, "crdt": {"pass": 34, "fail": 0},
"crdt-store": {"pass": 14, "fail": 0}, "crdt-store": {"pass": 14, "fail": 0},
"sync": {"pass": 14, "fail": 0}, "sync": {"pass": 14, "fail": 0},
"md-import": {"pass": 24, "fail": 0}, "md-import": {"pass": 29, "fail": 0},
"fed": {"pass": 20, "fail": 0} "fed": {"pass": 20, "fail": 0}
}, },
"total_pass": 455, "total_pass": 460,
"total_fail": 0, "total_fail": 0,
"total": 455 "total": 460
} }

View File

@@ -21,6 +21,6 @@ _Generated by `lib/content/conformance.sh`_
| crdt | 34 | 0 | 34 | | crdt | 34 | 0 | 34 |
| crdt-store | 14 | 0 | 14 | | crdt-store | 14 | 0 | 14 |
| sync | 14 | 0 | 14 | | sync | 14 | 0 | 14 |
| md-import | 24 | 0 | 24 | | md-import | 29 | 0 | 29 |
| fed | 20 | 0 | 20 | | fed | 20 | 0 | 20 |
| **Total** | **455** | **0** | **455** | | **Total** | **460** | **0** | **460** |

View File

@@ -4,6 +4,7 @@
(st-bootstrap-classes!) (st-bootstrap-classes!)
(content/bootstrap!) (content/bootstrap!)
(content-bootstrap-markdown!) (content-bootstrap-markdown!)
(content-bootstrap-table!)
(define nl (str "\n")) (define nl (str "\n"))
@@ -81,6 +82,54 @@
(doc-ids (md/import (str nl nl) "d")) (doc-ids (md/import (str nl nl) "d"))
(list)) (list))
;; ── pipe tables ──
(define
dt
(md/import
(str
"| Name | Age |"
nl
"| --- | --- |"
nl
"| Ada | 36 |"
nl
"| Al | 40 |")
"d"))
(content-test "table import type" (doc-types dt) (list "table"))
(content-test
"table headers"
(table-headers (doc-find dt "b0"))
(list "Name" "Age"))
(content-test
"table rows"
(table-rows (doc-find dt "b0"))
(list (list "Ada" "36") (list "Al" "40")))
(content-test
"table round-trip"
(asMarkdown
(md/import (str "| A | B |" nl "| --- | --- |" nl "| 1 | 2 |") "d"))
(str "| A | B |" nl "| --- | --- |" nl "| 1 | 2 |"))
(define
dmix
(md/import
(str
"# Title"
nl
nl
"| H1 | H2 |"
nl
"| --- | --- |"
nl
"| a | b |"
nl
nl
"para")
"d"))
(content-test
"table mixed types"
(doc-types dmix)
(list "heading" "table" "text"))
;; ── round-trip: import . export == identity (canonical markdown) ── ;; ── round-trip: import . export == identity (canonical markdown) ──
(define (define
src src

View File

@@ -19,7 +19,7 @@ injected adapter, not core.
## Status (rolling) ## Status (rolling)
`bash lib/content/conformance.sh`**455/455** (Phases 14 COMPLETE + 13 extensions: HTML/SX escaping, Markdown render+import, 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`**460/460** (Phases 14 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)
## Ground rules ## 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] Markdown render mode (`asMarkdown:` / `content/render doc "md"`)
- [x] durable CRDT replication (`crdt-store.sx`: ops on persist, replay + converge) - [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] 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) - [x] Markdown import adapter (`md-import.sx`: text → blocks, round-trips export; incl. pipe tables)
- [x] snapshot cache over replay (`snapshot.sx`: cache-not-primary, transparent) - [x] snapshot cache over replay (`snapshot.sx`: cache-not-primary, transparent)
- [x] document metadata (`meta.sx`: title/slug/tags + Ghost title plumbing) - [x] document metadata (`meta.sx`: title/slug/tags + Ghost title plumbing)
- [x] plain-text render + excerpt (`text.sx`: asText, content/excerpt) - [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 ## Progress log
- 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
the Markdown table round-trip (import∘export == identity). +5 tests; suite
460/460.
- 2026-06-07 — Extension: HTML page wrapper (`page.sx`). `content/page` composes - 2026-06-07 — Extension: HTML page wrapper (`page.sx`). `content/page` composes
metadata + render into a minimal valid HTML5 document — escaped `<title>` from metadata + render into a minimal valid HTML5 document — escaped `<title>` from
doc metadata (falling back to id) and the rendered blocks as the body. doc metadata (falling back to id) and the rendered blocks as the body.