blob: 27ca72a25c8ff1e6c0d5e4b4f098cb7ecf03e5cb (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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)))
|