local html = require'garkup.html' local T = html.T local prose = require'garkup.prose' function empipe(prog, input) local posix = require'posix' local pipe = assert(posix.popen_pipeline({ function() io.write(input) end, prog }, "r")) local output = "" repeat local next = assert(posix.unistd.read(pipe.fd, 8192)) output = output .. next until #next == 0 assert(posix.pclose(pipe)) return output end function highlight(code, lang) local highlighted = empipe( {"highlight", "-f", "-S", lang, "--inline-css", "--enclose-pre"}, code ) return html.safe(highlighted) 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, lang)) 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, T.li(prose(S,content))) else S:unline(line) break end end S:emit(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 function mode_hr(S) local line = S:line() S:emit(html.T.hr"") end local mode_patterns = { { "^```", mode_code }, { "^*", mode_list }, { "^#", mode_heading }, { "^%-%-%-", mode_hr }, } 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() local processed = prose(S, para) if tostring(html.html(processed)):match("%S") then S:emit(T.p(processed)) 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 finish() 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), output = "", }, {__index=State}) end function State.emit(self, x) self.output = self.output..tostring(html.html(x)).."\n" 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 return self.output end local function garkup(text) local S = State.new(text) return S:toplevel(), S end -- print(garkup(io.read"a") return garkup