diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/trac-lmx.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/trac-lmx.lua | 725 |
1 files changed, 612 insertions, 113 deletions
diff --git a/Master/texmf-dist/tex/context/base/trac-lmx.lua b/Master/texmf-dist/tex/context/base/trac-lmx.lua index 452d03002f9..18c7f60204f 100644 --- a/Master/texmf-dist/tex/context/base/trac-lmx.lua +++ b/Master/texmf-dist/tex/context/base/trac-lmx.lua @@ -6,42 +6,66 @@ if not modules then modules = { } end modules ['trac-lmx'] = { license = "see context related readme files" } --- todo: use lpeg instead (although not really needed) +-- this one will be adpated to the latest helpers -local gsub, format, concat, byte = string.gsub, string.format, table.concat, string.byte +local type, tostring, rawget, loadstring, pcall = type, tostring, rawget, loadstring, pcall +local format, sub, gsub = string.format, string.sub, string.gsub +local concat = table.concat +local collapsespaces = string.collapsespaces +local P, Cc, Cs, C, Carg, lpegmatch = lpeg.P, lpeg.Cc, lpeg.Cs, lpeg.C, lpeg.Carg, lpeg.match +local joinpath, replacesuffix, pathpart, filesuffix = file.join, file.replacesuffix, file.pathpart, file.suffix -local allocate = utilities.storage.allocate +local allocate = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex -lmx = lmx or { } -local lmx = lmx +----- trace_templates = false trackers .register("lmx.templates", function(v) trace_templates = v end) +local trace_variables = false trackers .register("lmx.variables", function(v) trace_variables = v end) -lmx.variables = allocate() -local lmxvariables = lmx.variables +local cache_templates = true directives.register("lmx.cache.templates",function(v) cache_templates = v end) +local cache_files = true directives.register("lmx.cache.files", function(v) cache_files = v end) -local escapes = allocate { - ['&'] = '&', - ['<'] = '<', - ['>'] = '>', - ['"'] = '"' -} +local report_lmx = logs.reporter("lmx") +local report_error = logs.reporter("lmx","error") --- variables +lmx = lmx or { } +local lmx = lmx -lmxvariables['title-default'] = 'ConTeXt LMX File' -lmxvariables['title'] = lmx.variables['title-default'] -lmxvariables['color-background-green'] = '#4F6F6F' -lmxvariables['color-background-blue'] = '#6F6F8F' -lmxvariables['color-background-yellow'] = '#8F8F6F' -lmxvariables['color-background-purple'] = '#8F6F8F' -lmxvariables['color-background-body'] = '#808080' -lmxvariables['color-background-main'] = '#3F3F3F' -lmxvariables['color-background-one'] = lmxvariables['color-background-green'] -lmxvariables['color-background-two'] = lmxvariables['color-background-blue'] +-- This will change: we will just pass the global defaults as argument, but then we need +-- to rewrite some older code or come up with an ugly trick. -lmxvariables['color-background-three'] = function() return lmxvariables['color-background-one'] end -lmxvariables['color-background-four'] = function() return lmxvariables['color-background-two'] end +local lmxvariables = { + ['title-default'] = 'ConTeXt LMX File', + ['color-background-green'] = '#4F6F6F', + ['color-background-blue'] = '#6F6F8F', + ['color-background-yellow'] = '#8F8F6F', + ['color-background-purple'] = '#8F6F8F', + ['color-background-body'] = '#808080', + ['color-background-main'] = '#3F3F3F', +} -function lmx.set(key, value) +local lmxinherited = { + ['title'] = 'title-default', + ['color-background-one'] = 'color-background-green', + ['color-background-two'] = 'color-background-blue', + ['color-background-three'] = 'color-background-one', + ['color-background-four'] = 'color-background-two', +} + +lmx.variables = lmxvariables +lmx.inherited = lmxinherited + +setmetatableindex(lmxvariables,function(t,k) + k = lmxinherited[k] + while k do + local v = rawget(lmxvariables,k) + if v then + return v + end + k = lmxinherited[k] + end +end) + +function lmx.set(key,value) lmxvariables[key] = value end @@ -49,9 +73,16 @@ function lmx.get(key) return lmxvariables[key] or "" end +lmx.report = report_lmx + -- helpers -local variables, result = { } -- we assume no nesting +-- the variables table is an empty one that gets linked to a defaults table +-- that gets passed with a creation (first time only) and that itself links +-- to one that gets passed to the converter + +local variables = { } -- we assume no nesting +local result = { } -- we assume no nesting local function do_print(one,two,...) if two then @@ -61,173 +92,641 @@ local function do_print(one,two,...) end end -local function do_escape(str) - str = tostring(str) - str = gsub(str,'&','&') - str = gsub(str,'[<>"]',escapes) - return str +-- Although it does not make much sense for most elements, we provide a mechanism +-- to print wrapped content, something that is more efficient when we are constructing +-- tables. + +local html = { } +lmx.html = html + +function html.td(str) + if type(str) == "table" then + for i=1,#str do -- spoils t ! + str[i] = format("<td>%s</td>",str[i] or "") + end + result[#result+1] = concat(str) + else + result[#result+1] = format("<td>%s</td>",str or "") + end end -local function do_urlescaped(str) - return (gsub(str,"[^%a%d]",format("%%0x",byte("%1")))) +function html.th(str) + if type(str) == "table" then + for i=1,#str do -- spoils t ! + str[i] = format("<th>%s</th>",str[i]) + end + result[#result+1] = concat(str) + else + result[#result+1] = format("<th>%s</th>",str or "") + end end -local function do_type(str) - if str then do_print("<tt>" .. do_escape(str) .. "</tt>") end +function html.a(text,url) + result[#result+1] = format("<a href=%q>%s</a>",url,text) +end + +setmetatableindex(html,function(t,k) + local f = format("<%s>%%s</%s>",k,k) + local v = function(str) result[#result+1] = format(f,str or "") end + t[k] = v + return v +end) + +-- Loading templates: + +local function loadedfile(name) + name = resolvers and resolvers.findfile and resolvers.findfile(name) or name + local data = io.loaddata(name) + if not data or data == "" then + report_lmx("file %a is empty",name) + end + return data +end + +local function loadedsubfile(name) + return io.loaddata(resolvers and resolvers.findfile and resolvers.findfile(name) or name) +end + +lmx.loadedfile = loadedfile + +-- A few helpers (the next one could end up in l-lpeg): + +local usedpaths = { } +local givenpath = nil + +local do_nested_include = nil + +local pattern = lpeg.replacer { + ["&"] = "&", + [">"] = ">", + ["<"] = "<", + ['"'] = """, +} + +local function do_escape(str) + return lpegmatch(pattern,str) or str end local function do_variable(str) - local value = variables[str] or lmxvariables[str] -- or format("<!-- unset lmx instance variable: %s -->",str or "?") - if type(value) == "function" then + local value = variables[str] + if not trace_variables then + -- nothing + elseif type(value) == "string" then + if #value > 80 then + report_lmx("variable %a is set to: %s ...",str,collapsespaces(sub(value,1,80))) + else + report_lmx("variable %a is set to: %s",str,collapsespaces(value)) + end + elseif type(value) == "nil" then + report_lmx("variable %a is set to: %s",str,"<!-- unset -->") + else + report_lmx("variable %a is set to: %S",str,value) + end + if type(value) == "function" then -- obsolete ... will go away return value(str) else return value end end -function lmx.loadedfile(name) - name = (resolvers and resolvers.findfile and resolvers.findfile(name)) or name - return io.loaddata(name) +local function do_type(str) + if str and str ~= "" then + result[#result+1] = format("<tt>%s</tt>",do_escape(str)) + end +end + +local function do_fprint(str,...) + if str and str ~= "" then + result[#result+1] = format(str,...) + end +end + +local function do_eprint(str,...) + if str and str ~= "" then + result[#result+1] = lpegmatch(pattern,format(str,...)) + end +end + +local function do_print_variable(str) + local str = do_variable(str) -- variables[str] + if str and str ~= "" then + result[#result+1] = str + end end -local function do_include(filename) - local stylepath = do_variable('includepath') -- todo: store paths of loaded files - local data = lmx.loadedfile(filename) - if (not data or data == "") and stylepath and stylepath ~= "" then - data = lmx.loadedfile(file.join(stylepath,filename)) +local function do_type_variable(str) + local str = do_variable(str) -- variables[str] + if str and str ~= "" then + result[#result+1] = format("<tt>%s</tt>",do_escape(str)) + end +end + +local function do_include(filename,option) + local data = loadedsubfile(filename) + if (not data or data == "") and givenpath then + data = loadedsubfile(joinpath(givenpath,filename)) + end + if (not data or data == "") and type(usedpaths) == "table" then + for i=1,#usedpaths do + data = loadedsubfile(joinpath(usedpaths[i],filename)) + if data and data ~= "" then + break + end + end end if not data or data == "" then data = format("<!-- unknown lmx include file: %s -->",filename) + report_lmx("include file %a is empty",filename) + else + -- report_lmx("included file: %s",filename) + data = do_nested_include(data) + end + if filesuffix(filename,"css") and option == "strip" then -- new + data = lmx.stripcss(data) end return data end +-- Flushers: + lmx.print = do_print lmx.type = do_type +lmx.eprint = do_eprint +lmx.fprint = do_fprint + lmx.escape = do_escape -lmx.urlescape = do_escape +lmx.urlescape = url.escape lmx.variable = do_variable lmx.include = do_include -function lmx.pv(str) - do_print(do_variable(str) or "") +lmx.inject = do_print +lmx.finject = do_fprint +lmx.einject = do_eprint + +lmx.pv = do_print_variable +lmx.tv = do_type_variable + +-- The next functions set up the closure. + +function lmx.initialize(d,v) + if not v then + setmetatableindex(d,lmxvariables) + if variables ~= d then + setmetatableindex(variables,d) + if trace_variables then + report_lmx("using chain: variables => given defaults => lmx variables") + end + elseif trace_variables then + report_lmx("using chain: variables == given defaults => lmx variables") + end + elseif d ~= v then + setmetatableindex(v,d) + if d ~= lmxvariables then + setmetatableindex(d,lmxvariables) + if variables ~= v then + setmetatableindex(variables,v) + if trace_variables then + report_lmx("using chain: variables => given variables => given defaults => lmx variables") + end + elseif trace_variables then + report_lmx("using chain: variables == given variables => given defaults => lmx variables") + end + else + if variables ~= v then + setmetatableindex(variables,v) + if trace_variables then + report_lmx("using chain: variabes => given variables => given defaults") + end + elseif trace_variables then + report_lmx("using chain: variables == given variables => given defaults") + end + end + else + setmetatableindex(v,lmxvariables) + if variables ~= v then + setmetatableindex(variables,v) + if trace_variables then + report_lmx("using chain: variables => given variables => lmx variables") + end + elseif trace_variables then + report_lmx("using chain: variables == given variables => lmx variables") + end + end + result = { } end -function lmx.tv(str) - lmx.type(do_variable(str) or "") +function lmx.finalized() + local collapsed = concat(result) + result = { } -- free memory + return collapsed end +function lmx.getvariables() + return variables +end + +function lmx.reset() + -- obsolete +end + +-- Creation: (todo: strip <!-- -->) + +-- local template = [[ +-- return function(defaults,variables) +-- +-- -- initialize +-- +-- lmx.initialize(defaults,variables) +-- +-- -- interface +-- +-- local definitions = { } +-- local variables = lmx.getvariables() +-- local html = lmx.html +-- local inject = lmx.print +-- local finject = lmx.fprint +-- local einject = lmx.eprint +-- local escape = lmx.escape +-- local verbose = lmx.type +-- +-- -- shortcuts (sort of obsolete as there is no gain) +-- +-- local p = lmx.print +-- local f = lmx.fprint +-- local v = lmx.variable +-- local e = lmx.escape +-- local t = lmx.type +-- local pv = lmx.pv +-- local tv = lmx.tv +-- +-- -- generator +-- +-- %s +-- +-- -- finalize +-- +-- return lmx.finalized() +-- +-- end +-- ]] + local template = [[ +-- interface + +local html = lmx.html +local inject = lmx.print +local finject = lmx.fprint -- better use the following +local einject = lmx.eprint -- better use the following +local injectf = lmx.fprint +local injecte = lmx.eprint +local injectfmt = lmx.fprint +local injectesc = lmx.eprint +local escape = lmx.escape +local verbose = lmx.type + +local i_n_j_e_c_t = lmx.print + +-- shortcuts (sort of obsolete as there is no gain) + +local p = lmx.print +local f = lmx.fprint +local v = lmx.variable +local e = lmx.escape +local t = lmx.type +local pv = lmx.pv +local tv = lmx.tv + +local lmx_initialize = lmx.initialize +local lmx_finalized = lmx.finalized +local lmx_getvariables = lmx.getvariables + +-- generator + +return function(defaults,variables) + + lmx_initialize(defaults,variables) + local definitions = { } - local p, v, e, t, pv, tv = lmx.print, lmx.variable, lmx.escape, lmx.type, lmx.pv, lmx.tv - %s + local variables = lmx_getvariables() + + %s -- the action: appends to result + + return lmx_finalized() + +end ]] +local function savedefinition(definitions,tag,content) + definitions[tag] = content + return "" +end + +local function getdefinition(definitions,tag) + return definitions[tag] or "" +end + +local whitespace = lpeg.patterns.whitespace +local optionalspaces = whitespace^0 + +local dquote = P('"') + +local begincomment = P("<!--") +local endcomment = P("-->") + +local beginembedxml = P("<?") +local endembedxml = P("?>") + +local beginembedcss = P("/*") +local endembedcss = P("*/") + +local gobbledendxml = (optionalspaces * endembedxml) / "" +----- argumentxml = (1-gobbledendxml)^0 +local argumentxml = (whitespace^1 + dquote * C((1-dquote)^1) * dquote + C((1-gobbledendxml-whitespace)^1))^0 + +local gobbledendcss = (optionalspaces * endembedcss) / "" +----- argumentcss = (1-gobbledendcss)^0 +local argumentcss = (whitespace^1 + dquote * C((1-dquote)^1) * dquote + C((1-gobbledendcss-whitespace)^1))^0 + +local commentxml = (begincomment * (1-endcomment)^0 * endcomment) / "" + +local beginluaxml = (beginembedxml * P("lua")) / "" +local endluaxml = endembedxml / "" + +local luacodexml = beginluaxml + * (1-endluaxml)^1 + * endluaxml + +local beginluacss = (beginembedcss * P("lua")) / "" +local endluacss = endembedcss / "" + +local luacodecss = beginluacss + * (1-endluacss)^1 + * endluacss + +local othercode = (1-beginluaxml-beginluacss)^1 / " i_n_j_e_c_t[==[%0]==] " + +local includexml = ((beginembedxml * P("lmx-include") * optionalspaces) / "") + * (argumentxml / do_include) + * gobbledendxml + +local includecss = ((beginembedcss * P("lmx-include") * optionalspaces) / "") + * (argumentcss / do_include) + * gobbledendcss + +local definexml_b = ((beginembedxml * P("lmx-define-begin") * optionalspaces) / "") + * argumentxml + * gobbledendxml + +local definexml_e = ((beginembedxml * P("lmx-define-end") * optionalspaces) / "") + * argumentxml + * gobbledendxml + +local definexml_c = C((1-definexml_e)^0) + +local definexml = (Carg(1) * C(definexml_b) * definexml_c * definexml_e) / savedefinition + +local resolvexml = ((beginembedxml * P("lmx-resolve") * optionalspaces) / "") + * ((Carg(1) * C(argumentxml)) / getdefinition) + * gobbledendxml + +local definecss_b = ((beginembedcss * P("lmx-define-begin") * optionalspaces) / "") + * argumentcss + * gobbledendcss + +local definecss_e = ((beginembedcss * P("lmx-define-end") * optionalspaces) / "") + * argumentcss + * gobbledendcss + +local definecss_c = C((1-definecss_e)^0) + +local definecss = (Carg(1) * C(definecss_b) * definecss_c * definecss_e) / savedefinition + +local resolvecss = ((beginembedcss * P("lmx-resolve") * optionalspaces) / "") + * ((Carg(1) * C(argumentcss)) / getdefinition) + * gobbledendcss + +local pattern_1 = Cs((commentxml + includexml + includecss + P(1))^0) -- get rid of xml comments asap +local pattern_2 = Cs((definexml + resolvexml + definecss + resolvecss + P(1))^0) +local pattern_3 = Cs((luacodexml + luacodecss + othercode)^0) + local cache = { } -local trace = false +local function lmxerror(str) + report_error(str) + return html.tt(str) +end + +local function wrapper(converter,defaults,variables) + local outcome, message = pcall(converter,defaults,variables) + if not outcome then + return lmxerror(format("error in conversion: %s",message)) + else + return message + end +end -function lmx.new(data,variables) +do_nested_include = function(data) -- also used in include + return lpegmatch(pattern_1,data) +end + +function lmxnew(data,defaults,nocache,path) -- todo: use defaults in calling routines data = data or "" local known = cache[data] if not known then - local definitions = { } - data = gsub(data,"<%?lmx%-include%s+(.-)%s-%?>", function(filename) - return lmx.include(filename) - end) - local definitions = { } - data = gsub(data,"<%?lmx%-define%-begin%s+(%S-)%s-%?>(.-)<%?lmx%-define%-end%s-%?>", function(tag,content) - definitions[tag] = content - return "" - end) - data = gsub(data,"<%?lmx%-resolve%s+(%S-)%s-%?>", function(tag) - return definitions[tag] or "" - end) - data = gsub(data .. "<?lua ?>","(.-)<%?lua%s+(.-)%s*%?>", function(txt,lua) - txt = gsub(txt,"%c+","\n") - return format("p(%q)%s ",txt,lua) -- nb! space - end) - data = format(template,data) + givenpath = path + usedpaths = lmxvariables.includepath or { } + if type(usedpaths) == "string" then + usedpaths = { usedpaths } + end + data = lpegmatch(pattern_1,data) + data = lpegmatch(pattern_2,data,1,{}) + data = lpegmatch(pattern_3,data) + local converted = loadstring(format(template,data)) + if converted then + converted = converted() + end + defaults = defaults or { } + local converter + if converted then + converter = function(variables) + return wrapper(converted,defaults,variables) + end + else + report_error("error in:\n%s\n:",data) + converter = function() lmxerror("error in template") end + end known = { - data = trace and data, - variables = variables or { }, - converter = loadstring(data), + data = defaults.trace and data or "", + variables = defaults, + converter = converter, } + if cache_templates and nocache ~= false then + cache[data] = known + end elseif variables then known.variables = variables end return known, known.variables end -function lmx.reset(self) - self.variables = { } -end - -function lmx.result(self) - if trace then - return self.data +local function lmxresult(self,variables) + if self then + local converter = self.converter + if converter then + local converted = converter(variables) + if trace_variables then -- will become templates + report_lmx("converted size: %s",#converted) + end + return converted or lmxerror("no result from converter") + else + return lmxerror("invalid converter") + end else - variables, result = self.variables, { } - self.converter() - return concat(result) + return lmxerror("invalid specification") end end --- file converter +lmx.new = lmxnew +lmx.result = lmxresult -local loaded = { } +local loadedfiles = { } -function lmx.convert(templatefile,resultfile,variables) - local data = loaded[templatefile] - if not data then - data = lmx.new(lmx.loadedfile(templatefile),variables) - loaded[template] = data - elseif variables then - data.variables = variables +function lmx.convertstring(templatestring,variables,nocache,path) + return lmxresult(lmxnew(templatestring,nil,nocache,path),variables) +end + +function lmx.convertfile(templatefile,variables,nocache) + if trace_variables then -- will become templates + report_lmx("converting file %a",templatefile) + end + local converter = loadedfiles[templatefile] + if not converter then + converter = lmxnew(loadedfile(templatefile),nil,nocache,pathpart(templatefile)) + loadedfiles[templatefile] = converter + end + return lmxresult(converter,variables) +end + +function lmxconvert(templatefile,resultfile,variables,nocache) -- or (templatefile,variables) + if trace_variables then -- will become templates + report_lmx("converting file %a",templatefile) + end + if not variables and type(resultfile) == "table" then + variables = resultfile + end + local converter = loadedfiles[templatefile] + if not converter then + converter = lmxnew(loadedfile(templatefile),nil,nocache,pathpart(templatefile)) + if cache_files then + loadedfiles[templatefile] = converter + end end - local result = lmx.result(data) + local result = lmxresult(converter,variables) if resultfile then io.savedata(resultfile,result) else - return lmx.result(data,result) + return result end end --- these can be overloaded; we assume that the os handles filename associations +lmx.convert = lmxconvert -lmx.lmxfile = function(filename) return filename end -- beware, these can be set! -lmx.htmfile = function(filename) return filename end -- beware, these can be set! +-- helpers + +local nocomment = (beginembedcss * (1 - endembedcss)^1 * endembedcss) / "" +local nowhitespace = whitespace^1 / " " -- "" +local semistripped = whitespace^1 / "" * P(";") +local stripper = Cs((nocomment + semistripped + nowhitespace + 1)^1) -if os.type == "windows" then - lmx.popupfile = function(filename) os.execute("start " .. filename) end -else - lmx.popupfile = function(filename) os.execute(filename) end +function lmx.stripcss(str) + return lpegmatch(stripper,str) end -function lmx.make(name,variables) +function lmx.color(r,g,b,a) + if r > 1 then + r = 1 + end + if g > 1 then + g = 1 + end + if b > 1 then + b = 1 + end + if not a then + a= 0 + elseif a > 1 then + a = 1 + end + if a > 0 then + return format("rgba(%s%%,%s%%,%s%%,%s)",r*100,g*100,b*100,a) + else + return format("rgb(%s%%,%s%%,%s%%)",r*100,g*100,b*100) + end +end + + +-- these can be overloaded + +lmx.lmxfile = string.itself +lmx.htmfile = string.itself +lmx.popupfile = os.launch + +function lmxmake(name,variables) local lmxfile = lmx.lmxfile(name) local htmfile = lmx.htmfile(name) if lmxfile == htmfile then - htmfile = gsub(lmxfile, "%.%a+$", "html") + htmfile = replacesuffix(lmxfile,"html") end - lmx.convert(lmxfile,htmfile,variables) + lmxconvert(lmxfile,htmfile,variables) return htmfile end +lmxmake = lmx.make + function lmx.show(name,variables) - local htmfile = lmx.make(name,variables) + local htmfile = lmxmake(name,variables) lmx.popupfile(htmfile) return htmfile end --- test - ---~ print(lmx.result(lmx.new(io.loaddata("t:/sources/context-timing.lmx")))) - --- command line +-- Command line (will become mtx-lmx): if arg then if arg[1] == "--show" then if arg[2] then lmx.show (arg[2]) end elseif arg[1] == "--convert" then if arg[2] then lmx.convert(arg[2], arg[3] or "temp.html") end end end + +-- Test 1: + +-- inspect(lmx.result(lmx.new(io.loaddata("t:/sources/context-timing.lmx")))) + +-- Test 2: + +-- local str = [[ +-- <?lmx-include context.css strip ?> +-- <test> +-- <?lmx-define-begin whatever?>some content a<?lmx-define-end ?> +-- <?lmx-define-begin somemore?>some content b<?lmx-define-end ?> +-- <more> +-- <?lmx-resolve whatever ?> +-- <?lua +-- for i=1,10 do end +-- ?> +-- <?lmx-resolve somemore ?> +-- </more> +-- <td><?lua p(100) ?></td> +-- <td><?lua p(variables.a) ?></td> +-- <td><?lua p(variables.b) ?></td> +-- <td><?lua p(variables.c) ?></td> +-- <td><?lua pv('title-default') ?></td> +-- </test> +-- ]] + +-- local defaults = { trace = true, a = 3, b = 3 } +-- local result = lmx.new(str,defaults) +-- inspect(result.data) +-- inspect(result.converter(defaults)) +-- inspect(result.converter { a = 1 }) +-- inspect(lmx.result(result, { b = 2 })) +-- inspect(lmx.result(result, { a = 20000, b = 40000 })) |