From 9b92dd173f5e6fb58d58417faa2015500b1b037b Mon Sep 17 00:00:00 2001 From: Caleb Maclennan Date: Tue, 3 Oct 2023 02:59:04 +0300 Subject: [PATCH] feat(core): Add vendored lunamark fork removed from SILE upstream --- lua-libraries/LICENSE-lunamark | 20 + lua-libraries/lunamark.lua | 94 ++ lua-libraries/lunamark/entities.lua | 282 +++++ lua-libraries/lunamark/reader.lua | 20 + lua-libraries/lunamark/reader/markdown.lua | 1172 ++++++++++++++++++++ lua-libraries/lunamark/util.lua | 228 ++++ lua-libraries/lunamark/writer.lua | 20 + lua-libraries/lunamark/writer/ast.lua | 73 ++ lua-libraries/lunamark/writer/context.lua | 194 ++++ lua-libraries/lunamark/writer/docbook.lua | 152 +++ lua-libraries/lunamark/writer/dzslides.lua | 368 ++++++ lua-libraries/lunamark/writer/generic.lua | 294 +++++ lua-libraries/lunamark/writer/groff.lua | 81 ++ lua-libraries/lunamark/writer/html.lua | 191 ++++ lua-libraries/lunamark/writer/html5.lua | 37 + lua-libraries/lunamark/writer/latex.lua | 260 +++++ lua-libraries/lunamark/writer/man.lua | 130 +++ lua-libraries/lunamark/writer/tex.lua | 89 ++ lua-libraries/lunamark/writer/xml.lua | 60 + 19 files changed, 3765 insertions(+) create mode 100644 lua-libraries/LICENSE-lunamark create mode 100644 lua-libraries/lunamark.lua create mode 100644 lua-libraries/lunamark/entities.lua create mode 100644 lua-libraries/lunamark/reader.lua create mode 100644 lua-libraries/lunamark/reader/markdown.lua create mode 100644 lua-libraries/lunamark/util.lua create mode 100644 lua-libraries/lunamark/writer.lua create mode 100644 lua-libraries/lunamark/writer/ast.lua create mode 100644 lua-libraries/lunamark/writer/context.lua create mode 100644 lua-libraries/lunamark/writer/docbook.lua create mode 100644 lua-libraries/lunamark/writer/dzslides.lua create mode 100644 lua-libraries/lunamark/writer/generic.lua create mode 100644 lua-libraries/lunamark/writer/groff.lua create mode 100644 lua-libraries/lunamark/writer/html.lua create mode 100644 lua-libraries/lunamark/writer/html5.lua create mode 100644 lua-libraries/lunamark/writer/latex.lua create mode 100644 lua-libraries/lunamark/writer/man.lua create mode 100644 lua-libraries/lunamark/writer/tex.lua create mode 100644 lua-libraries/lunamark/writer/xml.lua diff --git a/lua-libraries/LICENSE-lunamark b/lua-libraries/LICENSE-lunamark new file mode 100644 index 00000000..48d92db4 --- /dev/null +++ b/lua-libraries/LICENSE-lunamark @@ -0,0 +1,20 @@ +Copyright (C) 2009-2016 John MacFarlane + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lua-libraries/lunamark.lua b/lua-libraries/lunamark.lua new file mode 100644 index 00000000..cf8344ec --- /dev/null +++ b/lua-libraries/lunamark.lua @@ -0,0 +1,94 @@ +-- (c) 2009-2011 John MacFarlane. Released under MIT license. +-- See the file LICENSE in the source for details. + +--- Copyright © 2009-2011 John MacFarlane. +-- +-- Released under the MIT license (see LICENSE in the source for details). +-- +-- ## Description +-- +-- Lunamark is a lua library for conversion of markdown to +-- other textual formats. Currently HTML, Docbook, ConTeXt, +-- LaTeX, and Groff man are the supported output formats, +-- but lunamark's modular architecture makes it easy to add +-- writers and modify the markdown parser (written with a PEG +-- grammar). +-- +-- Lunamark's markdown parser currently supports the following +-- extensions (which can be turned on or off individually): +-- +-- - Smart typography (fancy quotes, dashes, ellipses) +-- - Significant start numbers in ordered lists +-- - Footnotes +-- - Definition lists +-- +-- More extensions will be supported in later versions. +-- +-- The library is as portable as lua and has very good performance. +-- It is about as fast as the author's own C library +-- [peg-markdown](http://github.com/jgm/peg-markdown). +-- +-- ## Simple usage example +-- +-- local lunamark = require("lunamark") +-- local writer = lunamark.writer.html.new() +-- local parse = lunamark.reader.markdown.new(writer, { smart = true }) +-- local result, metadata = parse("Here's 'my *text*'...") +-- print(result) +-- assert(result == 'Here’s ‘my text’…') +-- +-- ## Customizing the writer +-- +-- Render emphasized text using `` tags rather than ``. +-- +-- local unicode = require("unicode") +-- function writer.emphasis(s) +-- return {"",s,""} +-- end +-- local parse = lunamark.reader.markdown.new(writer, { smart = true }) +-- local result, metadata = parse("*Beiß* nicht in die Hand, die dich *füttert*.") +-- print(result) +-- assert(result == 'Beiß nicht in die Hand, die dich füttert.') +-- +-- Eliminate hyperlinks: +-- +-- function writer.link(lab,url,tit) +-- return lab +-- end +-- local parse = lunamark.reader.markdown.new(writer, { smart = true }) +-- local result, metadata = parse("[hi](/url) there") +-- print(result) +-- assert(result == 'hi there') +-- +-- ## Customizing the parser +-- +-- Parse CamelCase words as wikilinks: +-- +-- lpeg = require("lpeg") +-- local writer = lunamark.writer.html.new() +-- function add_wikilinks(syntax) +-- local capword = lpeg.R("AZ")^1 * lpeg.R("az")^1 +-- local parse_wikilink = lpeg.C(capword^2) +-- / function(wikipage) +-- return writer.link(writer.string(wikipage), +-- "/" .. wikipage, +-- "Go to " .. wikipage) +-- end +-- syntax.Inline = parse_wikilink + syntax.Inline +-- return syntax +-- end +-- local parse = lunamark.reader.markdown.new(writer, { alter_syntax = add_wikilinks }) +-- local result, metadata = parse("My text with WikiLinks.\n") +-- print(result) +-- assert(result == 'My text with WikiLinks.') +-- + +local G = {} + +setmetatable(G,{ __index = function(t,name) + local mod = require("lunamark." .. name) + rawset(t,name,mod) + return t[name] + end }) + +return G diff --git a/lua-libraries/lunamark/entities.lua b/lua-libraries/lunamark/entities.lua new file mode 100644 index 00000000..420d8b0c --- /dev/null +++ b/lua-libraries/lunamark/entities.lua @@ -0,0 +1,282 @@ +-- (c) 2009-2011 John MacFarlane. Released under MIT license. +-- See the file LICENSE in the source for details. + +--- Functions for dealing with HTML/XML entities. + +local M = {} + +local luautf8=require("lua-utf8") +utf8_char = luautf8.char + +local character_entities = { + ["quot"] = 0x0022, + ["amp"] = 0x0026, + ["apos"] = 0x0027, + ["lt"] = 0x003C, + ["gt"] = 0x003E, + ["nbsp"] = 160, + ["iexcl"] = 0x00A1, + ["cent"] = 0x00A2, + ["pound"] = 0x00A3, + ["curren"] = 0x00A4, + ["yen"] = 0x00A5, + ["brvbar"] = 0x00A6, + ["sect"] = 0x00A7, + ["uml"] = 0x00A8, + ["copy"] = 0x00A9, + ["ordf"] = 0x00AA, + ["laquo"] = 0x00AB, + ["not"] = 0x00AC, + ["shy"] = 173, + ["reg"] = 0x00AE, + ["macr"] = 0x00AF, + ["deg"] = 0x00B0, + ["plusmn"] = 0x00B1, + ["sup2"] = 0x00B2, + ["sup3"] = 0x00B3, + ["acute"] = 0x00B4, + ["micro"] = 0x00B5, + ["para"] = 0x00B6, + ["middot"] = 0x00B7, + ["cedil"] = 0x00B8, + ["sup1"] = 0x00B9, + ["ordm"] = 0x00BA, + ["raquo"] = 0x00BB, + ["frac14"] = 0x00BC, + ["frac12"] = 0x00BD, + ["frac34"] = 0x00BE, + ["iquest"] = 0x00BF, + ["Agrave"] = 0x00C0, + ["Aacute"] = 0x00C1, + ["Acirc"] = 0x00C2, + ["Atilde"] = 0x00C3, + ["Auml"] = 0x00C4, + ["Aring"] = 0x00C5, + ["AElig"] = 0x00C6, + ["Ccedil"] = 0x00C7, + ["Egrave"] = 0x00C8, + ["Eacute"] = 0x00C9, + ["Ecirc"] = 0x00CA, + ["Euml"] = 0x00CB, + ["Igrave"] = 0x00CC, + ["Iacute"] = 0x00CD, + ["Icirc"] = 0x00CE, + ["Iuml"] = 0x00CF, + ["ETH"] = 0x00D0, + ["Ntilde"] = 0x00D1, + ["Ograve"] = 0x00D2, + ["Oacute"] = 0x00D3, + ["Ocirc"] = 0x00D4, + ["Otilde"] = 0x00D5, + ["Ouml"] = 0x00D6, + ["times"] = 0x00D7, + ["Oslash"] = 0x00D8, + ["Ugrave"] = 0x00D9, + ["Uacute"] = 0x00DA, + ["Ucirc"] = 0x00DB, + ["Uuml"] = 0x00DC, + ["Yacute"] = 0x00DD, + ["THORN"] = 0x00DE, + ["szlig"] = 0x00DF, + ["agrave"] = 0x00E0, + ["aacute"] = 0x00E1, + ["acirc"] = 0x00E2, + ["atilde"] = 0x00E3, + ["auml"] = 0x00E4, + ["aring"] = 0x00E5, + ["aelig"] = 0x00E6, + ["ccedil"] = 0x00E7, + ["egrave"] = 0x00E8, + ["eacute"] = 0x00E9, + ["ecirc"] = 0x00EA, + ["euml"] = 0x00EB, + ["igrave"] = 0x00EC, + ["iacute"] = 0x00ED, + ["icirc"] = 0x00EE, + ["iuml"] = 0x00EF, + ["eth"] = 0x00F0, + ["ntilde"] = 0x00F1, + ["ograve"] = 0x00F2, + ["oacute"] = 0x00F3, + ["ocirc"] = 0x00F4, + ["otilde"] = 0x00F5, + ["ouml"] = 0x00F6, + ["divide"] = 0x00F7, + ["oslash"] = 0x00F8, + ["ugrave"] = 0x00F9, + ["uacute"] = 0x00FA, + ["ucirc"] = 0x00FB, + ["uuml"] = 0x00FC, + ["yacute"] = 0x00FD, + ["thorn"] = 0x00FE, + ["yuml"] = 0x00FF, + ["OElig"] = 0x0152, + ["oelig"] = 0x0153, + ["Scaron"] = 0x0160, + ["scaron"] = 0x0161, + ["Yuml"] = 0x0178, + ["fnof"] = 0x0192, + ["circ"] = 0x02C6, + ["tilde"] = 0x02DC, + ["Alpha"] = 0x0391, + ["Beta"] = 0x0392, + ["Gamma"] = 0x0393, + ["Delta"] = 0x0394, + ["Epsilon"] = 0x0395, + ["Zeta"] = 0x0396, + ["Eta"] = 0x0397, + ["Theta"] = 0x0398, + ["Iota"] = 0x0399, + ["Kappa"] = 0x039A, + ["Lambda"] = 0x039B, + ["Mu"] = 0x039C, + ["Nu"] = 0x039D, + ["Xi"] = 0x039E, + ["Omicron"] = 0x039F, + ["Pi"] = 0x03A0, + ["Rho"] = 0x03A1, + ["Sigma"] = 0x03A3, + ["Tau"] = 0x03A4, + ["Upsilon"] = 0x03A5, + ["Phi"] = 0x03A6, + ["Chi"] = 0x03A7, + ["Psi"] = 0x03A8, + ["Omega"] = 0x03A9, + ["alpha"] = 0x03B1, + ["beta"] = 0x03B2, + ["gamma"] = 0x03B3, + ["delta"] = 0x03B4, + ["epsilon"] = 0x03B5, + ["zeta"] = 0x03B6, + ["eta"] = 0x03B7, + ["theta"] = 0x03B8, + ["iota"] = 0x03B9, + ["kappa"] = 0x03BA, + ["lambda"] = 0x03BB, + ["mu"] = 0x03BC, + ["nu"] = 0x03BD, + ["xi"] = 0x03BE, + ["omicron"] = 0x03BF, + ["pi"] = 0x03C0, + ["rho"] = 0x03C1, + ["sigmaf"] = 0x03C2, + ["sigma"] = 0x03C3, + ["tau"] = 0x03C4, + ["upsilon"] = 0x03C5, + ["phi"] = 0x03C6, + ["chi"] = 0x03C7, + ["psi"] = 0x03C8, + ["omega"] = 0x03C9, + ["thetasym"] = 0x03D1, + ["upsih"] = 0x03D2, + ["piv"] = 0x03D6, + ["ensp"] = 0x2002, + ["emsp"] = 0x2003, + ["thinsp"] = 0x2009, + ["ndash"] = 0x2013, + ["mdash"] = 0x2014, + ["lsquo"] = 0x2018, + ["rsquo"] = 0x2019, + ["sbquo"] = 0x201A, + ["ldquo"] = 0x201C, + ["rdquo"] = 0x201D, + ["bdquo"] = 0x201E, + ["dagger"] = 0x2020, + ["Dagger"] = 0x2021, + ["bull"] = 0x2022, + ["hellip"] = 0x2026, + ["permil"] = 0x2030, + ["prime"] = 0x2032, + ["Prime"] = 0x2033, + ["lsaquo"] = 0x2039, + ["rsaquo"] = 0x203A, + ["oline"] = 0x203E, + ["frasl"] = 0x2044, + ["euro"] = 0x20AC, + ["image"] = 0x2111, + ["weierp"] = 0x2118, + ["real"] = 0x211C, + ["trade"] = 0x2122, + ["alefsym"] = 0x2135, + ["larr"] = 0x2190, + ["uarr"] = 0x2191, + ["rarr"] = 0x2192, + ["darr"] = 0x2193, + ["harr"] = 0x2194, + ["crarr"] = 0x21B5, + ["lArr"] = 0x21D0, + ["uArr"] = 0x21D1, + ["rArr"] = 0x21D2, + ["dArr"] = 0x21D3, + ["hArr"] = 0x21D4, + ["forall"] = 0x2200, + ["part"] = 0x2202, + ["exist"] = 0x2203, + ["empty"] = 0x2205, + ["nabla"] = 0x2207, + ["isin"] = 0x2208, + ["notin"] = 0x2209, + ["ni"] = 0x220B, + ["prod"] = 0x220F, + ["sum"] = 0x2211, + ["minus"] = 0x2212, + ["lowast"] = 0x2217, + ["radic"] = 0x221A, + ["prop"] = 0x221D, + ["infin"] = 0x221E, + ["ang"] = 0x2220, + ["and"] = 0x2227, + ["or"] = 0x2228, + ["cap"] = 0x2229, + ["cup"] = 0x222A, + ["int"] = 0x222B, + ["there4"] = 0x2234, + ["sim"] = 0x223C, + ["cong"] = 0x2245, + ["asymp"] = 0x2248, + ["ne"] = 0x2260, + ["equiv"] = 0x2261, + ["le"] = 0x2264, + ["ge"] = 0x2265, + ["sub"] = 0x2282, + ["sup"] = 0x2283, + ["nsub"] = 0x2284, + ["sube"] = 0x2286, + ["supe"] = 0x2287, + ["oplus"] = 0x2295, + ["otimes"] = 0x2297, + ["perp"] = 0x22A5, + ["sdot"] = 0x22C5, + ["lceil"] = 0x2308, + ["rceil"] = 0x2309, + ["lfloor"] = 0x230A, + ["rfloor"] = 0x230B, + ["lang"] = 0x27E8, + ["rang"] = 0x27E9, + ["loz"] = 0x25CA, + ["spades"] = 0x2660, + ["clubs"] = 0x2663, + ["hearts"] = 0x2665, + ["diams"] = 0x2666, +} + +--- Given a string of decimal digits, returns a UTF-8 encoded +-- string encoding a unicode character. +function M.dec_entity(s) + return utf8_char(tonumber(s)) +end + +--- Given a string of hexadecimal digits, returns a UTF-8 encoded +-- string encoding a unicode character. +function M.hex_entity(s) + return utf8_char(tonumber("0x"..s)) +end + +--- Given a character entity name (like `ouml`), returns a UTF-8 encoded +-- string encoding a unicode character. +function M.char_entity(s) + local n = character_entities[s] + return utf8_char(n) +end + +return M diff --git a/lua-libraries/lunamark/reader.lua b/lua-libraries/lunamark/reader.lua new file mode 100644 index 00000000..2ee5d0c0 --- /dev/null +++ b/lua-libraries/lunamark/reader.lua @@ -0,0 +1,20 @@ +-- (c) 2009-2011 John MacFarlane. Released under MIT license. +-- See the file LICENSE in the source for details. + +--- Provides access to all lunamark readers without preloading +-- them. Reader modules are loaded only when needed. +-- +-- local readers = require("lunamark.reader") +-- local htmlreader = readers.html -- html reader loaded now +-- local myformat = 'markdown' +-- local myreader = readers[myformat] -- markdown reader loaded now + +local G = {} + +setmetatable(G,{ __index = function(t,name) + local mod = require("lunamark.reader." .. name) + rawset(t,name,mod) + return t[name] + end }) + +return G diff --git a/lua-libraries/lunamark/reader/markdown.lua b/lua-libraries/lunamark/reader/markdown.lua new file mode 100644 index 00000000..d829b591 --- /dev/null +++ b/lua-libraries/lunamark/reader/markdown.lua @@ -0,0 +1,1172 @@ +-- (c) 2009-2011 John MacFarlane, Hans Hagen. Released under MIT license. +-- See the file LICENSE in the source for details. + +local util = require("lunamark.util") +local lpeg = require("lpeg") +local entities = require("lunamark.entities") +local lower, upper, gsub, format, length = + string.lower, string.upper, string.gsub, string.format, string.len +local P, R, S, V, C, Cg, Cb, Cmt, Cc, Ct, B, Cs = + lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Cg, lpeg.Cb, + lpeg.Cmt, lpeg.Cc, lpeg.Ct, lpeg.B, lpeg.Cs +local lpegmatch = lpeg.match +local expand_tabs_in_line = util.expand_tabs_in_line +local luautf8 = require("lua-utf8") + +local M = {} + +local rope_to_string = util.rope_to_string + +-- Normalize a markdown reference tag. (Make lowercase, and collapse +-- adjacent whitespace characters.) +local function normalize_tag(tag) + return luautf8.lower(gsub(rope_to_string(tag), "[ \n\r\t]+", " ")) +end + +------------------------------------------------------------------------------ +-- Character parsers +------------------------------------------------------------------------------ + +local percent = P("%") +local at = P("@") +local comma = P(",") +local asterisk = P("*") +local dash = P("-") +local plus = P("+") +local underscore = P("_") +local period = P(".") +local hash = P("#") +local ampersand = P("&") +local backtick = P("`") +local less = P("<") +local more = P(">") +local space = P(" ") +local squote = P("'") +local dquote = P('"') +local lparent = P("(") +local rparent = P(")") +local lbracket = P("[") +local rbracket = P("]") +local circumflex = P("^") +local slash = P("/") +local equal = P("=") +local colon = P(":") +local semicolon = P(";") +local exclamation = P("!") +local tilde = P("~") +local tab = P("\t") +local newline = P("\n") +local tightblocksep = P("\001") + +--- Create a new markdown parser. +-- +-- * `writer` is a writer table (see [lunamark.writer.generic]). +-- +-- * `options` is a table with parsing options. +-- The following fields are significant: +-- +-- `alter_syntax` +-- : Function from syntax table to syntax table, +-- allowing the user to change or extend the markdown syntax. +-- For an example, see the documentation for `lunamark`. +-- +-- `references` +-- : A table of references to be used in resolving links +-- in the document. The keys should be all lowercase, with +-- spaces and newlines collapsed into single spaces. +-- Example: +-- +-- { foo: { url = "/url", title = "my title" }, +-- bar: { url = "http://fsf.org" } } +-- +-- `preserve_tabs` +-- : Preserve tabs instead of converting to spaces. +-- +-- `smart` +-- : Parse quotation marks, dashes, ellipses intelligently. +-- +-- `startnum` +-- : Make the opening number in an ordered list significant. +-- +-- `notes` +-- : Enable footnotes as in pandoc. +-- +-- `definition_lists` +-- : Enable definition lists as in pandoc. +-- +-- `citations` +-- : Enable citations as in pandoc. +-- +-- `fenced_code_blocks` +-- : Enable fenced code blocks. +-- +-- `pandoc_title_blocks` +-- : Parse pandoc-style title block at the beginning of document: +-- +-- % Title +-- % Author1; Author2 +-- % Date +-- +-- `lua_metadata` +-- : Enable lua metadata. This is an HTML comment block +-- that starts with `"))^0 * P("-->") + + local htmlinstruction = P("" ))^0 * P("?>" ) + + local openelt_any = less * keyword * htmlattribute^0 * sp * more + + local function openelt_exact(s) + return (less * sp * keyword_exact(s) * htmlattribute^0 * sp * more) + end + + local openelt_block = sp * block_keyword * htmlattribute^0 * sp * more + + local closeelt_any = less * sp * slash * keyword * sp * more + + local function closeelt_exact(s) + return (less * sp * slash * keyword_exact(s) * sp * more) + end + + local emptyelt_any = less * sp * keyword * htmlattribute^0 * sp * slash * more + + local emptyelt_block = less * sp * block_keyword * htmlattribute^0 * sp * slash * more + + local displaytext = (any - less)^1 + + -- return content between two matched HTML tags + local function in_matched(s) + return { openelt_exact(s) + * (V(1) + displaytext + (less - closeelt_exact(s)))^0 + * closeelt_exact(s) } + end + + local function parse_matched_tags(s,pos) + local t = lower(lpegmatch(C(keyword),s,pos)) + return lpegmatch(in_matched(t),s,pos-1) + end + + local in_matched_block_tags = less * Cmt(#openelt_block, parse_matched_tags) + + local displayhtml = htmlcomment + + emptyelt_block + + openelt_exact("hr") + + in_matched_block_tags + + htmlinstruction + + local inlinehtml = emptyelt_any + + htmlcomment + + htmlinstruction + + openelt_any + + closeelt_any + + ------------------------------------------------------------------------------ + -- Entities + ------------------------------------------------------------------------------ + + local hexentity = ampersand * hash * S("Xx") * C(hexdigit ^1) * semicolon + local decentity = ampersand * hash * C(digit ^1) * semicolon + local tagentity = ampersand * C(alphanumeric^1) * semicolon + + ------------------------------------------------------------------------------ + -- Inline elements + ------------------------------------------------------------------------------ + + local Inline = V("Inline") + + local Str = normalchar^1 / writer.string + + local Ellipsis = P("...") / writer.ellipsis + + local Dash = P("---") * -dash / writer.mdash + + P("--") * -dash / writer.ndash + + P("-") * #digit * B(digit*1, 2) / writer.ndash + + local DoubleQuoted = dquote * Ct((Inline - dquote)^1) * dquote + / writer.doublequoted + + local squote_start = squote * -spacing + + local squote_end = squote * B(nonspacechar*1, 2) + + local SingleQuoted = squote_start * Ct((Inline - squote_end)^1) * squote_end + / writer.singlequoted + + local Apostrophe = squote * B(nonspacechar*1, 2) / "’" + + local Smart = Ellipsis + Dash + SingleQuoted + DoubleQuoted + Apostrophe + + local Symbol = (specialchar - tightblocksep) / writer.string + + local Code = inticks / writer.code + + local bqstart = more + local headerstart = hash + + (line * (equal^1 + dash^1) * optionalspace * newline) + local fencestart = fencehead(backtick) + fencehead(tilde) + + if options.require_blank_before_blockquote then + bqstart = fail + end + + if options.require_blank_before_header then + headerstart = fail + end + + if not options.fenced_code_blocks or + options.blank_before_fenced_code_blocks then + fencestart = fail + end + + local Endline = newline * -( -- newline, but not before... + blankline -- paragraph break + + tightblocksep -- nested list + + eof -- end of document + + bqstart + + headerstart + + fencestart + ) * spacechar^0 / writer.space + + local Space = spacechar^2 * Endline / writer.linebreak + + spacechar^1 * Endline^-1 * eof / "" + + spacechar^1 * Endline^-1 * optionalspace / writer.space + + local NonbreakingEndline + = newline * -( -- newline, but not before... + blankline -- paragraph break + + tightblocksep -- nested list + + eof -- end of document + + bqstart + + headerstart + + fencestart + ) * spacechar^0 / writer.nbsp + + local NonbreakingSpace + = spacechar^2 * Endline / writer.linebreak + + spacechar^1 * Endline^-1 * eof / "" + + spacechar^1 * Endline^-1 * optionalspace / writer.nbsp + + -- parse many p between starter and ender + local function between(p, starter, ender) + local ender2 = B(nonspacechar) * ender + return (starter * #nonspacechar * Ct(p * (p - ender2)^0) * ender2) + end + + local Strong = ( between(Inline, doubleasterisks, doubleasterisks) + + between(Inline, doubleunderscores, doubleunderscores) + ) / writer.strong + + local Emph = ( between(Inline, asterisk, asterisk) + + between(Inline, underscore, underscore) + ) / writer.emphasis + + local urlchar = anyescaped - newline - more + + local AutoLinkUrl = less + * C(alphanumeric^1 * P("://") * urlchar^1) + * more + / function(url) return writer.link(writer.string(url),url) end + + local AutoLinkEmail = less + * C((alphanumeric + S("-._+"))^1 * P("@") * urlchar^1) + * more + / function(email) return writer.link(writer.string(email),"mailto:"..email) end + + local DirectLink = (tag / parse_inlines_no_link) -- no links inside links + * spnl + * lparent + * (url + Cc("")) -- link can be empty [foo]() + * optionaltitle + * rparent + / writer.link + + local IndirectLink = tag * (C(spnl) * tag)^-1 / indirect_link + + -- parse a link or image (direct or indirect) + local Link = DirectLink + IndirectLink + + local DirectImage = exclamation + * (tag / parse_inlines) + * spnl + * lparent + * (url + Cc("")) -- link can be empty [foo]() + * optionaltitle + * rparent + / writer.image + + local IndirectImage = exclamation * tag * (C(spnl) * tag)^-1 / indirect_image + + local Image = DirectImage + IndirectImage + + local TextCitations = Ct(Cc("") + * citation_name + * ((spnl + * lbracket + * citation_headless_body + * rbracket) + Cc(""))) / + function(raw_cites) + return citations(true, raw_cites) + end + + local ParenthesizedCitations + = Ct(lbracket + * citation_body + * rbracket) / + function(raw_cites) + return citations(false, raw_cites) + end + + local Citations = TextCitations + ParenthesizedCitations + + -- avoid parsing long strings of * or _ as emph/strong + local UlOrStarLine = asterisk^4 + underscore^4 / writer.string + + local EscapedChar = S("\\") * C(escapable) / writer.string + + local InlineHtml = C(inlinehtml) / writer.inline_html + + local HtmlEntity = hexentity / entities.hex_entity / writer.string + + decentity / entities.dec_entity / writer.string + + tagentity / entities.char_entity / writer.string + + ------------------------------------------------------------------------------ + -- Block elements + ------------------------------------------------------------------------------ + + local Block = V("Block") + + local DisplayHtml = C(displayhtml) / expandtabs / writer.display_html + + local Verbatim = Cs( (blanklines + * ((indentedline - blankline))^1)^1 + ) / expandtabs / writer.verbatim + + local TildeFencedCodeBlock + = fencehead(tilde) + * Cs(fencedline(tilde)^0) + * fencetail(tilde) + + local BacktickFencedCodeBlock + = fencehead(backtick) + * Cs(fencedline(backtick)^0) + * fencetail(backtick) + + local FencedCodeBlock + = (TildeFencedCodeBlock + BacktickFencedCodeBlock) + / function(infostring, code) + return writer.fenced_code( + expandtabs(code), + writer.string(infostring)) + end + + -- strip off leading > and indents, and run through blocks + local Blockquote = Cs(( + ((leader * more * space^-1)/"" * linechar^0 * newline)^1 + * (-blankline * linechar^1 * newline)^0 + * blankline^0 + )^1) / parse_blocks / writer.blockquote + + local function lineof(c) + return (leader * (P(c) * optionalspace)^3 * newline * blankline^1) + end + + local HorizontalRule = ( lineof(asterisk) + + lineof(dash) + + lineof(underscore) + ) / writer.hrule + + local Reference = define_reference_parser / register_link + + local Paragraph = nonindentspace * Ct(Inline^1) * newline + * ( blankline^1 + + #hash + + #(leader * more * space^-1) + ) + / writer.paragraph + + local Plain = nonindentspace * Ct(Inline^1) / writer.plain + + ------------------------------------------------------------------------------ + -- Lists + ------------------------------------------------------------------------------ + + local starter = bullet + enumerator + + -- we use \001 as a separator between a tight list item and a + -- nested list under it. + local NestedList = Cs((optionallyindentedline - starter)^1) + / function(a) return "\001"..a end + + local ListBlockLine = optionallyindentedline + - blankline - (indent^-1 * starter) + + local ListBlock = line * ListBlockLine^0 + + local ListContinuationBlock = blanklines * (indent / "") * ListBlock + + local function TightListItem(starter) + return -HorizontalRule + * (Cs(starter / "" * ListBlock * NestedList^-1) / parse_blocks) + * -(blanklines * indent) + end + + local function LooseListItem(starter) + return -HorizontalRule + * Cs( starter / "" * ListBlock * Cc("\n") + * (NestedList + ListContinuationBlock^0) + * (blanklines / "\n\n") + ) / parse_blocks + end + + local BulletList = ( Ct(TightListItem(bullet)^1) + * Cc(true) * skipblanklines * -bullet + + Ct(LooseListItem(bullet)^1) + * Cc(false) * skipblanklines ) / writer.bulletlist + + local function ordered_list(s,tight,startnum) + if options.startnum then + startnum = tonumber(startnum) or 1 -- fallback for '#' + else + startnum = nil + end + return writer.orderedlist(s,tight,startnum) + end + + local OrderedList = Cg(enumerator, "listtype") * + ( Ct(TightListItem(Cb("listtype")) * TightListItem(enumerator)^0) + * Cc(true) * skipblanklines * -enumerator + + Ct(LooseListItem(Cb("listtype")) * LooseListItem(enumerator)^0) + * Cc(false) * skipblanklines + ) * Cb("listtype") / ordered_list + + local defstartchar = S("~:") + local defstart = ( defstartchar * #spacing * (tab + space^-3) + + space * defstartchar * #spacing * (tab + space^-2) + + space * space * defstartchar * #spacing * (tab + space^-1) + + space * space * space * defstartchar * #spacing + ) + + local dlchunk = Cs(line * (indentedline - blankline)^0) + + local function definition_list_item(term, defs, tight) + return { term = parse_inlines(term), definitions = defs } + end + + local DefinitionListItemLoose = C(line) * skipblanklines + * Ct((defstart * indented_blocks(dlchunk) / parse_blocks)^1) + * Cc(false) + / definition_list_item + + local DefinitionListItemTight = C(line) + * Ct((defstart * dlchunk / parse_blocks)^1) + * Cc(true) + / definition_list_item + + local DefinitionList = ( Ct(DefinitionListItemLoose^1) * Cc(false) + + Ct(DefinitionListItemTight^1) + * (skipblanklines * -DefinitionListItemLoose * Cc(true)) + ) / writer.definitionlist + + ------------------------------------------------------------------------------ + -- Lua metadata + ------------------------------------------------------------------------------ + + local function lua_metadata(s) -- run lua code in comment in sandbox + local env = { m = parse_markdown, markdown = parse_blocks } + local scode = s:match("^") + local untrusted_table, message = load(scode, nil, "t", env) + if not untrusted_table then + util.err(message, 37) + end + local ok, msg = pcall(untrusted_table) + if not ok then + util.err(msg) + end + for k,v in pairs(env) do + writer.set_metadata(k,v) + end + return "" + end + + local LuaMeta = fail + if options.lua_metadata then + LuaMeta = #P(" + + + + + + + + + + + + + + + + + + + + + +]===] + + return DZSlides +end + +return M diff --git a/lua-libraries/lunamark/writer/generic.lua b/lua-libraries/lunamark/writer/generic.lua new file mode 100644 index 00000000..92133e47 --- /dev/null +++ b/lua-libraries/lunamark/writer/generic.lua @@ -0,0 +1,294 @@ +-- (c) 2009-2011 John MacFarlane. Released under MIT license. +-- See the file LICENSE in the source for details. + +--- Generic writer for lunamark. +-- This serves as generic documentation for lunamark writers, +-- which all export a table with the same functions defined. +-- +-- New writers can simply modify the generic writer: for example, +-- +-- local Xml = generic.new(options) +-- +-- Xml.linebreak = "" +-- +-- local escaped = { +-- ["<" ] = "<", +-- [">" ] = ">", +-- ["&" ] = "&", +-- ["\"" ] = """, +-- ["'" ] = "'" +-- } +-- +-- function Xml.string(s) +-- return s:gsub(".",escaped) +-- end + +local util = require("lunamark.util") +local M = {} +local W = {} + +local meta = {} +meta.__index = + function(_, key) + io.stderr:write(string.format("WARNING: Undefined writer function '%s'\n",key)) + return (function(...) return table.concat({...}," ") end) + end +setmetatable(W, meta) + +local rope_to_string = util.rope_to_string + +--- Returns a table with functions defining a generic lunamark writer, +-- which outputs plain text with no formatting. `options` is an optional +-- table with the following fields: +-- +-- `layout` +-- : `minimize` (no space between blocks) +-- : `compact` (no extra blank lines between blocks) +-- : `default` (blank line between blocks) +function M.new(options) + +--- The table contains the following fields: + + options = options or {} + local metadata = {} + + --- Set metadata field `key` to `val`. + function W.set_metadata(key, val) + metadata[key] = val + return "" + end + + --- Add `val` to an array in metadata field `key`. + function W.add_metadata(key, val) + local cur = metadata[key] + if type(cur) == "table" then + table.insert(cur,val) + elseif cur then + metadata[key] = {cur, val} + else + metadata[key] = {val} + end + end + + --- Return metadata table. + function W.get_metadata() + return metadata + end + + -- Turn list of output into final result. + function W.merge(result) + return rope_to_string(result) + end + + --- A space (string). + W.space = " " + + --- Setup tasks at beginning of document. + function W.start_document() + return "" + end + + --- Finalization tasks at end of document. + function W.stop_document() + return "" + end + + --- Plain text block (not formatted as a pragraph). + function W.plain(s) + return s + end + + --- A line break (string). + W.linebreak = "\n" + + --- Line breaks to use between block elements. + W.interblocksep = "\n\n" + + --- Line breaks to use between a container (like a `
` + -- 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 {'', num, ''} + 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, "") + 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