content: plain-text render + excerpt (text.sx) + 20 tests (385/385)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 36s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 36s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,8 +49,8 @@
|
||||
(if (content/op? ops) (doc-apply doc ops) (doc-apply-all doc ops))))
|
||||
|
||||
;; ── render boundary ──
|
||||
;; fmt is "html"/"sx"/"md" (or :html/:sx/:md — keywords evaluate to their name).
|
||||
;; "md" needs markdown.sx loaded.
|
||||
;; fmt is "html"/"sx"/"md"/"text" (or the matching keyword). "md" needs
|
||||
;; markdown.sx loaded; "text" needs text.sx loaded.
|
||||
(define
|
||||
content/render
|
||||
(fn
|
||||
@@ -60,6 +60,7 @@
|
||||
((= fmt "sx") (asSx doc))
|
||||
((= fmt "md") (asMarkdown doc))
|
||||
((= fmt "markdown") (asMarkdown doc))
|
||||
((= fmt "text") (asText 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 meta markdown validate store snapshot crdt crdt-store sync md-import fed)
|
||||
SUITES=(block doc render api meta markdown text validate store snapshot crdt crdt-store sync md-import fed)
|
||||
|
||||
OUT_JSON="lib/content/scoreboard.json"
|
||||
OUT_MD="lib/content/scoreboard.md"
|
||||
@@ -43,6 +43,7 @@ run_suite() {
|
||||
(load "lib/content/render.sx")
|
||||
(load "lib/content/api.sx")
|
||||
(load "lib/content/meta.sx")
|
||||
(load "lib/content/text.sx")
|
||||
(load "lib/content/markdown.sx")
|
||||
(load "lib/content/validate.sx")
|
||||
(load "lib/content/store.sx")
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"api": {"pass": 26, "fail": 0},
|
||||
"meta": {"pass": 27, "fail": 0},
|
||||
"markdown": {"pass": 20, "fail": 0},
|
||||
"text": {"pass": 20, "fail": 0},
|
||||
"validate": {"pass": 17, "fail": 0},
|
||||
"store": {"pass": 29, "fail": 0},
|
||||
"snapshot": {"pass": 20, "fail": 0},
|
||||
@@ -15,7 +16,7 @@
|
||||
"md-import": {"pass": 24, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 365,
|
||||
"total_pass": 385,
|
||||
"total_fail": 0,
|
||||
"total": 365
|
||||
"total": 385
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| api | 26 | 0 | 26 |
|
||||
| meta | 27 | 0 | 27 |
|
||||
| markdown | 20 | 0 | 20 |
|
||||
| text | 20 | 0 | 20 |
|
||||
| validate | 17 | 0 | 17 |
|
||||
| store | 29 | 0 | 29 |
|
||||
| snapshot | 20 | 0 | 20 |
|
||||
@@ -18,4 +19,4 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| sync | 14 | 0 | 14 |
|
||||
| md-import | 24 | 0 | 24 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **365** | **0** | **365** |
|
||||
| **Total** | **385** | **0** | **385** |
|
||||
|
||||
72
lib/content/tests/text.sx
Normal file
72
lib/content/tests/text.sx
Normal file
@@ -0,0 +1,72 @@
|
||||
;; Extension — plain-text render mode + excerpts.
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
(content-bootstrap-text!)
|
||||
|
||||
;; ── per-block ──
|
||||
(content-test
|
||||
"heading text"
|
||||
(asText (mk-heading "h" 2 "Title"))
|
||||
"Title")
|
||||
(content-test "paragraph text" (asText (mk-text "p" "Body")) "Body")
|
||||
(content-test "code text" (asText (mk-code "c" "sx" "(+ 1 2)")) "(+ 1 2)")
|
||||
(content-test "quote text" (asText (mk-quote "q" "Ada" "to err")) "to err")
|
||||
(content-test
|
||||
"image -> alt"
|
||||
(asText (mk-image "i" "/c.png" "a cat"))
|
||||
"a cat")
|
||||
(content-test
|
||||
"embed -> empty"
|
||||
(asText (mk-embed "e" "https://v" "vimeo"))
|
||||
"")
|
||||
(content-test "divider -> empty" (asText (mk-divider "d")) "")
|
||||
(content-test
|
||||
"list -> joined"
|
||||
(asText (mk-list "l" false (list "a" "b" "c")))
|
||||
"a, b, c")
|
||||
(content-test "empty list -> empty" (asText (mk-list "l" false (list))) "")
|
||||
|
||||
;; ── document joins non-empty child texts with a space ──
|
||||
(define
|
||||
d
|
||||
(doc-append
|
||||
(doc-append
|
||||
(doc-append
|
||||
(doc-append (doc-empty "d") (mk-heading "h" 1 "Title"))
|
||||
(mk-text "p" "Hello world"))
|
||||
(mk-divider "dv"))
|
||||
(mk-list "l" true (list "x" "y"))))
|
||||
(content-test "doc text skips empties" (asText d) "Title Hello world x, y")
|
||||
(content-test "empty doc text" (asText (doc-empty "e")) "")
|
||||
|
||||
;; ── via facade ──
|
||||
(content-test "render text" (content/render d "text") (asText d))
|
||||
(content-test "render text keyword" (content/render d :text) (asText d))
|
||||
(content-test "content/text alias" (content/text d) (asText d))
|
||||
(content-test "block-text alias" (block-text (mk-text "p" "x")) "x")
|
||||
|
||||
;; ── excerpt ──
|
||||
(content-test
|
||||
"excerpt under limit"
|
||||
(content/excerpt d 100)
|
||||
"Title Hello world x, y")
|
||||
(content-test "excerpt truncates" (content/excerpt d 5) "Title…")
|
||||
(content-test
|
||||
"excerpt exact length"
|
||||
(content/excerpt
|
||||
(doc-append (doc-empty "e") (mk-text "p" "12345"))
|
||||
5)
|
||||
"12345")
|
||||
(content-test
|
||||
"excerpt one over"
|
||||
(content/excerpt
|
||||
(doc-append (doc-empty "e") (mk-text "p" "123456"))
|
||||
5)
|
||||
"12345…")
|
||||
|
||||
;; ── reflects edits ──
|
||||
(content-test
|
||||
"text after update"
|
||||
(asText (doc-update d "p" "text" "Changed"))
|
||||
"Title Changed x, y")
|
||||
46
lib/content/text.sx
Normal file
46
lib/content/text.sx
Normal file
@@ -0,0 +1,46 @@
|
||||
;; content-on-sx — plain-text render mode + excerpts.
|
||||
;;
|
||||
;; A fourth boundary format via polymorphic dispatch: blocks answer asText,
|
||||
;; stripping all markup. Useful for search indexing, meta descriptions and
|
||||
;; previews. The document joins non-empty child texts with a single space.
|
||||
;;
|
||||
;; Requires (loaded by harness): block.sx, doc.sx.
|
||||
|
||||
(define
|
||||
content-bootstrap-text!
|
||||
(fn
|
||||
()
|
||||
(begin
|
||||
(ct-def-method! "CtHeading" "asText" "asText ^ text")
|
||||
(ct-def-method! "CtText" "asText" "asText ^ text")
|
||||
(ct-def-method! "CtCode" "asText" "asText ^ text")
|
||||
(ct-def-method! "CtQuote" "asText" "asText ^ text")
|
||||
(ct-def-method! "CtImage" "asText" "asText ^ alt")
|
||||
(ct-def-method! "CtEmbed" "asText" "asText ^ ''")
|
||||
(ct-def-method! "CtDivider" "asText" "asText ^ ''")
|
||||
(ct-def-method!
|
||||
"CtList"
|
||||
"asText"
|
||||
"asText ^ (items inject: '' into: [:a :x | (a = '' ifTrue: [x] ifFalse: [a , ', ' , x])])")
|
||||
(ct-def-method!
|
||||
"CtDoc"
|
||||
"asText"
|
||||
"asText ^ (blocks inject: '' into: [:a :b | (b asText = '') ifTrue: [a] ifFalse: [(a = '' ifTrue: [b asText] ifFalse: [a , ' ' , b asText])]])")
|
||||
true)))
|
||||
|
||||
;; ── SX boundary ──
|
||||
(define asText (fn (node) (str (st-send node "asText" (list)))))
|
||||
(define content/text asText)
|
||||
(define block-text asText)
|
||||
|
||||
;; excerpt: first n chars of the plain text, with an ellipsis if truncated.
|
||||
(define
|
||||
content/excerpt
|
||||
(fn
|
||||
(doc n)
|
||||
(let
|
||||
((t (asText doc)))
|
||||
(if
|
||||
(<= (string-length t) n)
|
||||
t
|
||||
(str (substring t 0 n) "…")))))
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **365/365** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render+import, durable CRDT replication, validation, snapshot cache, doc metadata)
|
||||
`bash lib/content/conformance.sh` → **385/385** (Phases 1–4 COMPLETE + 9 extensions: HTML/SX escaping, Markdown render+import, CRDT replication, validation, snapshot cache, doc metadata, plain-text render)
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -84,9 +84,16 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
- [x] Markdown import adapter (`md-import.sx`: text → blocks, round-trips export)
|
||||
- [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)
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Extension: plain-text render + excerpts (`text.sx`). Fourth
|
||||
boundary format via polymorphic `asText` (heading/text/code/quote→text,
|
||||
image→alt, embed/divider→"", list→", "-joined); the document joins non-empty
|
||||
child texts with a space. `content/render doc "text"`, `content/text`,
|
||||
`content/excerpt doc n` (first n chars + "…" if truncated). For previews,
|
||||
meta-descriptions, search indexing. 20 tests; suite 385/385.
|
||||
- 2026-06-07 — Extension: document metadata (`meta.sx`). CtDoc gained optional
|
||||
title/slug/tags ivars (declared in doc.sx, default nil/empty, no effect on
|
||||
block ops). Reads via message dispatch; copy-on-write setters
|
||||
|
||||
Reference in New Issue
Block a user