feat: html rendering (almost)

This commit is contained in:
Anish Lakhwara 2023-12-29 23:34:23 -08:00
parent 8e17a8f94c
commit cd5d7afc96
4 changed files with 91 additions and 41 deletions

View file

@ -33,7 +33,7 @@
:sp-chars ["#" "-"] :sp-chars ["#" "-"]
:any-space true :any-space true
:continue-until-no-match true :continue-until-no-match true
:regex "[^%[][%-%#].-\n"} :regex "^[%s%-%#].-\n"}
{:name "Footnote" {:name "Footnote"
:sp-chars ["::\n"] :sp-chars ["::\n"]
:regex "%s*::%s-\n" :regex "%s*::%s-\n"

View file

@ -22,18 +22,20 @@
:Footnote "%[(.-)%]%_"} :Footnote "%[(.-)%]%_"}
(find-char _) (string.find str "%[") (find-char _) (string.find str "%[")
next-char (string.sub str (+ 1 find-char) (+ 1 find-char))] next-char (string.sub str (+ 1 find-char) (+ 1 find-char))]
(_G.pp (.. "find-char " find-char)) ; (_G.pp (.. "find-char " find-char))
(_G.pp (.. "next-char " next-char)) ; (_G.pp (.. "next-char " next-char))
(if (= next-char "[") (if (= next-char "[")
(if (not (= nil (string.match str (. patterns :Named-In-Link) current))) (if (not (= nil (string.match str (. patterns :Named-In-Link) current)))
(let [(start end filename title) (string.find str (. patterns :Named-In-Link) current)] (let [(start end filename title) (string.find str (. patterns :Named-In-Link) current)]
(values {:type "Link" (values {:type "Link"
:title title :title title
:class "internal"
:destination filename} :destination filename}
end)) end))
(let [(start end filename) (string.find str (. patterns :In-Link) current)] (let [(start end filename) (string.find str (. patterns :In-Link) current)]
(values {:type "Link" (values {:type "Link"
:title filename :title filename
:class "internal"
:destination filename} :destination filename}
end))) end)))
; If it's not a [[ type link ; If it's not a [[ type link
@ -42,18 +44,20 @@
; if it's ) it's an out-link ; if it's ) it's an out-link
(let [(_ close-bracket) (string.find str "%]" current) (let [(_ close-bracket) (string.find str "%]" current)
next-close-char (string.sub str (+ 1 close-bracket) (+ 1 close-bracket))] next-close-char (string.sub str (+ 1 close-bracket) (+ 1 close-bracket))]
(_G.pp "we think it's not a in-link") ; (_G.pp "we think it's not a in-link")
(_G.pp (.. "next-close-char " next-close-char)) ; (_G.pp (.. "next-close-char " next-close-char))
(match next-close-char (match next-close-char
"(" (let [(start end title destination) (string.find str (. patterns :Out-Link) current)] "(" (let [(start end title destination) (string.find str (. patterns :Out-Link) current)]
(values {:type "Link" (values {:type "Link"
:class "external"
:title title :title title
:destination destination} :destination destination}
end)) end))
"_" (let [(start end footnote-number) (string.find str (. patterns :Footnote) current)] "_" (let [(start end footnote-number) (string.find str (. patterns :Footnote) current)]
(_G.pp "In footnote") ; (_G.pp "In footnote")
(_G.pp (.. "end " end)) ; (_G.pp (.. "end " end))
(values {:type "Footnote-Link" (values {:type "Footnote-Link"
:class "footnote"
:number footnote-number} :number footnote-number}
end)) end))
_ (values nil next-close-char))))))}) ; Could not parse type of link, just skip it _ (values nil next-close-char))))))}) ; Could not parse type of link, just skip it
@ -66,14 +70,14 @@
(var lexed []) (var lexed [])
(while (and (not (= nil current)) (> (length contents) current)) (while (and (not (= nil current)) (> (length contents) current))
(let [found (string.find contents (. grammar :match-first) current)] (let [found (string.find contents (. grammar :match-first) current)]
(_G.pp (.. "current " current)) ; (_G.pp (.. "current " current))
(_G.pp (.. "found " found)) ; (_G.pp (.. "found " (tostring found)))
(if (not (= nil found)) (if (not (= nil found))
(let [char (string.sub contents found found)] (let [char (string.sub contents found found)]
(_G.pp (.. "char " char)) ; (_G.pp (.. "char " char))
(if (> found 1) (if (> found 1)
(table.insert lexed {:type "raw-text" (table.insert lexed {:type "raw-text"
:contents (string.sub contents current (- found 1))})) :text (string.sub contents current (- found 1))}))
(match char (match char
; This would be better if we had a reverse lookup table ; This would be better if we had a reverse lookup table
"_" (let [(block new-end) ((. grammar :Italics) contents current)] "_" (let [(block new-end) ((. grammar :Italics) contents current)]
@ -149,9 +153,9 @@
:Footnote {:rule (fn footnote-rule [str ?regex] :Footnote {:rule (fn footnote-rule [str ?regex]
(let [regex (or ?regex "::%s-\n%s*(.-)\n::\n") (let [regex (or ?regex "::%s-\n%s*(.-)\n::\n")
list (string.match str regex)] list (string.match str regex)]
{:type "Footnotes" {:type "Footnote"
:elements (. (list-rule list) :elements) :elements (. (list-rule list) :elements)}))}
:is-metadata true}))} ; :is-metadata false}))}
:Tag {:rule (fn tag-rule [str ?regex] :Tag {:rule (fn tag-rule [str ?regex]
(let [regex (or ?regex "%:(.-):")] (let [regex (or ?regex "%:(.-):")]
(var tags []) (var tags [])
@ -175,7 +179,7 @@
(var lexed []) (var lexed [])
(var section []) (var section [])
(each [_ block (ipairs blocks)] (each [_ block (ipairs blocks)]
(_G.pp block) ; (_G.pp block)
(let [rule-set (. grammar (. block :type))] (let [rule-set (. grammar (. block :type))]
(if (= "Section" (. block :type)) (if (= "Section" (. block :type))
(do ; Each section gets it's own list (do ; Each section gets it's own list

View file

@ -3,10 +3,19 @@
(local blocks (require :block)) (local blocks (require :block))
(local inline (require :inline)) (local inline (require :inline))
(local render (require :render))
(local test-input "= Big Test File =\n%title Some Title\n:note:\n*This* is a paragraph.\n- This\n- List\n- Should\n- Be One\n-----\n= A new Section Title =\n![Has an Image](file:///link-to-image.png)\n> With some quoted matieral\n>Across multiple lines\n > and different indents\n\n{{{python \n def file(arg, other):\n rturn rg + other\n}}}\n\nMore Paragraphs\n-----\n\nThis has a footnote [#]_ \n\n:: \n\n- This is the first note\n-This is the second\n::\n") (local test-input "= Big Test File =\n%title Some Title\n:note:\n*This* is a paragraph.\n- This\n- List\n- Should\n- Be One\n-----\n= A new Section Title =\n![Has an Image](file:///link-to-image.png)\n> With some quoted matieral\n>Across multiple lines\n > and different indents\n\n{{{python \n def file(arg, other):\n rturn rg + other\n}}}\n\nMore Paragraphs\n-----\n\nThis has a footnote [#]_ \n\n:: \n\n- This is the first note\n-This is the second\n::\n")
(-> test-input (fn write-file [text]
(_G.pp text)
(let [f (assert (io.open "./test.html" :w))]
(f:write text)
(f:close)))
(let [inline-blocks (-> test-input
(blocks.lexer blocks.grammar) (blocks.lexer blocks.grammar)
(inline.inline-lexer inline.inline-grammar) (inline.inline-lexer inline.inline-grammar))
(_G.pp)) (html metadata) (render.render inline-blocks render.render-grammar)]
(_G.pp html)
(write-file html))

View file

@ -1,7 +1,23 @@
(local fennel (require :fennel)) (local fennel (require :fennel))
(fn _G.pp [x] (print (fennel.view x))) (fn _G.pp [x] (print (fennel.view x)))
(fn render-contents [contents]) (var footnote-counter 1)
(fn render-contents [contents]
(var rendered "<p>\n")
(each [_ line (ipairs contents)]
(_G.pp line)
(match (. line :type)
"raw-text" (set rendered (.. rendered line.text "\n"))
"Italics" (set rendered (.. rendered "<em>" line.text "</em>\n"))
"Bold" (set rendered (.. rendered "<b>" line.text "</b>\n"))
"Strikethrough" (set rendered (.. rendered "<del>" line.text "</del>\n"))
"Monospace" (set rendered (.. rendered "<code>" line.text "</code>\n"))
"Link" (set rendered (.. rendered "<a href=\"" line.destination "\" class=\"" line.class "\">" line.title "</a>\n"))
"Footnote-Link" (do
(set rendered (.. rendered "<sup>\n<a href=fn:" (tostring footnote-counter) " id=fnref:" (tostring footnote-counter) ">note " (tostring footnote-counter) "</a>\n</sup>\n"))
(set footnote-counter (+ footnote-counter 1)))))
(.. rendered "</p>\n"))
(local render-grammar (local render-grammar
{:Header (fn render-header [{: contents : size}] {:Header (fn render-header [{: contents : size}]
@ -11,15 +27,17 @@
(.. "<" header ">\n" contents-render "</" header ">\n"))) (.. "<" header ">\n" contents-render "</" header ">\n")))
:List (fn render-list [{: elements}] :List (fn render-list [{: elements}]
(var list-str "<ul>\n") (var list-str "<ul>\n")
(var depth 1) (var depth 0)
(each [_ element (ipairs elements)] (each [_ element (ipairs elements)]
(_G.pp element)
(let [contents-render (render-contents (. element :contents)) (let [contents-render (render-contents (. element :contents))
indent-level (. element :indent-level)] indent-level (. element :indent-level)]
(_G.pp (.. "indent-level " indent-level " depth: " depth))
(if (= indent-level depth) ; If we're at the right indent level (if (= indent-level depth) ; If we're at the right indent level
(set list-str (.. list-str "<li>" contents-render "</li>\n")) ; Just add the string as a list element (set list-str (.. list-str "<li>" contents-render "</li>\n")) ; Just add the string as a list element
(do ; Otherwise (do ; Otherwise
(while (not (= depth indent-level)) ; While our depth level is different from the indent level (while (not (= depth indent-level)) ; While our depth level is different from the indent level
(if (< indent-level depth) ; If we need to increase depth (if (> indent-level depth) ; If we need to increase depth
(do (do
(set list-str (.. list-str "<ul>\n")) ; Add another layer in the render (set list-str (.. list-str "<ul>\n")) ; Add another layer in the render
(set depth (+ depth 1))) ; Increase our depth level (set depth (+ depth 1))) ; Increase our depth level
@ -27,35 +45,51 @@
(set list-str (.. list-str "</ul>\n")) ; Close a layer (set list-str (.. list-str "</ul>\n")) ; Close a layer
(set depth (- depth 1))))) ; Reduce depth level (set depth (- depth 1))))) ; Reduce depth level
(set list-str (.. list-str "<li>" contents-render "</li>\n")))))) (set list-str (.. list-str "<li>" contents-render "</li>\n"))))))
(while (not (= depth 1)) (while (not (= depth 0))
(set list-str (.. list-str "</ul>\n")) (set list-str (.. list-str "</ul>\n"))
(set depth (- depth 1))) (set depth (- depth 1))
(_G.pp (.. "depth: " depth)))
(set list-str (.. list-str "</ul>\n")) ; Close the first one
list-str) list-str)
:Quote (fn render-quote [{: contents}] :Quote (fn render-quote [{: contents}]
(let [contents-render (render-contents contents)] (let [contents-render (render-contents contents)]
(.. "<blockquote>\n" contents-render "</blockquote>\n"))) (.. "<blockquote>\n" contents-render "</blockquote>\n")))
:Paragraph (fn render-paragraph [{: contents}] :Paragraph (fn render-paragraph [{: contents}]
(let [contents-render (render-contents contents)] (render-contents contents)) ; Contents render handles the <p> tag?
(.. "<p>\n" contents-render "</p>\n")))
:Image (fn render-image [{: link : contents}] :Image (fn render-image [{: link : contents}]
(.. "<img\n src=" link "\n alt=" contents " />")) (.. "<img\n src=" link "\n alt=" (render-contents contents) " />")) ; TODO this fucks up the alt tag
; Tags are a type of metadata ; Tags are a type of metadata
; Not really sure how to render metadata :Tag (fn metadata-tag [{: tags} metadata]
; I think it actually shouldn't be rendered, and passed back up as... metadata (if (= metadata.tags nil)
; The SSG should handle how to render them (tset metadata "tags" [tags])
:Tag (fn metadata-tag [{: tags}] (table.insert metadata.tags tags))
(var metadata {})) metadata)
:Metadata "" :Metadata (fn metadata [{: key : val} metadata]
(if (= metadata.key nil)
(tset metadata key [val])
(table.insert metadata.key val))
metadata)
; Not sure what to do about code either ; Not sure what to do about code either
; Need to look into pygments or whatever see how that works ; Need to look into pygments or whatever see how that works
; Or find some sort of minimal js solution? ; Or find some sort of minimal js solution?
; Not really sure ; Not really sure
:Code "" :Code (fn code [{: codehint : contents}]
; Also not sure what to do about footnotes (.. "<code>\n" contents "</code>\n"))
; We need to link footnotes from inline parsing ; Each footnote gets an incremental ID
; (which I'm just realizing I've forgotton to do... ) ; the contents parser will link to the appropriate footnote
; And then link them here ; Don't forget to link to the ref (back link)
:Footnote ""}) ; See seirdy's footnotes for an example
; https://seirdy.one/meta/site-design/#fn:1
:Footnote (fn footnotes [{: elements}]
(var count 1)
(var rendered "<ol>\n")
(each [_ element (ipairs elements)]
(let [contents-render (render-contents element.contents)]
(set rendered (.. rendered "<li id=fn:" count ">\n"))
(set rendered (.. rendered "<a href=\"#fnref:" count "\">Back</a>\n"))
(set rendered (.. rendered contents-render "</li>\n"))
(set count (+ count 1))))
(.. rendered "</ol>\n"))})
(fn render [blocks grammar] (fn render [blocks grammar]
(var rendered "") (var rendered "")
@ -65,10 +99,13 @@
(let [rule-set (. grammar (. block :type))] (let [rule-set (. grammar (. block :type))]
(if (. block :is-metadata) (if (. block :is-metadata)
(set metadata (rule-set block metadata)) ; Handle metadata, TODO this doesn't handle sections for metadata semantics (set metadata (rule-set block metadata)) ; Handle metadata, TODO this doesn't handle sections for metadata semantics
(set rendered (.. rendered (rule-set block)))))) ; Write to the rendered string (set rendered (.. rendered (rule-set block))))))
;(_G.pp rendered))) ; Write to the rendered string
(set rendered (.. rendered "\n<break/>\n"))) ; TODO Section "rendering" (set rendered (.. rendered "\n<break/>\n"))) ; TODO Section "rendering"
; Should each section be returned seperately? ; Should each section be returned seperately?
; Then we can add metadata to it easily. ; Then we can add metadata to it easily.
; Final section ; Final section
rendered) (values rendered metadata))
{: render
: render-grammar}