`
+ -- tag) and the adjacent block element.
+ W.containersep = "\n"
+
+ if options.layout == "minimize" then
+ W.interblocksep = ""
+ W.containersep = ""
+ elseif options.layout == "compact" then
+ W.interblocksep = "\n"
+ W.containersep = "\n"
+ end
+
+ --- Ellipsis (string).
+ W.ellipsis = "…"
+
+ --- Em dash (string).
+ W.mdash = "—"
+
+ --- En dash (string).
+ W.ndash = "–"
+
+ --- Non-breaking space.
+ W.nbsp = " "
+
+ --- String in curly single quotes.
+ function W.singlequoted(s)
+ return {"‘", s, "’"}
+ end
+
+ --- String in curly double quotes.
+ function W.doublequoted(s)
+ return {"“", s, "”"}
+ end
+
+ --- String, escaped as needed for the output format.
+ function W.string(s)
+ return s
+ end
+
+ --- Inline (verbatim) code.
+ function W.code(s)
+ return s
+ end
+
+ --- A link with link text `label`, uri `uri`,
+ -- and title `title`.
+ function W.link(label, uri, title)
+ return label
+ end
+
+ --- An image link with alt text `label`,
+ -- source `src`, and title `title`.
+ function W.image(label, src, title)
+ return label
+ end
+
+ --- A paragraph.
+ function W.paragraph(s)
+ return s
+ end
+
+ --- A bullet list with contents `items` (an array). If
+ -- `tight` is true, returns a "tight" list (with
+ -- minimal space between items).
+ function W.bulletlist(items,tight)
+ return util.intersperse(items,W.interblocksep)
+ end
+
+ --- An ordered list with contents `items` (an array). If
+ -- `tight` is true, returns a "tight" list (with
+ -- minimal space between items). If optional
+ -- number `startnum` is present, use it as the
+ -- number of the first list item.
+ function W.orderedlist(items,tight,startnum)
+ return util.intersperse(items,W.interblocksep)
+ end
+
+ --- Inline HTML.
+ function W.inline_html(s)
+ return ""
+ end
+
+ --- Display HTML (HTML block).
+ function W.display_html(s)
+ return ""
+ end
+
+ --- Emphasized text.
+ function W.emphasis(s)
+ return s
+ end
+
+ --- Strongly emphasized text.
+ function W.strong(s)
+ return s
+ end
+
+ --- Block quotation.
+ function W.blockquote(s)
+ return s
+ end
+
+ --- Verbatim block.
+ function W.verbatim(s)
+ return s
+ end
+
+ --- Fenced code block, with infostring `i`.
+ function W.fenced_code(s, i)
+ return s
+ end
+
+ --- Header level `level`, with text `s`.
+ function W.header(s, level)
+ return s
+ end
+
+ --- Horizontal rule.
+ W.hrule = ""
+
+ --- A string of one or more citations. `text_cites` is a boolean, true if the
+ -- citations are in-text citations. `cites` - is an array of tables, each of
+ -- the form `{ prenote = q, name = n, postnote = e, suppress_author = s }`,
+ -- where:
+ -- - `q` is a nil or a rope that should be inserted before the citation,
+ -- - `e` is a nil or a rope that should be inserted after the citation,
+ -- - `n` is a string with the citation name, and
+ -- - `s` is a boolean, true if the author should be omitted from the
+ -- citation.
+ function W.citations(text_cites, cites)
+ local buffer = {}
+ local opened_brackets = false
+ for i, cite in ipairs(cites) do
+ if i == 1 then -- Opening in-text citation
+ if text_cites then
+ buffer[#buffer + 1] = {cite.suppress_author and "-" or "", "@",
+ cite.name}
+ if cite.postnote then
+ opened_brackets = true
+ buffer[#buffer + 1] = {" [", cite.postnote}
+ end
+ else -- Opening regular citation
+ opened_brackets = true
+ buffer[#buffer + 1] = {"[", cite.prenote and {cite.prenote, " "} or "",
+ cite.suppress_author and "-" or "", "@", cite.name, cite.postnote and
+ {" ", cite.postnote}}
+ end
+ else -- Continuation citation
+ buffer[#buffer + 1] = {"; ", cite.prenote and {cite.prenote, " "} or "",
+ cite.suppress_author and "-" or "", "@", cite.name, cite.postnote and
+ {" ", cite.postnote}}
+ end
+ end
+ if opened_brackets then
+ buffer[#buffer + 1] = "]"
+ end
+ return buffer
+ end
+
+ --- A footnote or endnote.
+ function W.note(contents)
+ return contents
+ end
+
+ --- A definition list. `items` is an array of tables,
+ -- each of the form `{ term = t, definitions = defs, tight = tight }`,
+ -- where `t` is a string and `defs` is an array of strings.
+ -- `tight` is a boolean, true if it is a tight list.
+ function W.definitionlist(items, tight)
+ local buffer = {}
+ for _,item in ipairs(items) do
+ buffer[#buffer + 1] = item.t
+ buffer[#buffer + 1] = util.intersperse(item.definitions, W.interblocksep)
+ end
+ return util.intersperse(buffer,W.interblocksep)
+ end
+
+ --- A cosmo template to be used in producing a standalone document.
+ -- `$body` is replaced with the document body, `$title` with the
+ -- title, and so on.
+ W.template = [[
+$body
+]]
+
+ return util.table_copy(W)
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/groff.lua b/lua-libraries/lunamark/writer/groff.lua
new file mode 100644
index 00000000..d722fea9
--- /dev/null
+++ b/lua-libraries/lunamark/writer/groff.lua
@@ -0,0 +1,81 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- Generic groff writer for lunamark.
+-- This is currently used as the basis for [lunamark.writer.man].
+-- In principle other groff-based writers could also extend it.
+
+local M = {}
+
+local util = require("lunamark.util")
+local generic = require("lunamark.writer.generic")
+
+--- Returns a new Groff writer.
+-- For a list of all fields, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local Groff = generic.new(options)
+
+ Groff.interblocksep = "\n\n" -- insensitive to layout
+
+ Groff.containersep = "\n"
+
+ Groff.linebreak = ".br\n"
+
+ Groff.ellipsis = "\\&..."
+
+ Groff.mdash = "\\[em]"
+
+ Groff.ndash = "\\[en]"
+
+ Groff.nbsp = "\\~"
+
+ function Groff.singlequoted(s)
+ return {"`",s,"'"}
+ end
+
+ function Groff.doublequoted(s)
+ return {"\\[lq]",s,"\\[rq]"}
+ end
+
+ Groff.escaped = {
+ ["@"] = "\\@",
+ ["\\"] = "\\\\",
+ }
+
+ local escaped_utf8_triplet = {
+ ["\226\128\156"] = "\\[lq]",
+ ["\226\128\157"] = "\\[rq]",
+ ["\226\128\152"] = "`",
+ ["\226\128\153"] = "'",
+ ["\226\128\148"] = "\\[em]",
+ ["\226\128\147"] = "\\[en]",
+ ["\194\160"] = "\\ ",
+ }
+
+ local escape = util.escaper(Groff.escaped, escaped_utf8_triplet)
+
+ Groff.string = escape
+
+ function Groff.inline_html(s)
+ end
+
+ function Groff.display_html(s)
+ end
+
+ function Groff.code(s)
+ return {"\\f[C]",s,"\\f[]"}
+ end
+
+ function Groff.emphasis(s)
+ return {"\\f[I]",s,"\\f[]"}
+ end
+
+ function Groff.strong(s)
+ return {"\\f[B]",s,"\\f[]"}
+ end
+
+ return Groff
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/html.lua b/lua-libraries/lunamark/writer/html.lua
new file mode 100644
index 00000000..eba6e8eb
--- /dev/null
+++ b/lua-libraries/lunamark/writer/html.lua
@@ -0,0 +1,191 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- HTML writer for lunamark.
+-- Extends [lunamark.writer.xml].
+
+local M = {}
+
+local xml = require("lunamark.writer.xml")
+local util = require("lunamark.util")
+local flatten, intersperse, map = util.flatten, util.intersperse, util.map
+
+--- Return a new HTML writer.
+-- For a list of all fields in the writer, see [lunamark.writer.generic].
+--
+--`options` is a table that can contain the following fields:
+--
+-- `containers`
+-- : Put sections in `
` tags.
+--
+-- `slides`
+-- : Do not allow containers to nest; when a subsection begins,
+-- close the section's container and start a new one.
+--
+-- `layout`
+-- : `minimize` removes semantically insignificant white space.
+-- : `compact` removes unneeded blank lines.
+-- : `default` puts blank lines between block elements.
+function M.new(options)
+ options = options or {}
+ local Html = xml.new(options)
+
+ local endnotes = {}
+ local containersep = Html.containersep
+ local interblocksep = Html.interblocksep
+
+ Html.container = "div"
+ Html.linebreak = "
"
+ Html.nbsp = " "
+
+ function Html.code(s)
+ return {"
", Html.string(s), "
"}
+ end
+
+ function Html.link(lab,src,tit)
+ local titattr
+ if type(tit) == "string" and #tit > 0
+ then titattr = " title=\"" .. Html.string(tit) .. "\""
+ else titattr = ""
+ end
+ return {"
", lab, ""}
+ end
+
+ function Html.image(lab,src,tit)
+ local titattr
+ if type(tit) == "string" and #tit > 0
+ then titattr = " title=\"" .. Html.string(tit) .. "\""
+ else titattr = ""
+ end
+ return {"
"}
+ end
+
+ function Html.paragraph(s)
+ return {"
", s, "
"}
+ end
+
+ local function listitem(s)
+ return {"
", s, ""}
+ end
+
+ function Html.bulletlist(items,tight)
+ return {"
", containersep, intersperse(map(items, listitem), containersep), containersep, "
"}
+ end
+
+ function Html.orderedlist(items,tight,startnum)
+ local start = ""
+ if startnum and startnum ~= 1 then
+ start = " start=\"" .. startnum .. "\""
+ end
+ return {"
", containersep, intersperse(map(items, listitem), containersep), containersep, "
"}
+ end
+
+ function Html.inline_html(s)
+ return s
+ end
+
+ function Html.display_html(s)
+ return s
+ end
+
+ function Html.emphasis(s)
+ return {"
", s, ""}
+ end
+
+ function Html.strong(s)
+ return {"
", s, ""}
+ end
+
+ function Html.blockquote(s)
+ return {"
", containersep, s, containersep, "
"}
+ end
+
+ function Html.verbatim(s)
+ return {"
", Html.string(s), "
"}
+ end
+
+ function Html.fenced_code(s,i)
+ if i ~= "" then
+ return {'
', Html.string(s), "
"}
+ else
+ return Html.verbatim(s)
+ end
+ end
+
+ function Html.header(s,level)
+ local sep = ""
+ if options.slides or options.containers then
+ local lev = (options.slides and 1) or level
+ local stop = Html.stop_section(lev)
+ if stop ~= "" then
+ stop = stop .. Html.interblocksep
+ end
+ sep = stop .. Html.start_section(lev) .. Html.containersep
+ end
+ return {sep, "
", s, ""}
+ end
+
+ Html.hrule = "
"
+
+ function Html.note(contents)
+ local num = #endnotes + 1
+ local backref = ' '
+ local contentsf = flatten(contents)
+ if contentsf[#contentsf] == "" then
+ table.insert(contentsf, #contentsf, backref)
+ else
+ contentsf[#contentsf + 1] = backref
+ end
+ endnotes[num] = {'
', contentsf, ''}
+ return {'
'}
+ end
+
+ function Html.start_document()
+ endnotes = {}
+ return ""
+ end
+
+ function Html.stop_document()
+ return function()
+ local stop = Html.stop_section(1) -- close section containers
+ if stop ~= "" then stop = Html.containersep .. stop end
+ if #endnotes == 0 then
+ return stop
+ else
+ return {stop, interblocksep, '
', interblocksep, '
',
+ containersep, intersperse(endnotes, interblocksep), containersep, '
'}
+ end
+ end
+ end
+
+ function Html.definitionlist(items, tight)
+ local buffer = {}
+ local sep
+ if tight then sep = "" else sep = Html.containersep end
+ for _,item in ipairs(items) do
+ local defs = {}
+ for _,def in ipairs(item.definitions) do
+ defs[#defs + 1] = {"
", sep, def, sep, ""}
+ end
+ buffer[#buffer + 1] = {"
", item.term, "", containersep, intersperse(defs, containersep)}
+ end
+ return {"
", containersep, intersperse(buffer, containersep), containersep, "
"}
+ end
+
+ Html.template = [[
+
+
+
+
$title
+
+
+$body
+
+
+]]
+
+ return Html
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/html5.lua b/lua-libraries/lunamark/writer/html5.lua
new file mode 100644
index 00000000..cfb1db93
--- /dev/null
+++ b/lua-libraries/lunamark/writer/html5.lua
@@ -0,0 +1,37 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- HTML 5 writer for lunamark.
+-- Extends [lunamark.writer.html], but uses `
` tags for sections
+-- if `options.containers` is true.
+
+local M = {}
+
+local html = require("lunamark.writer.html")
+
+--- Returns a new HTML 5 writer.
+-- `options` is as in `lunamark.writer.html`.
+-- For a list of fields, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local Html5 = html.new(options)
+
+ Html5.container = "section"
+
+ Html5.template = [[
+
+
+
+
+$title
+
+
+$body
+
+
+]]
+
+ return Html5
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/latex.lua b/lua-libraries/lunamark/writer/latex.lua
new file mode 100644
index 00000000..3b9f7e3c
--- /dev/null
+++ b/lua-libraries/lunamark/writer/latex.lua
@@ -0,0 +1,260 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- LaTeX writer for lunamark.
+-- Extends the [lunamark.writer.tex].
+
+local M = {}
+
+local tex = require("lunamark.writer.tex")
+local util = require("lunamark.util")
+local format = string.format
+
+--- Returns a new LaTeX writer.
+--
+-- * `options` is a table with parsing options.
+-- The following fields are significant:
+--
+-- `citations`
+-- : Enable citations as in pandoc. Either a boolean or one of
+-- the following strings should be specified:
+--
+-- - `latex` -- produce basic LaTeX2e citations,
+-- - `natbib` -- produce citations for the Natbib package, or
+-- - `biblatex` -- produce citations for the BibLaTeX package.
+--
+-- For a list of fields in the writer, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local LaTeX = tex.new(options)
+
+ function LaTeX.code(s)
+ return {"\\texttt{",LaTeX.string(s),"}"}
+ end
+
+ function LaTeX.link(lab,src,tit)
+ return {"\\href{",LaTeX.string(src),"}{",lab,"}"}
+ end
+
+ function LaTeX.image(lab,src,tit)
+ return {"\\includegraphics{",LaTeX.string(src),"}"}
+ end
+
+ local function listitem(s)
+ return {"\\item ",s}
+ end
+
+ function LaTeX.bulletlist(items)
+ local buffer = {}
+ for _,item in ipairs(items) do
+ buffer[#buffer + 1] = listitem(item)
+ end
+ local contents = util.intersperse(buffer,"\n")
+ return {"\\begin{itemize}\n",contents,"\n\\end{itemize}"}
+ end
+
+ function LaTeX.orderedlist(items)
+ local buffer = {}
+ for _,item in ipairs(items) do
+ buffer[#buffer + 1] = listitem(item)
+ end
+ local contents = util.intersperse(buffer,"\n")
+ return {"\\begin{enumerate}\n",contents,"\n\\end{enumerate}"}
+ end
+
+ function LaTeX.emphasis(s)
+ return {"\\emph{",s,"}"}
+ end
+
+ function LaTeX.strong(s)
+ return {"\\textbf{",s,"}"}
+ end
+
+ function LaTeX.blockquote(s)
+ return {"\\begin{quote}\n",s,"\n\\end{quote}"}
+ end
+
+ function LaTeX.verbatim(s)
+ return {"\\begin{verbatim}\n",s,"\\end{verbatim}"}
+ end
+
+ LaTeX.fenced_code = LaTeX.verbatim
+
+ function LaTeX.header(s,level)
+ local cmd
+ if level == 1 then
+ cmd = "\\section"
+ elseif level == 2 then
+ cmd = "\\subsection"
+ elseif level == 3 then
+ cmd = "\\subsubsection"
+ elseif level == 4 then
+ cmd = "\\paragraph"
+ elseif level == 5 then
+ cmd = "\\subparagraph"
+ else
+ cmd = ""
+ end
+ return {cmd,"{",s,"}"}
+ end
+
+ LaTeX.hrule = "\\hspace{\\fill}\\rule{.6\\linewidth}{0.4pt}\\hspace{\\fill}"
+
+ function LaTeX.note(contents)
+ return {"\\footnote{",contents,"}"}
+ end
+
+ local function citation_optargs(cite)
+ if cite.prenote and cite.postnote then
+ return {"[", cite.prenote, "][", cite.postnote, "]"}
+ elseif cite.prenote and not cite.postnote then
+ return {"[", cite.prenote, "][]"}
+ elseif not cite.prenote and cite.postnote then
+ return {"[", cite.postnote, "]"}
+ else
+ return ""
+ end
+ end
+
+ if options.citations == true or options.citations == "latex" then
+ --- Basic LaTeX2e citations
+ function LaTeX.citations(_, cites)
+ local buffer = {}
+ local opened_braces = false
+ for i, cite in ipairs(cites) do
+ if cite.prenote or cite.postnote then -- A separate complex citation
+ buffer[#buffer + 1] = {opened_braces and "}" or "",
+ cite.prenote and {i == 1 and "" or " ", cite.prenote, "~"} or
+ "", {(i == 1 or cite.prenote) and "" or " ", "\\cite"},
+ cite.postnote and {"[", cite.postnote, "]"} or "", "{", cite.name,
+ cite_postnote and {"~", cite_postnote} or "", "}"}
+ opened_braces = false
+ else -- A string of simple citations
+ buffer[#buffer + 1] = {opened_braces and ", " or {i == 1 and "" or
+ " ", "\\cite{"}, cite.name}
+ opened_braces = true
+ end
+ end
+ if opened_braces then
+ buffer[#buffer + 1] = "}"
+ end
+ return buffer
+ end
+ elseif options.citations == "natbib" then
+ --- NatBib citations
+ function LaTeX.citations(text_cites, cites)
+ if #cites == 1 then -- A single citation
+ local cite = cites[1]
+ if text_cites then
+ return {"\\citet", citation_optargs(cite), "{", cite.name, "}"}
+ else
+ return {cite.suppress_author and "\\citeyearpar" or "\\citep",
+ citation_optargs(cite), "{", cite.name, "}"}
+ end
+ else -- A string of citations
+ local complex = false
+ local last_suppressed = nil
+ for _, cite in ipairs(cites) do
+ if cite.prenote or cite.postnote or
+ cite.suppress_author == not last_suppressed then
+ complex = true
+ break
+ end
+ last_suppressed = cite.suppress_author
+ end
+ if complex then -- A string of complex citations
+ local buffer = {"\\citetext{"}
+ for i, cite in ipairs(cites) do
+ buffer[#buffer + 1] = {i ~= 1 and "; " or "", cite.suppress_author
+ and "\\citeyear" or (text_cites and "\\citealt" or "\\citealp"),
+ citation_optargs(cite), "{", cite.name, "}"}
+ end
+ buffer[#buffer + 1] = "}"
+ return buffer
+ else -- A string of simple citations
+ local buffer = {}
+ for i, cite in ipairs(cites) do
+ buffer[#buffer + 1] = {i == 1 and (text_cites and "\\citet{" or
+ "\\citep{") or ", ", cite.name}
+ end
+ buffer[#buffer + 1] = "}"
+ return buffer
+ end
+ end
+ end
+ elseif options.citations == "biblatex" then
+ --- BibLaTeX citations
+ function LaTeX.citations(text_cites, cites)
+ if #cites == 1 then -- A single citation
+ local cite = cites[1]
+ if text_cites then
+ return {"\\textcite", citation_optargs(cite), "{", cite.name, "}"}
+ else
+ return {"\\autocite", cite.suppress_author and "*" or "",
+ citation_optargs(cite), "{", cite.name, "}"}
+ end
+ else -- A string of citations
+ local buffer = {text_cites and "\\textcites" or "\\autocites"}
+ for _, cite in ipairs(cites) do
+ buffer[#buffer + 1] = {citation_optargs(cite), "{", cite.name, "}"}
+ end
+ return buffer
+ end
+ end
+ end
+
+ function LaTeX.definitionlist(items)
+ local buffer = {}
+ for _,item in ipairs(items) do
+ buffer[#buffer + 1] = format("\\item[%s]\n%s",
+ item.term, util.intersperse(item.definitions, LaTeX.interblocksep))
+ end
+ local contents = util.intersperse(buffer, LaTeX.containersep)
+ return {"\\begin{description}\n",contents,"\n\\end{description}"}
+ end
+
+ LaTeX.template = [===[
+\documentclass{article}
+\usepackage{amssymb,amsmath}
+\usepackage{ifxetex,ifluatex}
+\ifxetex
+ \usepackage{fontspec,xltxtra,xunicode}
+ \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
+\else
+ \ifluatex
+ \usepackage{fontspec}
+ \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
+ \else
+ \usepackage[utf8]{inputenc}
+ \fi
+\fi
+\ifxetex
+ \usepackage[setpagesize=false, % page size defined by xetex
+ unicode=false, % unicode breaks when used with xetex
+ xetex]{hyperref}
+\else
+ \usepackage[unicode=true]{hyperref}
+\fi
+\hypersetup{breaklinks=true, pdfborder={0 0 0}}
+\setlength{\parindent}{0pt}
+\setlength{\parskip}{6pt plus 2pt minus 1pt}
+\setlength{\emergencystretch}{3em} % prevent overfull lines
+\setcounter{secnumdepth}{0}
+
+\title{$title}
+\author{$sepby{author}[=[$it]=][=[ \and ]=]}
+\date{$date}
+
+\begin{document}
+
+$if{ title }[[\maketitle
+]]
+$body
+
+\end{document}
+]===]
+
+ return LaTeX
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/man.lua b/lua-libraries/lunamark/writer/man.lua
new file mode 100644
index 00000000..db4992a6
--- /dev/null
+++ b/lua-libraries/lunamark/writer/man.lua
@@ -0,0 +1,130 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- Groff man writer for lunamark.
+-- Extends [lunamark.writer.groff].
+--
+-- Note: continuation paragraphs in lists are not
+-- handled properly.
+
+local M = {}
+
+local groff = require("lunamark.writer.groff")
+local util = require("lunamark.util")
+local format = string.format
+
+--- Returns a new groff writer.
+-- For a list of fields, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local Man = groff.new(options)
+
+ local endnotes = {}
+
+ function Man.link(lab,src,tit)
+ return {lab," (",src,")"}
+ end
+
+ function Man.image(lab,src,tit)
+ return {"[IMAGE (",lab,")]"}
+ end
+
+ -- TODO handle continuations properly.
+ -- pandoc does this:
+ -- .IP \[bu] 2
+ -- one
+ -- .RS 2
+ -- .PP
+ -- cont
+ -- .RE
+
+ function Man.paragraph(contents)
+ return {".PP\n",contents}
+ end
+
+ function Man.bulletlist(items,tight)
+ local buffer = {}
+ for _,item in ipairs(items) do
+ local revitem = item
+ -- we don't want to have .IP then .PP
+ if revitem[1][1] == ".PP\n" then revitem[1][1] = "" end
+ buffer[#buffer + 1] = {".IP \\[bu] 2\n",item}
+ end
+ return util.intersperse(buffer, Man.containersep)
+ end
+
+ function Man.orderedlist(items,tight,startnum)
+ local buffer = {}
+ local num = startnum or 1
+ for _,item in ipairs(items) do
+ local revitem = item
+ -- we don't want to have .IP then .PP
+ if revitem[1][1] == ".PP\n" then revitem[1][1] = "" end
+ buffer[#buffer + 1] = {format(".IP \"%d.\" 4\n",num),item}
+ num = num + 1
+ end
+ return util.intersperse(buffer, Man.containersep)
+ end
+
+ function Man.blockquote(s)
+ return {".RS\n",s,"\n.RE"}
+ end
+
+ function Man.verbatim(s)
+ return {".IP\n.nf\n\\f[C]\n",s,".fi"}
+ end
+
+ Man.fenced_code = Man.verbatim
+
+ function Man.header(s,level)
+ local hcode = ".SS"
+ if level == 1 then hcode = ".SH" end
+ return {hcode," ",s}
+ end
+
+ Man.hrule = ".PP\n * * * * *"
+
+ function Man.note(contents)
+ local num = #endnotes + 1
+ endnotes[num] = {format(".SS [%d]\n",num),contents}
+ return format('[%d]', num)
+ end
+
+ function Man.definitionlist(items,tight)
+ local buffer = {}
+ local ds
+ for _,item in ipairs(items) do
+ if tight then
+ ds = util.intersperse(item.definitions,"\n.RS\n.RE\n")
+ buffer[#buffer + 1] = {".TP\n.B ",item.term,"\n",ds,"\n.RS\n.RE"}
+ else
+ ds = util.intersperse(item.definitions,"\n.RS\n.RE\n")
+ buffer[#buffer + 1] = {".TP\n.B ",item.term,"\n.RS\n",ds,"\n.RE"}
+ end
+ end
+ local contents = util.intersperse(buffer,"\n")
+ return contents
+ end
+
+ function Man.start_document()
+ endnotes = {}
+ return ""
+ end
+
+ function Man.stop_document()
+ if #endnotes == 0 then
+ return ""
+ else
+ return {"\n.SH NOTES\n", util.intersperse(endnotes, "\n")}
+ end
+ end
+
+ Man.template = [===[
+.TH "$title" "$section" "$date" "$left_footer" "$center_header"
+$body
+]===]
+
+ return Man
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/tex.lua b/lua-libraries/lunamark/writer/tex.lua
new file mode 100644
index 00000000..09cb24e0
--- /dev/null
+++ b/lua-libraries/lunamark/writer/tex.lua
@@ -0,0 +1,89 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- Generic TeX writer for lunamark.
+-- It extends [lunamark.writer.generic] and is extended by
+-- [lunamark.writer.latex] and [lunamark.writer.context].
+
+local M = {}
+
+local util = require("lunamark.util")
+local generic = require("lunamark.writer.generic")
+local format = string.format
+
+--- Returns a new TeX writer.
+-- For a list ofy fields, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local TeX = generic.new(options)
+
+ TeX.interblocksep = "\n\n" -- insensitive to layout
+
+ TeX.containersep = "\n"
+
+ TeX.linebreak = "\\\\"
+
+ TeX.ellipsis = "\\ldots{}"
+
+ TeX.mdash = "---"
+
+ TeX.ndash = "--"
+
+ TeX.nbsp = "~"
+
+ function TeX.singlequoted(s)
+ return format("`%s'",s)
+ end
+
+ function TeX.doublequoted(s)
+ return format("``%s''",s)
+ end
+
+ TeX.escaped = {
+ ["{"] = "\\{",
+ ["}"] = "\\}",
+ ["$"] = "\\$",
+ ["%"] = "\\%",
+ ["&"] = "\\&",
+ ["_"] = "\\_",
+ ["#"] = "\\#",
+ ["^"] = "\\^{}",
+ ["\\"] = "\\char92{}",
+ ["~"] = "\\char126{}",
+ ["|"] = "\\char124{}",
+ ["<"] = "\\char60{}",
+ [">"] = "\\char62{}",
+ ["["] = "{[}", -- to avoid interpretation as optional argument
+ ["]"] = "{]}",
+ }
+
+ local str_escaped = {
+ ["\226\128\156"] = "``",
+ ["\226\128\157"] = "''",
+ ["\226\128\152"] = "`",
+ ["\226\128\153"] = "'",
+ ["\226\128\148"] = "---",
+ ["\226\128\147"] = "--",
+ ["\194\160"] = "~",
+ }
+
+ local escaper = util.escaper(TeX.escaped, str_escaped)
+
+ TeX.string = escaper
+
+ function TeX.inline_html(s)
+ return ""
+ end
+
+ function TeX.display_html(s)
+ return ""
+ end
+
+ function TeX.paragraph(s)
+ return s
+ end
+
+ return TeX
+end
+
+return M
diff --git a/lua-libraries/lunamark/writer/xml.lua b/lua-libraries/lunamark/writer/xml.lua
new file mode 100644
index 00000000..8d02ec1f
--- /dev/null
+++ b/lua-libraries/lunamark/writer/xml.lua
@@ -0,0 +1,60 @@
+-- (c) 2009-2011 John MacFarlane. Released under MIT license.
+-- See the file LICENSE in the source for details.
+
+--- Generic XML writer for lunamark.
+-- It extends [lunamark.writer.generic] and is extended by
+-- [lunamark.writer.html] and [lunamark.writer.docbook].
+
+local M = {}
+
+local generic = require("lunamark.writer.generic")
+local util = require("lunamark.util")
+
+--- Returns a new XML writer.
+-- For a list of fields, see [lunamark.writer.generic].
+function M.new(options)
+ options = options or {}
+ local Xml = generic.new(options)
+
+ Xml.container = "section"
+ -- {1,2} means: a second level header inside a first-level
+ local header_level_stack = {}
+
+ function Xml.start_section(level)
+ header_level_stack[#header_level_stack + 1] = level
+ return "<" .. Xml.container .. ">"
+ end
+
+ function Xml.stop_section(level)
+ local len = #header_level_stack
+ if len == 0 then
+ return ""
+ else
+ local last = header_level_stack[len]
+ local res = {}
+ while last >= level do
+ header_level_stack[len] = nil
+ table.insert(res, "" .. Xml.container .. ">")
+ len = len - 1
+ last = (len > 0 and header_level_stack[len]) or 0
+ end
+ return table.concat(res, Xml.containersep)
+ end
+ end
+
+ Xml.linebreak = ""
+
+ local escape = util.escaper {
+ ["<" ] = "<",
+ [">" ] = ">",
+ ["&" ] = "&",
+ ["\"" ] = """,
+ ["'" ] = "'"
+ }
+
+ Xml.string = escape
+
+ return Xml
+end
+
+return M