#!/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