(local util (require :lua.util)) (local indent-level " ") (fn copy-range [t from to] (assert (util.array? t) (.. "Table is not an array" (_G.fennel.view t))) (let [r {}] (for [idx from to] (tset r (+ 1 (- idx from)) (. t idx))) r)) (lambda escape-html [writer str] (for [idx 1 (length str)] (case (str:byte idx) (where [b] (= b (str:byte "&"))) (writer:write "&") (where [b] (= b (str:byte "<"))) (writer:write "<") (where [b] (= b (str:byte ">"))) (writer:write ">") (where [b] (= b (str:byte "\""))) (writer:write """) (where [b] (= b (str:byte "'"))) (writer:write "'") _ (writer:write-byte (string.byte str idx))))) (lambda write-level [writer level] (for [_idx 1 level] (writer:write indent-level)) ) (fn render-attrs [writer attrs] (if attrs (each [key value (pairs attrs)] (writer:write " ") (writer:write key) (writer:write "=\"") ; TODO should attrs be escaped differently? (escape-html writer (tostring value)) (writer:write "\"")))) (local render-containers {:doc {:kind :function :begin (fn [_writer _attrs]) :end (fn [_writer _attrs])} :heading {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer attrs] (writer:write ""))} :link {:kind :function-inline :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write ""))} :code-block {:kind :function :begin (fn [writer attrs] (writer:write "
"))
                     :end (fn [writer _attrs]
                            (writer:write "
"))} :unordered-list {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write ""))} :task-list {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write ""))} :list {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write ""))} :list-item {:kind :function :begin (fn [writer attrs] (writer:write "") (if (and attrs attrs.checkbox) (writer:write (.. "")))) :end (fn [writer _attrs] (writer:write ""))} :right-single-quote {:kind "plain" :plain "‘"} :left-single-quote {:kind "plain" :plain "’"} :left-double-quote {:kind "plain" :plain "“"} :right-double-quote {:kind "plain" :plain "”"} :paragraph {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write "

"))} :section {:kind :function :begin (fn [writer attrs] (writer:write "")) :end (fn [writer _attrs] (writer:write ""))} :blankline {:kind "plain" :plain "\n"} :softbreak {:kind "plain" :plain "\n"}}) (var render-node (fn [] (error "Unset!"))) (fn render-container [state container attrs children] (let [renderer (. render-containers container)] (if renderer (case renderer.kind :none nil :plain (do (assert (or (not attrs) (= 0 (length attrs))) (.. "Plain renderer should not have attributes: " (_G.fennel.view container attrs))) (assert (or (not attrs) (= 0 (length children))) (.. "Plain renderer should not have children: " (_G.fennel.view container children))) (state.writer:write renderer.plain)) :function (do (write-level state.writer state.level) (renderer.begin state.writer attrs) (state.writer:write "\n") (each [_ child (ipairs children)] (render-node {:writer state.writer :level (+ 1 state.level)} child)) (state.writer:write "\n") (write-level state.writer state.level) (renderer.end state.writer attrs) (state.writer:write "\n")) :function-inline (do (renderer.begin state.writer attrs) (each [_ child (ipairs children)] (render-node state child)) (renderer.end state.writer attrs))) ;; if container begins with : it is an escaped string ;; render it literally (if (= (string.byte container 1) (string.byte ":")) (do (state.writer:write (string.sub container 2)) (assert (= 0 (length children)) (.. "Children for literal escapes are unsupported" (_G.fennel.view container) ": " (_G.fennel.view children)))) (do (write-level state.writer state.level) (state.writer:write "<") (assert (util.string? container)) (state.writer:write container) (render-attrs state.writer attrs) (state.writer:write ">\n") (each [_ child (ipairs children)] (render-node {:writer state.writer :level (+ 1 state.level)} child)) (write-level state.writer state.level) (state.writer:write "\n")))))) (set render-node (lambda [state node] (case (type node) "string" (escape-html state.writer node) "number" (do (write-level state.writer state.level) (state.writer:write (tostring node)) (state.writer:write "\n")) ;; If the first element is not a string, render them as a sequence "table" (if (not (util.string? (. node 1))) (each [_ node- (ipairs node)] (render-node state node-)) (let [container (. node 1) attrs (if (util.map? (. node 2)) (. node 2) nil) children (if attrs (copy-range node 3 (length node)) (copy-range node 2 (length node)))] (render-container state container attrs children))) else (error (.. "Unknown type for rendering " else))))) (lambda render [writer ast] (render-node {:level 0 :writer writer} ast)) render