diff options
Diffstat (limited to 'Master/texmf-dist/tex/generic/pgf/lua/pgf/manual/DocumentParser.lua')
-rw-r--r-- | Master/texmf-dist/tex/generic/pgf/lua/pgf/manual/DocumentParser.lua | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/generic/pgf/lua/pgf/manual/DocumentParser.lua b/Master/texmf-dist/tex/generic/pgf/lua/pgf/manual/DocumentParser.lua new file mode 100644 index 00000000000..3905f348358 --- /dev/null +++ b/Master/texmf-dist/tex/generic/pgf/lua/pgf/manual/DocumentParser.lua @@ -0,0 +1,596 @@ +-- Copyright 2013 by Till Tantau +-- +-- This file may be distributed an/or modified +-- +-- 1. under the LaTeX Project Public License and/or +-- 2. under the GNU Public License +-- +-- See the file doc/generic/pgf/licenses/LICENSE for more information + +-- @release $Header: /cvsroot/pgf/pgf/generic/pgf/lua/pgf/manual/DocumentParser.lua,v 1.2 2013/08/01 16:02:22 tantau Exp $ + + + +--- +-- This class offers functions for converting the documentation of Lua +-- and other code into \TeX. +-- +-- @field renderers This array must consist of tables having two +-- fields: |test| and |renderer|. The first must be set to a function +-- that is called with an information table as parameter and must +-- return |true| if the funciton stored in the |renderer| should be +-- called. (Typically, the |test| will test whether the ``head line'' +-- following a documentation block has a special form.) + +local DocumentParser = { + renderers = {} +} + +-- Namespace: +require 'pgf.manual'.DocumentParser = DocumentParser + +-- Imports +local keys = require 'pgf.gd.interface.InterfaceCore'.keys + +-- Forwards: + +local collect_infos, render_infos + + +--- +-- Includes the documentation stored in some file in the manual. +-- +-- @param filename The file from which the documentation is to be read +-- @param type The type of the file. + +function DocumentParser.include(filename, typ) + + local fullname = assert(kpse.find_file(filename:gsub("%.", "/"), typ or "lua") + or kpse.find_file(filename:gsub("%.", "\\"), typ or "lua"), + "file " .. filename .. " not found") + + local file, error = io.open(fullname) + + -- First, let us read the file into a table to make handling easier: + local lines = {} + + for line in file:lines() do + lines [#lines + 1] = line + end + + -- A table storing the current output. The array part contains TeX + -- lines, the table part contains information about special table.s + local output = {} + + -- Now, start the main parser loop. + local i = 1 + while i <= #lines do + + if lines[i]:match("^%-%-%-") then + local infos = collect_infos (lines, i) + + infos.filename = filename + + render_infos(infos, output) + + i = infos.last_line + end + + i = i + 1 + end + + -- Render output: + for _,l in ipairs(output) do + if type(l) == "string" then + tex.print(l) + else + l() + end + end + +end + + + +--- +-- Add a test and a renderer to the array of renderers. +-- +-- @param test A test function (see |DocumentParser.renderers|). +-- @param renderer A rendering function. + +function DocumentParser.addRenderer (test, renderer) + DocumentParser.renderers [#DocumentParser.renderers + 1] = + { test = test, renderer = renderer } +end + + +-- Forwards: +local print_on_output, print_on_output_escape, print_docline_on_output, open_mode, close_mode, print_lines_on_output + + + +local function strip_quotes(s) + if s then return string.gsub(s, '^"(.*)"$', "%1") end +end + +local function split(s) + local t = {} + for line in string.gmatch(s, ".-\n") do + t[#t+1] = line + end + for i=#t,1,-1 do + if t[i]:match("^%s*$") then + t[i] = nil + else + return t + end + end + return t +end + + +local function process_string(s) + if s then + local t = split(s.."\n") + -- Compute min spaces + local min = math.huge + for _,l in ipairs(t) do + min = math.min(min, string.find(l, "%S") or math.huge) + end + if min < math.huge then + -- Now, trim 'em all! + for i=1,#t do + t[i] = string.sub(t[i],min,-2) + end + end + return t + end +end + + +local function process_examples(t) + if not t then + return nil + end + + if type(t) == "string" then + t = {t} + end + + local n = {} + for i=1,#t do + n[i] = process_string(strip_quotes(t[i])) + end + return n +end + + + +-- The standard renderers: + + +-- The function renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return + infos.keywords["function"] or + infos.head_line:match("^%s*function%s+") or + infos.head_line:match("^%s*local%s+function%s+") + end, + function (infos, output) + -- The renderer + + if infos.keywords["function"] then + local k = infos.keywords["function"][1] + infos.head_line = k:match("^%s*@(.*)") + end + + local rest = infos.head_line:match("function%s+(.*)") + local tab = rest:match("([^(]*[%.%:]).*") + local fun = rest:match("[^(]*[%.%:](.-)%s*%(") or rest:match("(.-)%s*%(") + local pars = rest:match(".-%((.*)%)") + + -- Render the head + print_on_output_escape(output, [[\begin{luacommand}]]) + output[#output+1] = "{" .. (tab or "") .. fun .. "}" + print_on_output_escape(output, + "{", tab or "", "}", + "{", fun, "}", + "{", pars, "}") + + if tab then + local table_name = tab:sub(1,-2) + local t = output[table_name] or {} + t[#t+1] = { + link = "pgf/lua/" .. tab .. fun, + text = "function " .. tab .. "\\declare{" .. fun .. "} (" .. pars .. ")" + } + output[table_name] = t + end + + local mode = "text" + for _,l in ipairs(infos.doc_lines) do + if mode ~= "done" then + mode = print_docline_on_output(output, l, mode) + end + end + close_mode(output, mode) + + print_on_output(output, [[\end{luacommand}]]) + + end +) + + +-- The table renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return + infos.keywords["table"] or + infos.head_line:match("=%s*{") + end, + function (infos, output) + -- The renderer + + if infos.keywords["table"] then + local k = infos.keywords["table"][1] + infos.head_line = k:match("^%s*@table(.*)") .. "=" + end + + local name = + infos.head_line:match("^%s*local%s+(.-)%s*=") or + infos.head_line:match("^%s*(.*)%s*=") + + -- Render the head + print_on_output_escape(output, + [[\begin{luatable}]], + "{", name:match("(.*[%.%:]).*") or "", "}", + "{", name:match(".*[%.%:](*-)") or name,"}", + "{", infos.filename, "}") + + local mode = "text" + for _,l in ipairs(infos.doc_lines) do + mode = print_docline_on_output(output, l, mode) + end + close_mode(output, mode) + + output[#output+1] = + function () + if output[name] then + tex.print("\\par\\emph{Alphabetical method summary:}\\par{\\small") + table.sort(output[name], function (a,b) return a.text < b.text end) + for _,l in ipairs(output[name]) do + tex.print("\\texttt{\\hyperlink{" .. l.link .. "}{" .. l.text:gsub("_", "\\_") .. "}}\\par") + end + tex.print("}") + end + end + + print_on_output(output, [[\end{luatable}]]) + + end +) + + + +-- The library renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return infos.keywords["library"] + end, + function (infos, output) + -- The renderer + + local name = infos.filename:gsub("%.library$",""):gsub("^pgf%.gd%.","") + + -- Render the head + print_on_output_escape(output, "\\begin{lualibrary}{", name, "}") + + local mode = "text" + for _,l in ipairs(infos.doc_lines) do + mode = print_docline_on_output(output, l, mode) + end + close_mode(output, mode) + + print_on_output(output, "\\end{lualibrary}") + + end +) + + +-- The section renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return infos.keywords["section"] + end, + function (infos, output) + -- The renderer + local mode = "text" + for _,l in ipairs(infos.doc_lines) do + mode = print_docline_on_output(output, l, mode) + end + close_mode(output, mode) + end +) + + +-- The documentation (plain text) renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return infos.keywords["documentation"] + end, + function (infos, output) + -- The renderer + local mode = "text" + for _,l in ipairs(infos.doc_lines) do + mode = print_docline_on_output(output, l, mode) + end + close_mode(output, mode) + end +) + + +-- The declare renderer +DocumentParser.addRenderer ( + function (infos) + -- The test + return + infos.keywords["declare"] or + infos.head_line:match("declare%s*{") or + infos.head_line:match("^%s*key%s*") + end, + function (infos, output) + -- The renderer + + local key_name + + if infos.keywords["declare"] then + local k = infos.keywords["declare"][1] + key_name = k:match("^%s*@declare%s*(.*)") + elseif infos.head_line:match("^%s*key%s*") then + key_name = infos.head_line:match('^%s*key%s*"(.*)"') or + infos.head_line:match("^%s*key%s*'(.*)'") + else + local l = infos.lines [infos.last_line + 1] + key_name = l:match('key%s*=%s*"(.*)"') or l:match("key%s*=%s*'(.*)'") + end + + assert (key_name, "could not determine key") + local key = assert (keys[key_name], "unknown key '" .. key_name .. "'") + + -- Render the head + if key.type then + print_on_output_escape(output, + "\\begin{luadeclare}", + "{", key.key, "}", + "{\\meta{", key.type, "}}", + "{", key.default or "", "}", + "{", key.initial or "", "}") + else + print_on_output_escape(output, + "\\begin{luadeclarestyle}", + "{", key.key, "}", + "{}", + "{", key.default or "", "}", + "{", key.initial or "", "}") + end + + + local mode = "text" + + print_lines_on_output(output, process_string(strip_quotes(key.summary))) + print_lines_on_output(output, process_string(strip_quotes(key.documentation))) + + if key.examples then + local e = process_examples(key.examples) + print_on_output(output, + "\\par\\smallskip\\emph{Example" .. (((#e>1) and "s") or "") .. "}\\par") + for _,example in ipairs(e) do + print_on_output(output, "\\begin{codeexample}[]") + print_lines_on_output(output, example) + print_on_output(output, "\\end{codeexample}") + end + end + + print_on_output(output, key.type and "\\end{luadeclare}" or "\\end{luadeclarestyle}") + end +) + + + +-- The empty line +DocumentParser.addRenderer ( + function (infos) + -- The test + return + #infos.doc_lines == 1 and + infos.doc_lines[1]:match("^%-*%s*$") + end, + function (infos, output) + end +) + + +function print_lines_on_output(output, lines) + for _,l in ipairs(lines or {}) do + output[#output+1] = l + end +end + +function print_on_output(output, ...) + local args = {...} + if #args > 0 then + for i = 1, #args do + args[i] = tostring(args[i]) + end + output[#output+1] = table.concat(args) + end +end + +function print_on_output_escape(output, ...) + local args = {...} + if #args > 0 then + for i = 1, #args do + args[i] = tostring(args[i]):gsub("_", "\\_") + end + output[#output+1] = table.concat(args) + end +end + + +function print_docline_on_output(output, l, mode) + + if l:match("^%s*@section") then + print_on_output(output, "\\", l:match("%s*@section%s*(.*)")) + elseif l:match("^%s*@param%s+") then + if mode ~= "param" then + close_mode (output, mode) + mode = open_mode (output, "param") + end + print_on_output(output, "\\item[\\texttt{", + l:match("%s@param%s+(.-)%s"):gsub("_", "\\_"), + "}] ", + l:match("%s@param%s+.-%s+(.*)")) + elseif l:match("^%s*@return%s+") then + if mode ~= "return" then + close_mode (output, mode) + mode = open_mode (output, "returns") + end + print_on_output(output, "\\item[]", l:match("%s@return%s+(.*)")) + elseif l:match("^%s*@see%s+") then + if mode ~= "text" then + close_mode (output, mode) + mode = open_mode (output, "text") + end + print_on_output(output, "\\par\\emph{See also:} \\texttt{", + l:match("%s@see%s+(.*)"):gsub("_", "\\_"), + "}") + elseif l:match("^%s*@usage%s+") then + if mode ~= "text" then + close_mode (output, mode) + mode = open_mode (output, "text") + end + print_on_output(output, "\\par\\emph{Usage:} ", + l:match("%s@usage%s+(.*)")) + elseif l:match("^%s*@field+") then + close_mode (output, mode) + mode = open_mode (output, "field") + print_on_output(output, "{", + (l:match("%s@field%s+(.-)%s") or l:match("%s@field%s+(.*)")):gsub("_", "\\_"), + "}", + l:match("%s@field%s+.-%s+(.*)")) + elseif l:match("^%s*@done") or l:match("^%s*@text") then + close_mode(output, mode) + print_on_output(output, l) + + mode = "text" + elseif l:match("^%s*@library") then + -- do nothing + elseif l:match("^%s*@end") then + close_mode(output, mode) + mode = "done" + elseif l:match("^%s*@") then + error("Unknown mark " .. l) + else + print_on_output(output, l) + end + + return mode +end + +function open_mode (output, mode) + if mode == "param" then + print_on_output(output, "\\begin{luaparameters}") + elseif mode == "field" then + print_on_output(output, "\\begin{luafield}") + elseif mode == "returns" then + print_on_output(output, "\\begin{luareturns}") + end + return mode +end + +function close_mode (output, mode) + if mode == "param" then + print_on_output(output, "\\end{luaparameters}") + elseif mode == "field" then + print_on_output(output, "\\end{luafield}") + elseif mode == "returns" then + print_on_output(output, "\\end{luareturns}") + end + return mode +end + + +function collect_infos (lines, i, state) + + local doc_lines = {} + + local keywords = {} + + local function find_keywords(line) + local keyword = line:match("^%s*@([^%s]*)") + if keyword then + local t = keywords[keyword] or {} + t[#t+1] = line + keywords[keyword] = t + end + return line + end + + -- Copy triple matches: + while lines[i] and lines[i]:match("^%-%-%-") do + doc_lines [#doc_lines + 1] = find_keywords(lines[i]:sub(4)) + i = i + 1 + end + + -- Continue with double matches: + while lines[i] and lines[i]:match("^%-%-") do + doc_lines [#doc_lines + 1] = find_keywords(lines[i]:sub(3)) + i = i + 1 + end + + local head_line = "" + + if not keywords["end"] then + -- Skip empty lines + while lines[i] and lines[i]:match("^%s*$") do + i = i + 1 + end + head_line = lines[i] or "" + if lines[i] and lines[i]:match("^%-%-%-") then + i = i - 1 + end + end + + return { + lines = lines, + last_line = i, + doc_lines = doc_lines, + keywords = keywords, + head_line = head_line + } + +end + + + +function render_infos(infos, state) + + for _,renderer in ipairs(DocumentParser.renderers) do + if renderer.test (infos, state) then + renderer.renderer (infos, state) + return + end + end + + pgf.debug(infos) + error("Unkown documentation type") +end + + +return DocumentParser |