content: Markdown render mode (asMarkdown) + 20 tests (263/263)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 45s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,8 @@
|
||||
(if (content/op? ops) (doc-apply doc ops) (doc-apply-all doc ops))))
|
||||
|
||||
;; ── render boundary ──
|
||||
;; fmt is "html"/"sx" (or :html/:sx — keywords evaluate to their name).
|
||||
;; fmt is "html"/"sx"/"md" (or :html/:sx/:md — keywords evaluate to their name).
|
||||
;; "md" needs markdown.sx loaded.
|
||||
(define
|
||||
content/render
|
||||
(fn
|
||||
@@ -57,6 +58,8 @@
|
||||
(cond
|
||||
((= fmt "html") (asHTML doc))
|
||||
((= fmt "sx") (asSx doc))
|
||||
((= fmt "md") (asMarkdown doc))
|
||||
((= fmt "markdown") (asMarkdown doc))
|
||||
(else (error (str "unknown render format: " fmt))))))
|
||||
|
||||
(define content/html asHTML)
|
||||
|
||||
@@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
SUITES=(block doc render api store crdt sync fed)
|
||||
SUITES=(block doc render api markdown store crdt sync fed)
|
||||
|
||||
OUT_JSON="lib/content/scoreboard.json"
|
||||
OUT_MD="lib/content/scoreboard.md"
|
||||
@@ -42,6 +42,7 @@ run_suite() {
|
||||
(load "lib/content/doc.sx")
|
||||
(load "lib/content/render.sx")
|
||||
(load "lib/content/api.sx")
|
||||
(load "lib/content/markdown.sx")
|
||||
(load "lib/content/store.sx")
|
||||
(load "lib/content/crdt.sx")
|
||||
(load "lib/content/sync.sx")
|
||||
|
||||
55
lib/content/markdown.sx
Normal file
55
lib/content/markdown.sx
Normal file
@@ -0,0 +1,55 @@
|
||||
;; content-on-sx — Markdown render mode.
|
||||
;;
|
||||
;; A third boundary format alongside asHTML / asSx, via the same polymorphic
|
||||
;; dispatch. The newline is supplied by the boundary as a keyword arg
|
||||
;; (asMarkdown: nl) because this Smalltalk dialect has no Character newline
|
||||
;; constructor — blocks that need internal newlines (code, lists, doc) use it.
|
||||
;;
|
||||
;; No Markdown escaping yet (Markdown's escaping rules differ from HTML); raw
|
||||
;; text is emitted. Ordered lists emit "1." for every item (Markdown renumbers).
|
||||
;;
|
||||
;; Requires (loaded by harness): block.sx, doc.sx.
|
||||
|
||||
(define
|
||||
content-bootstrap-markdown!
|
||||
(fn
|
||||
()
|
||||
(begin
|
||||
(ct-def-method!
|
||||
"CtHeading"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl | h i | h := ''. i := 0. [i < level] whileTrue: [h := h , '#'. i := i + 1]. ^ h , ' ' , text")
|
||||
(ct-def-method! "CtText" "asMarkdown:" "asMarkdown: nl ^ text")
|
||||
(ct-def-method!
|
||||
"CtCode"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl ^ '```' , language , nl , text , nl , '```'")
|
||||
(ct-def-method! "CtQuote" "asMarkdown:" "asMarkdown: nl ^ '> ' , text")
|
||||
(ct-def-method!
|
||||
"CtImage"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl ^ ''")
|
||||
(ct-def-method!
|
||||
"CtEmbed"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl ^ '[embed](' , url , ')'")
|
||||
(ct-def-method! "CtDivider" "asMarkdown:" "asMarkdown: nl ^ '---'")
|
||||
(ct-def-method!
|
||||
"CtList"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl | mark | mark := ordered ifTrue: ['1. '] ifFalse: ['- ']. ^ (items inject: '' into: [:a :x | a , (a = '' ifTrue: [''] ifFalse: [nl]) , mark , x])")
|
||||
(ct-def-method!
|
||||
"CtDoc"
|
||||
"asMarkdown:"
|
||||
"asMarkdown: nl ^ (blocks inject: '' into: [:a :b | a , (a = '' ifTrue: [''] ifFalse: [nl , nl]) , (b asMarkdown: nl)])")
|
||||
true)))
|
||||
|
||||
(define ct-nl (str "\n"))
|
||||
|
||||
;; ── SX boundary ──
|
||||
(define
|
||||
asMarkdown
|
||||
(fn (node) (str (st-send node "asMarkdown:" (list ct-nl)))))
|
||||
(define content/markdown asMarkdown)
|
||||
(define render-markdown asMarkdown)
|
||||
(define block-markdown asMarkdown)
|
||||
@@ -4,12 +4,13 @@
|
||||
"doc": {"pass": 40, "fail": 0},
|
||||
"render": {"pass": 42, "fail": 0},
|
||||
"api": {"pass": 26, "fail": 0},
|
||||
"markdown": {"pass": 20, "fail": 0},
|
||||
"store": {"pass": 29, "fail": 0},
|
||||
"crdt": {"pass": 34, "fail": 0},
|
||||
"sync": {"pass": 14, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 243,
|
||||
"total_pass": 263,
|
||||
"total_fail": 0,
|
||||
"total": 243
|
||||
"total": 263
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| doc | 40 | 0 | 40 |
|
||||
| render | 42 | 0 | 42 |
|
||||
| api | 26 | 0 | 26 |
|
||||
| markdown | 20 | 0 | 20 |
|
||||
| store | 29 | 0 | 29 |
|
||||
| crdt | 34 | 0 | 34 |
|
||||
| sync | 14 | 0 | 14 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **243** | **0** | **243** |
|
||||
| **Total** | **263** | **0** | **263** |
|
||||
|
||||
79
lib/content/tests/markdown.sx
Normal file
79
lib/content/tests/markdown.sx
Normal file
@@ -0,0 +1,79 @@
|
||||
;; Extension — Markdown render mode. asMarkdown is a polymorphic message send;
|
||||
;; the boundary supplies the newline.
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
(content-bootstrap-markdown!)
|
||||
|
||||
(define nl (str "\n"))
|
||||
|
||||
;; ── per-block ──
|
||||
(content-test
|
||||
"heading h3"
|
||||
(asMarkdown (mk-heading "h" 3 "Title"))
|
||||
"### Title")
|
||||
(content-test
|
||||
"heading h1"
|
||||
(asMarkdown (mk-heading "h" 1 "T"))
|
||||
"# T")
|
||||
(content-test "text md" (asMarkdown (mk-text "p" "body")) "body")
|
||||
(content-test
|
||||
"quote md"
|
||||
(asMarkdown (mk-quote "q" "Ada" "to err"))
|
||||
"> to err")
|
||||
(content-test
|
||||
"image md"
|
||||
(asMarkdown (mk-image "i" "/c.png" "cat"))
|
||||
"")
|
||||
(content-test
|
||||
"embed md"
|
||||
(asMarkdown (mk-embed "e" "https://v/1" "vimeo"))
|
||||
"[embed](https://v/1)")
|
||||
(content-test "divider md" (asMarkdown (mk-divider "d")) "---")
|
||||
(content-test
|
||||
"code md"
|
||||
(asMarkdown (mk-code "c" "sx" "(+ 1 2)"))
|
||||
(str "```sx" nl "(+ 1 2)" nl "```"))
|
||||
(content-test
|
||||
"ul md"
|
||||
(asMarkdown (mk-list "u" false (list "a" "b" "c")))
|
||||
(str "- a" nl "- b" nl "- c"))
|
||||
(content-test
|
||||
"ol md"
|
||||
(asMarkdown (mk-list "o" true (list "x" "y")))
|
||||
(str "1. x" nl "1. y"))
|
||||
(content-test "empty list md" (asMarkdown (mk-list "e" false (list))) "")
|
||||
|
||||
;; ── document joins blocks with a blank line ──
|
||||
(define
|
||||
d
|
||||
(doc-append
|
||||
(doc-append
|
||||
(doc-append (doc-empty "doc") (mk-heading "h" 2 "Title"))
|
||||
(mk-text "p" "Hello"))
|
||||
(mk-divider "d")))
|
||||
(content-test
|
||||
"doc md"
|
||||
(asMarkdown d)
|
||||
(str "## Title" nl nl "Hello" nl nl "---"))
|
||||
(content-test "empty doc md" (asMarkdown (doc-empty "e")) "")
|
||||
|
||||
;; ── via facade ──
|
||||
(content-test "render md" (content/render d "md") (asMarkdown d))
|
||||
(content-test "render markdown" (content/render d "markdown") (asMarkdown d))
|
||||
(content-test "render md keyword" (content/render d :md) (asMarkdown d))
|
||||
(content-test "content/markdown alias" (content/markdown d) (asMarkdown d))
|
||||
(content-test
|
||||
"block-markdown alias"
|
||||
(block-markdown (mk-heading "h" 2 "X"))
|
||||
"## X")
|
||||
|
||||
;; ── reflects edits / immutability ──
|
||||
(content-test
|
||||
"md after update"
|
||||
(asMarkdown (doc-update d "p" "text" "Edited"))
|
||||
(str "## Title" nl nl "Edited" nl nl "---"))
|
||||
(content-test
|
||||
"md original unchanged"
|
||||
(asMarkdown d)
|
||||
(str "## Title" nl nl "Hello" nl nl "---"))
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **243/243** (Phases 1–4 COMPLETE + extensions: HTML + SX escaping)
|
||||
`bash lib/content/conformance.sh` → **263/263** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render)
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -78,9 +78,17 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
## Extensions (post-roadmap)
|
||||
- [x] HTML escaping at the render boundary (`String>>htmlEscaped`: & < > ")
|
||||
- [x] asSx wire string-escaping (`String>>sxEscaped`: \ and " in SX literals)
|
||||
- [x] Markdown render mode (`asMarkdown:` / `content/render doc "md"`)
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Extension: Markdown render mode (`markdown.sx`). Third boundary
|
||||
format alongside asHTML/asSx via the same polymorphic dispatch; blocks answer
|
||||
`asMarkdown: nl` (boundary supplies the newline — this Smalltalk dialect has
|
||||
no Character newline ctor). `content/render doc "md"`/`"markdown"`/`:md`,
|
||||
`content/markdown`, `asMarkdown`. headings (`#`×level), fenced code, `> ` quote,
|
||||
``, `- `/`1. ` lists, `---`; doc joins blocks with a blank line. No
|
||||
MD escaping yet. 20 tests; suite 263/263.
|
||||
- 2026-06-07 — Extension: asSx wire string-escaping. Added `String>>sxEscaped`
|
||||
(escapes `\`→`\\` then `"`→`\"`) and routed every `asSx` text/attr/list-item
|
||||
through it, so the SX wire format stays valid when content contains quotes or
|
||||
|
||||
Reference in New Issue
Block a user