#!/usr/bin/env texlua -------------------------------------------------------------------------------- -- FILE: rst_context.lua -- USAGE: ./rst_context.lua -- DESCRIPTION: -- OPTIONS: --- -- REQUIREMENTS: --- -- AUTHOR: Philipp Gesang (Phg), -- VERSION: 1.0 -- CREATED: 31/08/10 19:35:15 CEST -------------------------------------------------------------------------------- -- require "lpeg" local C, Cb, Cc, Cg, Cmt, Cp, Cs, Ct, P, R, S, V, match = 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 if not context then -- standard context lpeg stripper from l-string.lua local stripper = P{ [1] = "stripper", stripper = V"space"^0 * C((V"space"^0 * V"nospace"^1)^0), space = S(" \t\v\n"), nospace = 1 - V"space", } function string.strip(str) return stripper:match(str) or "" end end local rst_context = {} rst_context.collected_references = {} rst_context.collected_adornments = {} rst_context.last_section_level = 0 rst_context.anonymous_links = 0 rst_context.context_references = {} -- 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 refstring = "__contextref__" .. tostring(#crefs + 1) 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.paragraph (str) -- ugly as hell and probably slow too, but the alternative would be lots of -- concatenation return string.format([[ \\startparagraph %s \\stopparagraph ]], str) end function rst_context.literal (str) str = str:gsub([[\]], [[\\]]) -- evade escaping of backslashes return [[\\type{]] .. str .. [[}]] end function rst_context.interpreted_text (...) local tab = { ... } --print (tab, #tab, tab[1], tab[2], tab[3]) local role, str role = tab[1]:match("^:(.*):$") or tab[3]:match("^:(.*):$") str = tab[2] if not role then -- implicit role role = "emphasis" end --print(role, str) return rst_context[role](str) end function rst_context.link_standalone (str) return "\n" .. [[\\goto{\\hyphenatedurl{]] .. str .. [[}}[url(]] .. str .. [=[)]]=] end function rst_context.reference (str) str = str:match("[^_]*") local link = rst_context.collected_references[str] if not link then -- TODO make warning instead return([[{\\sc UNDEFINED REFERENCE ]] .. str .. [[}.]]) end return [[\\goto{]] .. str .. [[}[url(]] .. link .. [=[)]]=] end function rst_context.target (tab) --print("GOT ONE!") --local tab = { ... } local refs = rst_context.collected_references local target = tab[#tab] -- Ct + C could be clearer but who cares tab[#tab] = nil local function resolve_indirect (r) if r and r:match(".*_$") then -- pointing elsewhere return resolve_indirect (refs[r:match("(.*)_$")]) or "need another run!" -- TODO multiple runs && data collection end return r end local function create_anonymous () rst_context.anonymous_links = rst_context.anonymous_links + 1 return "__anon__" .. rst_context.anonymous_links end target = resolve_indirect (target) for i=1,#tab do local id = tab[i]:gsub("\\:",":") -- deescaping id = id ~= "" and id or create_anonymous () refs[id] = refs[id] or target end return "" end function rst_context.escape (str) return str:gsub("\\(.)", "%1") end function rst_context.joinindented (tab) return table.concat (tab, "") 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 if #tab == 3 then -- TODO use unicode length with ConTeXt --print(">>"..tab[1].."<>"..tab[2].."<<") adornchar = tab[1]:sub(1,1) -- overline == underline && len(overline) = len(sectionstring) section = tab[1] == tab[3] and #tab[1] >= #tab[2] -- if overline consists only of one char then keep truth value else -- false section = get_line_pattern(adornchar):match(tab[1]) ~= nil and section str = string.strip(tab[2]) else -- no overline --print(">>"..tab[1].."<>"..tab[2].."<<") adornchar = tab[2]:sub(1,1) section = #tab[1] <= #tab[2] section = get_line_pattern(adornchar):match(tab[2]) ~= nil and section 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 = string.format("\n\\\\%s[%s]{%s}\n", sectionlevels[level], ref, str) end return section and str or "" end -- Prime time for the fancybreak module. function rst_context.transition (str) return "\n\\hrule\n" end function rst_context.bullet_marker(str) return "marker" end do local space = lpeg.S(" \t\v\n") local nospace = 1 - space local stripper = space^0 * lpeg.C((space^0 * nospace^1)^0) function string.strip(str) return match(stripper,str) or "" end end local enumeration_types = { ["*"] = "*", -- unordered bulleted ["+"] = "*", ["-"] = "*", ["•"] = "*", ["‣"] = "*", ["⁃"] = "*", ["#"] = "1", -- 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 setup = [[\setupitemize[]] -- string.match is slightly faster than string.find if str:match("^%(") then setup = setup .. [[left=(,]] end if str:match("%)$") then setup = setup .. [[right=)]] end if str:match("%.$") then setup = setup .. [[stopper=.]] end setup = setup .. "]\n" str = itemstripper:match(str) return {setup = setup, str=str} end function rst_context.startitemize(str) local setup = "" str = string.strip(str) local listtype = enumeration_types[str] or parse_itemstring(str) if type(listtype) == "table" then print(type(listtype), listtype[2]) setup = listtype.setup listtype = listtype.str end return setup .. [[ \\startitemize[]] .. listtype .. [[] ]] end function rst_context.stopitemize(str) return str .. [[ \\stopitemize ]] end function rst_context.bullet_item (str) return [[ \\item ]] .. str .. [[ ]] end -------------------------------------------------------------------------------- -- Definition lists -------------------------------------------------------------------------------- -- TODO define proper setups (probably bnf-like and some narrower for def-paragraphs) function rst_context.deflist (str) return [[ \\startdefinitionlist ]] .. str .. [[ \\stopdefinitionlist ]] end function rst_context.deflist_item (str) return [[\\definitionitem{%]] .. str .. [[}% end definition item]] end function rst_context.deflist_classifier (str) return [[\\definitionclassifier{]] .. str .. [[}]] end function rst_context.deflist_term (str) return [[ \\definitionterm{]] .. str .. [[}]] end function rst_context.deflist_def (str) return [[ \\definitiondef{%]] .. str .. [[}]] 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) return [[ \\startfieldlist]] .. str .. [[\\stopfieldlist ]] end function rst_context.field_name (str) return [[\\fieldname{]] .. str .. [[}]] end function rst_context.field_body (str) return [[\\fieldbody{]] .. str .. [[}]] end function rst_context.field (tab) local name, body = tab[1], tab[2] return string.format([[ \\startfield \\fieldname{%s} \\fieldbody{%s} \\stopfield ]], name, body) end function rst_context.line_comment (str) return "% " .. str end function rst_context.block_comment (str) return string.format([[ \iffalse %s \fi ]], 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=repeat,option=stretch] \\bTABLEhead \\bTR \\bTH Option \\eTH \\bTH Description \\eTH \\eTR \\eTABLEhead \\bTABLEbody ]] .. str .. [[ \\eTABLEbody \\eTABLE ]] end function rst_context.option_item (tab) return string.format([[\\bTR\\bTC %s \\eTC\\bTC %s \\eTC\\eTR ]], tab[1], tab[2]) end function rst_context.literal_block (str) local indent = P" "^1 local stripme = indent:match(str) or 0 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 = strip:match(str) --strip:print() -- grammar consists of 45 rules only; wheras a plain -- pattern has 60+ return [[ \\starttyping[lines=hyphenated] ]] .. str .. [[ \\stoptyping ]] end function rst_context.line_block (str) return [[ \\startlines ]] .. 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) local str = [[ \\setupdelimitedtext [blockquote][style={\\setupbodyfont[11pt]}] % awful placeholder \\definedelimitedtext[attribution][blockquote] \\setupdelimitedtext [attribution][style={\\setupbodyfont[11pt]\\it}] \\startlinecorrection \\startblockquote ]] .. tab[1] .. [[ \\stopblockquote ]] return tab[2] and str .. [[ \\startattribution ]] .. tab[2] .. [[ \\stopattribution \\stoplinecorrection ]] or str .. [[ \\stoplinecorrection ]] end function rst_context.table (str) return [[ \\startlinecorrection ]] .. str .. [[ \\stoplinecorrection ]] end function rst_context.grid_table (tab) local head if tab.has_head then head = [[ \\setupTABLE[c][first] [background=color, backgroundcolor=grey, style=\tt] \\setupTABLE[c][each] [frame=on] \\setupTABLE[r][each] [frame=on] \\bTABLE[split=repeat,option=stretch] \\bTABLEhead \\bTR \\bTH first \\eTH \\bTH second \\eTH \\bTH third \\eTH \\bTH fourth \\eTH \\eTR \\eTABLEhead \\bTABLEbody ]] else head = [[ \\setupTABLE[c][each] [frame=on] \\setupTABLE[r][each] [frame=on] \\bTABLE[split=repeat,option=stretch] \\bTABLEbody ]] end local tail = [[ \\eTABLEbody \\eTABLE ]] local test = "" for i,r in ipairs(tab.rows) do local isempty = true for n, cell in ipairs(r) do if cell.variant == "normal" then isempty = false break end end if not isempty then local row = [[\\bTR]] for n,c in ipairs(r) do if not (c.parent or c.variant == "separator") then local celltext = 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 test = test .. row .. "\n" .. [[\\eTR]] .. "\n" end end return head .. test .. tail end function rst_context.table_row (tab) local tmp = [[\\bTR]] for n, cell in ipairs(tab) do tmp = tmp .. [[\\bTC]] .. cell .. [[\\eTC]] .. "\n" print (cell) end tmp = tmp .. [[\\eTR ]] return tmp end return rst_context