#!/usr/bin/env texlua
if not modules then modules = { } end modules ['mtxrun'] = {
version = 1.001,
comment = "runner, lua replacement for texmfstart.rb",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
-- one can make a stub:
--
-- #!/bin/sh
-- env LUATEXDIR=/....../texmf/scripts/context/lua luatex --luaonly mtxrun.lua "$@"
-- filename : mtxrun.lua
-- comment : companion to context.tex
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
-- This script is based on texmfstart.rb but does not use kpsewhich to
-- locate files. Although kpse is a library it never came to opening up
-- its interface to other programs (esp scripting languages) and so we
-- do it ourselves. The lua variant evolved out of an experimental ruby
-- one. Interesting is that using a scripting language instead of c does
-- not have a speed penalty. Actually the lua variant is more efficient,
-- especially when multiple calls to kpsewhich are involved. The lua
-- library also gives way more ocntrol.
-- to be done / considered
--
-- support for --exec or make it default
-- support for jar files (or maybe not, never used, too messy)
-- support for $RUBYINPUTS cum suis (if still needed)
-- remember for subruns: _CTX_K_V_#{original}_
-- remember for subruns: _CTX_K_S_#{original}_
-- remember for subruns: TEXMFSTART.#{original} [tex.rb texmfstart.rb]
banner = "version 1.0.2 - 2007+ - PRAGMA ADE / CONTEXT"
texlua = true
-- begin library merge
-- filename : l-string.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-string'] = 1.001
--~ function string.split(str, pat) -- taken from the lua wiki
--~ local t = {n = 0} -- so this table has a length field, traverse with ipairs then!
--~ local fpat = "(.-)"..pat
--~ local last_end = 1
--~ local s, e, cap = string.find(str, fpat, 1)
--~ while s ~= nil do
--~ if s~=1 or cap~="" then
--~ table.insert(t,cap)
--~ end
--~ last_end = e+1
--~ s, e, cap = string.find(str, fpat, last_end)
--~ end
--~ if last_end<=string.len(str) then
--~ table.insert(t,(string.sub(str,last_end)))
--~ end
--~ return t
--~ end
--~ function string:split(pat) -- taken from the lua wiki but adapted
--~ local t = { } -- self and colon usage (faster)
--~ local fpat = "(.-)"..pat
--~ local last_end = 1
--~ local s, e, cap = self:find(fpat, 1)
--~ while s ~= nil do
--~ if s~=1 or cap~="" then
--~ t[#t+1] = cap
--~ end
--~ last_end = e+1
--~ s, e, cap = self:find(fpat, last_end)
--~ end
--~ if last_end <= #self then
--~ t[#t+1] = self:sub(last_end)
--~ end
--~ return t
--~ end
--~ a piece of brilliant code by Rici Lake (posted on lua list) -- only names changed
--~
--~ function string:splitter(pat)
--~ local st, g = 1, self:gmatch("()"..pat.."()")
--~ local function splitter(self)
--~ if st then
--~ local s, f = g()
--~ local rv = self:sub(st, (s or 0)-1)
--~ st = f
--~ return rv
--~ end
--~ end
--~ return splitter, self
--~ end
function string:splitter(pat)
-- by Rici Lake (posted on lua list) -- only names changed
-- p 79 ref man: () returns position of match
local st, g = 1, self:gmatch("()("..pat..")")
local function strgetter(self, segs, seps, sep, cap1, ...)
st = sep and seps + #sep
return self:sub(segs, (seps or 0) - 1), cap1 or sep, ...
end
local function strsplitter(self)
if st then return strgetter(self, st, g()) end
end
return strsplitter, self
end
function string:split(separator)
local t = {}
for k in self:splitter(separator) do t[#t+1] = k end
return t
end
-- faster than a string:split:
function string:splitchr(chr)
if #self > 0 then
local t = { }
for s in string.gmatch(self..chr,"(.-)"..chr) do
t[#t+1] = s
end
return t
else
return { }
end
end
--~ function string.piecewise(str, pat, fnc) -- variant of split
--~ local fpat = "(.-)"..pat
--~ local last_end = 1
--~ local s, e, cap = string.find(str, fpat, 1)
--~ while s ~= nil do
--~ if s~=1 or cap~="" then
--~ fnc(cap)
--~ end
--~ last_end = e+1
--~ s, e, cap = string.find(str, fpat, last_end)
--~ end
--~ if last_end <= #str then
--~ fnc((string.sub(str,last_end)))
--~ end
--~ end
function string.piecewise(str, pat, fnc) -- variant of split
for k in string.splitter(str,pat) do fnc(k) end
end
--~ function string.piecewise(str, pat, fnc) -- variant of split
--~ for k in str:splitter(pat) do fnc(k) end
--~ end
--~ do if lpeg then
--~ -- this alternative is 30% faster esp when we cache them
--~ -- problem: no expressions
--~ splitters = { }
--~ function string:split(separator)
--~ if #self > 0 then
--~ local split = splitters[separator]
--~ if not split then
--~ -- based on code by Roberto
--~ local p = lpeg.P(separator)
--~ local c = lpeg.C((1-p)^0)
--~ split = lpeg.Ct(c*(p*c)^0)
--~ splitters[separator] = split
--~ end
--~ return split:match(self)
--~ else
--~ return { }
--~ end
--~ end
--~ string.splitchr = string.split
--~ function string:piecewise(separator,fnc)
--~ for _,v in pairs(self:split(separator)) do
--~ fnc(v)
--~ end
--~ end
--~ end end
string.chr_to_esc = {
["%"] = "%%",
["."] = "%.",
["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
["^"] = "%^", ["$"] = "%$",
["["] = "%[", ["]"] = "%]",
["("] = "%(", [")"] = "%)",
["{"] = "%{", ["}"] = "%}"
}
function string:esc() -- variant 2
return (self:gsub("(.)",string.chr_to_esc))
end
function string.unquote(str)
return (str:gsub("^([\"\'])(.*)%1$","%2"))
end
function string.quote(str)
return '"' .. str:unquote() .. '"'
end
function string:count(pattern) -- variant 3
local n = 0
for _ in self:gmatch(pattern) do
n = n + 1
end
return n
end
function string:limit(n,sentinel)
if #self > n then
sentinel = sentinel or " ..."
return self:sub(1,(n-#sentinel)) .. sentinel
else
return self
end
end
function string:strip()
return (self:gsub("^%s*(.-)%s*$", "%1"))
end
--~ function string.strip(str) -- slightly different
--~ return (string.gsub(string.gsub(str,"^%s*(.-)%s*$","%1"),"%s+"," "))
--~ end
function string:is_empty()
return not self:find("%S")
end
function string:enhance(pattern,action)
local ok, n = true, 0
while ok do
ok = false
self = self:gsub(pattern, function(...)
ok, n = true, n + 1
return action(...)
end)
end
return self, n
end
--~ function string:enhance(pattern,action)
--~ local ok, n = 0, 0
--~ repeat
--~ self, ok = self:gsub(pattern, function(...)
--~ n = n + 1
--~ return action(...)
--~ end)
--~ until ok == 0
--~ return self, n
--~ end
--~ function string:to_hex()
--~ if self then
--~ return (self:gsub("(.)",function(c)
--~ return string.format("%02X",c:byte())
--~ end))
--~ else
--~ return ""
--~ end
--~ end
--~ function string:from_hex()
--~ if self then
--~ return (self:gsub("(..)",function(c)
--~ return string.char(tonumber(c,16))
--~ end))
--~ else
--~ return ""
--~ end
--~ end
string.chr_to_hex = { }
string.hex_to_chr = { }
for i=0,255 do
local c, h = string.char(i), string.format("%02X",i)
string.chr_to_hex[c], string.hex_to_chr[h] = h, c
end
--~ function string:to_hex()
--~ if self then return (self:gsub("(.)",string.chr_to_hex)) else return "" end
--~ end
--~ function string:from_hex()
--~ if self then return (self:gsub("(..)",string.hex_to_chr)) else return "" end
--~ end
function string:to_hex()
return ((self or ""):gsub("(.)",string.chr_to_hex))
end
function string:from_hex()
return ((self or ""):gsub("(..)",string.hex_to_chr))
end
if not string.characters then
local function nextchar(str, index)
index = index + 1
return (index <= #str) and index or nil, str:sub(index,index)
end
function string:characters()
return nextchar, self, 0
end
local function nextbyte(str, index)
index = index + 1
return (index <= #str) and index or nil, string.byte(str:sub(index,index))
end
function string:bytes()
return nextbyte, self, 0
end
end
--~ function string:padd(n,chr)
--~ return self .. self.rep(chr or " ",n-#self)
--~ end
function string:rpadd(n,chr)
local m = n-#self
if m > 0 then
return self .. self.rep(chr or " ",m)
else
return self
end
end
function string:lpadd(n,chr)
local m = n-#self
if m > 0 then
return self.rep(chr or " ",m) .. self
else
return self
end
end
string.padd = string.rpadd
function is_number(str)
return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
end
--~ print(is_number("1"))
--~ print(is_number("1.1"))
--~ print(is_number(".1"))
--~ print(is_number("-0.1"))
--~ print(is_number("+0.1"))
--~ print(is_number("-.1"))
--~ print(is_number("+.1"))
function string:split_settings() -- no {} handling, see l-aux for lpeg variant
if self:find("=") then
local t = { }
for k,v in self:gmatch("(%a+)=([^%,]*)") do
t[k] = v
end
return t
else
return nil
end
end
local patterns_escapes = {
["-"] = "%-",
["."] = "%.",
["+"] = "%+",
["*"] = "%*",
["%"] = "%%",
["("] = "%)",
[")"] = "%)",
["["] = "%[",
["]"] = "%]",
}
function string:pattesc()
return (self:gsub(".",patterns_escapes))
end
function string:tohash()
local t = { }
for s in self:gmatch("([^, ]+)") do -- lpeg
t[s] = true
end
return t
end
-- filename : l-lpeg.lua
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-lpeg'] = 1.001
--~ l-lpeg.lua :
--~ lpeg.digit = lpeg.R('09')^1
--~ lpeg.sign = lpeg.S('+-')^1
--~ lpeg.cardinal = lpeg.P(lpeg.sign^0 * lpeg.digit^1)
--~ lpeg.integer = lpeg.P(lpeg.sign^0 * lpeg.digit^1)
--~ lpeg.float = lpeg.P(lpeg.sign^0 * lpeg.digit^0 * lpeg.P('.') * lpeg.digit^1)
--~ lpeg.number = lpeg.float + lpeg.integer
--~ lpeg.oct = lpeg.P("0") * lpeg.R('07')^1
--~ lpeg.hex = lpeg.P("0x") * (lpeg.R('09') + lpeg.R('AF'))^1
--~ lpeg.uppercase = lpeg.P("AZ")
--~ lpeg.lowercase = lpeg.P("az")
--~ lpeg.eol = lpeg.S('\r\n\f')^1 -- includes formfeed
--~ lpeg.space = lpeg.S(' ')^1
--~ lpeg.nonspace = lpeg.P(1-lpeg.space)^1
--~ lpeg.whitespace = lpeg.S(' \r\n\f\t')^1
--~ lpeg.nonwhitespace = lpeg.P(1-lpeg.whitespace)^1
local hash = { }
function lpeg.anywhere(pattern) --slightly adapted from website
return lpeg.P { lpeg.P(pattern) + 1 * lpeg.V(1) }
end
function lpeg.startswith(pattern) --slightly adapted
return lpeg.P(pattern)
end
--~ g = lpeg.splitter(" ",function(s) ... end) -- gmatch:lpeg = 3:2
function lpeg.splitter(pattern, action)
return (((1-lpeg.P(pattern))^1)/action+1)^0
end
local crlf = lpeg.P("\r\n")
local cr = lpeg.P("\r")
local lf = lpeg.P("\n")
local space = lpeg.S(" \t\f\v")
local newline = crlf + cr + lf
local spacing = space^0 * newline
local empty = spacing * lpeg.Cc("")
local nonempty = lpeg.Cs((1-spacing)^1) * spacing^-1
local content = (empty + nonempty)^1
local capture = lpeg.Ct(content^0)
function string:splitlines()
return capture:match(self)
end
-- filename : l-table.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-table'] = 1.001
table.join = table.concat
function table.strip(tab)
local lst = { }
for k, v in ipairs(tab) do
-- s = string.gsub(v, "^%s*(.-)%s*$", "%1")
s = v:gsub("^%s*(.-)%s*$", "%1")
if s == "" then
-- skip this one
else
lst[#lst+1] = s
end
end
return lst
end
--~ function table.sortedkeys(tab)
--~ local srt = { }
--~ for key,_ in pairs(tab) do
--~ srt[#srt+1] = key
--~ end
--~ table.sort(srt)
--~ return srt
--~ end
function table.sortedkeys(tab)
local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
for key,_ in pairs(tab) do
srt[#srt+1] = key
if kind == 3 then
-- no further check
else
local tkey = type(key)
if tkey == "string" then
-- if kind == 2 then kind = 3 else kind = 1 end
kind = (kind == 2 and 3) or 1
elseif tkey == "number" then
-- if kind == 1 then kind = 3 else kind = 2 end
kind = (kind == 1 and 3) or 2
else
kind = 3
end
end
end
if kind == 0 or kind == 3 then
table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
else
table.sort(srt)
end
return srt
end
function table.append(t, list)
for _,v in pairs(list) do
table.insert(t,v)
end
end
function table.prepend(t, list)
for k,v in pairs(list) do
table.insert(t,k,v)
end
end
function table.merge(t, ...) -- first one is target
t = t or {}
local lst = {...}
for i=1,#lst do
for k, v in pairs(lst[i]) do
t[k] = v
end
end
return t
end
function table.merged(...)
local tmp, lst = { }, {...}
for i=1,#lst do
for k, v in pairs(lst[i]) do
tmp[k] = v
end
end
return tmp
end
function table.imerge(t, ...)
local lst = {...}
for i=1,#lst do
local nst = lst[i]
for j=1,#nst do
t[#t+1] = nst[j]
end
end
return t
end
function table.imerged(...)
local tmp, lst = { }, {...}
for i=1,#lst do
local nst = lst[i]
for j=1,#nst do
tmp[#tmp+1] = nst[j]
end
end
return tmp
end
if not table.fastcopy then do
local type, pairs, getmetatable, setmetatable = type, pairs, getmetatable, setmetatable
local function fastcopy(old) -- fast one
if old then
local new = { }
for k,v in pairs(old) do
if type(v) == "table" then
new[k] = fastcopy(v) -- was just table.copy
else
new[k] = v
end
end
local mt = getmetatable(old)
if mt then
setmetatable(new,mt)
end
return new
else
return { }
end
end
table.fastcopy = fastcopy
end end
if not table.copy then do
local type, pairs, getmetatable, setmetatable = type, pairs, getmetatable, setmetatable
local function copy(t, tables) -- taken from lua wiki, slightly adapted
tables = tables or { }
local tcopy = {}
if not tables[t] then
tables[t] = tcopy
end
for i,v in pairs(t) do -- brrr, what happens with sparse indexed
if type(i) == "table" then
if tables[i] then
i = tables[i]
else
i = copy(i, tables)
end
end
if type(v) ~= "table" then
tcopy[i] = v
elseif tables[v] then
tcopy[i] = tables[v]
else
tcopy[i] = copy(v, tables)
end
end
local mt = getmetatable(t)
if mt then
setmetatable(tcopy,mt)
end
return tcopy
end
table.copy = copy
end end
-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
function table.sub(t,i,j)
return { unpack(t,i,j) }
end
function table.replace(a,b)
for k,v in pairs(b) do
a[k] = v
end
end
-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
function table.is_empty(t)
return not t or not next(t)
end
function table.one_entry(t)
local n = next(t)
return n and not next(t,n)
end
function table.starts_at(t)
return ipairs(t,1)(t,0)
end
do
-- one of my first exercises in lua ...
-- 34.055.092 32.403.326 arabtype.tma
-- 1.620.614 1.513.863 lmroman10-italic.tma
-- 1.325.585 1.233.044 lmroman10-regular.tma
-- 1.248.157 1.158.903 lmsans10-regular.tma
-- 194.646 153.120 lmtypewriter10-regular.tma
-- 1.771.678 1.658.461 palatinosanscom-bold.tma
-- 1.695.251 1.584.491 palatinosanscom-regular.tma
-- 13.736.534 13.409.446 zapfinoextraltpro.tma
-- 13.679.038 11.774.106 arabtype.tmc
-- 886.248 754.944 lmroman10-italic.tmc
-- 729.828 466.864 lmroman10-regular.tmc
-- 688.482 441.962 lmsans10-regular.tmc
-- 128.685 95.853 lmtypewriter10-regular.tmc
-- 715.929 582.985 palatinosanscom-bold.tmc
-- 669.942 540.126 palatinosanscom-regular.tmc
-- 1.560.588 1.317.000 zapfinoextraltpro.tmc
table.serialize_functions = true
table.serialize_compact = true
table.serialize_inline = true
local function key(k)
if type(k) == "number" then -- or k:find("^%d+$") then
return "["..k.."]"
elseif noquotes and k:find("^%a[%a%d%_]*$") then
return k
else
return '["'..k..'"]'
end
end
local function simple_table(t)
if #t > 0 then
local n = 0
for _,v in pairs(t) do
n = n + 1
end
if n == #t then
local tt = { }
for i=1,#t do
local v = t[i]
local tv = type(v)
if tv == "number" or tv == "boolean" then
tt[#tt+1] = tostring(v)
elseif tv == "string" then
tt[#tt+1] = ("%q"):format(v)
else
tt = nil
break
end
end
return tt
end
end
return nil
end
local function serialize(root,name,handle,depth,level,reduce,noquotes,indexed)
handle = handle or print
reduce = reduce or false
if depth then
depth = depth .. " "
if indexed then
handle(("%s{"):format(depth))
else
handle(("%s%s={"):format(depth,key(name)))
end
else
depth = ""
local tname = type(name)
if tname == "string" then
if name == "return" then
handle("return {")
else
handle(name .. "={")
end
elseif tname == "number" then
handle("[" .. name .. "]={")
elseif tname == "boolean" then
if name then
handle("return {")
else
handle("{")
end
else
handle("t={")
end
end
if root and next(root) then
local compact = table.serialize_compact
local inline = compact and table.serialize_inline
local first, last = nil, 0 -- #root cannot be trusted here
if compact then
for k,v in ipairs(root) do -- NOT: for k=1,#root do (why)
if not first then first = k end
last = last + 1
end
end
for _,k in pairs(table.sortedkeys(root)) do
local v = root[k]
local t = type(v)
if compact and first and type(k) == "number" and k >= first and k <= last then
if t == "number" then
handle(("%s %s,"):format(depth,v))
elseif t == "string" then
if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
handle(("%s %s,"):format(depth,v))
else
handle(("%s %q,"):format(depth,v))
end
elseif t == "table" then
if not next(v) then
handle(("%s {},"):format(depth))
elseif inline then
local st = simple_table(v)
if st then
handle(("%s { %s },"):format(depth,table.concat(st,", ")))
else
serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
end
else
serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
end
elseif t == "boolean" then
handle(("%s %s,"):format(depth,tostring(v)))
elseif t == "function" then
if table.serialize_functions then
handle(('%s loadstring(%q),'):format(depth,string.dump(v)))
else
handle(('%s "function",'):format(depth))
end
else
handle(("%s %q,"):format(depth,tostring(v)))
end
elseif k == "__p__" then -- parent
if false then
handle(("%s __p__=nil,"):format(depth))
end
elseif t == "number" then
handle(("%s %s=%s,"):format(depth,key(k),v))
elseif t == "string" then
if reduce and (v:find("^[%-%+]?[%d]-%.?[%d+]$") == 1) then
handle(("%s %s=%s,"):format(depth,key(k),v))
else
handle(("%s %s=%q,"):format(depth,key(k),v))
end
elseif t == "table" then
if not next(v) then
handle(("%s %s={},"):format(depth,key(k)))
elseif inline then
local st = simple_table(v)
if st then
handle(("%s %s={ %s },"):format(depth,key(k),table.concat(st,", ")))
else
serialize(v,k,handle,depth,level+1,reduce,noquotes)
end
else
serialize(v,k,handle,depth,level+1,reduce,noquotes)
end
elseif t == "boolean" then
handle(("%s %s=%s,"):format(depth,key(k),tostring(v)))
elseif t == "function" then
if table.serialize_functions then
handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(v)))
else
handle(('%s %s="function",'):format(depth,key(k)))
end
else
handle(("%s %s=%q,"):format(depth,key(k),tostring(v)))
-- handle(('%s %s=loadstring(%q),'):format(depth,key(k),string.dump(function() return v end)))
end
end
if level > 0 then
handle(("%s},"):format(depth))
else
handle(("%s}"):format(depth))
end
else
handle(("%s}"):format(depth))
end
end
--~ name:
--~
--~ true : return { }
--~ false : { }
--~ nil : t = { }
--~ string : string = { }
--~ 'return' : return { }
--~ number : [number] = { }
function table.serialize(root,name,reduce,noquotes)
local t = { }
local function flush(s)
t[#t+1] = s
end
serialize(root, name, flush, nil, 0, reduce, noquotes)
return table.concat(t,"\n")
end
function table.tohandle(handle,root,name,reduce,noquotes)
serialize(root, name, handle, nil, 0, reduce, noquotes)
end
-- sometimes tables are real use (zapfino extra pro is some 85M) in which
-- case a stepwise serialization is nice; actually, we could consider:
--
-- for line in table.serializer(root,name,reduce,noquotes) do
-- ...(line)
-- end
--
-- so this is on the todo list
table.tofile_maxtab = 2*1024
function table.tofile(filename,root,name,reduce,noquotes)
local f = io.open(filename,'w')
if f then
local concat = table.concat
local maxtab = table.tofile_maxtab
if maxtab > 1 then
local t = { }
local function flush(s)
t[#t+1] = s
if #t > maxtab then
f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
t = { }
end
end
serialize(root, name, flush, nil, 0, reduce, noquotes)
f:write(concat(t,"\n"),"\n")
else
local function flush(s)
f:write(s,"\n")
end
serialize(root, name, flush, nil, 0, reduce, noquotes)
end
f:close()
end
end
end
--~ t = {
--~ b = "123",
--~ a = "x",
--~ c = 1.23,
--~ d = "1.23",
--~ e = true,
--~ f = {
--~ d = "1.23",
--~ a = "x",
--~ b = "123",
--~ c = 1.23,
--~ e = true,
--~ f = {
--~ e = true,
--~ f = {
--~ e = true
--~ },
--~ },
--~ },
--~ g = function() end
--~ }
--~ print(table.serialize(t), "\n")
--~ print(table.serialize(t,"name"), "\n")
--~ print(table.serialize(t,false), "\n")
--~ print(table.serialize(t,true), "\n")
--~ print(table.serialize(t,"name",true), "\n")
--~ print(table.serialize(t,"name",true,true), "\n")
do
local function flatten(t,f,complete)
for i=1,#t do
local v = t[i]
if type(v) == "table" then
if complete or type(v[1]) == "table" then
flatten(v,f,complete)
else
f[#f+1] = v
end
else
f[#f+1] = v
end
end
end
function table.flatten(t)
local f = { }
flatten(t,f,true)
return f
end
function table.unnest(t) -- bad name
local f = { }
flatten(t,f,false)
return f
end
table.flatten_one_level = table.unnest
end
function table.insert_before_value(t,value,str)
for i=1,#t do
if t[i] == value then
table.insert(t,i,str)
return
end
end
table.insert(t,1,str)
end
function table.insert_after_value(t,value,str)
for i=1,#t do
if t[i] == value then
table.insert(t,i+1,str)
return
end
end
t[#t+1] = str
end
function table.are_equal(a,b,n,m)
if #a == #b then
n = n or 1
m = m or #a
for i=n,m do
local ai, bi = a[i], b[i]
if (ai==bi) or (type(ai)=="table" and type(bi)=="table" and table.are_equal(ai,bi)) then
-- continue
else
return false
end
end
return true
else
return false
end
end
function table.compact(t)
if t then
for k,v in pairs(t) do
if not next(v) then
t[k] = nil
end
end
end
end
function table.tohash(t)
local h = { }
for _, v in pairs(t) do -- no ipairs here
h[v] = true
end
return h
end
function table.fromhash(t)
local h = { }
for k, v in pairs(t) do -- no ipairs here
if v then h[#h+1] = k end
end
return h
end
function table.contains(t, v)
if t then
for i=1, #t do
if t[i] == v then
return true
end
end
end
return false
end
function table.count(t)
local n, e = 0, next(t)
while e do
n, e = n + 1, next(t,e)
end
return n
end
function table.swapped(t)
local s = { }
for k, v in pairs(t) do
s[v] = k
end
return s
end
--~ function table.are_equal(a,b)
--~ return table.serialize(a) == table.serialize(b)
--~ end
function table.clone(t,p) -- t is optional or nil or table
if not p then
t, p = { }, t or { }
elseif not t then
t = { }
end
setmetatable(t, { __index = function(_,key) return p[key] end })
return t
end
function table.hexed(t,seperator)
local tt = { }
for i=1,#t do tt[i] = string.format("0x%04X",t[i]) end
return table.concat(tt,seperator or " ")
end
function table.reverse_hash(h)
local r = { }
for k,v in pairs(h) do
r[v] = (k:gsub(" ","")):lower()
end
return r
end
function table.reverse(t)
local tt = { }
if #t > 0 then
for i=#t,1,-1 do
tt[#tt+1] = t[i]
end
end
return tt
end
-- filename : l-io.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-io'] = 1.001
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator = "\\", ";"
else
io.fileseparator, io.pathseparator = "/" , ":"
end
function io.loaddata(filename)
local f = io.open(filename,'rb')
if f then
local data = f:read('*all')
f:close()
return data
else
return nil
end
end
function io.savedata(filename,data,joiner)
local f = io.open(filename, "wb")
if f then
if type(data) == "table" then
f:write(table.join(data,joiner or ""))
elseif type(data) == "function" then
data(f)
else
f:write(data)
end
f:close()
end
end
function io.exists(filename)
local f = io.open(filename)
if f == nil then
return false
else
assert(f:close())
return true
end
end
function io.size(filename)
local f = io.open(filename)
if f == nil then
return 0
else
local s = f:seek("end")
assert(f:close())
return s
end
end
function io.noflines(f)
local n = 0
for _ in f:lines() do
n = n + 1
end
f:seek('set',0)
return n
end
do
local sb = string.byte
local nextchar = {
[ 4] = function(f)
return f:read(1,1,1,1)
end,
[ 2] = function(f)
return f:read(1,1)
end,
[ 1] = function(f)
return f:read(1)
end,
[-2] = function(f)
local a, b = f:read(1,1)
return b, a
end,
[-4] = function(f)
local a, b, c, d = f:read(1,1,1,1)
return d, c, b, a
end
}
function io.characters(f,n)
if f then
return nextchar[n or 1], f
else
return nil, nil
end
end
end
do
local sb = string.byte
--~ local nextbyte = {
--~ [4] = function(f)
--~ local a = f:read(1)
--~ local b = f:read(1)
--~ local c = f:read(1)
--~ local d = f:read(1)
--~ if d then
--~ return sb(a), sb(b), sb(c), sb(d)
--~ else
--~ return nil, nil, nil, nil
--~ end
--~ end,
--~ [2] = function(f)
--~ local a = f:read(1)
--~ local b = f:read(1)
--~ if b then
--~ return sb(a), sb(b)
--~ else
--~ return nil, nil
--~ end
--~ end,
--~ [1] = function (f)
--~ local a = f:read(1)
--~ if a then
--~ return sb(a)
--~ else
--~ return nil
--~ end
--~ end,
--~ [-2] = function (f)
--~ local a = f:read(1)
--~ local b = f:read(1)
--~ if b then
--~ return sb(b), sb(a)
--~ else
--~ return nil, nil
--~ end
--~ end,
--~ [-4] = function(f)
--~ local a = f:read(1)
--~ local b = f:read(1)
--~ local c = f:read(1)
--~ local d = f:read(1)
--~ if d then
--~ return sb(d), sb(c), sb(b), sb(a)
--~ else
--~ return nil, nil, nil, nil
--~ end
--~ end
--~ }
local nextbyte = {
[4] = function(f)
local a, b, c, d = f:read(1,1,1,1)
if d then
return sb(a), sb(b), sb(c), sb(d)
else
return nil, nil, nil, nil
end
end,
[2] = function(f)
local a, b = f:read(1,1)
if b then
return sb(a), sb(b)
else
return nil, nil
end
end,
[1] = function (f)
local a = f:read(1)
if a then
return sb(a)
else
return nil
end
end,
[-2] = function (f)
local a, b = f:read(1,1)
if b then
return sb(b), sb(a)
else
return nil, nil
end
end,
[-4] = function(f)
local a, b, c, d = f:read(1,1,1,1)
if d then
return sb(d), sb(c), sb(b), sb(a)
else
return nil, nil, nil, nil
end
end
}
function io.bytes(f,n)
if f then
return nextbyte[n or 1], f
else
return nil, nil
end
end
end
function io.ask(question,default,options)
while true do
io.write(question)
if options then
io.write(string.format(" [%s]",table.concat(options,"|")))
end
if default then
io.write(string.format(" [%s]",default))
end
io.write(string.format(" "))
local answer = io.read()
answer = answer:gsub("^%s*(.*)%s*$","%1")
if answer == "" and default then
return default
elseif not options then
return answer
else
for _,v in pairs(options) do
if v == answer then
return answer
end
end
local pattern = "^" .. answer
for _,v in pairs(options) do
if v:find(pattern) then
return v
end
end
end
end
end
-- filename : l-md5.lua
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-md5'] = 1.001
if md5 then do
local function convert(str,fmt)
return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
end
if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end
if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end
if not md5.dec then function md5.dec(str) return convert(str,"%03i") end end
end end
-- filename : l-number.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-number'] = 1.001
if not number then number = { } end
-- a,b,c,d,e,f = number.toset(100101)
function number.toset(n)
return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
end
local format = string.format
function number.toevenhex(n)
local s = format("%X",n)
if #s % 2 == 0 then
return s
else
return "0" .. s
end
end
-- the lpeg way is slower on 8 digits, but faster on 4 digits, some 7.5%
-- on
--
-- for i=1,1000000 do
-- local a,b,c,d,e,f,g,h = number.toset(12345678)
-- local a,b,c,d = number.toset(1234)
-- local a,b,c = number.toset(123)
-- end
--
-- of course dedicated "(.)(.)(.)(.)" matches are even faster
do
local one = lpeg.C(1-lpeg.S(''))^1
function number.toset(n)
return one:match(tostring(n))
end
end
-- filename : l-set.lua
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-set'] = 1.001
if not set then set = { } end
do
local nums = { }
local tabs = { }
local concat = table.concat
set.create = table.tohash
function set.tonumber(t)
if next(t) then
local s = ""
-- we could save mem by sorting, but it slows down
for k, v in pairs(t) do
if v then
-- why bother about the leading space
s = s .. " " .. k
end
end
if not nums[s] then
tabs[#tabs+1] = t
nums[s] = #tabs
end
return nums[s]
else
return 0
end
end
function set.totable(n)
if n == 0 then
return { }
else
return tabs[n] or { }
end
end
function set.contains(n,s)
if type(n) == "table" then
return n[s]
elseif n == 0 then
return false
else
local t = tabs[n]
return t and t[s]
end
end
end
--~ local c = set.create{'aap','noot','mies'}
--~ local s = set.tonumber(c)
--~ local t = set.totable(s)
--~ print(t['aap'])
--~ local c = set.create{'zus','wim','jet'}
--~ local s = set.tonumber(c)
--~ local t = set.totable(s)
--~ print(t['aap'])
--~ print(t['jet'])
--~ print(set.contains(t,'jet'))
--~ print(set.contains(t,'aap'))
-- filename : l-os.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-os'] = 1.001
function os.resultof(command)
return io.popen(command,"r"):read("*all")
end
if not os.exec then os.exec = os.execute end
if not os.spawn then os.spawn = os.execute end
--~ os.type : windows | unix (new, we already guessed os.platform)
--~ os.name : windows | msdos | linux | macosx | solaris | .. | generic (new)
if not io.fileseparator then
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator, os.platform = "\\", ";", os.type or "windows"
else
io.fileseparator, io.pathseparator, os.platform = "/" , ":", os.type or "unix"
end
end
os.platform = os.platform or os.type or (io.pathseparator == ";" and "windows") or "unix"
function os.launch(str)
if os.platform == "windows" then
os.execute("start " .. str) -- os.spawn ?
else
os.execute(str .. " &") -- os.spawn ?
end
end
if not os.setenv then
function os.setenv() return false end
end
if not os.times then
-- utime = user time
-- stime = system time
-- cutime = children user time
-- cstime = children system time
function os.times()
return {
utime = os.gettimeofday(), -- user
stime = 0, -- system
cutime = 0, -- children user
cstime = 0, -- children system
}
end
end
os.gettimeofday = os.gettimeofday or os.clock
do
local startuptime = os.gettimeofday()
function os.runtime()
return os.gettimeofday() - startuptime
end
end
--~ print(os.gettimeofday()-os.time())
--~ os.sleep(1.234)
--~ print (">>",os.runtime())
--~ print(os.date("%H:%M:%S",os.gettimeofday()))
--~ print(os.date("%H:%M:%S",os.time()))
-- filename : l-file.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-file'] = 1.001
if not file then file = { } end
function file.removesuffix(filename)
return filename:gsub("%.[%a%d]+$", "")
end
function file.addsuffix(filename, suffix)
if not filename:find("%.[%a%d]+$") then
return filename .. "." .. suffix
else
return filename
end
end
function file.replacesuffix(filename, suffix)
if not filename:find("%.[%a%d]+$") then
return filename .. "." .. suffix
else
return (filename:gsub("%.[%a%d]+$","."..suffix))
end
end
function file.dirname(name)
return name:match("^(.+)[/\\].-$") or ""
end
function file.basename(name)
return name:match("^.+[/\\](.-)$") or name
end
function file.nameonly(name)
return ((name:match("^.+[/\\](.-)$") or name):gsub("%..*$",""))
end
function file.extname(name)
return name:match("^.+%.([^/\\]-)$") or ""
end
file.suffix = file.extname
function file.stripsuffix(name)
return (name:gsub("%.[%a%d]+$",""))
end
--~ function file.join(...)
--~ local t = { ... }
--~ for i=1,#t do
--~ t[i] = (t[i]:gsub("\\","/")):gsub("/+$","")
--~ end
--~ return table.concat(t,"/")
--~ end
--~ print(file.join("x/","/y"))
--~ print(file.join("http://","/y"))
--~ print(file.join("http://a","/y"))
--~ print(file.join("http:///a","/y"))
--~ print(file.join("//nas-1","/y"))
function file.join(...)
local pth = table.concat({...},"/")
pth = pth:gsub("\\","/")
local a, b = pth:match("^(.*://)(.*)$")
if a and b then
return a .. b:gsub("//+","/")
end
a, b = pth:match("^(//)(.*)$")
if a and b then
return a .. b:gsub("//+","/")
end
return (pth:gsub("//+","/"))
end
function file.is_writable(name)
local f = io.open(name, 'w')
if f then
f:close()
return true
else
return false
end
end
function file.is_readable(name)
local f = io.open(name,'r')
if f then
f:close()
return true
else
return false
end
end
--~ function file.split_path(str)
--~ if str:find(';') then
--~ return str:splitchr(";")
--~ else
--~ return str:splitchr(io.pathseparator)
--~ end
--~ end
-- todo: lpeg
function file.split_path(str)
local t = { }
str = str:gsub("\\", "/")
str = str:gsub("(%a):([;/])", "%1\001%2")
for name in str:gmatch("([^;:]+)") do
if name ~= "" then
name = name:gsub("\001",":")
t[#t+1] = name
end
end
return t
end
function file.join_path(tab)
return table.concat(tab,io.pathseparator) -- can have trailing //
end
--~ print('test' .. " == " .. file.collapse_path("test"))
--~ print("test/test" .. " == " .. file.collapse_path("test/test"))
--~ print("test/test/test" .. " == " .. file.collapse_path("test/test/test"))
--~ print("test/test" .. " == " .. file.collapse_path("test/../test/test"))
--~ print("test" .. " == " .. file.collapse_path("test/../test"))
--~ print("../test" .. " == " .. file.collapse_path("../test"))
--~ print("../test/" .. " == " .. file.collapse_path("../test/"))
--~ print("a/a" .. " == " .. file.collapse_path("a/b/c/../../a"))
--~ function file.collapse_path(str)
--~ local ok, n = false, 0
--~ while not ok do
--~ ok = true
--~ str, n = str:gsub("[^%./]+/%.%./", function(s)
--~ ok = false
--~ return ""
--~ end)
--~ end
--~ return (str:gsub("/%./","/"))
--~ end
function file.collapse_path(str)
local n = 1
while n > 0 do
str, n = str:gsub("([^/%.]+/%.%./)","")
end
return (str:gsub("/%./","/"))
end
function file.robustname(str)
return (str:gsub("[^%a%d%/%-%.\\]+","-"))
end
file.readdata = io.loaddata
file.savedata = io.savedata
function file.copy(oldname,newname)
file.savedata(newname,io.loaddata(oldname))
end
-- filename : l-dir.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-dir'] = 1.001
dir = { }
-- optimizing for no string.find (*) does not save time
if lfs then do
--~ local attributes = lfs.attributes
--~ local walkdir = lfs.dir
--~
--~ local function glob_pattern(path,patt,recurse,action)
--~ local ok, scanner = xpcall(function() return walkdir(path) end, function() end) -- kepler safe
--~ if ok and type(scanner) == "function" then
--~ if not path:find("/$") then path = path .. '/' end
--~ for name in scanner do
--~ local full = path .. name
--~ local mode = attributes(full,'mode')
--~ if mode == 'file' then
--~ if name:find(patt) then
--~ action(full)
--~ end
--~ elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then
--~ glob_pattern(full,patt,recurse,action)
--~ end
--~ end
--~ end
--~ end
--~
--~ dir.glob_pattern = glob_pattern
--~
--~ local function glob(pattern, action)
--~ local t = { }
--~ local action = action or function(name) t[#t+1] = name end
--~ local path, patt = pattern:match("^(.*)/*%*%*/*(.-)$")
--~ local recurse = path and patt
--~ if not recurse then
--~ path, patt = pattern:match("^(.*)/(.-)$")
--~ if not (path and patt) then
--~ path, patt = '.', pattern
--~ end
--~ end
--~ patt = patt:gsub("([%.%-%+])", "%%%1")
--~ patt = patt:gsub("%*", ".*")
--~ patt = patt:gsub("%?", ".")
--~ patt = "^" .. patt .. "$"
--~ -- print('path: ' .. path .. ' | pattern: ' .. patt .. ' | recurse: ' .. tostring(recurse))
--~ glob_pattern(path,patt,recurse,action)
--~ return t
--~ end
--~
--~ dir.glob = glob
local attributes = lfs.attributes
local walkdir = lfs.dir
local function glob_pattern(path,patt,recurse,action)
local ok, scanner
if path == "/" then
ok, scanner = xpcall(function() return walkdir(path..".") end, function() end) -- kepler safe
else
ok, scanner = xpcall(function() return walkdir(path) end, function() end) -- kepler safe
end
if ok and type(scanner) == "function" then
if not path:find("/$") then path = path .. '/' end
for name in scanner do
local full = path .. name
local mode = attributes(full,'mode')
if mode == 'file' then
if full:find(patt) then
action(full)
end
elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then
glob_pattern(full,patt,recurse,action)
end
end
end
end
dir.glob_pattern = glob_pattern
--~ local function glob(pattern, action)
--~ local t = { }
--~ local path, rest, patt, recurse
--~ local action = action or function(name) t[#t+1] = name end
--~ local pattern = pattern:gsub("^%*%*","./**")
--~ local pattern = pattern:gsub("/%*/","/**/")
--~ path, rest = pattern:match("^(/)(.-)$")
--~ if path then
--~ path = path
--~ else
--~ path, rest = pattern:match("^([^/]*)/(.-)$")
--~ end
--~ if rest then
--~ patt = rest:gsub("([%.%-%+])", "%%%1")
--~ end
--~ patt = patt:gsub("%*", "[^/]*")
--~ patt = patt:gsub("%?", "[^/]")
--~ patt = patt:gsub("%[%^/%]%*%[%^/%]%*", ".*")
--~ if path == "" then path = "." end
--~ recurse = patt:find("%.%*/") ~= nil
--~ glob_pattern(path,patt,recurse,action)
--~ return t
--~ end
local P, S, R, C, Cc, Cs, Ct, Cv, V = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.Ct, lpeg.Cv, lpeg.V
local pattern = Ct {
[1] = (C(P(".") + P("/")^1) + C(R("az","AZ") * P(":") * P("/")^0) + Cc("./")) * V(2) * V(3),
[2] = C(((1-S("*?/"))^0 * P("/"))^0),
[3] = C(P(1)^0)
}
local filter = Cs ( (
P("**") / ".*" +
P("*") / "[^/]*" +
P("?") / "[^/]" +
P(".") / "%%." +
P("+") / "%%+" +
P("-") / "%%-" +
P(1)
)^0 )
local function glob(str,t)
if type(str) == "table" then
local t = t or { }
for _, s in ipairs(str) do
glob(s,t)
end
return t
else
local split = pattern:match(str)
if split then
local t = t or { }
local action = action or function(name) t[#t+1] = name end
local root, path, base = split[1], split[2], split[3]
local recurse = base:find("**")
local start = root .. path
local result = filter:match(start .. base)
glob_pattern(start,result,recurse,action)
return t
else
return { }
end
end
end
dir.glob = glob
--~ list = dir.glob("**/*.tif")
--~ list = dir.glob("/**/*.tif")
--~ list = dir.glob("./**/*.tif")
--~ list = dir.glob("oeps/**/*.tif")
--~ list = dir.glob("/oeps/**/*.tif")
local function globfiles(path,recurse,func,files) -- func == pattern or function
if type(func) == "string" then
local s = func -- alas, we need this indirect way
func = function(name) return name:find(s) end
end
files = files or { }
for name in walkdir(path) do
if name:find("^%.") then
--- skip
elseif attributes(name,'mode') == "directory" then
if recurse then
globfiles(path .. "/" .. name,recurse,func,files)
end
elseif func then
if func(name) then
files[#files+1] = path .. "/" .. name
end
else
files[#files+1] = path .. "/" .. name
end
end
return files
end
dir.globfiles = globfiles
-- t = dir.glob("c:/data/develop/context/sources/**/????-*.tex")
-- t = dir.glob("c:/data/develop/tex/texmf/**/*.tex")
-- t = dir.glob("c:/data/develop/context/texmf/**/*.tex")
-- t = dir.glob("f:/minimal/tex/**/*")
-- print(dir.ls("f:/minimal/tex/**/*"))
-- print(dir.ls("*.tex"))
function dir.ls(pattern)
return table.concat(glob(pattern),"\n")
end
--~ mkdirs("temp")
--~ mkdirs("a/b/c")
--~ mkdirs(".","/a/b/c")
--~ mkdirs("a","b","c")
local make_indeed = true -- false
if string.find(os.getenv("PATH"),";") then
function dir.mkdirs(...)
local str, pth = "", ""
for _, s in ipairs({...}) do
if s ~= "" then
if str ~= "" then
str = str .. "/" .. s
else
str = s
end
end
end
local first, middle, last
local drive = false
first, middle, last = str:match("^(//)(//*)(.*)$")
if first then
-- empty network path == local path
else
first, last = str:match("^(//)/*(.-)$")
if first then
middle, last = str:match("([^/]+)/+(.-)$")
if middle then
pth = "//" .. middle
else
pth = "//" .. last
last = ""
end
else
first, middle, last = str:match("^([a-zA-Z]:)(/*)(.-)$")
if first then
pth, drive = first .. middle, true
else
middle, last = str:match("^(/*)(.-)$")
if not middle then
last = str
end
end
end
end
for s in last:gmatch("[^/]+") do
if pth == "" then
pth = s
elseif drive then
pth, drive = pth .. s, false
else
pth = pth .. "/" .. s
end
if make_indeed and not lfs.isdir(pth) then
lfs.mkdir(pth)
end
end
return pth, (lfs.isdir(pth) == true)
end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
--~ print(dir.mkdirs("a:"))
--~ print(dir.mkdirs("a:/b/c"))
--~ print(dir.mkdirs("a:b/c"))
--~ print(dir.mkdirs("a:/bbb/c"))
--~ print(dir.mkdirs("/a/b/c"))
--~ print(dir.mkdirs("/aaa/b/c"))
--~ print(dir.mkdirs("//a/b/c"))
--~ print(dir.mkdirs("///a/b/c"))
--~ print(dir.mkdirs("a/bbb//ccc/"))
function dir.expand_name(str)
local first, nothing, last = str:match("^(//)(//*)(.*)$")
if first then
first = lfs.currentdir() .. "/"
first = first:gsub("\\","/")
end
if not first then
first, last = str:match("^(//)/*(.*)$")
end
if not first then
first, last = str:match("^([a-zA-Z]:)(.*)$")
if first and not last:find("^/") then
local d = lfs.currentdir()
if lfs.chdir(first) then
first = lfs.currentdir()
first = first:gsub("\\","/")
end
lfs.chdir(d)
end
end
if not first then
first, last = lfs.currentdir(), str
first = first:gsub("\\","/")
end
last = last:gsub("//","/")
last = last:gsub("/%./","/")
last = last:gsub("^/*","")
first = first:gsub("/*$","")
if last == "" then
return first
else
return first .. "/" .. last
end
end
else
function dir.mkdirs(...)
local str, pth = "", ""
for _, s in ipairs({...}) do
if s ~= "" then
if str ~= "" then
str = str .. "/" .. s
else
str = s
end
end
end
str = str:gsub("/+","/")
if str:find("^/") then
pth = "/"
for s in str:gmatch("[^/]+") do
local first = (pth == "/")
if first then
pth = pth .. s
else
pth = pth .. "/" .. s
end
if make_indeed and not first and not lfs.isdir(pth) then
lfs.mkdir(pth)
end
end
else
pth = "."
for s in str:gmatch("[^/]+") do
pth = pth .. "/" .. s
if make_indeed and not lfs.isdir(pth) then
lfs.mkdir(pth)
end
end
end
return pth, (lfs.isdir(pth) == true)
end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
--~ print(dir.mkdirs("/a/b/c"))
--~ print(dir.mkdirs("/aaa/b/c"))
--~ print(dir.mkdirs("//a/b/c"))
--~ print(dir.mkdirs("///a/b/c"))
--~ print(dir.mkdirs("a/bbb//ccc/"))
function dir.expand_name(str)
if not str:find("^/") then
str = lfs.currentdir() .. "/" .. str
end
str = str:gsub("//","/")
str = str:gsub("/%./","/")
return str
end
end
dir.makedirs = dir.mkdirs
end end
-- filename : l-boolean.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-boolean'] = 1.001
if not boolean then boolean = { } end
function boolean.tonumber(b)
if b then return 1 else return 0 end
end
function toboolean(str,tolerant)
if tolerant then
local tstr = type(str)
if tstr == "string" then
return str == "true" or str == "yes" or str == "on" or str == "1"
elseif tstr == "number" then
return tonumber(str) ~= 0
elseif tstr == "nil" then
return false
else
return str
end
elseif str == "true" then
return true
elseif str == "false" then
return false
else
return str
end
end
function string.is_boolean(str)
if type(str) == "string" then
if str == "true" or str == "yes" or str == "on" then
return true
elseif str == "false" or str == "no" or str == "off" then
return false
end
end
return nil
end
function boolean.alwaystrue()
return true
end
function boolean.falsetrue()
return false
end
if not modules then modules = { } end modules ['l-xml'] = {
version = 1.001,
comment = "this module is the basis for the lxml-* ones",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
-- RJ: key=value ... lpeg.Ca(lpeg.Cc({}) * (pattern-producing-key-and-value / rawset)^0)
-- some code may move to l-xmlext
--[[ldx--
The parser used here is inspired by the variant discussed in the lua book, but
handles comment and processing instructions, has a different structure, provides
parent access; a first version used different tricky but was less optimized to we
went this route. First we had a find based parser, now we have an based one.
The find based parser can be found in l-xml-edu.lua along with other older code.
Expecially the lpath code is experimental, we will support some of xpath, but
only things that make sense for us; as compensation it is possible to hook in your
own functions. Apart from preprocessing content for we also need
this module for process management, like handling and
files.
a/b/c /*/c
a/b/c/first() a/b/c/last() a/b/c/index(n) a/b/c/index(-n)
a/b/c/text() a/b/c/text(1) a/b/c/text(-1) a/b/c/text(n)
Beware, the interface may change. For instance at, ns, tg, dt may get more
verbose names. Once the code is stable we will also remove some tracing and
optimize the code.
--ldx]]--
xml = xml or { }
tex = tex or { }
xml.trace_lpath = false
xml.trace_print = false
xml.trace_remap = false
local format, concat = string.format, table.concat
--~ local pairs, next, type = pairs, next, type
-- todo: some things per xml file, liek namespace remapping
--[[ldx--
First a hack to enable namespace resolving. A namespace is characterized by
a . The following function associates a namespace prefix with a
pattern. We use , which in this case is more than twice as fast as a
find based solution where we loop over an array of patterns. Less code and
much cleaner.
--ldx]]--
xml.xmlns = xml.xmlns or { }
do
local check = lpeg.P(false)
local parse = check
--[[ldx--
The next function associates a namespace prefix with an . This
normally happens independent of parsing.
xml.registerns("mml","mathml")
--ldx]]--
function xml.registerns(namespace, pattern) -- pattern can be an lpeg
check = check + lpeg.C(lpeg.P(pattern:lower())) / namespace
parse = lpeg.P { lpeg.P(check) + 1 * lpeg.V(1) }
end
--[[ldx--
The next function also registers a namespace, but this time we map a
given namespace prefix onto a registered one, using the given
. This used for attributes like xmlns:m.
xml.checkns("m","http://www.w3.org/mathml")
--ldx]]--
function xml.checkns(namespace,url)
local ns = parse:match(url:lower())
if ns and namespace ~= ns then
xml.xmlns[namespace] = ns
end
end
--[[ldx--
Next we provide a way to turn an into a registered
namespace. This used for the xmlns attribute.
resolvedns = xml.resolvens("http://www.w3.org/mathml")
This returns mml.
--ldx]]--
function xml.resolvens(url)
return parse:match(url:lower()) or ""
end
--[[ldx--
A namespace in an element can be remapped onto the registered
one efficiently by using the xml.xmlns table.
--ldx]]--
end
--[[ldx--
This version uses . We follow the same approach as before, stack and top and
such. This version is about twice as fast which is mostly due to the fact that
we don't have to prepare the stream for cdata, doctype etc etc. This variant is
is dedicated to Luigi Scarso, who challenged me with 40 megabyte files that
took 12.5 seconds to load (1.5 for file io and the rest for tree building). With
the implementation we got that down to less 7.3 seconds. Loading the 14
interface definition files (2.6 meg) went down from 1.05 seconds to 0.55.
Next comes the parser. The rather messy doctype definition comes in many
disguises so it is no surprice that later on have to dedicate quite some
code to it.
The code may look a bit complex but this is mostly due to the fact that we
resolve namespaces and attach metatables. There is only one public function:
local x = xml.convert(somestring)
An optional second boolean argument tells this function not to create a root
element.
--ldx]]--
xml.strip_cm_and_dt = false -- an extra global flag, in case we have many includes
do
-- not just one big nested table capture (lpeg overflow)
local remove, nsremap, resolvens = table.remove, xml.xmlns, xml.resolvens
local stack, top, dt, at, xmlns, errorstr, entities = {}, {}, {}, {}, {}, nil, {}
local mt = { __tostring = xml.text }
function xml.check_error(top,toclose)
return ""
end
local strip = false
local cleanup = false
function xml.set_text_cleanup(fnc)
cleanup = fnc
end
local function add_attribute(namespace,tag,value)
if tag == "xmlns" then
xmlns[#xmlns+1] = resolvens(value)
at[tag] = value
elseif namespace == "xmlns" then
xml.checkns(tag,value)
at["xmlns:" .. tag] = value
else
at[tag] = value
end
end
local function add_begin(spacing, namespace, tag)
if #spacing > 0 then
dt[#dt+1] = spacing
end
local resolved = (namespace == "" and xmlns[#xmlns]) or nsremap[namespace] or namespace
top = { ns=namespace or "", rn=resolved, tg=tag, at=at, dt={}, __p__ = stack[#stack] }
setmetatable(top, mt)
dt = top.dt
stack[#stack+1] = top
at = { }
end
local function add_end(spacing, namespace, tag)
if #spacing > 0 then
dt[#dt+1] = spacing
end
local toclose = remove(stack)
top = stack[#stack]
if #stack < 1 then
errorstr = format("nothing to close with %s %s", tag, xml.check_error(top,toclose) or "")
elseif toclose.tg ~= tag then -- no namespace check
errorstr = format("unable to close %s with %s %s", toclose.tg, tag, xml.check_error(top,toclose) or "")
end
dt = top.dt
dt[#dt+1] = toclose
if at.xmlns then
remove(xmlns)
end
end
local function add_empty(spacing, namespace, tag)
if #spacing > 0 then
dt[#dt+1] = spacing
end
local resolved = (namespace == "" and xmlns[#xmlns]) or nsremap[namespace] or namespace
top = stack[#stack]
dt = top.dt
local t = { ns=namespace or "", rn=resolved, tg=tag, at=at, dt={}, __p__ = top }
dt[#dt+1] = t
setmetatable(t, mt)
at = { }
if at.xmlns then
remove(xmlns)
end
end
local function add_text(text)
if cleanup and #text > 0 then
dt[#dt+1] = cleanup(text)
else
dt[#dt+1] = text
end
end
local function add_special(what, spacing, text)
if #spacing > 0 then
dt[#dt+1] = spacing
end
if strip and (what == "@cm@" or what == "@dt@") then
-- forget it
else
dt[#dt+1] = { special=true, ns="", tg=what, dt={text} }
end
end
local function set_message(txt)
errorstr = "garbage at the end of the file: " .. txt:gsub("([ \n\r\t]*)","")
end
local P, S, R, C, V = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V
local space = S(' \r\n\t')
local open = P('<')
local close = P('>')
local squote = S("'")
local dquote = S('"')
local equal = P('=')
local slash = P('/')
local colon = P(':')
local valid = R('az', 'AZ', '09') + S('_-.')
local name_yes = C(valid^1) * colon * C(valid^1)
local name_nop = C(P(true)) * C(valid^1)
local name = name_yes + name_nop
local utfbom = P('\000\000\254\255') + P('\255\254\000\000') +
P('\255\254') + P('\254\255') + P('\239\187\191') -- no capture
local spacing = C(space^0)
local justtext = C((1-open)^1)
local somespace = space^1
local optionalspace = space^0
local value = (squote * C((1 - squote)^0) * squote) + (dquote * C((1 - dquote)^0) * dquote)
local attribute = (somespace * name * optionalspace * equal * optionalspace * value) / add_attribute
local attributes = attribute^0
local text = justtext / add_text
local balanced = P { "[" * ((1 - S"[]") + V(1))^0 * "]" } -- taken from lpeg manual, () example
local emptyelement = (spacing * open * name * attributes * optionalspace * slash * close) / add_empty
local beginelement = (spacing * open * name * attributes * optionalspace * close) / add_begin
local endelement = (spacing * open * slash * name * optionalspace * close) / add_end
local begincomment = open * P("!--")
local endcomment = P("--") * close
local begininstruction = open * P("?")
local endinstruction = P("?") * close
local begincdata = open * P("![CDATA[")
local endcdata = P("]]") * close
local someinstruction = C((1 - endinstruction)^0)
local somecomment = C((1 - endcomment )^0)
local somecdata = C((1 - endcdata )^0)
function entity(k,v) entities[k] = v end
local begindoctype = open * P("!DOCTYPE")
local enddoctype = close
local beginset = P("[")
local endset = P("]")
local doctypename = C((1-somespace)^0)
local elementdoctype = optionalspace * P("Packaging data in an xml like table is done with the following
function. Maybe it will go away (when not used).
--ldx]]--
function xml.is_valid(root)
return root and root.dt and root.dt[1] and type(root.dt[1]) == "table" and not root.dt[1].er
end
function xml.package(tag,attributes,data)
local ns, tg = tag:match("^(.-):?([^:]+)$")
local t = { ns = ns, tg = tg, dt = data or "", at = attributes or {} }
setmetatable(t, mt)
return t
end
function xml.is_valid(root)
return root and not root.error
end
xml.error_handler = (logs and logs.report) or (input and input.report) or print
end
--[[ldx--
We cannot load an from a filehandle so we need to load
the whole file first. The function accepts a string representing
a filename or a file handle.
--ldx]]--
function xml.load(filename)
if type(filename) == "string" then
local f = io.open(filename,'r')
if f then
local root = xml.convert(f:read("*all"))
f:close()
return root
else
return xml.convert("")
end
elseif filename then -- filehandle
return xml.convert(filename:read("*all"))
else
return xml.convert("")
end
end
--[[ldx--
When we inject new elements, we need to convert strings to
valid trees, which is what the next function does.
--ldx]]--
function xml.toxml(data)
if type(data) == "string" then
local root = { xml.convert(data,true) }
return (#root > 1 and root) or root[1]
else
return data
end
end
--[[ldx--
For copying a tree we use a dedicated function instead of the
generic table copier. Since we know what we're dealing with we
can speed up things a bit. The second argument is not to be used!
--ldx]]--
do
function copy(old,tables)
if old then
tables = tables or { }
local new = { }
if not tables[old] then
tables[old] = new
end
for k,v in pairs(old) do
new[k] = (type(v) == "table" and (tables[v] or copy(v, tables))) or v
end
local mt = getmetatable(old)
if mt then
setmetatable(new,mt)
end
return new
else
return { }
end
end
xml.copy = copy
end
--[[ldx--
In serializing the tree or parts of the tree is a major
actitivity which is why the following function is pretty optimized resulting
in a few more lines of code than needed. The variant that uses the formatting
function for all components is about 15% slower than the concatinating
alternative.
--ldx]]--
do
-- todo: add when not present
local fallbackhandle = (tex and tex.sprint) or io.write
local function serialize(e, handle, textconverter, attributeconverter, specialconverter, nocommands)
if not e then
return
elseif not nocommands then
local ec = e.command
if ec ~= nil then -- we can have all kind of types
if e.special then -- todo test for true/false
local etg, edt = e.tg, e.dt
local spc = specialconverter and specialconverter[etg]
if spc then
--~ print("SPECIAL",etg,table.serialize(specialconverter), spc)
local result = spc(edt[1])
if result then
handle(result)
return
else
-- no need to handle any further
end
end
end
local xc = xml.command
if xc then
xc(e,ec)
return
end
end
end
handle = handle or fallbackhandle
local etg = e.tg
if etg then
if e.special then
local edt = e.dt
local spc = specialconverter and specialconverter[etg]
if spc then
local result = spc(edt[1])
if result then
handle(result)
else
-- no need to handle any further
end
elseif etg == "@pi@" then
-- handle(format("%s?>",edt[1]))
handle("" .. edt[1] .. "?>")
elseif etg == "@cm@" then
-- handle(format("",edt[1]))
handle("")
elseif etg == "@cd@" then
-- handle(format("",edt[1]))
handle("")
elseif etg == "@dt@" then
-- handle(format("",edt[1]))
handle("")
elseif etg == "@rt@" then
serialize(edt,handle,textconverter,attributeconverter,specialconverter,nocommands)
end
else
local ens, eat, edt, ern = e.ns, e.at, e.dt, e.rn
local ats = eat and next(eat) and { } -- type test maybe faster
if ats then
if attributeconverter then
for k,v in pairs(eat) do
ats[#ats+1] = format('%s=%q',k,attributeconverter(v))
end
else
for k,v in pairs(eat) do
ats[#ats+1] = format('%s=%q',k,v)
end
end
end
if ern and xml.trace_remap and ern ~= ens then
--~ if ats then
--~ ats[#ats+1] = format("xmlns:remapped='%s'",ern)
--~ else
--~ ats = { format("xmlns:remapped='%s'",ern) }
--~ end
--~ if ats then
--~ ats[#ats+1] = format("remappedns='%s'",ens or '-')
--~ else
--~ ats = { format("remappedns='%s'",ens or '-') }
--~ end
ens = ern
end
if ens ~= "" then
if edt and #edt > 0 then
if ats then
-- handle(format("<%s:%s %s>",ens,etg,concat(ats," ")))
handle("<" .. ens .. ":" .. etg .. " " .. concat(ats," ") .. ">")
else
-- handle(format("<%s:%s>",ens,etg))
handle("<" .. ens .. ":" .. etg .. ">")
end
for i=1,#edt do
local e = edt[i]
if type(e) == "string" then
if textconverter then
handle(textconverter(e))
else
handle(e)
end
else
serialize(e,handle,textconverter,attributeconverter,specialconverter,nocommands)
end
end
-- handle(format("%s:%s>",ens,etg))
handle("" .. ens .. ":" .. etg .. ">")
else
if ats then
-- handle(format("<%s:%s %s/>",ens,etg,concat(ats," ")))
handle("<" .. ens .. ":" .. etg .. " " .. concat(ats," ") .. "/>")
else
-- handle(format("<%s:%s/>",ens,etg))
handle("<" .. ens .. ":" .. etg .. "/>")
end
end
else
if edt and #edt > 0 then
if ats then
-- handle(format("<%s %s>",etg,concat(ats," ")))
handle("<" .. etg .. " " .. concat(ats," ") .. ">")
else
-- handle(format("<%s>",etg))
handle("<" .. etg .. ">")
end
for i=1,#edt do
serialize(edt[i],handle,textconverter,attributeconverter,specialconverter,nocommands)
end
-- handle(format("%s>",etg))
handle("" .. etg .. ">")
else
if ats then
-- handle(format("<%s %s/>",etg,concat(ats," ")))
handle("<" .. etg .. " " .. concat(ats," ") .. "/>")
else
-- handle(format("<%s/>",etg))
handle("<" .. etg .. "/>")
end
end
end
end
elseif type(e) == "string" then
if textconverter then
handle(textconverter(e))
else
handle(e)
end
else
for i=1,#e do
serialize(e[i],handle,textconverter,attributeconverter,specialconverter,nocommands)
end
end
end
xml.serialize = serialize
function xml.checkbom(root) -- can be made faster
if root.ri then
local dt, found = root.dt, false
for k,v in ipairs(dt) do
if type(v) == "table" and v.special and v.tg == "@pi" and v.dt:find("xml.*version=") then
found = true
break
end
end
if not found then
table.insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } )
table.insert(dt, 2, "\n" )
end
end
end
--[[ldx--
At the cost of some 25% runtime overhead you can first convert the tree to a string
and then handle the lot.
--ldx]]--
function xml.tostring(root) -- 25% overhead due to collecting
if root then
if type(root) == 'string' then
return root
elseif next(root) then -- next is faster than type (and >0 test)
local result = { }
serialize(root,function(s) result[#result+1] = s end)
return concat(result,"")
end
end
return ""
end
end
--[[ldx--
The next function operated on the content only and needs a handle function
that accepts a string.
--ldx]]--
function xml.string(e,handle)
if not handle or (e.special and e.tg ~= "@rt@") then
-- nothing
elseif e.tg then
local edt = e.dt
if edt then
for i=1,#edt do
xml.string(edt[i],handle)
end
end
else
handle(e)
end
end
--[[ldx--
How you deal with saving data depends on your preferences. For a 40 MB database
file the timing on a 2.3 Core Duo are as follows (time in seconds):
1.3 : load data from file to string
6.1 : convert string into tree
5.3 : saving in file using xmlsave
6.8 : converting to string using xml.tostring
3.6 : saving converted string in file
The save function is given below.
--ldx]]--
function xml.save(root,name)
local f = io.open(name,"w")
if f then
xml.serialize(root,function(s) f:write(s) end)
f:close()
end
end
--[[ldx--
A few helpers:
--ldx]]--
function xml.body(root)
return (root.ri and root.dt[root.ri]) or root
end
function xml.text(root)
return (root and xml.tostring(root)) or ""
end
function xml.content(root) -- bugged
return (root and root.dt and xml.tostring(root.dt)) or ""
end
--[[ldx--
The next helper erases an element but keeps the table as it is,
and since empty strings are not serialized (effectively) it does
not harm. Copying the table would take more time. Usage:
dt[k] = xml.empty() or xml.empty(dt,k)
--ldx]]--
function xml.empty(dt,k)
if dt and k then
dt[k] = ""
return dt[k]
else
return ""
end
end
--[[ldx--
The next helper assigns a tree (or string). Usage:
dt[k] = xml.assign(root) or xml.assign(dt,k,root)
--ldx]]--
function xml.assign(dt,k,root)
if dt and k then
dt[k] = (type(root) == "table" and xml.body(root)) or root
return dt[k]
else
return xml.body(root)
end
end
--[[ldx--
We've now arrived at an intersting part: accessing the tree using a subset
of and since we're not compatible we call it . We
will explain more about its usage in other documents.
--ldx]]--
do
xml.functions = xml.functions or { }
local functions = xml.functions
local actions = {
[10] = "stay",
[11] = "parent",
[12] = "subtree root",
[13] = "document root",
[14] = "any",
[15] = "many",
[16] = "initial",
[20] = "match",
[21] = "match one of",
[22] = "match and attribute eq",
[23] = "match and attribute ne",
[24] = "match one of and attribute eq",
[25] = "match one of and attribute ne",
[27] = "has attribute",
[28] = "has value",
[29] = "fast match",
[30] = "select",
[31] = "expression",
[40] = "processing instruction",
}
--~ local function make_expression(str) --could also be an lpeg
--~ str = str:gsub("@([a-zA-Z%-_]+)", "(a['%1'] or '')")
--~ str = str:gsub("position%(%)", "i")
--~ str = str:gsub("text%(%)", "t")
--~ str = str:gsub("!=", "~=")
--~ str = str:gsub("([^=!~<>])=([^=!~<>])", "%1==%2")
--~ str = str:gsub("([a-zA-Z%-_]+)%(", "functions.%1(")
--~ return str, loadstring(format("return function(functions,i,a,t) return %s end", str))()
--~ end
-- a rather dumb lpeg
local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
local lp_position = P("position()") / "id"
local lp_text = P("text()") / "tx"
local lp_name = P("name()") / "((rt.ns~='' and rt.ns..':'..rt.tg) or '')"
local lp_tag = P("tag()") / "(rt.tg or '')"
local lp_ns = P("ns()") / "(rt.ns or '')"
local lp_noequal = P("!=") / "~=" + P("<=") + P(">=") + P("==")
local lp_doequal = P("=") / "=="
local lp_attribute = P("@") / "" * Cc("(at['") * R("az","AZ","--","__")^1 * Cc("'] or '')")
local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t)
if functions[t] then
return "functions." .. t .. "("
else
return "functions.error("
end
end
local lparent = lpeg.P("(")
local rparent = lpeg.P(")")
local noparent = 1 - (lparent+rparent)
local nested = lpeg.P{lparent * (noparent + lpeg.V(1))^0 * rparent}
local value = lpeg.P(lparent * lpeg.C((noparent + nested)^0) * rparent)
--~ local value = P { "(" * C(((1 - S("()")) + V(1))^0) * ")" }
local lp_special = (C(P("name")+P("text")+P("tag"))) * value / function(t,s)
if functions[t] then
if s then
return "functions." .. t .. "(rt,k," .. s ..")"
else
return "functions." .. t .. "(rt,k)"
end
else
return "functions.error(" .. t .. ")"
end
end
local converter = lpeg.Cs ( (
lp_position +
lp_text + lp_name + -- fast one
lp_special +
lp_noequal + lp_doequal +
lp_attribute +
lp_function +
1 )^1 )
local function make_expression(str)
str = converter:match(str)
return str, loadstring(format("return function(functions,id,at,tx,rt,k) return %s end", str))()
end
local map = { }
local space = S(' \r\n\t')
local squote = S("'")
local dquote = S('"')
local lparent = P('(')
local rparent = P(')')
local atsign = P('@')
local lbracket = P('[')
local rbracket = P(']')
local exclam = P('!')
local period = P('.')
local eq = P('==') + P('=')
local ne = P('<>') + P('!=')
local star = P('*')
local slash = P('/')
local colon = P(':')
local bar = P('|')
local hat = P('^')
local valid = R('az', 'AZ', '09') + S('_-')
--~ local name_yes = C(valid^1 + star) * colon * C(valid^1 + star) -- permits ns:* *:tg *:*
--~ local name_nop = C(P(true)) * C(valid^1)
local name_yes = C(valid^1 + star) * colon * C(valid^1 + star) -- permits ns:* *:tg *:*
local name_nop = Cc("*") * C(valid^1)
local name = name_yes + name_nop
local number = C((S('+-')^0 * R('09')^1)) / tonumber
local names = (bar^0 * name)^1
local morenames = name * (bar^0 * name)^1
local instructiontag = P('pi::')
local spacing = C(space^0)
local somespace = space^1
local optionalspace = space^0
local text = C(valid^0)
local value = (squote * C((1 - squote)^0) * squote) + (dquote * C((1 - dquote)^0) * dquote)
local empty = 1-slash
local is_eq = lbracket * atsign * name * eq * value * rbracket
local is_ne = lbracket * atsign * name * ne * value * rbracket
local is_attribute = lbracket * atsign * name * rbracket
local is_value = lbracket * value * rbracket
local is_number = lbracket * number * rbracket
local nobracket = 1-(lbracket+rbracket) -- must be improved
local is_expression = lbracket * C(((C(nobracket^1))/make_expression)) * rbracket
local is_expression = lbracket * (C(nobracket^1))/make_expression * rbracket
local is_one = name
local is_none = exclam * name
local is_one_of = ((lparent * names * rparent) + morenames)
local is_none_of = exclam * ((lparent * names * rparent) + morenames)
local stay = (period )
local parent = (period * period ) / function( ) map[#map+1] = { 11 } end
local subtreeroot = (slash + hat ) / function( ) map[#map+1] = { 12 } end
local documentroot = (hat * hat ) / function( ) map[#map+1] = { 13 } end
local any = (star ) / function( ) map[#map+1] = { 14 } end
local many = (star * star ) / function( ) map[#map+1] = { 15 } end
local initial = (hat * hat * hat ) / function( ) map[#map+1] = { 16 } end
local match = (is_one ) / function(...) map[#map+1] = { 20, true , ... } end
local match_one_of = (is_one_of ) / function(...) map[#map+1] = { 21, true , ... } end
local dont_match = (is_none ) / function(...) map[#map+1] = { 20, false, ... } end
local dont_match_one_of = (is_none_of ) / function(...) map[#map+1] = { 21, false, ... } end
local match_and_eq = (is_one * is_eq ) / function(...) map[#map+1] = { 22, true , ... } end
local match_and_ne = (is_one * is_ne ) / function(...) map[#map+1] = { 23, true , ... } end
local dont_match_and_eq = (is_none * is_eq ) / function(...) map[#map+1] = { 22, false, ... } end
local dont_match_and_ne = (is_none * is_ne ) / function(...) map[#map+1] = { 23, false, ... } end
local match_one_of_and_eq = (is_one_of * is_eq ) / function(...) map[#map+1] = { 24, true , ... } end
local match_one_of_and_ne = (is_one_of * is_ne ) / function(...) map[#map+1] = { 25, true , ... } end
local dont_match_one_of_and_eq = (is_none_of * is_eq ) / function(...) map[#map+1] = { 24, false, ... } end
local dont_match_one_of_and_ne = (is_none_of * is_ne ) / function(...) map[#map+1] = { 25, false, ... } end
local has_attribute = (is_one * is_attribute) / function(...) map[#map+1] = { 27, true , ... } end
local has_value = (is_one * is_value ) / function(...) map[#map+1] = { 28, true , ... } end
local dont_has_attribute = (is_none * is_attribute) / function(...) map[#map+1] = { 27, false, ... } end
local dont_has_value = (is_none * is_value ) / function(...) map[#map+1] = { 28, false, ... } end
local position = (is_one * is_number ) / function(...) map[#map+1] = { 30, true, ... } end
local dont_position = (is_none * is_number ) / function(...) map[#map+1] = { 30, false, ... } end
local expression = (is_one * is_expression)/ function(...) map[#map+1] = { 31, true, ... } end
local dont_expression = (is_none * is_expression)/ function(...) map[#map+1] = { 31, false, ... } end
local self_expression = ( is_expression) / function(...) if #map == 0 then map[#map+1] = { 11 } end
map[#map+1] = { 31, true, "*", "*", ... } end
local dont_self_expression = (exclam * is_expression) / function(...) if #map == 0 then map[#map+1] = { 11 } end
map[#map+1] = { 31, false, "*", "*", ... } end
local instruction = (instructiontag * text ) / function(...) map[#map+1] = { 40, ... } end
local nothing = (empty ) / function( ) map[#map+1] = { 15 } end -- 15 ?
local crap = (1-slash)^1
-- a few ugly goodies:
local docroottag = P('^^') / function( ) map[#map+1] = { 12 } end
local subroottag = P('^') / function( ) map[#map+1] = { 13 } end
local roottag = P('root::') / function( ) map[#map+1] = { 12 } end
local parenttag = P('parent::') / function( ) map[#map+1] = { 11 } end
local childtag = P('child::')
local selftag = P('self::')
-- there will be more and order will be optimized
local selector = (
instruction +
many + any +
parent + stay +
dont_position + position +
dont_match_one_of_and_eq + dont_match_one_of_and_ne +
match_one_of_and_eq + match_one_of_and_ne +
dont_match_and_eq + dont_match_and_ne +
match_and_eq + match_and_ne +
dont_expression + expression +
dont_self_expression + self_expression +
has_attribute + has_value +
dont_match_one_of + match_one_of +
dont_match + match +
crap + empty
)
local grammar = P { "startup",
startup = (initial + documentroot + subtreeroot + roottag + docroottag + subroottag)^0 * V("followup"),
followup = ((slash + parenttag + childtag + selftag)^0 * selector)^1,
}
local function compose(str)
if not str or str == "" then
-- wildcard
return true
elseif str == '/' then
-- root
return false
else
map = { }
grammar:match(str)
if #map == 0 then
return true
else
local m = map[1][1]
if #map == 1 then
if m == 14 or m == 15 then
-- wildcard
return true
elseif m == 12 then
-- root
return false
end
elseif #map == 2 and m == 12 and map[2][1] == 20 then
-- return { { 29, map[2][2], map[2][3], map[2][4], map[2][5] } }
map[2][1] = 29
return { map[2] }
end
if m ~= 11 and m ~= 12 and m ~= 13 and m ~= 14 and m ~= 15 and m ~= 16 then
table.insert(map, 1, { 16 })
end
-- print((table.serialize(map)):gsub("[ \n]+"," "))
return map
end
end
end
local cache = { }
function xml.lpath(pattern,trace)
if type(pattern) == "string" then
local result = cache[pattern]
if not result then
result = compose(pattern)
cache[pattern] = result
end
if trace or xml.trace_lpath then
xml.lshow(result)
end
return result
else
return pattern
end
end
local fallbackreport = (texio and texio.write) or io.write
function xml.lshow(pattern,report)
report = report or fallbackreport
local lp = xml.lpath(pattern)
if lp == false then
report(" -: root\n")
elseif lp == true then
report(" -: wildcard\n")
else
if type(pattern) == "string" then
report(format("pattern: %s\n",pattern))
end
for k,v in ipairs(lp) do
if #v > 1 then
local t = { }
for i=2,#v do
local vv = v[i]
if type(vv) == "string" then
t[#t+1] = (vv ~= "" and vv) or "#"
elseif type(vv) == "boolean" then
t[#t+1] = (vv and "==") or "<>"
end
end
report(format("%2i: %s %s -> %s\n", k,v[1],actions[v[1]],concat(t," ")))
else
report(format("%2i: %s %s\n", k,v[1],actions[v[1]]))
end
end
end
end
function xml.xshow(e,...) -- also handy when report is given, use () to isolate first e
local t = { ... }
local report = (type(t[#t]) == "function" and t[#t]) or fallbackreport
if e == nil then
report("\n")
elseif type(e) ~= "table" then
report(tostring(e))
elseif e.tg then
report(tostring(e) .. "\n")
else
for i=1,#e do
report(tostring(e[i]) .. "\n")
end
end
end
end
--[[ldx--
An is converted to a table with instructions for traversing the
tree. Hoever, simple cases are signaled by booleans. Because we don't know in
advance what we want to do with the found element the handle gets three arguments:
r : the root element of the data table
d : the data table of the result
t : the index in the data table of the result
Access to the root and data table makes it possible to construct insert and delete
functions.
--ldx]]--
do
local functions = xml.functions
functions.contains = string.find
functions.find = string.find
functions.upper = string.upper
functions.lower = string.lower
functions.number = tonumber
functions.boolean = toboolean
functions.oneof = function(s,...) -- slow
local t = {...} for i=1,#t do if s == t[i] then return true end end return false
end
functions.error = function(str)
xml.error_handler("unknown function in lpath expression",str)
return false
end
functions.text = function(root,k,n) -- unchecked, maybe one deeper
local t = type(t)
if t == "string" then
return t
else -- todo n
local rdt = root.dt
return (rdt and rdt[k]) or root[k] or ""
end
end
functions.name = function(root,k,n)
-- way too fuzzy
local found
if not k or not n then
local ns, tg = root.rn or root.ns or "", root.tg
if not tg then
for i=1,#root do
local e = root[i]
if type(e) == "table" then
found = e
break
end
end
elseif ns ~= "" then
return ns .. ":" .. tg
else
return tg
end
elseif n == 0 then
local e = root[k]
if type(e) ~= "table" then
found = e
end
elseif n < 0 then
for i=k-1,1,-1 do
local e = root[i]
if type(e) == "table" then
if n == -1 then
found = e
break
else
n = n + 1
end
end
end
else
--~ print(k,n,#root)
for i=k+1,#root,1 do
local e = root[i]
if type(e) == "table" then
if n == 1 then
found = e
break
else
n = n - 1
end
end
end
end
if found then
local ns, tg = found.rn or found.ns or "", found.tg
if ns ~= "" then
return ns .. ":" .. tg
else
return tg
end
else
return ""
end
end
local function traverse(root,pattern,handle,reverse,index,parent,wildcard) -- multiple only for tags, not for namespaces
if not root then -- error
return false
elseif pattern == false then -- root
handle(root,root.dt,root.ri)
return false
elseif pattern == true then -- wildcard
local rootdt = root.dt
if rootdt then
local start, stop, step = 1, #rootdt, 1
if reverse then
start, stop, step = stop, start, -1
end
for k=start,stop,step do
if handle(root,rootdt,root.ri or k) then return false end
if not traverse(rootdt[k],true,handle,reverse) then return false end
end
end
return false
elseif root.dt then
index = index or 1
local action = pattern[index]
local command = action[1]
if command == 29 then -- fast case /oeps
local rootdt = root.dt
for k=1,#rootdt do
local e = rootdt[k]
local tg = e.tg
if e.tg then
local ns = e.rn or e.ns
local ns_a, tg_a = action[3], action[4]
local matched = (ns_a == "*" or ns == ns_a) and (tg_a == "*" or tg == tg_a)
if not action[2] then matched = not matched end
if matched then
if handle(root,rootdt,k) then return false end
end
end
end
elseif command == 11 then -- parent
local ep = root.__p__ or parent
if index < #pattern then
if not traverse(ep,pattern,handle,reverse,index+1,root) then return false end
elseif handle(root,rootdt,k) then
return false
end
else
if (command == 16 or command == 12) and index == 1 then -- initial
-- wildcard = true
wildcard = command == 16 -- ok?
index = index + 1
action = pattern[index]
command = action and action[1] or 0 -- something is wrong
end
if command == 11 then -- parent
local ep = root.__p__ or parent
if index < #pattern then
if not traverse(ep,pattern,handle,reverse,index+1,root) then return false end
elseif handle(root,rootdt,k) then
return false
end
else
local rootdt = root.dt
local start, stop, step, n, dn = 1, #rootdt, 1, 0, 1
if command == 30 then
if action[5] < 0 then
start, stop, step = stop, start, -1
dn = -1
end
elseif reverse and index == #pattern then
start, stop, step = stop, start, -1
end
local idx = 0
for k=start,stop,step do -- we used to have functions for all but a case is faster
local e = rootdt[k]
local ns, tg = e.rn or e.ns, e.tg
if tg then
idx = idx + 1
if command == 30 then
local ns_a, tg_a = action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
if matched then
n = n + dn
if n == action[5] then
if index == #pattern then
if handle(root,rootdt,root.ri or k) then return false end
else
if not traverse(e,pattern,handle,reverse,index+1,root) then return false end
end
break
end
elseif wildcard then
if not traverse(e,pattern,handle,reverse,index,root,true) then return false end
end
else
local matched, multiple = false, false
if command == 20 then -- match
local ns_a, tg_a = action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
elseif command == 21 then -- match one of
multiple = true
for i=3,#action,2 do
local ns_a, tg_a = action[i], action[i+1]
if (ns_a == "*" or ns == ns_a) and (tg == "*" or tg == tg_a) then
matched = true
break
end
end
if not action[2] then matched = not matched end
elseif command == 22 then -- eq
local ns_a, tg_a = action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
matched = matched and e.at[action[6]] == action[7]
elseif command == 23 then -- ne
local ns_a, tg_a = action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
matched = mached and e.at[action[6]] ~= action[7]
elseif command == 24 then -- one of eq
multiple = true
for i=3,#action-2,2 do
local ns_a, tg_a = action[i], action[i+1]
if (ns_a == "*" or ns == ns_a) and (tg == "*" or tg == tg_a) then
matched = true
break
end
end
if not action[2] then matched = not matched end
matched = matched and e.at[action[#action-1]] == action[#action]
elseif command == 25 then -- one of ne
multiple = true
for i=3,#action-2,2 do
local ns_a, tg_a = action[i], action[i+1]
if (ns_a == "*" or ns == ns_a) and (tg == "*" or tg == tg_a) then
matched = true
break
end
end
if not action[2] then matched = not matched end
matched = matched and e.at[action[#action-1]] ~= action[#action]
elseif command == 27 then -- has attribute
local ns_a, tg_a = action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
matched = matched and e.at[action[5]]
elseif command == 28 then -- has value
local edt, ns_a, tg_a = e.dt, action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
matched = matched and edt and edt[1] == action[5]
elseif command == 31 then
local edt, ns_a, tg_a = e.dt, action[3], action[4]
if tg == tg_a then
matched = ns_a == "*" or ns == ns_a
elseif tg_a == '*' then
matched, multiple = ns_a == "*" or ns == ns_a, true
else
matched = false
end
if not action[2] then matched = not matched end
if matched then
matched = action[6](functions,idx,e.at or { },edt[1],rootdt,k)
end
end
if matched then -- combine tg test and at test
if index == #pattern then
if handle(root,rootdt,root.ri or k) then return false end
if wildcard then
if multiple then
if not traverse(e,pattern,handle,reverse,index,root,true) then return false end
else
-- maybe or multiple; anyhow, check on (section|title) vs just section and title in example in lxml
if not traverse(e,pattern,handle,reverse,index,root) then return false end
end
end
else
if not traverse(e,pattern,handle,reverse,index+1,root) then return false end
end
elseif command == 14 then -- any
if index == #pattern then
if handle(root,rootdt,root.ri or k) then return false end
else
if not traverse(e,pattern,handle,reverse,index+1,root) then return false end
end
elseif command == 15 then -- many
if index == #pattern then
if handle(root,rootdt,root.ri or k) then return false end
else
if not traverse(e,pattern,handle,reverse,index+1,root,true) then return false end
end
-- not here : 11
elseif command == 11 then -- parent
local ep = e.__p__ or parent
if index < #pattern then
if not traverse(ep,pattern,handle,reverse,root,index+1) then return false end
elseif handle(root,rootdt,k) then
return false
end
elseif command == 40 and e.special and tg == "@pi@" then -- pi
local pi = action[2]
if pi ~= "" then
local pt = e.dt[1]
if pt and pt:find(pi) then
if handle(root,rootdt,k) then
return false
end
end
elseif handle(root,rootdt,k) then
return false
end
elseif wildcard then
if not traverse(e,pattern,handle,reverse,index,root,true) then return false end
end
end
else
-- not here : 11
if command == 11 then -- parent
local ep = e.__p__ or parent
if index < #pattern then
if not traverse(ep,pattern,handle,reverse,index+1,root) then return false end
elseif handle(root,rootdt,k) then
return false
end
break -- else loop
end
end
end
end
end
end
return true
end
xml.traverse = traverse
end
--[[ldx--
Next come all kind of locators and manipulators. The most generic function here
is xml.filter(root,pattern). All registers functions in the filters namespace
can be path of a search path, as in:
local r, d, k = xml.filter(root,"/a/b/c/position(4)"
--ldx]]--
do
local traverse, lpath, convert = xml.traverse, xml.lpath, xml.convert
xml.filters = { }
function xml.filters.default(root,pattern)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end)
return dt and dt[dk], rt, dt, dk
end
function xml.filters.attributes(root,pattern,arguments)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk = r, d, k return true end)
local ekat = (dt and dt[dk] and dt[dk].at) or (rt and rt.at)
if ekat then
if arguments then
return ekat[arguments] or "", rt, dt, dk
else
return ekat, rt, dt, dk
end
else
return { }, rt, dt, dk
end
end
function xml.filters.reverse(root,pattern)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end, 'reverse')
return dt and dt[dk], rt, dt, dk
end
function xml.filters.count(root,pattern,everything)
local n = 0
traverse(root, lpath(pattern), function(r,d,t)
if everything or type(d[t]) == "table" then
n = n + 1
end
end)
return n
end
function xml.filters.elements(root, pattern) -- == all
local t = { }
traverse(root, lpath(pattern), function(r,d,k)
local e = d[k]
if e then
t[#t+1] = e
end
end)
return t
end
function xml.filters.texts(root, pattern)
local t = { }
traverse(root, lpath(pattern), function(r,d,k)
local e = d[k]
if e and e.dt then
t[#t+1] = e.dt
end
end)
return t
end
function xml.filters.first(root,pattern)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end)
return dt and dt[dk], rt, dt, dk
end
function xml.filters.last(root,pattern)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt,dt,dk = r,d,k return true end, 'reverse')
return dt and dt[dk], rt, dt, dk
end
function xml.filters.index(root,pattern,arguments)
local rt, dt, dk, reverse, i = nil, nil, nil, false, tonumber(arguments or '1') or 1
if i and i ~= 0 then
if i < 0 then
reverse, i = true, -i
end
traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk, i = r, d, k, i-1 return i == 0 end, reverse)
if i == 0 then
return dt and dt[dk], rt, dt, dk
end
end
return nil, nil, nil, nil
end
function xml.filters.attribute(root,pattern,arguments)
local rt, dt, dk
traverse(root, lpath(pattern), function(r,d,k) rt, dt, dk = r, d, k return true end)
local ekat = (dt and dt[dk] and dt[dk].at) or (rt and rt.at)
return (ekat and (ekat[arguments] or ekat[arguments:gsub("^([\"\'])(.*)%1$","%2")])) or ""
end
function xml.filters.text(root,pattern,arguments) -- ?? why index, tostring slow
local dtk, rt, dt, dk = xml.filters.index(root,pattern,arguments)
if dtk then -- n
local dtkdt = dtk.dt
if not dtkdt then
return "", rt, dt, dk
elseif #dtkdt == 1 and type(dtkdt[1]) == "string" then
return dtkdt[1], rt, dt, dk
else
return xml.tostring(dtkdt), rt, dt, dk
end
else
return "", rt, dt, dk
end
end
function xml.filters.tag(root,pattern,n)
local tag = ""
traverse(root, lpath(pattern), function(r,d,k)
tag = xml.functions.tag(d,k,n and tonumber(n))
return true
end)
return tag
end
function xml.filters.name(root,pattern,n)
local tag = ""
traverse(root, lpath(pattern), function(r,d,k)
tag = xml.functions.name(d,k,n and tonumber(n))
return true
end)
return tag
end
--[[ldx--
For splitting the filter function from the path specification, we can
use string matching or lpeg matching. Here the difference in speed is
neglectable but the lpeg variant is more robust.
--ldx]]--
-- not faster but hipper ... although ... i can't get rid of the trailing / in the path
local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
local slash = P('/')
local name = (R("az","AZ","--","__"))^1
local path = C(((1-slash)^0 * slash)^1)
local argument = P { "(" * C(((1 - S("()")) + V(1))^0) * ")" }
local action = Cc(1) * path * C(name) * argument
local attribute = Cc(2) * path * P('@') * C(name)
local direct = Cc(3) * Cc("../*") * slash^0 * C(name) * argument
local parser = direct + action + attribute
local filters = xml.filters
local attribute_filter = xml.filters.attributes
local default_filter = xml.filters.default
-- todo: also hash, could be gc'd
function xml.filter(root,pattern)
local kind, a, b, c = parser:match(pattern)
--~ if xml.trace_lpath then
--~ print(pattern,kind,a,b,c)
--~ end
if kind == 1 or kind == 3 then
return (filters[b] or default_filter)(root,a,c)
elseif kind == 2 then
return attribute_filter(root,a,b)
else
return default_filter(root,pattern)
end
end
--~ slightly faster, but first we need a proper test file
--~
--~ local hash = { }
--~
--~ function xml.filter(root,pattern)
--~ local h = hash[pattern]
--~ if not h then
--~ local kind, a, b, c = parser:match(pattern)
--~ if kind == 1 then
--~ h = { kind, filters[b] or default_filter, a, b, c }
--~ elseif kind == 2 then
--~ h = { kind, attribute_filter, a, b, c }
--~ else
--~ h = { kind, default_filter, a, b, c }
--~ end
--~ hash[pattern] = h
--~ end
--~ local kind = h[1]
--~ if kind == 1 then
--~ return h[2](root,h[2],h[4])
--~ elseif kind == 2 then
--~ return h[2](root,h[2],h[3])
--~ else
--~ return h[2](root,pattern)
--~ end
--~ end
--[[ldx--
The following functions collect elements and texts.
--ldx]]--
-- still somewhat bugged
function xml.collect_elements(root, pattern, ignorespaces)
local rr, dd = { }, { }
traverse(root, lpath(pattern), function(r,d,k)
local dk = d and d[k]
if dk then
if ignorespaces and type(dk) == "string" and dk:find("[^%S]") then
-- ignore
else
local n = #rr+1
rr[n], dd[n] = r, dk
end
end
end)
return dd, rr
end
function xml.collect_texts(root, pattern, flatten)
local t = { } -- no r collector
traverse(root, lpath(pattern), function(r,d,k)
if d then
local ek = d[k]
local tx = ek and ek.dt
if flatten then
if tx then
t[#t+1] = xml.tostring(tx) or ""
else
t[#t+1] = ""
end
else
t[#t+1] = tx or ""
end
else
t[#t+1] = ""
end
end)
return t
end
function xml.collect_tags(root, pattern, nonamespace)
local t = { }
xml.traverse(root, xml.lpath(pattern), function(r,d,k)
local dk = d and d[k]
if dk and type(dk) == "table" then
local ns, tg = e.ns, e.tg
if nonamespace then
t[#t+1] = tg -- if needed we can return an extra table
elseif ns == "" then
t[#t+1] = tg
else
t[#t+1] = ns .. ":" .. tg
end
end
end)
return #t > 0 and {}
end
--[[ldx--
Often using an iterators looks nicer in the code than passing handler
functions. The book describes how to use coroutines for that
purpose (). This permits
code like:
for r, d, k in xml.elements(xml.load('text.xml'),"title") do
print(d[k])
end
Which will print all the titles in the document. The iterator variant takes
1.5 times the runtime of the function variant which is due to the overhead in
creating the wrapper. So, instead of:
function xml.filters.first(root,pattern)
for rt,dt,dk in xml.elements(root,pattern)
return dt and dt[dk], rt, dt, dk
end
return nil, nil, nil, nil
end
We use the function variants in the filters.
--ldx]]--
local wrap, yield = coroutine.wrap, coroutine.yield
function xml.elements(root,pattern,reverse)
return wrap(function() traverse(root, lpath(pattern), yield, reverse) end)
end
function xml.elements_only(root,pattern,reverse)
return wrap(function() traverse(root, lpath(pattern), function(r,d,k) yield(d[k]) end, reverse) end)
end
function xml.each_element(root, pattern, handle, reverse)
local ok
traverse(root, lpath(pattern), function(r,d,k) ok = true handle(r,d,k) end, reverse)
return ok
end
function xml.process_elements(root, pattern, handle)
traverse(root, lpath(pattern), function(r,d,k)
local dkdt = d[k].dt
if dkdt then
for i=1,#dkdt do
local v = dkdt[i]
if v.tg then handle(v) end
end
end
end)
end
function xml.process_attributes(root, pattern, handle)
traverse(root, lpath(pattern), function(r,d,k)
local ek = d[k]
local a = ek.at or { }
handle(a)
if next(a) then -- next is faster than type (and >0 test)
ek.at = a
else
ek.at = nil
end
end)
end
--[[ldx--
We've now arrives at the functions that manipulate the tree.
--ldx]]--
function xml.inject_element(root, pattern, element, prepend)
if root and element then
local matches, collect = { }, nil
if type(element) == "string" then
element = convert(element,true)
end
if element then
collect = function(r,d,k) matches[#matches+1] = { r, d, k, element } end
traverse(root, lpath(pattern), collect)
for i=1,#matches do
local m = matches[i]
local r, d, k, element, edt = m[1], m[2], m[3], m[4], nil
if element.ri then
element = element.dt[element.ri].dt
else
element = element.dt
end
if r.ri then
edt = r.dt[r.ri].dt
else
edt = d and d[k] and d[k].dt
end
if edt then
local be, af
if prepend then
be, af = xml.copy(element), edt
else
be, af = edt, xml.copy(element)
end
for i=1,#af do
be[#be+1] = af[i]
end
if r.ri then
r.dt[r.ri].dt = be
else
d[k].dt = be
end
else
-- r.dt = element.dt -- todo
end
end
end
end
end
-- todo: copy !
function xml.insert_element(root, pattern, element, before) -- todo: element als functie
if root and element then
if pattern == "/" then
xml.inject_element(root, pattern, element, before)
else
local matches, collect = { }, nil
if type(element) == "string" then
element = convert(element,true)
end
if element and element.ri then
element = element.dt[element.ri]
end
if element then
collect = function(r,d,k) matches[#matches+1] = { r, d, k, element } end
traverse(root, lpath(pattern), collect)
for i=#matches,1,-1 do
local m = matches[i]
local r, d, k, element = m[1], m[2], m[3], m[4]
if not before then k = k + 1 end
if element.tg then
table.insert(d,k,element) -- untested
elseif element.dt then
for _,v in ipairs(element.dt) do -- i added
table.insert(d,k,v)
k = k + 1
end
end
end
end
end
end
end
xml.insert_element_after = xml.insert_element
xml.insert_element_before = function(r,p,e) xml.insert_element(r,p,e,true) end
xml.inject_element_after = xml.inject_element
xml.inject_element_before = function(r,p,e) xml.inject_element(r,p,e,true) end
function xml.delete_element(root, pattern)
local matches, deleted = { }, { }
local collect = function(r,d,k) matches[#matches+1] = { r, d, k } end
traverse(root, lpath(pattern), collect)
for i=#matches,1,-1 do
local m = matches[i]
deleted[#deleted+1] = table.remove(m[2],m[3])
end
return deleted
end
function xml.replace_element(root, pattern, element)
if type(element) == "string" then
element = convert(element,true)
end
if element and element.ri then
element = element.dt[element.ri]
end
if element then
traverse(root, lpath(pattern), function(rm, d, k)
d[k] = element.dt -- maybe not clever enough
end)
end
end
local function load_data(name) -- == io.loaddata
local f, data = io.open(name), ""
if f then
data = f:read("*all",'b') -- 'b' ?
f:close()
end
return data
end
function xml.include(xmldata,pattern,attribute,recursive,loaddata)
-- parse="text" (default: xml), encoding="" (todo)
-- attribute = attribute or 'href'
pattern = pattern or 'include'
loaddata = loaddata or load_data
local function include(r,d,k)
local ek, name = d[k], nil
if not attribute or attribute == "" then
local ekdt = ek.dt
name = (type(ekdt) == "table" and ekdt[1]) or ekdt
end
if not name then
if ek.at then
for a in (attribute or "href"):gmatch("([^|]+)") do
name = ek.at[a]
if name then break end
end
end
end
local data = (name and name ~= "" and loaddata(name)) or ""
if data == "" then
xml.empty(d,k)
elseif ek.at["parse"] == "text" then -- for the moment hard coded
d[k] = xml.escaped(data)
else
local xi = xml.convert(data)
if not xi then
xml.empty(d,k)
else
if recursive then
xml.include(xi,pattern,attribute,recursive,loaddata)
end
xml.assign(d,k,xi)
end
end
end
xml.each_element(xmldata, pattern, include)
end
function xml.strip_whitespace(root, pattern)
traverse(root, lpath(pattern), function(r,d,k)
local dkdt = d[k].dt
if dkdt then -- can be optimized
local t = { }
for i=1,#dkdt do
local str = dkdt[i]
if type(str) == "string" and str:find("^[ \n\r\t]*$") then
-- stripped
else
t[#t+1] = str
end
end
d[k].dt = t
end
end)
end
function xml.rename_space(root, oldspace, newspace) -- fast variant
local ndt = #root.dt
local rename = xml.rename_space
for i=1,ndt or 0 do
local e = root[i]
if type(e) == "table" then
if e.ns == oldspace then
e.ns = newspace
if e.rn then
e.rn = newspace
end
end
local edt = e.dt
if edt then
rename(edt, oldspace, newspace)
end
end
end
end
function xml.remap_tag(root, pattern, newtg)
traverse(root, lpath(pattern), function(r,d,k)
d[k].tg = newtg
end)
end
function xml.remap_namespace(root, pattern, newns)
traverse(root, lpath(pattern), function(r,d,k)
d[k].ns = newns
end)
end
function xml.check_namespace(root, pattern, newns)
traverse(root, lpath(pattern), function(r,d,k)
local dk = d[k]
if (not dk.rn or dk.rn == "") and dk.ns == "" then
dk.rn = newns
end
end)
end
function xml.remap_name(root, pattern, newtg, newns, newrn)
traverse(root, lpath(pattern), function(r,d,k)
local dk = d[k]
dk.tg = newtg
dk.ns = newns
dk.rn = newrn
end)
end
function xml.filters.found(root,pattern,check_content)
local found = false
traverse(root, lpath(pattern), function(r,d,k)
if check_content then
local dk = d and d[k]
found = dk and dk.dt and next(dk.dt) and true
else
found = true
end
return true
end)
return found
end
end
--[[ldx--
Here are a few synonyms.
--ldx]]--
xml.filters.position = xml.filters.index
xml.count = xml.filters.count
xml.index = xml.filters.index
xml.position = xml.filters.index
xml.first = xml.filters.first
xml.last = xml.filters.last
xml.found = xml.filters.found
xml.each = xml.each_element
xml.process = xml.process_element
xml.strip = xml.strip_whitespace
xml.collect = xml.collect_elements
xml.all = xml.collect_elements
xml.insert = xml.insert_element_after
xml.inject = xml.inject_element_after
xml.after = xml.insert_element_after
xml.before = xml.insert_element_before
xml.delete = xml.delete_element
xml.replace = xml.replace_element
--[[ldx--
The following helper functions best belong to the lmxl-ini
module. Some are here because we need then in the mk
document and other manuals, others came up when playing with
this module. Since this module is also used in we've
put them here instead of loading mode modules there then needed.
--ldx]]--
function xml.gsub(t,old,new)
if t.dt then
for k,v in ipairs(t.dt) do
if type(v) == "string" then
t.dt[k] = v:gsub(old,new)
else
xml.gsub(v,old,new)
end
end
end
end
function xml.strip_leading_spaces(dk,d,k) -- cosmetic, for manual
if d and k and d[k-1] and type(d[k-1]) == "string" then
local s = d[k-1]:match("\n(%s+)")
xml.gsub(dk,"\n"..string.rep(" ",#s),"\n")
end
end
function xml.serialize_path(root,lpath,handle)
local dk, r, d, k = xml.first(root,lpath)
dk = xml.copy(dk)
xml.strip_leading_spaces(dk,d,k)
xml.serialize(dk,handle)
end
--~ xml.escapes = { ['&'] = '&', ['<'] = '<', ['>'] = '>', ['"'] = '"' }
--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
--~ function xml.escaped (str) return str:gsub("(.)" , xml.escapes ) end
--~ function xml.unescaped(str) return str:gsub("(&.-;)", xml.unescapes) end
--~ function xml.cleansed (str) return str:gsub("<.->" , '' ) end -- "%b<>"
do
local P, S, R, C, V, Cc, Cs = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc, lpeg.Cs
-- 100 * 2500 * "oeps< oeps> oeps&" : gsub:lpeg|lpeg|lpeg
--
-- 1021:0335:0287:0247
-- 10 * 1000 * "oeps< oeps> oeps& asfjhalskfjh alskfjh alskfjh alskfjh ;al J;LSFDJ"
--
-- 1559:0257:0288:0190 (last one suggested by roberto)
-- escaped = Cs((S("<&>") / xml.escapes + 1)^0)
-- escaped = Cs((S("<")/"<" + S(">")/">" + S("&")/"&" + 1)^0)
local normal = (1 - S("<&>"))^0
local special = P("<")/"<" + P(">")/">" + P("&")/"&"
local escaped = Cs(normal * (special * normal)^0)
-- 100 * 1000 * "oeps< oeps> oeps&" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
-- unescaped = Cs((S("<")/"<" + S(">")/">" + S("&")/"&" + 1)^0)
-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
local normal = (1 - S"&")^0
local special = P("<")/"<" + P(">")/">" + P("&")/"&"
local unescaped = Cs(normal * (special * normal)^0)
-- 100 * 5000 * "oeps oeps oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
function xml.escaped (str) return escaped :match(str) end
function xml.unescaped(str) return unescaped:match(str) end
function xml.cleansed (str) return cleansed :match(str) end
end
function xml.join(t,separator,lastseparator)
if #t > 0 then
local result = { }
for k,v in pairs(t) do
result[k] = xml.tostring(v)
end
if lastseparator then
return concat(result,separator or "",1,#result-1) .. (lastseparator or "") .. result[#result]
else
return concat(result,separator)
end
else
return ""
end
end
--[[ldx--
We provide (at least here) two entity handlers. The more extensive
resolver consults a hash first, tries to convert to next,
and finaly calls a handler when defines. When this all fails, the
original entity is returned.
--ldx]]--
do if unicode and unicode.utf8 then
xml.entities = xml.entities or { } -- xml.entities.handler == function
function xml.entities.handler(e)
return format("[%s]",e)
end
local char = unicode.utf8.char
local function toutf(s)
return char(tonumber(s,16))
end
local entities = xml.entities -- global entities
function utfize(root)
local d = root.dt
for k=1,#d do
local dk = d[k]
if type(dk) == "string" then
-- test prevents copying if no match
if dk:find(".-;") then
d[k] = dk:gsub("(.-);",toutf)
end
else
utfize(dk)
end
end
end
xml.utfize = utfize
local function resolve(e) -- hex encoded always first, just to avoid mkii fallbacks
if e:find("#x") then
return char(tonumber(e:sub(3),16))
else
local ee = entities[e]
if ee then
return ee
else
local h = xml.entities.handler
return (h and h(e)) or "&" .. e .. ";"
end
end
end
local function resolve_entities(root)
if not root.special or root.tg == "@rt@" then
local d = root.dt
for k=1,#d do
local dk = d[k]
if type(dk) == "string" then
if dk:find("&.-;") then
d[k] = dk:gsub("&(.-);",resolve)
end
else
resolve_entities(dk)
end
end
end
end
xml.resolve_entities = resolve_entities
function xml.utfize_text(str)
if str:find("") then
return (str:gsub("(.-);",toutf))
else
return str
end
end
function xml.resolve_text_entities(str) -- maybe an lpeg. maybe resolve inline
if str:find("&") then
return (str:gsub("&(.-);",resolve))
else
return str
end
end
function xml.show_text_entities(str)
if str:find("&") then
return (str:gsub("&(.-);","[%1]"))
else
return str
end
end
-- experimental, this will be done differently
function xml.merge_entities(root)
local documententities = root.entities
local allentities = xml.entities
if documententities then
for k, v in pairs(documententities) do
allentities[k] = v
end
end
end
end end
-- xml.set_text_cleanup(xml.show_text_entities)
-- xml.set_text_cleanup(xml.resolve_text_entities)
--~ xml.lshow("/../../../a/(b|c)[@d='e']/f")
--~ xml.lshow("/../../../a/!(b|c)[@d='e']/f")
--~ xml.lshow("/../../../a/!b[@d!='e']/f")
--~ x = xml.convert([[
--~
--~ 01
--~ 02
--~ 03
--~ OK
--~ 05
--~ 06
--~ ALSO OK
--~
--~ ]])
--~ xml.trace_lpath = true
--~ xml.xshow(xml.first(x,"b[position() > 2 and position() < 5 and text() == 'ok']"))
--~ xml.xshow(xml.first(x,"b[position() > 2 and position() < 5 and text() == upper('ok')]"))
--~ xml.xshow(xml.first(x,"b[@n=='03' or @n=='08']"))
--~ xml.xshow(xml.all (x,"b[number(@n)>2 and number(@n)<6]"))
--~ xml.xshow(xml.first(x,"b[find(text(),'ALSO')]"))
--~ str = [[
--~
--~
--~ my secret
--~
--~ ]]
--~ x = xml.convert([[
--~ 0102xx03OK
--~ ]])
--~ xml.xshow(xml.first(x,"b[tag(2) == 'x']"))
--~ xml.xshow(xml.first(x,"b[tag(1) == 'x']"))
--~ xml.xshow(xml.first(x,"b[tag(-1) == 'x']"))
--~ xml.xshow(xml.first(x,"b[tag(-2) == 'x']"))
--~ print(xml.filter(x,"b/tag(2)"))
--~ print(xml.filter(x,"b/tag(1)"))
-- filename : l-utils.lua
-- comment : split off from luat-lib
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['l-utils'] = 1.001
if not utils then utils = { } end
if not utils.merger then utils.merger = { } end
if not utils.lua then utils.lua = { } end
utils.merger.m_begin = "begin library merge"
utils.merger.m_end = "end library merge"
utils.merger.pattern =
"%c+" ..
"%-%-%s+" .. utils.merger.m_begin ..
"%c+(.-)%c+" ..
"%-%-%s+" .. utils.merger.m_end ..
"%c+"
function utils.merger._self_fake_()
return
"-- " .. "created merged file" .. "\n\n" ..
"-- " .. utils.merger.m_begin .. "\n\n" ..
"-- " .. utils.merger.m_end .. "\n\n"
end
function utils.report(...)
print(...)
end
function utils.merger._self_load_(name)
local f, data = io.open(name), ""
if f then
data = f:read("*all")
f:close()
end
return data or ""
end
function utils.merger._self_save_(name, data)
if data ~= "" then
local f = io.open(name,'w')
if f then
f:write(data)
f:close()
end
end
end
function utils.merger._self_swap_(data,code)
if data ~= "" then
return (data:gsub(utils.merger.pattern, function(s)
return "\n\n" .. "-- "..utils.merger.m_begin .. "\n" .. code .. "\n" .. "-- "..utils.merger.m_end .. "\n\n"
end, 1))
else
return ""
end
end
function utils.merger._self_libs_(libs,list)
local result, f = { }, nil
if type(libs) == 'string' then libs = { libs } end
if type(list) == 'string' then list = { list } end
for _, lib in ipairs(libs) do
for _, pth in ipairs(list) do
local name = string.gsub(pth .. "/" .. lib,"\\","/")
f = io.open(name)
if f then
-- utils.report("merging library",name)
result[#result+1] = f:read("*all")
f:close()
list = { pth } -- speed up the search
break
else
-- utils.report("no library",name)
end
end
end
return table.concat(result, "\n\n")
end
function utils.merger.selfcreate(libs,list,target)
if target then
utils.merger._self_save_(
target,
utils.merger._self_swap_(
utils.merger._self_fake_(),
utils.merger._self_libs_(libs,list)
)
)
end
end
function utils.merger.selfmerge(name,libs,list,target)
utils.merger._self_save_(
target or name,
utils.merger._self_swap_(
utils.merger._self_load_(name),
utils.merger._self_libs_(libs,list)
)
)
end
function utils.merger.selfclean(name)
utils.merger._self_save_(
name,
utils.merger._self_swap_(
utils.merger._self_load_(name),
""
)
)
end
utils.lua.compile_strip = true
function utils.lua.compile(luafile, lucfile)
-- utils.report("compiling",luafile,"into",lucfile)
os.remove(lucfile)
local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
if utils.lua.compile_strip then
command = "-s " .. command
end
if os.spawn("texluac " .. command) == 0 then
return true
elseif os.spawn("luac " .. command) == 0 then
return true
else
return false
end
end
-- filename : luat-lib.lua
-- comment : companion to luat-lib.tex
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
if not versions then versions = { } end versions['luat-lib'] = 1.001
-- mostcode moved to the l-*.lua and other luat-*.lua files
-- os / io
os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
-- os.platform
-- mswin|bccwin|mingw|cygwin windows
-- darwin|rhapsody|nextstep macosx
-- netbsd|unix unix
-- linux linux
if not io.fileseparator then
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator, os.platform = "\\", ";", os.type or "windows"
else
io.fileseparator, io.pathseparator, os.platform = "/" , ":", os.type or "unix"
end
end
os.platform = os.platform or os.type or (io.pathseparator == ";" and "windows") or "unix"
-- arg normalization
--
-- for k,v in pairs(arg) do print(k,v) end
-- environment
if not environment then environment = { } end
environment.ownbin = environment.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
local ownpath = nil -- we could use a metatable here
function environment.ownpath()
if not ownpath then
for p in string.gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
local b = file.join(p,environment.ownbin)
if lfs.isfile(b..".exe") or lfs.isfile(b) then
ownpath = p
break
end
end
if not ownpath then ownpath = '.' end
end
return ownpath
end
if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
end
environment.arguments = { }
environment.files = { }
environment.sorted_argument_keys = nil
environment.platform = os.platform
function environment.initialize_arguments(arg)
environment.arguments = { }
environment.files = { }
environment.sorted_argument_keys = nil
for index, argument in pairs(arg) do
if index > 0 then
local flag, value = argument:match("^%-+(.+)=(.-)$")
if flag then
environment.arguments[flag] = string.unquote(value or "")
else
flag = argument:match("^%-+(.+)")
if flag then
environment.arguments[flag] = true
else
environment.files[#environment.files+1] = argument
end
end
end
end
environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
end
function environment.showarguments()
for k,v in pairs(environment.arguments) do
print(k .. " : " .. tostring(v))
end
if #environment.files > 0 then
print("files : " .. table.concat(environment.files, " "))
end
end
function environment.setargument(name,value)
environment.arguments[name] = value
end
function environment.argument(name)
if environment.arguments[name] then
return environment.arguments[name]
else
if not environment.sorted_argument_keys then
environment.sorted_argument_keys = { }
for _,v in pairs(table.sortedkeys(environment.arguments)) do
table.insert(environment.sorted_argument_keys, "^" .. v)
end
end
for _,v in pairs(environment.sorted_argument_keys) do
if name:find(v) then
return environment.arguments[v:sub(2,#v)]
end
end
end
return nil
end
function environment.split_arguments(separator) -- rather special, cut-off before separator
local done, before, after = false, { }, { }
for _,v in ipairs(environment.original_arguments) do
if not done and v == separator then
done = true
elseif done then
after[#after+1] = v
else
before[#before+1] = v
end
end
return before, after
end
function environment.reconstruct_commandline(arg)
if not arg then arg = environment.original_arguments end
local result = { }
for _,a in ipairs(arg) do -- ipairs 1 .. #n
local kk, vv = a:match("^(%-+.-)=(.+)$")
if kk and vv then
if vv:find(" ") then
result[#result+1] = kk .. "=" .. string.quote(vv)
else
result[#result+1] = a
end
elseif a:find(" ") then
result[#result+1] = string.quote(a)
else
result[#result+1] = a
end
end
return table.join(result," ")
end
if arg then
environment.initialize_arguments(arg)
environment.original_arguments = arg
arg = { } -- prevent duplicate handling
end
-- filename : luat-inp.lua
-- comment : companion to luat-lib.tex
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
-- This lib is multi-purpose and can be loaded again later on so that
-- additional functionality becomes available. We will split this
-- module in components when we're done with prototyping.
-- TODO: os.getenv -> os.env[]
-- TODO: instances.[hashes,cnffiles,configurations,522] -> ipairs (alles check, sneller)
-- TODO: check escaping in find etc, too much, too slow
-- This is the first code I wrote for LuaTeX, so it needs some cleanup.
-- To be considered: hash key lowercase, first entry in table filename
-- (any case), rest paths (so no need for optimization). Or maybe a
-- separate table that matches lowercase names to mixed case when
-- present. In that case the lower() cases can go away. I will do that
-- only when we run into problems with names ... well ... Iwona-Regular.
-- Beware, loading and saving is overloaded in luat-tmp!
if not versions then versions = { } end versions['luat-inp'] = 1.001
if not environment then environment = { } end
if not file then file = { } end
if environment.aleph_mode == nil then environment.aleph_mode = true end -- temp hack
if not input then input = { } end
if not input.suffixes then input.suffixes = { } end
if not input.formats then input.formats = { } end
if not input.aux then input.aux = { } end
if not input.suffixmap then input.suffixmap = { } end
if not input.locators then input.locators = { } end -- locate databases
if not input.hashers then input.hashers = { } end -- load databases
if not input.generators then input.generators = { } end -- generate databases
if not input.filters then input.filters = { } end -- conversion filters
local format = string.format
input.locators.notfound = { nil }
input.hashers.notfound = { nil }
input.generators.notfound = { nil }
input.cacheversion = '1.0.1'
input.banner = nil
input.verbose = false
input.debug = false
input.cnfname = 'texmf.cnf'
input.luaname = 'texmfcnf.lua'
input.lsrname = 'ls-R'
input.luasuffix = '.tma'
input.lucsuffix = '.tmc'
-- we use a cleaned up list / format=any is a wildcard, as is *name
input.formats['afm'] = 'AFMFONTS' input.suffixes['afm'] = { 'afm' }
input.formats['enc'] = 'ENCFONTS' input.suffixes['enc'] = { 'enc' }
input.formats['fmt'] = 'TEXFORMATS' input.suffixes['fmt'] = { 'fmt' }
input.formats['map'] = 'TEXFONTMAPS' input.suffixes['map'] = { 'map' }
input.formats['mp'] = 'MPINPUTS' input.suffixes['mp'] = { 'mp' }
input.formats['ocp'] = 'OCPINPUTS' input.suffixes['ocp'] = { 'ocp' }
input.formats['ofm'] = 'OFMFONTS' input.suffixes['ofm'] = { 'ofm', 'tfm' }
input.formats['otf'] = 'OPENTYPEFONTS' input.suffixes['otf'] = { 'otf' } -- 'ttf'
input.formats['opl'] = 'OPLFONTS' input.suffixes['opl'] = { 'opl' }
input.formats['otp'] = 'OTPINPUTS' input.suffixes['otp'] = { 'otp' }
input.formats['ovf'] = 'OVFFONTS' input.suffixes['ovf'] = { 'ovf', 'vf' }
input.formats['ovp'] = 'OVPFONTS' input.suffixes['ovp'] = { 'ovp' }
input.formats['tex'] = 'TEXINPUTS' input.suffixes['tex'] = { 'tex' }
input.formats['tfm'] = 'TFMFONTS' input.suffixes['tfm'] = { 'tfm' }
input.formats['ttf'] = 'TTFONTS' input.suffixes['ttf'] = { 'ttf', 'ttc' }
input.formats['pfb'] = 'T1FONTS' input.suffixes['pfb'] = { 'pfb', 'pfa' }
input.formats['vf'] = 'VFFONTS' input.suffixes['vf'] = { 'vf' }
input.formats['fea'] = 'FONTFEATURES' input.suffixes['fea'] = { 'fea' }
input.formats['cid'] = 'FONTCIDMAPS' input.suffixes['cid'] = { 'cid', 'cidmap' }
input.formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
input.suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
input.formats ['lua'] = 'LUAINPUTS' -- new
input.suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
-- here we catch a few new thingies (todo: add these paths to context.tmf)
--
-- FONTFEATURES = .;$TEXMF/fonts/fea//
-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
function input.checkconfigdata(instance) -- not yet ok, no time for debugging now
local function fix(varname,default)
local proname = varname .. "." .. instance.progname or "crap"
local p = instance.environment[proname]
local v = instance.environment[varname]
if not ((p and p ~= "") or (v and v ~= "")) then
instance.variables[varname] = default -- or environment?
end
end
fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS")
fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
end
-- backward compatible ones
input.alternatives = { }
input.alternatives['map files'] = 'map'
input.alternatives['enc files'] = 'enc'
input.alternatives['cid files'] = 'cid'
input.alternatives['fea files'] = 'fea'
input.alternatives['opentype fonts'] = 'otf'
input.alternatives['truetype fonts'] = 'ttf'
input.alternatives['truetype collections'] = 'ttc'
input.alternatives['type1 fonts'] = 'pfb'
-- obscure ones
input.formats ['misc fonts'] = ''
input.suffixes['misc fonts'] = { }
input.formats ['sfd'] = 'SFDFONTS'
input.suffixes ['sfd'] = { 'sfd' }
input.alternatives['subfont definition files'] = 'sfd'
function input.reset()
local instance = { }
instance.rootpath = ''
instance.treepath = ''
instance.progname = environment.progname or 'context'
instance.engine = environment.engine or 'luatex'
instance.format = ''
instance.environment = { }
instance.variables = { }
instance.expansions = { }
instance.files = { }
instance.remap = { }
instance.configuration = { }
instance.setup = { }
instance.order = { }
instance.found = { }
instance.foundintrees = { }
instance.kpsevars = { }
instance.hashes = { }
instance.cnffiles = { }
instance.luafiles = { }
instance.lists = { }
instance.remember = true
instance.diskcache = true
instance.renewcache = false
instance.scandisk = true
instance.cachepath = nil
instance.loaderror = false
instance.smallcache = false
instance.savelists = true
instance.cleanuppaths = true
instance.allresults = false
instance.pattern = nil -- lists
instance.kpseonly = false -- lists
instance.cachefile = 'tmftools'
instance.loadtime = 0
instance.starttime = 0
instance.stoptime = 0
instance.validfile = function(path,name) return true end
instance.data = { } -- only for loading
instance.force_suffixes = true
instance.dummy_path_expr = "^!*unset/*$"
instance.fakepaths = { }
instance.lsrmode = false
if os.env then
-- store once, freeze and faster
for k,v in pairs(os.env) do
instance.environment[k] = input.bare_variable(v)
end
else
-- we will access os.env frequently
for k,v in pairs({'HOME','TEXMF','TEXMFCNF'}) do
local e = os.getenv(v)
if e then
-- input.report("setting",v,"to",input.bare_variable(e))
instance.environment[v] = input.bare_variable(e)
end
end
end
-- cross referencing
for k, v in pairs(input.suffixes) do
for _, vv in pairs(v) do
if vv then
input.suffixmap[vv] = k
end
end
end
return instance
end
function input.reset_hashes(instance)
instance.lists = { }
instance.found = { }
end
function input.bare_variable(str) -- assumes str is a string
-- return string.gsub(string.gsub(string.gsub(str,"%s+$",""),'^"(.+)"$',"%1"),"^'(.+)'$","%1")
return (str:gsub("\s*([\"\']?)(.+)%1\s*", "%2"))
end
if texio then
input.log = texio.write_nl
else
input.log = print
end
function input.simple_logger(kind, name)
if name and name ~= "" then
if input.banner then
input.log(input.banner..kind..": "..name)
else
input.log("<<"..kind..": "..name..">>")
end
else
if input.banner then
input.log(input.banner..kind..": no name")
else
input.log("<<"..kind..": no name>>")
end
end
end
function input.dummy_logger()
end
function input.settrace(n)
input.trace = tonumber(n or 0)
if input.trace > 0 then
input.logger = input.simple_logger
input.verbose = true
else
input.logger = function() end
end
end
function input.report(...) -- inefficient
if input.verbose then
if input.banner then
input.log(input.banner .. table.concat({...},' '))
elseif input.logmode() == 'xml' then
input.log(""..table.concat({...},' ').."")
else
input.log("<<"..table.concat({...},' ')..">>")
end
end
end
function input.reportlines(str)
if type(str) == "string" then
str = str:split("\n")
end
for _,v in pairs(str) do input.report(v) end
end
input.settrace(tonumber(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0))
-- These functions can be used to test the performance, especially
-- loading the database files.
do
local clock = os.gettimeofday or os.clock
function input.starttiming(instance)
if instance then
instance.starttime = clock()
if not instance.loadtime then
instance.loadtime = 0
end
end
end
function input.stoptiming(instance, report)
if instance then
local starttime = instance.starttime
if starttime then
local stoptime = clock()
local loadtime = stoptime - starttime
instance.stoptime = stoptime
instance.loadtime = instance.loadtime + loadtime
if report then
input.report('load time', format("%0.3f",loadtime))
end
return loadtime
end
end
return 0
end
end
function input.elapsedtime(instance)
return format("%0.3f",(instance and instance.loadtime) or 0)
end
function input.report_loadtime(instance)
if instance then
input.report('total load time', input.elapsedtime(instance))
end
end
input.loadtime = input.elapsedtime
function input.env(instance,key)
return instance.environment[key] or input.osenv(instance,key)
end
function input.osenv(instance,key)
local ie = instance.environment
local value = ie[key]
if value == nil then
-- local e = os.getenv(key)
local e = os.env[key]
if e == nil then
-- value = "" -- false
else
value = input.bare_variable(e)
end
ie[key] = value
end
return value or ""
end
-- we follow a rather traditional approach:
--
-- (1) texmf.cnf given in TEXMFCNF
-- (2) texmf.cnf searched in TEXMF/web2c
--
-- for the moment we don't expect a configuration file in a zip
function input.identify_cnf(instance)
-- we no longer support treepath and rootpath (was handy for testing);
-- also we now follow the stupid route: if not set then just assume *one*
-- cnf file under texmf (i.e. distribution)
if #instance.cnffiles == 0 then
if input.env(instance,'TEXMFCNF') == "" then
local ownpath = environment.ownpath() or "."
if ownpath then
-- beware, this is tricky on my own system because at that location I do have
-- the raw tree that ends up in the zip; i.e. I cannot test this kind of mess
local function locate(filename,list)
local ownroot = input.normalize_name(file.join(ownpath,"../.."))
if not lfs.isdir(file.join(ownroot,"texmf")) then
ownroot = input.normalize_name(file.join(ownpath,".."))
if not lfs.isdir(file.join(ownroot,"texmf")) then
input.verbose = true
input.report("error", "unable to identify cnf file")
return
end
end
local texmfcnf = file.join(ownroot,"texmf-local/web2c",filename) -- for minimals and myself
if not lfs.isfile(texmfcnf) then
texmfcnf = file.join(ownroot,"texmf/web2c",filename)
if not lfs.isfile(texmfcnf) then
input.verbose = true
input.report("error", "unable to locate",filename)
return
end
end
table.insert(list,texmfcnf)
local ie = instance.environment
if not ie['SELFAUTOPARENT'] then ie['SELFAUTOPARENT'] = ownroot end
if not ie['TEXMFCNF'] then ie['TEXMFCNF'] = file.dirname(texmfcnf) end
end
locate(input.luaname,instance.luafiles)
locate(input.cnfname,instance.cnffiles)
if #instance.luafiles == 0 and instance.cnffiles == 0 then
input.verbose = true
input.report("error", "unable to locate",filename)
os.exit()
end
-- here we also assume then TEXMF is set in the distribution, if this trickery is
-- used in the minimals, then users who don't use setuptex are on their own with
-- regards to extra trees
else
input.verbose = true
input.report("error", "unable to identify own path")
os.exit()
end
else
local t = input.split_path(input.env(instance,'TEXMFCNF'))
t = input.aux.expanded_path(instance,t)
input.aux.expand_vars(instance,t)
local function locate(filename,list)
for _,v in ipairs(t) do
local texmfcnf = input.normalize_name(file.join(v,filename))
if lfs.isfile(texmfcnf) then
table.insert(list,texmfcnf)
end
end
end
locate(input.luaname,instance.luafiles)
locate(input.cnfname,instance.cnffiles)
end
end
end
function input.load_cnf(instance)
local function loadoldconfigdata()
for _, fname in ipairs(instance.cnffiles) do
input.aux.load_cnf(instance,fname)
end
end
-- instance.cnffiles contain complete names now !
if #instance.cnffiles == 0 then
input.report("no cnf files found (TEXMFCNF may not be set/known)")
else
instance.rootpath = instance.cnffiles[1]
for k,fname in ipairs(instance.cnffiles) do
instance.cnffiles[k] = input.normalize_name(fname:gsub("\\",'/'))
end
for i=1,3 do
instance.rootpath = file.dirname(instance.rootpath)
end
instance.rootpath = input.normalize_name(instance.rootpath)
instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
if instance.lsrmode then
loadoldconfigdata()
elseif instance.diskcache and not instance.renewcache then
input.loadoldconfig(instance,instance.cnffiles)
if instance.loaderror then
loadoldconfigdata()
input.saveoldconfig(instance)
end
else
loadoldconfigdata()
if instance.renewcache then
input.saveoldconfig(instance)
end
end
input.aux.collapse_cnf_data(instance)
end
input.checkconfigdata(instance)
end
function input.load_lua(instance)
if #instance.luafiles == 0 then
-- yet harmless
else
instance.rootpath = instance.luafiles[1]
for k,fname in ipairs(instance.luafiles) do
instance.luafiles[k] = input.normalize_name(fname:gsub("\\",'/'))
end
for i=1,3 do
instance.rootpath = file.dirname(instance.rootpath)
end
instance.rootpath = input.normalize_name(instance.rootpath)
instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
input.loadnewconfig(instance)
input.aux.collapse_cnf_data(instance)
end
input.checkconfigdata(instance)
end
function input.aux.collapse_cnf_data(instance) -- potential optmization: pass start index (setup and configuration are shared)
for _,c in ipairs(instance.order) do
for k,v in pairs(c) do
if not instance.variables[k] then
if instance.environment[k] then
instance.variables[k] = instance.environment[k]
else
instance.kpsevars[k] = true
instance.variables[k] = input.bare_variable(v)
end
end
end
end
end
function input.aux.load_cnf(instance,fname)
fname = input.clean_path(fname)
local lname = fname:gsub("%.%a+$",input.luasuffix)
local f = io.open(lname)
if f then -- this will go
f:close()
local dname = file.dirname(fname)
if not instance.configuration[dname] then
input.aux.load_configuration(instance,dname,lname)
instance.order[#instance.order+1] = instance.configuration[dname]
end
else
f = io.open(fname)
if f then
input.report("loading", fname)
local line, data, n, k, v
local dname = file.dirname(fname)
if not instance.configuration[dname] then
instance.configuration[dname] = { }
instance.order[#instance.order+1] = instance.configuration[dname]
end
local data = instance.configuration[dname]
while true do
local line, n = f:read(), 0
if line then
while true do -- join lines
line, n = line:gsub("\\%s*$", "")
if n > 0 then
line = line .. f:read()
else
break
end
end
if not line:find("^[%%#]") then
local k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
if k and v and not data[k] then
data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
instance.kpsevars[k] = true
end
end
else
break
end
end
f:close()
else
input.report("skipping", fname)
end
end
end
-- database loading
function input.load_hash(instance)
input.locatelists(instance)
if instance.lsrmode then
input.loadlists(instance)
elseif instance.diskcache and not instance.renewcache then
input.loadfiles(instance)
if instance.loaderror then
input.loadlists(instance)
input.savefiles(instance)
end
else
input.loadlists(instance)
if instance.renewcache then
input.savefiles(instance)
end
end
end
function input.aux.append_hash(instance,type,tag,name)
input.logger("= hash append",tag)
table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
end
function input.aux.prepend_hash(instance,type,tag,name)
input.logger("= hash prepend",tag)
table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
end
function input.aux.extend_texmf_var(instance,specification) -- crap
if instance.environment['TEXMF'] then
input.report("extending environment variable TEXMF with", specification)
instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
return "{" .. specification .. ","
end)
elseif instance.variables['TEXMF'] then
input.report("extending configuration variable TEXMF with", specification)
instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
return "{" .. specification .. ","
end)
else
input.report("setting configuration variable TEXMF to", specification)
instance.variables['TEXMF'] = "{" .. specification .. "}"
end
if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
input.report("adding {} to complex TEXMF variable, best do that yourself")
instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
end
input.expand_variables(instance)
input.reset_hashes(instance)
end
-- locators
function input.locatelists(instance)
for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
path = file.collapse_path(path)
input.report("locating list of",path)
input.locatedatabase(instance,input.normalize_name(path))
end
end
function input.locatedatabase(instance,specification)
return input.methodhandler('locators', instance, specification)
end
function input.locators.tex(instance,specification)
if specification and specification ~= '' and lfs.isdir(specification) then
input.logger('! tex locator', specification..' found')
input.aux.append_hash(instance,'file',specification,filename)
else
input.logger('? tex locator', specification..' not found')
end
end
-- hashers
function input.hashdatabase(instance,tag,name)
return input.methodhandler('hashers',instance,tag,name)
end
function input.loadfiles(instance)
instance.loaderror = false
instance.files = { }
if not instance.renewcache then
for _, hash in ipairs(instance.hashes) do
input.hashdatabase(instance,hash.tag,hash.name)
if instance.loaderror then break end
end
end
end
function input.hashers.tex(instance,tag,name)
input.aux.load_files(instance,tag)
end
-- generators:
function input.loadlists(instance)
for _, hash in ipairs(instance.hashes) do
input.generatedatabase(instance,hash.tag)
end
end
function input.generatedatabase(instance,specification)
return input.methodhandler('generators', instance, specification)
end
do
local weird = lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
function input.generators.tex(instance,specification)
local tag = specification
if not instance.lsrmode and lfs and lfs.dir then
input.report("scanning path",specification)
instance.files[tag] = { }
local files = instance.files[tag]
local n, m, r = 0, 0, 0
local spec = specification .. '/'
local attributes = lfs.attributes
local directory = lfs.dir
local small = instance.smallcache
local function action(path)
local mode, full
if path then
full = spec .. path .. '/'
else
full = spec
end
for name in directory(full) do
if name:find("^%.") then
-- skip
-- elseif name:find("[%~%`%!%#%$%%%^%&%*%(%)%=%{%}%[%]%:%;\"\'%|%<%>%,%?\n\r\t]") then -- too much escaped
elseif weird:match(name) then
-- texio.write_nl("skipping " .. name)
-- skip
else
mode = attributes(full..name,'mode')
if mode == "directory" then
m = m + 1
if path then
action(path..'/'..name)
else
action(name)
end
elseif path and mode == 'file' then
n = n + 1
local f = files[name]
if f then
if not small then
if type(f) == 'string' then
files[name] = { f, path }
else
f[#f+1] = path
end
end
else
files[name] = path
local lower = name:lower()
if name ~= lower then
files["remap:"..lower] = name
r = r + 1
end
end
end
end
end
end
action()
input.report(format("%s files found on %s directories with %s uppercase remappings",n,m,r))
else
local fullname = file.join(specification,input.lsrname)
local path = '.'
local f = io.open(fullname)
if f then
instance.files[tag] = { }
local files = instance.files[tag]
local small = instance.smallcache
input.report("loading lsr file",fullname)
-- for line in f:lines() do -- much slower then the next one
for line in (f:read("*a")):gmatch("(.-)\n") do
if line:find("^[%a%d]") then
local fl = files[line]
if fl then
if not small then
if type(fl) == 'string' then
files[line] = { fl, path } -- table
else
fl[#fl+1] = path
end
end
else
files[line] = path -- string
local lower = line:lower()
if line ~= lower then
files["remap:"..lower] = line
end
end
else
path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
end
end
f:close()
end
end
end
end
-- savers, todo
function input.savefiles(instance)
input.aux.save_data(instance, 'files', function(k,v)
return instance.validfile(k,v) -- path, name
end)
end
-- A config (optionally) has the paths split in tables. Internally
-- we join them and split them after the expansion has taken place. This
-- is more convenient.
function input.splitconfig(instance)
for i,c in ipairs(instance) do
for k,v in pairs(c) do
if type(v) == 'string' then
local t = file.split_path(v)
if #t > 1 then
c[k] = t
end
end
end
end
end
function input.joinconfig(instance)
for i,c in ipairs(instance.order) do
for k,v in pairs(c) do
if type(v) == 'table' then
c[k] = file.join_path(v)
end
end
end
end
function input.split_path(str)
if type(str) == 'table' then
return str
else
return file.split_path(str)
end
end
function input.join_path(str)
if type(str) == 'table' then
return file.join_path(str)
else
return str
end
end
function input.splitexpansions(instance)
for k,v in pairs(instance.expansions) do
local t, h = { }, { }
for _,vv in pairs(file.split_path(v)) do
if vv ~= "" and not h[vv] then
t[#t+1] = vv
h[vv] = true
end
end
if #t > 1 then
instance.expansions[k] = t
else
instance.expansions[k] = t[1]
end
end
end
-- end of split/join code
function input.saveoldconfig(instance)
input.splitconfig(instance)
input.aux.save_data(instance, 'configuration', nil)
input.joinconfig(instance)
end
input.configbanner = [[
-- This is a Luatex configuration file created by 'luatools.lua' or
-- 'luatex.exe' directly. For comment, suggestions and questions you can
-- contact the ConTeXt Development Team. This configuration file is
-- not copyrighted. [HH & TH]
]]
function input.serialize(files)
-- This version is somewhat optimized for the kind of
-- tables that we deal with, so it's much faster than
-- the generic serializer. This makes sense because
-- luatools and mtxtools are called frequently. Okay,
-- we pay a small price for properly tabbed tables.
local t = { }
local concat = table.concat
local sorted = table.sortedkeys
local function dump(k,v,m)
if type(v) == 'string' then
return m .. "['" .. k .. "']='" .. v .. "',"
elseif #v == 1 then
return m .. "['" .. k .. "']='" .. v[1] .. "',"
else
return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
end
end
t[#t+1] = "return {"
if instance.sortdata then
for _, k in pairs(sorted(files)) do
local fk = files[k]
if type(fk) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
for _, kk in pairs(sorted(fk)) do
t[#t+1] = dump(kk,fk[kk],"\t\t")
end
t[#t+1] = "\t},"
else
t[#t+1] = dump(k,fk,"\t")
end
end
else
for k, v in pairs(files) do
if type(v) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
for kk,vv in pairs(v) do
t[#t+1] = dump(kk,vv,"\t\t")
end
t[#t+1] = "\t},"
else
t[#t+1] = dump(k,v,"\t")
end
end
end
t[#t+1] = "}"
return concat(t,"\n")
end
if not texmf then texmf = {} end -- no longer needed, at least not here
function input.aux.save_data(instance, dataname, check, makename) -- untested without cache overload
for cachename, files in pairs(instance[dataname]) do
local name = (makename or file.join)(cachename,dataname)
local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
input.report("preparing " .. dataname .. " for", luaname)
for k, v in pairs(files) do
if not check or check(v,k) then -- path, name
if type(v) == "table" and #v == 1 then
files[k] = v[1]
end
else
files[k] = nil -- false
end
end
local data = {
type = dataname,
root = cachename,
version = input.cacheversion,
date = os.date("%Y-%m-%d"),
time = os.date("%H:%M:%S"),
content = files,
}
local f = io.open(luaname,'w')
if f then
input.report("saving " .. dataname .. " in", luaname)
f:write(input.serialize(data))
f:close()
input.report("compiling " .. dataname .. " to", lucname)
if not utils.lua.compile(luaname,lucname) then
input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
os.remove(lucname)
end
else
input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
end
end
end
function input.aux.load_data(instance,pathname,dataname,filename,makename) -- untested without cache overload
filename = ((not filename or (filename == "")) and dataname) or filename
filename = (makename and makename(dataname,filename)) or file.join(pathname,filename)
local blob = loadfile(filename .. input.lucsuffix) or loadfile(filename .. input.luasuffix)
if blob then
local data = blob()
if data and data.content and data.type == dataname and data.version == input.cacheversion then
input.report("loading",dataname,"for",pathname,"from",filename)
instance[dataname][pathname] = data.content
else
input.report("skipping",dataname,"for",pathname,"from",filename)
instance[dataname][pathname] = { }
instance.loaderror = true
end
else
input.report("skipping",dataname,"for",pathname,"from",filename)
end
end
-- some day i'll use the nested approach, but not yet (actually we even drop
-- engine/progname support since we have only luatex now)
--
-- first texmfcnf.lua files are located, next the cached texmf.cnf files
--
-- return {
-- TEXMFBOGUS = 'effe checken of dit werkt',
-- }
function input.aux.load_texmfcnf(instance,dataname,pathname)
local filename = file.join(pathname,input.luaname)
local blob = loadfile(filename)
if blob then
local data = blob()
if data then
input.report("loading","configuration file",filename)
if true then
-- flatten to variable.progname
local t = { }
for k, v in pairs(data) do -- v = progname
if type(v) == "string" then
t[k] = v
else
for kk, vv in pairs(v) do -- vv = variable
if type(vv) == "string" then
t[vv.."."..v] = kk
end
end
end
end
instance[dataname][pathname] = t
else
instance[dataname][pathname] = data
end
else
input.report("skipping","configuration file",filename)
instance[dataname][pathname] = { }
instance.loaderror = true
end
else
input.report("skipping","configuration file",filename)
end
end
function input.aux.load_configuration(instance,dname,lname)
input.aux.load_data(instance,dname,'configuration',lname and file.basename(lname))
end
function input.aux.load_files(instance,tag)
input.aux.load_data(instance,tag,'files')
end
function input.resetconfig(instance)
instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
end
function input.loadnewconfig(instance)
for _, cnf in ipairs(instance.luafiles) do
local dname = file.dirname(cnf)
input.aux.load_texmfcnf(instance,'setup',dname)
instance.order[#instance.order+1] = instance.setup[dname]
if instance.loaderror then break end
end
end
function input.loadoldconfig(instance)
if not instance.renewcache then
for _, cnf in ipairs(instance.cnffiles) do
local dname = file.dirname(cnf)
input.aux.load_configuration(instance,dname)
instance.order[#instance.order+1] = instance.configuration[dname]
if instance.loaderror then break end
end
end
input.joinconfig(instance)
end
function input.expand_variables(instance)
instance.expansions = { }
--~ instance.environment['SELFAUTOPARENT'] = instance.environment['SELFAUTOPARENT'] or instance.rootpath
if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
if instance.progname ~= "" then instance.environment['progname'] = instance.progname end
for k,v in pairs(instance.environment) do
local a, b = k:match("^(%a+)%_(.*)%s*$")
if a and b then
instance.expansions[a..'.'..b] = v
else
instance.expansions[k] = v
end
end
for k,v in pairs(instance.environment) do -- move environment to expansions
if not instance.expansions[k] then instance.expansions[k] = v end
end
for k,v in pairs(instance.variables) do -- move variables to expansions
if not instance.expansions[k] then instance.expansions[k] = v end
end
while true do
local busy = false
for k,v in pairs(instance.expansions) do
local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
busy = true
return instance.expansions[a] or input.env(instance,a)
end)
local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
busy = true
return instance.expansions[a] or input.env(instance,a)
end)
if n > 0 or m > 0 then
instance.expansions[k]= s
end
end
if not busy then break end
end
for k,v in pairs(instance.expansions) do
instance.expansions[k] = v:gsub("\\", '/')
end
end
function input.aux.expand_vars(instance,lst) -- simple vars
for k,v in pairs(lst) do
lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
return instance.variables[a] or input.env(instance,a)
end)
end
end
function input.aux.expanded_var(instance,var) -- simple vars
return var:gsub("%$([%a%d%_%-]+)", function(a)
return instance.variables[a] or input.env(instance,a)
end)
end
function input.aux.entry(instance,entries,name)
if name and (name ~= "") then
name = name:gsub('%$','')
local result = entries[name..'.'..instance.progname] or entries[name]
if result then
return result
else
result = input.env(instance,name)
if result then
instance.variables[name] = result
input.expand_variables(instance)
return instance.expansions[name] or ""
end
end
end
return ""
end
function input.variable(instance,name)
return input.aux.entry(instance,instance.variables,name)
end
function input.expansion(instance,name)
return input.aux.entry(instance,instance.expansions,name)
end
function input.aux.is_entry(instance,entries,name)
if name and name ~= "" then
name = name:gsub('%$','')
return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
else
return false
end
end
function input.is_variable(instance,name)
return input.aux.is_entry(instance,instance.variables,name)
end
function input.is_expansion(instance,name)
return input.aux.is_entry(instance,instance.expansions,name)
end
function input.simplified_list(str)
if type(str) == 'table' then
return str -- troubles ; ipv , in texmf
elseif str == '' then
return { }
else
local t = { }
for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
end
return t
end
end
function input.unexpanded_path_list(instance,str)
local pth = input.variable(instance,str)
local lst = input.split_path(pth)
return input.aux.expanded_path(instance,lst)
end
function input.unexpanded_path(instance,str)
return file.join_path(input.unexpanded_path_list(instance,str))
end
do
local done = { }
function input.reset_extra_path(instance)
local ep = instance.extra_paths
if not ep then
ep, done = { }, { }
instance.extra_paths = ep
elseif #ep > 0 then
instance.lists, done = { }, { }
end
end
function input.register_extra_path(instance,paths,subpaths)
local ep = instance.extra_paths or { }
local n = #ep
if paths and paths ~= "" then
if subpaths and subpaths ~= "" then
for p in paths:gmatch("[^,]+") do
-- we gmatch each step again, not that fast, but used seldom
for s in subpaths:gmatch("[^,]+") do
local ps = p .. "/" .. s
if not done[ps] then
ep[#ep+1] = input.clean_path(ps)
done[ps] = true
end
end
end
else
for p in paths:gmatch("[^,]+") do
if not done[p] then
ep[#ep+1] = input.clean_path(p)
done[p] = true
end
end
end
elseif subpaths and subpaths ~= "" then
for i=1,n do
-- we gmatch each step again, not that fast, but used seldom
for s in subpaths:gmatch("[^,]+") do
local ps = ep[i] .. "/" .. s
if not done[ps] then
ep[#ep+1] = input.clean_path(ps)
done[ps] = true
end
end
end
end
if #ep > 0 then
instance.extra_paths = ep -- register paths
end
if #ep > n then
instance.lists = { } -- erase the cache
end
end
end
function input.expanded_path_list(instance,str)
local function made_list(list)
local ep = instance.extra_paths
if not ep or #ep == 0 then
return list
else
local done, new = { }, { }
-- honour . .. ../.. but only when at the start
for k, v in ipairs(list) do
if not done[v] then
if v:find("^[%.%/]$") then
done[v] = true
new[#new+1] = v
else
break
end
end
end
-- first the extra paths
for k, v in ipairs(ep) do
if not done[v] then
done[v] = true
new[#new+1] = v
end
end
-- next the formal paths
for k, v in ipairs(list) do
if not done[v] then
done[v] = true
new[#new+1] = v
end
end
return new
end
end
if not str then
return ep or { }
elseif instance.savelists then
-- engine+progname hash
str = str:gsub("%$","")
if not instance.lists[str] then -- cached
local lst = made_list(input.split_path(input.expansion(instance,str)))
instance.lists[str] = input.aux.expanded_path(instance,lst)
end
return instance.lists[str]
else
local lst = input.split_path(input.expansion(instance,str))
return made_list(input.aux.expanded_path(instance,lst))
end
end
function input.expand_path(instance,str)
return file.join_path(input.expanded_path_list(instance,str))
end
--~ function input.first_writable_path(instance,name)
--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
--~ return v
--~ end
--~ end
--~ return "."
--~ end
function input.expanded_path_list_from_var(instance,str) -- brrr
local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
if tmp ~= "" then
return input.expanded_path_list(instance,str)
else
return input.expanded_path_list(instance,tmp)
end
end
function input.expand_path_from_var(instance,str)
return file.join_path(input.expanded_path_list_from_var(instance,str))
end
function input.format_of_var(str)
return input.formats[str] or input.formats[input.alternatives[str]] or ''
end
function input.format_of_suffix(str)
return input.suffixmap[file.extname(str)] or 'tex'
end
function input.variable_of_format(str)
return input.formats[str] or input.formats[input.alternatives[str]] or ''
end
function input.var_of_format_or_suffix(str)
local v = input.formats[str]
if v then
return v
end
v = input.formats[input.alternatives[str]]
if v then
return v
end
v = input.suffixmap[file.extname(str)]
if v then
return input.formats[isf]
end
return ''
end
function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
local ori = input.variable(instance,str)
local pth = input.aux.expanded_path(instance,input.split_path(ori))
return file.join_path(pth)
end
-- {a,b,c,d}
-- a,b,c/{p,q,r},d
-- a,b,c/{p,q,r}/d/{x,y,z}//
-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
-- a{b,c}{d,e}f
-- {a,b,c,d}
-- {a,b,c/{p,q,r},d}
-- {a,b,c/{p,q,r}/d/{x,y,z}//}
-- {a,b,c/{p,q/{x,y,z}},d/{p,q,r}}
-- {a,b,c/{p,q/{x,y,z},w}v,d/{p,q,r}}
-- this one is better and faster, but it took me a while to realize
-- that this kind of replacement is cleaner than messy parsing and
-- fuzzy concatenating we can probably gain a bit with selectively
-- applying lpeg, but experiments with lpeg parsing this proved not to
-- work that well; the parsing is ok, but dealing with the resulting
-- table is a pain because we need to work inside-out recursively
-- get rid of piecewise here, just a gmatch is ok
function input.aux.splitpathexpr(str, t, validate)
-- no need for optimization, only called a few times, we can use lpeg for the sub
t = t or { }
local concat = table.concat
while true do
local done = false
while true do
local ok = false
str = str:gsub("([^{},]+){([^{}]-)}", function(a,b)
local t = { }
b:piecewise(",", function(s) t[#t+1] = a .. s end)
ok, done = true, true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}([^{},]+)", function(a,b)
local t = { }
a:piecewise(",", function(s) t[#t+1] = s .. b end)
ok, done = true, true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("([,{]){([^{}]+)}([,}])", function(a,b,c)
ok, done = true, true
return a .. b .. c
end)
if not ok then break end
end
if not done then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}{([^{}]-)}", function(a,b)
local t = { }
a:piecewise(",", function(sa)
b:piecewise(",", function(sb)
t[#t+1] = sa .. sb
end)
end)
ok = true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}", function(a)
ok = true
return a
end)
if not ok then break end
end
if validate then
str:piecewise(",", function(s)
s = validate(s)
if s then t[#t+1] = s end
end)
else
str:piecewise(",", function(s)
t[#t+1] = s
end)
end
return t
end
function input.aux.expanded_path(instance,pathlist) -- maybe not a list, just a path
-- a previous version fed back into pathlist
local newlist, ok = { }, false
for _,v in ipairs(pathlist) do
if v:find("[{}]") then
ok = true
break
end
end
if ok then
for _, v in ipairs(pathlist) do
input.aux.splitpathexpr(v, newlist, function(s)
s = file.collapse_path(s)
return s ~= "" and not s:find(instance.dummy_path_expr) and s
end)
end
else
for _,v in ipairs(pathlist) do
for vv in string.gmatch(v..',',"(.-),") do
vv = file.collapse_path(v)
if vv ~= "" then newlist[#newlist+1] = vv end
end
end
end
return newlist
end
input.is_readable = { }
function input.aux.is_readable(readable, name)
if input.trace > 2 then
if readable then
input.logger("+ readable", name)
else
input.logger("- readable", name)
end
end
return readable
end
function input.is_readable.file(name)
-- return input.aux.is_readable(file.is_readable(name), name)
return input.aux.is_readable(input.aux.is_file(name), name)
end
input.is_readable.tex = input.is_readable.file
-- name
-- name/name
function input.aux.collect_files(instance,names)
local filelist = { }
for _, fname in pairs(names) do
if fname then
if input.trace > 2 then
input.logger("? blobpath asked",fname)
end
local bname = file.basename(fname)
local dname = file.dirname(fname)
if dname == "" or dname:find("^%.") then
dname = false
else
dname = "/" .. dname .. "$"
end
for _, hash in ipairs(instance.hashes) do
local blobpath = hash.tag
local files = blobpath and instance.files[blobpath]
if files then
if input.trace > 2 then
input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
end
local blobfile = files[bname]
if not blobfile then
local rname = "remap:"..bname
blobfile = files[rname]
if blobfile then
bname = files[rname]
blobfile = files[bname]
end
end
if blobfile then
if type(blobfile) == 'string' then
if not dname or blobfile:find(dname) then
filelist[#filelist+1] = {
hash.type,
file.join(blobpath,blobfile,bname), -- search
input.concatinators[hash.type](blobpath,blobfile,bname) -- result
}
end
else
for _, vv in pairs(blobfile) do
if not dname or vv:find(dname) then
filelist[#filelist+1] = {
hash.type,
file.join(blobpath,vv,bname), -- search
input.concatinators[hash.type](blobpath,vv,bname) -- result
}
end
end
end
end
elseif input.trace > 1 then
input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
end
end
end
end
if #filelist > 0 then
return filelist
else
return nil
end
end
function input.suffix_of_format(str)
if input.suffixes[str] then
return input.suffixes[str][1]
else
return ""
end
end
function input.suffixes_of_format(str)
if input.suffixes[str] then
return input.suffixes[str]
else
return {}
end
end
do
-- called about 700 times for an empty doc (font initializations etc)
-- i need to weed the font files for redundant calls
local letter = lpeg.R("az","AZ")
local separator = lpeg.P("://")
local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator
local rootbased = lpeg.P("/") + letter*lpeg.P(":")
-- ./name ../name /name c: ://
function input.aux.qualified_path(filename)
return qualified:match(filename)
end
function input.aux.rootbased_path(filename)
return rootbased:match(filename)
end
function input.normalize_name(original)
return original
end
input.normalize_name = file.collapse_path
end
function input.aux.register_in_trees(instance,name)
if not name:find("^%.") then
instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
end
end
-- split the next one up, better for jit
function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
local result = { }
local stamp = nil
filename = input.normalize_name(filename) -- elsewhere
filename = file.collapse_path(filename:gsub("\\","/")) -- elsewhere
-- speed up / beware: format problem
if instance.remember then
stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
if instance.found[stamp] then
input.logger('! remembered', filename)
return instance.found[stamp]
end
end
if filename:find('%*') then
input.logger('! wildcard', filename)
result = input.find_wildcard_files(instance,filename)
elseif input.aux.qualified_path(filename) then
if input.is_readable.file(filename) then
input.logger('! qualified', filename)
result = { filename }
else
local forcedname, ok = "", false
if file.extname(filename) == "" then
if instance.format == "" then
forcedname = filename .. ".tex"
if input.is_readable.file(forcedname) then
input.logger('! no suffix, forcing standard filetype tex')
result, ok = { forcedname }, true
end
else
for _, s in pairs(input.suffixes_of_format(instance.format)) do
forcedname = filename .. "." .. s
if input.is_readable.file(forcedname) then
input.logger('! no suffix, forcing format filetype', s)
result, ok = { forcedname }, true
break
end
end
end
end
if not ok then
input.logger('? qualified', filename)
end
end
else
-- search spec
local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
if ext == "" then
if not instance.force_suffixes then
wantedfiles[#wantedfiles+1] = filename
end
else
wantedfiles[#wantedfiles+1] = filename
end
if instance.format == "" then
if ext == "" then
local forcedname = filename .. '.tex'
wantedfiles[#wantedfiles+1] = forcedname
filetype = input.format_of_suffix(forcedname)
input.logger('! forcing filetype',filetype)
else
filetype = input.format_of_suffix(filename)
input.logger('! using suffix based filetype',filetype)
end
else
if ext == "" then
for _, s in pairs(input.suffixes_of_format(instance.format)) do
wantedfiles[#wantedfiles+1] = filename .. "." .. s
end
end
filetype = instance.format
input.logger('! using given filetype',filetype)
end
local typespec = input.variable_of_format(filetype)
local pathlist = input.expanded_path_list(instance,typespec)
if not pathlist or #pathlist == 0 then
-- no pathlist, access check only / todo == wildcard
if input.trace > 2 then
input.logger('? filename',filename)
input.logger('? filetype',filetype or '?')
input.logger('? wanted files',table.concat(wantedfiles," | "))
end
for _, fname in pairs(wantedfiles) do
if fname and input.is_readable.file(fname) then
filename, done = fname, true
result[#result+1] = file.join('.',fname)
break
end
end
-- this is actually 'other text files' or 'any' or 'whatever'
local filelist = input.aux.collect_files(instance,wantedfiles)
local fl = filelist and filelist[1]
if fl then
filename = fl[3]
result[#result+1] = filename
done = true
end
else
-- list search
local filelist = input.aux.collect_files(instance,wantedfiles)
local doscan, recurse
if input.trace > 2 then
input.logger('? filename',filename)
-- if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
-- if filelist then input.logger('? file list',table.concat(filelist," | ")) end
end
-- a bit messy ... esp the doscan setting here
for _, path in pairs(pathlist) do
if path:find("^!!") then doscan = false else doscan = true end
if path:find("//$") then recurse = true else recurse = false end
local pathname = path:gsub("^!+", '')
done = false
-- using file list
if filelist and not (done and not instance.allresults) and recurse then
-- compare list entries with permitted pattern
pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
pathname = pathname:gsub("//", '/.-/') -- not ok for /// but harmless
local expr = "^" .. pathname
-- input.debug('?',expr)
for _, fl in ipairs(filelist) do
local f = fl[2]
if f:find(expr) then
-- input.debug('T',' '..f)
if input.trace > 2 then
input.logger('= found in hash',f)
end
--- todo, test for readable
result[#result+1] = fl[3]
input.aux.register_in_trees(instance,f) -- for tracing used files
done = true
if not instance.allresults then break end
else
-- input.debug('F',' '..f)
end
end
end
if not done and doscan then
-- check if on disk / unchecked / does not work at all / also zips
if input.method_is_file(pathname) then -- ?
local pname = pathname:gsub("%.%*$",'')
if not pname:find("%*") then
local ppname = pname:gsub("/+$","")
if input.aux.can_be_dir(instance,ppname) then
for _, w in pairs(wantedfiles) do
local fname = file.join(ppname,w)
if input.is_readable.file(fname) then
if input.trace > 2 then
input.logger('= found by scanning',fname)
end
result[#result+1] = fname
done = true
if not instance.allresults then break end
end
end
else
-- no access needed for non existing path, speedup (esp in large tree with lots of fake)
end
end
end
end
if not done and doscan then
-- todo: slow path scanning
end
if done and not instance.allresults then break end
end
end
end
for k,v in pairs(result) do
result[k] = file.collapse_path(v)
end
if instance.remember then
instance.found[stamp] = result
end
return result
end
input.aux._find_file_ = input.aux.find_file
function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
local result = input.aux._find_file_(instance,filename)
if #result == 0 then
local lowered = filename:lower()
if filename ~= lowered then
return input.aux._find_file_(instance,lowered)
end
end
return result
end
if lfs and lfs.isfile then
input.aux.is_file = lfs.isfile -- to be done: use this
else
input.aux.is_file = file.is_readable
end
if lfs and lfs.isdir then
function input.aux.can_be_dir(instance,name)
if not instance.fakepaths[name] then
if lfs.isdir(name) then
instance.fakepaths[name] = 1 -- directory
else
instance.fakepaths[name] = 2 -- no directory
end
end
return (instance.fakepaths[name] == 1)
end
else
function input.aux.can_be_dir()
return true
end
end
if not input.concatinators then input.concatinators = { } end
input.concatinators.tex = file.join
input.concatinators.file = input.concatinators.tex
function input.find_files(instance,filename,filetype,mustexist)
if type(mustexist) == boolean then
-- all set
elseif type(filetype) == 'boolean' then
filetype, mustexist = nil, false
elseif type(filetype) ~= 'string' then
filetype, mustexist = nil, false
end
instance.format = filetype or ''
local t = input.aux.find_file(instance,filename,true)
instance.format = ''
return t
end
function input.find_file(instance,filename,filetype,mustexist)
return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
end
function input.find_given_files(instance,filename)
local bname, result = file.basename(filename), { }
for k, hash in ipairs(instance.hashes) do
local files = instance.files[hash.tag]
local blist = files[bname]
if not blist then
local rname = "remap:"..bname
blist = files[rname]
if blist then
bname = files[rname]
blist = files[bname]
end
end
if blist then
if type(blist) == 'string' then
result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
if not instance.allresults then break end
else
for kk,vv in pairs(blist) do
result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
if not instance.allresults then break end
end
end
end
end
return result
end
function input.find_given_file(instance,filename)
return (input.find_given_files(instance,filename)[1] or "")
end
function input.find_wildcard_files(instance,filename) -- todo: remap:
local result = { }
local bname, dname = file.basename(filename), file.dirname(filename)
local path = dname:gsub("^*/","")
path = path:gsub("*",".*")
path = path:gsub("-","%%-")
if dname == "" then
path = ".*"
end
local name = bname
name = name:gsub("*",".*")
name = name:gsub("-","%%-")
path = path:lower()
name = name:lower()
local function doit(blist,bname,hash,allresults)
local done = false
if blist then
if type(blist) == 'string' then
-- make function and share code
if (blist:lower()):find(path) then
result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
done = true
end
else
for kk,vv in pairs(blist) do
if (vv:lower()):find(path) then
result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
done = true
if not allresults then break end
end
end
end
end
return done
end
local files, allresults, done = instance.files, instance.allresults, false
if name:find("%*") then
for k, hash in ipairs(instance.hashes) do
for kk, hh in pairs(files[hash.tag]) do
if not kk:find("^remap:") then
if (kk:lower()):find(name) then
if doit(hh,kk,hash,allresults) then done = true end
if done and not allresults then break end
end
end
end
end
else
for k, hash in ipairs(instance.hashes) do
if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
if done and not allresults then break end
end
end
return result
end
function input.find_wildcard_file(instance,filename)
return (input.find_wildcard_files(instance,filename)[1] or "")
end
-- main user functions
function input.save_used_files_in_trees(instance, filename,jobname)
if not filename then filename = 'luatex.jlg' end
local f = io.open(filename,'w')
if f then
f:write("\n")
f:write("\n")
if jobname then
f:write("\t" .. jobname .. "\n")
end
f:write("\t\n")
for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
f:write("\t\t" .. v .. "\n")
end
f:write("\t\n")
f:write("\n")
f:close()
end
end
function input.automount(instance)
-- implemented later
end
function input.load(instance)
input.starttiming(instance)
input.resetconfig(instance)
input.identify_cnf(instance)
input.load_lua(instance)
input.expand_variables(instance)
input.load_cnf(instance)
input.expand_variables(instance)
input.load_hash(instance)
input.automount(instance)
input.stoptiming(instance)
end
function input.for_files(instance, command, files, filetype, mustexist)
if files and #files > 0 then
local function report(str)
if input.verbose then
input.report(str) -- has already verbose
else
print(str)
end
end
if input.verbose then
report('')
end
for _, file in pairs(files) do
local result = command(instance,file,filetype,mustexist)
if type(result) == 'string' then
report(result)
else
for _,v in pairs(result) do
report(v)
end
end
end
end
end
-- strtab
function input.var_value(instance,str) -- output the value of variable $STRING.
return input.variable(instance,str)
end
function input.expand_var(instance,str) -- output variable expansion of STRING.
return input.expansion(instance,str)
end
function input.show_path(instance,str) -- output search path for file type NAME
return file.join_path(input.expanded_path_list(instance,input.format_of_var(str)))
end
-- input.find_file(filename)
-- input.find_file(filename, filetype, mustexist)
-- input.find_file(filename, mustexist)
-- input.find_file(filename, filetype)
function input.aux.register_file(files, name, path)
if files[name] then
if type(files[name]) == 'string' then
files[name] = { files[name], path }
else
files[name] = path
end
else
files[name] = path
end
end
if not input.finders then input.finders = { } end
if not input.openers then input.openers = { } end
if not input.loaders then input.loaders = { } end
input.finders.notfound = { nil }
input.openers.notfound = { nil }
input.loaders.notfound = { false, nil, 0 }
function input.splitmethod(filename)
if not filename then
return { } -- safeguard
elseif type(filename) == "table" then
return filename -- already split
elseif not filename:find("://") then
return { scheme="file", path = filename, original=filename } -- quick hack
else
return url.hashed(filename)
end
end
function input.method_is_file(filename)
return input.splitmethod(filename).scheme == 'file'
end
function table.sequenced(t,sep) -- temp here
local s = { }
for k, v in pairs(t) do
s[#s+1] = k .. "=" .. v
end
return table.concat(s, sep or " | ")
end
function input.methodhandler(what, instance, filename, filetype) -- ...
local specification = (type(filename) == "string" and input.splitmethod(filename)) or filename -- no or { }, let it bomb
local scheme = specification.scheme
if input[what][scheme] then
input.logger('= handler',specification.original .." -> " .. what .. " -> " .. table.sequenced(specification))
return input[what][scheme](instance,filename,filetype) -- todo: specification
else
return input[what].tex(instance,filename,filetype) -- todo: specification
end
end
-- also inside next test?
function input.findtexfile(instance, filename, filetype)
return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
end
function input.opentexfile(instance,filename)
return input.methodhandler('openers',instance, input.normalize_name(filename))
end
function input.findbinfile(instance, filename, filetype)
return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
end
function input.openbinfile(instance,filename)
return input.methodhandler('loaders',instance, input.normalize_name(filename))
end
function input.loadbinfile(instance, filename, filetype)
local fname = input.findbinfile(instance, input.normalize_name(filename), filetype)
if fname and fname ~= "" then
return input.openbinfile(instance,fname)
else
return unpack(input.loaders.notfound)
end
end
function input.texdatablob(instance, filename, filetype)
local ok, data, size = input.loadbinfile(instance, filename, filetype)
return data or ""
end
input.loadtexfile = input.texdatablob
function input.openfile(filename) -- brrr texmf.instance here / todo ! ! ! ! !
local fullname = input.findtexfile(texmf.instance, filename)
if fullname and (fullname ~= "") then
return input.opentexfile(texmf.instance, fullname)
else
return nil
end
end
function input.logmode()
return (os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"):lower()
end
-- this is a prelude to engine/progname specific configuration files
-- in which case we can omit files meant for other programs and
-- packages
--- ctx
-- maybe texinputs + font paths
-- maybe positive selection tex/context fonts/tfm|afm|vf|opentype|type1|map|enc
input.validators = { }
input.validators.visibility = { }
function input.validators.visibility.default(path, name)
return true
end
function input.validators.visibility.context(path, name)
path = path[1] or path -- some day a loop
return not (
path:find("latex") or
-- path:find("doc") or
path:find("tex4ht") or
path:find("source") or
-- path:find("config") or
-- path:find("metafont") or
path:find("lists$") or
name:find("%.tpm$") or
name:find("%.bak$")
)
end
-- todo: describe which functions are public (maybe input.private. ... )
-- beware: i need to check where we still need a / on windows:
function input.clean_path(str)
--~ return (((str:gsub("\\","/")):gsub("^!+","")):gsub("//+","//"))
if str then
return ((str:gsub("\\","/")):gsub("^!+",""))
else
return nil
end
end
function input.do_with_path(name,func)
for _, v in pairs(input.expanded_path_list(instance,name)) do
func("^"..input.clean_path(v))
end
end
function input.do_with_var(name,func)
func(input.aux.expanded_var(name))
end
function input.with_files(instance,pattern,handle)
for _, hash in ipairs(instance.hashes) do
local blobpath = hash.tag
local blobtype = hash.type
if blobpath then
local files = instance.files[blobpath]
if files then
for k,v in pairs(files) do
if k:find("^remap:") then
k = files[k]
v = files[k] -- chained
end
if k:find(pattern) then
if type(v) == "string" then
handle(blobtype,blobpath,v,k)
else
for _,vv in pairs(v) do
handle(blobtype,blobpath,vv,k)
end
end
end
end
end
end
end
end
--~ function input.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
--~ newname = file.addsuffix(newname,"lua")
--~ local newscript = input.clean_path(input.find_file(instance, newname))
--~ local oldscript = input.clean_path(oldname)
--~ input.report("old script", oldscript)
--~ input.report("new script", newscript)
--~ if oldscript ~= newscript and (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
--~ local newdata = io.loaddata(newscript)
--~ if newdata then
--~ input.report("old script content replaced by new content")
--~ io.savedata(oldscript,newdata)
--~ end
--~ end
--~ end
function input.update_script(instance,oldname,newname) -- oldname -> own.name, not per se a suffix
local scriptpath = "scripts/context/lua"
newname = file.addsuffix(newname,"lua")
local oldscript = input.clean_path(oldname)
input.report("to be replaced old script", oldscript)
local newscripts = input.find_files(instance, newname) or { }
if #newscripts == 0 then
input.report("unable to locate new script")
else
for _, newscript in ipairs(newscripts) do
newscript = input.clean_path(newscript)
input.report("checking new script", newscript)
if oldscript == newscript then
input.report("old and new script are the same")
elseif not newscript:find(scriptpath) then
input.report("new script should come from",scriptpath)
elseif not (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
input.report("invalid new script name")
else
local newdata = io.loaddata(newscript)
if newdata then
input.report("old script content replaced by new content")
io.savedata(oldscript,newdata)
break
else
input.report("unable to load new script")
end
end
end
end
end
--~ print(table.serialize(input.aux.splitpathexpr("/usr/share/texmf-{texlive,tetex}", {})))
-- command line resolver:
--~ print(input.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
do
local resolvers = { }
resolvers.environment = function(instance,str)
return input.clean_path(os.getenv(str) or os.getenv(str:upper()) or os.getenv(str:lower()) or "")
end
resolvers.relative = function(instance,str,n)
if io.exists(str) then
-- nothing
elseif io.exists("./" .. str) then
str = "./" .. str
else
local p = "../"
for i=1,n or 2 do
if io.exists(p .. str) then
str = p .. str
break
else
p = p .. "../"
end
end
end
return input.clean_path(str)
end
resolvers.locate = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path((fullname ~= "" and fullname) or str)
end
resolvers.filename = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path(file.basename((fullname ~= "" and fullname) or str))
end
resolvers.pathname = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path(file.dirname((fullname ~= "" and fullname) or str))
end
resolvers.env = resolvers.environment
resolvers.rel = resolvers.relative
resolvers.loc = resolvers.locate
resolvers.kpse = resolvers.locate
resolvers.full = resolvers.locate
resolvers.file = resolvers.filename
resolvers.path = resolvers.pathname
local function resolve(instance,str)
if type(str) == "table" then
for k, v in pairs(str) do
str[k] = resolve(instance,v) or v
end
elseif str and str ~= "" then
str = str:gsub("([a-z]+):([^ ]+)", function(method,target)
if resolvers[method] then
return resolvers[method](instance,target)
else
return method .. ":" .. target
end
end)
end
return str
end
input.resolve = resolve
end
if not modules then modules = { } end modules ['luat-tmp'] = {
version = 1.001,
comment = "companion to luat-lib.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
--[[ldx--
This module deals with caching data. It sets up the paths and
implements loaders and savers for tables. Best is to set the
following variable. When not set, the usual paths will be
checked. Personally I prefer the (users) temporary path.
TEXMFCACHE=$TMP;$TEMP;$TMPDIR;$TEMPDIR;$HOME;$TEXMFVAR;$VARTEXMF;.
Currently we do no locking when we write files. This is no real
problem because most caching involves fonts and the chance of them
being written at the same time is small. We also need to extend
luatools with a recache feature.
--ldx]]--
caches = caches or { }
dir = dir or { }
texmf = texmf or { }
caches.path = caches.path or nil
caches.base = caches.base or "luatex-cache"
caches.more = caches.more or "context"
caches.direct = false -- true is faster but may need huge amounts of memory
caches.trace = false
caches.tree = false
caches.paths = caches.paths or nil
caches.force = false
input.usecache = not toboolean(os.getenv("TEXMFSHARECACHE") or "false",true) -- true
function caches.temp(instance)
local function checkpath(cachepath)
if not cachepath or cachepath == "" then
return nil
elseif lfs.attributes(cachepath,"mode") == "directory" then -- lfs.isdir(cachepath) then
return cachepath
elseif caches.force or io.ask(string.format("Should I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
dir.mkdirs(cachepath)
return (lfs.attributes(cachepath,"mode") == "directory") and cachepath
else
return nil
end
end
local cachepath = input.expanded_path_list(instance,"TEXMFCACHE")
cachepath = cachepath and #cachepath > 0 and checkpath(cachepath[1])
if not cachepath then
cachepath = os.getenv("TEXMFCACHE") or os.getenv("HOME") or os.getenv("HOMEPATH") or os.getenv("TMP") or os.getenv("TEMP") or os.getenv("TMPDIR") or nil
cachepath = checkpath(cachepath)
end
if not cachepath then
print("\nfatal error: there is no valid cache path defined\n")
os.exit()
elseif lfs.attributes(cachepath,"mode") ~= "directory" then
print(string.format("\nfatal error: cache path %s is not a directory\n",cachepath))
os.exit()
end
function caches.temp(instance)
return cachepath
end
return cachepath
end
function caches.configpath(instance)
return table.concat(instance.cnffiles,";")
end
function caches.hashed(tree)
return md5.hex((tree:lower()):gsub("[\\\/]+","/"))
end
function caches.treehash(instance)
local tree = caches.configpath(instance)
if not tree or tree == "" then
return false
else
return caches.hashed(tree)
end
end
function caches.setpath(instance,...)
if not caches.path then
if not caches.path then
caches.path = caches.temp(instance)
end
caches.path = input.clean_path(caches.path) -- to be sure
if lfs then
caches.tree = caches.tree or caches.treehash(instance)
if caches.tree then
caches.path = dir.mkdirs(caches.path,caches.base,caches.more,caches.tree)
else
caches.path = dir.mkdirs(caches.path,caches.base,caches.more)
end
end
end
if not caches.path then
caches.path = '.'
end
caches.path = input.clean_path(caches.path)
if lfs and not table.is_empty({...}) then
local pth = dir.mkdirs(caches.path,...)
return pth
end
caches.path = dir.expand_name(caches.path)
return caches.path
end
function caches.definepath(instance,category,subcategory)
return function()
return caches.setpath(instance,category,subcategory)
end
end
function caches.setluanames(path,name)
return path .. "/" .. name .. ".tma", path .. "/" .. name .. ".tmc"
end
function caches.loaddata(path,name)
local tmaname, tmcname = caches.setluanames(path,name)
local loader = loadfile(tmcname) or loadfile(tmaname)
if loader then
return loader()
else
return false
end
end
function caches.is_writable(filepath,filename)
local tmaname, tmcname = caches.setluanames(filepath,filename)
return file.is_writable(tmaname)
end
function caches.savedata(filepath,filename,data,raw) -- raw needed for file cache
local tmaname, tmcname = caches.setluanames(filepath,filename)
local reduce, simplify = true, true
if raw then
reduce, simplify = false, false
end
if caches.direct then
file.savedata(tmaname, table.serialize(data,'return',true,true))
else
table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
end
utils.lua.compile(tmaname, tmcname)
end
-- here we use the cache for format loading (texconfig.[formatname|jobname])
--~ if tex and texconfig and texconfig.formatname and texconfig.formatname == "" then
if tex and texconfig and (not texconfig.formatname or texconfig.formatname == "") and texmf.instance then
if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end -- or luc
texconfig.formatname = caches.setpath(texmf.instance,"formats") .. "/" .. texconfig.luaname:gsub("%.lu.$",".fmt")
end
--[[ldx--
Once we found ourselves defining similar cache constructs
several times, containers were introduced. Containers are used
to collect tables in memory and reuse them when possible based
on (unique) hashes (to be provided by the calling function).
Caching to disk is disabled by default. Version numbers are
stored in the saved table which makes it possible to change the
table structures without bothering about the disk cache.
Examples of usage can be found in the font related code.
--ldx]]--
containers = { }
containers.trace = false
do -- local report
local function report(container,tag,name)
if caches.trace or containers.trace or container.trace then
logs.report(string.format("%s cache",container.subcategory),string.format("%s: %s",tag,name or 'invalid'))
end
end
local allocated = { }
-- tracing
function containers.define(category, subcategory, version, enabled)
return function()
if category and subcategory then
local c = allocated[category]
if not c then
c = { }
allocated[category] = c
end
local s = c[subcategory]
if not s then
s = {
category = category,
subcategory = subcategory,
storage = { },
enabled = enabled,
version = version or 1.000,
trace = false,
path = caches.setpath(texmf.instance,category,subcategory),
}
c[subcategory] = s
end
return s
else
return nil
end
end
end
function containers.is_usable(container, name)
return container.enabled and caches.is_writable(container.path, name)
end
function containers.is_valid(container, name)
if name and name ~= "" then
local storage = container.storage[name]
return storage and not table.is_empty(storage) and storage.cache_version == container.version
else
return false
end
end
function containers.read(container,name)
if container.enabled and not container.storage[name] then
container.storage[name] = caches.loaddata(container.path,name)
if containers.is_valid(container,name) then
report(container,"loaded",name)
else
container.storage[name] = nil
end
end
if container.storage[name] then
report(container,"reusing",name)
end
return container.storage[name]
end
function containers.write(container, name, data)
if data then
data.cache_version = container.version
if container.enabled then
local unique, shared = data.unique, data.shared
data.unique, data.shared = nil, nil
caches.savedata(container.path, name, data)
report(container,"saved",name)
data.unique, data.shared = unique, shared
end
report(container,"stored",name)
container.storage[name] = data
end
return data
end
function containers.content(container,name)
return container.storage[name]
end
end
-- since we want to use the cache instead of the tree, we will now
-- reimplement the saver.
local save_data = input.aux.save_data
input.cachepath = nil
function input.aux.save_data(instance, dataname, check)
input.cachepath = input.cachepath or caches.definepath(instance,"trees")
save_data(instance, dataname, check, function(cachename,dataname)
if input.usecache then
return file.join(input.cachepath(),caches.hashed(cachename))
else
return file.join(cachename,dataname)
end
end)
end
local load_data = input.aux.load_data
function input.aux.load_data(instance,pathname,dataname,filename)
input.cachepath = input.cachepath or caches.definepath(instance,"trees")
load_data(instance,pathname,dataname,filename,function(dataname,filename)
if input.usecache then
return file.join(input.cachepath(),caches.hashed(pathname))
else
if not filename or (filename == "") then
filename = dataname
end
return file.join(pathname,filename)
end
end)
end
-- we will make a better format, maybe something xml or just text or lua
input.automounted = input.automounted or { }
function input.automount(instance,usecache)
local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
if table.is_empty(mountpaths) and usecache then
mountpaths = { caches.setpath(instance,"mount") }
end
if not table.is_empty(mountpaths) then
input.starttiming(instance)
for k, root in pairs(mountpaths) do
local f = io.open(root.."/url.tmi")
if f then
for line in f:lines() do
if line then
if line:find("^[%%#%-]") then -- or %W
-- skip
elseif line:find("^zip://") then
input.report("mounting",line)
table.insert(input.automounted,line)
input.usezipfile(instance,line)
end
end
end
f:close()
end
end
input.stoptiming(instance)
end
end
-- store info in format
input.storage = { }
input.storage.data = { }
input.storage.min = 0 -- 500
input.storage.max = input.storage.min - 1
input.storage.trace = false -- true
input.storage.done = 0
input.storage.evaluators = { }
-- (evaluate,message,names)
function input.storage.register(...)
input.storage.data[#input.storage.data+1] = { ... }
end
function input.storage.evaluate(name)
input.storage.evaluators[#input.storage.evaluators+1] = name
end
function input.storage.finalize() -- we can prepend the string with "evaluate:"
for _, t in ipairs(input.storage.evaluators) do
for i, v in pairs(t) do
if type(v) == "string" then
t[i] = loadstring(v)()
elseif type(v) == "table" then
for _, vv in pairs(v) do
if type(vv) == "string" then
t[i] = loadstring(vv)()
end
end
end
end
end
end
function input.storage.dump()
for name, data in ipairs(input.storage.data) do
local evaluate, message, original, target = data[1], data[2], data[3] ,data[4]
local name, initialize, finalize, code = nil, "", "", ""
for str in target:gmatch("([^%.]+)") do
if name then
name = name .. "." .. str
else
name = str
end
initialize = string.format("%s %s = %s or {} ", initialize, name, name)
end
if evaluate then
finalize = "input.storage.evaluate(" .. name .. ")"
end
input.storage.max = input.storage.max + 1
if input.storage.trace then
logs.report('storage',string.format('saving %s in slot %s',message,input.storage.max))
code =
initialize ..
string.format("logs.report('storage','restoring %s from slot %s') ",message,input.storage.max) ..
table.serialize(original,name) ..
finalize
else
code = initialize .. table.serialize(original,name) .. finalize
end
lua.bytecode[input.storage.max] = loadstring(code)
end
end
if lua.bytecode then -- from 0 upwards
local i = input.storage.min
while lua.bytecode[i] do
lua.bytecode[i]()
lua.bytecode[i] = nil
i = i + 1
end
input.storage.done = i
end
if not modules then modules = { } end modules ['luat-log'] = {
version = 1.001,
comment = "companion to luat-lib.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
--[[ldx--
This is a prelude to a more extensive logging module. For the sake
of parsing log files, in addition to the standard logging we will
provide an structured file. Actually, any logging that
is hooked into callbacks will be \XML\ by default.
--ldx]]--
input = input or { }
logs = logs or { }
--[[ldx--
This looks pretty ugly but we need to speed things up a bit.
--ldx]]--
logs.levels = {
['error'] = 1,
['warning'] = 2,
['info'] = 3,
['debug'] = 4
}
logs.functions = {
'error', 'warning', 'info', 'debug', 'report',
'start', 'stop', 'push', 'pop'
}
logs.callbacks = {
'start_page_number',
'stop_page_number',
'report_output_pages',
'report_output_log'
}
logs.xml = logs.xml or { }
logs.tex = logs.tex or { }
logs.level = 0
do
local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
if texlua then
write_nl = print
write = io.write
end
function logs.xml.debug(category,str)
if logs.level > 3 then write_nl(format("%s",category,str)) end
end
function logs.xml.info(category,str)
if logs.level > 2 then write_nl(format("%s",category,str)) end
end
function logs.xml.warning(category,str)
if logs.level > 1 then write_nl(format("%s",category,str)) end
end
function logs.xml.error(category,str)
if logs.level > 0 then write_nl(format("%s",category,str)) end
end
function logs.xml.report(category,str)
write_nl(format("%s",category,str))
end
function logs.xml.start() if logs.level > 0 then tw("<%s>" ) end end
function logs.xml.stop () if logs.level > 0 then tw("%s>") end end
function logs.xml.push () if logs.level > 0 then tw("" ) end end
function logs.tex.debug(category,str)
if logs.level > 3 then write_nl(format("debug >> %s: %s" ,category,str)) end
end
function logs.tex.info(category,str)
if logs.level > 2 then write_nl(format("info >> %s: %s" ,category,str)) end
end
function logs.tex.warning(category,str)
if logs.level > 1 then write_nl(format("warning >> %s: %s",category,str)) end
end
function logs.tex.error(category,str)
if logs.level > 0 then write_nl(format("error >> %s: %s" ,category,str)) end
end
function logs.tex.report(category,str)
write_nl(format("report >> %s: %s" ,category,str))
end
function logs.set_level(level)
logs.level = logs.levels[level] or level
end
function logs.set_method(method)
for _, v in pairs(logs.functions) do
logs[v] = logs[method][v] or function() end
end
if callback and input[method] then
for _, cb in pairs(logs.callbacks) do
callback.register(cb, input[method][cb])
end
end
end
function logs.xml.start_page_number()
write_nl(format("")
write_nl("")
end
function logs.xml.report_output_pages(p,b)
write_nl(format("", p))
write_nl(format("", b))
write_nl("")
end
function logs.xml.report_output_log()
end
end
logs.set_level('error')
logs.set_method('tex')
if not modules then modules = { } end modules ['luat-sta'] = {
version = 1.001,
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
states = states or { }
states.data = states.data or { }
states.hash = states.hash or { }
states.tag = states.tag or ""
states.filename = states.filename or ""
function states.save(filename,tag)
tag = tag or states.tag
filename = file.addsuffix(filename or states.filename,'lus')
io.savedata(filename,
"-- generator : luat-sta.lua\n" ..
"-- state tag : " .. tag .. "\n\n" ..
table.serialize(states.data[tag or states.tag] or {},true)
)
end
function states.load(filename,tag)
states.filename = filename
states.tag = tag or "whatever"
states.filename = file.addsuffix(states.filename,'lus')
states.data[states.tag], states.hash[states.tag] = (io.exists(filename) and dofile(filename)) or { }, { }
end
function states.set_by_tag(tag,key,value,default,persistent)
local d, h = states.data[tag], states.hash[tag]
if d then
local dkey, hkey = key, key
local pre, post = key:match("(.+)%.([^%.]+)$")
if pre and post then
for k in pre:gmatch("[^%.]+") do
local dk = d[k]
if not dk then
dk = { }
d[k] = dk
end
d = dk
end
dkey, hkey = post, key
end
if type(value) == nil then
value = value or default
elseif persistent then
value = value or d[dkey] or default
else
value = value or default
end
d[dkey], h[hkey] = value, value
end
end
function states.get_by_tag(tag,key,default)
local h = states.hash[tag]
if h and h[key] then
return h[key]
else
local d = states.data[tag]
if d then
for k in key:gmatch("[^%.]+") do
local dk = d[k]
if dk then
d = dk
else
return default
end
end
return d or default
end
end
end
function states.set(key,value,default,persistent)
states.set_by_tag(states.tag,key,value,default,persistent)
end
function states.get(key,default)
return states.get_by_tag(states.tag,key,default)
end
--~ states.data.update = {
--~ ["version"] = {
--~ ["major"] = 0,
--~ ["minor"] = 1,
--~ },
--~ ["rsync"] = {
--~ ["server"] = "contextgarden.net",
--~ ["module"] = "minimals",
--~ ["repository"] = "current",
--~ ["flags"] = "-rpztlv --stats",
--~ },
--~ ["tasks"] = {
--~ ["update"] = true,
--~ ["make"] = true,
--~ ["delete"] = false,
--~ },
--~ ["platform"] = {
--~ ["host"] = true,
--~ ["other"] = {
--~ ["mswin"] = false,
--~ ["linux"] = false,
--~ ["linux-64"] = false,
--~ ["osx-intel"] = false,
--~ ["osx-ppc"] = false,
--~ ["sun"] = false,
--~ },
--~ },
--~ ["context"] = {
--~ ["available"] = {"current", "beta", "alpha", "experimental"},
--~ ["selected"] = "current",
--~ },
--~ ["formats"] = {
--~ ["cont-en"] = true,
--~ ["cont-nl"] = true,
--~ ["cont-de"] = false,
--~ ["cont-cz"] = false,
--~ ["cont-fr"] = false,
--~ ["cont-ro"] = false,
--~ },
--~ ["engine"] = {
--~ ["pdftex"] = {
--~ ["install"] = true,
--~ ["formats"] = {
--~ ["pdftex"] = true,
--~ },
--~ },
--~ ["luatex"] = {
--~ ["install"] = true,
--~ ["formats"] = {
--~ },
--~ },
--~ ["xetex"] = {
--~ ["install"] = true,
--~ ["formats"] = {
--~ ["xetex"] = false,
--~ },
--~ },
--~ ["metapost"] = {
--~ ["install"] = true,
--~ ["formats"] = {
--~ ["mpost"] = true,
--~ ["metafun"] = true,
--~ },
--~ },
--~ },
--~ ["fonts"] = {
--~ },
--~ ["doc"] = {
--~ },
--~ ["modules"] = {
--~ ["f-urwgaramond"] = false,
--~ ["f-urwgothic"] = false,
--~ ["t-bnf"] = false,
--~ ["t-chromato"] = false,
--~ ["t-cmscbf"] = false,
--~ ["t-cmttbf"] = false,
--~ ["t-construction-plan"] = false,
--~ ["t-degrade"] = false,
--~ ["t-french"] = false,
--~ ["t-lettrine"] = false,
--~ ["t-lilypond"] = false,
--~ ["t-mathsets"] = false,
--~ ["t-tikz"] = false,
--~ ["t-typearea"] = false,
--~ ["t-vim"] = false,
--~ },
--~ }
--~ states.save("teststate", "update")
--~ states.load("teststate", "update")
--~ print(states.get_by_tag("update","rsync.server","unknown"))
--~ states.set_by_tag("update","rsync.server","oeps")
--~ print(states.get_by_tag("update","rsync.server","unknown"))
--~ states.save("teststate", "update")
--~ states.load("teststate", "update")
--~ print(states.get_by_tag("update","rsync.server","unknown"))
-- end library merge
own = { }
own.libs = { -- todo: check which ones are really needed
'l-string.lua',
'l-lpeg.lua',
'l-table.lua',
'l-io.lua',
'l-md5.lua',
'l-number.lua',
'l-set.lua',
'l-os.lua',
'l-file.lua',
'l-dir.lua',
'l-boolean.lua',
'l-xml.lua',
-- 'l-unicode.lua',
'l-utils.lua',
-- 'l-tex.lua',
'luat-lib.lua',
'luat-inp.lua',
-- 'luat-zip.lua',
-- 'luat-tex.lua',
-- 'luat-kps.lua',
'luat-tmp.lua',
'luat-log.lua',
'luat-sta.lua',
}
-- We need this hack till luatex is fixed.
--
-- for k,v in pairs(arg) do print(k,v) end
if arg and (arg[0] == 'luatex' or arg[0] == 'luatex.exe') and arg[1] == "--luaonly" then
arg[-1]=arg[0] arg[0]=arg[2] for k=3,#arg do arg[k-2]=arg[k] end arg[#arg]=nil arg[#arg]=nil
end
-- End of hack.
own.name = (environment and environment.ownname) or arg[0] or 'luatools.lua'
own.path = string.match(own.name,"^(.+)[\\/].-$") or "."
own.list = { '.' }
if own.path ~= '.' then
table.insert(own.list,own.path)
end
table.insert(own.list,own.path.."/../../../tex/context/base")
table.insert(own.list,own.path.."/mtx")
table.insert(own.list,own.path.."/../sources")
function locate_libs()
for _, lib in pairs(own.libs) do
for _, pth in pairs(own.list) do
local filename = string.gsub(pth .. "/" .. lib,"\\","/")
local codeblob = loadfile(filename)
if codeblob then
codeblob()
own.list = { pth } -- speed up te search
break
end
end
end
end
if not input then
locate_libs()
end
if not input then
print("")
print("Mtxrun is unable to start up due to lack of libraries. You may")
print("try to run 'lua mtxrun.lua --selfmerge' in the path where this")
print("script is located (normally under ..../scripts/context/lua) which")
print("will make this script library independent.")
os.exit()
end
instance = input.reset()
input.verbose = environment.argument("verbose") or false
input.banner = 'MtxRun | '
utils.report = input.report
instance.engine = environment.argument("engine") or 'luatex'
instance.progname = environment.argument("progname") or 'context'
instance.lsrmode = environment.argument("lsr") or false
-- use os.env or environment when available
--~ function input.check_environment(tree)
--~ input.report('')
--~ os.setenv('TMP', os.getenv('TMP') or os.getenv('TEMP') or os.getenv('TMPDIR') or os.getenv('HOME'))
--~ if os.platform == 'linux' then
--~ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-linux')
--~ elseif os.platform == 'windows' then
--~ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-windows')
--~ elseif os.platform == 'macosx' then
--~ os.setenv('TEXOS', os.getenv('TEXOS') or 'texmf-macosx')
--~ end
--~ os.setenv('TEXOS', string.gsub(string.gsub(os.getenv('TEXOS'),"^[\\\/]*", ''),"[\\\/]*$", ''))
--~ os.setenv('TEXPATH', string.gsub(tree,"\/+$",''))
--~ os.setenv('TEXMFOS', os.getenv('TEXPATH') .. "/" .. os.getenv('TEXOS'))
--~ input.report('')
--~ input.report("preset : TEXPATH => " .. os.getenv('TEXPATH'))
--~ input.report("preset : TEXOS => " .. os.getenv('TEXOS'))
--~ input.report("preset : TEXMFOS => " .. os.getenv('TEXMFOS'))
--~ input.report("preset : TMP => " .. os.getenv('TMP'))
--~ input.report('')
--~ end
function input.check_environment(tree)
input.report('')
os.setenv('TMP', os.getenv('TMP') or os.getenv('TEMP') or os.getenv('TMPDIR') or os.getenv('HOME'))
os.setenv('TEXOS', os.getenv('TEXOS') or ("texmf-" .. os.currentplatform()))
os.setenv('TEXPATH', (tree or "tex"):gsub("\/+$",''))
os.setenv('TEXMFOS', os.getenv('TEXPATH') .. "/" .. os.getenv('TEXOS'))
input.report('')
input.report("preset : TEXPATH => " .. os.getenv('TEXPATH'))
input.report("preset : TEXOS => " .. os.getenv('TEXOS'))
input.report("preset : TEXMFOS => " .. os.getenv('TEXMFOS'))
input.report("preset : TMP => " .. os.getenv('TMP'))
input.report('')
end
function input.load_environment(name) -- todo: key=value as well as lua
local f = io.open(name)
if f then
for line in f:lines() do
if line:find("^[%%%#]") then
-- skip comment
else
local key, how, value = line:match("^(.-)%s*([<=>%?]+)%s*(.*)%s*$")
if how then
value = value:gsub("%%(.-)%%", function(v) return os.getenv(v) or "" end)
if how == "=" or how == "<<" then
os.setenv(key,value)
elseif how == "?" or how == "??" then
os.setenv(key,os.getenv(key) or value)
elseif how == "<" or how == "+=" then
if os.getenv(key) then
os.setenv(key,os.getenv(key) .. io.fileseparator .. value)
else
os.setenv(key,value)
end
elseif how == ">" or how == "=+" then
if os.getenv(key) then
os.setenv(key,value .. io.pathseparator .. os.getenv(key))
else
os.setenv(key,value)
end
end
end
end
end
f:close()
end
end
function input.load_tree(tree)
if tree and tree ~= "" then
local setuptex = 'setuptex.tmf'
if lfs.attributes(tree, "mode") == "directory" then -- check if not nil
setuptex = tree .. "/" .. setuptex
else
setuptex = tree
end
if io.exists(setuptex) then
input.check_environment(tree)
input.load_environment(setuptex)
end
end
end
-- md5 extensions
-- maybe md.md5 md.md5hex md.md5HEX
if not md5 then md5 = { } end
if not md5.sum then
function md5.sum(k)
return string.rep("x",16)
end
end
function md5.hexsum(k)
return (string.gsub(md5.sum(k), ".", function(c) return string.format("%02x", string.byte(c)) end))
end
function md5.HEXsum(k)
return (string.gsub(md5.sum(k), ".", function(c) return string.format("%02X", string.byte(c)) end))
end
-- file extensions
file.needs_updating_threshold = 1
function file.needs_updating(oldname,newname) -- size modification access change
local oldtime = lfs.attributes(oldname, modification)
local newtime = lfs.attributes(newname, modification)
if newtime >= oldtime then
return false
elseif oldtime - newtime < file.needs_updating_threshold then
return false
else
return true
end
end
function file.mdchecksum(name)
if md5 then
local data = io.loadall(name)
if data then
return md5.HEXsum(data)
end
end
return nil
end
function file.loadchecksum(name)
if md then
local data = io.loadall(name .. ".md5")
if data then
return string.gsub(md5.HEXsum(data),"%s$","")
end
end
return nil
end
function file.savechecksum(name, checksum)
if not checksum then checksum = file.mdchecksum(name) end
if checksum then
local f = io.open(name .. ".md5","w")
if f then
f:write(checksum)
f:close()
return checksum
end
end
return nil
end
os.arch = os.arch or function()
return os.resultof("uname -m") or "linux"
end
function os.currentplatform(name, default)
local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
if name then
if name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
return "mswin"
elseif name == "linux" then
local architecture = os.arch()
if architecture:find("x86_64") then
return "linux-64"
else
return "linux"
end
elseif name == "macosx" then
local architecture = os.arch()
if architecture:find("i386") then
return "osx-intel"
else
return "osx-ppc"
end
elseif name == "freebsd" then
return "freebsd"
end
end
return default or name
end
-- it starts here
input.runners = { }
input.runners.applications = { }
input.runners.applications.lua = "luatex --luaonly"
input.runners.applications.pl = "perl"
input.runners.applications.py = "python"
input.runners.applications.rb = "ruby"
input.runners.suffixes = {
'rb', 'lua', 'py', 'pl'
}
input.runners.registered = {
texexec = { 'texexec.rb', true },
texutil = { 'texutil.rb', true },
texfont = { 'texfont.pl', true },
texshow = { 'texshow.pl', false },
makempy = { 'makempy.pl', true },
mptopdf = { 'mptopdf.pl', true },
pstopdf = { 'pstopdf.rb', true },
examplex = { 'examplex.rb', false },
concheck = { 'concheck.rb', false },
runtools = { 'runtools.rb', true },
textools = { 'textools.rb', true },
tmftools = { 'tmftools.rb', true },
ctxtools = { 'ctxtools.rb', true },
rlxtools = { 'rlxtools.rb', true },
pdftools = { 'pdftools.rb', true },
mpstools = { 'mpstools.rb', true },
exatools = { 'exatools.rb', true },
xmltools = { 'xmltools.rb', true },
luatools = { 'luatools.lua', true },
mtxtools = { 'mtxtools.rb', true },
pdftrimwhite = { 'pdftrimwhite.pl', false }
}
if not messages then messages = { } end
messages.help = [[
--script run an mtx script
--execute run a script or program
--resolve resolve prefixed arguments
--ctxlua run internally (using preloaded libs)
--locate locate given filename
--autotree use texmf tree cf. env 'texmfstart_tree' or 'texmfstarttree'
--tree=pathtotree use given texmf tree (default file: 'setuptex.tmf')
--environment=name use given (tmf) environment file
--path=runpath go to given path before execution
--ifchanged=filename only execute when given file has changed (md checksum)
--iftouched=old,new only execute when given file has changed (time stamp)
--make create stubs for (context related) scripts
--remove remove stubs (context related) scripts
--stubpath=binpath paths where stubs wil be written
--windows create windows (mswin) stubs
--unix create unix (linux) stubs
--verbose give a bit more info
--engine=str target engine
--progname=str format or backend
--edit launch editor with found file
--launch (--all) launch files (assume os support)
--intern run script using built in libraries
]]
function input.runners.my_prepare_a(instance)
input.resetconfig(instance)
input.identify_cnf(instance)
input.load_lua(instance)
input.expand_variables(instance)
input.load_cnf(instance)
input.expand_variables(instance)
end
function input.runners.my_prepare_b(instance)
input.runners.my_prepare_a(instance)
input.load_hash(instance)
input.automount(instance)
end
function input.runners.prepare(instance)
local checkname = environment.argument("ifchanged")
if checkname and checkname ~= "" then
local oldchecksum = file.loadchecksum(checkname)
local newchecksum = file.checksum(checkname)
if oldchecksum == newchecksum then
report("file '" .. checkname .. "' is unchanged")
return "skip"
else
report("file '" .. checkname .. "' is changed, processing started")
end
file.savechecksum(checkname)
end
local oldname, newname = string.split(environment.argument("iftouched") or "", ",")
if oldname and newname and oldname ~= "" and newname ~= "" then
if not file.needs_updating(oldname,newname) then
report("file '" .. oldname .. "' and '" .. newname .. "'have same age")
return "skip"
else
report("file '" .. newname .. "' is older than '" .. oldname .. "'")
end
end
local tree = environment.argument('tree') or ""
if environment.argument('autotree') then
tree = os.getenv('TEXMFSTART_TREE') or os.getenv('TEXMFSTARTTREE') or tree
end
if tree and tree ~= "" then
input.load_tree(tree)
end
local env = environment.argument('environment') or ""
if env and env ~= "" then
for _,e in pairs(string.split(env)) do
-- maybe force suffix when not given
input.load_tree(e)
end
end
local runpath = environment.argument("path")
if runpath and not dir.chdir(runpath) then
input.report("unable to change to path '" .. runpath .. "'")
return "error"
end
return "run"
end
function input.runners.execute_script(instance,fullname,internal)
if fullname and fullname ~= "" then
local state = input.runners.prepare(instance)
if state == 'error' then
return false
elseif state == 'skip' then
return true
elseif state == "run" then
instance.progname = environment.argument("progname") or instance.progname
instance.format = environment.argument("format") or instance.format
local path, name, suffix, result = file.dirname(fullname), file.basename(fullname), file.extname(fullname), ""
if path ~= "" then
result = fullname
elseif name then
name = name:gsub("^int[%a]*:",function()
internal = true
return ""
end )
name = name:gsub("^script:","")
if suffix == "" and input.runners.registered[name] and input.runners.registered[name][1] then
name = input.runners.registered[name][1]
suffix = file.extname(name)
end
if suffix == "" then
-- loop over known suffixes
for _,s in pairs(input.runners.suffixes) do
result = input.find_file(instance, name .. "." .. s, 'texmfscripts')
if result ~= "" then
break
end
end
elseif input.runners.applications[suffix] then
result = input.find_file(instance, name, 'texmfscripts')
else
-- maybe look on path
result = input.find_file(instance, name, 'other text files')
end
end
if result and result ~= "" then
if internal then
local before, after = environment.split_arguments(fullname)
arg = { } for _,v in pairs(after) do arg[#arg+1] = v end
dofile(result)
else
local binary = input.runners.applications[file.extname(result)]
if binary and binary ~= "" then
result = binary .. " " .. result
end
local before, after = environment.split_arguments(fullname)
local command = result .. " " .. environment.reconstruct_commandline(after)
input.report("")
input.report("executing: " .. command)
input.report("\n \n")
io.flush()
local code = os.exec(command) -- maybe spawn
return code == 0
end
end
end
end
return false
end
function input.runners.execute_program(instance,fullname)
if fullname and fullname ~= "" then
local state = input.runners.prepare(instance)
if state == 'error' then
return false
elseif state == 'skip' then
return true
elseif state == "run" then
local before, after = environment.split_arguments(fullname)
environment.initialize_arguments(after)
fullname = fullname:gsub("^bin:","")
local command = fullname .. " " .. environment.reconstruct_commandline(after)
input.report("")
input.report("executing: " .. command)
input.report("\n \n")
io.flush()
local code = os.exec(command) -- (fullname,unpack(after)) does not work / maybe spawn
return code == 0
end
end
return false
end
function input.runners.handle_stubs(instance,create)
local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer supported
local windows = environment.argument('windows') or environment.argument('mswin') or false
local unix = environment.argument('unix') or environment.argument('linux') or false
if not windows and not unix then
if environment.platform == "unix" then
unix = true
else
windows = true
end
end
for _,v in pairs(input.runners.registered) do
local name, doit = v[1], v[2]
if doit then
local base = string.gsub(file.basename(name), "%.(.-)$", "")
if create then
-- direct local command = input.runners.applications[file.extname(name)] .. " " .. name
local command = "luatex --luaonly mtxrun.lua " .. name
if windows then
io.savedata(base..".bat", {"@echo off", command.." %*"}, "\013\010")
input.report("windows stub for '" .. base .. "' created")
end
if unix then
io.savedata(base, {"#!/bin/sh", command..' "$@"'}, "\010")
input.report("unix stub for '" .. base .. "' created")
end
else
if windows and (os.remove(base..'.bat') or os.remove(base..'.cmd')) then
input.report("windows stub for '" .. base .. "' removed")
end
if unix and (os.remove(base) or os.remove(base..'.sh')) then
input.report("unix stub for '" .. base .. "' removed")
end
end
end
end
end
function input.runners.resolve_string(instance,filename)
if filename and filename ~= "" then
input.runners.report_location(instance,input.resolve(instance,filename))
end
end
function input.runners.locate_file(instance,filename)
if filename and filename ~= "" then
input.runners.report_location(instance,input.find_given_file(instance,filename))
end
end
function input.runners.locate_platform(instance)
input.runners.report_location(instance,os.currentplatform())
end
function input.runners.report_location(instance,result)
if input.verbose then
input.report("")
if result and result ~= "" then
input.report(result)
else
input.report("not found")
end
else
io.write(result)
end
end
function input.runners.edit_script(instance,filename)
local editor = os.getenv("MTXRUN_EDITOR") or os.getenv("TEXMFSTART_EDITOR") or os.getenv("EDITOR") or 'scite'
local rest = input.resolve(instance,filename)
if rest ~= "" then
os.launch(editor .. " " .. rest)
end
end
function input.runners.save_script_session(filename, list)
local t = { }
for _, key in ipairs(list) do
t[key] = environment.arguments[key]
end
io.savedata(filename,table.serialize(t,true))
end
function input.runners.load_script_session(filename)
if lfs.isfile(filename) then
local t = io.loaddata(filename)
if t then
t = loadstring(t)
if t then t = t() end
for key, value in pairs(t) do
environment.arguments[key] = value
end
end
end
end
input.runners.launchers = {
windows = { },
unix = { }
}
function input.launch(str)
-- maybe we also need to test on mtxrun.launcher.suffix environment
-- variable or on windows consult the assoc and ftype vars and such
local launchers = input.runners.launchers[os.platform] if launchers then
local suffix = file.extname(str) if suffix then
local runner = launchers[suffix] if runner then
str = runner .. " " .. str
end
end
end
os.launch(str)
end
function input.runners.launch_file(instance,filename)
instance.allresults = true
input.verbose = true
local pattern = environment.arguments["pattern"]
if not pattern or pattern == "" then
pattern = filename
end
if not pattern or pattern == "" then
input.report("provide name or --pattern=")
else
local t = input.find_files(instance,pattern)
-- local t = input.aux.find_file(instance,"*/" .. pattern,true)
if t and #t > 0 then
if environment.arguments["all"] then
for _, v in pairs(t) do
input.report("launching", v)
input.launch(v)
end
else
input.report("launching", t[1])
input.launch(t[1])
end
else
input.report("no match for", pattern)
end
end
end
function input.runners.execute_ctx_script(instance,filename,arguments)
local function found(name)
local path = file.dirname(name)
if path and path ~= "" then
return false
else
local fullname = own and own.path and file.join(own.path,name)
return io.exists(fullname) and fullname
end
end
local suffix = ""
if not filename:find("%.lua$") then suffix = ".lua" end
local fullname = filename
-- just
fullname = filename .. suffix
fullname = input.find_file(instance,fullname)
-- mtx-
if not fullname or fullname == "" then
fullname = "mtx-" .. filename .. suffix
fullname = found(fullname) or input.find_file(instance,fullname)
end
-- mtx-s
if not fullname or fullname == "" then
fullname = "mtx-" .. filename .. "s" .. suffix
fullname = found(fullname) or input.find_file(instance,fullname)
end
-- mtx-
if not fullname or fullname == "" then
fullname = "mtx-" .. filename:gsub("s$","") .. suffix
fullname = found(fullname) or input.find_file(instance,fullname)
end
-- that should do it
if fullname and fullname ~= "" then
local state = input.runners.prepare(instance)
if state == 'error' then
return false
elseif state == 'skip' then
return true
elseif state == "run" then
-- load and save ... kind of undocumented
arg = { } for _,v in pairs(arguments) do arg[#arg+1] = v end
environment.initialize_arguments(arg)
local loadname = environment.arguments['load']
if loadname then
if type(loadname) ~= "string" then loadname = file.basename(fullname) end
loadname = file.replacesuffix(loadname,"cfg")
input.runners.load_script_session(loadname)
end
filename = environment.files[1]
if input.verbose then
input.report("using script: " .. fullname)
end
dofile(fullname)
local savename = environment.arguments['save']
if savename and input.runners.save_list and not table.is_empty(input.runners.save_list or { }) then
if type(savename) ~= "string" then savename = file.basename(fullname) end
savename = file.replacesuffix(savename,"cfg")
input.runners.save_script_session(savename, input.runners.save_list)
end
return true
end
else
input.verbose = true
input.report("unknown script: " .. filename)
return false
end
end
input.report(banner,"\n")
function input.help(banner,message)
if not input.verbose then
input.verbose = true
input.report(banner,"\n")
end
input.reportlines(message)
end
-- this is a bit dirty ... first we store the first filename and next we
-- split the arguments so that we only see the ones meant for this script
-- ... later we will use the second half
local filename = environment.files[1] or ""
local ok = true
local before, after = environment.split_arguments(filename)
input.runners.my_prepare_b(instance)
before = input.resolve(instance,before) -- experimental here
after = input.resolve(instance,after) -- experimental here
environment.initialize_arguments(before)
if environment.argument("selfmerge") then
-- embed used libraries
utils.merger.selfmerge(own.name,own.libs,own.list)
elseif environment.argument("selfclean") then
-- remove embedded libraries
utils.merger.selfclean(own.name)
elseif environment.argument("selfupdate") then
input.verbose = true
input.update_script(instance,own.name,"mtxrun")
elseif environment.argument("ctxlua") or environment.argument("internal") then
-- run a script by loading it (using libs)
ok = input.runners.execute_script(instance,filename,true)
elseif environment.argument("script") then
-- run a script by loading it (using libs), pass args
ok = input.runners.execute_ctx_script(instance,filename,after)
elseif environment.argument("execute") then
-- execute script
ok = input.runners.execute_script(instance,filename)
elseif environment.argument("direct") then
-- equals bin:
ok = input.runners.execute_program(instance,filename)
elseif environment.argument("edit") then
-- edit file
input.runners.edit_script(instance,filename)
elseif environment.argument("launch") then
input.runners.launch_file(instance,filename)
elseif environment.argument("make") then
-- make stubs
input.runners.handle_stubs(instance,true)
elseif environment.argument("remove") then
-- remove stub
input.runners.handle_stubs(instance,false)
elseif environment.argument("resolve") then
-- resolve string
input.runners.resolve_string(instance,filename)
elseif environment.argument("locate") then
-- locate file
input.runners.locate_file(instance,filename)
elseif environment.argument("platform")then
-- locate platform
input.runners.locate_platform(instance)
elseif environment.argument("help") or filename=='help' or filename == "" then
input.help(banner,messages.help)
-- execute script
if filename:find("^bin:") then
ok = input.runners.execute_program(instance,filename)
else
ok = input.runners.execute_script(instance,filename)
end
end
--~ if input.verbose then
--~ input.report("")
--~ input.report(string.format("runtime: %0.3f seconds",os.runtime()))
--~ end
--~ if ok then
--~ input.report("exit code: 0") os.exit(0)
--~ else
--~ input.report("exit code: 1") os.exit(1)
--~ end
if environment.platform == "unix" then
io.write("\n")
end