summaryrefslogtreecommitdiff
path: root/tex/context/base/trac-lmx.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/trac-lmx.lua')
-rw-r--r--tex/context/base/trac-lmx.lua606
1 files changed, 493 insertions, 113 deletions
diff --git a/tex/context/base/trac-lmx.lua b/tex/context/base/trac-lmx.lua
index 452d03002..97938fb77 100644
--- a/tex/context/base/trac-lmx.lua
+++ b/tex/context/base/trac-lmx.lua
@@ -6,42 +6,62 @@ if not modules then modules = { } end modules ['trac-lmx'] = {
license = "see context related readme files"
}
--- todo: use lpeg instead (although not really needed)
+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 P, Cc, Cs, C, Carg, lpegmatch = lpeg.P, lpeg.Cc, lpeg.Cs, lpeg.C, lpeg.Carg, lpeg.match
-local gsub, format, concat, byte = string.gsub, string.format, table.concat, string.byte
+local allocate = utilities.storage.allocate
+local setmetatableindex = table.setmetatableindex
-local allocate = utilities.storage.allocate
+----- 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 = lmx or { }
-local lmx = lmx
+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)
-lmx.variables = allocate()
-local lmxvariables = lmx.variables
+local report_lmx = logs.reporter("lmx")
+local report_error = logs.reporter("lmx","error")
-local escapes = allocate {
- ['&'] = '&',
- ['<'] = '&lt;',
- ['>'] = '&gt;',
- ['"'] = '&quot;'
-}
+lmx = lmx or { }
+local lmx = lmx
+
+-- 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.
--- variables
+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',
+}
-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']
+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',
+}
-lmxvariables['color-background-three'] = function() return lmxvariables['color-background-one'] end
-lmxvariables['color-background-four'] = function() return lmxvariables['color-background-two'] end
+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)
+function lmx.set(key,value)
lmxvariables[key] = value
end
@@ -49,9 +69,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,40 +88,125 @@ local function do_print(one,two,...)
end
end
-local function do_escape(str)
- str = tostring(str)
- str = gsub(str,'&','&amp;')
- 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("empty file: %s",name)
+ end
+ return data
+end
+
+lmx.loadedfile = loadedfile
+
+-- A few helpers (the next one could end up in l-lpeg):
+
+local pattern = lpeg.replacer {
+ ["&"] = "&amp;",
+ [">"] = "&gt;",
+ ["<"] = "&lt;",
+ ['"'] = "&quot;",
+}
+
+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 %q => %s ...",str,string.collapsespaces(sub(value,1,80)))
+ else
+ report_lmx("variable %q => %s",str,string.collapsespaces(value))
+ end
+ elseif type(value) == "nil" then
+ report_lmx("variable %q => <!-- unset -->",str)
+ else
+ report_lmx("variable %q => %q",str,tostring(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_include(filename)
- local stylepath = do_variable('includepath') -- todo: store paths of loaded files
- local data = lmx.loadedfile(filename)
+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_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) -- todo: store paths of loaded files
+ local stylepath = lmxvariables.includepath
+ local data = loadedfile(filename)
if (not data or data == "") and stylepath and stylepath ~= "" then
- data = lmx.loadedfile(file.join(stylepath,filename))
+ data = loadedfile(file.join(stylepath,filename))
end
if not data or data == "" then
data = format("<!-- unknown lmx include file: %s -->",filename)
@@ -102,132 +214,400 @@ local function do_include(filename)
return data
end
+-- Flushers:
+
lmx.print = do_print
lmx.type = do_type
+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.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("variables => given defaults => lmx variables")
+ end
+ elseif trace_variables then
+ report_lmx("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("variables => given variables => given defaults => lmx variables")
+ end
+ elseif trace_variables then
+ report_lmx("variables == given variables => given defaults => lmx variables")
+ end
+ else
+ if variables ~= v then
+ setmetatableindex(variables,v)
+ if trace_variables then
+ report_lmx("variabes => given variables => given defaults")
+ end
+ elseif trace_variables then
+ report_lmx("variables == given variables => given defaults")
+ end
+ end
+ else
+ setmetatableindex(v,lmxvariables)
+ if variables ~= v then
+ setmetatableindex(variables,v)
+ if trace_variables then
+ report_lmx("variables => given variables => lmx variables")
+ end
+ elseif trace_variables then
+ report_lmx("variables == given variables => lmx variables")
+ end
+ end
+ result = { }
+end
+
+function lmx.finalized()
+ local collapsed = concat(result)
+ result = { } -- free memory
+ return collapsed
+end
+
+function lmx.getvariables()
+ return variables
end
-function lmx.tv(str)
- lmx.type(do_variable(str) or "")
+function lmx.reset()
+ -- obsolete
end
+-- Creation: (todo: strip <!-- -->)
+
local template = [[
- local definitions = { }
- local p, v, e, t, pv, tv = lmx.print, lmx.variable, lmx.escape, lmx.type, lmx.pv, lmx.tv
- %s
+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 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 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 begincomment = P("<!--")
+local endcomment = P("-->")
+
+local beginembedxml = P("<?")
+local endembedxml = P("?>")
+
+local beginembedcss = P("/*")
+local endembedcss = P("*/")
+
+local gobbledend = (optionalspaces * endembedxml) / ""
+local argument = (1-gobbledend)^0
+
+local comment = (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 / " p[==[%0]==] "
+
+local include = ((beginembedxml * P("lmx-include") * optionalspaces) / "")
+ * (argument / lmx.include)
+ * gobbledend
+
+local define_b = ((beginembedxml * P("lmx-define-begin") * optionalspaces) / "")
+ * argument
+ * gobbledend
+
+local define_e = ((beginembedxml * P("lmx-define-end") * optionalspaces) / "")
+ * argument
+ * gobbledend
+
+local define_c = C((1-define_e)^0)
+
+local define = (Carg(1) * C(define_b) * define_c * define_e) / savedefinition
+
+local resolve = ((beginembedxml * P("lmx-resolve") * optionalspaces) / "")
+ * ((Carg(1) * C(argument)) / getdefinition)
+ * gobbledend
+
+local pattern_1 = Cs((comment + include + P(1))^0) -- get rid of comments asap
+local pattern_2 = Cs((define + resolve + 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)
+function lmxnew(data,defaults,nocache) -- 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)
+ 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
+ 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)
+ return lmxresult(lmxnew(templatestring,nil,nocache),variables)
+end
+
+function lmx.convertfile(templatefile,variables,nocache)
+ if trace_variables then -- will become templates
+ report_lmx("converting file: %s",templatefile)
+ end
+ local converter = loadedfiles[templatefile]
+ if not converter then
+ converter = lmxnew(loadedfile(templatefile),nil,nocache)
+ loadedfiles[templatefile] = converter
end
- local result = lmx.result(data)
+ 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: %s",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)
+ if cache_files then
+ loadedfiles[templatefile] = converter
+ end
+ end
+ 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
+
+-- 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)
-lmx.lmxfile = function(filename) return filename end -- beware, these can be set!
-lmx.htmfile = function(filename) return filename end -- beware, these can be set!
+function lmx.stripcss(str)
+ return lpegmatch(stripper,str)
+end
-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.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 string.format("rgba(%s%%,%s%%,%s%%,%s)",r*100,g*100,b*100,a)
+ else
+ return string.format("rgb(%s%%,%s%%,%s%%)",r*100,g*100,b*100)
+ end
end
-function lmx.make(name,variables)
+
+-- 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 = file.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 somefile.css ?>
+-- <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 }))