From 7d1114cd66025cc18535f3cdab3105e66bbda48d Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Sat, 1 Mar 2014 22:47:25 +0100 Subject: adopt more conventional directory structure --- src/rst_context.lua | 1316 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1316 insertions(+) create mode 100644 src/rst_context.lua (limited to 'src/rst_context.lua') diff --git a/src/rst_context.lua b/src/rst_context.lua new file mode 100644 index 0000000..c7e21fe --- /dev/null +++ b/src/rst_context.lua @@ -0,0 +1,1316 @@ +#!/usr/bin/env texlua +-------------------------------------------------------------------------------- +-- FILE: rst_context.lua +-- USAGE: called by rst_parser.lua +-- DESCRIPTION: Complement to the reStructuredText parser +-- AUTHOR: Philipp Gesang (Phg), +-- CHANGED: 2013-03-26 22:46:17+0100 +-------------------------------------------------------------------------------- +-- +--- TODO +--- Find an appropriate way to handle generic tables irrespective of the grid +--- settings. The problem is: +--- http://archive.contextgarden.net/message/20100912.112605.8a1aaf13.en.html +--- Seems we'll have to choose either the grid or split tables as default. Not +--- good. + + +local helpers = helpers or thirddata and thirddata.rst_helpers +local rst_directives = rst_directives or thirddata and thirddata.rst_directives + +local utf = unicode.utf8 +local utflen = utf.len +local utflower = utf.lower +local utfupper = utf.upper +local iowrite = io.write +local tableconcat = table.concat + +local stringmatch = string.match +local stringgmatch = string.gmatch +local stringgsub = string.gsub + +local dbg_write = helpers.dbg_writef + +local C, Cb, Cc, Cg, Cmt, Cp, + Cs, Ct, P, R, S, V, lpegmatch + = lpeg.C, lpeg.Cb, lpeg.Cc, lpeg.Cg, lpeg.Cmt, lpeg.Cp, + lpeg.Cs, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.match + +-- This one should ignore escaped spaces. +do + local stripper = P{ + [1] = "stripper", + stripper = V"space"^0 * C((V"space"^0 * (V"escaped" + V"nospace")^1)^0), + space = S(" \t\v\n"), + nospace = 1 - V"space", + escaped = P"\\" * V"space" + } + function string.strip(str) + return lpegmatch(stripper, str) or "" + end +end +local stringstrip = string.strip +local stringformat = string.format + +local err = function(str) + if str then + iowrite("\n*[rstctx] Error: " .. str .. "\n\n") + end +end + +local rst_context = thirddata.rst + +rst_context.collected_adornments = {} +rst_context.last_section_level = 0 +rst_context.anonymous_targets = 0 +rst_context.anonymous_links = {} + +rst_context.collected_references = {} +rst_context.context_references = {} +rst_context.structure_references = {} +rst_context.anonymous_set = {} + +rst_context.substitutions = {} +rst_context.lastitemnumber = 0 -- enumerations in RST allow arbitrary skips + +rst_context.current_footnote_number = 0 +rst_context.current_symbolnote_number = 0 + +function rst_context.addsetups(item) + local state = rst_context.state + state.addme[item] = state.addme[item] or true + return 0 +end + +function rst_context.footnote_reference (label) + local tf = rst_context.state.footnotes + if stringmatch(label, "^%d+$") then -- all digits + local c = tonumber(label) + return [[\\footnote{\\getbuffer[__footnote_number_]].. c .."]}" + elseif label == "#" then --autonumber + local rc = rst_context.current_footnote_number + rc = rc + 1 + rst_context.current_footnote_number = rc + return [[\\footnote{\\getbuffer[__footnote_number_]].. rc .."]}" + elseif stringmatch(label, "^#.+$") then + local thelabel = stringmatch(label, "^#(.+)$") + return [[\\footnote{\\getbuffer[__footnote_label_]].. thelabel .."]}" + elseif label == "*" then + local rc = rst_context.current_symbolnote_number + rc = rc + 1 + rst_context.current_symbolnote_number = rc + return [[\\symbolnote{\\getbuffer[__footnote_symbol_]].. rc .."]}" + else -- “citation reference” for now treating them like footnotes + rst_context.addsetups("citations") + return [[\\cite{]] .. label .. [[}]] + end +end + +do + local w = S" \v\t\n" / "_" + local wp = Cs((w + 1)^1) + function rst_context.whitespace_to_underscore(str) + return str and lpegmatch(wp, str) or "" + end +end + +--- So we can use crefs[n][2] to refer to the place where the reference was +--- created. +local function get_context_reference (str) + local crefs = rst_context.context_references + local srefs = rst_context.structure_references + srefs[str] = true + refstring = "__target_" .. rst_context.whitespace_to_underscore(str) + crefs[#crefs + 1] = { refstring, str } + return refstring +end + +function rst_context.emphasis (str) + return [[{\\em ]] .. str .. [[}]] +end + +function rst_context.strong_emphasis (str) + return [[{\\sc ]] .. str .. [[}]] +end + +function rst_context.literal (str) + return [[\\type{]] .. str .. [[}]] +end + +--- ROLES for interpreted text + +rst_context.roles = {} +rst_context.roles.emphasis = rst_context.emphasis +rst_context.roles.strong_emphasis = rst_context.strong_emphasis +rst_context.roles.literal = rst_context.literal +rst_context.roles.bold = function(str) + return [[{\\bold ]] .. str .. [[}]] +end +rst_context.roles.bf = rst_context.roles.bold + +rst_context.roles.italic = function(str) + return [[{\\italic ]] .. str .. [[}]] +end +rst_context.roles.it = rst_context.roles.italic + +rst_context.roles.sans = function(str) + return [[{\\ss ]] .. str .. [[}]] +end +rst_context.roles.sans_serif = rst_context.roles.sans +rst_context.roles.ss = rst_context.roles.sans + +rst_context.roles.uppercase = function(str) + return utfupper(str) +end + +rst_context.roles.lowercase = function(str) + return utflower(str) +end + +rst_context.roles.color = function(color, str) + local p = helpers.patterns + local definition = stringmatch(color, "^color_(.+)$") + if stringmatch(definition, "^rgb_") then -- assume rgb + local rgb = lpegmatch(p.rgbvalues, definition) + definition = stringformat([[r=%s,g=%s,b=%s]], rgb[1], rgb[2], rgb[3]) + end + return stringformat([[\\colored[%s]{%s}]], definition, str) +end + +-------------------------------------------------------------------------------- +--- Inofficial text roles for my private bib +-------------------------------------------------------------------------------- + +-- Afterthought: +-- Different citation commands are essentially typographical instructions: +-- they are concerned with the final representation of the data with respect to +-- a concrete implementation. Not the thing at all that would make reST +-- portable. But then its support for Python-style string escaping &c. ain’t at +-- all portable either. The problem is the same with XML written to be +-- processed with ConTeXt -- when processing the text directly in MkIV you’ll +-- always find yourself adding setups that allow fine-grained control of the +-- typeset output. At the same time those instructions directly contradict the +-- main reason for XML: to provide an application-independent data markup. +-- Typesetting XML (and now reST) with TeX, you will always end up writing TeX +-- code disguised in XML brackets. (Btw. the docutils reST specification has +-- the same kind of inclination to HTML -- some of its components don’t even +-- have a meaning save in HTML peculiarities.) If you strive to avoid this +-- *and* would like to have decent typesetting, you should use the +-- automatically generated TeX code as a starting point for the actual +-- typesetting job. Wish it was possible to have both -- the data in a +-- universal form and the output in the Optimal Typesetting System -- but +-- that’s a dream for now. If you really read these musings, then prove me +-- wrong if you can! Or go tell those digital publishers and their willing +-- subordinates, the authors, who think they can save a few pennys, +-- substituting the typesetter and editor by some fancy software. Keep in mind +-- that zapf.tex is not just random dummy text. + +function rst_context.roles.ctsh(str) -- shorthand + rst_context.addsetups("citator") + return [[\\ctsh{]] .. str .. [[}]] +end + +function rst_context.roles.ctas(str) -- short cite + rst_context.addsetups("citator") + return [[\\ctas{]] .. str .. [[}]] +end + +function rst_context.roles.ctau(str) -- author only + rst_context.addsetups("citator") + return [[\\ctau{]] .. str .. [[}]] +end + +function rst_context.roles.cttt(str) -- title only + rst_context.addsetups("citator") + return [[\\cttt{]] .. str .. [[}]] +end + +function rst_context.roles.ctay(str) -- author year + rst_context.addsetups("citator") + return [[\\ctay{]] .. str .. [[}]] +end + +function rst_context.roles.ctfu(str) -- full cite + rst_context.addsetups("citator") + return [[\\ctfu{]] .. str .. [[}]] +end + +function rst_context.roles.nocite(str) -- nocite + rst_context.addsetups("citator") + return [[\\nocite[]] .. str .. [=[]]=] +end + +-------------------------------------------------------------------------------- +--- End citator roles +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +--- Experimental roles. +-------------------------------------------------------------------------------- + +--- Feature request by Philipp A. +function rst_context.roles.math(str) + return [[\\mathematics{]] .. str .. [[}]] +end + +-------------------------------------------------------------------------------- +--- End roles +-------------------------------------------------------------------------------- + +function rst_context.interpreted_text (...) + local tab = { ... } + local role, str + role = stringmatch(tab[1], "^:(.*):$") or stringmatch(tab[3], "^:(.*):$") + str = tab[2] + + if not role then -- implicit role + role = "emphasis" + end + + if stringmatch(role, "^color_") then + return rst_context.roles.color(role, str) + end + + return rst_context.roles[role](str) +end + +function rst_context.link_standalone (str) + return "\n" + .. [[\\goto{\\hyphenatedurl{]] .. str .. [[}}[url(]] .. str .. [=[)]]=] +end + +function rst_context.reference (str) + rst_context.addsetups("references") + str = stringmatch(str, "^`?([^`]+)`?_$") + return [[\\RSTchoosegoto{__target_]] .. rst_context.whitespace_to_underscore(str) .. "}{" + .. str .. "}" +end + +function rst_context.anon_reference (str) + rst_context.addsetups("references") + str = stringmatch(str, "^`?([^`]+)`?__$") + rst_context.anonymous_links[#rst_context.anonymous_links+1] = str + link = "__target_anon_" .. #rst_context.anonymous_links + return stringformat([[\\RSTchoosegoto{%s}{%s}]], link, str) +end + +local whitespace = S" \n\t\v" +local nowhitespace = 1 - whitespace +local removewhitespace = Cs((nowhitespace^1 + Cs(whitespace / ""))^0) + +function rst_context.target (tab) + rst_context.addsetups("references") + --local tab = { ... } + local refs = rst_context.collected_references + local arefs = rst_context.anonymous_set + local target = tab[#tab] -- Ct + C could be clearer but who cares + tab[#tab] = nil + + local function create_anonymous () + rst_context.anonymous_targets = rst_context.anonymous_targets + 1 + return { "anon_" .. rst_context.anonymous_targets, rst_context.anonymous_targets } + end + + local insert = "" + + if target == "" then -- links here + for _, id in next, tab do + insert = insert .. "\n\\reference[__target_" .. id .. "]{}" + end + else + for i=1,#tab do + local id = tab[i] + if id == "" then -- anonymous + local anon = create_anonymous() + id, arefs[anon[1]] = anon[1], anon[2] + else + local tmp = tab[i] + tmp = stringgsub(tmp, "\\:",":") + tmp = stringmatch(tmp, "`?([^`]+)`?") + id = tmp + --id = tab[i]:gsub("\\:",":"):match("`?([^`]+)`?") -- deescaping + end + if id then + refs[id] = refs[id] or target + end + end + end + + return insert +end + +function rst_context.inline_internal_target (str) + return "\\\\reference[__target_" .. rst_context.whitespace_to_underscore(str) .."]{}" +end + +function rst_context.substitution_reference (str, underscores) + local sub = "" + rst_context.addsetups "substitutions" + if underscores == "_" then -- normal reference + sub = sub .. [[\\reference[__target_]] .. rst_context.whitespace_to_underscore(stringstrip(str)) .. "]{}" + elseif underscores == "__" then -- normal reference + rst_context.anonymous_targets = rst_context.anonymous_targets + 1 + sub = sub .. [[\\reference[__target_anon_]] .. rst_context.anonymous_targets .. "]{}" + end + return sub .. [[{\\RSTsubstitution]] .. stringgsub(str, "%s", "") .. "}" +end + +do + -- see catc-sym.tex + local escape_me = { + ["&"] = [[\letterampersand ]], + ["$"] = [[\letterdollar ]], + ["#"] = [[\letterhash ]], + ["^"] = [[\letterhat ]], + ["_"] = [[\letterunderscore ]], + } + + local chars + for chr, repl in next, escape_me do + chars = chars and chars + (P(chr) / repl) or P(chr) / repl + end + + local p_escape = P{ + [1] = Cs((V"skip" + --+ V"literal" -- achieved via gsub later + + chars + + 1)^1), + skip1 = P"\\starttyping" * (1 - P"\\stoptyping")^1, + balanced = P"{" * (V"balanced" + (1 - P"}"))^0 * P"}", + skip2 = P"\\type" * V"balanced", + skip3 = P"\\mathematics" * V"balanced", + skip = V"skip1" + V"skip2" + V"skip3", + --literal = Cs(P"\\" / "") * 1 + } + + function rst_context.escape (str) + str = stringgsub(str, "\\(.)", "%1") + return lpegmatch(p_escape, str) + end +end + +function rst_context.joinindented (tab) + return tableconcat (tab, "") +end + +local corresponding = { + ['"'] = '"', + ["'"] = "'", + ["{"] = "}", + ["("] = ")", + ["["] = "]", + ["<"] = ">", +} + +local inline_parser = P{ + [1] = "block", + + block = Cs(V"inline_as_first"^-1 * (V"except" + V"inline_element" + V"normal_char")^0), + + inline_element = V"precede_inline" + * Cs(V"inline_do_elements") + * #V"succede_inline" + + V"footnote_reference" + , + + -- Ugly but needed in case the first element of a paragraph is inline + -- formatted. + inline_as_first = V"inline_do_elements" * #V"succede_inline", + + except = P"\\starttyping" * (1 - P"\\stoptyping")^1 * P"\\stoptyping" + + V"enclosed" + , + + inline_do_elements = V"strong_emphasis" + + V"substitution_reference" + + V"anon_reference" + + V"inline_literal" + + V"reference" + + V"emphasis" + + V"interpreted_text" + + V"inline_internal_target" + + V"link_standalone" + , + + precede_inline = V"spacing" + + V"eol" + + -P(1) + + S[['"([{<-/:]] + + P"‘" + P"“" + P"’" + P"«" + P"¡" + P"¿" + + V"inline_delimiter" + + P"„", -- not in standard Murkin reST + + succede_inline = V"spacing" + + V"eol" + + S[['")]}>-/:.,;!?\]] + + P"’" + P"”" + P"»" + + V"inline_delimiter" + + -P(1) + + P"“" -- non-standard again but who cares + , + + enclosed = V"precede_inline"^-1 + * Cg(V"quote_single" + V"quote_double" + V"leftpar", "lastgroup") + * V"inline_delimiter" + * Cmt(C(V"quote_single" + V"quote_double" + V"rightpar") * Cb("lastgroup"), function(s, i, char, oldchar) + return corresponding[oldchar] == char + end) + * V"succede_inline"^-1 + * -V"underscore" + , + + space = P" ", + whitespace = (P" " + Cs(P"\t") / " " + Cs(S"\v") / " "), + spacing = V"whitespace"^1, + + eol = P"\n", + --inline_delimiters = P"‐" + P"‑" + P"‒" + P"–" + V"emdash" + V"space", -- inline markup + inline_delimiter = P"‐" + P"‑" + P"‒" + P"–" + V"emdash" + V"space" + + V"bareia" + + V"asterisk" + + V"bar" + + V"lbrack" + V"rbrack" + , -- inline markup + asterisk = P"*", + quote_single = P"'", + quote_double = P'"', + double_asterisk = V"asterisk" * V"asterisk", + bareia = P"`", + backslash = P"\\", + bar = P"|", + double_bareia = V"bareia" * V"bareia", + escaped_bareia = (Cs(V"backslash") / "" * V"bareia") + 1, + colon = P":", + escaped_colon = (Cs(V"backslash") / "" * V"colon") + 1, + semicolon = P";", + underscore = P"_", + double_underscore = V"underscore" * V"underscore", + dot = P".", + interpunct = P"·", + comma = P",", + dash = P"-", + emdash = P"—", + ellipsis = P"…" + P"...", + exclamationmark = P"!", + questionmark = P"?", + interrobang = P"‽", + double_dash = V"dash" * V"dash", + triple_dash = V"double_dash" * V"dash", + hyphen = P"‐", + dashes = V"dash" + P"‒" + P"–" + V"emdash" + P"―", + + lparenthesis = P"(", + rparenthesis = P")", + lbrack = P"[", + rbrack = P"]", + lbrace = P"{" / [[{\\letterleftbrace}]], + rbrace = P"}" / [[{\\letterrightbrace}]], + less = P"<", + greater = P">", + leftpar = V"lparenthesis" + V"lbrack" + V"lbrace" + V"less", + rightpar = V"rparenthesis" + V"rbrack" + V"rbrace" + V"greater", + + normal_char = V"lbrace" + V"rbrace" + V"lbrack" + V"rbrack" -- escape those if in input + + 1 + , + + --groupchars = S"()[]{}", + groupchars = V"leftpar" + V"rightpar", + apostrophe = P"’" + P"'", + + guillemets = P"«" + P"»", + quotationmarks= P"‘" + P"’" + P"“" + P"”", + solidus= P"⁄", + slash = P"/", + + gartenzaun = P"#", + digit = R"09", + letter = R"az" + R"AZ", + + punctuation = V"apostrophe" + + V"colon" + + V"comma" + + V"dashes" + + V"dot" + + V"ellipsis" + + V"exclamationmark" + + V"guillemets" + + V"hyphen" + + V"interpunct" + + V"interrobang" + + V"questionmark" + + V"quotationmarks" + + V"semicolon" + + V"slash" + + V"solidus" + + V"underscore" + , + + emphasis = (V"asterisk" - V"double_asterisk") + * Cs((V"normal_char" - V"spacing" - V"eol" - V"asterisk") + * ((V"normal_char" - (V"normal_char" * V"asterisk"))^0 + * (V"normal_char" - V"spacing" - V"eol" - V"asterisk"))^-1) + * V"asterisk" + / rst_context.emphasis, + + strong_emphasis = V"double_asterisk" + * Cs((V"normal_char" - V"spacing" - V"eol" - V"asterisk") + * ((V"normal_char" - (V"normal_char" * V"double_asterisk"))^0 + * (V"normal_char" - V"spacing" - V"eol" - V"asterisk"))^-1) + * V"double_asterisk" + / rst_context.strong_emphasis, + + inline_literal = V"double_bareia" + * C ((V"escaped_bareia" - V"spacing" - V"eol" - V"bareia") + * ((V"escaped_bareia" - (V"normal_char" * V"double_bareia"))^0 + * (V"escaped_bareia" - V"spacing" - V"eol" - V"bareia"))^-1) + * V"double_bareia" + / rst_context.literal, + + interpreted_single_char = (V"normal_char" - V"spacing" - V"eol" - V"bareia") * #V"bareia", + interpreted_multi_char = (V"normal_char" - V"spacing" - V"eol" - V"bareia") * (V"normal_char" - (1 * V"bareia"))^0 * (1 - V"spacing" - V"eol" - V"bareia"), + + interpreted_text = C(V"role_marker"^-1) + * (V"bareia" - V"double_bareia") + * C(V"interpreted_single_char" + V"interpreted_multi_char") + * V"bareia" + * C(V"role_marker"^-1) + / rst_context.interpreted_text, + + role_marker = V"colon" * (V"backslash" * V"colon" + V"letter" + V"digit" + V"dash" + V"underscore" + V"dot")^1 * V"colon", + + link_standalone = C(V"uri") + / rst_context.link_standalone, + + anon_reference = Cs(V"anon_phrase_reference" + V"anon_normal_reference") + / rst_context.anon_reference, + + anon_normal_reference = C((1 - V"underscore" - V"spacing" - V"eol" - V"punctuation" - V"groupchars")^1) * V"double_underscore", + + anon_phrase_reference = (V"bareia" - V"double_bareia") + * C((1 - V"bareia")^1) + * V"bareia" * V"double_underscore" + , + + reference = Cs(V"normal_reference" + V"phrase_reference") + / rst_context.reference, + + normal_reference = (1 - V"underscore" - V"spacing" - V"eol" - V"punctuation" - V"groupchars")^1 * V"underscore", + + phrase_reference = (V"bareia" - V"double_bareia") + * C((1 - V"bareia")^1) + * V"bareia" * V"underscore" + , + + footnote_reference = V"lbrack" + * Cs(V"footnote_label" + V"citation_reference_label") + * V"rbrack" + * V"underscore" + / rst_context.footnote_reference + , + + footnote_label = V"digit"^1 + + V"gartenzaun" * V"letter"^1 + + V"gartenzaun" + + V"asterisk" + , + + citation_reference_label = V"letter" * (1 - V"rbrack")^1, + + inline_internal_target = V"underscore" + * V"bareia" + * Cs((1 - V"bareia")^1) + * V"bareia" + / rst_context.inline_internal_target + , + + substitution_reference = V"bar" + * C((1 - V"bar")^1) + * V"bar" + * C((V"double_underscore" + V"underscore")^-1) + / rst_context.substitution_reference + , + +-------------------------------------------------------------------------------- +-- Urls +-------------------------------------------------------------------------------- + uri = V"url_protocol" * V"url_domain" * V"url_path_char"^0, + + url_protocol = (P"http" + P"ftp" + P"shttp" + P"sftp") * P"://", + url_domain_char = 1 - V"dot" - V"spacing" - V"eol" - V"punctuation", + url_domain = V"url_domain_char"^1 * (V"dot" * V"url_domain_char"^1)^0, + url_path_char = R("az", "AZ", "09") + S[[-_.!~*'()/]], +} + +rst_context.inline_parser = inline_parser + +function rst_context.paragraph (data) + local str + if not data then + return "" + elseif type(data) == "table" then +-- str = #data > 1 and helpers.string.wrapat(lpegmatch(inline_parser, tableconcat(data, " ")), 65) +-- or inline_parser:match(data[1]) + if #data > 1 then + str = helpers.string.wrapat( + lpegmatch(inline_parser, tableconcat(data, " ")) + , 65) + else + str = lpegmatch(inline_parser, data[1]) + end + else + str = data + end + return stringformat([[ + +\\startparagraph +%s +\\stopparagraph +]], str) +end + +local sectionlevels = { + [1] = "chapter", + [2] = "section", + [3] = "subsection", + [4] = "subsubsection", + [5] = "subsubsubsection", +} + +local function get_line_pattern (chr) + return P(chr)^1 * (-P(1)) +end + +function rst_context.section (...) -- TODO general cleanup; move validity + local tab = { ... } -- checking to parser. + local section, str = true, "" + local adornchar + local ulen = utflen + if #tab == 3 then -- TODO use unicode length with ConTeXt + adornchar = tab[1]:sub(1,1) + section = ulen(tab[1]) >= ulen(tab[2]) + str = stringstrip(tab[2]) + else -- no overline + adornchar = tab[2]:sub(1,1) + section = ulen(tab[1]) <= ulen(tab[2]) + str = tab[1] + end + + if section then -- determine level + local level = rst_context.last_section_level + local rca = rst_context.collected_adornments + if rca[adornchar] then + level = rca[adornchar] + else + level = level + 1 + rca[adornchar] = level + rst_context.last_section_level = level + end + + ref = get_context_reference (str) + + str = stringformat("\n\\\\%s[%s]{%s}\n", sectionlevels[level], ref, str) + else + return [[{\\bf fix your sectioning!}\\endgraf}]] + end + + return section and str or "" +end + +-- Prime time for the fancybreak module. +function rst_context.transition (str) + rst_context.addsetups("breaks") + --return "\\fancybreak\n" + return "\\fancybreak{$* * *$}\n" +end + +function rst_context.bullet_marker(str) + return "marker" +end + +-- This one should ignore escaped spaces. +do + local stripper = P{ + [1] = "stripper", + stripper = V"space"^0 * C((V"space"^0 * V"nospace"^1)^0), + space = S(" \t\v\n"), + escaped = P"\\" * V"space", + nospace = V"escaped" + (1 - V"space"), + } + function stringstrip(str) + return lpegmatch(stripper, str) or "" + end +end + +local enumeration_types = { + ["*"] = "*", -- unordered bulleted + ["+"] = "*", + ["-"] = "*", + ["•"] = "*", + ["‣"] = "*", + ["⁃"] = "*", + + ["#"] = "n", -- numbered lists and conversion + ["A"] = "A", + ["a"] = "a", + ["I"] = "R", + ["i"] = "r", +} + +-- \setupitemize[left=(, right=), margin=4em, stopper=] + +local stripme = S"()." +local dontstrip = 1 - stripme +local itemstripper = stripme^0 * C(dontstrip^1) * stripme^0 + +local function parse_itemstring(str) + local offset = nil + local setup = ",fit][itemalign=flushright," + if stringmatch(str, "^%(") then + setup = setup .. [[left=(,]] + end + if stringmatch(str, "%)$") then + setup = setup .. [[right=)]] + end + if stringmatch(str, "%.$") then + setup = setup .. [[stopper={.\\space}]] + end + local num = stringmatch(str, "^%d") + if num then + -- http://thread.gmane.org/gmane.comp.tex.context/61728/focus=61729 + setup = setup .. ",start=" .. num + str = "n" + end + + str = lpegmatch(itemstripper, str) + str = enumeration_types[str] or str + return { setup = setup, str = str } +end + +function rst_context.startitemize(str) + local setup = "" + local result = "" + str = stringstrip(str) + + local listtype = enumeration_types[str] or parse_itemstring(str) + + if type(listtype) == "table" then + setup = listtype.setup + listtype = listtype.str + end + + result = [[ +\\startitemize[]] .. listtype .. setup .. [[] +]] + return result +end + +local last_item = {} -- stack +local current_itemdepth = 0 +function rst_context.stopitemize(str) + last_item[current_itemdepth] = nil + current_itemdepth = current_itemdepth - 1 + return str .. [[ +\\stopitemize +]] +end + +function rst_context.bullet_item (tab) + local li = last_item + -- The capture of the first item has the \startitemize as + -- *second* element in the array. + local content = #tab == 2 and tab[2] or tab[3] + local startstr = #tab == 3 and tab[2] or nil + local itemtype = tab[1] + local result = startstr or "" + if startstr then + current_itemdepth = current_itemdepth + 1 + li[current_itemdepth] = itemtype + elseif li[current_itemdepth] then + if helpers.list.successor(itemtype, li[current_itemdepth]) then + -- just leave it alone + elseif helpers.list.greater(itemtype, li[current_itemdepth]) then + local itemnum = tonumber(stringstrip(itemtype)) or helpers.list.get_decimal(itemtype) + result = result .. stringformat([[ +\\setnumber[itemgroup:itemize]{%s} +]], itemnum) + end + li[current_itemdepth] = itemtype + end + + return result .. [[ + +\\item ]] .. lpegmatch(inline_parser, content) .. [[ + +]] +end + +-------------------------------------------------------------------------------- +-- Definition lists +-------------------------------------------------------------------------------- +-- TODO define proper setups (probably bnf-like and some narrower for def-paragraphs) + +function rst_context.deflist (list) + rst_context.addsetups("deflist") + + local deflist = [[ +\\startRSTdefinitionlist +]] + for nd=1, #list do + local item = list[nd] + local term = item[1] + local nc = 2 + local tmp = [[ + + \\RSTdeflistterm{]] .. stringstrip(term) .. "}" + if #item > 2 then + while nc < #item do + tmp = tmp .. [[ + + \\RSTdeflistclassifier{]] .. stringstrip(item[nc]) .. "}" + nc = nc + 1 + end + end + tmp = tmp .. [[ + + \\RSTdeflistdefinition{% +]] + local final = item[#item] + for np=1, #final do + local par = final[np] + tmp = tmp .. [[ + \\RSTdeflistparagraph{% +]] .. lpegmatch(inline_parser, par) .. "}\n" + end + tmp = tmp .. " }" + deflist = deflist .. tmp + end + return deflist .. [[ + +\\stopRSTdefinitionlist +]] +end + +-------------------------------------------------------------------------------- +-- Field lists +-------------------------------------------------------------------------------- + +-- TODO Do something useful with field lists. For now I'm not sure what as the +-- bibliography directives from the reST specification seem to make sense only +-- when using docinfo and, after all, we have .bib files that are portable. + +function rst_context.field_list (str) + rst_context.addsetups("fieldlist") + return [[ + +\\startRSTfieldlist]] .. str .. [[\\eTABLEbody\\stopRSTfieldlist +]] +end + +function rst_context.field_name (str) + return [[\\fieldname{]] .. str .. [[}]] +end + +function rst_context.field_body (str) + return [[\\fieldbody{]] .. lpegmatch(inline_parser, str) .. [[}]] +end + +function rst_context.field (tab) + local name, body = tab[1], tab[2] + return stringformat([[ + + \\RSTfieldname{%s} + \\RSTfieldbody{%s} +]], name, lpegmatch(inline_parser, body)) +end + +function rst_context.line_comment (str) + return "% " .. str +end + +function rst_context.block_comment (str) + return stringformat([[ + +\iffalse %% start block comment +%s\fi %% stop block comment +]], str) +end + +function rst_context.option_list (str) + return [[ +\\setupTABLE[c][first] [background=color, backgroundcolor=grey, style=\tt] +\\setupTABLE[c][each] [frame=off] +\\setupTABLE[r][each] [frame=off] +\\bTABLE[split=yes,option=stretch] +\\bTABLEhead +\\bTR + \\bTH Option \\eTH + \\bTH Description \\eTH +\\eTR +\\eTABLEhead +\\bTABLEbody +]] .. lpegmatch(inline_parser, str) .. [[ + +\\eTABLEbody +\\eTABLE +]] +end + +function rst_context.option_item (tab) + return stringformat([[\\bTR\\bTC %s \\eTC\\bTC %s \\eTC\\eTR +]], tab[1], tab[2]) +end + +function rst_context.test(str) + return ":" +end + +function rst_context.literal_block (str, included) + local indent = P" "^1 + local stripme = #str + for line in stringgmatch(str, "[^\n]+") do + -- setting to the lowest indend of all lines + local idt = lpegmatch(indent, line) + if line and idt then + stripme = idt < stripme and idt or stripme + end + end + + local strip = P{ + [1] = "strip", + strip = Cs(V"line"^1), + eol = P"\n", + restofline = (1 - V"eol")^0, + stop = Cs(V"eol" * P" "^0) * -P(1) / "", -- remove trailing blank lines + line = Cs(V"restofline" * (V"stop" + V"eol")) / function (line) + return #line > stripme and line:sub(stripme) or line + end, + } + + str = lpegmatch(strip, str) + str = [[ + +\starttyping[lines=hyphenated] +]] .. str .. [[ + +\stoptyping +]] + if included then -- escaping can ruin your day + str = str:gsub("\\", "\\\\") + end + return str +end + +function rst_context.included_literal_block (str) + return rst_context.literal_block(str, true) +end + +function rst_context.line_block (str) + rst_context.addsetups("lines") + return [[ + +\\startlines +]] .. lpegmatch(inline_parser, str) .. [[\\stoplines +]] +end + +function rst_context.line_block_line(str) + str = str:gsub("\n", " ") + return str .. "\n" +end + +function rst_context.line_block_empty() + return "\n" +end + +function rst_context.block_quote (tab) + rst_context.addsetups("blockquote") + local str = [[ +\\startlinecorrection +\\blank[small] +\\startblockquote +]] .. lpegmatch(inline_parser, tab[1]) .. [[ + +\\stopblockquote +]] + + return tab[2] and str .. [[ +\\blank[small] +\\startattribution +]] .. lpegmatch(inline_parser, tab[2]) .. [[ +\\stopattribution +\\blank[small] +\\stoplinecorrection +]] or str .. [[ +\\blank[small] +\\stoplinecorrection +]] +end + +--function rst_context.table (str) + --return [[ +--\\startlinecorrection +--]] .. str .. [[ + +--\\stoplinecorrection +--]] +--end + +function rst_context.grid_table (tab) + local body = "" + local nr = 1 + local head + if tab.has_head then + head = [[ +\\setupTABLE[c][each] [frame=off] +\\setupTABLE[r][each] [frame=off] +%\\startlinecorrection +\\bTABLE[split=repeat,option=stretch] +\\bTABLEhead +]] + while nr <= tab.head_end do + local r = tab.rows[nr] + local isempty = true + for n=1, #r do + local cell = r[n] + if cell.variant == "normal" then + isempty = false + break + end + end + + if not isempty then + local row = [[\\bTR]] + for n=1, #r do + local c = r[n] + if not (c.parent or + c.variant == "separator") then + local celltext = lpegmatch(inline_parser, c.stripped) + if c.span.x or c.span.y then + local span_exp = "[" + if c.span.x then + span_exp = span_exp .. "nc=" .. c.span.x .. "," + end + if c.span.y then + span_exp = span_exp .. "nr=" .. c.span.y + end + celltext = span_exp .. "] " .. celltext + + end + + row = row .. "\n " .. [[\\bTH ]] .. celltext .. [[\\eTH]] + end + end + head = head .. row .. "\n" .. [[\\eTR]] .. "\n" + end + nr = nr + 1 + end + head = head .. [[ +\\eTABLEhead +\\bTABLEbody +]] + else + head = [[ +\\setupTABLE[c][each] [frame=off] +\\setupTABLE[r][each] [frame=off] +%\\startlinecorrection +\\bTABLE[split=repeat,option=stretch] +\\bTABLEbody +]] + end + while nr <= #tab.rows do + local r = tab.rows[nr] + local isempty = true + for n=1, #r do + local cell = r[n] + if cell.variant == "normal" then + isempty = false + break + end + end + + if not isempty then + local row = [[\\bTR]] + for n=1, #r do + local c = r[n] + if not (c.parent or + c.variant == "separator") then + local celltext = lpegmatch(inline_parser, c.stripped) + if c.span.x or c.span.y then + local span_exp = "[" + if c.span.x then + span_exp = span_exp .. "nc=" .. c.span.x .. "," + end + if c.span.y then + span_exp = span_exp .. "nr=" .. c.span.y + end + celltext = span_exp .. "] " .. celltext + + end + + row = row .. "\n " .. [[\\bTC ]] .. celltext .. [[\\eTC]] + end + end + body = body .. row .. "\n" .. [[\\eTR]] .. "\n" + end + nr = nr + 1 + end + local tail = [[ +\\eTABLEbody +\\eTABLE +%\\stoplinecorrection +]] + return head .. body .. tail +end + + +function rst_context.simple_table(tab) + local head + local nr = 1 + if tab.head_end then + head = [[ +\\setupTABLE[c][each] [frame=off] +\\setupTABLE[r][each] [frame=off] +%\\startlinecorrection +\\bTABLE[split=yes,option=stretch] +\\bTABLEhead +]] + while nr <= tab.head_end do + local row = tab[nr] + if not row.ignore then + dbg_write(">hr>" .. #row) + head = head .. [[\\bTR]] + for nc=1, #row do + local cell = row[nc] + dbg_write("%7s | ", cell.content) + local celltext = lpegmatch(inline_parser, cell.content) + if cell.span then + head = head .. stringformat([=[\\bTH[nc=%s]%s\\eTH]=], cell.span.x, celltext or "") + else + head = head .. [[\\bTH ]] .. celltext .. [[\\eTH]] + end + end + dbg_write("\n") + head = head .. "\\\\eTR\n" + end + nr = nr + 1 + end + + head = head .. [[ +\\eTABLEhead +\\bTABLEbody +]] + else + head = [[ +\\setupTABLE[c][each] [frame=off] +\\setupTABLE[r][each] [frame=off] +%\\startlinecorrection +\\bTABLE[split=yes,option=stretch] +\\bTABLEbody +]] + end + local tail = [[ +\\eTABLEbody +\\eTABLE +%\\stoplinecorrection +]] + local body = "" + while nr <= #tab do + local row = tab[nr] + if not row.ignore then + dbg_write(">tr>" .. #row) + body = body .. [[\\bTR]] + for nc=1, #row do + local cell = row[nc] + dbg_write("%7s | ", cell.content) + local celltext = lpegmatch(inline_parser, cell.content) + if cell.span then + body = body .. stringformat([=[\\bTC[nc=%s]%s\\eTC]=], cell.span.x, celltext or "") + else + body = body .. [[\\bTC ]] .. celltext .. [[\\eTC]] + end + end + dbg_write("\n") + body = body .. "\\\\eTR\n" + end + nr = nr + 1 + end + return head .. body .. tail +end + +function rst_context.footnote (label, content) + local tf = rst_context.state.footnotes + rst_context.addsetups("footnotes") + if stringmatch(label, "^%d+$") then -- all digits + tf.numbered[tonumber(label)] = + rst_context.escape(lpegmatch(inline_parser, content)) + elseif label == "#" then --autonumber + repeat -- until next unrequested number + tf.autonumber = tf.autonumber + 1 + until tf.numbered[tf.autonumber] == nil + tf.numbered[tf.autonumber] = + rst_context.escape(lpegmatch(inline_parser, content)) + elseif stringmatch(label, "^#.+$") then + local thelabel = stringmatch(label, "^#(.+)$") + tf.autolabel[thelabel] = + rst_context.escape(lpegmatch(inline_parser, content)) + elseif label == "*" then + rst_context.addsetups("footnote_symbol") + tf.symbol[#tf.symbol+1] = + rst_context.escape(lpegmatch(inline_parser, content)) + else -- “citation reference” treated like ordinary footnote + repeat -- until next unrequested number + tf.autonumber = tf.autonumber + 1 + until tf.numbered[tf.autonumber] == nil + tf.numbered[tf.autonumber] = + rst_context.escape(lpegmatch(inline_parser, content)) + end + return "" +end + +--- hack to differentiate inline images +local special_substitutions = { + image = "inline_image", +} + +function rst_context.substitution_definition (subtext, directive, data) + local special = special_substitutions[directive] + if special then + --- override; pass data directly + directive = special + else + local tmp + if data.first ~= "" then + tmp = { data.first } + else + tmp = { } + end + data.first = nil + for i=1, #data do -- paragraphs + local current = tableconcat(data[i], "\n") + --current = lpegmatch(inline_parser, current) + --current = rst_context.escape(current) + tmp[#tmp+1] = current + end + data = tableconcat(tmp, "\n\n") + data = stringstrip(data) + end + subtext = stringgsub(subtext, "%s", "") + rst_context.substitutions[subtext] = { directive = directive, + data = data } + return "" +end + +-- not to be confused with the directive definition table rst_directives +function rst_context.directive(directive, data) + local fun = rst_directives[directive] + if fun then + rst_context.addsetups("directive") + local result = "" + result = fun(data) + return result + end + return "" +end + +-- vim:ft=lua:sw=4:ts=4:expandtab -- cgit v1.2.3