summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--garkup.lua116
-rw-r--r--html.lua87
-rw-r--r--prose.lua90
3 files changed, 293 insertions, 0 deletions
diff --git a/garkup.lua b/garkup.lua
new file mode 100644
index 0000000..2469aa8
--- /dev/null
+++ b/garkup.lua
@@ -0,0 +1,116 @@
+local html = require'html'
+local prose = require'prose'
+
+local function highlight(code)
+ -- todo
+ return html.T.pre(code)
+end
+
+local function mode_code(S)
+ local lang = S:line():match("^```(%w+) ?(.*)")
+
+ local text = ""
+ for line in S:lines() do
+ if line:match("^```") then
+ break
+ else
+ text = text .. line
+ end
+ end
+ S:emit(highlight(text))
+end
+
+local function mode_list(S)
+ local items = {}
+ for line in S:lines() do
+ if line:match("^%*") then
+ local content = line:gsub("^%*%s*","")
+ table.insert(items, html.T.li(prose(S,content)))
+ else
+ S:unline(line)
+ break
+ end
+ end
+ S:emit(html.T.ul(items))
+end
+
+local function mode_heading(S)
+ local level, line = S:line():match("^#*()%s*(.*)")
+ level = level - 1
+ S:emit(html.tag('h'..level, prose(S,line)))
+end
+
+local mode_patterns = {
+ { "^```", mode_code },
+ { "^*", mode_list },
+ { "^#", mode_heading },
+}
+
+local function match_mode_pattern(S, line)
+ for i, row in ipairs(mode_patterns) do
+ local pattern, mode = row[1], row[2]
+ if line:match(pattern) then return mode end
+ end
+end
+
+local function mode_paragraphs(S)
+ local para = ""
+ local function finish()
+ if para:match("%S") then
+ S:emit(html.T.p(prose(S,para)))
+ end
+ para = ""
+ end
+
+ for line in S:lines() do
+ local newmode = match_mode_pattern(S, line)
+ if newmode then
+ S:unline(line)
+ finish()
+ return newmode(S)
+ elseif line:match("%S") then
+ para = para .. line
+ else
+ finish()
+ end
+ end
+end
+
+local function split_lines(text)
+ local lines = {}
+ for x in text:gmatch("[^\n]*") do
+ table.insert(lines, x.."\n")
+ end
+ return lines
+end
+
+local State = {}
+function State.new(input)
+ return setmetatable({
+ the_lines = split_lines(input)
+ }, {__index=State})
+end
+
+function State.emit(self, x)
+ print(html.html(x))
+end
+function State.line(self)
+ return table.remove(self.the_lines, 1)
+end
+function State.unline(self, line)
+ table.insert(self.the_lines, 1, line)
+end
+function State.lines(self)
+ return State.line, self
+end
+function State.remaining(self)
+ return #self.the_lines > 0
+end
+function State.toplevel(self)
+ while self:remaining() do
+ mode_paragraphs(self)
+ end
+end
+
+
+State.new(io.read"a"):toplevel()
diff --git a/html.lua b/html.lua
new file mode 100644
index 0000000..87ecc53
--- /dev/null
+++ b/html.lua
@@ -0,0 +1,87 @@
+local function fmt_attrs(attrs)
+ local function fmt_attr(k,v)
+ if v == true then
+ return k
+ else
+ if type(v) == "table" then v = table.concat(v," ") end
+ return ('%s="%s"'):format(k,v)
+ end
+ end
+
+ local o = ""
+ for k,v in pairs(attrs) do
+ o = o .. " " .. fmt_attr(k,v)
+ end
+ return o
+end
+
+local html
+local function fmt_tag(tag)
+ local attrs = tag.attrs and fmt_attrs(tag.attrs) or ""
+ local selfclosing = (tag.body == "")
+ local sameline = type(tag.body) == "string" or (type(tag.body) == "table" and tag.body.tag)
+ local maybenl = sameline and "" or "\n"
+ if selfclosing then
+ return ("<%s%s/>"):format(tag.tname,attrs)
+ else
+ return ("<%s%s>%s%s</%s>"):format(tag.tname,attrs,maybenl,html(tag.body),tag.tname)
+ end
+end
+
+local function tag(tname, a1, a2)
+ -- tag(tname,body) or tag(tname,attrs,body)
+ local body, attrs
+ if a2 then attrs=a1 body=a2 else attrs=nil body=a1 end
+ return setmetatable({tag=true,tname=tname,attrs=attrs,body=body},{__tostring=fmt_tag})
+end
+
+-- instead of tag('ul', {x, y, z})
+-- you can do T.ul{ x, y, z}
+local T = setmetatable({}, {__index=function(_,tname)
+ return function (...)
+ return tag(tname, ...)
+ end
+end})
+
+local function safe(s)
+ -- marks s as safe, doesn't escape it
+ assert(type(s) == "string","can only mark string as safe")
+ return setmetatable({safe=true,s=s},{__tostring=function(x)return x.s end})
+end
+
+local function escape(s)
+ s=s:gsub("&","&amp;")
+ s=s:gsub("<","&lt;")
+ s=s:gsub(">","&gt;")
+ s=s:gsub('"',"&quot;")
+ return safe(s)
+end
+
+
+html = function (x)
+ if type(x) == "string" then
+ return escape(x)
+ elseif type(x) == "table" then
+ if x.safe then
+ -- safestr. already escaped
+ return x
+ elseif x.tag then
+ -- it's a tag
+ return safe( tostring(x) )
+ else
+ -- just a regular list
+ local o = ""
+ for _,item in ipairs(x) do
+ o = o .. tostring(html(item)) .. "\n"
+ end
+ return safe(o)
+ end
+ end
+end
+
+return {
+ html = html,
+ tag = tag,
+ safe = safe,
+ T = T,
+}
diff --git a/prose.lua b/prose.lua
new file mode 100644
index 0000000..ceadec0
--- /dev/null
+++ b/prose.lua
@@ -0,0 +1,90 @@
+local html = require'html'
+
+local extensions = {}
+
+local function prose(S, text)
+ -- this may be a hack
+ local replacements = {}
+ local SO, SI = "\x0e", "\x0f"
+ -- if debugview then SO,SI = "\x1b[7m", "\x1b[0m" end
+ local function emplace(s)
+ table.insert(replacements, s)
+ return SO .. #replacements .. SI
+ end
+
+ -- no way to match 'after ws or at start of string'
+ -- so, just add ws to start and end of string
+ -- this may also be a hack
+ text = ' ' .. text .. ' '
+
+ local function simple_sub(delim, tagname)
+ return {
+ "(%s)"..delim..'(.-)'..delim..'(%W)', -- wo wimple
+ function(a,x,b) return a..emplace(html.T[tagname](x))..b end
+ }
+ end
+
+ local function extension(str)
+ local verb, rest = str:match("^{(%w+);%s*(.*)}$")
+ if verb then
+ return emplace(extensions[verb](S, rest))
+ end
+ local verbonly = str:match("^{(%w+)}$")
+ if verbonly then
+ return emplace(extensions[verbonly](S))
+ end
+ error("invalid extension syntax: "..str)
+ end
+
+ local subs = {
+ { "%b{}", extension },
+ simple_sub('%*', 'strong'),
+ simple_sub('_', 'em'),
+ simple_sub('`', 'code'),
+ }
+
+ for i, row in ipairs(subs) do
+ local pattern,replacement = row[1], row[2]
+ text = text:gsub(pattern,replacement)
+ end
+
+ text = text:gsub("^%s*",""):gsub("%s*$","")
+ -- print(text)
+ local insects = {""}
+ local n = 1
+ while n <= #text do
+ local _, nn, id = text:find('^'..SO:gsub("%[","%%[").."(%d+)"..SI:gsub("%[","%%["), n)
+ if nn then
+ table.insert(insects, replacements[tonumber(id)])
+ table.insert(insects, "")
+ n = nn + 1
+ else
+ local c = text:sub(n,n)
+ -- print("'"..c.."'")
+ insects[#insects] = insects[#insects] .. c
+ n = n + 1
+ end
+ end
+
+ -- for i,n in ipairs(insects) do print(i,type(n),n) end
+
+ return insects
+end
+
+extensions.fn = function(S,text)
+ S.footnotes = S.footnotes or {}
+ local n = #S.footnotes + 1
+ local link_id = "fnref_"..n
+ local note_id = "fn_"..n
+ S.footnotes[n] = html.T.li({id=note_id}, {
+ prose(S,text), html.safe"&nbsp;",
+ html.T.a({href="#"..link_id, role="doc-backlink"}, html.safe"&#x21a9;&#xfe0e;")})
+ return html.T.sup({id=link_id}, html.T.a({href="#"..note_id, role='doc-noteref'}, '('..n..')'))
+end
+
+extensions.fns = function(S)
+ if not S.footnotes then return "" end
+ return {html.T.hr{}, html.T.ol(S.footnotes)}
+end
+
+return prose