summaryrefslogtreecommitdiff
path: root/src/clarktown/renderers/list_block.clj
diff options
context:
space:
mode:
authorAsko Nõmm <asko@bien.ee>2022-04-19 17:50:19 +0300
committerAsko Nõmm <asko@bien.ee>2022-04-19 17:50:19 +0300
commit52203a49aa544b2c11c96445d8732893160c436b (patch)
tree84d7b0e9af92770647643bdf7ddbb898663bd7b8 /src/clarktown/renderers/list_block.clj
parent059bfa7bd9bfdde0c75646bf1dfc20d23da8a02c (diff)
WIP #16
Pretty much done. Needs more testing. And new documentation.
Diffstat (limited to 'src/clarktown/renderers/list_block.clj')
-rw-r--r--src/clarktown/renderers/list_block.clj121
1 files changed, 121 insertions, 0 deletions
diff --git a/src/clarktown/renderers/list_block.clj b/src/clarktown/renderers/list_block.clj
new file mode 100644
index 0000000..2a40b06
--- /dev/null
+++ b/src/clarktown/renderers/list_block.clj
@@ -0,0 +1,121 @@
+(ns clarktown.renderers.list-block
+ (:require
+ [clojure.string :as string]))
+
+
+(defn string->indent-n
+ "Returns the indentation count from left of `str`, which must be
+ in spaces and not tabs."
+ [str]
+ (count (take-while #{\space} str)))
+
+
+(defn compose-items-with-indent-guides
+ "Composes a vector of maps from given `block` that adds a unique
+ ID to each line as well as its `indent-n` which is used later
+ on to determine hierarchies. "
+ [block]
+ (->> (string/split-lines block)
+ (mapv
+ (fn [line]
+ {:id (random-uuid)
+ :indent-n (string->indent-n line)
+ :value (-> line
+ string/trim)}))))
+
+
+(defn find-parent-id
+ "Assuming a 1-level `items`, will attempt to find the parent `id`
+ of the item at given `index`. Will return `nil` otherwise."
+ [items index]
+ (let [indent-n-at-index (:indent-n (nth items index))]
+ (-> (->> (split-at index items)
+ first
+ reverse
+ (remove #(or (> (:indent-n %) indent-n-at-index)
+ (= (:indent-n %) indent-n-at-index)))
+ first
+ :id))))
+
+
+(defn compose-items-with-parents
+ "Composes a 1-level list of items from `block` and adds parent
+ information to each if they belong to another item. The result
+ of this is used to build the final data tree."
+ [block]
+ (let [items (compose-items-with-indent-guides block)]
+ (->> items
+ (map-indexed
+ (fn [index line]
+ (merge line {:parent (find-parent-id items index)}))))))
+
+
+(defn add-to-parent
+ "Recursively scans `items`, which can be multiple levels deep,
+ and tries to find a home for `item` according to its parent ID."
+ [items item]
+ (->> items
+ (mapv
+ (fn [i]
+ (if (= (:id i) (:parent item))
+ (if (:items i)
+ (assoc i :items (concat (:items i) [item]))
+ (assoc i :items [item]))
+ (if (:items i)
+ (assoc i :items (add-to-parent (:items i) item))
+ i))))))
+
+
+(defn compose-item-tree
+ "Given a `block`, composes a data representation of it based on
+ the indentation of each line."
+ [block]
+ (loop [result []
+ items (compose-items-with-parents block)]
+ (if (empty? items)
+ result
+ (let [item (first items)
+ parent (:parent item)
+ new-item {:id (:id item)
+ :value (:value item)}]
+ (recur (if parent
+ (add-to-parent result item)
+ (concat result [new-item]))
+ (drop 1 items))))))
+
+
+(defn render-items
+ "Renders an ordered/un-ordered list hierarchy from given `items`."
+ [items]
+ (loop [result ""
+ inner-items items]
+ (if (empty? inner-items)
+ (if (or (string/starts-with? (:value (first items)) "*")
+ (string/starts-with? (:value (first items)) "-"))
+ (str "<ul>" result "</ul>")
+ (str "<ol>" result "</ol>"))
+ (let [inner-item (first inner-items)
+ value (cond
+ ; * unordered list
+ (string/starts-with? (:value inner-item) "*")
+ (-> (string/replace-first (:value inner-item) "*" "")
+ string/trim)
+ ; - unordered list
+ (string/starts-with? (:value inner-item) "-")
+ (-> (string/replace-first (:value inner-item) "-" "")
+ string/trim)
+ ; ordered list
+ :else
+ (-> (string/replace-first (:value inner-item) #"\d\." "")
+ string/trim))]
+ (recur (if (:items inner-item)
+ (str result "<li>" value (render-items (:items inner-item)) "</li>")
+ (str result "<li>" value "</li>"))
+ (drop 1 inner-items))))))
+
+
+(defn render
+ "Renders the list block"
+ [block _]
+ (-> (compose-item-tree block)
+ (render-items))) \ No newline at end of file