content: block query + TOC (query.sx) + 13 tests (574/574)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m2s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m2s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone stats table data wire validate store snapshot crdt crdt-store sync md-import md-doc fed)
|
||||
SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone query stats table data wire validate store snapshot crdt crdt-store sync md-import md-doc fed)
|
||||
|
||||
OUT_JSON="lib/content/scoreboard.json"
|
||||
OUT_MD="lib/content/scoreboard.md"
|
||||
@@ -48,6 +48,7 @@ run_suite() {
|
||||
(load "lib/content/compose.sx")
|
||||
(load "lib/content/tree-edit.sx")
|
||||
(load "lib/content/clone.sx")
|
||||
(load "lib/content/query.sx")
|
||||
(load "lib/content/stats.sx")
|
||||
(load "lib/content/table.sx")
|
||||
(load "lib/content/data.sx")
|
||||
|
||||
51
lib/content/query.sx
Normal file
51
lib/content/query.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
;; content-on-sx — block query + table of contents.
|
||||
;;
|
||||
;; Collect blocks across the whole tree (descending into sections) by predicate
|
||||
;; or type, and derive a table of contents from headings. Tree detection is
|
||||
;; inline (class + st-iv-get) so this needs no section.sx.
|
||||
;;
|
||||
;; Requires (loaded by harness): block.sx, doc.sx.
|
||||
|
||||
(define
|
||||
qry-section?
|
||||
(fn (b) (and (st-instance? b) (= (get b :class) "CtSection"))))
|
||||
(define
|
||||
qry-tree
|
||||
(fn
|
||||
(blocks)
|
||||
(if
|
||||
(= (len blocks) 0)
|
||||
(list)
|
||||
(let
|
||||
((b (first blocks)))
|
||||
(append
|
||||
(cons
|
||||
b
|
||||
(if
|
||||
(qry-section? b)
|
||||
(let
|
||||
((ch (st-iv-get b "children")))
|
||||
(if (list? ch) (qry-tree ch) (list)))
|
||||
(list)))
|
||||
(qry-tree (rest blocks)))))))
|
||||
|
||||
(define
|
||||
content/select
|
||||
(fn (doc pred) (filter pred (qry-tree (doc-blocks doc)))))
|
||||
|
||||
(define
|
||||
content/select-type
|
||||
(fn (doc type) (content/select doc (fn (b) (= (blk-type b) type)))))
|
||||
|
||||
(define
|
||||
content/count-type
|
||||
(fn (doc type) (len (content/select-type doc type))))
|
||||
|
||||
(define
|
||||
content/select-ids
|
||||
(fn (doc pred) (map (fn (b) (blk-id b)) (content/select doc pred))))
|
||||
|
||||
;; table of contents: {:id :level :text} for every heading, in document order.
|
||||
(define
|
||||
content/headings
|
||||
(fn (doc) (map (fn (b) {:id (blk-id b) :text (blk-get b "text") :level (blk-get b "level")}) (content/select-type doc "heading"))))
|
||||
@@ -13,6 +13,7 @@
|
||||
"compose": {"pass": 17, "fail": 0},
|
||||
"tree-edit": {"pass": 17, "fail": 0},
|
||||
"clone": {"pass": 10, "fail": 0},
|
||||
"query": {"pass": 13, "fail": 0},
|
||||
"stats": {"pass": 17, "fail": 0},
|
||||
"table": {"pass": 15, "fail": 0},
|
||||
"data": {"pass": 21, "fail": 0},
|
||||
@@ -27,7 +28,7 @@
|
||||
"md-doc": {"pass": 12, "fail": 0},
|
||||
"fed": {"pass": 20, "fail": 0}
|
||||
},
|
||||
"total_pass": 561,
|
||||
"total_pass": 574,
|
||||
"total_fail": 0,
|
||||
"total": 561
|
||||
"total": 574
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| compose | 17 | 0 | 17 |
|
||||
| tree-edit | 17 | 0 | 17 |
|
||||
| clone | 10 | 0 | 10 |
|
||||
| query | 13 | 0 | 13 |
|
||||
| stats | 17 | 0 | 17 |
|
||||
| table | 15 | 0 | 15 |
|
||||
| data | 21 | 0 | 21 |
|
||||
@@ -30,4 +31,4 @@ _Generated by `lib/content/conformance.sh`_
|
||||
| md-import | 38 | 0 | 38 |
|
||||
| md-doc | 12 | 0 | 12 |
|
||||
| fed | 20 | 0 | 20 |
|
||||
| **Total** | **561** | **0** | **561** |
|
||||
| **Total** | **574** | **0** | **574** |
|
||||
|
||||
89
lib/content/tests/query.sx
Normal file
89
lib/content/tests/query.sx
Normal file
@@ -0,0 +1,89 @@
|
||||
;; Extension — block query + table of contents.
|
||||
|
||||
(st-bootstrap-classes!)
|
||||
(content/bootstrap!)
|
||||
(content-bootstrap-section!)
|
||||
|
||||
(define
|
||||
d
|
||||
(doc-append
|
||||
(doc-append
|
||||
(doc-append
|
||||
(doc-append (doc-empty "d") (mk-heading "h1" 1 "Intro"))
|
||||
(mk-text "p1" "para"))
|
||||
(mk-image "img" "/a.png" "alt"))
|
||||
(mk-section
|
||||
"s"
|
||||
(list
|
||||
(mk-heading "h2" 2 "Sub")
|
||||
(mk-text "p2" "more")
|
||||
(mk-image "img2" "/b.png" "b")))))
|
||||
|
||||
;; ── select-type (tree-wide) ──
|
||||
(content-test
|
||||
"select headings ids"
|
||||
(map (fn (b) (blk-id b)) (content/select-type d "heading"))
|
||||
(list "h1" "h2"))
|
||||
(content-test
|
||||
"select images ids"
|
||||
(map (fn (b) (blk-id b)) (content/select-type d "image"))
|
||||
(list "img" "img2"))
|
||||
(content-test
|
||||
"select text ids"
|
||||
(map (fn (b) (blk-id b)) (content/select-type d "text"))
|
||||
(list "p1" "p2"))
|
||||
(content-test
|
||||
"select section ids"
|
||||
(map (fn (b) (blk-id b)) (content/select-type d "section"))
|
||||
(list "s"))
|
||||
|
||||
;; ── count-type ──
|
||||
(content-test "count headings" (content/count-type d "heading") 2)
|
||||
(content-test "count images" (content/count-type d "image") 2)
|
||||
(content-test "count dividers" (content/count-type d "divider") 0)
|
||||
|
||||
;; ── select with custom predicate ──
|
||||
(content-test
|
||||
"select-ids custom"
|
||||
(content/select-ids d (fn (b) (= (blk-type b) "image")))
|
||||
(list "img" "img2"))
|
||||
(content-test
|
||||
"select custom field"
|
||||
(map
|
||||
(fn (b) (blk-id b))
|
||||
(content/select
|
||||
d
|
||||
(fn
|
||||
(b)
|
||||
(if
|
||||
(= (blk-type b) "heading")
|
||||
(= (blk-get b "level") 2)
|
||||
false))))
|
||||
(list "h2"))
|
||||
|
||||
;; ── headings / TOC ──
|
||||
(content-test
|
||||
"headings TOC"
|
||||
(content/headings d)
|
||||
(list {:id "h1" :text "Intro" :level 1} {:id "h2" :text "Sub" :level 2}))
|
||||
(content-test
|
||||
"empty doc no headings"
|
||||
(content/headings (doc-empty "e"))
|
||||
(list))
|
||||
|
||||
;; ── deeply nested ──
|
||||
(define
|
||||
deep
|
||||
(doc-append
|
||||
(doc-empty "d")
|
||||
(mk-section
|
||||
"o"
|
||||
(list (mk-section "i" (list (mk-heading "deep" 3 "Deep")))))))
|
||||
(content-test
|
||||
"deep heading found"
|
||||
(map (fn (b) (blk-id b)) (content/select-type deep "heading"))
|
||||
(list "deep"))
|
||||
(content-test
|
||||
"deep toc level"
|
||||
(get (first (content/headings deep)) :level)
|
||||
3)
|
||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
||||
|
||||
## Status (rolling)
|
||||
|
||||
`bash lib/content/conformance.sh` → **561/561** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep tree editing, doc stats, table block, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization)
|
||||
`bash lib/content/conformance.sh` → **574/574** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep tree editing, doc stats, table block, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization, block query + TOC)
|
||||
|
||||
## Ground rules
|
||||
|
||||
@@ -94,11 +94,17 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
||||
- [x] document composition (`compose.sx`: concat/prepend/concat-all/wrap-section)
|
||||
- [x] deep tree editing (`tree-edit.sx`: doc-deep-update/replace/delete/insert-into)
|
||||
- [x] id remapping / clone (`clone.sx`: content/remap-ids + prefix-ids, collision-free compose)
|
||||
- [x] block query + TOC (`query.sx`: content/select/select-type/count-type/headings)
|
||||
- [x] portable data serialization (`data.sx`: content/to-data + from-data, round-trips tree)
|
||||
- [x] wire serialization (`wire.sx`: content/to-wire + from-wire, SX-text on the wire)
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-06-07 — Extension: block query + TOC (`query.sx`). `content/select`
|
||||
(predicate) / `content/select-type` / `content/count-type` / `content/select-ids`
|
||||
collect blocks across the whole tree (sections recurse); `content/headings`
|
||||
derives a table of contents (`{:id :level :text}` per heading, document order).
|
||||
Inline tree detection (no section.sx dep). 13 tests; suite 574/574.
|
||||
- 2026-06-07 — Extension: id remapping / clone (`clone.sx`).
|
||||
`content/remap-ids` deep-rewrites every block id across the tree (sections
|
||||
recurse) via a function; `content/prefix-ids` prefixes them. Enables
|
||||
|
||||
Reference in New Issue
Block a user