cripslock

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 11d40133c39ce5fc163733c691027a011da06f96
parent 4607be8cdb1cb7445fdf9c18e4515f7803e827a8
Author: Daniel Barlow <dan@telent.net>
Date:   Wed, 16 Jul 2025 20:54:56 +0100

list markup

this approach works for homogenous lists but will break if
there's a ul inside an ol or vice versa

Diffstat:
Mtextile.fnl | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 66 insertions(+), 4 deletions(-)

diff --git a/textile.fnl b/textile.fnl @@ -9,6 +9,10 @@ nil (table.concat lines ""))))) +(fn cons-tag [tag content] + (doto content + (table.insert 1 tag))) + (define-tests :read-blocks (with-open [f (io.open "tests/paras.txt" :r)] (let [paras @@ -64,8 +68,7 @@ ;; walk the string from left to right. on encountering ;; any recognised opening token for inline markup, find the ;; matching closing token, and recursively walk the bit in the -;; middle. we have to find the first occurence of _any_ opening -;; token instead +;; middle. Note that markup may be nested (fn scan-tok [endtok tag] (fn [f str] @@ -129,9 +132,68 @@ ["john " [:em "paul " [:strong "george"]]]) (expect= (inline-markup "see \"a link\":https://www.example.com for more") - ["see " [["a" {:href "https://www.example.com"}] "a link"] ["for more"]] - ) + ["see " [["a" {:href "https://www.example.com"}] "a link"] ["for more"]]) + ) + +;; a list item starts with one or more * or # in the left-hand column and +;; continues until there is another list item of the same type (ul/ol) +;; nesting level, or until the end of the block + +;; a list item may contain another list + +;; there need not be a blank line before the list + +(fn wrap-list-item [tok content] + (let [list-type (if (= (string.sub tok 1 1) "#") :ol :ul) + inmost? (= (# tok) 1)] + (if inmost? + [list-type [:li content]] + [list-type [:li + (wrap-list-item (string.sub tok 2) content)]]))) + +(fn list-append [a1 a2] + (icollect [_ v (ipairs a2) &into a1] v)) + + +(fn collapse-lists [lists] + (case lists + [[tag1 & els1] [tag1 & els2] & more] + (do + (collapse-lists + (cons-tag + (cons-tag tag1 (list-append els1 els2)) + more))) + [first & rest] + (cons-tag first (collapse-lists rest)) + [] [] + )) + + +(fn extract-lists [block] + (. + (collapse-lists + (icollect [(tok content) (string.gmatch block "([%#%*]+) *([^%#%*]+)")] + (wrap-list-item tok content))) + 1)) + + +(define-tests :extract-lists + (expect= (extract-lists "* item 1\n* item 2\n*item 3\n") + [:ul [:li "item 1\n"] [:li "item 2\n"] [:li "item 3\n"]]) + + (expect= (extract-lists "* item 1\n** subitem\n*item 2\n") + [:ul [:li "item 1\n"] [:li [:ul [:li "subitem\n"]]] [:li "item 2\n"]]) + + (expect= (extract-lists "# item 1\n# item 2\n## subitem\n") + [:ol [:li "item 1\n"] [:li "item 2\n"] [:li [:ol [:li "subitem\n"]]]]) + + (expect= (extract-lists "* item 1\n** subitem\n*item 2\n") + [:ul [:li "item 1\n"] [:li [:ul [:li "subitem\n"]]] [:li "item 2\n"]]) + + ) + + {}