commit fa8b9f9c8df7c9b44a8a7cdbace1dd087a2dc216
parent 99599adeb54817639d592c72d929d7db1765f4a3
Author: Daniel Barlow <dan@telent.net>
Date: Thu, 17 Jul 2025 13:51:06 +0100
WIP change approach to list parsing
Diffstat:
| M | textile.fnl | | | 116 | +++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------- |
1 file changed, 78 insertions(+), 38 deletions(-)
diff --git a/textile.fnl b/textile.fnl
@@ -1,5 +1,8 @@
(import-macros { : define-tests : expect : expect= } :assert)
+(fn list-append [a1 a2]
+ (icollect [_ v (ipairs a2) &into a1] v))
+
(fn read-blocks [stream]
(fn []
(let [lines (icollect [l #(stream:read "L")
@@ -151,65 +154,102 @@
)
-;; 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
+;; a list item starts with any list token *+ or #+ and continues until
+;; - end of block
+;; - next occurence of the same token
+;; - occurence of a parent token
-(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)]])))
+;; a list item may contain another list
-(fn list-append [a1 a2]
- (icollect [_ v (ipairs a2) &into a1] v))
+;; however, the third case will never occur:
+;; - a toplevel list has no parent list and therefore lasts until end
+;; of the block.
+;; - when splitting a child list we [pass it only a single list item
+;; of the parent list, so so won't encounter a parent list token
+
+;; there need not be a blank line before the list
-(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 chop-block [tok block start]
+ (case (string.find block tok start)
+ (s e) (cons-tag (string.sub block start (- s 1))
+ (chop-block tok block (+ 1 e)))
+ nil [(string.sub block start)]))
+(fn split-list-items [block]
+ (let [tok (string.match block "%f[^\0\n]([%*%#]+)")]
+ (if tok
+ (values
+ (chop-block (.. "%f[^\0\n]" (string.gsub tok "(.)" "%%%1" ) "%f[^%*%#] *") block 1)
+ (if (= (string.sub tok 1 1) "#") :ol :ul))
+ (values [block] nil))))
(fn extract-lists [block]
- (.
- (collapse-lists
- (icollect [(tok content) (string.gmatch block "([%#%*]+) *([^%#%*]+)")]
- (wrap-list-item tok content)))
- 1))
+ (let [([preamble & lists] tag) (split-list-items block)]
+ (if tag
+ (list-append
+ (or (inline-markup preamble) [])
+ [(cons-tag tag (icollect [_ i (ipairs lists)]
+ (cons-tag :li (extract-lists i))))])
+ (inline-markup preamble))))
(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"]])
+
+ (case (split-list-items "there is no list in this para\n")
+ (text ?tag)
+ (and
+ (expect= text ["there is no list in this para\n"])
+ (expect= ?tag nil)))
+
+ (expect=
+ (pick-values 1 (split-list-items "para\n* item 1\n*item 2\n"))
+ ["para\n" "item 1\n" "item 2\n"])
+
+ (expect=
+ (pick-values 1 (split-list-items "* item 1\n*item 2\n"))
+ ["" "item 1\n" "item 2\n"])
+
+ (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* 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* 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"]])
+ [[:ul
+ [:li "item 1\n"
+ [: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"]]]])
+ [[:ol
+ [:li "item 1\n"]
+ [:li "item 2\n"
+ [: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"]])
-
+ [[:ul
+ [:li "item 1\n" [:ul [:li "subitem\n"]]]
+ [:li "item 2\n"]]])
- )
+ (expect= (extract-lists "* item 1\n# numbered item\n* item 2\n")
+ [[:ul
+ [:li "item 1\n"
+ [:ol [:li "numbered item\n"]]]
+ [:li "item 2\n"]]])
+ (expect= (extract-lists "* item 1\n* _emph_ item 2\n* item 3\n")
+ [ [:ul [:li "item 1\n"]
+ [:li [:em "emph"] " item 2\n"]
+ [:li "item 3\n"]]])
+ )
{}