summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/scripts')
-rwxr-xr-xMaster/texmf-dist/scripts/context/lua/luatools.lua8684
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-babel.lua112
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-cache.lua10
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-chars.lua336
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-check.lua45
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-context.lua1118
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-convert.lua121
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-fonts.lua128
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-grep.lua112
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-interface.lua266
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-metatex.lua69
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-mptopdf.lua136
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-package.lua68
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-patterns.lua362
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-profile.lua164
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-server-ctx-fonttest.lua681
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-server-ctx-help.lua648
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-server-ctx-startup.lua53
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-server.lua359
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-texworks.lua96
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-timing.lua201
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-tools.lua57
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-unzip.lua101
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-update.lua431
-rw-r--r--Master/texmf-dist/scripts/context/lua/mtx-watch.lua49
-rwxr-xr-xMaster/texmf-dist/scripts/context/lua/mtxrun.lua12382
-rw-r--r--Master/texmf-dist/scripts/context/lua/x-ldx.lua64
-rw-r--r--Master/texmf-dist/scripts/context/perl/cont_mis.pm69
-rw-r--r--Master/texmf-dist/scripts/context/perl/cont_set.pm670
-rw-r--r--Master/texmf-dist/scripts/context/perl/texexec.pl3294
-rw-r--r--Master/texmf-dist/scripts/context/perl/texexec.rme169
-rw-r--r--Master/texmf-dist/scripts/context/perl/texutil.pl2914
-rw-r--r--Master/texmf-dist/scripts/context/perl/utiplug.pm30
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/exa.rb9
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/file.rb7
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/kpse.rb8
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/kpsetrees.rb65
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/state.rb4
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/tex.rb316
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/texutil.rb38
-rw-r--r--Master/texmf-dist/scripts/context/ruby/base/tool.rb15
-rw-r--r--Master/texmf-dist/scripts/context/ruby/ctxtools.rb252
-rw-r--r--Master/texmf-dist/scripts/context/ruby/fcd_start.rb19
-rw-r--r--Master/texmf-dist/scripts/context/ruby/graphics/gs.rb20
-rwxr-xr-xMaster/texmf-dist/scripts/context/ruby/kpseclient.rb42
-rwxr-xr-xMaster/texmf-dist/scripts/context/ruby/kpseserver.rb7
-rw-r--r--Master/texmf-dist/scripts/context/ruby/pdftools.rb3
-rw-r--r--Master/texmf-dist/scripts/context/ruby/rlxtools.rb9
-rw-r--r--Master/texmf-dist/scripts/context/ruby/rsfiltool.rb3
-rw-r--r--Master/texmf-dist/scripts/context/ruby/rslibtool.rb114
-rw-r--r--Master/texmf-dist/scripts/context/ruby/runtools.rb3
-rw-r--r--Master/texmf-dist/scripts/context/ruby/texexec.rb11
-rwxr-xr-xMaster/texmf-dist/scripts/context/ruby/texmfstart.rb1258
-rw-r--r--Master/texmf-dist/scripts/context/ruby/textools.rb3
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/admin.rb215
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/common.rb80
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/dir.rb155
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/exa.rb387
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/lib.rb1405
-rw-r--r--Master/texmf-dist/scripts/context/ruby/www/login.rb13
-rw-r--r--Master/texmf-dist/scripts/context/ruby/wwwclient.rb677
-rw-r--r--Master/texmf-dist/scripts/context/ruby/wwwserver.rb293
-rw-r--r--Master/texmf-dist/scripts/context/ruby/wwwwatch.rb497
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/exatools.bat2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/luatools.bat2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/luatools.lua7267
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/makempy.bat5
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/metatex.cmd5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/mpstools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat5
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua10512
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/mtxtools.bat5
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/mtxworks.cmd1
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/pdftools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/pdftrimwhite.bat2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/runtools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/texexec.bat5
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/texexec.cmd5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/texfind.bat2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/texfont.bat5
-rw-r--r--Master/texmf-dist/scripts/context/stubs/mswin/texmfstart.cmd5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/texshow.bat2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/textools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/texutil.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/tmftools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/mswin/xmltools.bat5
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/context1
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/ctxtools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/exatools2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/unix/luatools7267
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/makempy2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/unix/metatex2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/mpstools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/mptopdf2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/unix/mtxrun10512
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/mtxtools2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/unix/mtxworks2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/pdftools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/pdftrimwhite2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/pstopdf2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/rlxtools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/runtools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/texexec2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/texfind2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/texfont2
-rw-r--r--Master/texmf-dist/scripts/context/stubs/unix/texmfstart2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/texshow2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/textools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/texutil2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/tmftools2
-rwxr-xr-xMaster/texmf-dist/scripts/context/stubs/unix/xmltools2
114 files changed, 53045 insertions, 22586 deletions
diff --git a/Master/texmf-dist/scripts/context/lua/luatools.lua b/Master/texmf-dist/scripts/context/lua/luatools.lua
index 35986137950..433d1b8dc0a 100755
--- a/Master/texmf-dist/scripts/context/lua/luatools.lua
+++ b/Master/texmf-dist/scripts/context/lua/luatools.lua
@@ -1,23 +1,26 @@
#!/usr/bin/env texlua
+if not modules then modules = { } end modules ['luatools'] = {
+ version = 1.001,
+ comment = "companion to context.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format = string.format
+
-- one can make a stub:
--
-- #!/bin/sh
--- env LUATEXDIR=/....../texmf/scripts/context/lua luatex --luaonly=luatools.lua "$@"
--- filename : luatools.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
+-- env LUATEXDIR=/....../texmf/scripts/context/lua texlua luatools.lua "$@"
+
-- Although this script is part of the ConTeXt distribution it is
-- relatively indepent of ConTeXt. The same is true for some of
-- the luat files. We may may make them even less dependent in
-- the future. As long as Luatex is under development the
-- interfaces and names of functions may change.
-banner = "version 1.2.0 - 2006+ - PRAGMA ADE / CONTEXT"
-texlua = true
-
-- For the sake of independence we optionally can merge the library
-- code here. It's too much code, but that does not harm. Much of the
-- library code is used elsewhere. We don't want dependencies on
@@ -26,158 +29,43 @@ texlua = true
-- needed when texmfstart is used, or when the proper stub is used or
-- when (windows) suffix binding is active.
+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
+do -- create closure to overcome 200 locals limit
-function string:split(separator)
- local t = {}
- for k in self:splitter(separator) do t[#t+1] = k end
- return t
-end
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
--- faster than a string:split:
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
-function string:splitchr(chr)
- if #self > 0 then
- local t = { }
- for s in string.gmatch(self..chr,"(.-)"..chr) do
- t[#t+1] = s
+if not string.split then
+
+ -- this will be overloaded by a faster lpeg variant
+
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
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 = {
+local chr_to_esc = {
["%"] = "%%",
["."] = "%.",
["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
@@ -187,21 +75,23 @@ string.chr_to_esc = {
["{"] = "%{", ["}"] = "%}"
}
+string.chr_to_esc = chr_to_esc
+
function string:esc() -- variant 2
- return (self:gsub("(.)",string.chr_to_esc))
+ return (gsub(self,"(.)",chr_to_esc))
end
-function string.unquote(str)
- return (str:gsub("^([\"\'])(.*)%1$","%2"))
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
end
-function string.quote(str)
- return '"' .. str:unquote() .. '"'
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
end
function string:count(pattern) -- variant 3
local n = 0
- for _ in self:gmatch(pattern) do
+ for _ in gmatch(self,pattern) do
n = n + 1
end
return n
@@ -210,29 +100,25 @@ end
function string:limit(n,sentinel)
if #self > n then
sentinel = sentinel or " ..."
- return self:sub(1,(n-#sentinel)) .. sentinel
+ return sub(self,1,(n-#sentinel)) .. sentinel
else
return self
end
end
function string:strip()
- return (self:gsub("^%s*(.-)%s*$", "%1"))
+ return (gsub(self,"^%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")
+ return not find(find,"%S")
end
function string:enhance(pattern,action)
local ok, n = true, 0
while ok do
ok = false
- self = self:gsub(pattern, function(...)
+ self = gsub(self,pattern, function(...)
ok, n = true, n + 1
return action(...)
end)
@@ -240,59 +126,19 @@ function string:enhance(pattern,action)
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 = { }
+local chr_to_hex, 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
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], 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))
+ return (gsub(self or "","(.)",chr_to_hex))
end
function string:from_hex()
- return ((self or ""):gsub("(..)",string.hex_to_chr))
+ return (gsub(self or "","(..)",hex_to_chr))
end
if not string.characters then
@@ -306,7 +152,7 @@ if not string.characters then
end
local function nextbyte(str, index)
index = index + 1
- return (index <= #str) and index or nil, string.byte(str:sub(index,index))
+ return (index <= #str) and index or nil, byte(str:sub(index,index))
end
function string:bytes()
return nextbyte, self, 0
@@ -314,9 +160,7 @@ if not string.characters then
end
---~ function string:padd(n,chr)
---~ return self .. self.rep(chr or " ",n-#self)
---~ end
+-- we can use format for this (neg n)
function string:rpadd(n,chr)
local m = n-#self
@@ -338,8 +182,8 @@ end
string.padd = string.rpadd
-function is_number(str)
- return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+function is_number(str) -- tonumber
+ return find(str,"^[%-%+]?[%d]-%.?[%d+]$") == 1
end
--~ print(is_number("1"))
@@ -351,9 +195,9 @@ end
--~ print(is_number("+.1"))
function string:split_settings() -- no {} handling, see l-aux for lpeg variant
- if self:find("=") then
+ if find(self,"=") then
local t = { }
- for k,v in self:gmatch("(%a+)=([^%,]*)") do
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
t[k] = v
end
return t
@@ -375,24 +219,72 @@ local patterns_escapes = {
}
function string:pattesc()
- return (self:gsub(".",patterns_escapes))
+ return (gsub(self,".",patterns_escapes))
end
function string:tohash()
local t = { }
- for s in self:gmatch("([^, ]+)") do -- lpeg
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
t[s] = true
end
return t
end
+local pattern = lpeg.Ct(lpeg.C(1)^0)
+
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
+
--- filename : l-lpeg.lua
--- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
--- copyright: PRAGMA ADE / ConTeXt Development Team
--- license : see context related readme files
+end -- of closure
-if not versions then versions = { } end versions['l-lpeg'] = 1.001
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
--~ l-lpeg.lua :
@@ -416,52 +308,101 @@ if not versions then versions = { } end versions['l-lpeg'] = 1.001
local hash = { }
function lpeg.anywhere(pattern) --slightly adapted from website
- return lpeg.P { lpeg.P(pattern) + 1 * lpeg.V(1) }
+ return P { P(pattern) + 1 * lpeg.V(1) }
end
function lpeg.startswith(pattern) --slightly adapted
- return lpeg.P(pattern)
+ return 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
+ return (((1-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")
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
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 empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
local content = (empty + nonempty)^1
-local capture = lpeg.Ct(content^0)
+local capture = Ct(content^0)
function string:splitlines()
return capture:match(self)
end
+lpeg.linebyline = content -- better make a sublibrary
--- 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
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
-if not versions then versions = { } end versions['l-table'] = 1.001
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c:match(self)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
table.join = table.concat
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
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")
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
if s == "" then
-- skip this one
else
@@ -471,18 +412,13 @@ function table.strip(tab)
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
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
-function table.sortedkeys(tab)
+local function sortedkeys(tab)
local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
- for key,_ in pairs(tab) do
+ for key,_ in next, tab do
srt[#srt+1] = key
if kind == 3 then
-- no further check
@@ -500,22 +436,45 @@ function table.sortedkeys(tab)
end
end
if kind == 0 or kind == 3 then
- table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+ sort(srt,compare)
else
- table.sort(srt)
+ sort(srt)
+ end
+ return srt
+end
+
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
end
+ sort(srt)
return srt
end
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
function table.append(t, list)
- for _,v in pairs(list) do
- table.insert(t,v)
+ for _,v in next, list do
+ insert(t,v)
end
end
function table.prepend(t, list)
- for k,v in pairs(list) do
- table.insert(t,k,v)
+ for k,v in next, list do
+ insert(t,k,v)
end
end
@@ -523,7 +482,7 @@ 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
+ for k, v in next, lst[i] do
t[k] = v
end
end
@@ -533,7 +492,7 @@ end
function table.merged(...)
local tmp, lst = { }, {...}
for i=1,#lst do
- for k, v in pairs(lst[i]) do
+ for k, v in next, lst[i] do
tmp[k] = v
end
end
@@ -562,70 +521,58 @@ function table.imerged(...)
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)
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
end
- return new
- else
- return { }
end
+ -- optional second arg
+ 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]
+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 next, t do -- brrr, what happens with sparse indexed
+ if type(i) == "table" then
+ if tables[i] then
+ i = tables[i]
else
- tcopy[i] = copy(v, tables)
+ i = copy(i, tables)
end
end
- local mt = getmetatable(t)
- if mt then
- setmetatable(tcopy,mt)
+ if type(v) ~= "table" then
+ tcopy[i] = v
+ elseif tables[v] then
+ tcopy[i] = tables[v]
+ else
+ tcopy[i] = copy(v, tables)
end
- return tcopy
end
+ local mt = getmetatable(t)
+ if mt then
+ setmetatable(tcopy,mt)
+ end
+ return tcopy
+end
- table.copy = copy
-
-end end
+table.fastcopy = fastcopy
+table.copy = copy
-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
@@ -634,7 +581,7 @@ function table.sub(t,i,j)
end
function table.replace(a,b)
- for k,v in pairs(b) do
+ for k,v in next, b do
a[k] = v
end
end
@@ -654,344 +601,483 @@ 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..'"]'
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
end
end
+ return h
+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)
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
else
- tt = nil
- break
+ tt[#tt+1] = tostring(v) -- tostring not needed
end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
end
- return tt
end
+ return tt
end
- return nil
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))
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
else
- handle(("%s%s={"):format(depth,key(name)))
+ handle(format("%s[%q]={",depth,name))
end
else
- depth = ""
- local tname = type(name)
- if tname == "string" then
- if name == "return" then
- handle("return {")
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
else
- handle(name .. "={")
+ handle(format("%s %q,",depth,tostring(v)))
end
- elseif tname == "number" then
- handle("[" .. name .. "]={")
- elseif tname == "boolean" then
- if name then
- handle("return {")
- else
- handle("{")
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
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
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
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))
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
else
- handle(("%s %q,"):format(depth,v))
+ handle(format("%s [%s]=%s,",depth,k,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
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ handle(format("%s [%s]=%q,",depth,k,v))
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)))
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
else
- handle(('%s "function",'):format(depth))
+ handle(format("%s [%s]={},",depth,k))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
else
- handle(("%s %q,"):format(depth,tostring(v)))
+ handle(format("%s [%q]={},",depth,k))
end
- elseif k == "__p__" then -- parent
- if false then
- handle(("%s __p__=nil,"):format(depth))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
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
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
else
- handle(("%s %s=%q,"):format(depth,key(k),v))
+ handle(format("%s [%s]=%s,",depth,k,tostring(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,", ")))
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
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)))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
else
- handle(('%s %s="function",'):format(depth,key(k)))
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
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)))
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
end
end
- if level > 0 then
- handle(("%s},"):format(depth))
- else
- handle(("%s}"):format(depth))
- end
- else
- handle(("%s}"):format(depth))
+ --~ end
end
end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
- --~ name:
- --~
- --~ true : return { }
- --~ false : { }
- --~ nil : t = { }
- --~ string : string = { }
- --~ 'return' : return { }
- --~ number : [number] = { }
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
- function table.serialize(root,name,reduce,noquotes)
- local t = { }
- local function flush(s)
- t[#t+1] = s
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
end
- serialize(root, name, flush, nil, 0, reduce, noquotes)
- return table.concat(t,"\n")
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
+ end
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
- function table.tohandle(handle,root,name,reduce,noquotes)
- serialize(root, name, handle, nil, 0, reduce, noquotes)
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+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
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+end
- table.tofile_maxtab = 2*1024
+-- 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
- 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")
+table.tofile_maxtab = 2*1024
+
+function table.tofile(filename,root,name,reduce,noquotes,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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
- serialize(root, name, flush, nil, 0, reduce, noquotes)
end
- f:close()
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
end
+ f:close()
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
+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.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
+function table.unnest(t) -- bad name
+ local f = { }
+ flatten(t,f,false)
+ return f
+end
+
+table.flatten_one_level = table.unnest
- table.flatten_one_level = table.unnest
+-- the next three may disappear
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
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
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
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
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
end
- t[#t+1] = str
end
-function table.are_equal(a,b,n,m)
- if #a == #b then
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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
+ if ai==bi then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
else
return false
end
@@ -1002,37 +1088,42 @@ function table.are_equal(a,b,n,m)
end
end
-function table.compact(t)
- if t then
- for k,v in pairs(t) do
- if not next(v) then
- t[k] = nil
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
end
+ else
+ return false
end
end
+ return true
end
-function table.tohash(t)
- local h = { }
- for _, v in pairs(t) do -- no ipairs here
- h[v] = true
- end
- return h
-end
+table.are_equal = are_equal
+table.identical = identical
-function table.fromhash(t)
- local h = { }
- for k, v in pairs(t) do -- no ipairs here
- if v then h[#h+1] = k end
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ 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
+ return i
end
end
end
@@ -1049,7 +1140,7 @@ end
function table.swapped(t)
local s = { }
- for k, v in pairs(t) do
+ for k, v in next, t do
s[v] = k
end
return s
@@ -1069,17 +1160,16 @@ function table.clone(t,p) -- t is optional or nil or table
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 " ")
+ for i=1,#t do tt[i] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
end
function table.reverse_hash(h)
local r = { }
- for k,v in pairs(h) do
- r[v] = (k:gsub(" ","")):lower()
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
end
return r
end
@@ -1094,14 +1184,36 @@ function table.reverse(t)
return tt
end
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ end
+
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ 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
+end -- of closure
-if not versions then versions = { } end versions['l-io'] = 1.001
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
+
+local byte = string.byte
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator = "\\", ";"
@@ -1109,10 +1221,12 @@ else
io.fileseparator, io.pathseparator = "/" , ":"
end
-function io.loaddata(filename)
- local f = io.open(filename,'rb')
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
local data = f:read('*all')
+ -- garbagecollector.check(data)
f:close()
return data
else
@@ -1121,7 +1235,7 @@ function io.loaddata(filename)
end
function io.savedata(filename,data,joiner)
- local f = io.open(filename, "wb")
+ local f = io.open(filename,"wb")
if f then
if type(data) == "table" then
f:write(table.join(data,joiner or ""))
@@ -1131,6 +1245,9 @@ function io.savedata(filename,data,joiner)
f:write(data)
end
f:close()
+ return true
+ else
+ return false
end
end
@@ -1164,146 +1281,83 @@ function io.noflines(f)
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
+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
-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
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
end
- }
-
- function io.bytes(f,n)
- if f then
- return nextbyte[n or 1], f
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
else
return nil, nil
end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
function io.ask(question,default,options)
@@ -1339,15 +1393,21 @@ function io.ask(question,default,options)
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
+end -- of closure
-if not versions then versions = { } end versions['l-number'] = 1.001
+do -- create closure to overcome 200 locals limit
-if not number then number = { } end
+if not modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
+
+local format = string.format
+
+number = number or { }
-- a,b,c,d,e,f = number.toset(100101)
@@ -1355,8 +1415,6 @@ 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
@@ -1377,72 +1435,89 @@ end
--
-- of course dedicated "(.)(.)(.)(.)" matches are even faster
-do
- local one = lpeg.C(1-lpeg.S(''))^1
+local one = lpeg.C(1-lpeg.S(''))^1
- function number.toset(n)
- return one:match(tostring(n))
- end
+function number.toset(n)
+ return one:match(tostring(n))
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
+end -- of closure
-if not set then set = { } end
+do -- create closure to overcome 200 locals limit
-do
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
- local nums = { }
- local tabs = { }
- local concat = table.concat
+set = set or { }
- set.create = table.tohash
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
- 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
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
end
- return nums[s]
- else
- return 0
end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
end
+end
- function set.totable(n)
- if n == 0 then
- return { }
- else
- return tabs[n] or { }
- 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]
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
end
+ return concat(t," ")
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
--~ local c = set.create{'aap','noot','mies'}
@@ -1459,13 +1534,19 @@ end
--- 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
+end -- of closure
-if not versions then versions = { } end versions['l-os'] = 1.001
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
function os.resultof(command)
return io.popen(command,"r"):read("*all")
@@ -1478,7 +1559,7 @@ if not os.spawn then os.spawn = os.execute end
--~ os.name : windows | msdos | linux | macosx | solaris | .. | generic (new)
if not io.fileseparator then
- if string.find(os.getenv("PATH"),";") then
+ if 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"
@@ -1516,11 +1597,10 @@ end
os.gettimeofday = os.gettimeofday or os.clock
-do
- local startuptime = os.gettimeofday()
- function os.runtime()
- return os.gettimeofday() - startuptime
- end
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
end
--~ print(os.gettimeofday()-os.time())
@@ -1529,43 +1609,92 @@ end
--~ print(os.date("%H:%M:%S",os.gettimeofday()))
--~ print(os.date("%H:%M:%S",os.time()))
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+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 platform
- local function convert(str,fmt)
- return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
end
+ return platform
+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
+end -- of closure
+do -- create closure to overcome 200 locals limit
--- 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 modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
-if not versions then versions = { } end versions['l-file'] = 1.001
+-- needs a cleanup
-if not file then file = { } end
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
function file.removesuffix(filename)
- return filename:gsub("%.[%a%d]+$", "")
+ return (gsub(filename,"%.[%a%d]+$",""))
end
function file.addsuffix(filename, suffix)
- if not filename:find("%.[%a%d]+$") then
+ if not find(filename,"%.[%a%d]+$") then
return filename .. "." .. suffix
else
return filename
@@ -1573,43 +1702,27 @@ function file.addsuffix(filename, suffix)
end
function file.replacesuffix(filename, suffix)
- if not filename:find("%.[%a%d]+$") then
- return filename .. "." .. suffix
- else
- return (filename:gsub("%.[%a%d]+$","."..suffix))
- end
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
end
-function file.dirname(name)
- return name:match("^(.+)[/\\].-$") or ""
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
end
function file.basename(name)
- return name:match("^.+[/\\](.-)$") or name
+ return match(name,"^.+[/\\](.-)$") or name
end
function file.nameonly(name)
- return ((name:match("^.+[/\\](.-)$") or name):gsub("%..*$",""))
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
end
function file.extname(name)
- return name:match("^.+%.([^/\\]-)$") or ""
+ return match(name,"^.+%.([^/\\]-)$") 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"))
@@ -1617,97 +1730,73 @@ end
--~ print(file.join("//nas-1","/y"))
function file.join(...)
- local pth = table.concat({...},"/")
- pth = pth:gsub("\\","/")
- local a, b = pth:match("^(.*://)(.*)$")
+ local pth = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
if a and b then
- return a .. b:gsub("//+","/")
+ return a .. gsub(b,"//+","/")
end
- a, b = pth:match("^(//)(.*)$")
+ a, b = match(pth,"^(//)(.*)$")
if a and b then
- return a .. b:gsub("//+","/")
+ return a .. gsub(b,"//+","/")
end
- return (pth:gsub("//+","/"))
+ return (gsub(pth,"//+","/"))
end
-function file.is_writable(name)
- local f = io.open(name, 'w')
- if f then
- f:close()
- return true
- else
- return false
- end
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
end
-function file.is_readable(name)
- local f = io.open(name,'r')
- if f then
- f:close()
- return true
- else
- return false
- end
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
end
---~ function file.split_path(str)
---~ if str:find(';') then
---~ return str:splitchr(";")
---~ else
---~ return str:splitchr(io.pathseparator)
---~ end
---~ end
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
-- 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
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
if name ~= "" then
- name = name:gsub("\001",":")
- t[#t+1] = name
+ t[#t+1] = gsub(name,"\001",":")
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
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
function file.collapse_path(str)
- local n = 1
- while n > 0 do
- str, n = str:gsub("([^/%.]+/%.%./)","")
- end
- return (str:gsub("/%./","/"))
-end
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
function file.robustname(str)
- return (str:gsub("[^%a%d%/%-%.\\]+","-"))
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
end
file.readdata = io.loaddata
@@ -1717,14 +1806,226 @@ function file.copy(oldname,newname)
file.savedata(newname,io.loaddata(oldname))
end
+-- lpeg variants, slightly faster, not always
+
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
--- filename : l-url.lua
--- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
--- copyright: PRAGMA ADE / ConTeXt Development Team
--- license : see context related readme files
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
-if not versions then versions = { } end versions['l-url'] = 1.001
-if not url then url = { } end
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-url'] = {
+ 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"
+}
+
+local char, gmatch = string.char, string.gmatch
+local tonumber, type = tonumber, type
-- from the spec (on the web):
--
@@ -1736,29 +2037,28 @@ if not url then url = { } end
-- / \ / \
-- urn:example:animal:ferret:nose
-do
+url = url or { }
- local function tochar(s)
- return string.char(tonumber(s,16))
- end
+local function tochar(s)
+ return char(tonumber(s,16))
+end
- local colon, qmark, hash, slash, percent, endofstring = lpeg.P(":"), lpeg.P("?"), lpeg.P("#"), lpeg.P("/"), lpeg.P("%"), lpeg.P(-1)
+local colon, qmark, hash, slash, percent, endofstring = lpeg.P(":"), lpeg.P("?"), lpeg.P("#"), lpeg.P("/"), lpeg.P("%"), lpeg.P(-1)
- local hexdigit = lpeg.R("09","AF","af")
- local escaped = percent * lpeg.C(hexdigit * hexdigit) / tochar
+local hexdigit = lpeg.R("09","AF","af")
+local plus = lpeg.P("+")
+local escaped = (plus / " ") + (percent * lpeg.C(hexdigit * hexdigit) / tochar)
- local scheme = lpeg.Cs((escaped+(1-colon-slash-qmark-hash))^0) * colon + lpeg.Cc("")
- local authority = slash * slash * lpeg.Cs((escaped+(1- slash-qmark-hash))^0) + lpeg.Cc("")
- local path = slash * lpeg.Cs((escaped+(1- qmark-hash))^0) + lpeg.Cc("")
- local query = qmark * lpeg.Cs((escaped+(1- hash))^0) + lpeg.Cc("")
- local fragment = hash * lpeg.Cs((escaped+(1- endofstring))^0) + lpeg.Cc("")
+local scheme = lpeg.Cs((escaped+(1-colon-slash-qmark-hash))^0) * colon + lpeg.Cc("")
+local authority = slash * slash * lpeg.Cs((escaped+(1- slash-qmark-hash))^0) + lpeg.Cc("")
+local path = slash * lpeg.Cs((escaped+(1- qmark-hash))^0) + lpeg.Cc("")
+local query = qmark * lpeg.Cs((escaped+(1- hash))^0) + lpeg.Cc("")
+local fragment = hash * lpeg.Cs((escaped+(1- endofstring))^0) + lpeg.Cc("")
- local parser = lpeg.Ct(scheme * authority * path * query * fragment)
-
- function url.split(str)
- return (type(str) == "string" and parser:match(str)) or str
- end
+local parser = lpeg.Ct(scheme * authority * path * query * fragment)
+function url.split(str)
+ return (type(str) == "string" and parser:match(str)) or str
end
function url.hashed(str)
@@ -1781,7 +2081,7 @@ end
function url.query(str)
if type(str) == "string" then
local t = { }
- for k, v in str:gmatch("([^&=]*)=([^&=]*)") do
+ for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
t[k] = v
end
return t
@@ -1796,12 +2096,12 @@ end
--~ print(url.filename("file:///etc/test.txt"))
--~ print(url.filename("/oeps.txt"))
--- from the spec on the web (sort of):
+--~ from the spec on the web (sort of):
--~
--~ function test(str)
--~ print(table.serialize(url.hashed(str)))
--~ end
----~
+--~
--~ test("%56pass%20words")
--~ test("file:///c:/oeps.txt")
--~ test("file:///c|/oeps.txt")
@@ -1823,265 +2123,210 @@ end
--~ test("zip:///oeps/oeps.zip?bla/bla.tex")
--- 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
+end -- of closure
-if not versions then versions = { } end versions['l-dir'] = 1.001
+do -- create closure to overcome 200 locals limit
-dir = { }
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
--- optimizing for no string.find (*) does not save time
+local type = type
+local find, gmatch = string.find, string.gmatch
-if lfs then do
+dir = dir or { }
---~ 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
+-- optimizing for no string.find (*) does not save time
- local attributes = lfs.attributes
- local walkdir = lfs.dir
+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)
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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)
- }
+dir.glob_pattern = glob_pattern
- local filter = Cs ( (
- P("**") / ".*" +
- P("*") / "[^/]*" +
- P("?") / "[^/]" +
- P(".") / "%%." +
- P("+") / "%%+" +
- P("-") / "%%-" +
- P(1)
- )^0 )
-
- local function glob(str,t)
- if type(str) == "table" then
+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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ return t
+ else
+ local split = pattern:match(str)
+ if split then
local t = t or { }
- for _, s in ipairs(str) do
- glob(s,t)
- end
+ local action = action or function(name) t[#t+1] = name end
+ local root, path, base = split[1], split[2], split[3]
+ local recurse = find(base,"%*%*")
+ local start = root .. path
+ local result = filter:match(start .. base)
+ glob_pattern(start,result,recurse,action)
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
+ return { }
end
end
+end
- dir.glob = glob
+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")
+--~ 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
+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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
if recurse then
globfiles(path .. "/" .. name,recurse,func,files)
end
- elseif func then
- if func(name) then
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
files[#files+1] = path .. "/" .. name
end
- else
- files[#files+1] = path .. "/" .. name
end
end
- return files
end
+ return files
+end
- dir.globfiles = globfiles
+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"))
+-- 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
+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")
+--~ mkdirs("temp")
+--~ mkdirs("a/b/c")
+--~ mkdirs(".","/a/b/c")
+--~ mkdirs("a","b","c")
- local make_indeed = true -- false
+local make_indeed = true -- false
- if string.find(os.getenv("PATH"),";") then
+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
+ 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
- local first, middle, last
- local drive = false
- first, middle, last = str:match("^(//)(//*)(.*)$")
+ 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
- -- empty network path == local path
+ middle, last = str:match("([^/]+)/+(.-)$")
+ if middle then
+ pth = "//" .. middle
+ else
+ pth = "//" .. last
+ last = ""
+ end
else
- first, last = str:match("^(//)/*(.-)$")
+ first, middle, last = str:match("^([a-zA-Z]:)(/*)(.-)$")
if first then
- middle, last = str:match("([^/]+)/+(.-)$")
- if middle then
- pth = "//" .. middle
- else
- pth = "//" .. last
- last = ""
- end
+ pth, drive = first .. middle, true
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
+ middle, last = str:match("^(/*)(.-)$")
+ if not middle then
+ last = str
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
+ for s in gmatch(last,"[^/]+") 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
- return pth, (lfs.isdir(pth) == true)
end
+ return pth, (lfs.isdir(pth) == true)
+ end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
@@ -2095,79 +2340,79 @@ if lfs then do
--~ 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)
+ 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 find(last,"^/") then
+ local d = lfs.currentdir()
+ if lfs.chdir(first) then
+ first = lfs.currentdir()
+ first = first:gsub("\\","/")
end
+ lfs.chdir(d)
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
+ 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
+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
+ 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
- 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
+ end
+ str = str:gsub("/+","/")
+ if find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") do
+ local first = (pth == "/")
+ if first then
+ pth = pth .. s
+ else
pth = pth .. "/" .. s
- if make_indeed and not lfs.isdir(pth) then
- lfs.mkdir(pth)
- end
+ end
+ if make_indeed and not first and not lfs.isdir(pth) then
+ lfs.mkdir(pth)
+ end
+ end
+ else
+ pth = "."
+ for s in gmatch(str,"[^/]+") do
+ pth = pth .. "/" .. s
+ if make_indeed and not lfs.isdir(pth) then
+ lfs.mkdir(pth)
end
end
- return pth, (lfs.isdir(pth) == true)
end
+ return pth, (lfs.isdir(pth) == true)
+ end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
@@ -2177,30 +2422,35 @@ if lfs then do
--~ 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
+ function dir.expand_name(str)
+ if not find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
end
-
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
end
- dir.makedirs = dir.mkdirs
+end
-end end
+dir.makedirs = dir.mkdirs
--- 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
+end -- of closure
-if not versions then versions = { } end versions['l-boolean'] = 1.001
-if not boolean then boolean = { } end
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
+
+local type, tonumber = type, tonumber
function boolean.tonumber(b)
if b then return 1 else return 0 end
@@ -2210,7 +2460,7 @@ 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"
+ return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t"
elseif tstr == "number" then
return tonumber(str) ~= 0
elseif tstr == "nil" then
@@ -2229,9 +2479,9 @@ end
function string.is_boolean(str)
if type(str) == "string" then
- if str == "true" or str == "yes" or str == "on" then
+ if str == "true" or str == "yes" or str == "on" or str == "t" then
return true
- elseif str == "false" or str == "no" or str == "off" then
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
return false
end
end
@@ -2247,21 +2497,24 @@ function boolean.falsetrue()
end
--- filename : l-unicode.lua
--- comment : split off from luat-inp
--- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
--- copyright: PRAGMA ADE / ConTeXt Development Team
--- license : see context related readme files
+end -- of closure
-if not versions then versions = { } end versions['l-unicode'] = 1.001
-if not unicode then unicode = { } end
+do -- create closure to overcome 200 locals limit
-if not garbagecollector then
- garbagecollector = {
- push = function() collectgarbage("stop") end,
- pop = function() collectgarbage("restart") end,
- }
-end
+if not modules then modules = { } end modules ['l-unicode'] = {
+ 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"
+}
+
+utf = utf or unicode.utf8
+
+local concat, utfchar, utfgsub = table.concat, utf.char, utf.gsub
+local char, byte, find, bytepairs = string.char, string.byte, string.find, string.bytepairs
+
+unicode = unicode or { }
-- 0 EF BB BF UTF-8
-- 1 FF FE UTF-16-little-endian
@@ -2282,17 +2535,17 @@ function unicode.utftype(f) -- \000 fails !
if not str then
f:seek('set')
return 0
- elseif str:find("^%z%z\254\255") then
+ elseif find(str,"^%z%z\254\255") then
return 4
- elseif str:find("^\255\254%z%z") then
+ elseif find(str,"^\255\254%z%z") then
return 3
- elseif str:find("^\254\255") then
+ elseif find(str,"^\254\255") then
f:seek('set',2)
return 2
- elseif str:find("^\255\254") then
+ elseif find(str,"^\255\254") then
f:seek('set',2)
return 1
- elseif str:find("^\239\187\191") then
+ elseif find(str,"^\239\187\191") then
f:seek('set',3)
return 0
else
@@ -2301,74 +2554,70 @@ function unicode.utftype(f) -- \000 fails !
end
end
-function unicode.utf16_to_utf8(str, endian)
- garbagecollector.push()
- local result = { }
- local tc, uc = table.concat, unicode.utf8.char
- local tmp, n, m, p = { }, 0, 0, 0
+function unicode.utf16_to_utf8(str, endian) -- maybe a gsub is faster or an lpeg
+ local result, tmp, n, m, p = { }, { }, 0, 0, 0
-- lf | cr | crlf / (cr:13, lf:10)
local function doit()
if n == 10 then
if p ~= 13 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
tmp = { }
p = 0
end
elseif n == 13 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
tmp = { }
p = n
else
- tmp[#tmp+1] = uc(n)
+ tmp[#tmp+1] = utfchar(n)
p = 0
end
end
- for l,r in str:bytepairs() do
- if endian then
- n = l*256 + r
- else
- n = r*256 + l
- end
- if m > 0 then
- n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
- m = 0
- doit()
- elseif n >= 0xD800 and n <= 0xDBFF then
- m = n
- else
- doit()
+ for l,r in bytepairs(str) do
+ if r then
+ if endian then
+ n = l*256 + r
+ else
+ n = r*256 + l
+ end
+ if m > 0 then
+ n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
+ m = 0
+ doit()
+ elseif n >= 0xD800 and n <= 0xDBFF then
+ m = n
+ else
+ doit()
+ end
end
end
if #tmp > 0 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
end
- garbagecollector.pop()
return result
end
function unicode.utf32_to_utf8(str, endian)
- garbagecollector.push()
local result = { }
- local tc, uc = table.concat, unicode.utf8.char
local tmp, n, m, p = { }, 0, -1, 0
-- lf | cr | crlf / (cr:13, lf:10)
local function doit()
if n == 10 then
if p ~= 13 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
tmp = { }
p = 0
end
elseif n == 13 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
tmp = { }
p = n
else
- tmp[#tmp+1] = uc(n)
+ tmp[#tmp+1] = utfchar(n)
p = 0
end
end
- for a,b in str:bytepairs() do
+ for a,b in bytepairs(str) do
if a and b then
if m < 0 then
if endian then
@@ -2390,20 +2639,102 @@ function unicode.utf32_to_utf8(str, endian)
end
end
if #tmp > 0 then
- result[#result+1] = tc(tmp,"")
+ result[#result+1] = concat(tmp)
end
- garbagecollector.pop()
return result
end
+local function little(c)
+ local b = byte(c) -- b = c:byte()
+ if b < 0x10000 then
+ return char(b%256,b/256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1%256,b1/256,b2%256,b2/256)
+ end
+end
+
+local function big(c)
+ local b = byte(c)
+ if b < 0x10000 then
+ return char(b/256,b%256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1/256,b1%256,b2/256,b2%256)
+ end
+end
+
+function unicode.utf8_to_utf16(str,littleendian)
+ if littleendian then
+ return char(255,254) .. utfgsub(str,".",little)
+ else
+ return char(254,255) .. utfgsub(str,".",big)
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
+ 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"
+}
+
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
--- 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 math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
-if not versions then versions = { } end versions['l-utils'] = 1.001
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
if not utils then utils = { } end
if not utils.merger then utils.merger = { } end
@@ -2429,11 +2760,20 @@ function utils.report(...)
print(...)
end
+utils.merger.strip_comment = true
+
function utils.merger._self_load_(name)
local f, data = io.open(name), ""
if f then
+ utils.report("reading merge from %s",name)
data = f:read("*all")
f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
end
return data or ""
end
@@ -2442,6 +2782,7 @@ function utils.merger._self_save_(name, data)
if data ~= "" then
local f = io.open(name,'w')
if f then
+ utils.report("saving merge from %s",name)
f:write(data)
f:close()
end
@@ -2458,24 +2799,52 @@ function utils.merger._self_swap_(data,code)
end
end
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
function utils.merger._self_libs_(libs,list)
- local result, f = { }, nil
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
if type(libs) == 'string' then libs = { libs } end
if type(list) == 'string' then list = { list } end
+ local foundpath = nil
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
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
else
- -- utils.report("no library",name)
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
end
end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
end
return table.concat(result, "\n\n")
end
@@ -2512,108 +2881,549 @@ function utils.merger.selfclean(name)
)
end
-utils.lua.compile_strip = true
-
-function utils.lua.compile(luafile, lucfile)
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
-- utils.report("compiling",luafile,"into",lucfile)
os.remove(lucfile)
local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
- if utils.lua.compile_strip then
+ if strip ~= false then
command = "-s " .. command
end
- if os.spawn("texluac " .. command) == 0 then
- return true
- elseif os.spawn("luac " .. command) == 0 then
- return true
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
else
- return false
+ return (pattern_a/set)^1
end
end
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ 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
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
-if not versions then versions = { } end versions['luat-lib'] = 1.001
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
--- mostcode moved to the l-*.lua and other luat-*.lua files
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
--- os / io
+aux.settings_to_array_pattern = pattern
-os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
--- os.platform
+local function set(t,v)
+ t[#t+1] = v
+end
--- mswin|bccwin|mingw|cygwin windows
--- darwin|rhapsody|nextstep macosx
--- netbsd|unix unix
--- linux linux
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
-if not io.fileseparator then
- if string.find(os.getenv("PATH"),";") then
- io.fileseparator, io.pathseparator, os.platform = "\\", ";", os.type or "windows"
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
else
- io.fileseparator, io.pathseparator, os.platform = "/" , ":", os.type or "unix"
+ return ""
end
end
-os.platform = os.platform or os.type or (io.pathseparator == ";" and "windows") or "unix"
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
--- arg normalization
---
--- for k,v in pairs(arg) do print(k,v) end
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
--- environment
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
-if not environment then environment = { } end
+-- temporary here
-environment.ownbin = environment.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
-local ownpath = nil -- we could use a metatable here
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
-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
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
+
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
+
+debugger = debugger or { }
+
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
end
end
- if not ownpath then ownpath = '.' end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
+
+-- two
+
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
+
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
+ else
+ function debugger.tracing() return false end ; return false
+ end
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
+trackers = trackers or { }
+
+local data, done = { }, { }
+
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
+ end
+ end
+ end
+end
+
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
end
- return ownpath
end
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
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
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
-environment.platform = os.platform
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
function environment.initialize_arguments(arg)
- environment.arguments = { }
- environment.files = { }
- environment.sorted_argument_keys = nil
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, 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 "")
+ arguments[flag] = string.unquote(value or "")
else
flag = argument:match("^%-+(.+)")
if flag then
- environment.arguments[flag] = true
+ arguments[flag] = true
else
- environment.files[#environment.files+1] = argument
+ files[#files+1] = argument
end
end
end
@@ -2621,32 +3431,31 @@ function environment.initialize_arguments(arg)
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
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
if name:find(v) then
- return environment.arguments[v:sub(2,#v)]
+ return arguments[v:sub(2,#v)]
end
end
end
@@ -2667,355 +3476,899 @@ function environment.split_arguments(separator) -- rather special, cut-off befor
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)
+function environment.reconstruct_commandline(arg,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
else
result[#result+1] = a
end
- elseif a:find(" ") then
- result[#result+1] = string.quote(a)
- else
- result[#result+1] = a
end
+ return table.join(result," ")
+ else
+ return ""
end
- return table.join(result," ")
end
if arg then
- environment.initialize_arguments(arg)
- environment.original_arguments = arg
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
arg = { } -- prevent duplicate handling
+
end
+-- weird place ... depends on a not yet loaded module
--- 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
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
--- 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.
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
--- 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
+environment.loadedluacode = loadfile -- can be overloaded
--- This is the first code I wrote for LuaTeX, so it needs some cleanup.
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
--- 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.
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
--- Beware, loading and saving is overloaded in luat-tmp!
+-- the next ones can use the previous ones / combine
-if not versions then versions = { } end versions['luat-inp'] = 1.001
-if not environment then environment = { } end
-if not file then file = { } end
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+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
+end -- of closure
-if not input.suffixmap then input.suffixmap = { } end
+do -- create closure to overcome 200 locals limit
-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
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
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' }
+local statusinfo, n, registered = { }, 0, { }
--- here we catch a few new thingies (todo: add these paths to context.tmf)
---
--- FONTFEATURES = .;$TEXMF/fonts/fea//
--- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+statistics = statistics or { }
-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
+statistics.enable = true
+statistics.threshold = 0.05
--- backward compatible ones
+-- timing functions
-input.alternatives = { }
+local clock = os.gettimeofday or os.clock
-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'
+local notimer
--- obscure ones
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
-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)
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
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
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
end
end
end
+ return 0
+end
+
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
- -- cross referencing
+-- general function
- for k, v in pairs(input.suffixes) do
- for _, vv in pairs(v) do
- if vv then
- input.suffixmap[vv] = k
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
end
end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
end
+end
- return instance
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
end
-function input.reset_hashes(instance)
- instance.lists = { }
- instance.found = { }
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
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"))
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
end
-if texio then
- input.log = texio.write_nl
-else
- input.log = print
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
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
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
else
- if input.banner then
- input.log(input.banner..kind..": no name")
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
+ else
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
else
- input.log("<<"..kind..": no name>>")
+ write(format("[%s",real))
end
+ else
+ write("[-")
end
end
-function input.dummy_logger()
+function logs.tex.stop_page_number()
+ write("]")
end
-function input.settrace(n)
- input.trace = tonumber(n or 0)
- if input.trace > 0 then
- input.logger = input.simple_logger
- input.verbose = true
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
+ else
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
else
- input.logger = function() end
+ write_nl("<r/>")
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("<t>"..table.concat({...},' ').."</t>")
- else
- input.log("<<"..table.concat({...},' ')..">>")
- 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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
end
end
-function input.reportlines(str)
- if type(str) == "string" then
- str = str:split("\n")
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
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))
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
--- These functions can be used to test the performance, especially
--- loading the database files.
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
-do
- local clock = os.gettimeofday or os.clock
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
- function input.starttiming(instance)
- if instance then
- instance.starttime = clock()
- if not instance.loadtime then
- instance.loadtime = 0
- end
- end
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
+
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
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
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
end
- return 0
+ end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
+end
+
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
end
+ return newinstance
+
end
-function input.elapsedtime(instance)
- return format("%0.3f",(instance and instance.loadtime) or 0)
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
end
-function input.report_loadtime(instance)
- if instance then
- input.report('total load time', input.elapsedtime(instance))
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
end
-input.loadtime = input.elapsedtime
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
-function input.env(instance,key)
- return instance.environment[key] or input.osenv(instance,key)
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
end
-function input.osenv(instance,key)
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(key)
local ie = instance.environment
local value = ie[key]
if value == nil then
@@ -3024,174 +4377,291 @@ function input.osenv(instance,key)
if e == nil then
-- value = "" -- false
else
- value = input.bare_variable(e)
+ value = resolvers.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
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
--
--- 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
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
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
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
end
- locate(input.luaname,instance.luafiles)
- locate(input.cnfname,instance.cnffiles)
end
end
+ return ""
end
-function input.load_cnf(instance)
- local function loadoldconfigdata()
- for _, fname in ipairs(instance.cnffiles) do
- input.aux.load_cnf(instance,fname)
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
end
+ return "{" .. concat(t,",") .. "}"
end
- -- instance.cnffiles contain complete names now !
- if #instance.cnffiles == 0 then
- input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
else
- instance.rootpath = instance.cnffiles[1]
- for k,fname in ipairs(instance.cnffiles) do
- instance.cnffiles[k] = input.normalize_name(fname:gsub("\\",'/'))
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
end
- for i=1,3 do
- instance.rootpath = file.dirname(instance.rootpath)
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
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
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
else
- loadoldconfigdata()
- if instance.renewcache then
- input.saveoldconfig(instance)
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
end
end
- input.aux.collapse_cnf_data(instance)
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
end
- input.checkconfigdata(instance)
+ return resolvers.ownpath
end
-function input.load_lua(instance)
- if #instance.luafiles == 0 then
- -- yet harmless
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
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)
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
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)
+ identify_own = function() end
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)
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
end
end
end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
end
end
-function input.aux.load_cnf(instance,fname)
- fname = input.clean_path(fname)
- local lname = fname:gsub("%.%a+$",input.luasuffix)
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
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)
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
instance.order[#instance.order+1] = instance.configuration[dname]
end
else
f = io.open(fname)
if f then
- input.report("loading", fname)
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
local line, data, n, k, v
local dname = file.dirname(fname)
if not instance.configuration[dname] then
@@ -3203,17 +4673,19 @@ function input.aux.load_cnf(instance,fname)
local line, n = f:read(), 0
if line then
while true do -- join lines
- line, n = line:gsub("\\%s*$", "")
+ line, n = gsub(line,"\\%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 not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
if k and v and not data[k] then
- data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
instance.kpsevars[k] = true
end
end
@@ -3222,236 +4694,261 @@ function input.aux.load_cnf(instance,fname)
end
end
f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
else
- input.report("skipping", fname)
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
+ end
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
end
+ check_configuration()
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)
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
if instance.loaderror then
- input.loadlists(instance)
- input.savefiles(instance)
+ resolvers.loadlists()
+ resolvers.savefiles()
end
else
- input.loadlists(instance)
+ resolvers.loadlists()
if instance.renewcache then
- input.savefiles(instance)
+ resolvers.savefiles()
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 } )
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ 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 } )
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ 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)
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
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'] .. "}"
+ -- weird
end
- input.expand_variables(instance)
- input.reset_hashes(instance)
+ resolvers.expand_variables()
+ reset_hashes()
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))
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
end
end
-function input.locatedatabase(instance,specification)
- return input.methodhandler('locators', instance, specification)
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
end
-function input.locators.tex(instance,specification)
+function resolvers.locators.tex(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')
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
end
end
-- hashers
-function input.hashdatabase(instance,tag,name)
- return input.methodhandler('hashers',instance,tag,name)
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
end
-function input.loadfiles(instance)
+function resolvers.loadfiles()
instance.loaderror = false
instance.files = { }
if not instance.renewcache then
for _, hash in ipairs(instance.hashes) do
- input.hashdatabase(instance,hash.tag,hash.name)
+ resolvers.hashdatabase(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)
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
end
-- generators:
-function input.loadlists(instance)
+function resolvers.loadlists()
for _, hash in ipairs(instance.hashes) do
- input.generatedatabase(instance,hash.tag)
+ resolvers.generatedatabase(hash.tag)
end
end
-function input.generatedatabase(instance,specification)
- return input.methodhandler('generators', instance, specification)
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
end
-do
+-- starting with . or .. etc or funny char
- local weird = lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+local weird = lpeg.P(".")^1 + 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)
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
else
- action(name)
+ f[#f+1] = path
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
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
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
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
else
- path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ action(name)
end
end
- f:close()
end
end
end
-
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ 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)
+function resolvers.savefiles()
+ resolvers.save_data('files')
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)
+function resolvers.splitconfig()
for i,c in ipairs(instance) do
for k,v in pairs(c) do
if type(v) == 'string' then
@@ -3463,23 +4960,24 @@ function input.splitconfig(instance)
end
end
end
-function input.joinconfig(instance)
+
+function resolvers.joinconfig()
for i,c in ipairs(instance.order) do
- for k,v in pairs(c) do
+ for k,v in pairs(c) do -- ipairs?
if type(v) == 'table' then
c[k] = file.join_path(v)
end
end
end
end
-function input.split_path(str)
+function resolvers.split_path(str)
if type(str) == 'table' then
return str
else
return file.split_path(str)
end
end
-function input.join_path(str)
+function resolvers.join_path(str)
if type(str) == 'table' then
return file.join_path(str)
else
@@ -3487,48 +4985,47 @@ function input.join_path(str)
end
end
-function input.splitexpansions(instance)
- for k,v in pairs(instance.expansions) do
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
local t, h = { }, { }
- for _,vv in pairs(file.split_path(v)) do
+ for _,vv in ipairs(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
+ ie[k] = t
else
- instance.expansions[k] = t[1]
+ ie[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)
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
end
-input.configbanner = [[
+resolvers.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)
+function resolvers.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)
+ local function dump(k,v,m) -- could be moved inline
if type(v) == 'string' then
return m .. "['" .. k .. "']='" .. v .. "',"
elseif #v == 1 then
@@ -3539,11 +5036,11 @@ function input.serialize(files)
end
t[#t+1] = "return {"
if instance.sortdata then
- for _, k in pairs(sorted(files)) do
+ for _, k in pairs(sortedkeys(files)) do -- ipairs
local fk = files[k]
if type(fk) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
- for _, kk in pairs(sorted(fk)) do
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
t[#t+1] = dump(kk,fk[kk],"\t\t")
end
t[#t+1] = "\t},"
@@ -3552,10 +5049,10 @@ function input.serialize(files)
end
end
else
- for k, v in pairs(files) do
+ for k, v in next, files do
if type(v) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
- for kk,vv in pairs(v) do
+ for kk,vv in next, v do
t[#t+1] = dump(kk,vv,"\t\t")
end
t[#t+1] = "\t},"
@@ -3568,62 +5065,67 @@ function input.serialize(files)
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
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, 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
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
end
end
local data = {
type = dataname,
root = cachename,
- version = input.cacheversion,
+ version = resolvers.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)
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
os.remove(lucname)
end
- else
- input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
end
end
end
-function input.aux.load_data(instance,pathname,dataname,filename,makename) -- untested without cache overload
+function resolvers.load_data(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)
+ local blob = loadfile(filename .. ".luc") or loadfile(filename .. ".lua")
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)
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
instance[dataname][pathname] = data.content
else
- input.report("skipping",dataname,"for",pathname,"from",filename)
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
instance[dataname][pathname] = { }
instance.loaderror = true
end
- else
- input.report("skipping",dataname,"for",pathname,"from",filename)
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
end
end
@@ -3636,198 +5138,139 @@ end
-- 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
+function resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
end
end
end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
end
- instance[dataname][pathname] = t
else
- instance[dataname][pathname] = data
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
end
- else
- input.report("skipping","configuration file",filename)
- instance[dataname][pathname] = { }
- instance.loaderror = true
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
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]
+ instance.order[#instance.order+1] = instance.setup[pathname]
if instance.loaderror then break end
end
end
-function input.loadoldconfig(instance)
+function resolvers.loadoldconfig()
if not instance.renewcache then
for _, cnf in ipairs(instance.cnffiles) do
local dname = file.dirname(cnf)
- input.aux.load_configuration(instance,dname)
+ resolvers.load_data(dname,'configuration')
instance.order[#instance.order+1] = instance.configuration[dname]
if instance.loaderror then break end
end
end
- input.joinconfig(instance)
+ resolvers.joinconfig()
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*$")
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
if a and b then
- instance.expansions[a..'.'..b] = v
+ expansions[a..'.'..b] = v
else
- instance.expansions[k] = v
+ 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
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then 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
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
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)
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
if n > 0 or m > 0 then
- instance.expansions[k]= s
+ expansions[k]= s
end
end
if not busy then break end
end
- local homedir =
- instance.environment[(os.type == "windows" and 'USERPROFILE') or 'HOME'] or '~'
- for k,v in pairs(instance.expansions) do
- v = v:gsub("^~", homedir)
- instance.expansions[k] = v:gsub("\\", '/')
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
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
+function resolvers.variable(name)
+ return entry(instance.variables,name)
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)
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
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)
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,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
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
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)
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
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
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
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 -- no longer needed
-do
local done = { }
- function input.reset_extra_path(instance)
+ function resolvers.reset_extra_path()
local ep = instance.extra_paths
if not ep then
ep, done = { }, { }
@@ -3837,25 +5280,25 @@ do
end
end
- function input.register_extra_path(instance,paths,subpaths)
+ function resolvers.register_extra_path(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
+ for p in gmatch(paths,"[^,]+") do
-- we gmatch each step again, not that fast, but used seldom
- for s in subpaths:gmatch("[^,]+") do
+ for s in gmatch(subpaths,"[^,]+") do
local ps = p .. "/" .. s
if not done[ps] then
- ep[#ep+1] = input.clean_path(ps)
+ ep[#ep+1] = resolvers.clean_path(ps)
done[ps] = true
end
end
end
else
- for p in paths:gmatch("[^,]+") do
+ for p in gmatch(paths,"[^,]+") do
if not done[p] then
- ep[#ep+1] = input.clean_path(p)
+ ep[#ep+1] = resolvers.clean_path(p)
done[p] = true
end
end
@@ -3863,10 +5306,10 @@ do
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
+ for s in gmatch(subpaths,"[^,]+") do
local ps = ep[i] .. "/" .. s
if not done[ps] then
- ep[#ep+1] = input.clean_path(ps)
+ ep[#ep+1] = resolvers.clean_path(ps)
done[ps] = true
end
end
@@ -3882,311 +5325,196 @@ do
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
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
done[v] = true
new[#new+1] = v
+ else
+ break
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
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
end
- return new
end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
if not str then
return ep or { }
elseif instance.savelists then
-- engine+progname hash
- str = str:gsub("%$","")
+ str = gsub(str,"%$","")
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)
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(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))
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(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("%$",""))
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
if tmp ~= "" then
- return input.expanded_path_list(instance,str)
+ return resolvers.expanded_path_list(str)
else
- return input.expanded_path_list(instance,tmp)
+ return resolvers.expanded_path_list(tmp)
end
end
-function input.expand_path_from_var(instance,str)
- return file.join_path(input.expanded_path_list_from_var(instance,str))
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
end
-function input.format_of_var(str)
- return input.formats[str] or input.formats[input.alternatives[str]] or ''
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
end
-function input.format_of_suffix(str)
- return input.suffixmap[file.extname(str)] or 'tex'
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
end
-function input.variable_of_format(str)
- return input.formats[str] or input.formats[input.alternatives[str]] or ''
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
end
-function input.var_of_format_or_suffix(str)
- local v = input.formats[str]
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
if v then
return v
end
- v = input.formats[input.alternatives[str]]
+ v = formats[alternatives[str]]
if v then
return v
end
- v = input.suffixmap[file.extname(str)]
+ v = suffixmap[file.extname(str)]
if v then
- return input.formats[isf]
+ return 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))
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.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
+resolvers.isreadable = { }
-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
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
if readable then
- input.logger("+ readable", name)
+ logs.report("fileio","+ readable: %s",name)
else
- input.logger("- readable", name)
+ logs.report("fileio","- readable: %s", 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
+resolvers.isreadable.tex = resolvers.isreadable.file
-- name
-- name/name
-function input.aux.collect_files(instance,names)
+local function collect_files(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 ..")")
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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
- 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
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
end
- end
- if blobfile then
- if type(blobfile) == 'string' then
- if not dname or blobfile:find(dname) then
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
filelist[#filelist+1] = {
hash.type,
- file.join(blobpath,blobfile,bname), -- search
- input.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,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
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
end
end
end
@@ -4197,99 +5525,145 @@ function input.aux.collect_files(instance,names)
end
end
-function input.suffix_of_format(str)
- if input.suffixes[str] then
- return input.suffixes[str][1]
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
else
return ""
end
end
-function input.suffixes_of_format(str)
- if input.suffixes[str] then
- return input.suffixes[str]
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return 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
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
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
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
end
+ return (fakepaths[name] == 1)
end
--- split the next one up, better for jit
-
-function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
- local result = { }
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
local stamp = nil
- filename = input.normalize_name(filename) -- elsewhere
- filename = file.collapse_path(filename:gsub("\\","/")) -- elsewhere
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- 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)
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
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)
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
result = { filename }
else
- local forcedname, ok = "", false
- if file.extname(filename) == "" then
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
if instance.format == "" then
forcedname = filename .. ".tex"
- if input.is_readable.file(forcedname) then
- input.logger('! no suffix, forcing standard filetype tex')
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
result, ok = { forcedname }, true
end
else
- for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
forcedname = filename .. "." .. s
- if input.is_readable.file(forcedname) then
- input.logger('! no suffix, forcing format filetype', s)
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
result, ok = { forcedname }, true
break
end
end
end
end
- if not ok then
- input.logger('? qualified', filename)
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', filename)
end
end
else
@@ -4306,39 +5680,47 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
if ext == "" then
local forcedname = filename .. '.tex'
wantedfiles[#wantedfiles+1] = forcedname
- filetype = input.format_of_suffix(forcedname)
- input.logger('! forcing filetype',filetype)
+ filetype = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
else
- filetype = input.format_of_suffix(filename)
- input.logger('! using suffix based filetype',filetype)
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
end
else
if ext == "" then
- for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
wantedfiles[#wantedfiles+1] = filename .. "." .. s
end
end
filetype = instance.format
- input.logger('! using given filetype',filetype)
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
end
- local typespec = input.variable_of_format(filetype)
- local pathlist = input.expanded_path_list(instance,typespec)
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(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
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 filelist = collect_files(wantedfiles)
local fl = filelist and filelist[1]
if fl then
filename = fl[3]
@@ -4347,56 +5729,53 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
end
else
-- list search
- local filelist = input.aux.collect_files(instance,wantedfiles)
+ local filelist = collect_files(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
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
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("^!+", '')
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
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
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
local expr = "^" .. pathname
- -- input.debug('?',expr)
- for _, fl in ipairs(filelist) do
+ for k=1,#filelist do
+ local fl = filelist[k]
local f = fl[2]
- if f:find(expr) then
- -- input.debug('T',' '..f)
- if input.trace > 2 then
- input.logger('= found in hash',f)
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
end
--- todo, test for readable
result[#result+1] = fl[3]
- input.aux.register_in_trees(instance,f) -- for tracing used files
+ resolvers.register_in_trees(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
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
local fname = file.join(ppname,w)
- if input.is_readable.file(fname) then
- if input.trace > 2 then
- input.logger('= found by scanning',fname)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',fname)
end
result[#result+1] = fname
done = true
@@ -4416,8 +5795,8 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
end
end
end
- for k,v in pairs(result) do
- result[k] = file.collapse_path(v)
+ for k=1,#result do
+ result[k] = file.collapse_path(result[k])
end
if instance.remember then
instance.found[stamp] = result
@@ -4425,48 +5804,12 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
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
+if not resolvers.concatinators then resolvers.concatinators = { } end
-input.concatinators.tex = file.join
-input.concatinators.file = input.concatinators.tex
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
-function input.find_files(instance,filename,filetype,mustexist)
+function resolvers.find_files(filename,filetype,mustexist)
if type(mustexist) == boolean then
-- all set
elseif type(filetype) == 'boolean' then
@@ -4475,18 +5818,26 @@ function input.find_files(instance,filename,filetype,mustexist)
filetype, mustexist = nil, false
end
instance.format = filetype or ''
- local t = input.aux.find_file(instance,filename,true)
+ local result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
instance.format = ''
- return t
+ return result
end
-function input.find_file(instance,filename,filetype,mustexist)
- return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
end
-function input.find_given_files(instance,filename)
+function resolvers.find_given_files(filename)
local bname, result = file.basename(filename), { }
- for k, hash in ipairs(instance.hashes) do
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
local files = instance.files[hash.tag]
local blist = files[bname]
if not blist then
@@ -4499,11 +5850,12 @@ function input.find_given_files(instance,filename)
end
if blist then
if type(blist) == 'string' then
- result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
+ result[#result+1] = resolvers.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 ""
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
if not instance.allresults then break end
end
end
@@ -4512,126 +5864,119 @@ function input.find_given_files(instance,filename)
return result
end
-function input.find_given_file(instance,filename)
- return (input.find_given_files(instance,filename)[1] or "")
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
end
-function input.find_wildcard_files(instance,filename) -- todo: remap:
+function resolvers.find_wildcard_files(filename) -- todo: remap:
local result = { }
local bname, dname = file.basename(filename), file.dirname(filename)
- local path = dname:gsub("^*/","")
- path = path:gsub("*",".*")
- path = path:gsub("-","%%-")
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
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
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
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 find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,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
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
if done and not allresults then break end
end
end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
return result
end
-function input.find_wildcard_file(instance,filename)
- return (input.find_wildcard_files(instance,filename)[1] or "")
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(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("<?xml version='1.0' standalone='yes'?>\n")
- f:write("<rl:job>\n")
- if jobname then
- f:write("\t<rl:name>" .. jobname .. "</rl:name>\n")
- end
- f:write("\t<rl:files>\n")
- for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
- f:write("\t\t<rl:file n='" .. instance.foundintrees[v] .. "'>" .. v .. "</rl:file>\n")
- end
- f:write("\t</rl:files>\n")
- f:write("</rl:usedfiles>\n")
- f:close()
- end
-end
-
-function input.automount(instance)
+function resolvers.automount()
-- 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)
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
end
-function input.for_files(instance, command, files, filetype, mustexist)
+function resolvers.for_files(command, files, filetype, mustexist)
if files and #files > 0 then
local function report(str)
- if input.verbose then
- input.report(str) -- has already verbose
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
else
print(str)
end
end
- if input.verbose then
+ if trace_verbose then
report('')
end
- for _, file in pairs(files) do
- local result = command(instance,file,filetype,mustexist)
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
if type(result) == 'string' then
report(result)
else
- for _,v in pairs(result) do
+ for _,v in ipairs(result) do
report(v)
end
end
@@ -4641,22 +5986,19 @@ 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)))
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.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)
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
-function input.aux.register_file(files, name, path)
+function resolvers.register_file(files, name, path)
if files[name] then
if type(files[name]) == 'string' then
files[name] = { files[name], path }
@@ -4668,165 +6010,77 @@ function input.aux.register_file(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)
+function resolvers.splitmethod(filename)
if not filename then
return { } -- safeguard
elseif type(filename) == "table" then
return filename -- already split
- elseif not filename:find("://") then
+ elseif not find(filename,"://") 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
+ for k, v in pairs(t) do -- pairs?
s[#s+1] = k .. "=" .. v
end
- return table.concat(s, sep or " | ")
+ return 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
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.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)
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
else
- return nil
+ return resolvers[what].tex(filename,filetype) -- todo: specification
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("//+","//"))
+function resolvers.clean_path(str)
if str then
- return ((str:gsub("\\","/")):gsub("^!+",""))
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
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))
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
end
end
-function input.do_with_var(name,func)
- func(input.aux.expanded_var(name))
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
end
-function input.with_files(instance,pattern,handle)
+function resolvers.with_files(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
+ for k,v in next, files do
+ if find(k,"^remap:") then
k = files[k]
v = files[k] -- chained
end
- if k:find(pattern) then
+ if find(k,pattern) then
if type(v) == "string" then
handle(blobtype,blobpath,v,k)
else
- for _,vv in pairs(v) do
+ for _,vv in pairs(v) do -- ipairs?
handle(blobtype,blobpath,vv,k)
end
end
@@ -4837,129 +6091,54 @@ function input.with_files(instance,pattern,handle)
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 resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
+ end
+ end
+ return nil, nil
+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")
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
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
+ b = toboolean(b)
+ return (b == nil and default) or b
end
end
+texconfig.kpse_init = false
---~ print(table.serialize(input.aux.splitpathexpr("/usr/share/texmf-{texlive,tetex}", {})))
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
--- command line resolver:
+-- for a while
---~ print(input.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
+input = resolvers
-do
- local resolvers = { }
+end -- of closure
- 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
+do -- create closure to overcome 200 locals limit
- input.resolve = resolve
-
-end
-
-
-if not modules then modules = { } end modules ['luat-tmp'] = {
+if not modules then modules = { } end modules ['data-tmp'] = {
version = 1.001,
comment = "companion to luat-lib.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -4983,63 +6162,71 @@ being written at the same time is small. We also need to extend
luatools with a recache feature.</p>
--ldx]]--
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
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
+
+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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
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
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
if not cachepath then
- print("\nfatal error: there is no valid cache path defined\n")
+ print("\nfatal error: there is no valid (writable) 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))
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
os.exit()
end
- function caches.temp(instance)
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
return cachepath
end
return cachepath
end
-function caches.configpath(instance)
- return table.concat(instance.cnffiles,";")
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
end
function caches.hashed(tree)
- return md5.hex((tree:lower()):gsub("[\\\/]+","/"))
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
end
-function caches.treehash(instance)
- local tree = caches.configpath(instance)
+function caches.treehash()
+ local tree = caches.configpath()
if not tree or tree == "" then
return false
else
@@ -5047,26 +6234,24 @@ function caches.treehash(instance)
end
end
-function caches.setpath(instance,...)
+function caches.setpath(...)
if not caches.path then
if not caches.path then
- caches.path = caches.temp(instance)
+ caches.path = caches.temp()
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
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
if not caches.path then
caches.path = '.'
end
- caches.path = input.clean_path(caches.path)
- if lfs and not table.is_empty({...}) then
+ caches.path = resolvers.clean_path(caches.path)
+ if not table.is_empty({...}) then
local pth = dir.mkdirs(caches.path,...)
return pth
end
@@ -5074,9 +6259,9 @@ function caches.setpath(instance,...)
return caches.path
end
-function caches.definepath(instance,category,subcategory)
+function caches.definepath(category,subcategory)
return function()
- return caches.setpath(instance,category,subcategory)
+ return caches.setpath(category,subcategory)
end
end
@@ -5088,39 +6273,106 @@ function caches.loaddata(path,name)
local tmaname, tmcname = caches.setluanames(path,name)
local loader = loadfile(tmcname) or loadfile(tmaname)
if loader then
- return loader()
+ loader = loader()
+ collectgarbage("step")
+ return loader
else
return false
end
end
-function caches.is_writable(filepath,filename)
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
local tmaname, tmcname = caches.setluanames(filepath,filename)
- return file.is_writable(tmaname)
+ return file.iswritable(tmaname)
end
-function caches.savedata(filepath,filename,data,raw) -- raw needed for file cache
+function caches.savedata(filepath,filename,data,raw)
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))
+ file.savedata(tmaname, table.serialize(data,'return',false,true,false)) -- no hex
else
- table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
end
- utils.lua.compile(tmaname, tmcname)
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
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 tex and texconfig and (not texconfig.formatname or texconfig.formatname == "") and input and resolvers.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")
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
end
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
--[[ldx--
<p>Once we found ourselves defining similar cache constructs
several times, containers were introduced. Containers are used
@@ -5134,124 +6386,145 @@ table structures without bothering about the disk cache.</p>
<p>Examples of usage can be found in the font related code.</p>
--ldx]]--
-containers = { }
-containers.trace = false
+containers = containers or { }
-do -- local report
+containers.usecache = true
- 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
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%s: %s",tag,name or 'invalid')
end
+end
- local allocated = { }
+local allocated = { }
- -- tracing
+-- 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
+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 and caches.setpath and caches.setpath(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)
+function containers.is_usable(container, name)
+ return container.enabled and caches and caches.iswritable(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.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
+function containers.read(container,name)
+ if container.enabled and caches and not container.storage[name] and containers.usecache then
+ container.storage[name] = caches.loaddata(container.path,name)
+ if containers.is_valid(container,name) then
+ report(container,"loaded",name)
else
- return false
+ container.storage[name] = nil
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]
+ 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
+function containers.write(container, name, data)
+ if data then
+ data.cache_version = container.version
+ if container.enabled and caches 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
- return data
+ report(container,"stored",name)
+ container.storage[name] = data
end
+ return data
+end
- function containers.content(container,name)
- return container.storage[name]
- end
+function containers.content(container,name)
+ return container.storage[name]
+end
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
end
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") 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
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
-input.cachepath = nil
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
-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))
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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))
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(pathname))
else
if not filename or (filename == "") then
filename = dataname
@@ -5263,15 +6536,15 @@ end
-- we will make a better format, maybe something xml or just text or lua
-input.automounted = input.automounted or { }
+resolvers.automounted = resolvers.automounted or { }
-function input.automount(instance,usecache)
- local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
if table.is_empty(mountpaths) and usecache then
- mountpaths = { caches.setpath(instance,"mount") }
+ mountpaths = { caches.setpath("mount") }
end
if not table.is_empty(mountpaths) then
- input.starttiming(instance)
+ statistics.starttiming(resolvers.instance)
for k, root in pairs(mountpaths) do
local f = io.open(root.."/url.tmi")
if f then
@@ -5280,938 +6553,302 @@ function input.automount(instance,usecache)
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)
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
end
end
end
f:close()
end
end
- input.stoptiming(instance)
+ statistics.stoptiming(resolvers.instance)
end
end
--- store info in format
+-- status info
-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)
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
-function input.storage.register(...)
- input.storage.data[#input.storage.data+1] = { ... }
-end
+-- experiment (code will move)
-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
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
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
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
else
- name = str
+ return "invalid status file"
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
+ return "missing status file"
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
+ return true
end
--- filename : luat-zip.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
+end -- of closure
-if not versions then versions = { } end versions['luat-zip'] = 1.001
+do -- create closure to overcome 200 locals limit
-if zip and input then
- zip.supported = true
-else
- zip = { }
- zip.supported = false
-end
-
-if not zip.supported then
-
- if not input then input = { } end -- will go away
-
- function zip.openarchive (...) return nil end -- needed ?
- function zip.closenarchive (...) end -- needed ?
- function input.usezipfile (...) end -- needed ?
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
-else
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
- -- zip:///oeps.zip?name=bla/bla.tex
- -- zip:///oeps.zip?tree=tex/texmf-local
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
- local function validzip(str)
- if not str:find("^zip://") then
- return "zip:///" .. str
- else
- return str
- end
- end
+<p>How about just forgetting about them?</p>
+--ldx]]--
- zip.archives = { }
- zip.registeredfiles = { }
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
- function zip.openarchive(instance,name)
- if not name or name == "" then
- return nil
- else
- local arch = zip.archives[name]
- if arch then
- return arch
- else
- local full = input.find_file(instance,name) or ""
- local arch = (full ~= "" and zip.open(full)) or false
- zip.archives[name] = arch
- return arch
- end
- end
- end
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
- function zip.closearchive(instance,name)
- if not name or name == "" and zip.archives[name] then
- zip.close(zip.archives[name])
- zip.archives[name] = nil
- end
- end
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
- -- zip:///texmf.zip?tree=/tex/texmf
- -- zip:///texmf.zip?tree=/tex/texmf-local
- -- zip:///texmf-mine.zip?tree=/tex/texmf-projects
+local find = string.find
- function input.locators.zip(instance,specification) -- where is this used? startup zips (untested)
- specification = input.splitmethod(specification)
- local zipfile = specification.path
- local zfile = zip.openarchive(instance,name) -- tricky, could be in to be initialized tree
- if zfile then
- input.logger('! zip locator', specification.original ..' found')
- else
- input.logger('? zip locator', specification.original ..' not found')
- end
- end
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
- function input.hashers.zip(instance,tag,name)
- input.report("loading zip file",name,"as",tag)
- input.usezipfile(instance,tag .."?tree=" .. name)
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
end
-
- function input.concatinators.zip(tag,path,name)
- if not path or path == "" then
- return tag .. '?name=' .. name
- else
- return tag .. '?name=' .. path .. "/" .. name
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
end
- end
-
- function input.is_readable.zip(name)
- return true
- end
-
- function input.finders.zip(instance,specification,filetype)
- specification = input.splitmethod(specification)
- if specification.path then
- local q = url.query(specification.query)
- if q.name then
- local zfile = zip.openarchive(instance,specification.path)
- if zfile then
- input.logger('! zip finder',specification.path)
- local dfile = zfile:open(q.name)
- if dfile then
- dfile = zfile:close()
- input.logger('+ zip finder',q.name)
- return specification.original
- end
- else
- input.logger('? zip finder',specification.path)
- end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
end
- end
- input.logger('- zip finder',filename)
- return unpack(input.finders.notfound)
- end
-
- function input.openers.zip(instance,specification)
- local zipspecification = input.splitmethod(specification)
- if zipspecification.path then
- local q = url.query(zipspecification.query)
- if q.name then
- local zfile = zip.openarchive(instance,zipspecification.path)
- if zfile then
- input.logger('+ zip starter',zipspecification.path)
- local dfile = zfile:open(q.name)
- if dfile then
- input.show_open(specification)
- return input.openers.text_opener(specification,dfile,'zip')
- end
- else
- input.logger('- zip starter',zipspecification.path)
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
end
- end
- end
- input.logger('- zip opener',filename)
- return unpack(input.openers.notfound)
- end
-
- function input.loaders.zip(instance,specification)
- specification = input.splitmethod(specification)
- if specification.path then
- local q = url.query(specification.query)
- if q.name then
- local zfile = zip.openarchive(instance,specification.path)
- if zfile then
- input.logger('+ zip starter',specification.path)
- local dfile = zfile:open(q.name)
- if dfile then
- input.show_load(filename)
- input.logger('+ zip loader',filename)
- local s = dfile:read("*all")
- dfile:close()
- return true, s, #s
- end
- else
- input.logger('- zip starter',specification.path)
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
end
- end
- end
- input.logger('- zip loader',filename)
- return unpack(input.openers.notfound)
- end
-
- -- zip:///somefile.zip
- -- zip:///somefile.zip?tree=texmf-local -> mount
-
- function input.usezipfile(instance,zipname)
- zipname = validzip(zipname)
- input.logger('! zip use','file '..zipname)
- local specification = input.splitmethod(zipname)
- local zipfile = specification.path
- if zipfile and not zip.registeredfiles[zipname] then
- local tree = url.query(specification.query).tree or ""
- input.logger('! zip register','file '..zipname)
- local z = zip.openarchive(instance,zipfile)
- if z then
- input.logger("= zipfile","registering "..zipname)
- input.starttiming(instance)
- input.aux.prepend_hash(instance,'zip',zipname,zipfile)
- input.aux.extend_texmf_var(instance,zipname) -- resets hashes too
- zip.registeredfiles[zipname] = z
- instance.files[zipname] = input.aux.register_zip_file(z,tree or "")
- input.stoptiming(instance)
- else
- input.logger("? zipfile","unknown "..zipname)
- end
- else
- input.logger('! zip register','no file '..zipname)
- end
- end
-
- function input.aux.register_zip_file(z,tree)
- local files, filter = { }, ""
- if tree == "" then
- filter = "^(.+)/(.-)$"
- else
- filter = "^"..tree.."/(.+)/(.-)$"
- end
- input.logger('= zip filter',filter)
- local register, n = input.aux.register_file, 0
- for i in z:files() do
- local path, name = i.filename:match(filter)
- if path then
- if name and name ~= '' then
- register(files, name, path)
- n = n + 1
- else
- -- directory
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
end
else
- register(files, i.filename, '')
- n = n + 1
- end
- end
- input.report('= zip entries',n)
- return files
- end
-
-end
-
-
--- filename : luat-zip.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-tex'] = 1.001
-
--- special functions that deal with io
-
-if texconfig and not texlua then
-
- input.level = input.level or 0
-
- if input.logmode() == 'xml' then
- function input.show_open(name)
- input.level = input.level + 1
- texio.write_nl("<f l='"..input.level.."' n='"..name.."'>")
- end
- function input.show_close(name)
- texio.write("</f> ")
- input.level = input.level - 1
- end
- function input.show_load(name)
- texio.write_nl("<f l='"..(input.level+1).."' n='"..name.."'/>") -- level?
- end
- else
- function input.show_open () end
- function input.show_close() end
- function input.show_load () end
- end
-
- function input.finders.generic(instance,tag,filename,filetype)
- local foundname = input.find_file(instance,filename,filetype)
- if foundname and foundname ~= "" then
- input.logger('+ ' .. tag .. ' finder',filename,'filetype')
- return foundname
- else
- input.logger('- ' .. tag .. ' finder',filename,'filetype')
- return unpack(input.finders.notfound)
- end
- end
-
- input.filters.dynamic_translator = nil
- input.filters.frozen_translator = nil
- input.filters.utf_translator = nil
-
- function input.openers.text_opener(filename,file_handle,tag)
- local u = unicode.utftype(file_handle)
- local t = { }
- if u > 0 then
- input.logger('+ ' .. tag .. ' opener (' .. unicode.utfname[u] .. ')',filename)
- local l
- if u > 2 then
- l = unicode.utf32_to_utf8(file_handle:read("*a"),u==4)
- else
- l = unicode.utf16_to_utf8(file_handle:read("*a"),u==2)
- end
- file_handle:close()
- t = {
- utftype = u, -- may go away
- lines = l,
- current = 0, -- line number, not really needed
- handle = nil,
- noflines = #l,
- close = function()
- input.logger('= ' .. tag .. ' closer (' .. unicode.utfname[u] .. ')',filename)
- input.show_close(filename)
- end,
---~ getline = function(n)
---~ local line = t.lines[n]
---~ if not line or line == "" then
---~ return ""
---~ else
---~ local translator = input.filters.utf_translator
---~ return (translator and translator(line)) or line
---~ end
---~ end,
- reader = function(self)
- self = self or t
- local current, lines = self.current, self.lines
- if current >= #lines then
- return nil
- else
- current = current + 1
- self.current = current
- local line = lines[current]
- if line == "" then
- return ""
- else
- local translator = input.filters.utf_translator
- -- return (translator and translator(line)) or line
- if translator then
- return translator(line)
- else
- return line
- end
- end
- end
- end
- }
- else
- input.logger('+ ' .. tag .. ' opener',filename)
- -- todo: file;name -> freeze / eerste regel scannen -> freeze
- local filters = input.filters
- t = {
- reader = function(self)
- local line = file_handle:read()
- if line == "" then
- return ""
- end
- local translator = filters.utf_translator
- if translator then
- return translator(line)
- end
- translator = filters.dynamic_translator
- if translator then
- return translator(line)
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
end
- return line
- end,
- close = function()
- input.logger('= ' .. tag .. ' closer',filename)
- input.show_close(filename)
- file_handle:close()
- end,
- handle = function()
- return file_handle
- end,
- noflines = function()
- t.noflines = io.noflines(file_handle)
- return t.noflines
- end
- }
- end
- return t
- end
-
- function input.openers.generic(instance,tag,filename)
- if filename and filename ~= "" then
- local f = io.open(filename,"r")
- if f then
- input.show_open(filename)
- return input.openers.text_opener(filename,f,tag)
- end
- end
- input.logger('- ' .. tag .. ' opener',filename)
- return unpack(input.openers.notfound)
- end
-
- function input.loaders.generic(instance,tag,filename)
- if filename and filename ~= "" then
- local f = io.open(filename,"rb")
- if f then
- input.show_load(filename)
- input.logger('+ ' .. tag .. ' loader',filename)
- local s = f:read("*a")
- f:close()
- if s then
- return true, s, #s
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
end
end
end
- input.logger('- ' .. tag .. ' loader',filename)
- return unpack(input.loaders.notfound)
- end
-
- function input.finders.tex(instance,filename,filetype)
- return input.finders.generic(instance,'tex',filename,filetype)
- end
- function input.openers.tex(instance,filename)
- return input.openers.generic(instance,'tex',filename)
- end
- function input.loaders.tex(instance,filename)
- return input.loaders.generic(instance,'tex',filename)
end
-
end
--- callback into the file io and related things; disabling kpse
-
-
-if texconfig and not texlua then do
- -- this is not the right place, because we refer to quite some not yet defined tables, but who cares ...
+end -- of closure
- ctx = ctx or { }
+do -- create closure to overcome 200 locals limit
- local ss = { }
-
- function ctx.writestatus(a,b)
- local s = ss[a]
- if not ss[a] then
- s = a:rpadd(15) .. ": "
- ss[a] = s
- end
- texio.write_nl(s .. b .. "\n")
- end
+if not modules then modules = { } end modules ['data-lst'] = {
+ 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"
+}
- -- this will become: ctx.install_statistics(fnc() return ..,.. end) etc
+-- used in mtxrun
- local statusinfo, n = { }, 0
+local find, concat, upper, format = string.find, table.concat, string.upper, string.format
- function ctx.register_statistics(tag,pattern,fnc)
- statusinfo[#statusinfo+1] = { tag, pattern, fnc }
- if #tag > n then n = #tag end
- end
+resolvers.listers = resolvers.listers or { }
- function ctx.show_statistics() -- todo: move calls
- if caches then
- ctx.register_statistics("used config path", "%s", function() return caches.configpath(texmf.instance) end)
- ctx.register_statistics("used cache path", "%s", function() return caches.path end)
- end
- if status.luabytecodes > 0 and input.storage and input.storage.done then
- ctx.register_statistics("modules/dumps/instances", "%s/%s/%s", function() return status.luabytecodes-500, input.storage.done, status.luastates end)
- end
- if texmf.instance then
- ctx.register_statistics("input load time", "%s seconds", function() return input.loadtime(texmf.instance) end)
- end
- if fonts then
- ctx.register_statistics("fonts load time","%s seconds", function() return input.loadtime(fonts) end)
- end
- if xml then
- ctx.register_statistics("xml load time", "%s seconds, backreferences: %i, outer filtering time: %s", function() return input.loadtime(xml), #lxml.self, input.loadtime(lxml) end)
- end
- if mptopdf then
- ctx.register_statistics("mps conversion time", "%s seconds", function() return input.loadtime(mptopdf) end)
- end
- if nodes then
- ctx.register_statistics("node processing time", "%s seconds (including kernel)", function() return input.loadtime(nodes) end)
- end
- if kernel then
- ctx.register_statistics("kernel processing time", "%s seconds", function() return input.loadtime(kernel) end)
- end
- if attributes then
- ctx.register_statistics("attribute processing time", "%s seconds", function() return input.loadtime(attributes) end)
- end
- if languages then
- ctx.register_statistics("language load time", "%s seconds, n=%s", function() return input.loadtime(languages), languages.hyphenation.n() end)
- end
- if figures then
- ctx.register_statistics("graphics processing time", "%s seconds, n=%s (including tex)", function() return input.loadtime(figures), figures.n or "?" end)
- end
- if metapost then
- ctx.register_statistics("metapost processing time", "%s seconds, loading: %s seconds, execution: %s seconds, n: %s", function() return input.loadtime(metapost), input.loadtime(mplib), input.loadtime(metapost.exectime), metapost.n end)
- end
- if status.luastate_bytes then
- ctx.register_statistics("current memory usage", "%s bytes", function() return status.luastate_bytes end)
- end
- if nodes then
- ctx.register_statistics("cleaned up reserved nodes", "%s nodes, %s lists of %s", function() return nodes.cleanup_reserved(tex.count[24]) end) -- \topofboxstack
- end
- if status.node_mem_usage then
- ctx.register_statistics("node memory usage", "%s", function() return status.node_mem_usage end)
- end
- if languages then
- ctx.register_statistics("loaded patterns", "%s", function() return languages.logger.report() end)
- end
- if fonts then
- ctx.register_statistics("loaded fonts", "%s", function() return fonts.logger.report() end)
- end
- if xml then -- so we are in mkiv, we need a different check
- ctx.register_statistics("runtime", "%s seconds, %i processed pages, %i shipped pages, %.3f pages/second", function()
- input.stoptiming(texmf)
- local runtime = input.loadtime(texmf)
- local shipped = tex.count['nofshipouts']
- local pages = tex.count['realpageno'] - 1
- local persecond = shipped / runtime
- return runtime, pages, shipped, persecond
- end)
- end
- for _, t in ipairs(statusinfo) do
- local tag, pattern, fnc = t[1], t[2], t[3]
- ctx.writestatus("mkiv lua stats", string.format("%s - %s", tag:rpadd(n," "), pattern:format(fnc())))
- end
+local function tabstr(str)
+ if type(str) == 'table' then
+ return concat(str," | ")
+ else
+ return str
end
+end
-end end
-
-if texconfig and not texlua then
-
- texconfig.kpse_init = false
- texconfig.trace_file_names = input.logmode() == 'tex'
- texconfig.max_print_line = 100000
-
- -- if still present, we overload kpse (put it off-line so to say)
-
- if not texmf then texmf = { } end
-
- input.starttiming(texmf)
-
- if not texmf.instance then
-
- if not texmf.instance then -- prevent a second loading
-
- texmf.instance = input.reset()
- texmf.instance.progname = environment.progname or 'context'
- texmf.instance.engine = environment.engine or 'luatex'
- texmf.instance.validfile = input.validctxfile
-
- input.load(texmf.instance)
-
- end
-
- if callback then
- callback.register('find_read_file' , function(id,name) return input.findtexfile(texmf.instance,name) end)
- callback.register('open_read_file' , function( name) return input.opentexfile(texmf.instance,name) end)
- end
-
- if callback then
- callback.register('find_data_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
- callback.register('find_enc_file' , function(name) return input.findbinfile(texmf.instance,name,"enc") end)
- callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"tfm") end)
- callback.register('find_format_file' , function(name) return input.findbinfile(texmf.instance,name,"fmt") end)
- callback.register('find_image_file' , function(name) return input.findbinfile(texmf.instance,name,"tex") end)
- callback.register('find_map_file' , function(name) return input.findbinfile(texmf.instance,name,"map") end)
- callback.register('find_ocp_file' , function(name) return input.findbinfile(texmf.instance,name,"ocp") end)
- callback.register('find_opentype_file' , function(name) return input.findbinfile(texmf.instance,name,"otf") end)
- callback.register('find_output_file' , function(name) return name end)
- callback.register('find_pk_file' , function(name) return input.findbinfile(texmf.instance,name,"pk") end)
- callback.register('find_sfd_file' , function(name) return input.findbinfile(texmf.instance,name,"sfd") end)
- callback.register('find_truetype_file' , function(name) return input.findbinfile(texmf.instance,name,"ttf") end)
- callback.register('find_type1_file' , function(name) return input.findbinfile(texmf.instance,name,"pfb") end)
- callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"vf") end)
-
- callback.register('read_data_file' , function(file) return input.loadbinfile(texmf.instance,file,"tex") end)
- callback.register('read_enc_file' , function(file) return input.loadbinfile(texmf.instance,file,"enc") end)
- callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"tfm") end)
- -- format
- -- image
- callback.register('read_map_file' , function(file) return input.loadbinfile(texmf.instance,file,"map") end)
- callback.register('read_ocp_file' , function(file) return input.loadbinfile(texmf.instance,file,"ocp") end)
- callback.register('read_opentype_file' , function(file) return input.loadbinfile(texmf.instance,file,"otf") end)
- -- output
- callback.register('read_pk_file' , function(file) return input.loadbinfile(texmf.instance,file,"pk") end)
- callback.register('read_sfd_file' , function(file) return input.loadbinfile(texmf.instance,file,"sfd") end)
- callback.register('read_truetype_file' , function(file) return input.loadbinfile(texmf.instance,file,"ttf") end)
- callback.register('read_type1_file' , function(file) return input.loadbinfile(texmf.instance,file,"pfb") end)
- callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"vf" ) end)
- end
-
- if callback and environment.aleph_mode then
- callback.register('find_font_file' , function(name) return input.findbinfile(texmf.instance,name,"ofm") end)
- callback.register('read_font_file' , function(file) return input.loadbinfile(texmf.instance,file,"ofm") end)
- callback.register('find_vf_file' , function(name) return input.findbinfile(texmf.instance,name,"ovf") end)
- callback.register('read_vf_file' , function(file) return input.loadbinfile(texmf.instance,file,"ovf") end)
- end
-
- if callback then
- callback.register('find_write_file' , function(id,name) return name end)
- end
-
- if callback and (not config or (#config == 0)) then
- callback.register('find_format_file' , function(name) return name end)
- end
-
- if callback and false then
- for k, v in pairs(callback.list()) do
- if not v then texio.write_nl("<w>callback "..k.." is not set</w>") end
- end
- end
-
- if callback then
-
- input.start_actions = { }
- input.stop_actions = { }
-
- function input.register_start_actions(f) table.insert(input.start_actions, f) end
- function input.register_stop_actions (f) table.insert(input.stop_actions, f) end
-
- --~ callback.register('start_run', function() for _, a in pairs(input.start_actions) do a() end end)
- --~ callback.register('stop_run' , function() for _, a in pairs(input.stop_actions ) do a() end end)
-
- end
-
- if callback then
-
- if input.logmode() == 'xml' then
-
- function input.start_page_number()
- texio.write_nl("<p real='" .. tex.count[0] .. "' page='"..tex.count[1].."' sub='"..tex.count[2].."'")
- end
- function input.stop_page_number()
- texio.write("/>")
- texio.write_nl("")
- end
-
- callback.register('start_page_number' , input.start_page_number)
- callback.register('stop_page_number' , input.stop_page_number )
-
- function input.report_output_pages(p,b)
- texio.write_nl("<v k='pages'>"..p.."</v>")
- texio.write_nl("<v k='bytes'>"..b.."</v>")
- texio.write_nl("")
- end
- function input.report_output_log()
- end
-
- callback.register('report_output_pages', input.report_output_pages)
- callback.register('report_output_log' , input.report_output_log )
-
- function input.start_run()
- texio.write_nl("<?xml version='1.0' standalone='yes'?>")
- texio.write_nl("<job xmlns='www.tug.org/luatex/schemas/context-job.rng'>")
- texio.write_nl("")
- end
- function input.stop_run()
- texio.write_nl("</job>")
- end
- function input.show_statistics()
- for k,v in pairs(status.list()) do
- texio.write_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
- end
+local function list(list,report)
+ local instance = resolvers.instance
+ local pat = upper(pattern or "","")
+ local report = report or texio.write_nl
+ for _,key in pairs(table.sortedkeys(list)) do
+ if instance.pattern == "" or find(upper(key),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ report(format("%s=%s",key,tabstr(list[key])))
end
-
- table.insert(input.start_actions, input.start_run)
- table.insert(input.stop_actions , input.show_statistics)
- table.insert(input.stop_actions , input.stop_run)
-
else
- table.insert(input.stop_actions , input.show_statistics)
+ report(format('%s %s=%s',(instance.kpsevars[key] and 'K') or 'E',key,tabstr(list[key])))
end
-
- callback.register('start_run', function() for _, a in pairs(input.start_actions) do a() end end)
- callback.register('stop_run' , function() for _, a in pairs(input.stop_actions ) do a() end ctx.show_statistics() end)
-
- end
-
- end
-
- if kpse then
-
- function kpse.find_file(filename,filetype,mustexist)
- return input.find_file(texmf.instance,filename,filetype,mustexist)
- end
- function kpse.expand_path(variable)
- return input.expand_path(texmf.instance,variable)
- end
- function kpse.expand_var(variable)
- return input.expand_var(texmf.instance,variable)
end
- function kpse.expand_braces(variable)
- return input.expand_braces(texmf.instance,variable)
- end
-
end
-
end
--- program specific configuration (memory settings and alike)
-
-if texconfig and not texlua then
-
- luatex = luatex or { }
-
- luatex.variablenames = {
- 'main_memory', 'extra_mem_bot', 'extra_mem_top',
- 'buf_size','expand_depth',
- 'font_max', 'font_mem_size',
- 'hash_extra', 'max_strings', 'pool_free', 'pool_size', 'string_vacancies',
- 'obj_tab_size', 'pdf_mem_size', 'dest_names_size',
- 'nest_size', 'param_size', 'save_size', 'stack_size',
- 'trie_size', 'hyph_size', 'max_in_open',
- 'ocp_stack_size', 'ocp_list_size', 'ocp_buf_size'
- }
+function resolvers.listers.variables () list(resolvers.instance.variables ) end
+function resolvers.listers.expansions() list(resolvers.instance.expansions) end
- function luatex.variables()
- local t, x = { }, nil
- for _,v in pairs(luatex.variablenames) do
- x = input.var_value(texmf.instance,v)
- if x and x:find("^%d+$") then
- t[v] = tonumber(x)
+function resolvers.listers.configurations(report)
+ local report = report or texio.write_nl
+ local instance = resolvers.instance
+ for _,key in ipairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or find(key,instance.pattern) then
+ report(format("%s\n",key))
+ for i,c in ipairs(instance.order) do
+ local str = c[key]
+ if str then
+ report(format("\t%s\t%s",i,str))
+ end
end
+ report("")
end
- return t
- end
-
- function luatex.setvariables(tab)
- for k,v in pairs(luatex.variables()) do
- tab[k] = v
- end
- end
-
- if not luatex.variables_set then
- luatex.setvariables(texconfig)
- luatex.variables_set = true
end
-
- texconfig.max_print_line = 100000
- texconfig.max_in_open = 127
-
end
--- some tex basics
-
-if not cs then cs = { } end
-
-function cs.def(k,v)
- tex.sprint(tex.texcatcodes, "\\def\\" .. k .. "{" .. v .. "}")
-end
-
-function cs.chardef(k,v)
- tex.sprint(tex.texcatcodes, "\\chardef\\" .. k .. "=" .. v .. "\\relax")
-end
-
-function cs.boolcase(b)
- if b then tex.write(1) else tex.write(0) end
-end
-
-function cs.testcase(b)
- if b then
- tex.sprint(tex.texcatcodes, "\\firstoftwoarguments")
- else
- tex.sprint(tex.texcatcodes, "\\secondoftwoarguments")
- end
-end
-
-
-if not modules then modules = { } end modules ['luat-kps'] = {
- version = 1.001,
- comment = "companion to luatools.lua",
- author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
- copyright = "PRAGMA ADE / ConTeXt Development Team",
- license = "see context related readme files"
-}
-
---[[ldx--
-<p>This file is used when we want the input handlers to behave like
-<type>kpsewhich</type>. What to do with the following:</p>
-
-<typing>
-{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
-$SELFAUTOLOC : /usr/tex/bin/platform
-$SELFAUTODIR : /usr/tex/bin
-$SELFAUTOPARENT : /usr/tex
-</typing>
-
-<p>How about just forgetting abou them?</p>
---ldx]]--
-
-input = input or { }
-input.suffixes = input.suffixes or { }
-input.formats = input.formats or { }
-
-input.suffixes['gf'] = { '<resolution>gf' }
-input.suffixes['pk'] = { '<resolution>pk' }
-input.suffixes['base'] = { 'base' }
-input.suffixes['bib'] = { 'bib' }
-input.suffixes['bst'] = { 'bst' }
-input.suffixes['cnf'] = { 'cnf' }
-input.suffixes['mem'] = { 'mem' }
-input.suffixes['mf'] = { 'mf' }
-input.suffixes['mfpool'] = { 'pool' }
-input.suffixes['mft'] = { 'mft' }
-input.suffixes['mppool'] = { 'pool' }
-input.suffixes['graphic/figure'] = { 'eps', 'epsi' }
-input.suffixes['texpool'] = { 'pool' }
-input.suffixes['PostScript header'] = { 'pro' }
-input.suffixes['ist'] = { 'ist' }
-input.suffixes['web'] = { 'web', 'ch' }
-input.suffixes['cweb'] = { 'w', 'web', 'ch' }
-input.suffixes['cmap files'] = { 'cmap' }
-input.suffixes['lig files'] = { 'lig' }
-input.suffixes['bitmap font'] = { }
-input.suffixes['MetaPost support'] = { }
-input.suffixes['TeX system documentation'] = { }
-input.suffixes['TeX system sources'] = { }
-input.suffixes['dvips config'] = { }
-input.suffixes['type42 fonts'] = { }
-input.suffixes['web2c files'] = { }
-input.suffixes['other text files'] = { }
-input.suffixes['other binary files'] = { }
-input.suffixes['opentype fonts'] = { 'otf' }
-
-input.suffixes['fmt'] = { 'fmt' }
-input.suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
-
-input.suffixes['pdftex config'] = { }
-input.suffixes['Troff fonts'] = { }
-
-input.suffixes['ls-R'] = { }
-
---[[ldx--
-<p>If you wondered abou tsome of the previous mappings, how about
-the next bunch:</p>
---ldx]]--
-
-input.formats['bib'] = ''
-input.formats['bst'] = ''
-input.formats['mft'] = ''
-input.formats['ist'] = ''
-input.formats['web'] = ''
-input.formats['cweb'] = ''
-input.formats['MetaPost support'] = ''
-input.formats['TeX system documentation'] = ''
-input.formats['TeX system sources'] = ''
-input.formats['Troff fonts'] = ''
-input.formats['dvips config'] = ''
-input.formats['graphic/figure'] = ''
-input.formats['ls-R'] = ''
-input.formats['other text files'] = ''
-input.formats['other binary files'] = ''
-
-input.formats['gf'] = ''
-input.formats['pk'] = ''
-input.formats['base'] = 'MFBASES'
-input.formats['cnf'] = ''
-input.formats['mem'] = 'MPMEMS'
-input.formats['mf'] = 'MFINPUTS'
-input.formats['mfpool'] = 'MFPOOL'
-input.formats['mppool'] = 'MPPOOL'
-input.formats['texpool'] = 'TEXPOOL'
-input.formats['PostScript header'] = 'TEXPSHEADERS'
-input.formats['cmap files'] = 'CMAPFONTS'
-input.formats['type42 fonts'] = 'T42FONTS'
-input.formats['web2c files'] = 'WEB2C'
-input.formats['pdftex config'] = 'PDFTEXCONFIG'
-input.formats['texmfscripts'] = 'TEXMFSCRIPTS'
-input.formats['bitmap font'] = ''
-input.formats['lig files'] = 'LIGFONTS'
+end -- of closure
-- end library merge
-- We initialize some characteristics of this program. We need to
@@ -6229,19 +6866,34 @@ own.libs = { -- todo: check which ones are really needed
'l-number.lua',
'l-set.lua',
'l-os.lua',
- 'l-md5.lua',
'l-file.lua',
+ 'l-md5.lua',
'l-url.lua',
'l-dir.lua',
'l-boolean.lua',
'l-unicode.lua',
+ 'l-math.lua',
'l-utils.lua',
- 'luat-lib.lua',
- 'luat-inp.lua',
- 'luat-tmp.lua',
- 'luat-zip.lua',
- 'luat-tex.lua',
- 'luat-kps.lua',
+ 'l-aux.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+-- 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-lst.lua', -- lister
}
-- We need this hack till luatex is fixed.
@@ -6278,11 +6930,11 @@ function locate_libs()
end
end
-if not input then
+if not resolvers then
locate_libs()
end
-if not input then
+if not resolvers then
print("")
print("Luatools is unable to start up due to lack of libraries. You may")
print("try to run 'lua luatools.lua --selfmerge' in the path where this")
@@ -6291,61 +6943,65 @@ if not input then
os.exit()
end
-instance = input.reset()
-input.verbose = environment.arguments["verbose"] or false
-input.banner = 'LuaTools | '
-utils.report = input.report
+logs.setprogram('LuaTools',"TDS Management Tool 1.31",environment.arguments["verbose"] or false)
-input.defaultlibs = { -- not all are needed
- 'l-string.lua', 'l-lpeg.lua', 'l-table.lua', 'l-boolean.lua', 'l-number.lua', 'l-set.lua', 'l-unicode.lua',
- 'l-md5.lua', 'l-os.lua', 'l-io.lua', 'l-file.lua', 'l-url.lua', 'l-dir.lua', 'l-utils.lua', 'l-tex.lua',
- 'luat-env.lua', 'luat-lib.lua', 'luat-inp.lua', 'luat-tmp.lua', 'luat-zip.lua', 'luat-tex.lua'
-}
+local instance = resolvers.reset()
--- todo: use environment.argument() instead of environment.arguments[]
+resolvers.defaultlibs = { -- not all are needed (this will become: context.lus (lua spec)
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-boolean.lua',
+ 'l-number.lua',
+ 'l-unicode.lua',
+ 'l-os.lua',
+ 'l-io.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-url.lua',
+ 'l-dir.lua',
+ 'l-utils.lua',
+ 'l-dimen.lua',
+ 'trac-inf.lua',
+ 'trac-tra.lua',
+ 'trac-log.lua',
+ 'luat-env.lua', -- here ?
+ 'data-res.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-tmp.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-pre.lua',
+ 'data-tex.lua',
+ 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-clr.lua',
+ 'data-lua.lua',
+ 'data-ctx.lua',
+ 'luat-fio.lua',
+ 'luat-cnf.lua',
+}
instance.engine = environment.arguments["engine"] or 'luatex'
instance.progname = environment.arguments["progname"] or 'context'
instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
-instance.lualibs = environment.arguments["lualibs"] or table.concat(input.defaultlibs,",")
+instance.lualibs = environment.arguments["lualibs"] or table.concat(resolvers.defaultlibs,",")
instance.allresults = environment.arguments["all"] or false
instance.pattern = environment.arguments["pattern"] or nil
instance.sortdata = environment.arguments["sort"] or false
instance.kpseonly = not environment.arguments["all"] or false
instance.my_format = environment.arguments["format"] or instance.format
-instance.lsrmode = environment.arguments["lsr"] or false
if type(instance.pattern) == 'boolean' then
- input.report("invalid pattern specification") -- toto, force verbose for one message
+ logs.simple("invalid pattern specification")
instance.pattern = nil
end
-if environment.arguments["trace"] then input.settrace(environment.arguments["trace"]) end
+if environment.arguments["trace"] then resolvers.settrace(environment.arguments["trace"]) end
-if environment.arguments["minimize"] then
- if input.validators.visibility[instance.progname] then
- instance.validfile = input.validators.visibility[instance.progname]
- end
-end
-
-function input.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.my_prepare_b(instance)
- input.my_prepare_a(instance)
- input.load_hash(instance)
- input.automount(instance)
-end
-
--- barename
-
-if not messages then messages = { } end
+runners = runners or { }
+messages = messages or { }
messages.no_ini_file = [[
There is no lua initialization file found. This file can be forced by the
@@ -6371,21 +7027,19 @@ messages.help = [[
--luafile=str lua inifile (default is <progname>.lua)
--lualibs=list libraries to assemble (optional when --compile)
--compile assemble and compile lua inifile
---mkii force context mkii mode (only for testing, not usable!)
--verbose give a bit more info
---minimize optimize lists for format
--all show all found files
--sort sort cached data
--engine=str target engine
--progname=str format or backend
--pattern=str filter variables
---lsr use lsr and cnf directly
]]
-function input.my_make_format(instance,texname)
+function runners.make_format(texname)
+ local instance = resolvers.instance
if texname and texname ~= "" then
- if input.usecache then
- local path = file.join(caches.setpath(instance,"formats")) -- maybe platform
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
if path and lfs then
lfs.chdir(path)
end
@@ -6394,22 +7048,22 @@ function input.my_make_format(instance,texname)
if barename == texname then
texname = texname .. ".tex"
end
- local fullname = input.find_files(instance,texname)[1] or ""
+ local fullname = resolvers.find_files(texname)[1] or ""
if fullname == "" then
- input.report("no tex file with name",texname)
+ logs.simple("no tex file with name: %s",texname)
else
local luaname, lucname, luapath, lualibs = "", "", "", { }
-- the following is optional, since context.lua can also
-- handle this collect and compile business
if environment.arguments["compile"] then
if luaname == "" then luaname = barename end
- input.report("creating initialization file " .. luaname)
+ logs.simple("creating initialization file: %s",luaname)
luapath = file.dirname(luaname)
if luapath == "" then
luapath = file.dirname(texname)
end
if luapath == "" then
- luapath = file.dirname(input.find_files(instance,texname)[1] or "")
+ luapath = file.dirname(resolvers.find_files(texname)[1] or "")
end
lualibs = string.split(instance.lualibs,",")
luaname = file.basename(barename .. ".lua")
@@ -6419,74 +7073,86 @@ function input.my_make_format(instance,texname)
if lualibs[1] then
local firstlib = file.join(luapath,lualibs[1])
if not lfs.isfile(firstlib) then
- local foundname = input.find_files(instance,lualibs[1])[1]
+ local foundname = resolvers.find_files(lualibs[1])[1]
if foundname then
- input.report("located library path : " .. luapath)
+ logs.simple("located library path: %s",luapath)
luapath = file.dirname(foundname)
end
end
end
- input.report("using library path : " .. luapath)
- input.report("using lua libraries: " .. table.join(lualibs," "))
+ logs.simple("using library path: %s",luapath)
+ logs.simple("using lua libraries: %s",table.join(lualibs," "))
utils.merger.selfcreate(lualibs,luapath,luaname)
- if utils.lua.compile(luaname, lucname) and io.exists(lucname) then
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ if utils.lua.compile(luaname,lucname,false,strip) and io.exists(lucname) then
luaname = lucname
- input.report("using compiled initialization file " .. lucname)
+ logs.simple("using compiled initialization file: %s",lucname)
else
- input.report("using uncompiled initialization file " .. luaname)
+ logs.simple("using uncompiled initialization file: %s",luaname)
end
else
for _, v in pairs({instance.luaname, instance.progname, barename}) do
v = string.gsub(v..".lua","%.lua%.lua$",".lua")
if v and (v ~= "") then
- luaname = input.find_files(instance,v)[1] or ""
+ luaname = resolvers.find_files(v)[1] or ""
if luaname ~= "" then
break
end
end
end
end
+ if environment.arguments["noluc"] then
+ luaname = luaname:gsub("%.luc$",".lua") -- make this an option
+ end
if luaname == "" then
- input.reportlines(messages.no_ini_file)
- input.report("texname : " .. texname)
- input.report("luaname : " .. instance.luaname)
- input.report("progname : " .. instance.progname)
- input.report("barename : " .. barename)
+ if logs.verbose then
+ logs.simplelines(messages.no_ini_file)
+ logs.simple("texname : %s",texname)
+ logs.simple("luaname : %s",instance.luaname)
+ logs.simple("progname: %s",instance.progname)
+ logs.simple("barename: %s",barename)
+ end
else
- input.report("using lua initialization file " .. luaname)
- local flags = { "--ini" }
- if environment.arguments["mkii"] then
- flags[#flags+1] = "--progname=" .. instance.progname
- else
- flags[#flags+1] = "--lua=" .. string.quote(luaname)
+ logs.simple("using lua initialization file: %s",luaname)
+ local mp = dir.glob(file.removesuffix(file.basename(luaname)).."-*.mem")
+ if mp and #mp > 0 then
+ for _, name in ipairs(mp) do
+ logs.simple("removing related mplib format %s", file.basename(name))
+ os.remove(name)
+ end
end
- local bs = (environment.platform == "unix" and "\\\\") or "\\" -- todo: make a function
+ local flags = {
+ "--ini",
+ "--lua=" .. string.quote(luaname)
+ }
+ local bs = (os.platform == "unix" and "\\\\") or "\\" -- todo: make a function
local command = "luatex ".. table.concat(flags," ") .. " " .. string.quote(fullname) .. " " .. bs .. "dump"
- input.report("running command: " .. command .. "\n")
+ logs.simple("running command: %s\n",command)
os.spawn(command)
+ -- todo: do a dummy run that generates the related metafun and mfplain formats
end
end
else
- input.report("no tex file given")
+ logs.simple("no tex file given")
end
end
-function input.my_run_format(instance,name,data,more)
+function runners.run_format(name,data,more)
-- hm, rather old code here; we can now use the file.whatever functions
if name and (name ~= "") then
local barename = name:gsub("%.%a+$","")
local fmtname = ""
- if input.usecache then
- local path = file.join(caches.setpath(instance,"formats")) -- maybe platform
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
fmtname = file.join(path,barename..".fmt") or ""
end
if fmtname == "" then
- fmtname = input.find_files(instance,barename..".fmt")[1] or ""
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
end
- fmtname = input.clean_path(fmtname)
+ fmtname = resolvers.clean_path(fmtname)
barename = fmtname:gsub("%.%a+$","")
if fmtname == "" then
- input.report("no format with name",name)
+ logs.simple("no format with name: %s",name)
else
local luaname = barename .. ".luc"
local f = io.open(luaname)
@@ -6496,164 +7162,106 @@ function input.my_run_format(instance,name,data,more)
end
if f then
f:close()
- local command = "luatex --fmt=" .. string.quote(barename) .. " --lua=" .. string.quote(luaname) .. " " .. string.quote(data) .. " " .. string.quote(more)
- input.report("running command: " .. command)
+ local command = "luatex --fmt=" .. string.quote(barename) .. " --lua=" .. string.quote(luaname) .. " " .. string.quote(data) .. " " .. (more ~= "" and string.quote(more) or "")
+ logs.simple("running command: %s",command)
os.spawn(command)
else
- input.report("using format name",fmtname)
- input.report("no luc/lua with name",barename)
+ logs.simple("using format name: %s",fmtname)
+ logs.simple("no luc/lua with name: %s",barename)
end
end
end
end
--- helpers for verbose lists
-
-input.listers = input.listers or { }
-
-local function tabstr(str)
- if type(str) == 'table' then
- return table.concat(str," | ")
- else
- return str
- end
-end
-
-local function list(instance,list)
- local pat = string.upper(instance.pattern or "","")
- for _,key in pairs(table.sortedkeys(list)) do
- if instance.pattern == "" or string.find(key:upper(),pat) then
- if instance.kpseonly then
- if instance.kpsevars[key] then
- print(format("%s=%s",key,tabstr(list[key])))
- end
- else
- print(format('%s %s=%s',(instance.kpsevars[key] and 'K') or 'E',key,tabstr(list[key])))
- end
- end
- end
-end
-
-function input.listers.variables (instance) list(instance,instance.variables ) end
-function input.listers.expansions(instance) list(instance,instance.expansions) end
-
-function input.listers.configurations(instance)
- for _,key in pairs(table.sortedkeys(instance.kpsevars)) do
- if not instance.pattern or (instance.pattern=="") or key:find(instance.pattern) then
- print(key.."\n")
- for i,c in ipairs(instance.order) do
- local str = c[key]
- if str then
- print(format("\t%s\t\t%s",i,input.aux.tabstr(str)))
- end
- end
- print()
- end
- end
-end
-
-input.report(banner,"\n")
-
local ok = true
+-- private option --noluc for testing errors in the stub
+
if environment.arguments["find-file"] then
- input.my_prepare_b(instance)
+ resolvers.load()
instance.format = environment.arguments["format"] or instance.format
if instance.pattern then
instance.allresults = true
- input.for_files(instance, input.find_files, { instance.pattern }, instance.my_format)
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
else
- input.for_files(instance, input.find_files, environment.files, instance.my_format)
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
end
elseif environment.arguments["find-path"] then
- input.my_prepare_b(instance)
- local path = input.find_file(instance, environment.files[1], instance.my_format)
- if input.verbose then
- input.report(file.dirname(path))
+ resolvers.load()
+ local path = resolvers.find_file(environment.files[1], instance.my_format)
+ if logs.verbose then
+ logs.simple(file.dirname(path))
else
print(file.dirname(path))
end
---~ elseif environment.arguments["first-writable-path"] then
---~ input.my_prepare_b(instance)
---~ input.report(input.first_writable_path(instance,environment.files[1] or "."))
elseif environment.arguments["run"] then
- input.my_prepare_a(instance) -- ! no need for loading databases
- input.verbose = true
- input.my_run_format(instance,environment.files[1] or "",environment.files[2] or "",environment.files[3] or "")
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.files[1] or "",environment.files[2] or "",environment.files[3] or "")
elseif environment.arguments["fmt"] then
- input.my_prepare_a(instance) -- ! no need for loading databases
- input.verbose = true
- input.my_run_format(instance,environment.arguments["fmt"], environment.files[1] or "",environment.files[2] or "")
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.arguments["fmt"], environment.files[1] or "",environment.files[2] or "")
elseif environment.arguments["expand-braces"] then
- input.my_prepare_a(instance)
- input.for_files(instance, input.expand_braces, environment.files)
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_braces, environment.files)
elseif environment.arguments["expand-path"] then
- input.my_prepare_a(instance)
- input.for_files(instance, input.expand_path, environment.files)
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_path, environment.files)
elseif environment.arguments["expand-var"] or environment.arguments["expand-variable"] then
- input.my_prepare_a(instance)
- input.for_files(instance, input.expand_var, environment.files)
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_var, environment.files)
elseif environment.arguments["show-path"] or environment.arguments["path-value"] then
- input.my_prepare_a(instance)
- input.for_files(instance, input.show_path, environment.files)
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.show_path, environment.files)
elseif environment.arguments["var-value"] or environment.arguments["show-value"] then
- input.my_prepare_a(instance)
- input.for_files(instance, input.var_value, environment.files)
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.var_value, environment.files)
elseif environment.arguments["format-path"] then
- input.my_prepare_b(instance)
- input.report(caches.setpath(instance,"format"))
+ resolvers.load()
+ logs.simple(caches.setpath("format"))
elseif instance.pattern then -- brrr
- input.my_prepare_b(instance)
+ resolvers.load()
instance.format = environment.arguments["format"] or instance.format
instance.allresults = true
- input.for_files(instance, input.find_files, { instance.pattern }, instance.my_format)
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
elseif environment.arguments["generate"] then
instance.renewcache = true
- input.verbose = true
- input.my_prepare_b(instance)
+ logs.setverbose(true)
+ resolvers.load()
elseif environment.arguments["make"] or environment.arguments["ini"] or environment.arguments["compile"] then
- input.my_prepare_b(instance)
- input.verbose = true
- input.my_make_format(instance,environment.files[1] or "")
+ resolvers.load()
+ logs.setverbose(true)
+ runners.make_format(environment.files[1] or "")
elseif environment.arguments["selfmerge"] then
utils.merger.selfmerge(own.name,own.libs,own.list)
elseif environment.arguments["selfclean"] then
utils.merger.selfclean(own.name)
elseif environment.arguments["selfupdate"] then
- input.my_prepare_b(instance)
- input.verbose = true
- input.update_script(instance,own.name,"luatools")
+ resolvers.load()
+ logs.setverbose(true)
+ resolvers.update_script(own.name,"luatools")
elseif environment.arguments["variables"] or environment.arguments["show-variables"] then
- input.my_prepare_a(instance)
- input.listers.variables(instance)
+ resolvers.load("nofiles")
+ resolvers.listers.variables()
elseif environment.arguments["expansions"] or environment.arguments["show-expansions"] then
- input.my_prepare_a(instance)
- input.listers.expansions(instance)
+ resolvers.load("nofiles")
+ resolvers.listers.expansions()
elseif environment.arguments["configurations"] or environment.arguments["show-configurations"] then
- input.my_prepare_a(instance)
- input.listers.configurations(instance)
+ resolvers.load("nofiles")
+ resolvers.listers.configurations()
elseif environment.arguments["help"] or (environment.files[1]=='help') or (#environment.files==0) then
- if not input.verbose then
- input.verbose = true
- input.report(banner,"\n")
- end
- input.reportlines(messages.help)
+ logs.help(messages.help)
else
- input.my_prepare_b(instance)
- input.for_files(instance, input.find_files, environment.files, instance.my_format)
+ resolvers.load()
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
end
-if input.verbose then
- input.report("")
- input.report(string.format("runtime: %0.3f seconds",os.runtime()))
+if logs.verbose then
+ logs.simpleline()
+ logs.simple("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
+if os.platform == "unix" then
io.write("\n")
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-babel.lua b/Master/texmf-dist/scripts/context/lua/mtx-babel.lua
index c9855b86a40..e241e933454 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-babel.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-babel.lua
@@ -1,5 +1,5 @@
if not modules then modules = { } end modules ['mtx-babel'] = {
- version = 1.001,
+ version = 1.002,
comment = "companion to mtxrun.lua",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
@@ -8,8 +8,6 @@ if not modules then modules = { } end modules ['mtx-babel'] = {
-- data tables by Thomas A. Schmitz
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
-
scripts = scripts or { }
scripts.babel = scripts.babel or { }
@@ -190,6 +188,7 @@ do
o = "ό",
u = "ύ",
w = "ώ",
+ ["'"] = "’",
}
local replace_20 = { -- ` *
@@ -226,7 +225,7 @@ do
O = "Ὁ",
U = "Ὑ",
W = "Ὡ",
- R = "Ῥ",
+ R = "Ῥ",
}
local replace_23 = { -- > *
@@ -306,38 +305,42 @@ do
F = "Φ",
Q = "Χ",
Y = "Ψ",
- W = "Ω"
+ W = "Ω",
+ [";"] = "·",
+ ["?"] = ";",
}
- local skips_01 = lpeg.P("\\") * lpeg.R("az", "AZ")^1
- local skips_02 = lpeg.P("[") * (1- lpeg.S("[]"))^1 * lpeg.P("]")
-
- local greek_01 = (lpeg.P("<'") * lpeg.Cs(1) * lpeg.P('|')) / replace_01
- local greek_02 = (lpeg.P(">'") * lpeg.Cs(1) * lpeg.P('|')) / replace_02
- local greek_03 = (lpeg.P("<`") * lpeg.Cs(1) * lpeg.P('|')) / replace_03
- local greek_04 = (lpeg.P(">`") * lpeg.Cs(1) * lpeg.P('|')) / replace_04
- local greek_05 = (lpeg.P("<~") * lpeg.Cs(1) * lpeg.P('|')) / replace_05
- local greek_06 = (lpeg.P(">~") * lpeg.Cs(1) * lpeg.P('|')) / replace_06
- local greek_07 = (lpeg.P('"\'') * lpeg.Cs(1) ) / replace_07
- local greek_08 = (lpeg.P('"`') * lpeg.Cs(1) ) / replace_08
- local greek_09 = (lpeg.P('"~') * lpeg.Cs(1) ) / replace_09
- local greek_10 = (lpeg.P("<'") * lpeg.Cs(1) ) / replace_10
- local greek_11 = (lpeg.P(">'") * lpeg.Cs(1) ) / replace_11
- local greek_12 = (lpeg.P("<`") * lpeg.Cs(1) ) / replace_12
- local greek_13 = (lpeg.P(">`") * lpeg.Cs(1) ) / replace_13
- local greek_14 = (lpeg.P("<~") * lpeg.Cs(1) ) / replace_14
- local greek_15 = (lpeg.P(">~") * lpeg.Cs(1) ) / replace_15
- local greek_16 = (lpeg.P("'") * lpeg.Cs(1) * lpeg.P('|')) / replace_16
- local greek_17 = (lpeg.P("`") * lpeg.Cs(1) * lpeg.P('|')) / replace_17
- local greek_18 = (lpeg.P("~") * lpeg.Cs(1) * lpeg.P('|')) / replace_18
- local greek_19 = (lpeg.P("'") * lpeg.Cs(1) ) / replace_19
- local greek_20 = (lpeg.P("`") * lpeg.Cs(1) ) / replace_20
- local greek_21 = (lpeg.P("~") * lpeg.Cs(1) ) / replace_21
- local greek_22 = (lpeg.P("<") * lpeg.Cs(1) ) / replace_22
- local greek_23 = (lpeg.P(">") * lpeg.Cs(1) ) / replace_23
- local greek_24 = (lpeg.Cs(1) * lpeg.P('|') ) / replace_24
- local greek_25 = (lpeg.P('"') * lpeg.Cs(1) ) / replace_25
- local greek_26 = (lpeg.Cs(1) ) / replace_26
+ local P, R, S, V, Cs = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.Cs
+
+ local skips_01 = P("\\") * R("az", "AZ")^1
+ local skips_02 = P("[") * (1- S("[]"))^1 * P("]")
+
+ local greek_01 = (P("<'") * Cs(1) * P('|')) / replace_01
+ local greek_02 = (P(">'") * Cs(1) * P('|')) / replace_02
+ local greek_03 = (P("<`") * Cs(1) * P('|')) / replace_03
+ local greek_04 = (P(">`") * Cs(1) * P('|')) / replace_04
+ local greek_05 = (P("<~") * Cs(1) * P('|')) / replace_05
+ local greek_06 = (P(">~") * Cs(1) * P('|')) / replace_06
+ local greek_07 = (P('"\'') * Cs(1) ) / replace_07
+ local greek_08 = (P('"`') * Cs(1) ) / replace_08
+ local greek_09 = (P('"~') * Cs(1) ) / replace_09
+ local greek_10 = (P("<'") * Cs(1) ) / replace_10
+ local greek_11 = (P(">'") * Cs(1) ) / replace_11
+ local greek_12 = (P("<`") * Cs(1) ) / replace_12
+ local greek_13 = (P(">`") * Cs(1) ) / replace_13
+ local greek_14 = (P("<~") * Cs(1) ) / replace_14
+ local greek_15 = (P(">~") * Cs(1) ) / replace_15
+ local greek_16 = (P("'") * Cs(1) * P('|')) / replace_16
+ local greek_17 = (P("`") * Cs(1) * P('|')) / replace_17
+ local greek_18 = (P("~") * Cs(1) * P('|')) / replace_18
+ local greek_19 = (P("'") * Cs(1) ) / replace_19
+ local greek_20 = (P("`") * Cs(1) ) / replace_20
+ local greek_21 = (P("~") * Cs(1) ) / replace_21
+ local greek_22 = (P("<") * Cs(1) ) / replace_22
+ local greek_23 = (P(">") * Cs(1) ) / replace_23
+ local greek_24 = (Cs(1) * P('|') ) / replace_24
+ local greek_25 = (P('"') * Cs(1) ) / replace_25
+ local greek_26 = (Cs(1) ) / replace_26
local skips =
skips_01 + skips_02
@@ -350,20 +353,20 @@ do
greek_21 + greek_22 + greek_23 + greek_24 + greek_25 +
greek_26
- local spacing = lpeg.S(" \n\r\t")
- local startgreek = lpeg.P("\\startgreek")
- local stopgreek = lpeg.P("\\stopgreek")
- local localgreek = lpeg.P("\\localgreek")
- local lbrace = lpeg.P("{")
- local rbrace = lpeg.P("}")
+ local spacing = S(" \n\r\t")
+ local startgreek = P("\\startgreek")
+ local stopgreek = P("\\stopgreek")
+ local localgreek = P("\\localgreek")
+ local lbrace = P("{")
+ local rbrace = P("}")
- local documentparser = lpeg.Cs((skips + greek + 1)^0)
+ local documentparser = Cs((skips + greek + 1)^0)
- local contextgrammar = lpeg.Cs ( lpeg.P { "scan",
- ["scan"] = (lpeg.V("global") + lpeg.V("local") + skips + 1)^0,
+ local contextgrammar = Cs ( P { "scan",
+ ["scan"] = (V("global") + V("local") + skips + 1)^0,
["global"] = startgreek * ((skips + greek + 1)-stopgreek )^0 ,
- ["local"] = localgreek * lpeg.V("grouped"),
- ["grouped"] = spacing^0 * lbrace * (lpeg.V("grouped") + skips + (greek - rbrace))^0 * rbrace,
+ ["local"] = localgreek * V("grouped"),
+ ["grouped"] = spacing^0 * lbrace * (V("grouped") + skips + (greek - rbrace))^0 * rbrace,
} )
converters['greek'] = {
@@ -384,22 +387,22 @@ do
local structure = environment.argument("structure") or "document"
converter = converter[structure]
if converter then
- input.report(string.format("converting '%s' using language '%s' with structure '%s'", filename, language, structure))
+ logs.simple("converting '%s' using language '%s' with structure '%s'", filename, language, structure)
data = converter:match(data)
local newfilename = filename .. ".utf"
io.savedata(newfilename, data)
- input.report(string.format("converted data saved in '%s'", newfilename))
+ logs.simple("converted data saved in '%s'", newfilename)
else
- input.report(string.format("unknown structure '%s' language '%s'", structure, language))
+ logs.simple("unknown structure '%s' language '%s'", structure, language)
end
else
- input.report(string.format("no converter for language '%s'", language))
+ logs.simple("no converter for language '%s'", language)
end
else
- input.report(string.format("provide language"))
+ logs.simple("provide language")
end
else
- input.report(string.format("no data in '%s'",filename))
+ logs.simple("no data in '%s'",filename)
end
end
end
@@ -412,7 +415,7 @@ do
end
-banner = banner .. " | babel conversion tools "
+logs.extendbanner("Babel Conversion Tools 1.2",true)
messages.help = [[
--language=string conversion language (e.g. greek)
@@ -420,11 +423,8 @@ messages.help = [[
--convert convert babel codes into utf
]]
-input.verbose = true
-
if environment.argument("convert") then
scripts.babel.convert(environment.files[1] or "")
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
-
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-cache.lua b/Master/texmf-dist/scripts/context/lua/mtx-cache.lua
index 8091eaa6515..a1fbed8255d 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-cache.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-cache.lua
@@ -6,20 +6,18 @@ if not modules then modules = { } end modules ['mtx-cache'] = {
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
-
scripts = scripts or { }
scripts.cache = scripts.cache or { }
function scripts.cache.collect_one(...)
- local path = caches.setpath(instance,...)
+ local path = caches.setpath(...)
local tmas = dir.glob(path .. "/*.tma")
local tmcs = dir.glob(path .. "/*.tmc")
return path, tmas, tmcs
end
function scripts.cache.collect_two(...)
- local path = caches.setpath(instance,...)
+ local path = caches.setpath(...)
local rest = dir.glob(path .. "/**/*")
return path, rest
end
@@ -78,7 +76,7 @@ function scripts.cache.list(all)
end)
end
-banner = banner .. " | cache tools "
+logs.extendbanner("Cache Tools 0.10")
messages.help = [[
--purge remove not used files
@@ -95,5 +93,5 @@ elseif environment.argument("erase") then
elseif environment.argument("list") then
scripts.cache.list(environment.argument("all"))
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-chars.lua b/Master/texmf-dist/scripts/context/lua/mtx-chars.lua
index a05ab9f0860..d4330ca304d 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-chars.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-chars.lua
@@ -6,39 +6,12 @@ if not modules then modules = { } end modules ['mtx-chars'] = {
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+local format, concat, utfchar, upper = string.format, table.concat, unicode.utf8.char, string.upper
scripts = scripts or { }
scripts.chars = scripts.chars or { }
-function scripts.chars.stixtomkiv(inname,outname)
- if inname == "" then
- logs.report("aquiring math data","invalid datafilename")
- end
- local f = io.open(inname)
- if not f then
- logs.report("aquiring math data","invalid datafile")
- else
- logs.report("aquiring math data","processing " .. inname)
- if not outname or outname == "" then
- outname = "char-mth.lua"
- end
- local classes = {
- N = "normal",
- A = "alphabetic",
- D = "diacritic",
- P = "punctuation",
- B = "binary",
- R = "relation",
- L = "large",
- O = "opening",
- C = "closing",
- F = "fence"
- }
- local format, concat = string.format, table.concat
- local valid, done = false, { }
- local g = io.open(outname,'w')
- g:write([[
+local banner = [[
-- filename : char-mth.lua
-- comment : companion to char-mth.tex (in ConTeXt)
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
@@ -47,136 +20,303 @@ function scripts.chars.stixtomkiv(inname,outname)
if not versions then versions = { } end versions['char-mth'] = 1.001
if not characters then characters = { } end
- ]])
- g:write(format("\ncharacters.math = {\n"))
- for l in f:lines() do
- if not valid then
- valid = l:find("AMS/TeX name")
- end
- if valid then
- local unicode = l:sub(2,6)
- if unicode:sub(1,1) ~= " " and unicode ~= "" and not done[unicode] then
- local mathclass, adobename, texname = l:sub(57,57) or "", l:sub(13,36) or "", l:sub(84,109) or ""
- texname, adobename = texname:gsub("[\\ ]",""), adobename:gsub("[\\ ]","")
- local t = { }
- if mathclass ~= "" then t[#t+1] = format("mathclass='%s'", classes[mathclass] or "unknown") end
- if adobename ~= "" then t[#t+1] = format("adobename='%s'", adobename ) end
- if texname ~= "" then t[#t+1] = format("texname='%s'" , texname ) end
- if #t > 0 then
- g:write(format("\t[0x%s] = { %s },\n",unicode, concat(t,", ")))
+]]
+
+--~ function scripts.chars.stixtomkiv(inname,outname)
+--~ if inname == "" then
+--~ logs.report("aquiring math data","invalid datafilename")
+--~ end
+--~ local f = io.open(inname)
+--~ if not f then
+--~ logs.report("aquiring math data","invalid datafile")
+--~ else
+--~ logs.report("aquiring math data","processing " .. inname)
+--~ if not outname or outname == "" then
+--~ outname = "char-mth.lua"
+--~ end
+--~ local classes = {
+--~ N = "normal",
+--~ A = "alphabetic",
+--~ D = "diacritic",
+--~ P = "punctuation",
+--~ B = "binary",
+--~ R = "relation",
+--~ L = "large",
+--~ O = "opening",
+--~ C = "closing",
+--~ F = "fence"
+--~ }
+--~ local valid, done = false, { }
+--~ local g = io.open(outname,'w')
+--~ g:write(banner)
+--~ g:write(format("\ncharacters.math = {\n"))
+--~ for l in f:lines() do
+--~ if not valid then
+--~ valid = l:find("AMS/TeX name")
+--~ end
+--~ if valid then
+--~ local unicode = l:sub(2,6)
+--~ if unicode:sub(1,1) ~= " " and unicode ~= "" and not done[unicode] then
+--~ local mathclass, adobename, texname = l:sub(57,57) or "", l:sub(13,36) or "", l:sub(84,109) or ""
+--~ texname, adobename = texname:gsub("[\\ ]",""), adobename:gsub("[\\ ]","")
+--~ local t = { }
+--~ if mathclass ~= "" then t[#t+1] = format("mathclass='%s'", classes[mathclass] or "unknown") end
+--~ if adobename ~= "" then t[#t+1] = format("adobename='%s'", adobename ) end
+--~ if texname ~= "" then t[#t+1] = format("texname='%s'" , texname ) end
+--~ if #t > 0 then
+--~ g:write(format("\t[0x%s] = { %s },\n",unicode, concat(t,", ")))
+--~ end
+--~ done[unicode] = true
+--~ end
+--~ end
+--~ end
+--~ if not valid then
+--~ g:write("\t-- The data file is corrupt, invalid or maybe the format has changed.\n")
+--~ logs.report("aquiring math data","problems with data table")
+--~ else
+--~ logs.report("aquiring math data","table saved in " .. outname)
+--~ end
+--~ g:write("}\n")
+--~ g:close()
+--~ f:close()
+--~ end
+--~ end
+
+function scripts.chars.stixtomkiv(inname,outname)
+ logs.report("we no longer use this options but use our own tables instead")
+end
+
+local banner_pdf_1 = [[
+% filename : pdfr-def.tex
+% comment : generated by mtxrun --script chars --pdf
+% author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+% copyright: PRAGMA ADE / ConTeXt Development Team
+% license : see context related readme files
+%
+]]
+
+local banner_pdf_2 = [[
+%
+\endinput
+]]
+
+function scripts.chars.makepdfr()
+ local chartable = resolvers.find_file("char-def.lua") or ""
+ if chartable ~= "" then
+ dofile(chartable)
+ if characters and characters.data then
+ local f = io.open("pdfr-def.tex", 'w')
+ if f then
+ f:write(banner_pdf_1)
+ local cd = characters.data
+ local sd = table.sortedkeys(cd)
+ for i=1,#sd do
+ local char = cd[sd[i]]
+ if char.adobename then
+ f:write(format("\\pdfglyphtounicode{%s}{%04X}%%\n",char.adobename,char.unicodeslot))
end
- done[unicode] = true
end
+ f:write(banner_pdf_2)
+ f:close()
end
end
- if not valid then
- g:write("\t-- The data file is corrupt, invalid or maybe the format has changed.\n")
- logs.report("aquiring math data","problems with data table")
- else
- logs.report("aquiring math data","table saved in " .. outname)
- end
- g:write("}\n")
- g:close()
- f:close()
end
end
-scripts.chars.banner_utf_1 = [[
-% filename : enco-utf.tex
-% comment : generated by mtxrun --script chars --utf
-% author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-% copyright: PRAGMA ADE / ConTeXt Development Team
-% license : see context related readme files
+local banner_utf_module = [[
+%% filename : %s
+%% comment : generated by mtxrun --script chars --xtx
+%% author : Hans Hagen, PRAGMA-ADE, Hasselt NL
+%% copyright: PRAGMA ADE / ConTeXt Development Team
+%% license : see context related readme files
+]]
-\ifx\setcclcucx\undefined
+local banner_utf_mappings = [[
- \def\setcclcucx #1 #2 #3 %
- {\global\catcode"#1=11
- \global\lccode "#1="#2
- \global\uccode "#1="#3 }
+% lc/uc/catcode mappings
-\fi
]]
-scripts.chars.banner_utf_2 = [[
+local banner_utf_patch = [[
-% lc/uc/catcode mappings
+% patch needed for turkish
+\setXTXcharcodes "201C "201C "201C
+\setXTXcharcodes "201D "201D "201D
]]
-scripts.chars.banner_utf_3 = [[
+local banner_utf_names = [[
% named characters mapped onto utf (\\char is needed for accents)
]]
-scripts.chars.banner_utf_4 = [[
+local banner_utf_classes = [[
+
+% some character classes for xetex; seems to be rather hard coded, these numbers
+% and also a mix of several classes; here we do linebreaks
+
+]]
+
+local banner_utf_finish = [[
\endinput
]]
+local xtxclasses = {
+ id = 1,
+ ex = 3,
+ is = 3,
+ cm = 256,
+ op = 2,
+ ns = 3,
+ cl = 3,
+}
+
function scripts.chars.makeencoutf()
- local chartable = input.find_file(instance,"char-def.lua") or ""
+ local chartable = resolvers.find_file("char-def.lua") or ""
if chartable ~= "" then
dofile(chartable)
- if characters and characters.data then
- local f = io.open("enco-utf.tex", 'w')
+ local function open(name,banner)
+ local f = io.open(name,'w')
+ if f then
+ logs.simple("writing '%s'",name)
+ f:write(format(banner_utf_module,name))
+ f:write(banner)
+ f:write()
+ return f
+ end
+ end
+ local function close(f)
+ f:write(banner_utf_finish)
+ f:close()
+ end
+ local data = characters and characters.data
+ if data then
+ local list = table.sortedkeys(characters.data)
+ local f = open("xetx-utf.tex",banner_utf_mappings)
if f then
- local char, format = unicode.utf8.char, string.format
- f:write(scripts.chars.banner_utf_1)
- f:write(scripts.chars.banner_utf_2)
- local list = table.sortedkeys(characters.data)
- local length = 0
for i=1,#list do
local code = list[i]
if code <= 0xFFFF then
- local chr = characters.data[code]
+ local chr = data[code]
local cc = chr.category
if cc == 'll' or cc == 'lu' or cc == 'lt' then
if not chr.lccode then chr.lccode = code end
if not chr.uccode then chr.uccode = code end
- f:write(format("\\setcclcucx %04X %04X %04X %% %s\n",code,chr.lccode,chr.uccode,chr.description))
+ f:write(format('\\setXTXcharcodes "%05X "%05X "%05X %% %s\n',code,chr.lccode,chr.uccode,chr.description))
+ end
+ end
+ end
+ f:write("\n")
+ for i=1,#list do
+ local code = list[i]
+ local chr = data[code]
+ if chr and chr.range then
+ local cc = chr.category
+ if cc == 'lo' then
+ f:write(format('\\dofastrecurse{"%05X}{"%05X}{1}{\\dosetXTXcharcodes\\recurselevel\\recurselevel\\recurselevel}\n',code,chr.range))
end
- if #(chr.contextname or "") > length then
+ end
+ end
+ f:write(banner_utf_patch)
+ close(f)
+ end
+ local f = open("xetx-chr.tex",banner_utf_names)
+ if f then
+ local length = 0
+ for i=1,#list do
+ local code = list[i]
+ if code > 0x5B and code <= 0xFFFF then
+ local chr = data[code]
+ if chr and #(chr.contextname or "") > length then
length = #chr.contextname
end
end
end
- f:write(scripts.chars.banner_utf_3)
for i=1,#list do
local code = list[i]
if code > 0x5B and code <= 0xFFFF then
- local chr = characters.data[code]
- if chr.contextname then
- local ch = char(code)
---~ if ch:find("[~#$%%^&{}\\]") then
- f:write(format("\\def\\%s{\\char\"%04X } %% %s: %s\n", chr.contextname:rpadd(length," "), code, chr.description, ch))
---~ else
---~ f:write(format("\\def\\%s{%s} %% %s\n", chr.contextname:rpadd(length," "), ch,chr.description))
---~ end
+ local chr = data[code]
+ if chr and chr.contextname then
+ local ch = utfchar(code)
+ f:write(format("\\def\\%s{\\char\"%05X } %% %s: %s\n", chr.contextname:rpadd(length," "), code, chr.description, ch))
end
end
end
- f:write(scripts.chars.banner_utf_4)
- f:close()
+ close(f)
+ end
+ local f = open("xetx-cls.tex",banner_utf_classes)
+ if f then
+ for k, v in pairs(xtxclasses) do
+ f:write(format("\\defineXTXcharinjectionclass[lb:%s]\n",k))
+ end
+ f:write("\n")
+ local i_first, i_last, i_clb = nil, nil, nil
+ local function flush()
+ if i_first then
+ if i_first == i_last then
+ f:write(format('\\dosetXTXcharacterclass{"%05X}{lb:%s}\n',i_first,i_clb))
+ else
+ f:write(format('\\dofastrecurse{"%05X}{"%05X}{1}{\\dosetXTXcharacterclass\\fastrecursecounter{lb:%s}}\n',i_first,i_last,i_clb))
+ end
+ end
+ i_first, i_last, i_clb = nil, nil, nil
+ end
+ for i=1,#list do
+ local code = list[i]
+ local code_next = list[i+1]
+ local chr = data[code]
+ local chr_next = data[code_next]
+ local clb = chr and chr.linebreak
+ local lbc = xtxclasses[clb]
+ if not lbc then
+ flush()
+ elseif clb == i_clb then
+ if i_first then
+ i_last = code
+ else
+ i_first, i_last, i_clb = code, code, clb
+ end
+ else
+ flush()
+ i_first, i_last, i_clb = code, code, clb
+ end
+ end
+ flush()
+ f:write("\n")
+ for i=1,#list do
+ local code = list[i]
+ local chr = data[code]
+ if chr and chr.range then
+ local lbc = chr.linebreak
+ if xtxclasses[lbc] then
+ f:write(format('\\dofastrecurse{"%05X}{"%05X}{1}{\\dosetXTXcharacterclass\\fastrecursecounter{lb:%s}}\n',code,chr.range,lbc))
+ end
+ end
+ end
+ close(f)
end
end
end
end
-banner = banner .. " | character tools "
+logs.extendbanner("Character Tools 0.10")
messages.help = [[
--stix convert stix table to math table
---utf generate enco-utf.tex (used by xetex)
+--xtx generate xetx-*.tex (used by xetex)
+--pdf generate pdfr-def.tex (used by pdftex)
]]
if environment.argument("stix") then
local inname = environment.files[1] or ""
local outname = environment.files[2] or ""
scripts.chars.stixtomkiv(inname,outname)
-elseif environment.argument("utf") then
+elseif environment.argument("xtx") then
scripts.chars.makeencoutf()
+elseif environment.argument("pdf") then
+ scripts.chars.makepdfr()
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-check.lua b/Master/texmf-dist/scripts/context/lua/mtx-check.lua
index dd8a71264ac..6be7f276597 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-check.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-check.lua
@@ -6,8 +6,6 @@ if not modules then modules = { } end modules ['mtx-check'] = {
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
-
scripts = scripts or { }
scripts.checker = scripts.checker or { }
@@ -36,7 +34,7 @@ do
end
end
- local P, S, V, C, CP, CC = lpeg.P, lpeg.S, lpeg.V, lpeg.C, lpeg.Cp, lpeg.Cc
+ local P, R, S, V, C, CP, CC = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.C, lpeg.Cp, lpeg.Cc
local i_m, d_m = P("$"), P("$$")
local l_s, r_s = P("["), P("]")
@@ -51,22 +49,27 @@ do
local line = newline / function() validator.n = validator.n + 1 end
- local grammar = P { "tokens",
- ["tokens"] = (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + V("errors") + 1)^0,
- ["whatever"] = line + esc * 1 + C(P("%") * (1-line)^0),
- ["grouped"] = CP() * C(l_g * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_g - r_g))^0 * r_g) * CC("group") / progress,
- ["setup"] = CP() * C(l_s * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_s - r_s))^0 * r_s) * CC("setup") / progress,
- ["display"] = CP() * C(d_m * (V("whatever") + V("grouped") + (1 - d_m))^0 * d_m) * CC("display") / progress,
- ["inline"] = CP() * C(i_m * (V("whatever") + V("grouped") + (1 - i_m))^0 * i_m) * CC("inline") / progress,
- ["errors"] = (V("gerror") + V("serror") + V("derror") + V("ierror")) * true,
- ["gerror"] = CP() * (l_g + r_g) * CC("grouping") / message,
- ["serror"] = CP() * (l_s + r_g) * CC("setup error") / message,
- ["derror"] = CP() * d_m * CC("display math error") / message,
- ["ierror"] = CP() * i_m * CC("inline math error") / message,
- }
+ -- local grammar = P { "tokens",
+ -- ["tokens"] = (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + V("errors") + 1)^0,
+ -- ["whatever"] = line + esc * 1 + C(P("%") * (1-line)^0),
+ -- ["grouped"] = CP() * C(l_g * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_g - r_g))^0 * r_g) * CC("group") / progress,
+ -- ["setup"] = CP() * C(l_s * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_s - r_s))^0 * r_s) * CC("setup") / progress,
+ -- ["display"] = CP() * C(d_m * (V("whatever") + V("grouped") + (1 - d_m))^0 * d_m) * CC("display") / progress,
+ -- ["inline"] = CP() * C(i_m * (V("whatever") + V("grouped") + (1 - i_m))^0 * i_m) * CC("inline") / progress,
+ -- ["errors"] = (V("gerror") + V("serror") + V("derror") + V("ierror")) * true,
+ -- ["gerror"] = CP() * (l_g + r_g) * CC("grouping") / message,
+ -- ["serror"] = CP() * (l_s + r_g) * CC("setup error") / message,
+ -- ["derror"] = CP() * d_m * CC("display math error") / message,
+ -- ["ierror"] = CP() * i_m * CC("inline math error") / message,
+ -- }
+
+ local startluacode = P("\\startluacode")
+ local stopluacode = P("\\stopluacode")
+
+ local somecode = startluacode * (1-stopluacode)^1 * stopluacode
local grammar = P { "tokens",
- ["tokens"] = (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + V("errors") + 1)^0,
+ ["tokens"] = (V("ignore") + V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + V("errors") + 1)^0,
["whatever"] = line + esc * 1 + C(P("%") * (1-line)^0),
["grouped"] = l_g * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_g - r_g))^0 * r_g,
["setup"] = l_s * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_s - r_s))^0 * r_s,
@@ -77,6 +80,7 @@ do
["serror"] = CP() * (l_s + r_g) * CC("setup error") / message,
["derror"] = CP() * d_m * CC("display math error") / message,
["ierror"] = CP() * i_m * CC("inline math error") / message,
+ ["ignore"] = somecode,
}
function validator.check(str)
@@ -119,19 +123,16 @@ function scripts.checker.check(filename)
end
end
-
-banner = banner .. " | tex check tools "
+logs.extendbanner("Syntax Checking 0.10",true)
messages.help = [[
--convert check tex file for errors
]]
-input.verbose = true
-
if environment.argument("check") then
scripts.checker.check(environment.files[1])
elseif environment.argument("help") then
- input.help(banner,messages.help)
+ logs.help(messages.help)
elseif environment.files[1] then
scripts.checker.check(environment.files[1])
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-context.lua b/Master/texmf-dist/scripts/context/lua/mtx-context.lua
index 6c444d53162..71d2eee5611 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-context.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-context.lua
@@ -6,11 +6,16 @@ if not modules then modules = { } end modules ['mtx-context'] = {
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
-
scripts = scripts or { }
scripts.context = scripts.context or { }
+-- a demo cld file:
+--
+-- context.starttext()
+-- context.chapter("Hello There")
+-- context.readfile("tufte","","not found")
+-- context.stoptext()
+
-- l-file / todo
function file.needsupdate(oldfile,newfile)
@@ -25,30 +30,6 @@ function io.copydata(fromfile,tofile)
io.savedata(tofile,io.loaddata(fromfile) or "")
end
--- luat-inp
-
-function input.locate_format(name) -- move this to core / luat-xxx
- local barename, fmtname = name:gsub("%.%a+$",""), ""
- if input.usecache then
- local path = file.join(caches.setpath(instance,"formats")) -- maybe platform
- fmtname = file.join(path,barename..".fmt") or ""
- end
- if fmtname == "" then
- fmtname = input.find_files(instance,barename..".fmt")[1] or ""
- end
- fmtname = input.clean_path(fmtname)
- if fmtname ~= "" then
- barename = fmtname:gsub("%.%a+$","")
- local luaname, lucname = barename .. ".lua", barename .. ".luc"
- if lfs.isfile(lucname) then
- return barename, luaname
- elseif lfs.isfile(luaname) then
- return barename, luaname
- end
- end
- return nil, nil
-end
-
-- ctx
ctxrunner = { }
@@ -137,15 +118,15 @@ do
elseif ctxdata.ctxname then
ctlname = file.replacesuffix(ctxdata.ctxname,'ctl')
else
- input.report(string.format("invalid ctl name %s",ctlname or "?"))
+ logs.simple("invalid ctl name: %s",ctlname or "?")
return
end
end
if table.is_empty(ctxdata.prepfiles) then
- input.report("nothing prepared, no ctl file saved")
+ logs.simple("nothing prepared, no ctl file saved")
os.remove(ctlname)
else
- input.report(string.format("saving logdata in %s",ctlname))
+ logs.simple("saving logdata in: %s",ctlname)
f = io.open(ctlname,'w')
if f then
f:write("<?xml version='1.0' standalone='yes'?>\n\n")
@@ -190,8 +171,8 @@ do
ctxdata.jobname = file.addsuffix(ctxdata.jobname,'tex')
ctxdata.ctxname = file.addsuffix(ctxdata.ctxname,'ctx')
- input.report("jobname:",ctxdata.jobname)
- input.report("ctxname:",ctxdata.ctxname)
+ logs.simple("jobname: %s",ctxdata.jobname)
+ logs.simple("ctxname: %s",ctxdata.ctxname)
-- mtxrun should resolve kpse: and file:
@@ -208,7 +189,7 @@ do
end
end
- if not found and defaultname and defaultname ~= "" and file.exists(defaultname) then
+ if not found and defaultname and defaultname ~= "" and lfs.isfile(defaultname) then
usedname, found = defaultname, true
end
@@ -238,17 +219,9 @@ do
ctxdata.flags = ctxrunner.reflag(ctxdata.flags)
for _, message in ipairs(ctxdata.messages) do
- -- message ctxdata.justtext(xml.tostring(message))
+ logs.simple("ctx comment: %s", xml.tostring(message))
end
- --~ REXML::XPath.each(root,"//ctx:block") do |blk|
- --~ if @jobname && blk.attributes['pattern'] then
- --~ root.delete(blk) unless @jobname =~ /#{blk.attributes['pattern']}/
- --~ else
- --~ root.delete(blk)
- --~ end
- --~ end
-
xml.each(ctxdata.xmldata,"ctx:value[@name='job']", function(ek,e,k)
e[k] = ctxdata.variables['job'] or ""
end)
@@ -259,8 +232,8 @@ do
commands[ek.at and ek.at['name'] or "unknown"] = ek
end)
- local suffix = xml.first(ctxdata.xmldata,"/ctx:job/ctx:preprocess/@suffix") or ctxdata.suffix
- local runlocal = xml.first(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/@local")
+ local suffix = xml.filter(ctxdata.xmldata,"/ctx:job/ctx:preprocess/attribute(suffix)") or ctxdata.suffix
+ local runlocal = xml.filter(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/attribute(local)")
runlocal = toboolean(runlocal)
@@ -283,6 +256,7 @@ do
pattern = ctxrunner.justtext(xml.tostring(pattern))
local oldfiles = dir.glob(pattern)
+
local pluspath = false
if #oldfiles == 0 then
-- message: no files match pattern
@@ -333,11 +307,12 @@ do
end
end)
-- potential optimization: when mtxrun run internal
+ command = xml.text(command)
command = ctxrunner.justtext(command) -- command is still xml element here
- input.report("command",command)
- local result = os.spawn(command)
+ logs.simple("command: %s",command)
+ local result = os.spawn(command) or 0
if result > 0 then
- input.report("error, return code",result)
+ logs.simple("error, return code: %s",result)
end
if ctxdata.runlocal then
oldfile = file.basename(oldfile)
@@ -348,11 +323,11 @@ do
file.syncmtimes(oldfile,newfile)
ctxdata.prepfiles[oldfile] = true
else
- input.report("error, check target location of new file", newfile)
+ logs.simple("error, check target location of new file: %s", newfile)
ctxdata.prepfiles[oldfile] = false
end
else
- input.report("old file needs no preprocessing")
+ logs.simple("old file needs no preprocessing")
ctxdata.prepfiles[oldfile] = lfs.isfile(newfile)
end
end
@@ -370,7 +345,8 @@ end
-- rest
scripts.context.multipass = {
- suffixes = { ".tuo", ".tuc" },
+-- suffixes = { ".tuo", ".tuc" },
+ suffixes = { ".tuc" },
nofruns = 8,
}
@@ -400,15 +376,14 @@ scripts.context.backends = {
dvips = 'dvips'
}
-function scripts.context.multipass.makeoptionfile(jobname,ctxdata)
+function scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,currentrun,finalrun)
-- take jobname from ctx
+ jobname = file.removesuffix(jobname)
local f = io.open(jobname..".top","w")
if f then
- local finalrun, kindofrun, currentrun = false, 0, 0
local function someflag(flag)
return (ctxdata and ctxdata.flags[flag]) or environment.argument(flag)
end
---~ local someflag = environment.argument
local function setvalue(flag,format,hash,default)
local a = someflag(flag) or default
if a and a ~= "" then
@@ -421,13 +396,13 @@ function scripts.context.multipass.makeoptionfile(jobname,ctxdata)
end
end
end
- local function setvalues(flag,format)
+ local function setvalues(flag,format,plural)
if type(flag) == "table" then
for k, v in pairs(flag) do
f:write(format:format(v),"\n")
end
else
- local a = someflag(flag)
+ local a = someflag(flag) or (plural and someflag(flag.."s"))
if a and a ~= "" then
for v in a:gmatch("%s*([^,]+)") do
f:write(format:format(v),"\n")
@@ -443,98 +418,235 @@ function scripts.context.multipass.makeoptionfile(jobname,ctxdata)
local function setalways(format,...)
f:write(format:format(...),"\n")
end
+ --
+ setalways("%% runtime options files (command line driven)")
+ --
setalways("\\unprotect")
- setvalue('output' , "\\setupoutput[%s]", scripts.context.backends, 'pdftex')
- setalways( "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun, currentrun)
- setalways( "\\setupsystem[\\c!type=%s]",os.platform)
+ --
+ setalways("%% special commands, mostly for the ctx development team")
+ --
+ if environment.argument("dumpdelta") then
+ setalways("\\tracersdumpdelta")
+ elseif environment.argument("dumphash") then
+ setalways("\\tracersdumphash")
+ end
+ setalways("%% feedback and basic job control")
+ if type(environment.argument("track")) == "string" then
+ setvalue ("track" , "\\enabletrackers[%s]")
+ end
+ setfixed ("timing" , "\\usemodule[timing]")
setfixed ("batchmode" , "\\batchmode")
setfixed ("nonstopmode" , "\\nonstopmode")
setfixed ("tracefiles" , "\\tracefilestrue")
+ setfixed ("nostats" , "\\nomkivstatistics")
setfixed ("paranoid" , "\\def\\maxreadlevel{1}")
- setvalues("modefile" , "\\readlocfile{%s}{}{}")
+ --
+ setalways("%% handy for special styles")
+ --
+ setalways("\\startluacode")
+ setalways("document = document or { }")
+ setalways(table.serialize(environment.arguments, "document.arguments"))
+ setalways(table.serialize(environment.files, "document.files"))
+ setalways("\\stopluacode")
+ --
+ setalways("%% process info")
+ --
+ setalways( "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun or 0, currentrun or 0)
+ setalways( "\\setupsystem[\\c!type=%s]",os.platform)
+ setvalue ("inputfile" , "\\setupsystem[inputfile=%s]")
setvalue ("result" , "\\setupsystem[file=%s]")
setvalues("path" , "\\usepath[%s]")
+ setvalue ("setuppath" , "\\setupsystem[\\c!directory={%s}]")
+ setvalue ("randomseed" , "\\setupsystem[\\c!random=%s]")
+ setvalue ("arguments" , "\\setupenv[%s]")
+ setalways("%% modes")
+ setvalues("modefile" , "\\readlocfile{%s}{}{}")
+ setvalues("mode" , "\\enablemode[%s]", true)
+ if ctxdata then
+ setvalues(ctxdata.modes, "\\enablemode[%s]")
+ end
+ --
+ setalways("%% options (not that important)")
+ --
+ setalways("\\startsetups *runtime:options")
+ setvalue ('output' , "\\setupoutput[%s]", scripts.context.backends, 'pdftex')
setfixed ("color" , "\\setupcolors[\\c!state=\\v!start]")
- setfixed ("nompmode" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed ("nomprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed ("automprun" , "\\runMPgraphicsfalse") -- obsolete, we assume runtime mp graphics
- setfixed ("fast" , "\\fastmode\n")
- setfixed ("silentmode" , "\\silentmode\n")
setvalue ("separation" , "\\setupcolors[\\c!split=%s]")
- setvalue ("setuppath" , "\\setupsystem[\\c!directory={%s}]")
setfixed ("noarrange" , "\\setuparranging[\\v!disable]")
if environment.argument('arrange') and not finalrun then
setalways( "\\setuparranging[\\v!disable]")
end
- setvalue ("arguments" , "\\setupenv[%s]")
- setvalue ("randomseed" , "\\setupsystem[\\c!random=%s]")
- setvalues("modes" , "\\enablemode[%s]")
- setvalues("mode" , "\\enablemode[%s]")
- setvalues("filters" , "\\useXMLfilter[%s]")
- setvalues("usemodules" , "\\usemodule[%s]")
- setvalues("environments" , "\\environment %s ")
- -- ctx stuff
+ setalways("\\stopsetups")
+ --
+ setalways("%% styles and modules")
+ --
+ setalways("\\startsetups *runtime:modules")
+ setvalues("filter" , "\\useXMLfilter[%s]", true)
+ setvalues("usemodule" , "\\usemodule[%s]", true)
+ setvalues("environment" , "\\environment %s ", true)
if ctxdata then
- setvalues(ctxdata.modes, "\\enablemode[%s]")
setvalues(ctxdata.modules, "\\usemodule[%s]")
setvalues(ctxdata.environments, "\\environment %s ")
end
- -- done
- setalways( "\\protect")
- setalways( "\\endinput")
+ setalways("\\stopsetups")
+ --
+ setalways("%% done")
+ --
+ setalways("\\protect \\endinput")
f:close()
end
end
function scripts.context.multipass.copyluafile(jobname)
- io.savedata(jobname..".tuc",io.loaddata(jobname..".tua") or "")
-end
-
-function scripts.context.multipass.copytuifile(jobname)
- local f, g = io.open(jobname..".tui"), io.open(jobname..".tuo",'w')
- if f and g then
- g:write("% traditional utility file, only commands written by mtxrun/context\n%\n")
- for line in f:lines() do
- if line:find("^c ") then
- g:write((line:gsub("^c ","")),"%\n")
- end
- end
- g:write("\\endinput\n")
- f:close()
- g:close()
+-- io.savedata(jobname..".tuc",io.loaddata(jobname..".tua") or "")
+ local tuaname, tucname = jobname..".tua", jobname..".tuc"
+ if lfs.isfile(tuaname) then
+ os.remove(tucname)
+ os.rename(tuaname,tucname)
end
end
+-- obsolete:
+--
+-- function scripts.context.multipass.copytuifile(jobname)
+-- local tuiname, tuoname = jobname .. ".tui", jobname .. ".tuo"
+-- if lfs.isfile(tuiname) then
+-- local f, g = io.open(tuiname), io.open(tuoname,'w')
+-- if f and g then
+-- g:write("% traditional utility file, only commands written by mtxrun/context\n%\n")
+-- for line in f:lines() do
+-- if line:find("^c ") then
+-- g:write((line:gsub("^c ","")),"%\n")
+-- end
+-- end
+-- g:write("\\endinput\n")
+-- f:close()
+-- g:close()
+-- end
+-- else
+-- -- os.remove(tuoname)
+-- end
+-- end
+
+scripts.context.cldsuffixes = table.tohash {
+ "cld",
+}
+
scripts.context.xmlsuffixes = table.tohash {
"xml",
}
-function scripts.context.run(ctxdata)
- local function makestub(format,filename)
- local stubname = file.replacesuffix(file.basename(filename),'run')
- local f = io.open(stubname,'w')
- if f then
- f:write("\\starttext\n")
- f:write(string.format(format,filename),"\n")
- f:write("\\stoptext\n")
- f:close()
- filename = stubname
+scripts.context.luasuffixes = table.tohash {
+ "lua",
+}
+
+scripts.context.beforesuffixes = {
+ "tuo", "tuc"
+}
+scripts.context.aftersuffixes = {
+ "pdf", "tuo", "tuc", "log"
+}
+
+scripts.context.interfaces = {
+ en = "cont-en",
+ uk = "cont-uk",
+ de = "cont-de",
+ fr = "cont-fr",
+ nl = "cont-nl",
+ cz = "cont-cz",
+ it = "cont-it",
+ ro = "cont-ro",
+ pe = "cont-pe",
+ -- for taco and me
+ -- xp = "cont-xp",
+}
+
+scripts.context.defaultformats = {
+ "cont-en",
+ "cont-nl",
+-- "cont-xp",
+ "mptopdf",
+-- "metatex",
+ "metafun",
+ "plain"
+}
+
+local function analyze(filename)
+ local f = io.open(file.addsuffix(filename,"tex"))
+ if f then
+ local t = { }
+ local line = f:read("*line") or ""
+ local preamble = line:match("[\254\255]*%%%s+(.+)$") -- there can be an utf bomb in front
+ if preamble then
+ for key, value in preamble:gmatch("(%S+)=(%S+)") do
+ t[key] = value
+ end
+ t.type = "tex"
+ elseif line:find("^<?xml ") then
+ t.type = "xml"
+ end
+ if not t.engine then
+ t.engine = 'luatex'
end
- return filename
+ f:close()
+ return t
end
+ return nil
+end
+
+local function makestub(format,filename)
+ local stubname = file.replacesuffix(file.basename(filename),'run')
+ local f = io.open(stubname,'w')
+ if f then
+ f:write("\\starttext\n")
+ f:write(string.format(format,filename),"\n")
+ f:write("\\stoptext\n")
+ f:close()
+ filename = stubname
+ end
+ return filename
+end
+
+function scripts.context.openpdf(name)
+ os.spawn(string.format('pdfopen --file "%s" 2>&1', file.replacesuffix(name,"pdf")))
+end
+function scripts.context.closepdf(name)
+ os.spawn(string.format('pdfclose --file "%s" 2>&1', file.replacesuffix(name,"pdf")))
+end
+
+--~ function scripts.context.openpdf(name)
+--~ -- somehow two instances start up, one with a funny filename
+--~ os.spawn(string.format("\"c:/program files/kde/bin/okular.exe\" --unique %s",file.replacesuffix(name,"pdf")))
+--~ end
+--~ function scripts.context.closepdf(name)
+--~ --
+--~ end
+
+function scripts.context.run(ctxdata,filename)
+ -- filename overloads environment.files
+ local files = (filename and { filename }) or environment.files
if ctxdata then
-- todo: interface
for k,v in pairs(ctxdata.flags) do
environment.setargument(k,v)
end
end
- local files = environment.files
if #files > 0 then
- input.identify_cnf(instance)
- input.load_cnf(instance)
- input.expand_variables(instance)
- local formatname = "cont-en"
- local formatfile, scriptfile = input.locate_format(formatname)
+ --
+ local interface = environment.argument("interface")
+ -- todo: environment.argument("interface","en")
+ interface = (type(interface) == "string" and interface) or "en"
+ --
+ local formatname = scripts.context.interfaces[interface] or "cont-en"
+ local formatfile, scriptfile = resolvers.locate_format(formatname)
+ -- this catches the command line
+ if not formatfile or not scriptfile then
+ logs.simple("warning: no format found, forcing remake (commandline driven)")
+ scripts.context.generate()
+ scripts.context.make(formatname)
+ formatfile, scriptfile = resolvers.locate_format(formatname)
+ end
+ --
if formatfile and scriptfile then
for _, filename in ipairs(files) do
local basename, pathname = file.basename(filename), file.dirname(filename)
@@ -542,66 +654,278 @@ function scripts.context.run(ctxdata)
if pathname == "" then
filename = "./" .. filename
end
- -- also other stubs
- if environment.argument("forcexml") or scripts.context.xmlsuffixes[file.extname(filename) or "?"] then -- mkii
- filename = makestub("\\processXMLfilegrouped{%s}",filename)
- elseif environment.argument("processxml") then -- mkiv
- filename = makestub("\\xmlprocess{%s}",filename)
- end
- --
- if environment.argument("autopdf") then
- os.spawn(string.format('pdfclose --file "%s" 2>&1', file.replacesuffix(filename,"pdf")))
- end
- --
- local command = "luatex --fmt=" .. string.quote(formatfile) .. " --lua=" .. string.quote(scriptfile) .. " " .. string.quote(filename)
- local oldhash, newhash = scripts.context.multipass.hashfiles(jobname), { }
- scripts.context.multipass.makeoptionfile(jobname,ctxdata)
- for i=1, scripts.context.multipass.nofruns do
- input.report(string.format("run %s: %s",i,command))
- local returncode = os.spawn(command)
- input.report("return code: " .. returncode)
- if returncode > 0 then
- input.report("fatal error, run aborted")
- break
+ -- look at the first line
+ local a = analyze(filename)
+ if a and (a.engine == 'pdftex' or a.engine == 'xetex' or environment.argument("pdftex") or environment.argument("xetex")) then
+ local texexec = resolvers.find_file("texexec.rb") or ""
+ if texexec ~= "" then
+ os.setenv("RUBYOPT","")
+ local command = string.format("ruby %s %s",texexec,environment.reconstruct_commandline(environment.arguments_after))
+ os.exec(command)
+ end
+ else
+ if a and a.interface and a.interface ~= interface then
+ formatname = scripts.context.interfaces[a.interface] or formatname
+ formatfile, scriptfile = resolvers.locate_format(formatname)
+ end
+ -- this catches the command line
+ if not formatfile or not scriptfile then
+ logs.simple("warning: no format found, forcing remake (source driven)")
+ scripts.context.generate()
+ scripts.context.make(formatname)
+ formatfile, scriptfile = resolvers.locate_format(formatname)
+ end
+ if formatfile and scriptfile then
+ -- we default to mkiv xml !
+ local suffix = file.extname(filename) or "?"
+ if scripts.context.xmlsuffixes[suffix] or environment.argument("forcexml") then
+ if environment.argument("mkii") then
+ filename = makestub("\\processXMLfilegrouped{%s}",filename)
+ else
+ filename = makestub("\\xmlprocess{\\xmldocument}{%s}{}",filename)
+ end
+ elseif scripts.context.cldsuffixes[suffix] or environment.argument("forcecld") then
+ filename = makestub("\\ctxlua{context.runfile('%s')}",filename)
+ elseif scripts.context.luasuffixes[suffix] or environment.argument("forcelua") then
+ filename = makestub("\\ctxlua{dofile('%s')}",filename)
+ end
+ --
+ -- todo: also other stubs
+ --
+ local suffix, resultname = environment.argument("suffix"), environment.argument("result")
+ if type(suffix) == "string" then
+ resultname = file.removesuffix(jobname) .. suffix
+ end
+ local oldbase, newbase = "", ""
+ if type(resultname) == "string" then
+ oldbase = file.removesuffix(jobname)
+ newbase = file.removesuffix(resultname)
+ if oldbase ~= newbase then
+ for _, suffix in pairs(scripts.context.beforesuffixes) do
+ local oldname = file.addsuffix(oldbase,suffix)
+ local newname = file.addsuffix(newbase,suffix)
+ local tmpname = "keep-"..oldname
+ os.remove(tmpname)
+ os.rename(oldname,tmpname)
+ os.remove(oldname)
+ os.rename(newname,oldname)
+ end
+ else
+ resultname = nil
+ end
+ else
+ resultname = nil
+ end
+ --
+ if environment.argument("autopdf") then
+ scripts.context.closepdf(filename)
+ if resultname then
+ scripts.context.closepdf(resultname)
+ end
+ end
+ --
+ local okay = statistics.check_fmt_status(formatfile)
+ if okay ~= true then
+ logs.simple("warning: %s, forcing remake",tostring(okay))
+ scripts.context.generate()
+ scripts.context.make(formatname)
+ end
+ --
+ local flags = { }
+ if environment.argument("batchmode") then
+ flags[#flags+1] = "--interaction=batchmode"
+ end
+ flags[#flags+1] = "--fmt=" .. string.quote(formatfile)
+ flags[#flags+1] = "--lua=" .. string.quote(scriptfile)
+ flags[#flags+1] = "--backend=pdf"
+ local command = string.format("luatex %s %s", table.concat(flags," "), string.quote(filename))
+ local oldhash, newhash = scripts.context.multipass.hashfiles(jobname), { }
+ local once = environment.argument("once")
+ local maxnofruns = (once and 1) or scripts.context.multipass.nofruns
+ local arrange = environment.argument("arrange")
+ for i=1,maxnofruns do
+ -- 1:first run, 2:successive run, 3:once, 4:last of maxruns
+ local kindofrun = (once and 3) or (i==1 and 1) or (i==maxnofruns and 4) or 2
+ scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,false) -- kindofrun, currentrun, final
+ logs.simple("run %s: %s",i,command)
+ local returncode, errorstring = os.spawn(command)
+ --~ if returncode == 3 then
+ --~ scripts.context.generate()
+ --~ scripts.context.make(formatname)
+ --~ returncode, errorstring = os.spawn(command)
+ --~ if returncode == 3 then
+ --~ logs.simple("fatal error, return code 3, message: %s",errorstring or "?")
+ --~ os.exit(1)
+ --~ end
+ --~ end
+ if not returncode then
+ logs.simple("fatal error, no return code, message: %s",errorstring or "?")
+ os.exit(1)
+ break
+ elseif returncode > 0 then
+ logs.simple("fatal error, return code: %s",returncode or "?")
+ os.exit(returncode)
+ break
+ else
+ scripts.context.multipass.copyluafile(jobname)
+ -- scripts.context.multipass.copytuifile(jobname)
+ newhash = scripts.context.multipass.hashfiles(jobname)
+ if scripts.context.multipass.changed(oldhash,newhash) then
+ oldhash = newhash
+ else
+ break
+ end
+ end
+ end
+ --
+ if arrange then
+ local kindofrun = 3
+ scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,true) -- kindofrun, currentrun, final
+ logs.simple("arrange run: %s",command)
+ local returncode, errorstring = os.spawn(command)
+ if not returncode then
+ logs.simple("fatal error, no return code, message: %s",errorstring or "?")
+ os.exit(1)
+ elseif returncode > 0 then
+ logs.simple("fatal error, return code: %s",returncode or "?")
+ os.exit(returncode)
+ end
+ end
+ --
+ if environment.argument("purge") then
+ scripts.context.purge_job(filename)
+ elseif environment.argument("purgeall") then
+ scripts.context.purge_job(filename,true)
+ end
+ --
+ os.remove(jobname..".top")
+ --
+ if resultname then
+ for _, suffix in pairs(scripts.context.aftersuffixes) do
+ local oldname = file.addsuffix(oldbase,suffix)
+ local newname = file.addsuffix(newbase,suffix)
+ local tmpname = "keep-"..oldname
+ os.remove(newname)
+ os.rename(oldname,newname)
+ os.rename(tmpname,oldname)
+ end
+ logs.simple("result renamed to: %s",newbase)
+ end
+ --
+ if environment.argument("purge") then
+ scripts.context.purge_job(resultname)
+ elseif environment.argument("purgeall") then
+ scripts.context.purge_job(resultname,true)
+ end
+ --
+ if environment.argument("autopdf") then
+ scripts.context.openpdf(resultname or filename)
+ end
+ --
+ if environment.argument("timing") then
+ logs.line()
+ logs.simple("you can process (timing) statistics with:",jobname)
+ logs.line()
+ logs.simple("context --extra=timing '%s'",jobname)
+ logs.simple("mtxrun --script timing --xhtml [--launch --remove] '%s'",jobname)
+ logs.line()
+ end
else
- scripts.context.multipass.copyluafile(jobname)
- scripts.context.multipass.copytuifile(jobname)
- newhash = scripts.context.multipass.hashfiles(jobname)
- if scripts.context.multipass.changed(oldhash,newhash) then
- oldhash = newhash
+ if formatname then
+ logs.simple("error, no format found with name: %s, skipping",formatname)
else
- break
+ logs.simple("error, no format found (provide formatname or interface)")
end
+ break
end
end
- --
- -- todo: result
- --
- if environment.argument("autopdf") then
- os.spawn(string.format('pdfopen --file "%s" 2>&1', file.replacesuffix(filename,"pdf")))
- end
- --
end
else
- input.verbose = true
- input.report("error", "no format found with name " .. formatname)
+ if formatname then
+ logs.simple("error, no format found with name: %s, aborting",formatname)
+ else
+ logs.simple("error, no format found (provide formatname or interface)")
+ end
end
end
end
-function scripts.context.make()
- local list = (environment.files[1] and environment.files) or { "cont-en", "cont-nl", "mptopdf" }
- for _, name in ipairs(list) do
- local command = "luatools --make --compile " .. name
- input.report("running command: " .. command)
+function scripts.context.pipe()
+ -- context --pipe
+ -- context --pipe --purge --dummyfile=whatever.tmp
+ local interface = environment.argument("interface")
+ interface = (type(interface) == "string" and interface) or "en"
+ local formatname = scripts.context.interfaces[interface] or "cont-en"
+ local formatfile, scriptfile = resolvers.locate_format(formatname)
+ if not formatfile or not scriptfile then
+ logs.simple("warning: no format found, forcing remake (commandline driven)")
+ scripts.context.generate()
+ scripts.context.make(formatname)
+ formatfile, scriptfile = resolvers.locate_format(formatname)
+ end
+ if formatfile and scriptfile then
+ local okay = statistics.check_fmt_status(formatfile)
+ if okay ~= true then
+ logs.simple("warning: %s, forcing remake",tostring(okay))
+ scripts.context.generate()
+ scripts.context.make(formatname)
+ end
+ local flags = {
+ "--interaction=scrollmode",
+ "--fmt=" .. string.quote(formatfile),
+ "--lua=" .. string.quote(scriptfile),
+ "--backend=pdf",
+ }
+ local filename = environment.argument("dummyfile") or ""
+ if filename == "" then
+ filename = "\\relax"
+ logs.simple("entering scrollmode, end job with \\end")
+ else
+ filename = file.addsuffix(filename,"tmp")
+ io.savedata(filename,"\\relax")
+ scripts.context.multipass.makeoptionfile(filename,{ flags = flags },3,1,false) -- kindofrun, currentrun, final
+ logs.simple("entering scrollmode using '%s' with optionfile, end job with \\end",filename)
+ end
+ local command = string.format("luatex %s %s", table.concat(flags," "), string.quote(filename))
os.spawn(command)
+ if environment.argument("purge") then
+ scripts.context.purge_job(filename)
+ elseif environment.argument("purgeall") then
+ scripts.context.purge_job(filename,true)
+ os.remove(filename)
+ end
+ else
+ if formatname then
+ logs.simple("error, no format found with name: %s, aborting",formatname)
+ else
+ logs.simple("error, no format found (provide formatname or interface)")
+ end
+ end
+end
+
+function scripts.context.make(name)
+ local runners = {
+ "luatools --make --compile ",
+ (environment.argument("pdftex") and "mtxrun texexec.rb --make --pdftex ") or false,
+ (environment.argument("xetex") and "mtxrun texexec.rb --make --xetex " ) or false,
+ }
+ local list = (name and { name }) or (environment.files[1] and environment.files) or scripts.context.defaultformats
+ for _, name in ipairs(list) do
+ name = scripts.context.interfaces[name] or name
+ for _, runner in ipairs(runners) do
+ if runner then
+ local command = runner .. name
+ logs.simple("running command: %s",command)
+ os.spawn(command)
+ end
+ end
end
end
function scripts.context.generate()
-- hack, should also be a shared function
local command = "luatools --generate "
- input.report("running command: " .. command)
+ logs.simple("running command: %s",command)
os.spawn(command)
end
@@ -612,110 +936,508 @@ function scripts.context.ctx()
scripts.context.run(ctxdata)
end
+function scripts.context.autoctx()
+ local ctxdata = nil
+ local files = (filename and { filename }) or environment.files
+ local firstfile = #files > 0 and files[1]
+ if firstfile and file.extname(firstfile) == "xml" then
+ local f = io.open(firstfile)
+ if f then
+ local chunk = f:read(512) or ""
+ f:close()
+ local ctxname = string.match(chunk,"<%?context%-directive%s+job%s+ctxfile%s+([^ ]-)%s*?>")
+ if ctxname then
+ ctxdata = ctxrunner.new()
+ ctxdata.jobname = firstfile
+ ctxrunner.manipulate(ctxdata,ctxname)
+ end
+ end
+ end
+ scripts.context.run(ctxdata)
+end
+
+-- todo: quite after first image
+
+local template = [[
+ \starttext
+ \startMPpage %% %s
+ input "%s" ;
+ \stopMPpage
+ \stoptext
+]]
+
+local loaded = false
+
+function scripts.context.metapost()
+ local filename = environment.files[1] or ""
+--~ local tempname = "mtx-context-metapost.tex"
+--~ local tempdata = string.format(template,"metafun",filename)
+--~ io.savedata(tempname,tempdata)
+--~ environment.files[1] = tempname
+--~ environment.setargument("result",file.removesuffix(filename))
+--~ environment.setargument("once",true)
+--~ scripts.context.run()
+ if not loaded then
+ dofile(resolvers.find_file("mlib-run.lua"))
+ loaded = true
+ commands = commands or { }
+ commands.writestatus = logs.report
+ end
+ local formatname = environment.arguments("format") or "metafun"
+ if formatname == "" or type(format) == "boolean" then
+ formatname = "metafun"
+ end
+ if environment.arguments("svg") then
+ metapost.directrun(formatname,filename,"svg")
+ else
+ metapost.directrun(formatname,filename,"mps")
+ end
+end
+
function scripts.context.version()
- local name = input.find_file(instance,"context.tex")
+ local name = resolvers.find_file("context.tex")
if name ~= "" then
- input.report(string.format("main context file: %s",name))
+ logs.simple("main context file: %s",name)
local data = io.loaddata(name)
if data then
local version = data:match("\\edef\\contextversion{(.-)}")
if version then
- input.report(string.format("current version : %s",version))
+ logs.simple("current version: %s",version)
else
- input.report("context version: unknown, no timestamp found")
+ logs.simple("context version: unknown, no timestamp found")
end
else
- input.report("context version: unknown, load error")
+ logs.simple("context version: unknown, load error")
end
else
- input.report("main context file: unknown, 'context.tex' not found")
+ logs.simple("main context file: unknown, 'context.tex' not found")
+ end
+end
+
+local generic_files = {
+ "texexec.tex", "texexec.tui", "texexec.tuo",
+ "texexec.tuc", "texexec.tua",
+ "texexec.ps", "texexec.pdf", "texexec.dvi",
+ "cont-opt.tex", "cont-opt.bak"
+}
+
+local obsolete_results = {
+ "dvi",
+}
+
+local temporary_runfiles = {
+ "tui", "tua", "tup", "ted", "tes", "top",
+ "log", "tmp", "run", "bck", "rlg",
+ "mpt", "mpx", "mpd", "mpo", "mpb", "ctl",
+ "synctex.gz", "pgf"
+}
+
+local persistent_runfiles = {
+ "tuo", "tub", "top", "tuc"
+}
+
+local function purge_file(dfile,cfile)
+ if cfile and lfs.isfile(cfile) then
+ if os.remove(dfile) then
+ return file.basename(dfile)
+ end
+ else
+ if os.remove(dfile) then
+ return file.basename(dfile)
+ end
+ end
+end
+
+function scripts.context.purge_job(jobname,all)
+ jobname = file.basename(jobname)
+ local filebase = file.removesuffix(jobname)
+ local deleted = { }
+ for _, suffix in ipairs(obsolete_results) do
+ deleted[#deleted+1] = purge_file(filebase.."."..suffix,filebase..".pdf")
+ end
+ for _, suffix in ipairs(temporary_runfiles) do
+ deleted[#deleted+1] = purge_file(filebase.."."..suffix)
+ end
+ if all then
+ for _, suffix in ipairs(persistent_runfiles) do
+ deleted[#deleted+1] = purge_file(filebase.."."..suffix)
+ end
+ end
+ if #deleted > 0 then
+ logs.simple("purged files: %s", table.join(deleted,", "))
+ end
+end
+
+function scripts.context.purge(all)
+ local all = all or environment.argument("all")
+ local pattern = environment.argument("pattern") or "*.*"
+ local files = dir.glob(pattern)
+ local obsolete = table.tohash(obsolete_results)
+ local temporary = table.tohash(temporary_runfiles)
+ local persistent = table.tohash(persistent_runfiles)
+ local generic = table.tohash(generic_files)
+ local deleted = { }
+ for _, name in ipairs(files) do
+ local suffix = file.extname(name)
+ local basename = file.basename(name)
+ if obsolete[suffix] or temporary[suffix] or persistent[suffix] or generic[basename] then
+ deleted[#deleted+1] = purge_file(name)
+ end
+ end
+ if #deleted > 0 then
+ logs.simple("purged files: %s", table.join(deleted,", "))
+ end
+end
+
+--~ purge_for_files("test",true)
+--~ purge_all_files()
+
+local function touch(name,pattern)
+ local name = resolvers.find_file(name)
+ local olddata = io.loaddata(name)
+ if olddata then
+ local oldversion, newversion = "", os.date("%Y.%m.%d %H:%M")
+ local newdata, ok = olddata:gsub(pattern,function(pre,mid,post)
+ oldversion = mid
+ return pre .. newversion .. post
+ end)
+ if ok > 0 then
+ local backup = file.replacesuffix(name,"tmp")
+ os.remove(backup)
+ os.rename(name,backup)
+ io.savedata(name,newdata)
+ return true, oldversion, newversion, name
+ else
+ return false
+ end
end
end
function scripts.context.touch()
if environment.argument("expert") then
- local function touch(name,pattern)
- local name = input.find_file(instance,name)
- local olddata = io.loaddata(name)
- if olddata then
- local oldversion, newversion = "", os.date("%Y.%M.%d %H:%m")
- local newdata, ok = olddata:gsub(pattern,function(pre,mid,post)
- oldversion = mid
- return pre .. newversion .. post
- end)
- if ok > 0 then
- local backup = file.replacesuffix(name,"tmp")
- os.remove(backup)
- os.rename(name,backup)
- io.savedata(name,newdata)
- return true, oldversion, newversion, name
- else
- return false
- end
- end
- end
local done, oldversion, newversion, foundname = touch("context.tex", "(\\edef\\contextversion{)(.-)(})")
if done then
- input.report(string.format("old version : %s", oldversion))
- input.report(string.format("new version : %s", newversion))
- input.report(string.format("touched file: %s", foundname))
+ logs.simple("old version : %s", oldversion)
+ logs.simple("new version : %s", newversion)
+ logs.simple("touched file: %s", foundname)
local ok, _, _, foundname = touch("cont-new.tex", "(\\newcontextversion{)(.-)(})")
if ok then
- input.report(string.format("touched file: %s", foundname))
+ logs.simple("touched file: %s", foundname)
+ end
+ local ok, _, _, foundname = touch("cont-xp.tex", "(\\edef\\contextversion{)(.-)(})")
+ if ok then
+ logs.simple("touched file: %s", foundname)
+ end
+ end
+ end
+end
+
+-- extras
+
+function scripts.context.extras(pattern)
+ local found = resolvers.find_file("context.tex")
+ if found == "" then
+ logs.simple("unknown extra: %s", extra)
+ else
+ pattern = file.join(dir.expand_name(file.dirname(found)),string.format("mtx-context-%s.tex",pattern or "*"))
+ local list = dir.glob(pattern)
+ if not extra or extra == "" then
+ logs.extendbanner("extras")
+ else
+ logs.extendbanner(extra)
+ end
+ for k,v in ipairs(list) do
+ local data = io.loaddata(v) or ""
+ data = string.match(data,"begin help(.-)end help")
+ if data then
+ local h = { string.format("extra: %s (%s)",string.gsub(v,"^.*mtx%-context%-(.-)%.tex$","%1"),v) }
+ for s in string.gmatch(data,"%% *(.-)[\n\r]") do
+ h[#h+1] = s
+ end
+ logs.help(table.concat(h,"\n"),"nomoreinfo")
+ end
+ end
+ end
+end
+
+function scripts.context.extra()
+ local extra = environment.argument("extra")
+ if type(extra) == "string" then
+ if environment.argument("help") then
+ scripts.context.extras(extra)
+ else
+ local fullextra = extra
+ if not string.find(fullextra,"mtx%-context%-") then
+ fullextra = "mtx-context-" .. extra
+ end
+ local foundextra = resolvers.find_file(fullextra)
+ if foundextra == "" then
+ scripts.context.extras()
+ return
+ else
+ logs.simple("processing extra: %s", foundextra)
+ end
+ environment.setargument("purgeall",true)
+ local result = environment.setargument("result") or ""
+ if result == "" then
+ environment.setargument("result","context-extra")
end
+ scripts.context.run(nil,foundextra)
end
+ else
+ scripts.context.extras()
end
end
+-- todo: we need to do a dummy run
+
+function scripts.context.track()
+ environment.files = { "m-track" }
+ scripts.context.multipass.nofruns = 1
+ scripts.context.run()
+ -- maybe filter from log
+end
+
function scripts.context.timed(action)
- input.starttiming(scripts.context)
- action()
- input.stoptiming(scripts.context)
- input.report("total runtime: " .. input.elapsedtime(scripts.context))
+ statistics.timed(action)
+end
+
+local zipname = "cont-tmf.zip"
+local mainzip = "http://www.pragma-ade.com/context/latest/" .. zipname
+local validtrees = { "texmf-local", "texmf-context" }
+
+function zip.loaddata(zipfile,filename) -- should be in zip lib
+ local f = zipfile:open(filename)
+ if f then
+ local data = f:read("*a")
+ f:close()
+ return data
+ end
+ return nil
+end
+
+function scripts.context.update()
+ local force = environment.argument("force")
+ local socket = require("socket")
+ local http = require("socket.http")
+ local basepath = resolvers.find_file("context.tex") or ""
+ if basepath == "" then
+ logs.simple("quiting, no 'context.tex' found")
+ return
+ end
+ local basetree = basepath.match(basepath,"^(.-)tex/context/base/context.tex$") or ""
+ if basetree == "" then
+ logs.simple("quiting, no proper tds structure (%s)",basepath)
+ return
+ end
+ local function is_okay(basetree)
+ for _, tree in next, validtrees do
+ local pattern = string.gsub(tree,"%-","%%-")
+ if basetree:find(pattern) then
+ return tree
+ end
+ end
+ return false
+ end
+ local okay = is_okay(basetree)
+ if not okay then
+ logs.simple("quiting, tree '%s' is protected",okay)
+ return
+ else
+ logs.simple("updating tree '%s'",okay)
+ end
+ if not lfs.chdir(basetree) then
+ logs.simple("quiting, unable to change to '%s'",okay)
+ return
+ end
+ logs.simple("fetching '%s'",mainzip)
+ local latest = http.request(mainzip)
+ if not latest then
+ logs.simple("context tree '%s' can be updated, use --force",okay)
+ return
+ end
+ io.savedata("cont-tmf.zip",latest)
+ if false then
+ -- variant 1
+ os.execute("mtxrun --script unzip cont-tmf.zip")
+ else
+ -- variant 2
+ local zipfile = zip.open(zipname)
+ if not zipfile then
+ logs.simple("quiting, unable to open '%s'",zipname)
+ return
+ end
+ local newfile = zip.loaddata(zipfile,"tex/context/base/context.tex")
+ if not newfile then
+ logs.simple("quiting, unable to open '%s'","context.tex")
+ return
+ end
+ local oldfile = io.loaddata(resolvers.find_file("context.tex")) or ""
+ local function versiontonumber(what,str)
+ local version = str:match("\\edef\\contextversion{(.-)}") or ""
+ local year, month, day, hour, minute = str:match("\\edef\\contextversion{(%d+)%.(%d+)%.(%d+) *(%d+)%:(%d+)}")
+ if year and minute then
+ local time = os.time { year=year,month=month,day=day,hour=hour,minute=minute}
+ logs.simple("%s version: %s (%s)",what,version,time)
+ return time
+ else
+ logs.simple("%s version: %s (unknown)",what,version)
+ return nil
+ end
+ end
+ local oldversion = versiontonumber("old",oldfile)
+ local newversion = versiontonumber("new",newfile)
+ if not oldversion or not newversion then
+ logs.simple("quiting, version cannot be determined")
+ return
+ elseif oldversion == newversion then
+ logs.simple("quiting, your current version is up-to-date")
+ return
+ elseif oldversion > newversion then
+ logs.simple("quiting, your current version is newer")
+ return
+ end
+ for k in zipfile:files() do
+ local filename = k.filename
+ if filename:find("/$") then
+ lfs.mkdir(filename)
+ else
+ local data = zip.loaddata(zipfile,filename)
+ if data then
+ if force then
+ io.savedata(filename,data)
+ end
+ logs.simple(filename)
+ end
+ end
+ end
+ for _, scriptname in next, { "luatools.lua", "mtxrun.lua" } do
+ local oldscript = resolvers.find_file(scriptname) or ""
+ if oldscript ~= "" and is_okay(oldscript) then
+ local newscript = "./scripts/context/lua/" .. scriptname
+ local data = io.loaddata(newscript) or ""
+ if data ~= "" then
+ logs.simple("replacing script '%s' by '%s'",oldscript,newscript)
+ if force then
+ io.savedata(oldscript,data)
+ end
+ end
+ else
+ logs.simple("keeping script '%s'",oldscript)
+ end
+ end
+ if force then
+ os.execute("context --generate")
+ os.execute("context --make")
+ end
+ end
+ if force then
+ logs.simple("context tree '%s' has been updated",okay)
+ else
+ logs.simple("context tree '%s' can been updated (use --force)",okay)
+ end
end
-banner = banner .. " | context tools "
+logs.extendbanner("ConTeXt Tools 0.51",true)
messages.help = [[
--run process (one or more) files (default action)
---make create context formats formats
+--make create context formats
--generate generate file database etc.
--ctx=name use ctx file
--version report installed context version
---autopdf open pdf file afterwards
+--forcexml force xml stub (optional flag: --mkii)
+--forcecld force cld (context lua document) stub
+--autopdf close pdf file in viewer and start pdf viewer afterwards
+--once only one run
+--purge(all) purge files (--pattern=...)
+--result=name rename result to given name
+--arrange run extra arrange pass
--expert expert options
+--interface use specified user interface
]]
messages.expert = [[
-expert options: also provide --expert
+expert options:
+
+--touch update context version number (remake needed afterwards, also provide --expert)
+--update update context from website (not to be confused with contextgarden)
+--profile profile job (use: mtxrun --script profile --analyse)
+--track show/set tracker variables
+--timing generate timing and statistics overview
+--extra=name process extra (mtx-context-<name> in distribution)
+]]
---touch update context version (remake needed afterwards)
+messages.private = [[
+private options:
+
+--dumphash dump hash table afterwards
+--dumpdelta dump hash table afterwards (only new entries)
]]
-input.verbose = true
+messages.special = [[
+special options:
+
+--pdftex process file with texexec using pdftex
+--xetex process file with texexec using xetex
+
+--pipe don't check for file and enter scroll mode (--dummyfile=whatever.tmp)
+]]
if environment.argument("once") then
scripts.context.multipass.nofruns = 1
end
+if environment.argument("profile") then
+ os.setenv("MTX_PROFILE_RUN","YES")
+end
+
if environment.argument("run") then
- scripts.context.timed(scripts.context.run)
-elseif environment.argument("make") then
- scripts.context.timed(scripts.context.make)
+-- scripts.context.timed(scripts.context.run)
+ scripts.context.timed(scripts.context.autoctx)
+elseif environment.argument("make") or environment.argument("generate") then
+ scripts.context.timed(function()
+ if environment.argument("generate") then
+ scripts.context.generate()
+ end
+ if environment.argument("make") then
+ scripts.context.make()
+ end
+ end)
elseif environment.argument("ctx") then
scripts.context.timed(scripts.context.ctx)
+elseif environment.argument("mp") or environment.argument("metapost") then
+ scripts.context.timed(scripts.context.metapost)
elseif environment.argument("version") then
scripts.context.version()
elseif environment.argument("touch") then
scripts.context.touch()
-elseif environment.argument("help") then
- input.help(banner,messages.help)
+elseif environment.argument("update") then
+ scripts.context.update()
elseif environment.argument("expert") then
- input.help(banner,messages.expert)
+ logs.help(table.join({ messages.expert, messages.private, messages.special },"\n"))
+elseif environment.argument("extra") then
+ scripts.context.extra()
+elseif environment.argument("help") then
+ logs.help(messages.help)
+elseif environment.argument("track") and type(environment.argument("track")) == "boolean" then
+ scripts.context.track()
elseif environment.files[1] then
- scripts.context.timed(scripts.context.run)
+-- scripts.context.timed(scripts.context.run)
+ scripts.context.timed(scripts.context.autoctx)
+elseif environment.argument("pipe") then
+ scripts.context.timed(scripts.context.pipe)
+elseif environment.argument("purge") then
+ -- only when no filename given, supports --pattern
+ scripts.context.purge()
+elseif environment.argument("purgeall") then
+ -- only when no filename given, supports --pattern
+ scripts.context.purge(true)
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
+if environment.argument("profile") then
+ os.setenv("MTX_PROFILE_RUN","NO")
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-convert.lua b/Master/texmf-dist/scripts/context/lua/mtx-convert.lua
index 1bfc10c0fa8..c0c383b176f 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-convert.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-convert.lua
@@ -6,46 +6,89 @@ if not modules then modules = { } end modules ['mtx-convert'] = {
license = "see context related readme files"
}
-do
-
- graphics = graphics or { }
- graphics.converters = graphics.converters or { }
-
- local gsprogram = (os.platform == "windows" and "gswin32c") or "gs"
- local gstemplate = "%s -q -sDEVICE=pdfwrite -dEPSCrop -dNOPAUSE -dNOCACHE -dBATCH -dAutoRotatePages=/None -dProcessColorModel=/DeviceCMYK -sOutputFile=%s %s -c quit"
-
- function graphics.converters.epstopdf(inputpath,outputpath,epsname)
- inputpath = inputpath or "."
- outputpath = outputpath or "."
- local oldname = file.join(inputpath,epsname)
- local newname = file.join(outputpath,file.replacesuffix(epsname,"pdf"))
- local et = lfs.attributes(oldname,"modification")
- local pt = lfs.attributes(newname,"modification")
- if not pt or et > pt then
- dir.mkdirs(outputpath)
- local tmpname = file.replacesuffix(newname,"tmp")
- local command = string.format(gstemplate,gsprogram,tmpname,oldname)
- os.spawn(command)
+-- todo: eps and svg
+
+graphics = graphics or { }
+graphics.converters = graphics.converters or { }
+
+local gsprogram = (os.platform == "windows" and "gswin32c") or "gs"
+local gstemplate = "%s -q -sDEVICE=pdfwrite -dEPSCrop -dNOPAUSE -dNOCACHE -dBATCH -dAutoRotatePages=/None -dProcessColorModel=/DeviceCMYK -sOutputFile=%s %s -c quit"
+
+function graphics.converters.eps(oldname,newname)
+ return gstemplate:format(gsprogram,newname,oldname)
+end
+
+local improgram = "convert"
+local imtemplate = {
+ low = "%s -quality 0 -compress zip %s pdf:%s",
+ medium = "%s -quality 75 -compress zip %s pdf:%s",
+ high = "%s -quality 100 -compress zip %s pdf:%s",
+}
+
+function graphics.converters.jpg(oldname,newname)
+ local ea = environment.arguments
+ local quality = (ea.high and 'high') or (ea.medium and 'medium') or (ea.low and 'low') or 'high'
+ return imtemplate[quality]:format(improgram,oldname,newname)
+end
+
+graphics.converters.tif = graphics.converters.jpg
+graphics.converters.tiff = graphics.converters.jpg
+graphics.converters.png = graphics.converters.jpg
+
+local function convert(kind,oldname,newname)
+ if graphics.converters[kind] then -- extra test
+ local tmpname = file.replacesuffix(newname,"tmp")
+ local command = graphics.converters[kind](oldname,tmpname)
+ logs.simple("command: %s",command)
+ io.flush()
+ os.spawn(command)
+ os.remove(newname)
+ os.rename(tmpname,newname)
+ if lfs.attributes(newname,"size") == 0 then
os.remove(newname)
- os.rename(tmpname,newname)
end
end
+end
- function graphics.converters.convertpath(inputpath,outputpath)
- for name in lfs.dir(inputpath or ".") do
- if name:find("%.$") then
- -- skip . and ..
- elseif name:find("%.eps$") then
- graphics.converters.epstopdf(inputpath,outputpath, name)
- elseif lfs.attributes(inputpath .. "/".. name,"mode") == "directory" then
- graphics.converters.convertpath(inputpath .. "/".. name,outputpath .. "/".. name)
+function graphics.converters.convertpath(inputpath,outputpath)
+ inputpath = inputpath or "."
+ outputpath = outputpath or "."
+ for name in lfs.dir(inputpath) do
+ local suffix = file.extname(name)
+ if name:find("%.$") then
+ -- skip . and ..
+ elseif graphics.converters[suffix] then
+ local oldname = file.join(inputpath,name)
+ local newname = file.join(outputpath,file.replacesuffix(name,"pdf"))
+ local et = lfs.attributes(oldname,"modification")
+ local pt = lfs.attributes(newname,"modification")
+ if not pt or et > pt then
+ dir.mkdirs(outputpath)
+ convert(suffix,oldname,newname)
end
+ elseif lfs.isdir(inputpath .. "/".. name) then
+ graphics.converters.convertpath(inputpath .. "/".. name,outputpath .. "/".. name)
end
end
-
end
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+function graphics.converters.convertfile(oldname)
+ local suffix = file.extname(oldname)
+ if graphics.converters[suffix] then
+ local newname = file.replacesuffix(name,"pdf")
+ if oldname == newname then
+ -- todo: downsample, crop etc
+ elseif environment.argument("force") then
+ convert(suffix,oldname,newname)
+ else
+ local et = lfs.attributes(oldname,"modification")
+ local pt = lfs.attributes(newname,"modification")
+ if not pt or et > pt then
+ convert(suffix,oldname,newname)
+ end
+ end
+ end
+end
scripts = scripts or { }
scripts.convert = scripts.convert or { }
@@ -67,20 +110,28 @@ function scripts.convert.convertall()
end
end
-banner = banner .. " | graphic conversion tools "
+function scripts.convert.convertgiven()
+ for _, name in ipairs(environment.files) do
+ graphics.converters.convertfile(name)
+ end
+end
+
+
+logs.extendbanner("Graphic Conversion Tools 0.10",true)
messages.help = [[
--convertall convert all graphics on path
--inputpath=string original graphics path
--outputpath=string converted graphics path
--watch watch folders
+--force force conversion (even if older)
--delay time between sweeps
]]
-input.verbose = true
-
if environment.argument("convertall") then
scripts.convert.convertall()
+elseif environment.files[1] then
+ scripts.convert.convertgiven()
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua b/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua
index 395e9764ed4..dd319047590 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua
@@ -6,43 +6,106 @@ if not modules then modules = { } end modules ['mtx-fonts'] = {
license = "see context related readme files"
}
-dofile(input.find_file(instance,"font-syn.lua"))
+if not fontloader then fontloader = fontforge end
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+dofile(resolvers.find_file("font-otp.lua","tex"))
+dofile(resolvers.find_file("font-syn.lua","tex"))
+dofile(resolvers.find_file("font-mis.lua","tex"))
scripts = scripts or { }
scripts.fonts = scripts.fonts or { }
-function scripts.fonts.reload()
- fonts.names.load(true)
+function scripts.fonts.reload(verbose)
+ fonts.names.load(true,verbose)
end
-function scripts.fonts.list(pattern,reload,all)
+function scripts.fonts.names(name)
+ name = name or "luatex-fonts-names.lua"
+ fonts.names.identify(true)
+ local data = fonts.names.data
+ if data then
+ data.fallback_mapping = nil
+ logs.report("fontnames","saving names in '%s'",name)
+ io.savedata(name,table.serialize(data,true))
+ elseif lfs.isfile(name) then
+ os.remove(name)
+ end
+end
+
+local function showfeatures(v,n,f,s,t)
+ logs.simple("fontname: %s",v)
+ logs.simple("fullname: %s",n)
+ logs.simple("filename: %s",f)
+ local features = fonts.get_features(f,t)
+ if features then
+ for what, v in table.sortedpairs(features) do
+ local data = features[what]
+ if data and next(data) then
+ logs.simple()
+ logs.simple("%s features:",what)
+ logs.simple()
+ logs.simple("feature script languages")
+ logs.simple()
+ for f,ff in table.sortedpairs(data) do
+ local done = false
+ for s, ss in table.sortedpairs(ff) do
+ if s == "*" then s = "all" end
+ if ss ["*"] then ss["*"] = nil ss.all = true end
+ if done then
+ f = ""
+ else
+ done = true
+ end
+ logs.simple("% -8s % -8s % -8s",f,s,table.concat(table.sortedkeys(ss), " "))
+ end
+ end
+ end
+ end
+ end
+ logs.reportline()
+end
+
+function scripts.fonts.list(pattern,reload,all,info)
if reload then
- logs.report("fontnames","reloading font database")
+ logs.simple("fontnames, reloading font database")
end
+ -- make a function for this
+ pattern = pattern:lower()
+ pattern = pattern:gsub("%-","%%-")
+ pattern = pattern:gsub("%.","%%.")
+ pattern = pattern:gsub("%*",".*")
+ pattern = pattern:gsub("%?",".?")
+ if pattern == "" then
+ pattern = ".*"
+ else
+ pattern = "^" .. pattern .. "$"
+ end
+ --
local t = fonts.names.list(pattern,reload)
if reload then
- logs.report("fontnames","done\n\n")
+ logs.simple("fontnames, done\n\n")
end
if t then
local s, w = table.sortedkeys(t), { 0, 0, 0 }
local function action(f)
- for k,v in pairs(s) do
- if all or v == t[v][2]:lower() then
- local type, name, file, sub = unpack(t[v])
- f(v,name,file,sub)
- end
+ for k,v in ipairs(s) do
+ local type, name, file, sub = unpack(t[v])
+ f(v,name,file,sub,type)
end
end
- action(function(v,n,f,s)
+ action(function(v,n,f,s,t)
if #v > w[1] then w[1] = #v end
if #n > w[2] then w[2] = #n end
if #f > w[3] then w[3] = #f end
end)
- action(function(v,n,f,s)
+ action(function(v,n,f,s,t)
if s then s = "(sub)" else s = "" end
- print(string.format("%s %s %s %s",v:padd(w[1]," "),n:padd(w[2]," "),f:padd(w[3]," "), s))
+ if info then
+ showfeatures(v,n,f,s,t)
+ else
+ local str = string.format("%s %s %s %s",v:padd(w[1]," "),n:padd(w[2]," "),f:padd(w[3]," "), s)
+ print(str:strip())
+ end
end)
end
end
@@ -51,53 +114,62 @@ function scripts.fonts.save(name,sub)
local function save(savename,fontblob)
if fontblob then
savename = savename:lower() .. ".lua"
- logs.report("fontsave","saving data in " .. savename)
- table.tofile(savename,fontforge.to_table(fontblob),"return")
- fontforge.close(fontblob)
+ logs.simple("fontsave, saving data in %s",savename)
+ table.tofile(savename,fontloader.to_table(fontblob),"return")
+ fontloader.close(fontblob)
end
end
if name and name ~= "" then
- local filename = input.find_file(texmf.instance,name) -- maybe also search for opentype
+ local filename = resolvers.find_file(name) -- maybe also search for opentype
if filename and filename ~= "" then
local suffix = file.extname(filename)
- if suffix == 'ttf' or suffix == 'otf' or suffix == 'ttc' then
- local fontinfo = fontforge.info(filename)
+ if suffix == 'ttf' or suffix == 'otf' or suffix == 'ttc' or suffix == "dfont" then
+ local fontinfo = fontloader.info(filename)
if fontinfo then
+ logs.simple("font: %s located as %s",name,filename)
if fontinfo[1] then
for _, v in ipairs(fontinfo) do
- save(v.fontname,fontforge.open(filename,v.fullname))
+ save(v.fontname,fontloader.open(filename,v.fullname))
end
else
- save(fontinfo.fullname,fontforge.open(filename))
+ save(fontinfo.fullname,fontloader.open(filename))
end
end
+ else
+ logs.simple("font: %s not saved",filename)
end
+ else
+ logs.simple("font: %s not found",name)
end
end
end
-banner = banner .. " | font tools "
+logs.extendbanner("Font Tools 0.20",true)
messages.help = [[
--reload generate new font database
---list list installed fonts
+--list [--info] list installed fonts (show info)
--save save open type font in raw table
+--names generate 'luatex-fonts-names.lua' (not for context!)
--pattern=str filter files
--all provide alternatives
]]
if environment.argument("reload") then
- scripts.fonts.reload()
+ scripts.fonts.reload(true)
+elseif environment.argument("names") then
+ scripts.fonts.names()
elseif environment.argument("list") then
local pattern = environment.argument("pattern") or environment.files[1] or ""
local all = environment.argument("all")
+ local info = environment.argument("info")
local reload = environment.argument("reload")
- scripts.fonts.list(pattern,reload,all)
+ scripts.fonts.list(pattern,reload,all,info)
elseif environment.argument("save") then
local name = environment.files[1] or ""
local sub = environment.files[2] or ""
scripts.fonts.save(name,sub)
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-grep.lua b/Master/texmf-dist/scripts/context/lua/mtx-grep.lua
new file mode 100644
index 00000000000..a6617d711f7
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-grep.lua
@@ -0,0 +1,112 @@
+if not modules then modules = { } end modules ['mtx-babel'] = {
+ version = 1.001,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+scripts = scripts or { }
+scripts.grep = scripts.grep or { }
+
+logs.extendbanner("Simple Grepper 0.10",true)
+
+local find, format = string.find, string.format
+
+local cr = lpeg.P("\r")
+local lf = lpeg.P("\n")
+local crlf = cr * lf
+local newline = crlf + cr + lf
+local content = lpeg.C((1-newline)^0) * newline
+
+local write_nl = texio.write_nl
+
+function scripts.grep.find(pattern, files, offset)
+ if pattern and pattern ~= "" then
+ statistics.starttiming(scripts.grep)
+ local nofmatches, noffiles, nofmatchedfiles = 0, 0, 0
+ local n, m, name, check = 0, 0, "", nil
+ local count, nocomment = environment.argument("count"), environment.argument("nocomment")
+ if nocomment then
+ if count then
+ check = function(line)
+ n = n + 1
+ if find(line,"^[%%#]") then
+ -- skip
+ elseif find(line,pattern) then
+ m = m + 1
+ end
+ end
+ else
+ check = function(line)
+ n = n + 1
+ if find(line,"^[%%#]") then
+ -- skip
+ elseif find(line,pattern) then
+ m = m + 1
+ write_nl(format("%s %6i: %s",name,n,line))
+ io.flush()
+ end
+ end
+ end
+ else
+ if count then
+ check = function(line)
+ n = n + 1
+ if find(line,pattern) then
+ m = m + 1
+ end
+ end
+ else
+ check = function(line)
+ n = n + 1
+ if find(line,pattern) then
+ m = m + 1
+ write_nl(format("%s %6i: %s",name,n,line))
+ io.flush()
+ end
+ end
+ end
+ end
+ local capture = (content/check)^0
+ for i=offset or 1, #files do
+ for _, nam in ipairs(dir.glob(files[i])) do
+ name = nam
+ local data = io.loaddata(name)
+ if data then
+ n, m, noffiles = 0, 0, noffiles + 1
+ capture:match(data)
+ if count and m > 0 then
+ nofmatches = nofmatches + m
+ nofmatchedfiles = nofmatchedfiles + 1
+ write_nl(format("%s: %s",name,m))
+ io.flush()
+ end
+ end
+ end
+ end
+ statistics.stoptiming(scripts.grep)
+ if count and nofmatches > 0 then
+ write_nl(format("\nfiles: %s, matches: %s, matched files: %s, runtime: %0.3f seconds",noffiles,nofmatches,nofmatchedfiles,statistics.elapsedtime(scripts.grep)))
+ end
+ end
+end
+
+messages.help = [[
+--pattern search for pattern (optional)
+--count count matches only
+--nocomment skip lines that start with %% or #
+
+patterns are lua patterns and need to be escaped accordingly
+]]
+
+local pattern = environment.argument("pattern")
+local files = environment.files and #environment.files > 0 and environment.files
+
+if pattern and files then
+ scripts.grep.find(pattern, files)
+elseif files then
+ scripts.grep.find(files[1], files, 2)
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-interface.lua b/Master/texmf-dist/scripts/context/lua/mtx-interface.lua
new file mode 100644
index 00000000000..264a2dbe4f4
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-interface.lua
@@ -0,0 +1,266 @@
+if not modules then modules = { } end modules ['mtx-cache'] = {
+ version = 1.001,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format = string.format
+
+scripts = scripts or { }
+scripts.interface = scripts.interface or { }
+
+local flushers = { }
+local userinterfaces = { 'en','cs','de','it','nl','ro','fr','pe' }
+local messageinterfaces = { 'en','cs','de','it','nl','ro','fr','pe','no' }
+
+function flushers.scite(interface,collection)
+ local result, i = {}, 0
+ result[#result+1] = format("keywordclass.macros.context.%s=",interface)
+ for _, command in ipairs(collection) do
+ if i==0 then
+ result[#result+1] = "\\\n"
+ i = 5
+ else
+ i = i - 1
+ end
+ result[#result+1] = format("%s ",command)
+ end
+ io.savedata(format("cont-%s-scite.properties",interface), table.concat(result),"\n")
+ io.savedata(format("cont-%s-scite.lua",interface), table.serialize(collection,true))
+end
+
+function flushers.jedit(interface,collection)
+ local result = {}
+ result[#result+1] = "<?xml version='1.0'?>"
+ result[#result+1] = "<!DOCTYPE MODE SYSTEM 'xmode.dtd'>\n"
+ result[#result+1] = "<MODE>"
+ result[#result+1] = "\t<RULES>"
+ result[#result+1] = "\t\t<KEYWORDS>"
+ for _, command in ipairs(collection) do
+ result[#result+1] = format("\t\t\t<KEYWORD2>%s</KEYWORD2>",command)
+ end
+ result[#result+1] = "\t\t</KEYWORDS>"
+ result[#result+1] = "\t</RULES>"
+ result[#result+1] = "</MODE>"
+ io.savedata(format("context-jedit-%s.xml",interface), table.concat(result),"\n")
+end
+
+function flushers.bbedit(interface,collection)
+ local result = {}
+ result[#result+1] = "<?xml version='1.0'?>"
+ result[#result+1] = "<key>BBLMKeywordList</key>"
+ result[#result+1] = "<array>"
+ for _, command in ipairs(collection) do
+ result[#result+1] = format("\t<string>\\%s</string>",command)
+ end
+ result[#result+1] = "</array>"
+ io.savedata(format("context-bbedit-%s.xml",interface), table.concat(result),"\n")
+end
+
+function flushers.raw(interface,collection)
+ for _, command in ipairs(collection) do
+ logs.simple(command)
+ end
+end
+
+function scripts.interface.editor(editor)
+ local interfaces= environment.files
+ if #interfaces == 0 then
+ interfaces= userinterfaces
+ end
+ local xmlfile = resolvers.find_file("cont-en.xml") or ""
+ if xmlfile == "" then
+ logs.simple("unable to locate cont-en.xml")
+ end
+ for _, interface in ipairs(interfaces) do
+ local keyfile = resolvers.find_file(format("keys-%s.xml",interface)) or ""
+ if keyfile == "" then
+ logs.simple("unable to locate keys-*.xml")
+ else
+ local collection = { }
+ local mappings = { }
+ local x = xml.load(keyfile)
+ for e, d, k in xml.elements(x,"cd:command") do
+ local at = d[k].at
+ local name, value = at.name, at.value
+ if name and value then
+ mappings[name] = value
+ end
+ end
+ local x = xml.load(xmlfile)
+ for e, d, k in xml.elements(x,"cd:command") do
+ local at = d[k].at
+ local name, type = at.name, at["type"]
+ if name and name ~= "" then
+ local remapped = mappings[name] or name
+ if type == "environment" then
+ collection[#collection+1] = "start" .. remapped
+ collection[#collection+1] = "stop" .. remapped
+ else
+ collection[#collection+1] = remapped
+ end
+ end
+ end
+ if #collection > 0 then
+ table.sort(collection)
+ flushers[editor](interface,collection)
+ end
+ end
+ end
+end
+
+function scripts.interface.check()
+ local xmlfile = resolvers.find_file("cont-en.xml") or ""
+ if xmlfile ~= "" then
+ local f = io.open("cont-en-check.tex","w")
+ if f then
+ f:write("\\starttext\n")
+ local x = xml.load(xmlfile)
+ for e, d, k in xml.elements(x,"cd:command") do
+ local dk = d[k]
+ local at = dk.at
+ if at then
+ local name = xml.filter(dk,"cd:sequence/cd:string/attribute(value)")
+ if name and name ~= "" then
+ if at.type == "environment" then
+ name = "start" .. name
+ end
+ f:write(format("\\doifundefined{%s}{\\writestatus{check}{command '%s' is undefined}}\n",name,name))
+ end
+ end
+ end
+ f:write("\\stoptext\n")
+ f:close()
+ end
+ end
+end
+
+function scripts.interface.context()
+ local filename = resolvers.find_file("mult-def.lua") or ""
+ if filename ~= "" then
+ local interface = dofile(filename)
+ if interface and next(interface) then
+ local variables, constants, commands, elements = interface.variables, interface.constants, interface.commands, interface.elements
+ local filename = resolvers.find_file("cont-en.xml") or ""
+ local xmldata = filename ~= "" and (io.loaddata(filename) or "")
+ local function flush(texresult,xmlresult,language,what,tag)
+ local t = interface[what]
+ texresult[#texresult+1] = format("%% definitions for interface %s for language %s\n%%",what,language)
+ xmlresult[#xmlresult+1] = format("\t<!-- definitions for interface %s for language %s -->\n",what,language)
+ xmlresult[#xmlresult+1] = format("\t<cd:%s>",what)
+ for _, key in ipairs(table.sortedkeys(t)) do
+ local v = t[key]
+ local value = v[language] or v["en"]
+ if not value then
+ logs.simple(format("warning, no value for key '%s' for language '%s'",key,language))
+ else
+ local value = t[key][language] or t[key].en
+ texresult[#texresult+1] = format("\\setinterface%s{%s}{%s}",tag,key,value)
+ xmlresult[#xmlresult+1] = format("\t\t<cd:%s name='%s' value='%s'/>",tag,key,value)
+ end
+ end
+ xmlresult[#xmlresult+1] = format("\t</cd:%s>\n",tag)
+ end
+ local function replace(str, element, attribute, category, othercategory, language)
+ return str:gsub(format("(<%s[^>]-%s=)([\"\'])([^\"\']-)([\"\'])",element,attribute), function(a,b,c)
+ local cc = category[c]
+ if not cc and othercategory then
+ cc = othercategory[c]
+ end
+ if cc then
+ ccl = cc[language]
+ if ccl then
+ return a .. b .. ccl .. b
+ end
+ end
+ return a .. b .. c .. b
+ end)
+ end
+ for language, _ in pairs(commands.setuplayout) do
+ local texresult, xmlresult = { }, { }
+ texresult[#texresult+1] = format("%% this file is auto-generated, don't edit this file\n%%")
+ xmlresult[#xmlresult+1] = format("<?xml version='1.0'?>\n",tag)
+ xmlresult[#xmlresult+1] = format("<cd:interface xmlns:cd='http://www.pragma-ade.com/commands' name='context' language='%s' version='2008.10.21 19:42'>\n",language)
+ flush(texresult,xmlresult,language,"variables","variable")
+ flush(texresult,xmlresult,language,"constants","constant")
+ flush(texresult,xmlresult,language,"elements", "element")
+ flush(texresult,xmlresult,language,"commands", "command")
+ texresult[#texresult+1] = format("%%\n\\endinput")
+ xmlresult[#xmlresult+1] = format("</cd:interface>")
+ local texfilename = format("mult-%s.tex",language)
+ local xmlfilename = format("keys-%s.xml",language)
+ io.savedata(texfilename,table.concat(texresult,"\n"))
+ logs.simple(format("saving interface definitions '%s'",texfilename))
+ io.savedata(xmlfilename,table.concat(xmlresult,"\n"))
+ logs.simple(format("saving interface translations '%s'",xmlfilename))
+ if language ~= "en" and xmldata ~= "" then
+ local newdata = xmldata:gsub("(<cd:interface.*language=.)en(.)","%1"..language.."%2",1)
+ newdata = replace(newdata, 'cd:string', 'value', interface.commands, interface.elements, language)
+ newdata = replace(newdata, 'cd:variable' , 'value', interface.variables, nil, language)
+ newdata = replace(newdata, 'cd:parameter', 'name', interface.constants, nil, language)
+ newdata = replace(newdata, 'cd:constant', 'type', interface.variables, nil, language)
+ newdata = replace(newdata, 'cd:variable', 'type', interface.variables, nil, language)
+ newdata = replace(newdata, 'cd:inherit', 'name', interface.commands, interface.elements, language)
+ local xmlfilename = format("cont-%s.xml",language)
+ io.savedata(xmlfilename,newdata)
+ logs.simple(format("saving interface specification '%s'",xmlfilename))
+ end
+ end
+ end
+ end
+end
+
+function scripts.interface.messages()
+ local filename = resolvers.find_file("mult-mes.lua") or ""
+ if filename ~= "" then
+ local messages = dofile(filename)
+ for _, interface in ipairs(messageinterfaces) do
+ local texresult = { }
+ for category, data in next, messages do
+ for tag, message in next, data do
+ if tag ~= "files" then
+ local msg = message[interface] or message["all"] or message["en"]
+ if msg then
+ texresult[#texresult+1] = format("\\setinterfacemessage{%s}{%s}{%s}",category,tag,msg)
+ end
+ end
+ end
+ end
+ texresult[#texresult+1] = format("%%\n\\endinput")
+ io.savedata(format("mult-m%s.tex",interface),table.concat(texresult,"\n"))
+ end
+ end
+end
+
+logs.extendbanner("Interface Tools 0.11",true)
+
+messages.help = [[
+--scite generate scite interface
+--bbedit generate scite interface
+--jedit generate scite interface
+--check generate check file
+--context generate context definition files
+--messages generate context message files
+]]
+
+if environment.argument("context") then
+ scripts.interface.context()
+elseif environment.argument("messages") then
+ scripts.interface.messages()
+elseif environment.argument("scite") or environment.argument("bbedit") or environment.argument("jedit") then
+ if environment.argument("scite") then
+ scripts.interface.editor("scite")
+ end
+ if environment.argument("bbedit") then
+ scripts.interface.editor("bbedit")
+ end
+ if environment.argument("jedit") then
+ scripts.interface.editor("jedit")
+ end
+elseif environment.argument("check") then
+ scripts.interface.check()
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-metatex.lua b/Master/texmf-dist/scripts/context/lua/mtx-metatex.lua
new file mode 100644
index 00000000000..f8c871a7b1c
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-metatex.lua
@@ -0,0 +1,69 @@
+if not modules then modules = { } end modules ['mtx-metatex'] = {
+ version = 1.001,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- future versions will deal with specific variants of metatex
+
+scripts = scripts or { }
+scripts.metatex = scripts.metatex or { }
+
+-- metatex
+
+function scripts.metatex.make()
+ local command = "luatools --make --compile metatex"
+ logs.simple("running command: %s",command)
+ os.spawn(command)
+end
+
+--~ function scripts.metatex.run()
+--~ local name = environment.files[1] or ""
+--~ if name ~= "" then
+--~ local command = "luatools --fmt=metatex " .. name
+--~ logs.simple("running command: %s",command)
+--~ os.spawn(command)
+--~ end
+--~ end
+
+function scripts.metatex.run(ctxdata,filename)
+ local filename = environment.files[1] or ""
+ if filename ~= "" then
+ local formatfile, scriptfile = resolvers.locate_format("metatex")
+ if formatfile and scriptfile then
+ local command = string.format("luatex --fmt=%s --lua=%s %s",
+ string.quote(formatfile), string.quote(scriptfile), string.quote(filename))
+ logs.simple("running command: %s",command)
+ os.spawn(command)
+ elseif formatname then
+ logs.simple("error, no format found with name: %s",formatname)
+ else
+ logs.simple("error, no format found (provide formatname or interface)")
+ end
+ end
+end
+
+function scripts.metatex.timed(action)
+ statistics.timed(action)
+end
+
+logs.extendbanner("MetaTeX Tools 0.10",true)
+
+messages.help = [[
+--run process (one or more) files (default action)
+--make create metatex format(s)
+]]
+
+if environment.argument("run") then
+ scripts.metatex.timed(scripts.metatex.run)
+elseif environment.argument("make") then
+ scripts.metatex.timed(scripts.metatex.make)
+elseif environment.argument("help") then
+ logs.help(messages.help,false)
+elseif environment.files[1] then
+ scripts.metatex.timed(scripts.metatex.run)
+else
+ logs.help(messages.help,false)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-mptopdf.lua b/Master/texmf-dist/scripts/context/lua/mtx-mptopdf.lua
new file mode 100644
index 00000000000..4243625adb9
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-mptopdf.lua
@@ -0,0 +1,136 @@
+if not modules then modules = { } end modules ['mtx-mptopdf'] = {
+ version = 1.303,
+ comment = "companion to mtxrun.lua",
+ author = "Taco Hoekwater, Elvenkind BV, Dordrecht NL",
+ copyright = "Elvenkind BV / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+scripts = scripts or { }
+scripts.mptopdf = scripts.mptopdf or { }
+scripts.mptopdf.aux = scripts.mptopdf.aux or { }
+
+do
+ -- setup functions and variables here
+
+ local dosish, miktex, escapeshell = false, false, false
+
+ if os.platform == 'windows' then
+ dosish = true
+ if environment.TEXSYSTEM and environment.TEXSYSTEM:find("miktex") then
+ miktex = true
+ end
+ end
+ if environment.SHELL and environment.SHELL:find("sh") then
+ escapeshell = true
+ end
+
+ function scripts.mptopdf.aux.find_latex(fname)
+ local d = io.loaddata(fname) or ""
+ return d:find("\\documentstyle") or d:find("\\documentclass") or d:find("\\begin{document}")
+ end
+
+ function scripts.mptopdf.aux.do_convert (fname)
+ local command, done, pdfdest = "", 0, ""
+ if fname:find(".%d+$") or fname:find("%.mps$") then
+ if miktex then
+ command = "pdftex -undump=mptopdf"
+ else
+ command = "pdftex -fmt=mptopdf -progname=context"
+ end
+ if dosish then
+ command = string.format('%s \\relax "%s"',command,fname)
+ else
+ command = string.format('%s \\\\relax "%s"',command,fname)
+ end
+ os.execute(command)
+ local name, suffix = file.nameonly(fname), file.extname(fname)
+ local pdfsrc = name .. ".pdf"
+ if lfs.isfile(pdfsrc) then
+ pdfdest = name .. "-" .. suffix .. ".pdf"
+ os.rename(pdfsrc, pdfdest)
+ if lfs.isfile(pdfsrc) then -- rename failed
+ file.copy(pdfsrc, pdfdest)
+ end
+ done = 1
+ end
+ end
+ return done, pdfdest
+ end
+
+ function scripts.mptopdf.aux.make_mps(fn,latex,rawmp,metafun)
+ local rest, mpbin = latex and " --tex=latex " or " ", ""
+ if rawmp then
+ if metafun then
+ mpbin = "mpost --progname=mpost --mem=metafun"
+ else
+ mpbin = "mpost --mem=mpost"
+ end
+ else
+ if latex then
+ mpbin = "mpost --mem=mpost"
+ else
+ mpbin = "texexec --mptex"
+ end
+ end
+ local runner = mpbin .. rest .. fn
+ logs.simple("running: %s\n", runner)
+ return (os.execute(runner))
+ end
+
+end
+
+function scripts.mptopdf.convertall()
+ local rawmp = environment.arguments.rawmp or false
+ local metafun = environment.arguments.metafun or false
+ local latex = environment.arguments.latex or false
+ local files = dir.glob(environment.files)
+ if #files > 0 then
+ local fn = files[1]
+ if #files == 1 and fn:find("%.mp$") then
+ latex = scripts.mptopdf.aux.find_latex(fn) or latex
+ end
+ if scripts.mptopdf.aux.make_mps(fn,latex,rawmp,metafun) then
+ files = dir.glob(file.nameonly(fn) .. ".*") -- reset
+ else
+ logs.simple("error while processing mp file '%s'", fn)
+ exit(1)
+ end
+ local report = { }
+ for _,fn in ipairs(files) do
+ local success, name = scripts.mptopdf.aux.do_convert(fn)
+ if success > 0 then
+ report[#report+1] = { fn, name }
+ end
+ end
+ if #report > 0 then
+ logs.simple("number of converted files: %i", #report)
+ logs.simple("")
+ for _, r in ipairs(report) do
+ logs.simple("%s => %s", r[1], r[2])
+ end
+ else
+ logs.simple("no input files match %s", table.concat(files,' '))
+ end
+ else
+ logs.simple("no files match %s", table.concat(environment.files,' '))
+ end
+end
+
+logs.extendbanner("MetaPost to PDF Converter 0.51",true)
+
+messages.help = [[
+--rawmp raw metapost run
+--metafun use metafun instead of plain
+--latex force --tex=latex
+]]
+
+if environment.files[1] then
+ scripts.mptopdf.convertall()
+else
+ if not environment.arguments.help then
+ logs.simple("provide MP output file (or pattern)")
+ logs.simple("")
+ end
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-package.lua b/Master/texmf-dist/scripts/context/lua/mtx-package.lua
new file mode 100644
index 00000000000..06c89907adf
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-package.lua
@@ -0,0 +1,68 @@
+if not modules then modules = { } end modules ['mtx-package'] = {
+ version = 1.002,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format, gsub, gmatch = string.format, string.gsub, string.gmatch
+
+scripts = scripts or { }
+messages = messages or { }
+scripts.package = scripts.package or { }
+
+function scripts.package.merge_luatex_files(name,strip)
+ local oldname = resolvers.find_file(name) or ""
+ oldname = file.replacesuffix(oldname,"lua")
+ if oldname == "" then
+ logs.simple("missing '%s'",name)
+ else
+ local newname = file.removesuffix(oldname) .. "-merged.lua"
+ local data = io.loaddata(oldname) or ""
+ if data == "" then
+ logs.simple("missing '%s'",newname)
+ else
+ logs.simple("loading '%s'",oldname)
+ local collected = { }
+ collected[#collected+1] = format("-- merged file : %s\n",newname)
+ collected[#collected+1] = format("-- parent file : %s\n",oldname)
+ collected[#collected+1] = format("-- merge date : %s\n",os.date())
+ -- loadmodule can have extra arguments
+ for lib in gmatch(data,"loadmodule *%([\'\"](.-)[\'\"]") do
+ if file.basename(lib) ~= file.basename(newname) then
+ local fullname = resolvers.find_file(lib) or ""
+ if fullname == "" then
+ logs.simple("missing '%s'",lib)
+ else
+ logs.simple("fetching '%s'",fullname)
+ local data = io.loaddata(fullname)
+ if strip then
+ data = gsub(data,"%-%-%[%[ldx%-%-.-%-%-%ldx%]%]%-%-[\n\r]*","")
+ data = gsub(data,"%-%-%~[^\n\r]*[\n\r]*","\n")
+ data = gsub(data,"%s+%-%-[^\n\r]*[\n\r]*","\n")
+ data = gsub(data,"[\n\r]+","\n")
+ end
+ collected[#collected+1] = "\ndo -- begin closure to overcome local limits and interference\n\n"
+ collected[#collected+1] = data
+ collected[#collected+1] = "\nend -- closure\n"
+ end
+ end
+ end
+ logs.simple("saving '%s'",newname)
+ io.savedata(newname,table.concat(collected))
+ end
+ end
+end
+
+logs.extendbanner("Package Tools 0.1",true)
+
+messages.help = [[
+--merge merge 'loadmodule' into merge file
+]]
+
+if environment.argument("merge") then
+ scripts.package.merge_luatex_files(environment.files[1] or "")
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua b/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua
new file mode 100644
index 00000000000..7f130465bdf
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua
@@ -0,0 +1,362 @@
+if not modules then modules = { } end modules ['mtx-patterns'] = {
+ version = 1.001,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format = string.format
+
+scripts = scripts or { }
+scripts.patterns = scripts.patterns or { }
+
+scripts.patterns.list = {
+ { "??", "hyph-ar.tex", "arabic" },
+ { "bg", "hyph-bg.tex", "bulgarian" },
+-- { "ca", "hyph-ca.tex", "" },
+ { "??", "hyph-cop.tex", "coptic" },
+ { "cs", "hyph-cs.tex", "czech" },
+ { "??", "hyph-cy.tex", "welsh" },
+ { "da", "hyph-da.tex", "danish" },
+ { "deo", "hyph-de-1901.tex", "german, old spelling" },
+ { "de", "hyph-de-1996.tex", "german, new spelling" },
+--~ { "??", "hyph-el-monoton.tex", "" },
+--~ { "??", "hyph-el-polyton.tex", "" },
+--~ { "agr", "hyph-grc", "ancient greek" },
+--~ { "???", "hyph-x-ibycus", "ancient greek in ibycus encoding" },
+--~ { "gr", "", "" },
+ { "??", "hyph-eo.tex", "esperanto" },
+ { "gb", "hyph-en-gb.tex", "british english" },
+ { "us", "hyph-en-us.tex", "american english" },
+ { "es", "hyph-es.tex", "spanish" },
+ { "et", "hyph-et.tex", "estonian" },
+ { "eu", "hyph-eu.tex", "basque" }, -- ba is Bashkir!
+ { "??", "hyph-fa.tex", "farsi" },
+ { "fi", "hyph-fi.tex", "finnish" },
+ { "fr", "hyph-fr.tex", "french" },
+-- { "??", "hyph-ga.tex", "" },
+-- { "??", "hyph-gl.tex", "" },
+-- { "??", "hyph-grc.tex", "" },
+ { "hr", "hyph-hr.tex", "croatian" },
+ { "??", "hyph-hsb.tex", "upper sorbian" },
+ { "hu", "hyph-hu.tex", "hungarian" },
+ { "??", "hyph-ia.tex", "interlingua" },
+ { "??", "hyph-id.tex", "indonesian" },
+ { "??", "hyph-is.tex", "icelandic" },
+ { "it", "hyph-it.tex", "italian" },
+ { "la", "hyph-la.tex", "latin" },
+ { "??", "hyph-mn-cyrl.tex", "mongolian, cyrillic script" },
+ { "??", "hyph-mn-cyrl-x-new.tex", "mongolian, cyrillic script (new patterns)" },
+ { "nb", "hyph-nb.tex", "norwegian bokmål" },
+ { "nl", "hyph-nl.tex", "dutch" },
+ { "nn", "hyph-nn.tex", "norwegian nynorsk" },
+ { "pl", "hyph-pl.tex", "polish" },
+ { "pt", "hyph-pt.tex", "portuguese" },
+ { "ro", "hyph-ro.tex", "romanian" },
+ { "ru", "hyph-ru.tex", "russian" },
+ { "sk", "hyph-sk.tex", "" },
+ { "sl", "hyph-sl.tex", "slovenian" },
+ { "??", "hyph-sr-cyrl.tex", "serbian" },
+ { "sv", "hyph-sv.tex", "swedish" },
+ { "tr", "hyph-tr.tex", "turkish" },
+ { "uk", "hyph-uk.tex", "ukrainian" },
+ { "??", "hyph-zh-latn.tex", "zh-latn, chinese Pinyin" },
+}
+
+
+-- stripped down from lpeg example:
+
+local utf = unicode.utf8
+
+local cont = lpeg.R("\128\191") -- continuation byte
+
+local utf8 = lpeg.R("\0\127")
+ + lpeg.R("\194\223") * cont
+ + lpeg.R("\224\239") * cont * cont
+ + lpeg.R("\240\244") * cont * cont * cont
+
+local validutf = (utf8^0/function() return true end) * (lpeg.P(-1)/function() return false end)
+
+function utf.check(str)
+ return lpeg.match(validutf,str)
+end
+
+local permitted_commands = table.tohash {
+ "message",
+ "endinput"
+}
+
+local permitted_characters = table.tohash {
+ 0x0009, -- tab
+ 0x0027, -- apostrofe
+ 0x002D, -- hyphen
+ 0x200C, --
+}
+
+function scripts.patterns.load(path,name,mnemonic,fullcheck)
+ local fullname = file.join(path,name)
+ local data = io.loaddata(fullname) or ""
+ local byte, char = utf.byte, utf.char
+ if data ~= "" then
+ data = data:gsub("([\n\r])\\input ([^ \n\r]+)", function(previous,subname)
+ local subname = file.addsuffix(subname,"tex")
+ local subfull = file.join(file.dirname(fullname),subname)
+ local subdata = io.loaddata(subfull) or ""
+ if subdata == "" then
+ if mnemonic then
+ logs.simple("no subfile %s for language %s",subname,mnemonic)
+ else
+ logs.simple("no subfile %s",name)
+ end
+ end
+ return previous .. subdata
+ end)
+ local comment = data:match("^(.-)[\n\r]\\patterns") or ""
+ local n, okay = 0, true
+ local cd = characters.data
+ for line in data:gmatch("[^ \n\r]+") do
+ local ok = utf.check(line)
+ n = n + 1
+ if not ok then
+ okay = false
+ line = line:gsub("%%","%%%%")
+ if fullcheck then
+ if mnemonic then
+ logs.simple("invalid utf in language %s, file %s, line %s: %s",mnemonic,name,n,line)
+ else
+ logs.simple("invalid utf in file %s, line %s: %s",name,n,line)
+ end
+ else
+ if mnemonic then
+ logs.simple("file %s for %s contains invalid utf",name,mnemonic)
+ else
+ logs.simple("file %s contains invalid utf",name)
+ end
+ break
+ end
+ end
+ end
+ local c, h = { }, { }
+ for line in data:gmatch("[^\n\r]+") do
+ local txt, cmt = line:match("^(.-)%%(.*)$")
+ if not txt then
+ txt, cmt = line, ""
+ end
+ for s in txt:gmatch("\\([a-zA-Z]+)") do
+ h[s] = (h[s] or 0) + 1
+ end
+ for s in cmt:gmatch("\\([a-zA-Z]+)") do
+ c[s] = (c[s] or 0) + 1
+ end
+ end
+ h.patterns = nil
+ h.hyphenation = nil
+ for k, v in pairs(h) do
+ if not permitted_commands[k] then okay = false end
+ if mnemonic then
+ logs.simple("command \\%s found in language %s, file %s, n=%s",k,mnemonic,name,v)
+ else
+ logs.simple("command \\%s found in file %s, n=%s",k,name,v)
+ end
+ end
+ if not environment.argument("fast") then
+ for k, v in pairs(c) do
+ if mnemonic then
+ logs.simple("command \\%s found in comment of language %s, file %s, n=%s",k,mnemonic,name,v)
+ else
+ logs.simple("command \\%s found in comment of file %s, n=%s",k,name,v)
+ end
+ end
+ end
+ data = data:gsub("%%.-[\n\r]","")
+ data = data:gsub(" *[\n\r]+","\n")
+ local patterns = data:match("\\patterns[%s]*{[%s]*(.-)[%s]*}") or ""
+ local hyphenations = data:match("\\hyphenation[%s]*{[%s]*(.-)[%s]*}") or ""
+ patterns = patterns:gsub(" +","\n")
+ hyphenations = hyphenations:gsub(" +","\n")
+ local p, h = { }, { }
+ local pats, hyps = { } , { }
+ local pused, hused = { } , { }
+ local period = byte(".")
+ for line in patterns:gmatch("[^ \n\r]+") do
+ local ok = true
+ for b in line:utfvalues() do
+ if b == period then
+ -- ok
+ else
+ local ct = cd[b].category
+ if ct == "lu" or ct == "ll" then
+ pused[char(b)] = true
+ elseif ct == "nd" then
+ -- ok
+ else
+ p[b] = (p[b] or 0) + 1
+ ok = false
+ end
+ end
+ end
+ if ok then
+ pats[#pats+1] = line
+ end
+ end
+ local hyphen = byte("-")
+ for line in hyphenations:gmatch("[^ \n\r]+") do
+ local ok = true
+ for b in line:utfvalues() do
+ if b == hyphen then
+ -- ok
+ else
+ local ct = cd[b].category
+ if ct == "lu" or ct == "ll" then
+ hused[char(b)] = true
+ else
+ h[b] = (h[b] or 0) + 1
+ ok = false
+ end
+ end
+ end
+ if ok then
+ hyps[#hyps+1] = line
+ end
+ end
+ local stripped = { }
+ for k, v in pairs(p) do
+ if mnemonic then
+ logs.simple("invalid character %s (0x%04X) in patterns of language %s, file %s, n=%s",char(k),k,mnemonic,name,v)
+ else
+ logs.simple("invalid character %s (0x%04X) in patterns of file %s, n=%s",char(k),k,name,v)
+ end
+ if not permitted_characters[k] then
+ okay = false
+ else
+ stripped[k] = true
+ end
+ end
+ for k, v in pairs(h) do
+ if mnemonic then
+ logs.simple("invalid character %s (0x%04X) in exceptions of language %s, file %s, n=%s",char(k),k,mnemonic,name,v)
+ else
+ logs.simple("invalid character %s (0x%04X) in exceptions of file %s, n=%s",char(k),k,name,v)
+ end
+ if not permitted_characters[k] then
+ okay = false
+ else
+ stripped[k] = true
+ end
+ end
+ local stripset = ""
+ for k, v in pairs(stripped) do
+ logs.simple("entries that contain character %s will be omitted",char(k))
+ stripset = stripset .. "%" .. char(k)
+ end
+ return okay, pats, hyps, comment, stripset, pused, hused
+ else
+ if mnemonic then
+ logs.simple("no file %s for language %s",fullname,mnemonic)
+ else
+ logs.simple("no file %s",fullname)
+ end
+ return false, { }, { }, "", "", { }, { }
+ end
+end
+
+function scripts.patterns.save(destination,mnemonic,patterns,hyphenations,comment,stripped,pused,hused)
+ local nofpatterns = #patterns
+ local nofhyphenations = #hyphenations
+ local pu = table.concat(table.sortedkeys(pused), " ")
+ local hu = table.concat(table.sortedkeys(hused), " ")
+ logs.simple("language %s has %s patterns and %s exceptions",mnemonic,nofpatterns,nofhyphenations)
+ if mnemonic ~= "??" then
+ local rmefile = file.join(destination,"lang-"..mnemonic..".rme")
+ local patfile = file.join(destination,"lang-"..mnemonic..".pat")
+ local hypfile = file.join(destination,"lang-"..mnemonic..".hyp")
+ local topline = "% generated by mtxrun --script pattern --convert"
+ local banner = "% for comment and copyright, see " .. rmefile
+ logs.simple("saving language data for %s",mnemonic)
+ if not comment or comment == "" then comment = "% no comment" end
+ if not type(destination) == "string" then destination = "." end
+ os.remove(rmefile)
+ os.remove(patfile)
+ os.remove(hypfile)
+ io.savedata(rmefile,format("%s\n\n%s",topline,comment))
+ io.savedata(patfile,format("%s\n\n%s\n\n%% used: %s\n\n\\patterns{\n%s}",topline,banner,pu,table.concat(patterns,"\n")))
+ io.savedata(hypfile,format("%s\n\n%s\n\n%% used: %s\n\n\\hyphenation{\n%s}",topline,banner,hu,table.concat(hyphenations,"\n")))
+ end
+end
+
+function scripts.patterns.prepare()
+ dofile(resolvers.find_file("char-def.lua"))
+end
+
+function scripts.patterns.check()
+ local path = environment.argument("path") or "."
+ local found = false
+ if #environment.files > 0 then
+ for _, name in ipairs(environment.files) do
+ logs.simple("checking language file %s", name)
+ local okay = scripts.patterns.load(path,name,nil,not environment.argument("fast"))
+ if #environment.files > 1 then
+ logs.simple("")
+ end
+ end
+ else
+ for k, v in pairs(scripts.patterns.list) do
+ local mnemonic, name = v[1], v[2]
+ logs.simple("checking language %s, file %s", mnemonic, name)
+ local okay = scripts.patterns.load(path,name,mnemonic,not environment.argument("fast"))
+ if not okay then
+ logs.simple("there are errors that need to be fixed")
+ end
+ logs.simple("")
+ end
+ end
+end
+
+function scripts.patterns.convert()
+ local path = environment.argument("path") or "."
+ if path == "" then
+ logs.simple("provide sourcepath using --path ")
+ else
+ local destination = environment.argument("destination") or "."
+ if path == destination then
+ logs.simple("source path and destination path should differ (use --path and/or --destination)")
+ else
+ for k, v in pairs(scripts.patterns.list) do
+ local mnemonic, name = v[1], v[2]
+ logs.simple("converting language %s, file %s", mnemonic, name)
+ local okay, patterns, hyphenations, comment, stripped, pused, hused = scripts.patterns.load(path,name,false)
+ if okay then
+ scripts.patterns.save(destination,mnemonic,patterns,hyphenations,comment,stripped,pused,hused)
+ else
+ logs.simple("convertion aborted due to error(s)")
+ end
+ logs.simple("")
+ end
+ end
+ end
+end
+
+logs.extendbanner("Pattern Tools 0.20",true)
+
+messages.help = [[
+--convert generate context language files (mnemonic driven, if not given then all)
+--check check pattern file (or those used by context when no file given)
+
+--fast only report filenames, no lines
+]]
+
+if environment.argument("check") then
+ scripts.patterns.prepare()
+ scripts.patterns.check()
+elseif environment.argument("convert") then
+ scripts.patterns.prepare()
+ scripts.patterns.convert()
+else
+ logs.help(messages.help)
+end
+
+-- mtxrun --script pattern --check hyph-*.tex
+-- mtxrun --script pattern --check --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns
+-- mtxrun --script pattern --check --fast --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns
+-- mtxrun --script pattern --convert --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns --destination=e:/tmp/patterns
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-profile.lua b/Master/texmf-dist/scripts/context/lua/mtx-profile.lua
new file mode 100644
index 00000000000..d99f7e926bc
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-profile.lua
@@ -0,0 +1,164 @@
+if not modules then modules = { } end modules ['mtx-profile'] = {
+ version = 1.000,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- todo: also line number
+-- todo: sort runtime as option
+
+local match, format, find = string.match, string.format, string.find
+
+scripts = scripts or { }
+scripts.profiler = scripts.profiler or { }
+
+local timethreshold = 0
+local callthreshold = 2500
+local countthreshold = 2500
+
+local functiontemplate = "%12s %03.4f %9i %s"
+local calltemplate = "%9i %s"
+local totaltemplate = "%i internal calls, %i function calls taking %3.4f seconds"
+local thresholdtemplate = "thresholds: %i internal calls, %i function calls, %i seconds"
+
+function scripts.profiler.analyse(filename)
+ local f = io.open(filename)
+ if f then
+ local times, counts, calls = { }, { }, { }
+ local totalruntime, totalcount, totalcalls = 0, 0, 0
+ while true do
+ local line = f:read()
+ if line then
+ local stacklevel, filename, functionname, linenumber, currentline, localtime, totaltime = line:match("^(%d+)\t(.-)\t(.-)\t(.-)\t(.-)\t(.-)\t(.-)")
+ if not filename then
+ -- next
+ elseif filename == "=[C]" then
+ if not functionname:find("^%(") then
+ calls[functionname] = (calls[functionname] or 0) + 1
+ end
+ else
+ local filename = filename:match("^@(.*)$")
+ if filename then
+ local fi = times[filename]
+ if not fi then fi = { } times[filename] = fi end
+ fi[functionname] = (fi[functionname] or 0) + tonumber(localtime)
+ counts[functionname] = (counts[functionname] or 0) + 1
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ print("")
+ local loaded = { }
+ for _, filename in ipairs(table.sortedkeys(times)) do
+ local functions = times[filename]
+ for _, functionname in ipairs(table.sortedkeys(functions)) do
+ local totaltime = functions[functionname]
+ local count = counts[functionname]
+ totalcount = totalcount + count
+ if totaltime > timethreshold or count > countthreshold then
+ totalruntime = totalruntime + totaltime
+ local functionfile, somenumber = functionname:match("^@(.+):(.-)$")
+ if functionfile then
+ local number = tonumber(somenumber)
+ if number then
+ if not loaded[functionfile] then
+ loaded[functionfile] = string.splitlines(io.loaddata(functionfile) or "")
+ end
+ functionname = loaded[functionfile][number] or functionname
+ functionname = functionname:gsub("^%s*","")
+ functionname = functionname:gsub("%s*%-%-.*$","")
+ functionname = number .. ": " .. functionname
+ end
+ end
+ filename = file.basename(filename)
+ print(functiontemplate:format(filename,totaltime,count,functionname))
+ end
+ end
+ end
+ print("")
+ for _, call in ipairs(table.sortedkeys(calls)) do
+ local n = calls[call]
+ totalcalls = totalcalls + n
+ if n > callthreshold then
+ print(calltemplate:format(n,call))
+ end
+ end
+ print("")
+ print(totaltemplate:format(totalcalls,totalcount,totalruntime))
+ print("")
+ print(thresholdtemplate:format(callthreshold,countthreshold,timethreshold))
+ end
+end
+
+function scripts.profiler.analyse(filename)
+ local f = io.open(filename)
+ local calls = { }
+ local lines = 0
+ if f then
+ while true do
+ local line = f:read()
+ if line then
+ lines = lines + 1
+ local c = match(line,"\\([a-zA-Z%!%?@]+) *%->")
+ if c then
+ local cc = calls[c]
+ if not cc then
+ calls[c] = 1
+ else
+ calls[c] = cc + 1
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ local noc = 0
+local criterium = 100
+ for name, n in next, calls do
+ if n > criterium then
+ if find(name,"^@@[a-z][a-z]") then
+ -- parameter
+ elseif find(name,"^[cvserft]%!") then
+ -- variables and constants
+ elseif find(name,"^%?%?[a-z][a-z]$") then
+ -- prefix
+ elseif find(name,"^%!%!") then
+ -- reserved
+ elseif find(name,"^@.+@$") then
+ -- weird
+ else
+ noc = noc + n
+ print(format("%6i: %s",n,name))
+ end
+ end
+ end
+ print("")
+ print(format("number of lines: %s",lines))
+ print(format("number of calls: %s",noc))
+ print(format("criterium calls: %s",criterium))
+ end
+end
+
+--~ scripts.profiler.analyse("t:/manuals/mk/mk-fonts-profile.lua")
+--~ scripts.profiler.analyse("t:/manuals/mk/mk-introduction-profile.lua")
+
+logs.extendbanner("LuaTeX Profiler 1.00",true)
+
+messages.help = [[
+--analyse analyse lua calls
+--trace analyse tex calls
+]]
+
+if environment.argument("analyse") then
+ scripts.profiler.analyse(environment.files[1] or "luatex-profile.log")
+elseif environment.argument("trace") then
+ scripts.profiler.analyse(environment.files[1] or "temp.log")
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-fonttest.lua b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-fonttest.lua
new file mode 100644
index 00000000000..f593c2e351d
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-fonttest.lua
@@ -0,0 +1,681 @@
+if not modules then modules = { } end modules ['mtx-server-ctx-fonttest'] = {
+ version = 1.001,
+ comment = "Font Feature Tester",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+dofile(resolvers.find_file("l-aux.lua","tex"))
+dofile(resolvers.find_file("trac-lmx.lua","tex"))
+dofile(resolvers.find_file("font-ott.lua","tex"))
+dofile(resolvers.find_file("font-syn.lua","tex"))
+dofile(resolvers.find_file("font-mis.lua","tex"))
+dofile(resolvers.find_file("font-otp.lua","tex"))
+
+local format, gsub, concat, match, find = string.format, string.gsub, table.concat, string.match, string.find
+
+local sample_line = "This is a sample line!"
+local tempname = "mtx-server-ctx-fonttest-temp"
+local temppath = caches.setpath("temp","mtx-server-ctx-fonttest")
+local basename = "mtx-server-ctx-fonttest-data.lua"
+local basepath = temppath
+
+for _, suffix in ipairs { "tex", "pdf", "log" } do
+ os.remove(file.join(temppath,file.addsuffix(tempname,suffix)))
+end
+
+local process_templates = { }
+
+process_templates.default = [[
+\starttext
+ \setcharactermirroring[1]
+ \definefontfeature[sample][%s]
+ \definedfont[name:%s*sample]
+ \startTEXpage[offset=3pt]
+ \detokenize{%s}
+ \stopTEXpage
+\stoptext
+]]
+
+process_templates.cache = [[
+\starttext
+ \definedfont[name:%s]
+ \startTEXpage[offset=3pt]
+ cached: \detokenize{%s}
+ \stopTEXpage
+\stoptext
+]]
+
+process_templates.trace = [[
+\usemodule[fnt-20]
+
+\definefontfeature[sample][%s]
+
+\setupcolors[state=start]
+
+\setcharactermirroring[1]
+
+\setvariables
+ [otftracker]
+ [title=Test Run,
+ font=name:%s,
+ direction=0,
+ features=sample,
+ sample={‍\detokenize{%s}}]
+]]
+
+local javascripts = [[
+function selected_radio(name) {
+ var form = document.forms["main-form"] ;
+ var script = form.elements[name] ;
+ if (script) {
+ var n = script.length ;
+ if (n) {
+ for (var i=0; i<n; i++) {
+ if (script[i].checked) {
+ return script[i].value ;
+ }
+ }
+ }
+ }
+ return "" ;
+}
+
+function reset_valid() {
+ var fields = document.getElementsByTagName("span") ;
+ for (var i=0; i<fields.length; i++) {
+ var e = fields[i]
+ if (e) {
+ if (e.className == "valid") {
+ e.className = "" ;
+ }
+ }
+ }
+}
+
+function set_valid() {
+ var script = selected_radio("script") ;
+ var language = selected_radio("language") ;
+ if (script && language) {
+ var s = feature_hash[script] ;
+ if (s) {
+ for (l in s) {
+ var e = document.getElementById("t-l-" + l) ;
+ if (e) {
+ e.className = "valid" ;
+ }
+ }
+ var l = s[language] ;
+ if (l) {
+ for (i in l) {
+ var e = document.getElementById("t-f-" + i) ;
+ if (e) {
+ e.className = "valid" ;
+ }
+ }
+ }
+ var e = document.getElementById("t-s-" + script) ;
+ if (e) {
+ e.className = "valid" ;
+ }
+ }
+ }
+}
+
+function check_form() {
+ reset_valid() ;
+ set_valid() ;
+}
+
+function check_script() {
+ reset_valid() ;
+ set_valid() ;
+}
+
+function check_language() {
+ reset_valid() ;
+ set_valid() ;
+}
+
+function check_feature() {
+ // not needed
+}
+]]
+
+local cache = { }
+
+local function showfeatures(f)
+ if f then
+ local features = cache[f]
+ if features == nil then
+ features = fonts.get_features(f)
+ if not features then
+ logs.simple("building cache for '%s'",f)
+ io.savedata(file.join(temppath,file.addsuffix(tempname,"tex")),format(process_templates.cache,f,f))
+ os.execute(format("mtxrun --path=%s --script context --once --batchmode %s",temppath,tempname))
+ features = fonts.get_features(f)
+ end
+ cache[f] = features or false
+ logs.simple("caching info of '%s'",f)
+ else
+ logs.simple("using cached info of '%s'",f)
+ end
+ if features then
+ local scr, lan, fea, rev = { }, { }, { }, { }
+ local function show(what)
+ local data = features[what]
+ if data and next(data) then
+ for f,ff in pairs(data) do
+ if find(f,"<") then
+ -- ignore aat for the moment
+ else
+ fea[f] = true
+ for s, ss in pairs(ff) do
+ if find(s,"%*") then
+ -- ignore *
+ else
+ scr[s] = true
+ local rs = rev[s] if not rs then rs = {} rev[s] = rs end
+ for k, l in pairs(ss) do
+ if find(k,"%*") then
+ -- ignore *
+ else
+ lan[k] = true
+ local rsk = rs[k] if not rsk then rsk = { } rs[k] = rsk end
+ rsk[f] = true
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ for what, v in table.sortedpairs(features) do
+ show(what)
+ end
+ local stupid = { }
+ stupid[#stupid+1] = "var feature_hash = new Array ;"
+ for s, sr in pairs(rev) do
+ stupid[#stupid+1] = format("feature_hash['%s'] = new Array ;",s)
+ for l, lr in pairs(sr) do
+ stupid[#stupid+1] = format("feature_hash['%s']['%s'] = new Array ;",s,l)
+ for f, fr in pairs(lr) do
+ stupid[#stupid+1] = format("feature_hash['%s']['%s']['%s'] = true ;",s,l,f)
+ end
+ end
+ end
+ -- gpos feature script languages
+ return {
+ scripts = scr,
+ languages = lan,
+ features = fea,
+ javascript = concat(stupid,"\n")
+ }
+ end
+ end
+end
+
+local function select_font()
+ local t = fonts.names.list(".*")
+ if t then
+ local listoffonts = { }
+ local t = fonts.names.list(".*")
+ if t then
+ listoffonts[#listoffonts+1] = "<table>"
+ listoffonts[#listoffonts+1] = "<tr><th>safe name</th><th>font name</th><th>filename</th><th>sub font&nbsp;</th><th>type</th></tr>"
+ for k, id in ipairs(table.sortedkeys(t)) do
+ local ti = t[id]
+ local type, name, file, sub = ti[1], ti[2], ti[3], ti[4]
+ if type == "otf" or type == "ttf" or type == "ttc" then
+ if sub then sub = "(sub)" else sub = "" end
+ listoffonts[#listoffonts+1] = format("<tr><td><a href='mtx-server-ctx-fonttest.lua?selection=%s'>%s</a></td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>",id,id,name,file,sub,type)
+ end
+ end
+ listoffonts[#listoffonts+1] = "</table>"
+ return concat(listoffonts,"\n")
+ end
+ end
+ return "<b>no fonts</b>"
+end
+
+local edit_template = [[
+ <textarea name='sampletext' rows='5' cols='100'>%s</textarea>
+ <br/> <br/>name:&nbsp;<input type='text' name='name' size='20' value=%q/>&nbsp;&nbsp; title:&nbsp;<input type='text' name='title' size='40' value=%q/>
+ <br/> <br/>scripts:&nbsp;%s
+ <br/> <br/>languages:&nbsp;%s
+ <br/> <br/>features:&nbsp;%s
+ <br/> <br/>options:&nbsp;%s
+]]
+
+local result_template = [[
+ <br/> <br/>
+ <embed src="%s#toolbar=0&amp;navpanes=0&amp;scrollbar=0" width="100%%"/>
+ <br/> <br/> results:
+ <a href='%s' target="source">tex file</a>
+ <a href='%s' target="result">pdf file</a>
+ <br/> <br/>
+]]
+
+scripts.webserver.registerpath(temppath)
+
+local function edit_font(currentfont,detail,tempname)
+ local fontname, fontfile, issub = fonts.names.specification(currentfont or "")
+ local htmldata = showfeatures(fontfile)
+ if htmldata then
+ local features, languages, scripts, options = { }, { }, { }, { }
+ for k,v in ipairs(table.sortedkeys(htmldata.scripts)) do
+ local s = fonts.otf.tables.scripts[v] or v
+ if detail and v == detail.script then
+ scripts[#scripts+1] = format("<input title='%s' id='s-%s' type='radio' name='script' value='%s' onclick='check_script()' checked='checked'/>&nbsp;<span id='t-s-%s'>%s</span>",s,v,v,v,v)
+ else
+ scripts[#scripts+1] = format("<input title='%s' id='s-%s' type='radio' name='script' value='%s' onclick='check_script()' />&nbsp;<span id='t-s-%s'>%s</span>",s,v,v,v,v)
+ end
+ end
+ for k,v in ipairs(table.sortedkeys(htmldata.languages)) do
+ local l = fonts.otf.tables.languages[v] or v
+ if detail and v == detail.language then
+ languages[#languages+1] = format("<input title='%s' id='l-%s' type='radio' name='language' value='%s' onclick='check_language()' checked='checked'/>&nbsp;<span id='t-l-%s'>%s</span>",l,v,v,v,v)
+ else
+ languages[#languages+1] = format("<input title='%s' id='l-%s' type='radio' name='language' value='%s' onclick='check_language()' />&nbsp;<span id='t-l-%s'>%s</span>",l,v,v,v,v)
+ end
+ end
+ for k,v in ipairs(table.sortedkeys(htmldata.features)) do
+ local f = fonts.otf.tables.features[v] or v
+ if detail and detail["f-"..v] then
+ features[#features+1] = format("<input title='%s' id='f-%s' type='checkbox' name='f-%s' onclick='check_feature()' checked='checked'/>&nbsp;<span id='t-f-%s'>%s</span>",f,v,v,v,v)
+ else
+ features[#features+1] = format("<input title='%s' id='f-%s' type='checkbox' name='f-%s' onclick='check_feature()' />&nbsp;<span id='t-f-%s'>%s</span>",f,v,v,v,v)
+ end
+ end
+ for k, v in ipairs { "trace", "basemode" } do
+ if detail and detail["o-"..v] then
+ options[#options+1] = format("<input id='o-%s' type='checkbox' name='o-%s' checked='checked'/>&nbsp;%s",v,v,v)
+ else
+ options[#options+1] = format("<input id='o-%s' type='checkbox' name='o-%s'/>&nbsp;%s",v,v,v)
+ end
+ end
+ local e = format(edit_template,
+ (detail and detail.sampletext) or sample_line,(detail and detail.name) or "no name",(detail and detail.title) or "",
+ concat(scripts," "),concat(languages," "),concat(features," "),concat(options," "))
+ if tempname then
+ local pdffile, texfile = file.addsuffix(tempname,"pdf"), file.addsuffix(tempname,"tex")
+ local r = format(result_template,pdffile,texfile,pdffile)
+ return e .. r, htmldata.javascript or ""
+ else
+ return e, htmldata.javascript or ""
+ end
+ else
+ return "error, nothing set up yet"
+ end
+end
+
+local function process_font(currentfont,detail) -- maybe just fontname
+ local fontname, fontfile, issub = fonts.names.specification(currentfont or "")
+ local features = {
+ "mode=node",
+ format("language=%s",detail.language or "dflt"),
+ format("script=%s",detail.script or "dflt"),
+ }
+ for k,v in pairs(detail) do
+ local f = match(k,"^f%-(.*)$")
+ if f then
+ features[#features+1] = format("%s=yes",f)
+ end
+ end
+ local variant = process_templates.default
+ if detail["o-trace"] then
+ variant = process_templates.trace
+ end
+ local sample = string.strip(detail.sampletext or "")
+ if sample == "" then sample = sample_line end
+ logs.simple("sample text: %s",sample)
+ io.savedata(file.join(temppath,file.addsuffix(tempname,"tex")),format(variant,concat(features,","),currentfont,sample))
+ os.execute(format("mtxrun --path=%s --script context --once --batchmode %s",temppath,tempname))
+ return edit_font(currentfont,detail,tempname)
+end
+
+local tex_template = [[
+<pre><tt>
+%s
+</tt></pre>
+]]
+
+local function show_source(currentfont,detail)
+ if tempname and tempname ~= "" then
+ return format(tex_template,io.loaddata(file.join(temppath,file.addsuffix(tempname,"tex"))) or "no source yet")
+ else
+ return "no source file"
+ end
+end
+
+local function show_log(currentfont,detail)
+ if tempname and tempname ~= "" then
+ local data = io.loaddata(file.join(temppath,file.addsuffix(tempname,'log'))) or "no log file yet"
+ data = gsub(data,"[%s%%]*begin of optionfile.-end of optionfile[%s%%]*","\n")
+ return format(tex_template,data)
+ else
+ return "no log file"
+ end
+end
+
+local function show_font(currentfont,detail)
+ local fontname, fontfile, issub = fonts.names.specification(currentfont or "")
+ local features = fonts.get_features(fontfile)
+ local result = { }
+ result[#result+1] = format("<h1>names</h1>",what)
+ result[#result+1] = "<table>"
+ result[#result+1] = format("<tr><td class='tc'>fontname:</td><td>%s</td></tr>",currentfont)
+ result[#result+1] = format("<tr><td class='tc'>fullname:</td><td>%s</td></tr>",fontname)
+ result[#result+1] = format("<tr><td class='tc'>filename:</td><td>%s</td></tr>",fontfile)
+ result[#result+1] = "</table>"
+ if features then
+ for what, v in table.sortedpairs(features) do
+ local data = features[what]
+ if data and next(data) then
+ result[#result+1] = format("<h1>%s features</h1>",what)
+ result[#result+1] = "<table>"
+ result[#result+1] = "<tr><th>feature</th><th>tag&nbsp;</th><th>script&nbsp;</th><th>languages&nbsp;</th></tr>"
+ for f,ff in table.sortedpairs(data) do
+ local done = false
+ for s, ss in table.sortedpairs(ff) do
+ if s == "*" then s = "all" end
+ if ss ["*"] then ss["*"] = nil ss.all = true end
+ if done then
+ f = ""
+ else
+ done = true
+ end
+ local title = fonts.otf.tables.features[f] or ""
+ result[#result+1] = format("<tr><td width='50%%'>%s&nbsp;&nbsp;</td><td><tt>%s&nbsp;&nbsp;</tt></td><td><tt>%s&nbsp;&nbsp;</tt></td><td><tt>%s&nbsp;&nbsp;</tt></td></tr>",title,f,s,concat(table.sortedkeys(ss)," "))
+ end
+ end
+ result[#result+1] = "</table>"
+ end
+ end
+ else
+ result[#result+1] = "<br/><br/>This font has no features."
+ end
+ return concat(result,"\n")
+end
+
+
+local info_template = [[
+<pre><tt>
+version : %s
+comment : %s
+author : %s
+copyright : %s
+
+maillist : ntg-context at ntg.nl
+webpage : www.pragma-ade.nl
+wiki : contextgarden.net
+</tt></pre>
+]]
+
+local function info_about()
+ local m = modules ['mtx-server-ctx-fonttest']
+ return format(info_template,m.version,m.comment,m.author,m.copyright)
+end
+
+local save_template = [[
+ the current setup has been saved:
+ <br/> <br/>
+ <table>
+ <tr><td class='tc'>name&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>title&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>font&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>script&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>language&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>features&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>options&nbsp; </td><td>%s</td></tr>
+ <tr><td class='tc'>sampletext&nbsp;</td><td>%s</td></tr>
+ </table>
+]]
+
+local function loadbase()
+ local datafile = file.join(basepath,basename)
+ local storage = io.loaddata(datafile) or ""
+ if storage == "" then
+ storage = { }
+ else
+ logs.simple("loading '%s'",datafile)
+ storage = loadstring(storage)
+ storage = (storage and storage()) or { }
+ end
+ return storage
+end
+
+local function loadstored(detail,currentfont,name)
+ local storage = loadbase()
+ storage = storage and storage[name]
+ if storage then
+ currentfont = storage.font
+ detail.script = storage.script or detail.script
+ detail.language = storage.language or detail.language
+ detail.title = storage.title or detail.title
+ detail.sampletext = storage.text or detail.sampletext
+ detail.name = name or "no name"
+ for k,v in pairs(storage.features) do
+ detail["f-"..k] = v
+ end
+ for k,v in pairs(storage.options) do
+ detail["o-"..k] = v
+ end
+ end
+ detail.loadname = nil
+ return detail, currentfont
+end
+
+local function savebase(storage,name)
+ local datafile = file.join(basepath,basename)
+ logs.simple("saving '%s' in '%s'",name or "data",datafile)
+ io.savedata(datafile,table.serialize(storage,true))
+end
+
+local function deletestored(detail,currentfont,name)
+ local storage = loadbase()
+ if storage and name and storage[name] then
+ logs.simple("deleting '%s' from base",name)
+ storage[name] = nil
+ savebase(storage)
+ end
+ detail.deletename = nil
+ return detail, ""
+end
+
+local function save_font(currentfont,detail)
+ local fontname, fontfile, issub = fonts.names.specification(currentfont or "")
+ local name, title, script, language, features, options, text = currentfont, "", "dflt", "dflt", { }, { }, ""
+ if detail then
+ local htmldata = showfeatures(fontfile)
+ script = detail.script or script
+ language = detail.language or language
+ text = string.strip(detail.sampletext or text)
+ name = string.strip(detail.name or name)
+ title = string.strip(detail.title or title)
+ for k,v in pairs(htmldata.features) do
+ if detail["f-"..k] then features[k] = true end
+ end
+ for k,v in ipairs { "trace", "basemode" } do
+ if detail["o-"..v] then options[k] = true end
+ end
+ end
+ if name == "" then
+ name = "no name"
+ end
+ local storage = loadbase()
+ storage[name] = {
+ font = currentfont, title = title, script = script, language = language, features = features, options = options, text = text,
+ }
+ savebase(storage,name)
+ return format(save_template,name,title,currentfont,script,language,concat(table.sortedkeys(features)," "),concat(table.sortedkeys(options)," "),text)
+end
+
+local function load_font(currentfont)
+ local datafile = file.join(basepath,basename)
+ local storage = loadbase(datafile)
+ local result = {}
+ result[#result+1] = format("<tr><th>del&nbsp;</th><th>name&nbsp;</th><th>font&nbsp;</th><th>fontname&nbsp;</th><th>script&nbsp;</th><th>language&nbsp;</th><th>features&nbsp;</th><th>title&nbsp;</th><th>sampletext&nbsp;</th></tr>")
+ for k,v in table.sortedpairs(storage) do
+ local fontname, fontfile, issub = fonts.names.specification(v.font or "")
+ result[#result+1] = format("<tr><td><a href='mtx-server-ctx-fonttest.lua?deletename=%s'>x</a>&nbsp;</td><td><a href='mtx-server-ctx-fonttest.lua?loadname=%s'>%s</a>&nbsp;</td><td>%s&nbsp;</td<td>%s&nbsp;</td><td>%s&nbsp;</td><td>%s&nbsp;</td><td>%s&nbsp;</td><td>%s&nbsp;</td><td>%s&nbsp;</td></tr>",
+ k,k,k,v.font,fontname,v.script,v.language,concat(table.sortedkeys(v.features)," "),v.title or "no title",v.text or "")
+ end
+ if #result == 1 then
+ return "nothing saved yet"
+ else
+ return format("<table>%s</table>",concat(result,"\n"))
+ end
+end
+
+local function reset_font(currentfont)
+ return edit_font(currentfont)
+end
+
+local extras_template = [[
+ <a href='mtx-server-ctx-fonttest.lua?extra=reload'>remake font database</a> (take some time)<br/><br/>
+]]
+
+local function do_extras(detail,currentfont,extra)
+ return extras_template
+end
+
+local extras = { }
+
+local function do_extra(detail,currentfont,extra)
+ local e = extras[extra]
+ if e then e(detail,currentfont,extra) end
+ return do_extras(detail,currentfont,extra)
+end
+
+function extras.reload()
+ local command = "mtxrun --script font --reload"
+ logs.simple("run command: %s",command)
+ os.execute(command)
+ return do_extras()
+end
+
+
+local status_template = [[
+ <input type="hidden" name="currentfont" value="%s" />
+]]
+
+function doit(configuration,filename,hashed)
+
+ local start = os.clock()
+
+ local detail = url.query(hashed.query or "")
+
+ local currentfont = detail.currentfont
+ local action = detail.action
+ local selection = detail.selection
+
+ local loadname = detail.loadname
+ local deletename = detail.deletename
+ local extra = detail.extra
+
+ if loadname and loadname ~= "" then
+ detail, currentfont = loadstored(detail,currentfont,loadname)
+ action = "process"
+ elseif deletename and deletename ~= "" then
+ detail, currentfont = deletestored(detail,currentfont,deletename)
+ action = "load"
+ elseif selection and selection ~= "" then
+ currentfont = selection
+ elseif extra and extra ~= "" then
+ do_extra(detail,currentfont,extra)
+ action = "extras"
+ end
+
+ lmx.restore()
+
+ local fontname, fontfile, issub = fonts.names.specification(currentfont or "")
+
+ if fontfile then
+ lmx.variables['title-default'] = format('ConTeXt Font Tester: %s (%s)',fontname,fontfile)
+ else
+ lmx.variables['title-default'] = 'ConTeXt Font Tester'
+ end
+
+ lmx.variables['color-background-green'] = '#4F6F6F'
+ lmx.variables['color-background-blue'] = '#6F6F8F'
+ lmx.variables['color-background-yellow'] = '#8F8F6F'
+ lmx.variables['color-background-purple'] = '#8F6F8F'
+
+ lmx.variables['color-background-body'] = '#808080'
+ lmx.variables['color-background-main'] = '#3F3F3F'
+ lmx.variables['color-background-one'] = lmx.variables['color-background-green']
+ lmx.variables['color-background-two'] = lmx.variables['color-background-blue']
+
+ lmx.variables['title'] = lmx.variables['title-default']
+
+ lmx.set('title', lmx.get('title'))
+ lmx.set('color-background-one', lmx.get('color-background-green'))
+ lmx.set('color-background-two', lmx.get('color-background-blue'))
+
+ -- lua table and adapt
+
+ lmx.set('formaction', "mtx-server-ctx-fonttest.lua")
+
+ local menu = { }
+ for k, v in ipairs { 'process', 'select', 'save', 'load', 'edit', 'reset', 'features', 'source', 'log', 'info', 'extras'} do
+ menu[#menu+1] = format("<button name='action' value='%s' type='submit'>%s</button>",v,v)
+ end
+ lmx.set('menu', concat(menu,"&nbsp;"))
+
+ logs.simple("action: %s",action or "no action")
+
+ lmx.set("status",format(status_template,currentfont or ""))
+
+ local result
+
+ if action == "select" then
+ lmx.set('maintext',select_font())
+ elseif action == "info" then
+ lmx.set('maintext',info_about())
+ elseif action == "extras" then
+ lmx.set('maintext',do_extras())
+ elseif currentfont and currentfont ~= "" then
+ if action == "save" then
+ lmx.set('maintext',save_font(currentfont,detail))
+ elseif action == "load" then
+ lmx.set('maintext',load_font(currentfont,detail))
+ elseif action == "source" then
+ lmx.set('maintext',show_source(currentfont,detail))
+ elseif action == "log" then
+ lmx.set('maintext',show_log(currentfont,detail))
+ elseif action == "features" then
+ lmx.set('maintext',show_font(currentfont,detail))
+ else
+ local e, s
+ if action == "process" then
+ e, s = process_font(currentfont,detail)
+ elseif action == "reset" then
+ e, s = reset_font(currentfont)
+ elseif action == "edit" then
+ e, s = edit_font(currentfont,detail)
+ else
+ e, s = process_font(currentfont,detail)
+ end
+ lmx.set('maintext',e)
+ lmx.set('javascriptdata',s)
+ lmx.set('javascripts',javascripts)
+ lmx.set('javascriptinit', "check_form()")
+ end
+ else
+ lmx.set('maintext',select_font())
+ end
+
+ result = { content = lmx.convert('context-fonttest.lmx') }
+
+ logs.simple("time spent on page: %0.03f seconds",os.clock()-start)
+
+ return result
+
+end
+
+return doit, true
+
+--~ make_lmx_page("test")
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-help.lua b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-help.lua
new file mode 100644
index 00000000000..c53d9f6e0f2
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-help.lua
@@ -0,0 +1,648 @@
+if not modules then modules = { } end modules ['mtx-server-ctx-help'] = {
+ version = 1.001,
+ comment = "Basic Definition Browser",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--~ dofile(resolvers.find_file("l-xml.lua","tex"))
+dofile(resolvers.find_file("l-aux.lua","tex"))
+dofile(resolvers.find_file("trac-lmx.lua","tex"))
+
+-- problem ... serialize parent stack
+
+local format = string.format
+local concat = table.concat
+
+-- -- -- make this a module: cont-xx.lua
+
+document = document or { }
+document.setups = document.setups or { }
+
+document.setups.div = {
+ pe = "<div dir='rtl' lang='arabic'>%s</div>"
+}
+
+document.setups.span = {
+ pe = "<span dir='rtl' lang='arabic'>%s</span>"
+}
+
+document.setups.translations = document.setups.translations or {
+
+ nl = {
+ ["title"] = "setup",
+ ["formula"] = "formule",
+ ["number"] = "getal",
+ ["list"] = "lijst",
+ ["dimension"] = "maat",
+ ["mark"] = "markering",
+ ["reference"] = "verwijzing",
+ ["command"] = "commando",
+ ["file"] = "file",
+ ["name"] = "naam",
+ ["identifier"] = "naam",
+ ["text"] = "tekst",
+ ["section"] = "sectie",
+ ["singular"] = "naam enkelvoud",
+ ["plural"] = "naam meervoud",
+ ["matrix"] = "n*m",
+ ["see"] = "zie",
+ ["inherits"] = "erft van",
+ ["optional"] = "optioneel",
+ ["displaymath"] = "formule",
+ ["index"] = "ingang",
+ ["math"] = "formule",
+ ["nothing"] = "leeg",
+ ["file"] = "file",
+ ["position"] = "positie",
+ ["reference"] = "verwijzing",
+ ["csname"] = "naam",
+ ["destination"] = "bestemming",
+ ["triplet"] = "triplet",
+ ["word"] = "woord",
+ ["content"] = "tekst",
+ },
+
+ en = {
+ ["title"] = "setup",
+ ["formula"] = "formula",
+ ["number"] = "number",
+ ["list"] = "list",
+ ["dimension"] = "dimension",
+ ["mark"] = "mark",
+ ["reference"] = "reference",
+ ["command"] = "command",
+ ["file"] = "file",
+ ["name"] = "name",
+ ["identifier"] = "identifier",
+ ["text"] = "text",
+ ["section"] = "section",
+ ["singular"] = "singular name",
+ ["plural"] = "plural name",
+ ["matrix"] = "n*m",
+ ["see"] = "see",
+ ["inherits"] = "inherits from",
+ ["optional"] = "optional",
+ ["displaymath"] = "formula",
+ ["index"] = "entry",
+ ["math"] = "formula",
+ ["nothing"] = "empty",
+ ["file"] = "file",
+ ["position"] = "position",
+ ["reference"] = "reference",
+ ["csname"] = "name",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "word",
+ ["content"] = "text",
+
+ ["noargument"] = "\\cs",
+ ["oneargument"] = "\\cs#1{..}",
+ ["twoarguments"] = "\\cs#1#2{..}{..}",
+ ["threearguments"] = "\\cs#1#2#3{..}{..}{..}",
+
+ },
+
+ de = {
+ ["title"] = "Setup",
+ ["formula"] = "Formel",
+ ["number"] = "Nummer",
+ ["list"] = "Liste",
+ ["dimension"] = "Dimension",
+ ["mark"] = "Beschriftung",
+ ["reference"] = "Referenz",
+ ["command"] = "Befehl",
+ ["file"] = "Datei",
+ ["name"] = "Name",
+ ["identifier"] = "Name",
+ ["text"] = "Text",
+ ["section"] = "Abschnitt",
+ ["singular"] = "singular",
+ ["plural"] = "plural",
+ ["matrix"] = "n*m",
+ ["see"] = "siehe",
+ ["inherits"] = "inherits from",
+ ["optional"] = "optioneel",
+ ["displaymath"] = "formula",
+ ["index"] = "entry",
+ ["math"] = "formula",
+ ["nothing"] = "empty",
+ ["file"] = "file",
+ ["position"] = "position",
+ ["reference"] = "reference",
+ ["csname"] = "name",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "word",
+ ["content"] = "text",
+ },
+
+ cz = {
+ ["title"] = "setup",
+ ["formula"] = "rovnice",
+ ["number"] = "cislo",
+ ["list"] = "seznam",
+ ["dimension"] = "dimenze",
+ ["mark"] = "znacka",
+ ["reference"] = "reference",
+ ["command"] = "prikaz",
+ ["file"] = "soubor",
+ ["name"] = "jmeno",
+ ["identifier"] = "jmeno",
+ ["text"] = "text",
+ ["section"] = "sekce",
+ ["singular"] = "jmeno v singularu",
+ ["plural"] = "jmeno v pluralu",
+ ["matrix"] = "n*m",
+ ["see"] = "viz",
+ ["inherits"] = "inherits from",
+ ["optional"] = "optioneel",
+ ["displaymath"] = "formula",
+ ["index"] = "entry",
+ ["math"] = "formula",
+ ["nothing"] = "empty",
+ ["file"] = "file",
+ ["position"] = "position",
+ ["reference"] = "reference",
+ ["csname"] = "name",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "word",
+ ["content"] = "text",
+ },
+
+ it = {
+ ["title"] = "setup",
+ ["formula"] = "formula",
+ ["number"] = "number",
+ ["list"] = "list",
+ ["dimension"] = "dimension",
+ ["mark"] = "mark",
+ ["reference"] = "reference",
+ ["command"] = "command",
+ ["file"] = "file",
+ ["name"] = "name",
+ ["identifier"] = "name",
+ ["text"] = "text",
+ ["section"] = "section",
+ ["singular"] = "singular name",
+ ["plural"] = "plural name",
+ ["matrix"] = "n*m",
+ ["see"] = "see",
+ ["inherits"] = "inherits from",
+ ["optional"] = "optioneel",
+ ["displaymath"] = "formula",
+ ["index"] = "entry",
+ ["math"] = "formula",
+ ["nothing"] = "empty",
+ ["file"] = "file",
+ ["position"] = "position",
+ ["reference"] = "reference",
+ ["csname"] = "name",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "word",
+ ["content"] = "text",
+ },
+
+ ro = {
+ ["title"] = "setari",
+ ["formula"] = "formula",
+ ["number"] = "numar",
+ ["list"] = "lista",
+ ["dimension"] = "dimensiune",
+ ["mark"] = "marcaj",
+ ["reference"] = "referinta",
+ ["command"] = "comanda",
+ ["file"] = "fisier",
+ ["name"] = "nume",
+ ["identifier"] = "nume",
+ ["text"] = "text",
+ ["section"] = "sectiune",
+ ["singular"] = "nume singular",
+ ["plural"] = "nume pluram",
+ ["matrix"] = "n*m",
+ ["see"] = "vezi",
+ ["inherits"] = "inherits from",
+ ["optional"] = "optioneel",
+ ["displaymath"] = "formula",
+ ["index"] = "entry",
+ ["math"] = "formula",
+ ["nothing"] = "empty",
+ ["file"] = "file",
+ ["position"] = "position",
+ ["reference"] = "reference",
+ ["csname"] = "name",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "word",
+ ["content"] = "text",
+ },
+
+ fr = {
+ ["title"] = "réglage",
+ ["formula"] = "formule",
+ ["number"] = "numéro",
+ ["list"] = "liste",
+ ["dimension"] = "dimension",
+ ["mark"] = "marquage",
+ ["reference"] = "reference",
+ ["command"] = "commande",
+ ["file"] = "fichier",
+ ["name"] = "nom",
+ ["identifier"] = "identificateur",
+ ["text"] = "texte",
+ ["section"] = "section",
+ ["singular"] = "nom singulier",
+ ["plural"] = "nom pluriel",
+ ["matrix"] = "n*m",
+ ["see"] = "vois",
+ ["inherits"] = "herite de",
+ ["optional"] = "optionel",
+ ["displaymath"] = "formule",
+ ["index"] = "entrée",
+ ["math"] = "formule",
+ ["nothing"] = "vide",
+ ["file"] = "fichier",
+ ["position"] = "position",
+ ["reference"] = "réference",
+ ["csname"] = "nom",
+ ["destination"] = "destination",
+ ["triplet"] = "triplet",
+ ["word"] = "mot",
+ ["content"] = "texte",
+ }
+
+}
+
+document.setups.formats = {
+ interface = [[<a href='mtx-server-ctx-help.lua?interface=%s'>%s</a>]],
+ href = [[<a href='mtx-server-ctx-help.lua?command=%s'>%s</a>]],
+ source = [[<a href='mtx-server-ctx-help.lua?source=%s'>%s</a>]],
+ optional_single = "[optional string %s]",
+ optional_list = "[optional list %s]",
+ mandate_single = "[mandate string %s]",
+ mandate_list = "[mandate list %s]",
+ parameter = [[<tr><td width='15%%'>%s</td><td width='15%%'>%s</td><td width='70%%'>%s</td></tr>]],
+ parameters = [[<table width='100%%'>%s</table>]],
+ listing = [[<pre><t>%s</t></listing>]],
+ special = "<i>%s</i>",
+ default = "<u>%s</u>",
+}
+
+local function translate(tag,int,noformat)
+ local t = document.setups.translations
+ local te = t["en"]
+ local ti = t[int] or te
+ if noformat then
+ return ti[tag] or te[tag] or tag
+ else
+ return document.setups.formats.special:format(ti[tag] or te[tag] or tag)
+ end
+end
+
+local function translated(e,int)
+ local attributes = e.at
+ local s = attributes.type or "?"
+ local tag = s:match("^cd:(.*)$")
+ if attributes.default == "yes" then
+ return document.setups.formats.default:format(tag)
+ elseif tag then
+ return translate(tag,int)
+ else
+ return s
+ end
+end
+
+document.setups.loaded = document.setups.loaded or { }
+
+document.setups.current = { }
+document.setups.showsources = false
+
+function document.setups.load(filename)
+ filename = resolvers.find_file(filename) or ""
+ if filename ~= "" then
+ local current = document.setups.loaded[filename]
+ if not current then
+ local loaded = xml.load(filename)
+ if loaded then
+ -- xml.inject(document.setups.root,"/",loaded)
+ current = {
+ file = filename,
+ root = loaded,
+ names = { },
+ used = { },
+ }
+ document.setups.loaded[filename] = current
+ end
+ end
+ document.setups.current = current or { }
+ end
+end
+
+function document.setups.name(ek)
+ local at = ek.at
+ local name = at.name
+ if at.type == 'environment' then
+ name = "start" .. name
+ end
+ if at.variant then
+ name = name .. ":" .. at.variant
+ end
+ if at.generated == "yes" then
+ name = name .. "*"
+ end
+ return name:lower()
+end
+
+function document.setups.csname(ek,int)
+ local cs = ""
+ local at = ek.at
+ if at.type == 'environment' then
+ cs = translate("start",int,true) .. cs
+ end
+ for r, d, k in xml.elements(ek,'cd:sequence/(cd:string|variable)') do
+ local dk = d[k]
+ if dk.tg == "string" then
+ cs = cs .. dk.at.value
+ else
+ cs = cs .. dk.at.value -- to be translated
+ end
+ end
+ return cs
+end
+
+function document.setups.names()
+ local current = document.setups.current
+ local names = current.names
+ if not names or #names == 0 then
+ names = { }
+ local name = document.setups.name
+ local csname = document.setups.csname
+ for r, d, k in xml.elements(current.root,'cd:command') do
+ local dk = d[k]
+ names[#names+1] = { dk.at.name, csname(dk,int) }
+ end
+ table.sort(names, function(a,b) return a[2]:lower() < b[2]:lower() end)
+ current.names = names
+ end
+ return names
+end
+
+function document.setups.show(name)
+ local current = document.setups.current
+ if current.root then
+ local name = name:gsub("[<>]","")
+ local setup = xml.first(current.root,"cd:command[@name='" .. name .. "']")
+ current.used[#current.used+1] = setup
+ xml.sprint(setup)
+ end
+end
+
+function document.setups.showused()
+ local current = document.setups.current
+ if current.root and next(current.used) then
+ for k,v in ipairs(table.sortedkeys(current.used)) do
+ xml.sprint(current.used[v])
+ end
+ end
+end
+function document.setups.showall()
+ local current = document.setups.current
+ if current.root then
+ local list = { }
+ xml.each_element(current.root,"cd:command", function(r,d,t)
+ local ek = d[t]
+ list[document.setups.name(ek)] = ek
+ end )
+ for k,v in ipairs(table.sortedkeys(list)) do
+ xml.sprint(list[v])
+ end
+ end
+end
+function document.setups.resolve(name)
+ local current = document.setups.current
+ if current.root then
+ local e = xml.filter(current.root,format("cd:define[@name='%s']/text()",name))
+ if e then
+ xml.sprint(e)
+ end
+ end
+end
+
+function document.setups.collect(name,int)
+ local current = document.setups.current
+ local formats = document.setups.formats
+ local command = xml.filter(current.root,format("cd:command[@name='%s']",name))
+ if command then
+ local attributes = command.at
+ local data = {
+ command = command,
+ category = attributes.category or "",
+ }
+ if document.setups.showsources then
+ data.source = (attributes.file and formats.source:format(attributes.file,attributes.file)) or ""
+ else
+ data.source = attributes.file or ""
+ end
+ local sequence, n = { "\\" .. document.setups.csname(command,int) }, 0
+ local arguments = { }
+ for r, d, k in xml.elements(command,"(cd:keywords|cd:assignments)") do
+ n = n + 1
+ local attributes = d[k].at
+ if attributes.optional == 'yes' then
+ if attributes.list == 'yes' then
+ sequence[#sequence+1] = formats.optional_list:format(n)
+ else
+ sequence[#sequence+1] = formats.optional_single:format(n)
+ end
+ else
+ if attributes.list == 'yes' then
+ sequence[#sequence+1] = formats.mandate_list:format(n)
+ else
+ sequence[#sequence+1] = formats.mandate_single:format(n)
+ end
+ end
+ end
+ data.sequence = concat(sequence, " ")
+ local parameters, n = { }, 0
+ for r, d, k in xml.elements(command,"(cd:keywords|cd:assignments)") do
+ n = n + 1
+ if d[k].tg == "keywords" then
+ local left = sequence[n+1]
+ local right = { }
+ for r, d, k in xml.elements(d[k],"(cd:constant|cd:resolve)") do
+ local tag = d[k].tg
+ if tag == "resolve" then
+ local name = d[k].at.name or ""
+ if name ~= "" then
+ local resolved = xml.filter(current.root,format("cd:define[@name='%s']",name))
+ for r, d, k in xml.elements(resolved,"cd:constant") do
+ right[#right+1] = translated(d[k],int)
+ end
+ end
+ else
+ right[#right+1] = translated(d[k],int)
+ end
+ end
+ parameters[#parameters+1] = formats.parameter:format(left,"",concat(right, ", "))
+ else
+ local what = sequence[n+1]
+ for r, d, k in xml.elements(d[k],"(cd:parameter|cd:inherit)") do
+ local tag = d[k].tg
+ local left, right = d[k].at.name or "?", { }
+ if tag == "inherit" then
+ local name = d[k].at.name or "?"
+ local goto = document.setups.formats.href:format(name,"\\"..name)
+ if #parameters > 0 and not parameters[#parameters]:find("<br/>") then
+ parameters[#parameters+1] = formats.parameter:format("<br/>","","")
+ end
+ parameters[#parameters+1] = formats.parameter:format(what,formats.special:format(translate("inherits",int)),goto)
+ else
+ for r, d, k in xml.elements(d[k],"(cd:constant|cd:resolve)") do
+ local tag = d[k].tg
+ if tag == "resolve" then
+ local name = d[k].at.name or ""
+ if name ~= "" then
+ local resolved = xml.filter(current.root,format("cd:define[@name='%s']",name))
+ for r, d, k in xml.elements(resolved,"cd:constant") do
+ right[#right+1] = translated(d[k],int)
+ end
+ end
+ else
+ right[#right+1] = translated(d[k],int)
+ end
+ end
+ parameters[#parameters+1] = formats.parameter:format(what,left,concat(right, ", "))
+ end
+ what = ""
+ end
+ end
+ parameters[#parameters+1] = formats.parameter:format("<br/>","","")
+ end
+ data.parameters = parameters
+ return data
+ else
+ return nil
+ end
+end
+
+-- -- --
+
+tex = tex or { }
+
+lmx.variables['color-background-green'] = '#4F6F6F'
+lmx.variables['color-background-blue'] = '#6F6F8F'
+lmx.variables['color-background-yellow'] = '#8F8F6F'
+lmx.variables['color-background-purple'] = '#8F6F8F'
+
+lmx.variables['color-background-body'] = '#808080'
+lmx.variables['color-background-main'] = '#3F3F3F'
+lmx.variables['color-background-main-left'] = '#3F3F3F'
+lmx.variables['color-background-main-right'] = '#5F5F5F'
+lmx.variables['color-background-one'] = lmx.variables['color-background-green']
+lmx.variables['color-background-two'] = lmx.variables['color-background-blue']
+
+lmx.variables['title-default'] = 'ConTeXt Help Information'
+lmx.variables['title'] = lmx.variables['title-default']
+
+function lmx.loadedfile(filename)
+ return io.loaddata(resolvers.find_file(filename)) -- return resolvers.texdatablob(filename)
+end
+
+-- -- --
+
+local interfaces = {
+ czech = 'cz',
+ dutch = 'nl',
+ english = 'en',
+ french = 'fr',
+ german = 'de',
+ italian = 'it',
+ persian = 'pe',
+ romanian = 'ro',
+}
+
+local lastinterface, lastcommand, lastsource = "en", "", ""
+
+local function doit(configuration,filename,hashed)
+
+ local formats = document.setups.formats
+
+ local start = os.clock()
+
+ local detail = aux.settings_to_hash(hashed.query or "")
+
+ lastinterface, lastcommand, lastsource = detail.interface or lastinterface, detail.command or lastcommand, detail.source or lastsource
+
+ if lastinterface then
+ logs.simple("checking interface: %s",lastinterface)
+ document.setups.load(format("cont-%s.xml",lastinterface))
+ end
+
+ local div = document.setups.div[lastinterface]
+ local span = document.setups.span[lastinterface]
+
+ local result = { content = "error" }
+
+ local names, refs, ints = document.setups.names(lastinterface), { }, { }
+ for k,v in ipairs(names) do
+ refs[k] = document.setups.formats.href:format(v[1],v[2])
+ end
+ for k,v in ipairs(table.sortedkeys(interfaces)) do
+ ints[k] = document.setups.formats.interface:format(interfaces[v],v)
+ end
+
+ lmx.restore()
+ lmx.set('title', 'ConTeXt Help Information')
+ lmx.set('color-background-one', lmx.get('color-background-green'))
+ lmx.set('color-background-two', lmx.get('color-background-blue'))
+
+ local n = concat(refs,"<br/>")
+ local i = concat(ints,"<br/><br/>")
+
+ if div then
+ lmx.set('names',div:format(n))
+ lmx.set('interfaces',div:format(i))
+ else
+ lmx.set('names', n)
+ lmx.set('interfaces', i)
+ end
+
+ -- first we need to add information about mkii/mkiv
+
+ if document.setups.showsources and lastsource and lastsource ~= "" then
+ -- todo: mkii, mkiv, tex (can be different)
+ local data = io.loaddata(resolvers.find_file(lastsource))
+ lmx.set('maintitle', lastsource)
+ lmx.set('maintext', formats.listing:format(data))
+ lastsource = ""
+ elseif lastcommand and lastcommand ~= "" then
+ local data = document.setups.collect(lastcommand,lastinterface)
+ if data then
+ lmx.set('maintitle', data.sequence)
+ local extra = { }
+ for k, v in ipairs { "environment", "category", "source" } do
+ if data[v] and data[v] ~= "" then
+ lmx.set(v, data[v])
+ extra[#extra+1] = v .. ": " .. data[v]
+ end
+ end
+ lmx.set('extra', concat(extra,", "))
+ lmx.set('maintext', formats.parameters:format(concat(data.parameters)))
+ else
+ lmx.set('maintext', "select command")
+ end
+ else
+ lmx.set('maintext', "no definition")
+ end
+
+ local content = lmx.convert('context-help.lmx')
+
+ logs.simple("time spent on page: %0.03f seconds",os.clock()-start)
+
+ return { content = content }
+end
+
+return doit, true
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-startup.lua b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-startup.lua
new file mode 100644
index 00000000000..fcb757b3e60
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-server-ctx-startup.lua
@@ -0,0 +1,53 @@
+if not modules then modules = { } end modules ['mtx-server-ctx-startup'] = {
+ version = 1.001,
+ comment = "Overview Of Goodies",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+dofile(resolvers.find_file("trac-lmx.lua","tex"))
+
+function doit(configuration,filename,hashed)
+
+ lmx.restore()
+
+ lmx.variables['color-background-green'] = '#4F6F6F'
+ lmx.variables['color-background-blue'] = '#6F6F8F'
+ lmx.variables['color-background-yellow'] = '#8F8F6F'
+ lmx.variables['color-background-purple'] = '#8F6F8F'
+
+ lmx.variables['color-background-body'] = '#808080'
+ lmx.variables['color-background-main'] = '#3F3F3F'
+ lmx.variables['color-background-one'] = lmx.variables['color-background-green']
+ lmx.variables['color-background-two'] = lmx.variables['color-background-blue']
+
+ lmx.variables['title'] = "Overview Of Goodies"
+
+ lmx.set('title', lmx.get('title'))
+ lmx.set('color-background-one', lmx.get('color-background-green'))
+ lmx.set('color-background-two', lmx.get('color-background-blue'))
+
+
+ local list = { }
+ local root = file.dirname(resolvers.find_file("mtx-server.lua") or ".")
+ if root == "" then root = "." end
+ local pattern = root .. "/mtx-server-ctx-*.lua"
+ local files = dir.glob(pattern)
+ for i=1,#files do
+ local filename = file.basename(files[i])
+ local name = string.match(filename,"mtx%-server%-ctx%-(.-)%.lua$")
+ if name and name ~= "startup" then
+ list[#list+1] = string.format("<a href='%s' target='ctx-%s'>%s</a><br/><br/>",filename,name,name)
+ end
+ end
+
+ lmx.set('maintext',table.concat(list,"\n"))
+
+ result = { content = lmx.convert('context-base.lmx') }
+
+ return result
+
+end
+
+return doit, true
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-server.lua b/Master/texmf-dist/scripts/context/lua/mtx-server.lua
index 293bc0c1c37..615506ac0ce 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-server.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-server.lua
@@ -1,83 +1,356 @@
if not modules then modules = { } end modules ['mtx-server'] = {
version = 1.001,
comment = "companion to mtxrun.lua",
- author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ author = "Hans Hagen & Taco Hoekwater",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+scripts = scripts or { }
+scripts.webserver = scripts.webserver or { }
--- The starting point was stripped down webserver.lua by Samuel
--- Saint-Pettersen (as downloaded on 21-5-2008) which only served
--- html and was not configureable. In due time I will extend the
--- next code. Eventually we may move code to l-server.
+dofile(resolvers.find_file("l-url.lua","tex"))
+dofile(resolvers.find_file("luat-soc.lua","tex"))
-scripts = scripts or { }
-scripts.webserver = scripts.webserver or {}
+local socket = socket or require("socket") -- redundant in future version
+local format = string.format
+
+-- The following two lists are taken from webrick (ruby) and
+-- extended with a few extra suffixes.
+
+local mimetypes = {
+ ai = 'application/postscript',
+ asc = 'text/plain',
+ avi = 'video/x-msvideo',
+ bin = 'application/octet-stream',
+ bmp = 'image/bmp',
+ bz2 = 'application/x-bzip2',
+ cer = 'application/pkix-cert',
+ class = 'application/octet-stream',
+ crl = 'application/pkix-crl',
+ crt = 'application/x-x509-ca-cert',
+ css = 'text/css',
+ dms = 'application/octet-stream',
+ doc = 'application/msword',
+ dvi = 'application/x-dvi',
+ eps = 'application/postscript',
+ etx = 'text/x-setext',
+ exe = 'application/octet-stream',
+ gif = 'image/gif',
+ gz = 'application/x-tar',
+ hqx = 'application/mac-binhex40',
+ htm = 'text/html',
+ html = 'text/html',
+ jpe = 'image/jpeg',
+ jpeg = 'image/jpeg',
+ jpg = 'image/jpeg',
+ lha = 'application/octet-stream',
+ lzh = 'application/octet-stream',
+ mov = 'video/quicktime',
+ mpe = 'video/mpeg',
+ mpeg = 'video/mpeg',
+ mpg = 'video/mpeg',
+ pbm = 'image/x-portable-bitmap',
+ pdf = 'application/pdf',
+ pgm = 'image/x-portable-graymap',
+ png = 'image/png',
+ pnm = 'image/x-portable-anymap',
+ ppm = 'image/x-portable-pixmap',
+ ppt = 'application/vnd.ms-powerpoint',
+ ps = 'application/postscript',
+ qt = 'video/quicktime',
+ ras = 'image/x-cmu-raster',
+ rb = 'text/plain',
+ rd = 'text/plain',
+ rgb = 'image/x-rgb',
+ rtf = 'application/rtf',
+ sgm = 'text/sgml',
+ sgml = 'text/sgml',
+ snd = 'audio/basic',
+ tar = 'application/x-tar',
+ tgz = 'application/x-tar',
+ tif = 'image/tiff',
+ tiff = 'image/tiff',
+ txt = 'text/plain',
+ xbm = 'image/x-xbitmap',
+ xls = 'application/vnd.ms-excel',
+ xml = 'text/xml',
+ xpm = 'image/x-xpixmap',
+ xwd = 'image/x-xwindowdump',
+ zip = 'application/zip',
+}
+
+local messages = {
+ [100] = 'Continue',
+ [101] = 'Switching Protocols',
+ [200] = 'OK',
+ [201] = 'Created',
+ [202] = 'Accepted',
+ [203] = 'Non-Authoritative Information',
+ [204] = 'No Content',
+ [205] = 'Reset Content',
+ [206] = 'Partial Content',
+ [300] = 'Multiple Choices',
+ [301] = 'Moved Permanently',
+ [302] = 'Found',
+ [303] = 'See Other',
+ [304] = 'Not Modified',
+ [305] = 'Use Proxy',
+ [307] = 'Temporary Redirect',
+ [400] = 'Bad Request',
+ [401] = 'Unauthorized',
+ [402] = 'Payment Required',
+ [403] = 'Forbidden',
+ [404] = 'Not Found',
+ [405] = 'Method Not Allowed',
+ [406] = 'Not Acceptable',
+ [407] = 'Proxy Authentication Required',
+ [408] = 'Request Timeout',
+ [409] = 'Conflict',
+ [410] = 'Gone',
+ [411] = 'Length Required',
+ [412] = 'Precondition Failed',
+ [413] = 'Request Entity Too Large',
+ [414] = 'Request-URI Too Large',
+ [415] = 'Unsupported Media Type',
+ [416] = 'Request Range Not Satisfiable',
+ [417] = 'Expectation Failed',
+ [500] = 'Internal Server Error',
+ [501] = 'Not Implemented',
+ [502] = 'Bad Gateway',
+ [503] = 'Service Unavailable',
+ [504] = 'Gateway Timeout',
+ [505] = 'HTTP Version Not Supported',
+}
+
+local handlers = { }
+
+local function errormessage(client,configuration,n)
+ local data = format("<head><title>%s %s</title></head><html><h2>%s %s</h2></html>",n,messages[n],n,messages[n])
+ logs.simple("handling error %s: %s",n,messages[n])
+ handlers.generic(client,configuration,data,nil,true)
+end
+
+local validpaths, registered = { }, { }
+
+function scripts.webserver.registerpath(name)
+ if not registered[name] then
+ local cleanname = string.gsub(name,"%.%.","deleted-parent")
+ logs.simple("registering path '%s'",cleanname)
+ validpaths[#validpaths+1] = cleanname
+ registered[name] = true
+ end
+end
-local socket = require("socket")
+function handlers.generic(client,configuration,data,suffix,iscontent)
+ if not iscontent then
+ local name = data
+ logs.simple("requested file '%s'",name)
+ local fullname = file.join(configuration.root,name)
+ data = io.loaddata(fullname) or ""
+ if data == "" then
+ for n=1,#validpaths do
+ local fullname = file.join(validpaths[n],name)
+ data = io.loaddata(fullname) or ""
+ if data ~= "" then
+ logs.simple("sending generic file '%s'",fullname)
+ break
+ end
+ end
+ else
+ logs.simple("sending generic file '%s'",fullname)
+ end
+ end
+ if data and data ~= "" then
+ client:send("HTTP/1.1 200 OK\r\n")
+ client:send("Connection: close\r\n")
+ client:send(format("Content-Length: %s\r\n",#data))
+ client:send(format("Content-Type: %s\r\n",(suffix and mimetypes[suffix]) or "text/html"))
+ client:send("\r\n")
+ client:send(data)
+ client:send("\r\n")
+ else
+ errormessage(client,configuration,404)
+ end
+end
-local function message(str)
- return string.format("<h1>%s</h1>",str)
+--~ return os.date()
+
+--~ return { content = "crap" }
+
+--~ return function(configuration,filename)
+--~ return { content = filename }
+--~ end
+
+local loaded = { }
+
+function handlers.lua(client,configuration,filename,suffix,iscontent,hashed) -- filename will disappear, and become hashed.filename
+ local filename = file.join(configuration.scripts,filename)
+ if not file.is_qualified_path(filename) then
+ filename = file.join(configuration.root,filename)
+ end
+ -- todo: split url in components, see l-url; rather trivial
+ local result, keep = loaded[filename], false
+ if result then
+ logs.simple("reusing script: %s",filename)
+ else
+ logs.simple("locating script: %s",filename)
+ if lfs.isfile(filename) then
+ logs.simple("loading script: %s",filename)
+ result = loadfile(filename)
+ logs.simple("return type: %s",type(result))
+ if result and type(result) == "function" then
+ -- result() should return a table { [type=,] [length=,] content= }, function or string
+ result, keep = result()
+ if keep then
+ logs.simple("saving script: %s",type(result))
+ loaded[filename] = result
+ end
+ end
+ else
+ errormessage(client,configuration,404)
+ end
+ end
+ if result then
+ if type(result) == "function" then
+ result = result(configuration,filename,hashed) -- second argument will become query
+ end
+ if result and type(result) == "string" then
+ result = { content = result }
+ end
+ if result and type(result) == "table" then
+ if result.content then
+ local suffix = result.type or "text/html"
+ local action = handlers[suffix] or handlers.generic
+ action(client,configuration,result.content,suffix,true) -- content
+ elseif result.filename then
+ local suffix = file.extname(result.filename) or "text/html"
+ local action = handlers[suffix] or handlers.generic
+ action(client,configuration,result.filename,suffix,false) -- filename
+ else
+ errormessage(client,configuration,404)
+ end
+ else
+ errormessage(client,configuration,500)
+ end
+ else
+ errormessage(client,configuration,404)
+ end
end
+handlers.luc = handlers.lua
+handlers.html = handlers.htm
+
+local indices = { "index.htm", "index.html" }
+local portnumber = 31415 -- pi suits tex
+
function scripts.webserver.run(configuration)
- local server = assert(socket.bind("*", tonumber(configuration.port or 8080)))
- while true do
+ -- check configuration
+ configuration.port = tonumber(configuration.port or os.getenv("MTX_SERVER_PORT") or portnumber) or portnumber
+ if not configuration.root or not lfs.isdir(configuration.root) then
+ configuration.root = os.getenv("MTX_SERVER_ROOT") or "."
+ end
+ -- locate root and index file in tex tree
+ if not lfs.isdir(configuration.root) then
+ for _, name in ipairs(indices) do
+ local root = resolvers.resolve("path:" .. name) or ""
+ if root ~= "" then
+ configuration.root = root
+ configuration.index = configuration.index or name
+ break
+ end
+ end
+ end
+ configuration.root = dir.expand_name(configuration.root)
+ if not configuration.index then
+ for _, name in ipairs(indices) do
+ if lfs.isfile(file.join(configuration.root,name)) then
+ configuration.index = name -- we will prepend the rootpath later
+ break
+ end
+ end
+ configuration.index = configuration.index or "unknown"
+ end
+ if not configuration.scripts or configuration.scripts == "" then
+ configuration.scripts = dir.expand_name(file.join(configuration.root or ".",configuration.scripts or "."))
+ end
+ -- so far for checks
+ logs.simple("running at port: %s",configuration.port)
+ logs.simple("document root: %s",configuration.root or resolvers.ownpath)
+ logs.simple("main index file: %s",configuration.index)
+ logs.simple("scripts subpath: %s",configuration.scripts)
+ logs.simple("context services: http://localhost:%s/mtx-server-ctx-startup.lua",configuration.port)
+ local server = assert(socket.bind("*", configuration.port))
+ while true do -- no multiple clients
+ local start = os.clock()
local client = server:accept()
client:settimeout(configuration.timeout or 60)
local request, e = client:receive()
if e then
- client:send(message("404 Not Found"))
+ errormessage(client,configuration,404)
else
- -- GET /showcase.pdf HTTP/1.1
- local filename = request:match("GET (.+) HTTP/.*$") -- todo: more clever
- -- filename = filename:gsub("%%(%d%d)",function(c) return string.char(tonumber(c,16)) end)
- filename = socket.url.unescape(filename)
- if filename == nil or filename == "" then
- filename = configuration.index or "index.html"
- end
- -- todo chunked
- local fullname = file.join(configuration.root,filename)
- local data = io.loaddata(fullname)
- if data and data ~= "" then
- local result
- client:send("HTTP/1.1 200 OK\r\n")
- client:send("Connection: close\r\n")
- if filename:find(".pdf$") then -- todo: special handler
- client:send(string.format("Content-Length: %s\r\n",#data))
- client:send("Content-Type: application/pdf\r\n")
+ local from = client:getpeername()
+ logs.simple("request from: %s",tostring(from))
+ local fullurl = request:match("GET (.+) HTTP/.*$") -- todo: more clever
+ fullurl = socket.url.unescape(fullurl)
+ local hashed = url.hashed(fullurl)
+ local query = url.query(hashed.query)
+ local filename = hashed.path
+ if filename then
+ filename = socket.url.unescape(filename)
+ logs.simple("requested action: %s",filename)
+ if filename:find("%.%.") then
+ filename = nil -- invalid path
+ end
+ if filename == nil or filename == "" or filename == "/" then
+ filename = configuration.index
+ logs.simple("invalid filename, forcing: %s",filename)
+ end
+ local suffix = file.extname(filename)
+ local action = handlers[suffix] or handlers.generic
+ if action then
+ logs.simple("performing action: %s",filename)
+ action(client,configuration,filename,suffix,false,hashed) -- filename and no content
else
- client:send("Content-Type: text/html\r\n")
+ errormessage(client,configuration,404)
end
- client:send("\r\n")
- client:send(data)
- client:send("\r\n")
else
- client:send(message("404 Not Found"))
+ errormessage(client,configuration,404)
end
end
client:close()
+ logs.simple("time spent with client: %0.03f seconds",os.clock()-start)
end
end
-
-banner = banner .. " | webserver "
+logs.extendbanner("Simple Webserver 0.10")
messages.help = [[
--start start server
--port port to listen to
--root server root
+--scripts scripts sub path
--index index file
+--auto start on own path
]]
-if environment.argument("start") then
+if environment.argument("auto") then
+ local path = resolvers.find_file("mtx-server.lua") or "."
scripts.webserver.run {
- port = environment.argument("port") or "8080",
- root = environment.argument("root") or ".", -- "e:/websites/www.pragma-ade.com",
- index = environment.argument("index") or "index.html",
+ port = environment.argument("port"),
+ root = environment.argument("root") or file.dirname(path) or ".",
+ scripts = environment.argument("scripts") or file.dirname(path) or ".",
+ }
+elseif environment.argument("start") then
+ scripts.webserver.run {
+ port = environment.argument("port"),
+ root = environment.argument("root") or ".", -- "e:/websites/www.pragma-ade.com",
+ index = environment.argument("index"),
+ scripts = environment.argument("scripts"),
}
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
+
+
+-- mtxrun --script server --start => http://localhost:8080/mtx-server-ctx-help.lua
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-texworks.lua b/Master/texmf-dist/scripts/context/lua/mtx-texworks.lua
new file mode 100644
index 00000000000..f525d5336e2
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-texworks.lua
@@ -0,0 +1,96 @@
+if not modules then modules = { } end modules ['mtx-texworks'] = {
+ version = 1.002,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+scripts = scripts or { }
+scripts.texworks = scripts.texworks or { }
+
+local texworkspaths = {
+ "completion",
+ "configuration",
+ "dictionaries",
+ "translations",
+ "scripts",
+ "templates",
+ "TUG"
+}
+
+local texworkssignal = "texworks-context.rme"
+local texworkininame = "TeXworks.ini"
+
+function scripts.texworks.start(indeed)
+ local is_mswin = os.platform == "windows"
+ local workname = (is_mswin and "texworks.exe") or "TeXworks"
+ local fullname = nil
+ local binpaths = file.split_path(os.getenv("PATH")) or file.split_path(os.getenv("path"))
+ local datapath = resolvers.find_file(texworkssignal,"other text files") or ""
+ if datapath ~= "" then
+ datapath = file.dirname(datapath) -- data
+ if datapath == "" then
+ datapath = resolvers.ownpath
+ end
+ else
+ datapath = resolvers.find_file(texworkininame,"other text files") or ""
+ if datapath == "" then
+ datapath = resolvers.find_file(string.lower(texworkininame),"other text files") or ""
+ end
+ if datapath ~= "" and lfs.isfile(datapath) then
+ datapath = file.dirname(datapath) -- TUG
+ datapath = file.dirname(datapath) -- data
+ if datapath == "" then
+ datapath = resolvers.ownpath
+ end
+ end
+ end
+ if datapath == "" then
+ logs.simple("invalid datapath, maybe you need to regenerate the file database")
+ return false
+ end
+ if not binpaths or #binpaths == 0 then
+ logs.simple("invalid binpath")
+ return false
+ end
+ for i=1,#binpaths do
+ local p = file.join(binpaths[i],workname)
+ if lfs.isfile(p) then
+ fullname = p
+ break
+ end
+ end
+ if not fullname then
+ logs.simple("unable to locate %s",workname)
+ return false
+ end
+ for _, subpath in ipairs(texworkspaths) do
+ dir.makedirs(file.join(datapath,subpath))
+ end
+ os.setenv("TW_INIPATH",datapath)
+ os.setenv("TW_LIBPATH",datapath)
+ if not indeed or environment.argument("verbose") then
+ logs.simple("data path: %s", datapath)
+ logs.simple("full name: %s", fullname)
+ end
+ if indeed then
+ os.launch(fullname)
+ end
+end
+
+
+logs.extendbanner("TeXworks startup script 1.0",true)
+
+messages.help = [[
+--start [--verbose] start texworks
+--test report what will happen
+]]
+
+if environment.argument("start") then
+ scripts.texworks.start(true)
+elseif environment.argument("test") then
+ scripts.texworks.start()
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-timing.lua b/Master/texmf-dist/scripts/context/lua/mtx-timing.lua
new file mode 100644
index 00000000000..1dcb9aa0e0d
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-timing.lua
@@ -0,0 +1,201 @@
+if not modules then modules = { } end modules ['mtx-timing'] = {
+ version = 1.002,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format, gsub, concat = string.format, string.gsub, table.concat
+
+dofile(resolvers.find_file("trac-tim.lua","tex"))
+dofile(resolvers.find_file("trac-lmx.lua","tex"))
+
+local meta = [[
+ beginfig(%s) ;
+ begingroup ;
+ save p, q, b, h, w ;
+ path p, q, b ; numeric h, w ;
+ linecap := butt ;
+ h := 100 ;
+ w := 800pt ;
+ p := %s ;
+ q := %s ;
+ p := p shifted -llcorner p ;
+ q := q shifted -llcorner q ;
+ q := q xstretched w ;
+ p := p xstretched w ;
+ b := boundingbox (llcorner p -- llcorner p shifted (w,h)) ;
+ draw b withcolor white withpen pencircle scaled 4pt ;
+ draw p withcolor red withpen pencircle scaled 4pt ;
+ draw q withcolor blue withpen pencircle scaled 2pt ;
+ endgroup ;
+ endfig ;
+]]
+
+local html_graphic = [[
+ <h1><a name='graphic-%s'>%s (red) %s (blue)</a></h1>
+ <table>
+ <tr>
+ <td>%s</td>
+ <td valign='top'>
+ &nbsp;&nbsp;min: %s<br/>
+ &nbsp;&nbsp;max: %s<br/>
+ &nbsp;&nbsp;pages: %s<br/>
+ &nbsp;&nbsp;average: %s<br/>
+ </td>
+ </tr>
+ </table>
+ <br/>
+]]
+
+local html_menu = [[
+ <a href='#graphic-%s'>%s</a>
+]]
+
+local directrun = true
+
+function goodies.progress.make_svg(filename,other)
+ local metadata, menudata, c = { }, { }, 0
+ metadata[#metadata+1] = 'outputformat := "svg" ;'
+ for _, kind in pairs { "parameters", "nodes" } do
+ local mdk = { }
+ menudata[kind] = mdk
+ for n, name in pairs(goodies.progress[kind](filename)) do
+ local first = goodies.progress.path(filename,name)
+ local second = goodies.progress.path(filename,other)
+ c = c + 1
+ metadata[#metadata+1] = format(meta,c,first,second)
+ mdk[#mdk+1] = { name, c }
+ end
+ end
+ metadata[#metadata+1] = "end ."
+ metadata = concat(metadata,"\n\n")
+ if directrun then
+ dofile(resolvers.find_file("mlib-run.lua","tex"))
+ commands = commands or { }
+ commands.writestatus = logs.report
+ local result = metapost.directrun("metafun","timing data","svg",true,metadata)
+ return menudata, result
+ else
+ local mpname = file.replacesuffix(filename,"mp")
+ io.savedata(mpname,metadata)
+ os.execute(format("mpost --progname=context --mem=metafun.mem %s",mpname))
+ os.remove(mpname)
+ os.remove(file.removesuffix(filename).."-mpgraph.mpo") -- brr
+ os.remove(file.removesuffix(filename)..".log") -- brr
+ return menudata
+ end
+end
+
+function goodies.progress.makehtml(filename,other,menudata,metadata)
+ local graphics = { }
+ local result = { graphics = graphics }
+ for _, kind in pairs { "parameters", "nodes" } do
+ local md = menudata[kind]
+ local menu = { }
+ result[kind] = menu
+ for k, v in ipairs(md) do
+ local name, number = v[1], v[2]
+ local min = goodies.progress.bot(filename,name)
+ local max = goodies.progress.top(filename,name)
+ local pages = goodies.progress.pages(filename)
+ local average = math.round(max/pages)
+ if directrun then
+ local data = metadata[number]
+ menu[#menu+1] = format(html_menu,name,name)
+ graphics[#graphics+1] = format(html_graphic,name,name,other,data,min,max,pages,average)
+ else
+ local mpname = file.replacesuffix(filename,number)
+ local data = io.loaddata(mpname) or ""
+ -- data = gsub(data,"<!%-%-(.-)%-%->[\n\r]*","")
+ data = gsub(data,"<%?xml.->","")
+ menu[#menu+1] = format(html_menu,name,name)
+ graphics[#graphics+1] = format(html_graphic,name,name,other,data,min,max,pages,average)
+ os.remove(mpname)
+ end
+ end
+ end
+ return result
+end
+
+function goodies.progress.valid_file(name)
+ return name and name ~= "" and lfs.isfile(name .. "-luatex-progress.lut")
+end
+
+function goodies.progress.make_lmx_page(name,launch,remove)
+ local filename = name .. "-luatex-progress"
+ local other = "elapsed_time"
+ local template = 'context-timing.lmx'
+
+ lmx.variables['color-background-green'] = '#4F6F6F'
+ lmx.variables['color-background-blue'] = '#6F6F8F'
+ lmx.variables['color-background-yellow'] = '#8F8F6F'
+ lmx.variables['color-background-purple'] = '#8F6F8F'
+
+ lmx.variables['color-background-body'] = '#808080'
+ lmx.variables['color-background-main'] = '#3F3F3F'
+ lmx.variables['color-background-one'] = lmx.variables['color-background-green']
+ lmx.variables['color-background-two'] = lmx.variables['color-background-blue']
+
+ lmx.variables['title-default'] = 'ConTeXt Timing Information'
+ lmx.variables['title'] = lmx.variables['title-default']
+
+ lmx.htmfile = function(name) return name .. "-timing.xhtml" end
+ lmx.lmxfile = function(name) return resolvers.find_file(name,'tex') end
+
+ lmx.set('title', format('ConTeXt Timing Information: %s',file.basename(name)))
+ lmx.set('color-background-one', lmx.get('color-background-green'))
+ lmx.set('color-background-two', lmx.get('color-background-blue'))
+
+ goodies.progress.convert(filename)
+
+ local menudata, metadata = goodies.progress.make_svg(filename,other)
+ local htmldata = goodies.progress.makehtml(filename,other,menudata,metadata)
+
+ lmx.set('parametersmenu', concat(htmldata.parameters, "&nbsp;&nbsp;"))
+ lmx.set('nodesmenu', concat(htmldata.nodes, "&nbsp;&nbsp;"))
+ lmx.set('graphics', concat(htmldata.graphics, "\n\n"))
+
+ if launch then
+ local htmfile = lmx.show(template)
+ if remove then
+ os.sleep(1) -- give time to launch
+ os.remove(htmfile)
+ end
+ else
+ lmx.make(template)
+ end
+
+ lmx.restore()
+end
+
+scripts = scripts or { }
+scripts.timings = scripts.timings or { }
+
+function scripts.timings.xhtml(filename)
+ if filename == "" then
+ logs.simple("provide filename")
+ elseif not goodies.progress.valid_file(filename) then
+ logs.simple("first run context again with the --timing option")
+ else
+ local basename = file.removesuffix(filename)
+ local launch = environment.argument("launch")
+ local remove = environment.argument("remove")
+ goodies.progress.make_lmx_page(basename,launch,remove)
+ end
+end
+
+logs.extendbanner("ConTeXt Timing Tools 0.1",true)
+
+messages.help = [[
+--xhtml make xhtml file
+--launch launch after conversion
+--remove remove after launching
+]]
+
+if environment.argument("xhtml") then
+ scripts.timings.xhtml(environment.files[1] or "")
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-tools.lua b/Master/texmf-dist/scripts/context/lua/mtx-tools.lua
new file mode 100644
index 00000000000..87fd51dc60d
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-tools.lua
@@ -0,0 +1,57 @@
+if not modules then modules = { } end modules ['mtx-tools'] = {
+ version = 1.002,
+ comment = "companion to mtxrun.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- data tables by Thomas A. Schmitz
+
+local find, gsub = string.find, string.gsub
+
+scripts = scripts or { }
+scripts.tools = scripts.tools or { }
+
+local bomb_1, bomb_2 = "^\254\255", "^\239\187\191"
+
+function scripts.tools.disarmutfbomb()
+ local force, done = environment.argument("force"), false
+ for _, name in ipairs(environment.files) do
+ if lfs.isfile(name) then
+ local data = io.loaddata(name)
+ if not data then
+ -- just skip
+ elseif find(data,bomb_1) then
+ logs.simple("file '%s' has a 2 character utf bomb",name)
+ if force then
+ io.savedata(name,(gsub(data,bomb_1,"")))
+ end
+ done = true
+ elseif find(data,bomb_2) then
+ logs.simple("file '%s' has a 3 character utf bomb",name)
+ if force then
+ io.savedata(name,(gsub(data,bomb_2,"")))
+ end
+ done = true
+ else
+ -- logs.simple("file '%s' has no utf bomb",name)
+ end
+ end
+ end
+ if done and not force then
+ logs.simple("use --force to do a real disarming")
+ end
+end
+
+logs.extendbanner("All Kind Of Tools 1.0",true)
+
+messages.help = [[
+--disarmutfbomb remove utf bomb if present
+]]
+
+if environment.argument("disarmutfbomb") then
+ scripts.tools.disarmutfbomb()
+else
+ logs.help(messages.help)
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-unzip.lua b/Master/texmf-dist/scripts/context/lua/mtx-unzip.lua
new file mode 100644
index 00000000000..f990f421032
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/lua/mtx-unzip.lua
@@ -0,0 +1,101 @@
+-- maybe --pattern
+
+logs.extendbanner("Simple Unzipper 0.10")
+
+messages.help = [[
+--list list files in archive
+--junk flatten unzipped directory structure
+--extract extract files
+]]
+
+scripts = scripts or { }
+scripts.unzipper = scripts.unzipper or { }
+
+function scripts.unzipper.help()
+ logs.help(messages.help)
+end
+
+function scripts.unzipper.opened()
+ local filename = environment.files[1]
+ if filename and filename ~= "" then
+ filename = file.addsuffix(filename,'zip')
+ local zipfile = zip.open(filename)
+ if zipfile then
+ return zipfile
+ end
+ end
+ logs.report("unzip", "no zip file: " .. filename)
+ return false
+end
+
+function scripts.unzipper.list()
+ local zipfile = scripts.unzipper.opened()
+ if zipfile then
+ local n = 0
+ for k in zipfile:files() do
+ if #k.filename > n then n = #k.filename end
+ end
+ local files, paths, compressed, uncompressed = 0, 0, 0, 0
+ for k in zipfile:files() do
+ if k.filename:find("/$") then
+ paths = paths + 1
+ print(string.format("%s", k.filename:rpadd(n," ")))
+ else
+ files = files + 1
+ local cs, us = k.compressed_size, k.uncompressed_size
+ if cs > compressed then
+ compressed = cs
+ end
+ if us > uncompressed then
+ uncompressed = us
+ end
+ print(string.format("%s % 9i % 9i", k.filename:rpadd(n," "),cs,us))
+ end
+ end
+ print(string.format("\n%s % 9i % 9i", (files .. " files, " .. paths .. " directories"):rpadd(n," "),compressed,uncompressed))
+ end
+end
+
+function zip.loaddata(zipfile,filename)
+ local f = zipfile:open(filename)
+ if f then
+ local data = f:read("*a")
+ f:close()
+ return data
+ end
+ return nil
+end
+
+function scripts.unzipper.extract()
+ local zipfile = scripts.unzipper.opened()
+ if zipfile then
+ local junk = environment.arguments["j"] or environment.arguments["junk"]
+ for k in zipfile:files() do
+ local filename = k.filename
+ if filename:find("/$") then
+ if not junk then
+ lfs.mkdir(filename)
+ end
+ else
+ local data = zip.loaddata(zipfile,filename)
+ if data then
+ if junk then
+ filename = file.basename(filename)
+ end
+ io.savedata(filename,data)
+ print(filename)
+ end
+ end
+ end
+ end
+end
+
+if environment.arguments["h"] or environment.arguments["help"] then
+ scripts.unzipper.help()
+elseif environment.arguments["l"] or environment.arguments["list"] then
+ scripts.unzipper.list(zipfile)
+elseif environment.files[1] then -- implicit --extract
+ scripts.unzipper.extract(zipfile)
+else
+ scripts.unzipper.help()
+end
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-update.lua b/Master/texmf-dist/scripts/context/lua/mtx-update.lua
index 581774f1ea0..bc6ca402639 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-update.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-update.lua
@@ -1,5 +1,5 @@
if not modules then modules = { } end modules ['mtx-update'] = {
- version = 1.001,
+ version = 1.002,
comment = "companion to mtxrun.lua",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
@@ -11,7 +11,7 @@ if not modules then modules = { } end modules ['mtx-update'] = {
-- Together with Arthur Reutenauer she made sure that it worked well on all
-- platforms that matter.
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
+local format, concat, gmatch = string.format, table.concat, string.gmatch
scripts = scripts or { }
scripts.update = scripts.update or { }
@@ -19,9 +19,12 @@ scripts.update = scripts.update or { }
minimals = minimals or { }
minimals.config = minimals.config or { }
+-- this is needed under windows
+-- else rsync fails to set the right chmod flags to files
+
os.setenv("CYGWIN","nontsec")
-scripts.update.formats = {
+scripts.update.texformats = {
"cont-en",
"cont-nl",
"cont-cz",
@@ -30,109 +33,114 @@ scripts.update.formats = {
"cont-it",
"cont-ro",
"cont-uk",
- "metafun",
+ "cont-pe",
+ "cont-xp",
"mptopdf",
"plain"
}
+scripts.update.mpformats = {
+ "metafun",
+ "mpost",
+}
+
+-- experimental is not functional at the moment
+
scripts.update.repositories = {
"current",
"experimental"
}
+-- more options than just these two are available (no idea why this is here)
+
scripts.update.versions = {
"current",
"latest"
}
+-- list of basic folders that are needed to make a functional distribution
+
+scripts.update.base = {
+ { "base/tex/", "texmf" },
+ { "base/metapost/", "texmf" },
+ { "fonts/common/", "texmf" },
+ { "fonts/other/", "texmf" }, -- not *really* needed, but helpful
+ { "context/<version>/", "texmf-context" },
+ { "context/img/", "texmf-context" },
+ { "misc/setuptex/", "." },
+ { "misc/web2c", "texmf" },
+ { "bin/common/<platform>/", "texmf-<platform>" },
+ { "bin/context/<platform>/", "texmf-<platform>" },
+ { "bin/metapost/<platform>/", "texmf-<platform>" },
+ { "bin/man/", "texmf-<platform>" },
+}
+
+-- binaries and font-related files
+-- for pdftex we don't need OpenType fonts, for LuaTeX/XeTeX we don't need TFM files
+
scripts.update.engines = {
["luatex"] = {
- { "base/tex/", "texmf" },
- { "base/metapost/", "texmf" },
{ "fonts/new/", "texmf" },
- { "fonts/common/", "texmf" },
- { "fonts/other/", "texmf" },
- { "context/<version>/", "texmf-context" },
- { "context/img/", "texmf-context" },
- { "context/config/", "texmf-context" },
- { "misc/setuptex/", "." },
- { "misc/web2c", "texmf" },
- { "bin/common/<platform>/", "texmf-<platform>" },
- { "bin/context/<platform>/", "texmf-<platform>" },
- { "bin/metapost/<platform>/", "texmf-<platform>" },
{ "bin/luatex/<platform>/", "texmf-<platform>" },
- { "bin/man/", "texmf-<platform>" }
},
["xetex"] = {
- { "base/tex/", "texmf" },
- { "base/metapost/", "texmf" },
{ "base/xetex/", "texmf" },
{ "fonts/new/", "texmf" },
- { "fonts/common/", "texmf" },
- { "fonts/other/", "texmf" },
- { "context/<version>/", "texmf-context" },
- { "context/img/", "texmf-context" },
- { "context/config/", "texmf-context" },
- { "misc/setuptex/", "." },
- { "misc/web2c", "texmf" },
- { "bin/common/<platform>/", "texmf-<platform>" },
- { "bin/context/<platform>/", "texmf-<platform>" },
- { "bin/metapost/<platform>/", "texmf-<platform>" },
{ "bin/xetex/<platform>/", "texmf-<platform>" },
- { "bin/man/", "texmf-<platform>" }
},
["pdftex"] = {
- { "base/tex/", "texmf" },
- { "base/metapost/", "texmf" },
{ "fonts/old/", "texmf" },
- { "fonts/common/", "texmf" },
- { "fonts/other/", "texmf" },
- { "context/<version>/", "texmf-context" },
- { "context/img/", "texmf-context" },
- { "context/config/", "texmf-context" },
- { "misc/setuptex/", "." },
- { "misc/web2c", "texmf" },
- { "bin/common/<platform>/", "texmf-<platform>" },
- { "bin/context/<platform>/", "texmf-<platform>" },
- { "bin/metapost/<platform>/", "texmf-<platform>" },
{ "bin/pdftex/<platform>/", "texmf-<platform>" },
- { "bin/man/", "texmf-<platform>" }
},
["all"] = {
- { "base/tex/", "texmf" },
- { "base/metapost/", "texmf" },
- { "base/xetex/", "texmf" },
- { "fonts/old/", "texmf" },
{ "fonts/new/", "texmf" },
- { "fonts/common/", "texmf" },
- { "fonts/other/", "texmf" },
- { "context/<version>/", "texmf-context" },
- { "context/img/", "texmf-context" },
- { "context/config/", "texmf-context" },
- { "misc/setuptex/", "." },
- { "misc/web2c", "texmf" },
- { "bin/common/<platform>/", "texmf-<platform>" },
- { "bin/context/<platform>/", "texmf-<platform>" },
- { "bin/metapost/<platform>/", "texmf-<platform>" },
+ { "fonts/old/", "texmf" },
+ { "base/xetex/", "texmf" },
{ "bin/luatex/<platform>/", "texmf-<platform>" },
{ "bin/xetex/<platform>/", "texmf-<platform>" },
{ "bin/pdftex/<platform>/", "texmf-<platform>" },
- { "bin/man/", "texmf-<platform>" }
+ },
+}
+
+scripts.update.goodies = {
+ ["scite"] = {
+ { "bin/<platform>/scite/", "texmf-<platform>" },
+ },
+ ["texworks"] = {
+ { "bin/<platform>/texworks/", "texmf-<platform>" },
},
}
scripts.update.platforms = {
- ["mswin"] = "mswin",
- ["windows"] = "mswin",
- ["win32"] = "mswin",
- ["win"] = "mswin",
- ["linux"] = "linux",
- ["freebsd"] = "freebsd",
- ["linux-32"] = "linux",
- ["linux-64"] = "linux-64",
- ["osx"] = "osx-intel",
- ["osx-intel"] = "osx-intel",
- ["osx-ppc"] = "osx-ppc",
+ ["mswin"] = "mswin",
+ ["windows"] = "mswin",
+ ["win32"] = "mswin",
+ ["win"] = "mswin",
+ ["linux"] = "linux",
+ ["freebsd"] = "freebsd",
+ ["freebsd-amd64"] = "freebsd-amd64",
+ ["linux-32"] = "linux",
+ ["linux-64"] = "linux-64",
+ ["linux32"] = "linux",
+ ["linux64"] = "linux-64",
+ ["linux-ppc"] = "linux-ppc",
+ ["ppc"] = "linux-ppc",
+ ["osx"] = "osx-intel",
+ ["macosx"] = "osx-intel",
+ ["osx-intel"] = "osx-intel",
+ ["osx-ppc"] = "osx-ppc",
+ ["osx-powerpc"] = "osx-ppc",
+ ["osxintel"] = "osx-intel",
+ ["osxppc"] = "osx-ppc",
+ ["osxpowerpc"] = "osx-ppc",
+ ["solaris-intel"] = "solaris-intel",
+ ["solaris-sparc"] = "solaris-sparc",
+ ["solaris"] = "solaris-sparc",
+}
+
+-- the list is filled up later (when we know what modules to download)
+
+scripts.update.modules = {
}
function scripts.update.run(str)
@@ -146,7 +154,7 @@ function scripts.update.run(str)
end
function scripts.update.fullpath(path)
- if input.aux.rootbased_path(path) then
+ if file.is_rootbased_path(path) then
return path
else
return lfs.currentdir() .. "/" .. path
@@ -154,46 +162,147 @@ function scripts.update.fullpath(path)
end
function scripts.update.synchronize()
+
logs.report("update","start")
+
local texroot = scripts.update.fullpath(states.get("paths.root"))
- local engines = states.get('engines')
- local platforms = states.get('platforms')
- local repositories = states.get('repositories')
- local bin = states.get("rsync.program")
- local url = states.get("rsync.server")
- local version = states.get("context.version")
+ local engines = states.get('engines') or { }
+ local platforms = states.get('platforms') or { }
+ local repositories = states.get('repositories') -- minimals
+ local bin = states.get("rsync.program") -- rsync
+ local url = states.get("rsync.server") -- contextgarden.net
+ local version = states.get("context.version") -- current (or beta)
+ local extras = states.get("extras") -- extras (like modules)
+ local goodies = states.get("goodies") -- goodies (like editors)
local force = environment.argument("force")
+
+ bin = string.gsub(bin,"\\","/")
+
if not url:find("::$") then url = url .. "::" end
local ok = lfs.attributes(texroot,"mode") == "directory"
if not ok and force then
dir.mkdirs(texroot)
ok = lfs.attributes(texroot,"mode") == "directory"
end
+
+ if force then
+ dir.mkdirs(format("%s/%s", texroot, "texmf-cache"))
+ dir.mkdirs(format("%s/%s", texroot, "texmf-local"))
+ end
+
if ok or not force then
- if force then
- dir.mkdirs(string.format("%s/%s", texroot, "texmf-cache"))
+
+ local fetched, individual, osplatform = { }, { }, os.currentplatform()
+
+ -- takes a collection as argument and returns a list of folders
+
+ local function collection_to_list_of_folders(collection, platform)
+ local archives = {}
+ for _, c in ipairs(collection) do
+ local archive = c[1]
+ archive = archive:gsub("<platform>", platform)
+ archive = archive:gsub("<version>", version)
+ archives[#archives+1] = archive
+ end
+ return archives
end
- local fetched, individual = { }, { }
- for engine, _ in pairs(engines) do
- local collections = scripts.update.engines[engine]
- if collections then
- for _, collection in ipairs(collections) do
- for platform, _ in pairs(platforms) do
- platform = scripts.update.platforms[platform]
- if platform then
- local archive = collection[1]:gsub("<platform>", platform)
- local destination = string.format("%s/%s", texroot, collection[2]:gsub("<platform>", platform))
- destination = destination:gsub("\\","/")
- archive = archive:gsub("<version>",version)
- if platform == "windows" or platform == "mswin" then
- destination = destination:gsub("([a-zA-Z]):/", "/cygdrive/%1/")
- end
- individual[#individual+1] = { archive, destination }
+
+ -- takes a list of folders as argument and returns a string for rsync
+ -- sample input:
+ -- {'bin/common', 'bin/context'}
+ -- output:
+ -- 'minimals/current/bin/common minimals/current/bin/context'
+
+ local function list_of_folders_to_rsync_string(list_of_folders)
+ local repository = 'current'
+ local prefix = format("%s/%s/", states.get('rsync.module'), repository) -- minimals/current/
+
+ return prefix .. concat(list_of_folders, format(" %s", prefix))
+ end
+
+ -- example of usage: print(list_of_folders_to_rsync_string(collection_to_list_of_folders(scripts.update.base, os.currentplatform)))
+
+ -- rename function and add some more functionality:
+ -- * recursive/non-recursive (default: non-recursive)
+ -- * filter folders or regular files only (default: no filter)
+ -- * grep for size of included files (with --stats switch)
+
+ local function get_list_of_files_from_rsync(list_of_folders)
+ -- temporary file to store the output of rsync (could be a more random name; watch for overwrites)
+ local temp_file = "rsync.tmp.txt"
+ -- a set of folders
+ local folders = {}
+ local command = format("%s %s'%s' > %s", bin, url, list_of_folders_to_rsync_string(list_of_folders), temp_file)
+ os.execute(command)
+ -- read output of rsync
+ local data = io.loaddata(temp_file) or ""
+ -- for every line extract the filename
+ for chmod, s in data:gmatch("([d%-][rwx%-]+).-(%S+)[\n\r]") do
+ -- skip "current" folder
+ if s ~= '.' and chmod:len() == 10 then
+ folders[#folders+1] = s
+ end
+ end
+ -- delete the file to which we have put output of rsync
+ os.remove(temp_file)
+ return folders
+ end
+
+ -- rsync://contextgarden.net/minimals/current/modules/
+
+ if extras and type(extras) == "table" then
+ -- fetch the list of available modules from rsync server
+ local available_modules = get_list_of_files_from_rsync({"modules/"})
+ -- hash of requested modules
+ -- local h = table.tohash(extras:split(","))
+ for _, s in ipairs(available_modules) do
+ -- if extras == "all" or h[s] then
+ if extras.all or extras[s] then
+ scripts.update.modules[#scripts.update.modules+1] = { format("modules/%s/",s), "texmf-context" }
+ end
+ end
+ -- TODO: check if every module from the list has been added and issue warning otherwise
+ -- one idea to do it: remove every value from h once added and then check if anything is left in h
+ end
+
+ local function add_collection(collection,platform)
+ if collection and platform then
+ platform = scripts.update.platforms[platform]
+ if platform then
+ for _, c in ipairs(collection) do
+ local archive = c[1]:gsub("<platform>", platform)
+ local destination = format("%s/%s", texroot, c[2]:gsub("<platform>", platform))
+ destination = destination:gsub("\\","/")
+ archive = archive:gsub("<version>",version)
+ if osplatform == "windows" or osplatform == "mswin" then
+ destination = destination:gsub("([a-zA-Z]):/", "/cygdrive/%1/")
end
+ individual[#individual+1] = { archive, destination }
end
end
end
end
+
+ for platform, _ in pairs(platforms) do
+ add_collection(scripts.update.base,platform)
+ end
+ for platform, _ in pairs(platforms) do
+ add_collection(scripts.update.modules,platform)
+ end
+ for engine, _ in pairs(engines) do
+ for platform, _ in pairs(platforms) do
+ add_collection(scripts.update.engines[engine],platform)
+ end
+ end
+
+ if goodies and type(goodies) == "table" then
+ for goodie, _ in pairs(goodies) do
+ for platform, _ in pairs(platforms) do
+ add_collection(scripts.update.goodies[goodie],platform)
+ end
+ end
+ end
+
local combined = { }
for _, repository in ipairs(scripts.update.repositories) do
if repositories[repository] then
@@ -204,11 +313,11 @@ function scripts.update.synchronize()
cd = { }
combined[destination] = cd
end
- cd[#cd+1] = string.format("%s/%s/%s",states.get('rsync.module'),repository,archive)
+ cd[#cd+1] = format("%s/%s/%s",states.get('rsync.module'),repository,archive)
end
end
end
- if input.verbose then
+ if logs.verbose then
for k, v in pairs(combined) do
logs.report("update", k)
for k,v in ipairs(v) do
@@ -217,24 +326,53 @@ function scripts.update.synchronize()
end
end
for destination, archive in pairs(combined) do
- local archives, command = table.concat(archive," "), ""
- local normalflags, deleteflags = states.get("rsync.flags.normal"), states.get("rsync.flags.delete")
- if true then -- environment.argument("keep") or destination:find("%.$") then
- command = string.format("%s %s %s'%s' '%s'", bin, normalflags, url, archives, destination)
- else
- command = string.format("%s %s %s %s'%s' '%s'", bin, normalflags, deleteflags, url, archives, destination)
+ local archives, command = concat(archive," "), ""
+ -- local normalflags, deleteflags = states.get("rsync.flags.normal"), states.get("rsync.flags.delete")
+ -- if environment.argument("keep") or destination:find("%.$") then
+ -- command = format("%s %s %s'%s' '%s'", bin, normalflags, url, archives, destination)
+ -- else
+ -- command = format("%s %s %s %s'%s' '%s'", bin, normalflags, deleteflags, url, archives, destination)
+ -- end
+ local normalflags, deleteflags = states.get("rsync.flags.normal"), ""
+ if (destination:find("texmf$") or destination:find("texmf%-context$")) and (not environment.argument("keep")) then
+ deleteflags = states.get("rsync.flags.delete")
end
- logs.report("mtx update", string.format("running command: %s",command))
+ command = format("%s %s %s %s'%s' '%s'", bin, normalflags, deleteflags, url, archives, destination)
+ logs.report("mtx update", format("running command: %s",command))
if not fetched[command] then
scripts.update.run(command)
fetched[command] = command
end
end
+
+ local function update_script(script, platform)
+ local bin = bin:gsub("\\","/")
+ local texroot = texroot:gsub("\\","/")
+ platform = scripts.update.platforms[platform]
+ if platform then
+ local command
+ if platform == 'mswin' then
+ bin = bin:gsub("([a-zA-Z]):/", "/cygdrive/%1/")
+ texroot = texroot:gsub("([a-zA-Z]):/", "/cygdrive/%1/")
+ command = string.format("%s -t %s/texmf-context/scripts/context/lua/%s.lua %s/texmf-mswin/bin/", bin, texroot, script, texroot)
+ else
+ command = string.format("%s -tgo --chmod=a+x %s/texmf-context/scripts/context/lua/%s.lua %s/texmf-%s/bin/%s", bin, texroot, script, texroot, platform, script)
+ end
+ logs.report("mtx update", format("updating %s for %s: %s", script, platform, command))
+ scripts.update.run(command)
+ end
+ end
+
+ for platform, _ in pairs(platforms) do
+ update_script('luatools',platform)
+ update_script('mtxrun',platform)
+ end
+
else
- logs.report("mtx update", string.format("no valid texroot: %s",texroot))
+ logs.report("mtx update", format("no valid texroot: %s",texroot))
end
if not force then
- logs.report("update", "use --force to really update")
+ logs.report("update", "use --force to really update files")
end
logs.report("update","done")
end
@@ -247,42 +385,72 @@ function table.fromhash(t)
return h
end
-
+-- make the ConTeXt formats
function scripts.update.make()
+
logs.report("make","start")
+
local force = environment.argument("force")
local texroot = scripts.update.fullpath(states.get("paths.root"))
local engines = states.get('engines')
+ local goodies = states.get('goodies')
local platforms = states.get('platforms')
local formats = states.get('formats')
- input.load_tree(texroot)
+
+ resolvers.load_tree(texroot)
+ -- update filename database for pdftex/xetex
scripts.update.run("mktexlsr")
+ -- update filename database for luatex
scripts.update.run("luatools --generate")
- local formatlist = table.concat(table.fromhash(formats), " ")
+ local askedformats = formats
+ local texformats = table.tohash(scripts.update.texformats)
+ local mpformats = table.tohash(scripts.update.mpformats)
+ for k,v in pairs(texformats) do
+ if not askedformats[k] then
+ texformats[k] = nil
+ end
+ end
+ for k,v in pairs(mpformats) do
+ if not askedformats[k] then
+ mpformats[k] = nil
+ end
+ end
+ local formatlist = concat(table.fromhash(texformats), " ")
if formatlist ~= "" then
for engine in pairs(engines) do
- -- todo: just handle make here or in mtxrun --script context --make
---~ os.execute("set")
- scripts.update.run(string.format("texexec --make --all --fast --%s %s",engine,formatlist))
+ if engine == "luatex" then
+ scripts.update.run(format("context --make")) -- maybe also formatlist
+ else
+ -- todo: just handle make here or in mtxrun --script context --make
+ scripts.update.run(format("texexec --make --all --fast --%s %s",engine,formatlist))
+ end
end
end
+ local formatlist = concat(table.fromhash(mpformats), " ")
+ if formatlist ~= "" then
+ scripts.update.run(format("texexec --make --all --fast %s",formatlist))
+ end
if not force then
- logs.report("make", "use --force to really make")
+ logs.report("make", "use --force to really make formats")
end
+ scripts.update.run("mktexlsr")
+ scripts.update.run("luatools --generate")
logs.report("make","done")
end
-banner = banner .. " | download tools "
+logs.extendbanner("Download Tools 0.20",true)
messages.help = [[
---platform=string platform (windows, linux, linux-64, osx-intel, osx-ppc)
+--platform=string platform (windows, linux, linux-64, osx-intel, osx-ppc, linux-ppc)
--server=string repository url (rsync://contextgarden.net)
--module=string repository url (minimals)
--repository=string specify version (current, experimental)
--context=string specify version (current, latest, yyyy.mm.dd)
--rsync=string rsync binary (rsync)
---texroot installation directory (not guessed for the moment)
---engine tex engine (luatex, pdftex, xetex)
+--texroot=string installation directory (not guessed for the moment)
+--engine=string tex engine (luatex, pdftex, xetex)
+--extras=string extra modules (can be list or 'all')
+--goodies=string extra binaries (like scite and texworks)
--force instead of a dryrun, do the real thing
--update update minimal tree
--make also make formats and generate file databases
@@ -290,8 +458,6 @@ messages.help = [[
--state update tree using saved state
]]
-input.verbose = true
-
scripts.savestate = true
if scripts.savestate then
@@ -300,7 +466,7 @@ if scripts.savestate then
-- tag, value, default, persistent
- input.starttiming(states)
+ statistics.starttiming(states)
states.set("info.version",0.1) -- ok
states.set("info.count",(states.get("info.count") or 0) + 1,1,false) -- ok
@@ -318,11 +484,11 @@ if scripts.savestate then
states.set("context.version", environment.argument("context"), "current", true) -- ok
local valid = table.tohash(scripts.update.repositories)
- for r in string.gmatch(environment.argument("repository") or "current","([^, ]+)") do
+ for r in gmatch(environment.argument("repository") or "current","([^, ]+)") do
if valid[r] then states.set("repositories." .. r, true) end
end
local valid = scripts.update.engines
- for r in string.gmatch(environment.argument("engine") or "all","([^, ]+)") do
+ for r in gmatch(environment.argument("engine") or "all","([^, ]+)") do
if r == "all" then
for k, v in pairs(valid) do
if k ~= "all" then
@@ -334,12 +500,16 @@ if scripts.savestate then
end
end
local valid = scripts.update.platforms
- for r in string.gmatch(environment.argument("platform") or os.currentplatform(),"([^, ]+)") do
+ for r in gmatch(environment.argument("platform") or os.currentplatform(),"([^, ]+)") do
if valid[r] then states.set("platforms." .. r, true) end
end
- local valid = table.tohash(scripts.update.formats)
- for r in string.gmatch(environment.argument("formats") or "","([^, ]+)") do
+ local valid = table.tohash(scripts.update.texformats)
+ for r in gmatch(environment.argument("formats") or "","([^, ]+)") do
+ if valid[r] then states.set("formats." .. r, true) end
+ end
+ local valid = table.tohash(scripts.update.mpformats)
+ for r in gmatch(environment.argument("formats") or "","([^, ]+)") do
if valid[r] then states.set("formats." .. r, true) end
end
@@ -347,7 +517,12 @@ if scripts.savestate then
states.set("formats.cont-nl", true)
states.set("formats.metafun", true)
- -- modules
+ for r in gmatch(environment.argument("extras") or "","([^, ]+)") do
+ states.set("extras." .. r, true)
+ end
+ for r in gmatch(environment.argument("goodies") or "","([^, ]+)") do
+ states.set("goodies." .. r, true)
+ end
logs.report("state","loaded")
@@ -367,12 +542,12 @@ if environment.argument("update") then
elseif environment.argument("make") then
scripts.update.make()
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
if scripts.savestate then
- input.stoptiming(states)
- states.set("info.runtime",tonumber(input.elapsedtime(states)))
+ statistics.stoptiming(states)
+ states.set("info.runtime",tonumber(statistics.elapsedtime(states)))
if environment.argument("force") then
states.save()
logs.report("state","saved")
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-watch.lua b/Master/texmf-dist/scripts/context/lua/mtx-watch.lua
index f9e81da4257..2e4dcf6efec 100644
--- a/Master/texmf-dist/scripts/context/lua/mtx-watch.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtx-watch.lua
@@ -6,13 +6,29 @@ if not modules then modules = { } end modules ['mtx-watch'] = {
license = "see context related readme files"
}
-texmf.instance = instance -- we need to get rid of this / maybe current instance in global table
-
scripts = scripts or { }
scripts.watch = scripts.watch or { }
do
+ function scripts.watch.save_exa_modes(joblog,ctmname)
+ local t= { }
+ if joblog then
+ t[#t+1] = "<?xml version='1.0' standalone='yes'?>\n"
+ t[#t+1] = "<exa:variables xmlns:exa='htpp://www.pragma-ade.com/schemas/exa-variables.rng'>"
+ if joblog.values then
+ for k, v in pairs(joblog.values) do
+ t[#t+1] = string.format("\t<exa:variable label='%s'>%s</exa:variable>", k, tostring(v))
+ end
+ else
+ t[#t+1] = "<!-- no modes -->"
+ end
+ t[#t+1] = "</exa:variables>"
+ end
+ os.remove(ctmname)
+ io.savedata(ctmname,table.concat(t,"\n"))
+ end
+
function scripts.watch.watch()
local delay = environment.argument("delay") or 5
local logpath = environment.argument("logpath") or ""
@@ -42,6 +58,20 @@ do
end
end
end
+ local function toset(t)
+ if type(t) == "table" then
+ return table.concat(t,",")
+ else
+ return t
+ end
+ end
+ local function noset(t)
+ if type(t) == "table" then
+ return t[1]
+ else
+ return t
+ end
+ end
local function process()
local done = false
for _, path in ipairs(environment.files) do
@@ -61,8 +91,8 @@ do
local command = joblog.command
if command then
local replacements = {
- inputpath = (joblog.paths and joblog.paths.input ) or ".",
- outputpath = (joblog.paths and joblog.paths.output) or ".",
+ inputpath = toset((joblog.paths and joblog.paths.input ) or "."),
+ outputpath = noset((joblog.paths and joblog.paths.output) or "."),
filename = joblog.filename or "",
}
command = command:gsub("%%(.-)%%", replacements)
@@ -74,12 +104,17 @@ do
local newpath = file.dirname(name)
io.flush()
local result = ""
+ local ctmname = file.basename(replacements.filename)
+ if ctmname == "" then ctmname = name end -- use self as fallback
+ ctmname = file.replacesuffix(ctmname,"ctm")
if newpath ~= "" and newpath ~= "." then
local oldpath = lfs.currentdir()
lfs.chdir(newpath)
+ scripts.watch.save_exa_modes(joblog,ctmname)
if pipe then result = os.resultof(command) else result = os.spawn(command) end
lfs.chdir(oldpath)
else
+ scripts.watch.save_exa_modes(joblog,ctmname)
if pipe then result = os.resultof(command) else result = os.spawn(command) end
end
logs.report("watch",string.format("return value: %s", result))
@@ -204,7 +239,7 @@ function scripts.watch.show_logs(path) -- removes duplicates
end
end
-banner = banner .. " | watchdog"
+logs.extendbanner("Watchdog 1.00",true)
messages.help = [[
--logpath optional path for log files
@@ -215,8 +250,6 @@ messages.help = [[
--showlog show log data
]]
-input.verbose = true
-
if environment.argument("watch") then
scripts.watch.watch()
elseif environment.argument("collect") then
@@ -224,5 +257,5 @@ elseif environment.argument("collect") then
elseif environment.argument("showlog") then
scripts.watch.show_logs()
else
- input.help(banner,messages.help)
+ logs.help(messages.help)
end
diff --git a/Master/texmf-dist/scripts/context/lua/mtxrun.lua b/Master/texmf-dist/scripts/context/lua/mtxrun.lua
index 0fb45cc635b..82d1edecbc5 100755
--- a/Master/texmf-dist/scripts/context/lua/mtxrun.lua
+++ b/Master/texmf-dist/scripts/context/lua/mtxrun.lua
@@ -8,6 +8,7 @@ if not modules then modules = { } end modules ['mtxrun'] = {
license = "see context related readme files"
}
+
-- one can make a stub:
--
-- #!/bin/sh
@@ -26,7 +27,7 @@ if not modules then modules = { } end modules ['mtxrun'] = {
-- 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.
+-- library also gives way more control.
-- to be done / considered
--
@@ -37,161 +38,43 @@ if not modules then modules = { } end modules ['mtxrun'] = {
-- 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
+do -- create closure to overcome 200 locals limit
---~ 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
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
-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
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
-function string:split(separator)
- local t = {}
- for k in self:splitter(separator) do t[#t+1] = k end
- return t
-end
+if not string.split then
--- faster than a string:split:
+ -- this will be overloaded by a faster lpeg variant
-function string:splitchr(chr)
- if #self > 0 then
- local t = { }
- for s in string.gmatch(self..chr,"(.-)"..chr) do
- t[#t+1] = s
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
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 = {
+local chr_to_esc = {
["%"] = "%%",
["."] = "%.",
["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
@@ -201,21 +84,23 @@ string.chr_to_esc = {
["{"] = "%{", ["}"] = "%}"
}
+string.chr_to_esc = chr_to_esc
+
function string:esc() -- variant 2
- return (self:gsub("(.)",string.chr_to_esc))
+ return (gsub(self,"(.)",chr_to_esc))
end
-function string.unquote(str)
- return (str:gsub("^([\"\'])(.*)%1$","%2"))
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
end
-function string.quote(str)
- return '"' .. str:unquote() .. '"'
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
end
function string:count(pattern) -- variant 3
local n = 0
- for _ in self:gmatch(pattern) do
+ for _ in gmatch(self,pattern) do
n = n + 1
end
return n
@@ -224,29 +109,25 @@ end
function string:limit(n,sentinel)
if #self > n then
sentinel = sentinel or " ..."
- return self:sub(1,(n-#sentinel)) .. sentinel
+ return sub(self,1,(n-#sentinel)) .. sentinel
else
return self
end
end
function string:strip()
- return (self:gsub("^%s*(.-)%s*$", "%1"))
+ return (gsub(self,"^%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")
+ return not find(find,"%S")
end
function string:enhance(pattern,action)
local ok, n = true, 0
while ok do
ok = false
- self = self:gsub(pattern, function(...)
+ self = gsub(self,pattern, function(...)
ok, n = true, n + 1
return action(...)
end)
@@ -254,59 +135,19 @@ function string:enhance(pattern,action)
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 = { }
+local chr_to_hex, 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
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], 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))
+ return (gsub(self or "","(.)",chr_to_hex))
end
function string:from_hex()
- return ((self or ""):gsub("(..)",string.hex_to_chr))
+ return (gsub(self or "","(..)",hex_to_chr))
end
if not string.characters then
@@ -320,7 +161,7 @@ if not string.characters then
end
local function nextbyte(str, index)
index = index + 1
- return (index <= #str) and index or nil, string.byte(str:sub(index,index))
+ return (index <= #str) and index or nil, byte(str:sub(index,index))
end
function string:bytes()
return nextbyte, self, 0
@@ -328,9 +169,7 @@ if not string.characters then
end
---~ function string:padd(n,chr)
---~ return self .. self.rep(chr or " ",n-#self)
---~ end
+-- we can use format for this (neg n)
function string:rpadd(n,chr)
local m = n-#self
@@ -352,8 +191,8 @@ end
string.padd = string.rpadd
-function is_number(str)
- return str:find("^[%-%+]?[%d]-%.?[%d+]$") == 1
+function is_number(str) -- tonumber
+ return find(str,"^[%-%+]?[%d]-%.?[%d+]$") == 1
end
--~ print(is_number("1"))
@@ -365,9 +204,9 @@ end
--~ print(is_number("+.1"))
function string:split_settings() -- no {} handling, see l-aux for lpeg variant
- if self:find("=") then
+ if find(self,"=") then
local t = { }
- for k,v in self:gmatch("(%a+)=([^%,]*)") do
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
t[k] = v
end
return t
@@ -389,24 +228,72 @@ local patterns_escapes = {
}
function string:pattesc()
- return (self:gsub(".",patterns_escapes))
+ return (gsub(self,".",patterns_escapes))
end
function string:tohash()
local t = { }
- for s in self:gmatch("([^, ]+)") do -- lpeg
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
t[s] = true
end
return t
end
+local pattern = lpeg.Ct(lpeg.C(1)^0)
--- filename : l-lpeg.lua
--- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
--- copyright: PRAGMA ADE / ConTeXt Development Team
--- license : see context related readme files
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
-if not versions then versions = { } end versions['l-lpeg'] = 1.001
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
--~ l-lpeg.lua :
@@ -430,52 +317,101 @@ if not versions then versions = { } end versions['l-lpeg'] = 1.001
local hash = { }
function lpeg.anywhere(pattern) --slightly adapted from website
- return lpeg.P { lpeg.P(pattern) + 1 * lpeg.V(1) }
+ return P { P(pattern) + 1 * lpeg.V(1) }
end
function lpeg.startswith(pattern) --slightly adapted
- return lpeg.P(pattern)
+ return 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
+ return (((1-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")
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
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 empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
local content = (empty + nonempty)^1
-local capture = lpeg.Ct(content^0)
+local capture = Ct(content^0)
function string:splitlines()
return capture:match(self)
end
+lpeg.linebyline = content -- better make a sublibrary
+
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
+
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c: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
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
table.join = table.concat
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
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")
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
if s == "" then
-- skip this one
else
@@ -485,18 +421,13 @@ function table.strip(tab)
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
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
-function table.sortedkeys(tab)
+local function sortedkeys(tab)
local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
- for key,_ in pairs(tab) do
+ for key,_ in next, tab do
srt[#srt+1] = key
if kind == 3 then
-- no further check
@@ -514,22 +445,45 @@ function table.sortedkeys(tab)
end
end
if kind == 0 or kind == 3 then
- table.sort(srt,function(a,b) return (tostring(a) < tostring(b)) end)
+ sort(srt,compare)
else
- table.sort(srt)
+ sort(srt)
end
return srt
end
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
+ end
+ sort(srt)
+ return srt
+end
+
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
function table.append(t, list)
- for _,v in pairs(list) do
- table.insert(t,v)
+ for _,v in next, list do
+ insert(t,v)
end
end
function table.prepend(t, list)
- for k,v in pairs(list) do
- table.insert(t,k,v)
+ for k,v in next, list do
+ insert(t,k,v)
end
end
@@ -537,7 +491,7 @@ 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
+ for k, v in next, lst[i] do
t[k] = v
end
end
@@ -547,7 +501,7 @@ end
function table.merged(...)
local tmp, lst = { }, {...}
for i=1,#lst do
- for k, v in pairs(lst[i]) do
+ for k, v in next, lst[i] do
tmp[k] = v
end
end
@@ -576,70 +530,58 @@ function table.imerged(...)
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)
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
end
- return new
- else
- return { }
end
+ -- optional second arg
+ 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]
+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 next, t do -- brrr, what happens with sparse indexed
+ if type(i) == "table" then
+ if tables[i] then
+ i = tables[i]
else
- tcopy[i] = copy(v, tables)
+ i = copy(i, tables)
end
end
- local mt = getmetatable(t)
- if mt then
- setmetatable(tcopy,mt)
+ if type(v) ~= "table" then
+ tcopy[i] = v
+ elseif tables[v] then
+ tcopy[i] = tables[v]
+ else
+ tcopy[i] = copy(v, tables)
end
- return tcopy
end
+ local mt = getmetatable(t)
+ if mt then
+ setmetatable(tcopy,mt)
+ end
+ return tcopy
+end
- table.copy = copy
-
-end end
+table.fastcopy = fastcopy
+table.copy = copy
-- rougly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
@@ -648,7 +590,7 @@ function table.sub(t,i,j)
end
function table.replace(a,b)
- for k,v in pairs(b) do
+ for k,v in next, b do
a[k] = v
end
end
@@ -668,344 +610,483 @@ 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..'"]'
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
end
end
+ return h
+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)
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
else
- tt = nil
- break
+ tt[#tt+1] = tostring(v) -- tostring not needed
end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
end
- return tt
end
+ return tt
end
- return nil
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))
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
else
- handle(("%s%s={"):format(depth,key(name)))
+ handle(format("%s[%q]={",depth,name))
end
else
- depth = ""
- local tname = type(name)
- if tname == "string" then
- if name == "return" then
- handle("return {")
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
else
- handle(name .. "={")
+ handle(format("%s %q,",depth,tostring(v)))
end
- elseif tname == "number" then
- handle("[" .. name .. "]={")
- elseif tname == "boolean" then
- if name then
- handle("return {")
- else
- handle("{")
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
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
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
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))
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
else
- handle(("%s %q,"):format(depth,v))
+ handle(format("%s [%s]=%s,",depth,k,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
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes,true)
+ handle(format("%s [%s]=%q,",depth,k,v))
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)))
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
else
- handle(('%s "function",'):format(depth))
+ handle(format("%s [%s]={},",depth,k))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
else
- handle(("%s %q,"):format(depth,tostring(v)))
+ handle(format("%s [%q]={},",depth,k))
end
- elseif k == "__p__" then -- parent
- if false then
- handle(("%s __p__=nil,"):format(depth))
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
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
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
else
- handle(("%s %s=%q,"):format(depth,key(k),v))
+ handle(format("%s [%s]=%s,",depth,k,tostring(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,", ")))
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
else
- serialize(v,k,handle,depth,level+1,reduce,noquotes)
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
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)))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
else
- handle(('%s %s="function",'):format(depth,key(k)))
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
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)))
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
end
end
- if level > 0 then
- handle(("%s},"):format(depth))
- else
- handle(("%s}"):format(depth))
- end
- else
- handle(("%s}"):format(depth))
+ --~ end
end
end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
- --~ name:
- --~
- --~ true : return { }
- --~ false : { }
- --~ nil : t = { }
- --~ string : string = { }
- --~ 'return' : return { }
- --~ number : [number] = { }
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
- function table.serialize(root,name,reduce,noquotes)
- local t = { }
- local function flush(s)
- t[#t+1] = s
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
end
- serialize(root, name, flush, nil, 0, reduce, noquotes)
- return table.concat(t,"\n")
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
- function table.tohandle(handle,root,name,reduce,noquotes)
- serialize(root, name, handle, nil, 0, reduce, noquotes)
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+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
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+end
- table.tofile_maxtab = 2*1024
+-- 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
- 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")
+table.tofile_maxtab = 2*1024
+
+function table.tofile(filename,root,name,reduce,noquotes,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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
- serialize(root, name, flush, nil, 0, reduce, noquotes)
end
- f:close()
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
end
+ f:close()
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
+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.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
+function table.unnest(t) -- bad name
+ local f = { }
+ flatten(t,f,false)
+ return f
+end
- table.flatten_one_level = table.unnest
+table.flatten_one_level = table.unnest
+-- the next three may disappear
+
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
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
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
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
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
end
- t[#t+1] = str
end
-function table.are_equal(a,b,n,m)
- if #a == #b then
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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
+ if ai==bi then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
else
return false
end
@@ -1016,37 +1097,42 @@ function table.are_equal(a,b,n,m)
end
end
-function table.compact(t)
- if t then
- for k,v in pairs(t) do
- if not next(v) then
- t[k] = nil
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
end
+ else
+ return false
end
end
+ return true
end
-function table.tohash(t)
- local h = { }
- for _, v in pairs(t) do -- no ipairs here
- h[v] = true
- end
- return h
-end
+table.are_equal = are_equal
+table.identical = identical
-function table.fromhash(t)
- local h = { }
- for k, v in pairs(t) do -- no ipairs here
- if v then h[#h+1] = k end
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ 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
+ return i
end
end
end
@@ -1063,7 +1149,7 @@ end
function table.swapped(t)
local s = { }
- for k, v in pairs(t) do
+ for k, v in next, t do
s[v] = k
end
return s
@@ -1083,17 +1169,16 @@ function table.clone(t,p) -- t is optional or nil or table
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 " ")
+ for i=1,#t do tt[i] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
end
function table.reverse_hash(h)
local r = { }
- for k,v in pairs(h) do
- r[v] = (k:gsub(" ","")):lower()
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
end
return r
end
@@ -1108,14 +1193,36 @@ function table.reverse(t)
return tt
end
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ 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
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
-if not versions then versions = { } end versions['l-io'] = 1.001
+local byte = string.byte
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator = "\\", ";"
@@ -1123,10 +1230,12 @@ else
io.fileseparator, io.pathseparator = "/" , ":"
end
-function io.loaddata(filename)
- local f = io.open(filename,'rb')
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
local data = f:read('*all')
+ -- garbagecollector.check(data)
f:close()
return data
else
@@ -1135,7 +1244,7 @@ function io.loaddata(filename)
end
function io.savedata(filename,data,joiner)
- local f = io.open(filename, "wb")
+ local f = io.open(filename,"wb")
if f then
if type(data) == "table" then
f:write(table.join(data,joiner or ""))
@@ -1145,6 +1254,9 @@ function io.savedata(filename,data,joiner)
f:write(data)
end
f:close()
+ return true
+ else
+ return false
end
end
@@ -1178,146 +1290,83 @@ function io.noflines(f)
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
+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
-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
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
end
- }
-
- function io.bytes(f,n)
- if f then
- return nextbyte[n or 1], f
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
function io.ask(question,default,options)
@@ -1353,35 +1402,21 @@ function io.ask(question,default,options)
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
+end -- of closure
- local function convert(str,fmt)
- return (string.gsub(md5.sum(str),".",function(chr) return string.format(fmt,string.byte(chr)) end))
- end
+do -- create closure to overcome 200 locals limit
- 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 modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
-if not versions then versions = { } end versions['l-number'] = 1.001
+local format = string.format
-if not number then number = { } end
+number = number or { }
-- a,b,c,d,e,f = number.toset(100101)
@@ -1389,8 +1424,6 @@ 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
@@ -1411,72 +1444,89 @@ end
--
-- of course dedicated "(.)(.)(.)(.)" matches are even faster
-do
- local one = lpeg.C(1-lpeg.S(''))^1
+local one = lpeg.C(1-lpeg.S(''))^1
- function number.toset(n)
- return one:match(tostring(n))
- end
+function number.toset(n)
+ return one:match(tostring(n))
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
+end -- of closure
-if not set then set = { } end
+do -- create closure to overcome 200 locals limit
-do
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
- local nums = { }
- local tabs = { }
- local concat = table.concat
+set = set or { }
- set.create = table.tohash
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
- 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
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
end
- return nums[s]
- else
- return 0
end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
end
+end
- function set.totable(n)
- if n == 0 then
- return { }
- else
- return tabs[n] or { }
- 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]
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
end
+ return concat(t," ")
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
--~ local c = set.create{'aap','noot','mies'}
@@ -1493,13 +1543,19 @@ end
--- 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
+end -- of closure
-if not versions then versions = { } end versions['l-os'] = 1.001
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
function os.resultof(command)
return io.popen(command,"r"):read("*all")
@@ -1512,7 +1568,7 @@ if not os.spawn then os.spawn = os.execute end
--~ os.name : windows | msdos | linux | macosx | solaris | .. | generic (new)
if not io.fileseparator then
- if string.find(os.getenv("PATH"),";") then
+ if 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"
@@ -1550,11 +1606,10 @@ end
os.gettimeofday = os.gettimeofday or os.clock
-do
- local startuptime = os.gettimeofday()
- function os.runtime()
- return os.gettimeofday() - startuptime
- end
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
end
--~ print(os.gettimeofday()-os.time())
@@ -1563,23 +1618,92 @@ end
--~ print(os.date("%H:%M:%S",os.gettimeofday()))
--~ print(os.date("%H:%M:%S",os.time()))
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+end
--- 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
+local platform
+
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
+ end
+ return platform
+end
-if not versions then versions = { } end versions['l-file'] = 1.001
-if not file then file = { } end
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
+
+-- needs a cleanup
+
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
function file.removesuffix(filename)
- return filename:gsub("%.[%a%d]+$", "")
+ return (gsub(filename,"%.[%a%d]+$",""))
end
function file.addsuffix(filename, suffix)
- if not filename:find("%.[%a%d]+$") then
+ if not find(filename,"%.[%a%d]+$") then
return filename .. "." .. suffix
else
return filename
@@ -1587,43 +1711,27 @@ function file.addsuffix(filename, suffix)
end
function file.replacesuffix(filename, suffix)
- if not filename:find("%.[%a%d]+$") then
- return filename .. "." .. suffix
- else
- return (filename:gsub("%.[%a%d]+$","."..suffix))
- end
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
end
-function file.dirname(name)
- return name:match("^(.+)[/\\].-$") or ""
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
end
function file.basename(name)
- return name:match("^.+[/\\](.-)$") or name
+ return match(name,"^.+[/\\](.-)$") or name
end
function file.nameonly(name)
- return ((name:match("^.+[/\\](.-)$") or name):gsub("%..*$",""))
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
end
function file.extname(name)
- return name:match("^.+%.([^/\\]-)$") or ""
+ return match(name,"^.+%.([^/\\]-)$") 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"))
@@ -1631,97 +1739,73 @@ end
--~ print(file.join("//nas-1","/y"))
function file.join(...)
- local pth = table.concat({...},"/")
- pth = pth:gsub("\\","/")
- local a, b = pth:match("^(.*://)(.*)$")
+ local pth = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
if a and b then
- return a .. b:gsub("//+","/")
+ return a .. gsub(b,"//+","/")
end
- a, b = pth:match("^(//)(.*)$")
+ a, b = match(pth,"^(//)(.*)$")
if a and b then
- return a .. b:gsub("//+","/")
+ return a .. gsub(b,"//+","/")
end
- return (pth:gsub("//+","/"))
+ return (gsub(pth,"//+","/"))
end
-function file.is_writable(name)
- local f = io.open(name, 'w')
- if f then
- f:close()
- return true
- else
- return false
- end
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
end
-function file.is_readable(name)
- local f = io.open(name,'r')
- if f then
- f:close()
- return true
- else
- return false
- end
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
end
---~ function file.split_path(str)
---~ if str:find(';') then
---~ return str:splitchr(";")
---~ else
---~ return str:splitchr(io.pathseparator)
---~ end
---~ end
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
-- 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
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
if name ~= "" then
- name = name:gsub("\001",":")
- t[#t+1] = name
+ t[#t+1] = gsub(name,"\001",":")
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
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
function file.collapse_path(str)
- local n = 1
- while n > 0 do
- str, n = str:gsub("([^/%.]+/%.%./)","")
- end
- return (str:gsub("/%./","/"))
-end
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
function file.robustname(str)
- return (str:gsub("[^%a%d%/%-%.\\]+","-"))
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
end
file.readdata = io.loaddata
@@ -1731,266 +1815,416 @@ function file.copy(oldname,newname)
file.savedata(newname,io.loaddata(oldname))
end
+-- lpeg variants, slightly faster, not always
--- 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
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
-if not versions then versions = { } end versions['l-dir'] = 1.001
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
-dir = { }
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
--- optimizing for no string.find (*) does not save time
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
-if lfs then do
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
---~ 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
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
--~ 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
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
--~ end
---~
---~ dir.glob = glob
+--~ end
- local attributes = lfs.attributes
- local walkdir = lfs.dir
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
- 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
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
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
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
+
+local type = type
+local find, gmatch = string.find, string.gmatch
+
+dir = dir or { }
+
+-- optimizing for no string.find (*) does not save time
+
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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)
- }
+dir.glob_pattern = glob_pattern
- local filter = Cs ( (
- P("**") / ".*" +
- P("*") / "[^/]*" +
- P("?") / "[^/]" +
- P(".") / "%%." +
- P("+") / "%%+" +
- P("-") / "%%-" +
- P(1)
- )^0 )
-
- local function glob(str,t)
- if type(str) == "table" then
+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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ return t
+ else
+ local split = pattern:match(str)
+ if split then
local t = t or { }
- for _, s in ipairs(str) do
- glob(s,t)
- end
+ local action = action or function(name) t[#t+1] = name end
+ local root, path, base = split[1], split[2], split[3]
+ local recurse = find(base,"%*%*")
+ local start = root .. path
+ local result = filter:match(start .. base)
+ glob_pattern(start,result,recurse,action)
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
+ return { }
end
end
+end
- dir.glob = glob
+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")
+--~ 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
+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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
if recurse then
globfiles(path .. "/" .. name,recurse,func,files)
end
- elseif func then
- if func(name) then
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
files[#files+1] = path .. "/" .. name
end
- else
- files[#files+1] = path .. "/" .. name
end
end
- return files
end
+ return files
+end
- dir.globfiles = globfiles
+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"))
+-- 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
+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")
+--~ mkdirs("temp")
+--~ mkdirs("a/b/c")
+--~ mkdirs(".","/a/b/c")
+--~ mkdirs("a","b","c")
- local make_indeed = true -- false
+local make_indeed = true -- false
- if string.find(os.getenv("PATH"),";") then
+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
+ 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
- local first, middle, last
- local drive = false
- first, middle, last = str:match("^(//)(//*)(.*)$")
+ 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
- -- empty network path == local path
+ middle, last = str:match("([^/]+)/+(.-)$")
+ if middle then
+ pth = "//" .. middle
+ else
+ pth = "//" .. last
+ last = ""
+ end
else
- first, last = str:match("^(//)/*(.-)$")
+ first, middle, last = str:match("^([a-zA-Z]:)(/*)(.-)$")
if first then
- middle, last = str:match("([^/]+)/+(.-)$")
- if middle then
- pth = "//" .. middle
- else
- pth = "//" .. last
- last = ""
- end
+ pth, drive = first .. middle, true
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
+ middle, last = str:match("^(/*)(.-)$")
+ if not middle then
+ last = str
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
+ for s in gmatch(last,"[^/]+") 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
- return pth, (lfs.isdir(pth) == true)
end
+ return pth, (lfs.isdir(pth) == true)
+ end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
@@ -2004,79 +2238,79 @@ if lfs then do
--~ 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)
+ 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 find(last,"^/") then
+ local d = lfs.currentdir()
+ if lfs.chdir(first) then
+ first = lfs.currentdir()
+ first = first:gsub("\\","/")
end
+ lfs.chdir(d)
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
+ 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
+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
+ 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
- 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
+ end
+ str = str:gsub("/+","/")
+ if find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") do
+ local first = (pth == "/")
+ if first then
+ pth = pth .. s
+ else
pth = pth .. "/" .. s
- if make_indeed and not lfs.isdir(pth) then
- lfs.mkdir(pth)
- end
+ end
+ if make_indeed and not first and not lfs.isdir(pth) then
+ lfs.mkdir(pth)
+ end
+ end
+ else
+ pth = "."
+ for s in gmatch(str,"[^/]+") do
+ pth = pth .. "/" .. s
+ if make_indeed and not lfs.isdir(pth) then
+ lfs.mkdir(pth)
end
end
- return pth, (lfs.isdir(pth) == true)
end
+ return pth, (lfs.isdir(pth) == true)
+ end
--~ print(dir.mkdirs("","","a","c"))
--~ print(dir.mkdirs("a"))
@@ -2086,30 +2320,35 @@ if lfs then do
--~ 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
+ function dir.expand_name(str)
+ if not find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
end
-
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
end
- dir.makedirs = dir.mkdirs
+end
-end end
+dir.makedirs = dir.mkdirs
--- 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
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
-if not versions then versions = { } end versions['l-boolean'] = 1.001
-if not boolean then boolean = { } end
+local type, tonumber = type, tonumber
function boolean.tonumber(b)
if b then return 1 else return 0 end
@@ -2119,7 +2358,7 @@ 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"
+ return str == "true" or str == "yes" or str == "on" or str == "1" or str == "t"
elseif tstr == "number" then
return tonumber(str) ~= 0
elseif tstr == "nil" then
@@ -2138,9 +2377,9 @@ end
function string.is_boolean(str)
if type(str) == "string" then
- if str == "true" or str == "yes" or str == "on" then
+ if str == "true" or str == "yes" or str == "on" or str == "t" then
return true
- elseif str == "false" or str == "no" or str == "off" then
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
return false
end
end
@@ -2156,22 +2395,474 @@ function boolean.falsetrue()
end
-if not modules then modules = { } end modules ['l-xml'] = {
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
version = 1.001,
- comment = "this module is the basis for the lxml-* ones",
+ 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"
}
--- RJ: key=value ... lpeg.Ca(lpeg.Cc({}) * (pattern-producing-key-and-value / rawset)^0)
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
+
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
+
+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
+
+utils.merger.strip_comment = true
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ utils.report("reading merge from %s",name)
+ data = f:read("*all")
+ f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ utils.report("saving merge from %s",name)
+ 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
+
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
+function utils.merger._self_libs_(libs,list)
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ local foundpath = nil
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
+ else
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
+ end
+ end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
+ 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
+
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if strip ~= false then
+ command = "-s " .. command
+ end
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
--- some code may move to l-xmlext
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
+ else
+ return (pattern_a/set)^1
+ end
+end
+
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
+
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
+
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
+
+aux.settings_to_array_pattern = pattern
+
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
+
+local function set(t,v)
+ t[#t+1] = v
+end
+
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
+
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
+
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
+
+-- temporary here
+
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
+
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
+
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-tab'] = {
+ 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"
+}
--[[ldx--
<p>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
+parent access; a first version used different trickery but was less optimized to we
went this route. First we had a find based parser, now we have an <l n='lpeg'/> based one.
The find based parser can be found in l-xml-edu.lua along with other older code.</p>
@@ -2193,17 +2884,30 @@ optimize the code.</p>
--ldx]]--
xml = xml or { }
-tex = tex or { }
-xml.trace_lpath = false
-xml.trace_print = false
-xml.trace_remap = false
+--~ local xml = xml
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, setmetatable = type, next, setmetatable
+local format, lower, find = string.format, string.lower, string.find
-local format, concat = string.format, table.concat
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers.</p>
+--ldx]]--
---~ local pairs, next, type = pairs, next, type
+local trace_remap = false
--- todo: some things per xml file, liek namespace remapping
+if trackers then
+ trackers.register("xml.remap", function(v) trace_remap = v end)
+end
+
+function xml.settrace(str,value)
+ if str == "remap" then
+ trace_remap = value or false
+ end
+end
--[[ldx--
<p>First a hack to enable namespace resolving. A namespace is characterized by
@@ -2215,65 +2919,61 @@ much cleaner.</p>
xml.xmlns = xml.xmlns or { }
-do
-
- local check = lpeg.P(false)
- local parse = check
+local check = lpeg.P(false)
+local parse = check
- --[[ldx--
- <p>The next function associates a namespace prefix with an <l n='url'/>. This
- normally happens independent of parsing.</p>
+--[[ldx--
+<p>The next function associates a namespace prefix with an <l n='url'/>. This
+normally happens independent of parsing.</p>
- <typing>
- xml.registerns("mml","mathml")
- </typing>
- --ldx]]--
+<typing>
+xml.registerns("mml","mathml")
+</typing>
+--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
+function xml.registerns(namespace, pattern) -- pattern can be an lpeg
+ check = check + lpeg.C(lpeg.P(lower(pattern))) / namespace
+ parse = lpeg.P { lpeg.P(check) + 1 * lpeg.V(1) }
+end
- --[[ldx--
- <p>The next function also registers a namespace, but this time we map a
- given namespace prefix onto a registered one, using the given
- <l n='url'/>. This used for attributes like <t>xmlns:m</t>.</p>
+--[[ldx--
+<p>The next function also registers a namespace, but this time we map a
+given namespace prefix onto a registered one, using the given
+<l n='url'/>. This used for attributes like <t>xmlns:m</t>.</p>
- <typing>
- xml.checkns("m","http://www.w3.org/mathml")
- </typing>
- --ldx]]--
+<typing>
+xml.checkns("m","http://www.w3.org/mathml")
+</typing>
+--ldx]]--
- function xml.checkns(namespace,url)
- local ns = parse:match(url:lower())
- if ns and namespace ~= ns then
- xml.xmlns[namespace] = ns
- end
+function xml.checkns(namespace,url)
+ local ns = parse:match(lower(url))
+ if ns and namespace ~= ns then
+ xml.xmlns[namespace] = ns
end
+end
- --[[ldx--
- <p>Next we provide a way to turn an <l n='url'/> into a registered
- namespace. This used for the <t>xmlns</t> attribute.</p>
-
- <typing>
- resolvedns = xml.resolvens("http://www.w3.org/mathml")
- </typing>
-
- This returns <t>mml</t>.
- --ldx]]--
+--[[ldx--
+<p>Next we provide a way to turn an <l n='url'/> into a registered
+namespace. This used for the <t>xmlns</t> attribute.</p>
- function xml.resolvens(url)
- return parse:match(url:lower()) or ""
- end
+<typing>
+resolvedns = xml.resolvens("http://www.w3.org/mathml")
+</typing>
- --[[ldx--
- <p>A namespace in an element can be remapped onto the registered
- one efficiently by using the <t>xml.xmlns</t> table.</p>
- --ldx]]--
+This returns <t>mml</t>.
+--ldx]]--
+function xml.resolvens(url)
+ return parse:match(lower(url)) or ""
end
--[[ldx--
+<p>A namespace in an element can be remapped onto the registered
+one efficiently by using the <t>xml.xmlns</t> table.</p>
+--ldx]]--
+
+--[[ldx--
<p>This version uses <l n='lpeg'/>. 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
@@ -2308,247 +3008,253 @@ element.</p>
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)
- -- not just one big nested table capture (lpeg overflow)
+local nsremap, resolvens = xml.xmlns, xml.resolvens
- local remove, nsremap, resolvens = table.remove, xml.xmlns, xml.resolvens
+local stack, top, dt, at, xmlns, errorstr, entities = {}, {}, {}, {}, {}, nil, {}
- local stack, top, dt, at, xmlns, errorstr, entities = {}, {}, {}, {}, {}, nil, {}
+local mt = { __tostring = xml.text }
- local mt = { __tostring = xml.text }
+function xml.check_error(top,toclose)
+ return ""
+end
- function xml.check_error(top,toclose)
- return ""
- end
+local strip = false
+local cleanup = false
- local strip = false
- local cleanup = false
+function xml.set_text_cleanup(fnc)
+ cleanup = fnc
+end
- function xml.set_text_cleanup(fnc)
- cleanup = fnc
+local function add_attribute(namespace,tag,value)
+ if cleanup and #value > 0 then
+ value = cleanup(value) -- new
end
+ 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_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
+local function add_begin(spacing, namespace, tag)
+ if #spacing > 0 then
+ dt[#dt+1] = spacing
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("<!ELEMENT") * (1-close)^0 * close
- local entitydoctype = optionalspace * P("<!ENTITY") * somespace * (doctypename * somespace * value)/entity * optionalspace * close
- local publicdoctype = doctypename * somespace * P("PUBLIC") * somespace * value * somespace * value * somespace
- local systemdoctype = doctypename * somespace * P("SYSTEM") * somespace * value * somespace
- local definitiondoctype= doctypename * somespace * beginset * P(elementdoctype + entitydoctype)^0 * optionalspace * endset
- local simpledoctype = (1-close)^1 -- * balanced^0
- local somedoctype = C((somespace * (publicdoctype + systemdoctype + definitiondoctype + simpledoctype) * optionalspace)^0)
-
- local instruction = (spacing * begininstruction * someinstruction * endinstruction) / function(...) add_special("@pi@",...) end
- local comment = (spacing * begincomment * somecomment * endcomment ) / function(...) add_special("@cm@",...) end
- local cdata = (spacing * begincdata * somecdata * endcdata ) / function(...) add_special("@cd@",...) end
- local doctype = (spacing * begindoctype * somedoctype * enddoctype ) / function(...) add_special("@dt@",...) end
-
- -- nicer but slower:
- --
- -- local instruction = (lpeg.Cc("@pi@") * spacing * begininstruction * someinstruction * endinstruction) / add_special
- -- local comment = (lpeg.Cc("@cm@") * spacing * begincomment * somecomment * endcomment ) / add_special
- -- local cdata = (lpeg.Cc("@cd@") * spacing * begincdata * somecdata * endcdata ) / add_special
- -- local doctype = (lpeg.Cc("@dt@") * spacing * begindoctype * somedoctype * enddoctype ) / add_special
-
- local trailer = space^0 * (justtext/set_message)^0
-
- -- comment + emptyelement + text + cdata + instruction + V("parent"), -- 6.5 seconds on 40 MB database file
- -- text + comment + emptyelement + cdata + instruction + V("parent"), -- 5.8
- -- text + V("parent") + emptyelement + comment + cdata + instruction, -- 5.5
-
- local grammar = P { "preamble",
- preamble = utfbom^0 * instruction^0 * (doctype + comment + instruction)^0 * V("parent") * trailer,
- parent = beginelement * V("children")^0 * endelement,
- children = text + V("parent") + emptyelement + comment + cdata + instruction,
- }
+ 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
- -- todo: xml.new + properties like entities and strip and such (store in root)
-
- function xml.convert(data, no_root, strip_cm_and_dt, given_entities) -- maybe use table met k/v (given_entities may disapear)
- strip = strip_cm_and_dt or xml.strip_cm_and_dt
- stack, top, at, xmlns, errorstr, result, entities = {}, {}, {}, {}, nil, nil, given_entities or {}
- stack[#stack+1] = top
- top.dt = { }
- dt = top.dt
- if not data or data == "" then
- errorstr = "empty xml file"
- elseif not grammar:match(data) then
- errorstr = "invalid xml file"
- else
- errorstr = ""
- end
- if errorstr and errorstr ~= "" then
- result = { dt = { { ns = "", tg = "error", dt = { errorstr }, at={}, er = true } }, error = true }
- setmetatable(stack, mt)
- if xml.error_handler then xml.error_handler("load",errorstr) end
- else
- result = stack[1]
- end
- if not no_root then
- result = { special = true, ns = "", tg = '@rt@', dt = result.dt, at={}, entities = entities }
- setmetatable(result, mt)
- local rdt = result.dt
- for k=1,#rdt do
- local v = rdt[k]
- if type(v) == "table" and not v.special then -- always table -)
- result.ri = k -- rootindex
- break
- end
- end
- end
- return result
+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
+ dt[0] = top
+ if toclose.at.xmlns then
+ remove(xmlns)
+ end
+end
- --[[ldx--
- <p>Packaging data in an xml like table is done with the following
- function. Maybe it will go away (when not used).</p>
- --ldx]]--
+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)
+ if at.xmlns then
+ remove(xmlns)
+ end
+ at = { }
+end
- 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
+local function add_text(text)
+ if cleanup and #text > 0 then
+ dt[#dt+1] = cleanup(text)
+ else
+ dt[#dt+1] = text
end
+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
+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: " .. gsub(txt,"([ \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)
+
+local 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("<!ELEMENT") * (1-close)^0 * close
+local entitydoctype = optionalspace * P("<!ENTITY") * somespace * (doctypename * somespace * value)/entity * optionalspace * close
+local publicdoctype = doctypename * somespace * P("PUBLIC") * somespace * value * somespace * value * somespace
+local systemdoctype = doctypename * somespace * P("SYSTEM") * somespace * value * somespace
+local definitiondoctype= doctypename * somespace * beginset * P(elementdoctype + entitydoctype)^0 * optionalspace * endset
+local simpledoctype = (1-close)^1 -- * balanced^0
+local somedoctype = C((somespace * (publicdoctype + systemdoctype + definitiondoctype + simpledoctype) * optionalspace)^0)
+
+local instruction = (spacing * begininstruction * someinstruction * endinstruction) / function(...) add_special("@pi@",...) end
+local comment = (spacing * begincomment * somecomment * endcomment ) / function(...) add_special("@cm@",...) end
+local cdata = (spacing * begincdata * somecdata * endcdata ) / function(...) add_special("@cd@",...) end
+local doctype = (spacing * begindoctype * somedoctype * enddoctype ) / function(...) add_special("@dt@",...) end
+
+-- nicer but slower:
+--
+-- local instruction = (lpeg.Cc("@pi@") * spacing * begininstruction * someinstruction * endinstruction) / add_special
+-- local comment = (lpeg.Cc("@cm@") * spacing * begincomment * somecomment * endcomment ) / add_special
+-- local cdata = (lpeg.Cc("@cd@") * spacing * begincdata * somecdata * endcdata ) / add_special
+-- local doctype = (lpeg.Cc("@dt@") * spacing * begindoctype * somedoctype * enddoctype ) / add_special
- function xml.is_valid(root)
- return root and not root.error
+local trailer = space^0 * (justtext/set_message)^0
+
+-- comment + emptyelement + text + cdata + instruction + V("parent"), -- 6.5 seconds on 40 MB database file
+-- text + comment + emptyelement + cdata + instruction + V("parent"), -- 5.8
+-- text + V("parent") + emptyelement + comment + cdata + instruction, -- 5.5
+
+local grammar = P { "preamble",
+ preamble = utfbom^0 * instruction^0 * (doctype + comment + instruction)^0 * V("parent") * trailer,
+ parent = beginelement * V("children")^0 * endelement,
+ children = text + V("parent") + emptyelement + comment + cdata + instruction,
+}
+
+-- todo: xml.new + properties like entities and strip and such (store in root)
+
+function xml.convert(data, no_root, strip_cm_and_dt, given_entities) -- maybe use table met k/v (given_entities may disapear)
+ strip = strip_cm_and_dt or xml.strip_cm_and_dt
+ stack, top, at, xmlns, errorstr, result, entities = {}, {}, {}, {}, nil, nil, given_entities or {}
+ stack[#stack+1] = top
+ top.dt = { }
+ dt = top.dt
+ if not data or data == "" then
+ errorstr = "empty xml file"
+ elseif not grammar:match(data) then
+ errorstr = "invalid xml file"
+ else
+ errorstr = ""
end
+ if errorstr and errorstr ~= "" then
+ result = { dt = { { ns = "", tg = "error", dt = { errorstr }, at={}, er = true } }, error = true }
+ setmetatable(stack, mt)
+ if xml.error_handler then xml.error_handler("load",errorstr) end
+ else
+ result = stack[1]
+ end
+ if not no_root then
+ result = { special = true, ns = "", tg = '@rt@', dt = result.dt, at={}, entities = entities }
+ setmetatable(result, mt)
+ local rdt = result.dt
+ for k=1,#rdt do
+ local v = rdt[k]
+ if type(v) == "table" and not v.special then -- always table -)
+ result.ri = k -- rootindex
+ break
+ end
+ end
+ end
+ return result
+end
- xml.error_handler = (logs and logs.report) or (input and input.report) or print
+--[[ldx--
+<p>Packaging data in an xml like table is done with the following
+function. Maybe it will go away (when not used).</p>
+--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 logs.report) or print
+
--[[ldx--
<p>We cannot load an <l n='lpeg'/> from a filehandle so we need to load
the whole file first. The function accepts a string representing
@@ -2592,32 +3298,28 @@ 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!</p>
--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 { }
+local 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
-
- xml.copy = copy
-
end
+xml.copy = copy
+
--[[ldx--
<p>In <l n='context'/> serializing the tree or parts of the tree is a major
actitivity which is why the following function is pretty optimized resulting
@@ -2626,202 +3328,204 @@ function for all components is about 15% slower than the concatinating
alternative.</p>
--ldx]]--
-do
-
- -- todo: add <?xml version='1.0' standalone='yes'?> when not present
-
- local fallbackhandle = (tex and tex.sprint) or io.write
+-- todo: add <?xml version='1.0' standalone='yes'?> when not present
- 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
+local fallbackhandle = (tex and tex.sprint) or io.write
-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
+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
- local edt = e.dt
+ local etg, edt = e.tg, e.dt
local spc = specialconverter and specialconverter[etg]
if spc then
local result = spc(edt[1])
if result then
handle(result)
+ return
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("<!--%s-->",edt[1]))
- handle("<!--" .. edt[1] .. "-->")
- elseif etg == "@cd@" then
- -- handle(format("<![CDATA[%s]]>",edt[1]))
- handle("<![CDATA[" .. edt[1] .. "]]>")
- elseif etg == "@dt@" then
- -- handle(format("<!DOCTYPE %s>",edt[1]))
- handle("<!DOCTYPE " .. edt[1] .. ">")
- 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
+ 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
- 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
+ elseif etg == "@pi@" then
+ -- handle(format("<?%s?>",edt[1]))
+ handle("<?" .. edt[1] .. "?>")
+ elseif etg == "@cm@" then
+ -- handle(format("<!--%s-->",edt[1]))
+ handle("<!--" .. edt[1] .. "-->")
+ elseif etg == "@cd@" then
+ -- handle(format("<![CDATA[%s]]>",edt[1]))
+ handle("<![CDATA[" .. edt[1] .. "]]>")
+ elseif etg == "@dt@" then
+ -- handle(format("<!DOCTYPE %s>",edt[1]))
+ handle("<!DOCTYPE " .. edt[1] .. ">")
+ 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 next, eat do
+ ats[#ats+1] = format('%s=%q',k,attributeconverter(v))
+ end
+ else
+ for k,v in next, eat do
+ ats[#ats+1] = format('%s=%q',k,v)
+ end
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
+ end
+ if ern and trace_remap and ern ~= ens then
+ 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
- serialize(e,handle,textconverter,attributeconverter,specialconverter,nocommands)
+ handle(e)
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 .. "/>")
+ serialize(e,handle,textconverter,attributeconverter,specialconverter,nocommands)
end
end
+ -- handle(format("</%s:%s>",ens,etg))
+ handle("</" .. ens .. ":" .. etg .. ">")
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 .. ">")
+ 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
- if ats then
- -- handle(format("<%s %s/>",etg,concat(ats," ")))
- handle("<" .. etg .. " " .. concat(ats," ") .. "/>")
+ -- handle(format("<%s>",etg))
+ handle("<" .. etg .. ">")
+ end
+ for i=1,#edt do
+ local ei = edt[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
else
- -- handle(format("<%s/>",etg))
- handle("<" .. etg .. "/>")
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
end
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
- elseif type(e) == "string" then
- if textconverter then
- handle(textconverter(e))
- else
- handle(e)
- end
+ end
+ elseif type(e) == "string" then
+ if textconverter then
+ handle(textconverter(e))
else
- for i=1,#e do
- serialize(e[i],handle,textconverter,attributeconverter,specialconverter,nocommands)
+ handle(e)
+ end
+ else
+ for i=1,#e do
+ local ei = e[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
+ else
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
end
end
end
+end
- xml.serialize = serialize
+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" )
+function xml.checkbom(root) -- can be made faster
+ if root.ri then
+ local dt, found = root.dt, false
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "table" and v.special and v.tg == "@pi" and find(v.dt,"xml.*version=") then
+ found = true
+ break
end
end
+ if not found then
+ insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } )
+ insert(dt, 2, "\n" )
+ end
end
+end
- --[[ldx--
- <p>At the cost of some 25% runtime overhead you can first convert the tree to a string
- and then handle the lot.</p>
- --ldx]]--
+--[[ldx--
+<p>At the cost of some 25% runtime overhead you can first convert the tree to a string
+and then handle the lot.</p>
+--ldx]]--
- function xml.tostring(root) -- 25% overhead due to collecting
- if root then
+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)
+ serialize(root,function(s) result[#result+1] = s end) -- brrr, slow (direct printing is faster)
return concat(result,"")
end
end
- return ""
- end
-
+ return ""
end
--[[ldx--
@@ -2883,6 +3587,18 @@ function xml.content(root) -- bugged
return (root and root.dt and xml.tostring(root.dt)) or ""
end
+function xml.isempty(root, pattern)
+ if pattern == "" or pattern == "*" then
+ pattern = nil
+ end
+ if pattern then
+ -- todo
+ return false
+ else
+ return not root or not root.dt or #root.dt == 0 or root.dt == ""
+ end
+end
+
--[[ldx--
<p>The next helper erases an element but keeps the table as it is,
and since empty strings are not serialized (effectively) it does
@@ -2919,334 +3635,394 @@ function xml.assign(dt,k,root)
end
end
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-pth'] = {
+ 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"
+}
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, lower, gmatch, gsub, find = string.format, string.lower, string.gmatch, string.gsub, string.find
+
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers. Here we overload a previously defined
+function.</p>
+--ldx]]--
+
+local trace_lpath = false
+
+if trackers then
+ trackers.register("xml.lpath", function(v) trace_lpath = v end)
+end
+
+local settrace = xml.settrace -- lxml-tab
+
+function xml.settrace(str,value)
+ if str == "lpath" then
+ trace_lpath = value or false
+ else
+ settrace(str,value) -- lxml-tab
+ end
+end
+
--[[ldx--
<p>We've now arrived at an intersting part: accessing the tree using a subset
of <l n='xpath'/> and since we're not compatible we call it <l n='lpath'/>. We
will explain more about its usage in other documents.</p>
--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 lpathcalls = 0 -- statistics
+local lpathcached = 0 -- statistics
+
+xml.functions = xml.functions or { }
+xml.expressions = xml.expressions or { }
+
+local functions = xml.functions
+local expressions = xml.expressions
+
+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",
+}
+
+-- a rather dumb lpeg
+
+local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
+
+-- instead of using functions we just parse a few names which saves a call
+-- later on
+
+local lp_position = P("position()") / "ps"
+local lp_index = P("index()") / "id"
+local lp_text = P("text()") / "tx"
+local lp_name = P("name()") / "(ns~='' and ns..':'..tg)" -- "((rt.ns~='' and rt.ns..':'..rt.tg) or '')"
+local lp_tag = P("tag()") / "tg" -- (rt.tg or '')
+local lp_ns = P("ns()") / "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_lua_function = C(R("az","AZ","--","__")^1 * (P(".") * R("az","AZ","--","__")^1)^1) * P("(") / function(t) -- todo: better . handling
+ return t .. "("
+end
+
+local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t) -- todo: better . handling
+ if expressions[t] then
+ return "expressions." .. t .. "("
+ else
+ return "expressions.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) -- lpeg.P{"("*C(((1-S("()"))+V(1))^0)*")"}
- --~ 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 .. "("
+-- if we use a dedicated namespace then we don't need to pass rt and k
+
+local lp_special = (C(P("name")+P("text")+P("tag"))) * value / function(t,s)
+ if expressions[t] then
+ if s then
+ return "expressions." .. t .. "(r,k," .. s ..")"
else
- return "functions.error("
+ return "expressions." .. t .. "(r,k)"
end
+ else
+ return "expressions.error(" .. t .. ")"
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 converter = lpeg.Cs ( (
+ lp_position +
+ lp_index +
+ lp_text + lp_name + -- fast one
+ lp_special +
+ lp_noequal + lp_doequal +
+ lp_attribute +
+ lp_lua_function +
+ lp_function +
+1 )^1 )
---~ local value = P { "(" * C(((1 - S("()")) + V(1))^0) * ")" }
+-- expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1
- 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 template = [[
+ return function(expressions,r,d,k,e,dt,ns,tg,id,ps)
+ local at, tx = e.at or { }, dt[1] or ""
+ return %s
+ end
+]]
- local grammar = P { "startup",
- startup = (initial + documentroot + subtreeroot + roottag + docroottag + subroottag)^0 * V("followup"),
- followup = ((slash + parenttag + childtag + selftag)^0 * selector)^1,
- }
+local function make_expression(str)
+ str = converter:match(str)
+ return str, loadstring(format(template,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 = 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 + -- brrr, not here !
+ 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 +
+ many + any +
+ 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
+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
- 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 })
+ 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
- -- print((table.serialize(map)):gsub("[ \n]+"," "))
- return map
+ 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
+ insert(map, 1, { 16 })
+ end
+ -- print(gsub(table.serialize(map),"[ \n]+"," "))
+ return map
end
end
+end
- local cache = { }
+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
+function xml.lpath(pattern,trace)
+ lpathcalls = lpathcalls + 1
+ if type(pattern) == "string" then
+ local result = cache[pattern]
+ if result == nil then -- can be false which is valid -)
+ result = compose(pattern)
+ cache[pattern] = result
+ lpathcached = lpathcached + 1
end
+ if trace or 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.cached_patterns()
+ return cache
+end
- 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
+-- we run out of locals (limited to 200)
+--
+-- local fallbackreport = (texio and texio.write) or io.write
+
+function xml.lshow(pattern,report)
+-- report = report or fallbackreport
+ report = report or (texio and texio.write) or io.write
+ 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=1,#lp do
+ local v = lp[k]
+ 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
- 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
+ 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("<!-- no element -->\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
+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
+ local report = (type(t[#t]) == "function" and t[#t]) or (texio and texio.write) or io.write
+ if e == nil then
+ report("<!-- no element -->\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
--[[ldx--
@@ -3264,133 +4040,174 @@ advance what we want to do with the found element the handle gets three argument
functions.</p>
--ldx]]--
-do
+local functions = xml.functions
+local expressions = xml.expressions
- local functions = xml.functions
+expressions.contains = string.find
+expressions.find = string.find
+expressions.upper = string.upper
+expressions.lower = string.lower
+expressions.number = tonumber
+expressions.boolean = toboolean
- functions.contains = string.find
- functions.find = string.find
- functions.upper = string.upper
- functions.lower = string.lower
- functions.number = tonumber
- functions.boolean = toboolean
+expressions.oneof = function(s,...) -- slow
+ local t = {...} for i=1,#t do if s == t[i] then return true end end return false
+end
- 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
+expressions.error = function(str)
+ xml.error_handler("unknown function in lpath expression",str or "?")
+ 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(d,k,n) -- ns + tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 1
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
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ break
+ else
+ n = n - 1
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
+ end
+ if found then
+ local ns, tg = found.rn or found.ns or "", found.tg
+ if ns ~= "" then
+ return ns .. ":" .. tg
else
- return ""
+ 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
+functions.tag = function(d,k,n) -- only tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 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
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ break
+ else
+ n = n - 1
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
+ return (found and found.tg) or ""
+end
+
+expressions.text = functions.text
+expressions.name = functions.name
+expressions.tag = functions.tag
+
+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
- elseif command == 11 then -- parent
+ 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
@@ -3398,38 +4215,51 @@ do
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
+ 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
- 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
+ elseif reverse and index == #pattern then
+ start, stop, step = stop, start, -1
+ end
+ local idx = 0
+ local hsh = { } -- this will slooow down the lot
+ 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
+ -- we can optimize this for simple searches, but it probably does not pay off
+ hsh[tg] = (hsh[tg] or 0) + 1
+ 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
@@ -3439,193 +4269,166 @@ do
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
+ 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
- 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
+ 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
- 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
+ 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
- -- 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
+ 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
- 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
+ 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](expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1)
+ 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
- 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
+ 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
- end
- else
-- not here : 11
- if command == 11 then -- parent
+ elseif 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
+ 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
- break -- else loop
+ 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
- return true
end
-
- xml.traverse = traverse
-
+ return true
end
+xml.traverse = traverse
+
--[[ldx--
<p>Next come all kind of locators and manipulators. The most generic function here
is <t>xml.filter(root,pattern)</t>. All registers functions in the filters namespace
@@ -3636,399 +4439,414 @@ local r, d, k = xml.filter(root,"/a/b/c/position(4)"
</typing>
--ldx]]--
-do
+local traverse, lpath, convert = xml.traverse, xml.lpath, xml.convert
- local traverse, lpath, convert = xml.traverse, xml.lpath, xml.convert
+xml.filters = { }
- 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.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
+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 { }, rt, dt, dk
+ return ekat, rt, dt, dk
end
+ else
+ return { }, rt, dt, dk
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.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
- 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 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[gsub(arguments,"^([\"\'])(.*)%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
- 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
+end
- --[[ldx--
- <p>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.</p>
- --ldx]]--
+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
- -- not faster but hipper ... although ... i can't get rid of the trailing / in the path
+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
- local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
+--[[ldx--
+<p>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.</p>
+--ldx]]--
- 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
+-- not faster but hipper ... although ... i can't get rid of the trailing / in the path
- local parser = direct + action + attribute
+local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
- local filters = xml.filters
- local attribute_filter = xml.filters.attributes
- local default_filter = xml.filters.default
+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
- -- todo: also hash, could be gc'd
+local parser = direct + action + attribute
- 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--
- <p>The following functions collect elements and texts.</p>
- --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
+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 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--
+<p>The following functions collect elements and texts.</p>
+--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)
- 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
+ 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] = tx or ""
+ t[#t+1] = ""
end
else
- t[#t+1] = ""
+ t[#t+1] = tx or ""
end
- end)
- return t
- 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
+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)
- return #t > 0 and {}
- end
+ end
+ end)
+ return #t > 0 and {}
+end
- --[[ldx--
- <p>Often using an iterators looks nicer in the code than passing handler
- functions. The <l n='lua'/> book describes how to use coroutines for that
- purpose (<url href='http://www.lua.org/pil/9.3.html'/>). This permits
- code like:</p>
+--[[ldx--
+<p>Often using an iterators looks nicer in the code than passing handler
+functions. The <l n='lua'/> book describes how to use coroutines for that
+purpose (<url href='http://www.lua.org/pil/9.3.html'/>). This permits
+code like:</p>
- <typing>
- for r, d, k in xml.elements(xml.load('text.xml'),"title") do
- print(d[k])
- end
- </typing>
+<typing>
+for r, d, k in xml.elements(xml.load('text.xml'),"title") do
+ print(d[k])
+end
+</typing>
- <p>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:</p>
+<p>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:</p>
- <typing>
- 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
+<typing>
+function xml.filters.first(root,pattern)
+ for rt,dt,dk in xml.elements(root,pattern)
+ return dt and dt[dk], rt, dt, dk
end
- </typing>
+ return nil, nil, nil, nil
+end
+</typing>
- <p>We use the function variants in the filters.</p>
- --ldx]]--
+<p>We use the function variants in the filters.</p>
+--ldx]]--
- local wrap, yield = coroutine.wrap, coroutine.yield
+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(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.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.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
+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
+ 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
+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--
- <p>We've now arrives at the functions that manipulate the tree.</p>
- --ldx]]--
+--[[ldx--
+<p>We've now arrives at the functions that manipulate the tree.</p>
+--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
+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
- element = element.dt
+ be, af = edt, xml.copy(element)
end
- if r.ri then
- edt = r.dt[r.ri].dt
- else
- edt = d and d[k] and d[k].dt
+ for i=1,#af do
+ be[#be+1] = af[i]
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
+ if r.ri then
+ r.dt[r.ri].dt = be
else
- -- r.dt = element.dt -- todo
+ d[k].dt = be
end
+ else
+ -- r.dt = element.dt -- todo
end
end
end
end
+end
- -- todo: copy !
+-- 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)
+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
+ insert(d,k,element) -- untested
+--~ elseif element.dt then
+--~ for _,v in ipairs(element.dt) do -- i added
+--~ insert(d,k,v)
+--~ k = k + 1
+--~ end
+--~ end
+ else
+ local edt = element.dt
+ if edt then
+ for i=1,#edt do
+ insert(d,k,edt[i])
k = k + 1
end
end
@@ -4037,164 +4855,176 @@ do
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
+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
+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] = 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
+function xml.replace_element(root, pattern, element)
+ if type(element) == "string" then
+ element = convert(element,true)
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
+ 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
- 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
+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 gmatch(attribute or "href","([^|]+)") do
+ name = ek.at[a]
+ if name then break end
end
end
- local data = (name and name ~= "" and loaddata(name)) or ""
- if data == "" then
+ 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)
- 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)
+ if recursive then
+ xml.include(xi,pattern,attribute,recursive,loaddata)
end
+ xml.assign(d,k,xi)
end
end
- xml.each_element(xmldata, pattern, include)
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
+function xml.strip_whitespace(root, pattern, nolines) -- strips all leading and trailing space !
+ 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" then
+
+ if str == "" then
-- stripped
else
- t[#t+1] = str
+ if nolines then
+ str = gsub(str,"[ \n\r\t]+"," ")
+ end
+ if str == "" then
+ -- stripped
+ else
+ t[#t+1] = str
+ end
end
+ else
+ t[#t+1] = str
end
- d[k].dt = t
end
- 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)
+local function rename_space(root, oldspace, newspace) -- fast variant
+ local ndt = #root.dt
+ 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_space(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
+xml.rename_space = rename_space
- 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
+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
--[[ldx--
@@ -4232,10 +5062,12 @@ put them here instead of loading mode modules there then needed.</p>
--ldx]]--
function xml.gsub(t,old,new)
- if t.dt then
- for k,v in ipairs(t.dt) do
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
if type(v) == "string" then
- t.dt[k] = v:gsub(old,new)
+ dt[k] = gsub(v,old,new)
else
xml.gsub(v,old,new)
end
@@ -4260,45 +5092,41 @@ end
--~ xml.escapes = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
--~ 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
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
- local P, S, R, C, V, Cc, Cs = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc, lpeg.Cs
+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)
+-- 100 * 2500 * "oeps< oeps> oeps&" : gsub:lpeg|lpeg|lpeg
+--
+-- 1021:0335:0287:0247
- -- escaped = Cs((S("<&>") / xml.escapes + 1)^0)
- -- escaped = Cs((S("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
- local normal = (1 - S("<&>"))^0
- local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
- local escaped = Cs(normal * (special * normal)^0)
+-- 10 * 1000 * "oeps< oeps> oeps& asfjhalskfjh alskfjh alskfjh alskfjh ;al J;LSFDJ"
+--
+-- 1559:0257:0288:0190 (last one suggested by roberto)
- -- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+-- escaped = Cs((S("<&>") / xml.escapes + 1)^0)
+-- escaped = Cs((S("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
- -- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
- -- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
- local normal = (1 - S"&")^0
- local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
- local unescaped = Cs(normal * (special * normal)^0)
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
- -- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> oeps </oeps> oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
- local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> oeps </oeps> oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
- 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
+local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
-end
+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
function xml.join(t,separator,lastseparator)
if #t > 0 then
@@ -4316,116 +5144,12 @@ function xml.join(t,separator,lastseparator)
end
end
-
---[[ldx--
-<p>We provide (at least here) two entity handlers. The more extensive
-resolver consults a hash first, tries to convert to <l n='utf'/> next,
-and finaly calls a handler when defines. When this all fails, the
-original entity is returned.</p>
---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("&#x.-;") then
- d[k] = dk:gsub("&#x(.-);",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("&#x(.-);",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
+function xml.statistics()
+ return {
+ lpathcalls = lpathcalls,
+ lpathcached = lpathcached,
+ }
+end
-- xml.set_text_cleanup(xml.show_text_entities)
-- xml.set_text_cleanup(xml.resolve_text_entities)
@@ -4446,7 +5170,7 @@ end end
--~ </a>
--~ ]])
---~ xml.trace_lpath = true
+--~ xml.settrace("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')]"))
@@ -4473,223 +5197,536 @@ end end
--~ 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
+end -- of closure
-if not versions then versions = { } end versions['l-utils'] = 1.001
+do -- create closure to overcome 200 locals limit
-if not utils then utils = { } end
-if not utils.merger then utils.merger = { } end
-if not utils.lua then utils.lua = { } end
+if not modules then modules = { } end modules ['lxml-ent'] = {
+ 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"
+}
-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+"
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub, find = string.format, string.gsub, string.find
+local utfchar = unicode.utf8.char
-function utils.merger._self_fake_()
- return
- "-- " .. "created merged file" .. "\n\n" ..
- "-- " .. utils.merger.m_begin .. "\n\n" ..
- "-- " .. utils.merger.m_end .. "\n\n"
-end
+--[[ldx--
+<p>We provide (at least here) two entity handlers. The more extensive
+resolver consults a hash first, tries to convert to <l n='utf'/> next,
+and finaly calls a handler when defines. When this all fails, the
+original entity is returned.</p>
+--ldx]]--
-function utils.report(...)
- print(...)
+xml.entities = xml.entities or { } -- xml.entity_handler == function
+
+function xml.entity_handler(e)
+ return format("[%s]",e)
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 ""
+local function toutf(s)
+ return utfchar(tonumber(s,16))
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()
+local 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 find(dk,"&#x.-;") then
+ d[k] = gsub(dk,"&#x(.-);",toutf)
+ end
+ else
+ utfize(dk)
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))
+xml.utfize = utfize
+
+local function resolve(e) -- hex encoded always first, just to avoid mkii fallbacks
+ if find(e,"^#x") then
+ return utfchar(tonumber(e:sub(3),16))
+ elseif find(e,"^#") then
+ return utfchar(tonumber(e:sub(2)))
else
- return ""
+ local ee = xml.entities[e] -- we cannot shortcut this one (is reloaded)
+ if ee then
+ return ee
+ else
+ local h = xml.entity_handler
+ return (h and h(e)) or "&" .. e .. ";"
+ end
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
+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 find(dk,"&.-;") then
+ d[k] = gsub(dk,"&(.-);",resolve)
+ end
else
- -- utils.report("no library",name)
+ resolve_entities(dk)
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)
- )
- )
+xml.resolve_entities = resolve_entities
+
+function xml.utfize_text(str)
+ if find(str,"&#") then
+ return (gsub(str,"&#x(.-);",toutf))
+ else
+ return str
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)
- )
- )
+function xml.resolve_text_entities(str) -- maybe an lpeg. maybe resolve inline
+ if find(str,"&") then
+ return (gsub(str,"&(.-);",resolve))
+ else
+ return str
+ end
end
-function utils.merger.selfclean(name)
- utils.merger._self_save_(
- name,
- utils.merger._self_swap_(
- utils.merger._self_load_(name),
- ""
- )
- )
+function xml.show_text_entities(str)
+ if find(str,"&") then
+ return (gsub(str,"&(.-);","[%1]"))
+ else
+ return str
+ end
end
-utils.lua.compile_strip = true
+-- experimental, this will be done differently
-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
+function xml.merge_entities(root)
+ local documententities = root.entities
+ local allentities = xml.entities
+ if documententities then
+ for k, v in next, documententities do
+ allentities[k] = v
+ end
end
- if os.spawn("texluac " .. command) == 0 then
- return true
- elseif os.spawn("luac " .. command) == 0 then
- return true
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-mis'] = {
+ 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"
+}
+
+local concat = table.concat
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub = string.format, string.gsub
+
+--[[ldx--
+<p>The following helper functions best belong to the <t>lmxl-ini</t>
+module. Some are here because we need then in the <t>mk</t>
+document and other manuals, others came up when playing with
+this module. Since this module is also used in <l n='mtxrun'/> we've
+put them here instead of loading mode modules there then needed.</p>
+--ldx]]--
+
+function xml.gsub(t,old,new)
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "string" then
+ dt[k] = gsub(v,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 = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
+
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
+
+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("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> oeps </oeps> oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
+
+local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
+
+xml.escaped_pattern = escaped
+xml.unescaped_pattern = unescaped
+xml.cleansed_pattern = cleansed
+
+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
+
+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 false
+ return ""
end
end
+end -- of closure
--- 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
+do -- create closure to overcome 200 locals limit
-if not versions then versions = { } end versions['luat-lib'] = 1.001
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
--- mostcode moved to the l-*.lua and other luat-*.lua files
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
--- os / io
+debugger = debugger or { }
-os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
+ end
+ end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
--- os.platform
+-- two
--- mswin|bccwin|mingw|cygwin windows
--- darwin|rhapsody|nextstep macosx
--- netbsd|unix unix
--- linux linux
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
-if not io.fileseparator then
- if string.find(os.getenv("PATH"),";") then
- io.fileseparator, io.pathseparator, os.platform = "\\", ";", os.type or "windows"
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
else
- io.fileseparator, io.pathseparator, os.platform = "/" , ":", os.type or "unix"
+ function debugger.tracing() return false end ; return false
end
end
-os.platform = os.platform or os.type or (io.pathseparator == ";" and "windows") or "unix"
+--~ debugger.enable()
--- arg normalization
---
--- for k,v in pairs(arg) do print(k,v) end
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
--- environment
+--~ debugger.disable()
-if not environment then environment = { } end
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
-environment.ownbin = environment.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+trackers = trackers or { }
-local ownpath = nil -- we could use a metatable here
+local data, done = { }, { }
-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
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
end
end
- if not ownpath then ownpath = '.' end
end
- return ownpath
end
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
+ end
+end
+
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
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
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
-environment.platform = os.platform
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
function environment.initialize_arguments(arg)
- environment.arguments = { }
- environment.files = { }
- environment.sorted_argument_keys = nil
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, 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 "")
+ arguments[flag] = string.unquote(value or "")
else
flag = argument:match("^%-+(.+)")
if flag then
- environment.arguments[flag] = true
+ arguments[flag] = true
else
- environment.files[#environment.files+1] = argument
+ files[#files+1] = argument
end
end
end
@@ -4697,32 +5734,31 @@ function environment.initialize_arguments(arg)
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
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
if name:find(v) then
- return environment.arguments[v:sub(2,#v)]
+ return arguments[v:sub(2,#v)]
end
end
end
@@ -4743,355 +5779,899 @@ function environment.split_arguments(separator) -- rather special, cut-off befor
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)
+function environment.reconstruct_commandline(arg,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
else
result[#result+1] = a
end
- elseif a:find(" ") then
- result[#result+1] = string.quote(a)
- else
- result[#result+1] = a
end
+ return table.join(result," ")
+ else
+ return ""
end
- return table.join(result," ")
end
if arg then
- environment.initialize_arguments(arg)
- environment.original_arguments = arg
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
arg = { } -- prevent duplicate handling
+
end
+-- weird place ... depends on a not yet loaded module
--- 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
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
--- 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.
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
--- 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
+environment.loadedluacode = loadfile -- can be overloaded
--- This is the first code I wrote for LuaTeX, so it needs some cleanup.
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
--- 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.
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
--- Beware, loading and saving is overloaded in luat-tmp!
+-- the next ones can use the previous ones / combine
-if not versions then versions = { } end versions['luat-inp'] = 1.001
-if not environment then environment = { } end
-if not file then file = { } end
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+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
+end -- of closure
-if not input.suffixmap then input.suffixmap = { } end
+do -- create closure to overcome 200 locals limit
-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
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
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' }
+local statusinfo, n, registered = { }, 0, { }
--- here we catch a few new thingies (todo: add these paths to context.tmf)
---
--- FONTFEATURES = .;$TEXMF/fonts/fea//
--- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+statistics = statistics or { }
-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
+statistics.enable = true
+statistics.threshold = 0.05
--- backward compatible ones
+-- timing functions
-input.alternatives = { }
+local clock = os.gettimeofday or os.clock
-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'
+local notimer
--- obscure ones
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
-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)
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
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
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
end
end
end
+ return 0
+end
- -- cross referencing
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
- for k, v in pairs(input.suffixes) do
- for _, vv in pairs(v) do
- if vv then
- input.suffixmap[vv] = k
+-- general function
+
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
end
end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
end
+end
- return instance
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
end
-function input.reset_hashes(instance)
- instance.lists = { }
- instance.found = { }
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
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"))
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
end
-if texio then
- input.log = texio.write_nl
-else
- input.log = print
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
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
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
+ else
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
else
- if input.banner then
- input.log(input.banner..kind..": no name")
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
else
- input.log("<<"..kind..": no name>>")
+ write(format("[%s",real))
end
+ else
+ write("[-")
end
end
-function input.dummy_logger()
+function logs.tex.stop_page_number()
+ write("]")
end
-function input.settrace(n)
- input.trace = tonumber(n or 0)
- if input.trace > 0 then
- input.logger = input.simple_logger
- input.verbose = true
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
else
- input.logger = function() end
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
+ else
+ write_nl("<r/>")
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("<t>"..table.concat({...},' ').."</t>")
- else
- input.log("<<"..table.concat({...},' ')..">>")
- 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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
end
end
-function input.reportlines(str)
- if type(str) == "string" then
- str = str:split("\n")
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
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))
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
+
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
--- These functions can be used to test the performance, especially
--- loading the database files.
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
-do
- local clock = os.gettimeofday or os.clock
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
- function input.starttiming(instance)
- if instance then
- instance.starttime = clock()
- if not instance.loadtime then
- instance.loadtime = 0
- end
- end
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
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
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
end
- return 0
end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
end
-function input.elapsedtime(instance)
- return format("%0.3f",(instance and instance.loadtime) or 0)
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
+ end
+
+ return newinstance
+
end
-function input.report_loadtime(instance)
- if instance then
- input.report('total load time', input.elapsedtime(instance))
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
+end
+
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
end
-input.loadtime = input.elapsedtime
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
-function input.env(instance,key)
- return instance.environment[key] or input.osenv(instance,key)
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
end
-function input.osenv(instance,key)
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(key)
local ie = instance.environment
local value = ie[key]
if value == nil then
@@ -5100,174 +6680,291 @@ function input.osenv(instance,key)
if e == nil then
-- value = "" -- false
else
- value = input.bare_variable(e)
+ value = resolvers.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
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
--
--- 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
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
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
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
end
- locate(input.luaname,instance.luafiles)
- locate(input.cnfname,instance.cnffiles)
end
end
+ return ""
end
-function input.load_cnf(instance)
- local function loadoldconfigdata()
- for _, fname in ipairs(instance.cnffiles) do
- input.aux.load_cnf(instance,fname)
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
end
+ return "{" .. concat(t,",") .. "}"
end
- -- instance.cnffiles contain complete names now !
- if #instance.cnffiles == 0 then
- input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
else
- instance.rootpath = instance.cnffiles[1]
- for k,fname in ipairs(instance.cnffiles) do
- instance.cnffiles[k] = input.normalize_name(fname:gsub("\\",'/'))
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
end
- for i=1,3 do
- instance.rootpath = file.dirname(instance.rootpath)
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
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
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
else
- loadoldconfigdata()
- if instance.renewcache then
- input.saveoldconfig(instance)
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
end
end
- input.aux.collapse_cnf_data(instance)
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
end
- input.checkconfigdata(instance)
+ return resolvers.ownpath
end
-function input.load_lua(instance)
- if #instance.luafiles == 0 then
- -- yet harmless
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
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)
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
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)
+ identify_own = function() end
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)
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
end
end
end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
end
end
-function input.aux.load_cnf(instance,fname)
- fname = input.clean_path(fname)
- local lname = fname:gsub("%.%a+$",input.luasuffix)
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
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)
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
instance.order[#instance.order+1] = instance.configuration[dname]
end
else
f = io.open(fname)
if f then
- input.report("loading", fname)
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
local line, data, n, k, v
local dname = file.dirname(fname)
if not instance.configuration[dname] then
@@ -5279,17 +6976,19 @@ function input.aux.load_cnf(instance,fname)
local line, n = f:read(), 0
if line then
while true do -- join lines
- line, n = line:gsub("\\%s*$", "")
+ line, n = gsub(line,"\\%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 not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
if k and v and not data[k] then
- data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
instance.kpsevars[k] = true
end
end
@@ -5298,236 +6997,261 @@ function input.aux.load_cnf(instance,fname)
end
end
f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
else
- input.report("skipping", fname)
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
end
+ collapse_cnf_data()
end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
+ end
+ check_configuration()
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)
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
if instance.loaderror then
- input.loadlists(instance)
- input.savefiles(instance)
+ resolvers.loadlists()
+ resolvers.savefiles()
end
else
- input.loadlists(instance)
+ resolvers.loadlists()
if instance.renewcache then
- input.savefiles(instance)
+ resolvers.savefiles()
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 } )
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ 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 } )
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ 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)
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
else
- input.report("setting configuration variable TEXMF to", specification)
- instance.variables['TEXMF'] = "{" .. specification .. "}"
+ -- weird
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)
+ resolvers.expand_variables()
+ reset_hashes()
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))
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
end
end
-function input.locatedatabase(instance,specification)
- return input.methodhandler('locators', instance, specification)
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
end
-function input.locators.tex(instance,specification)
+function resolvers.locators.tex(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')
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
end
end
-- hashers
-function input.hashdatabase(instance,tag,name)
- return input.methodhandler('hashers',instance,tag,name)
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
end
-function input.loadfiles(instance)
+function resolvers.loadfiles()
instance.loaderror = false
instance.files = { }
if not instance.renewcache then
for _, hash in ipairs(instance.hashes) do
- input.hashdatabase(instance,hash.tag,hash.name)
+ resolvers.hashdatabase(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)
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
end
-- generators:
-function input.loadlists(instance)
+function resolvers.loadlists()
for _, hash in ipairs(instance.hashes) do
- input.generatedatabase(instance,hash.tag)
+ resolvers.generatedatabase(hash.tag)
end
end
-function input.generatedatabase(instance,specification)
- return input.methodhandler('generators', instance, specification)
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
end
-do
+-- starting with . or .. etc or funny char
- local weird = lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+local weird = lpeg.P(".")^1 + 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)
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
else
- action(name)
+ f[#f+1] = path
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
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
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
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
else
- path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ action(name)
end
end
- f:close()
end
end
end
-
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ 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)
+function resolvers.savefiles()
+ resolvers.save_data('files')
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)
+function resolvers.splitconfig()
for i,c in ipairs(instance) do
for k,v in pairs(c) do
if type(v) == 'string' then
@@ -5539,23 +7263,24 @@ function input.splitconfig(instance)
end
end
end
-function input.joinconfig(instance)
+
+function resolvers.joinconfig()
for i,c in ipairs(instance.order) do
- for k,v in pairs(c) do
+ for k,v in pairs(c) do -- ipairs?
if type(v) == 'table' then
c[k] = file.join_path(v)
end
end
end
end
-function input.split_path(str)
+function resolvers.split_path(str)
if type(str) == 'table' then
return str
else
return file.split_path(str)
end
end
-function input.join_path(str)
+function resolvers.join_path(str)
if type(str) == 'table' then
return file.join_path(str)
else
@@ -5563,48 +7288,47 @@ function input.join_path(str)
end
end
-function input.splitexpansions(instance)
- for k,v in pairs(instance.expansions) do
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
local t, h = { }, { }
- for _,vv in pairs(file.split_path(v)) do
+ for _,vv in ipairs(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
+ ie[k] = t
else
- instance.expansions[k] = t[1]
+ ie[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)
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
end
-input.configbanner = [[
+resolvers.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)
+function resolvers.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)
+ local function dump(k,v,m) -- could be moved inline
if type(v) == 'string' then
return m .. "['" .. k .. "']='" .. v .. "',"
elseif #v == 1 then
@@ -5615,11 +7339,11 @@ function input.serialize(files)
end
t[#t+1] = "return {"
if instance.sortdata then
- for _, k in pairs(sorted(files)) do
+ for _, k in pairs(sortedkeys(files)) do -- ipairs
local fk = files[k]
if type(fk) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
- for _, kk in pairs(sorted(fk)) do
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
t[#t+1] = dump(kk,fk[kk],"\t\t")
end
t[#t+1] = "\t},"
@@ -5628,10 +7352,10 @@ function input.serialize(files)
end
end
else
- for k, v in pairs(files) do
+ for k, v in next, files do
if type(v) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
- for kk,vv in pairs(v) do
+ for kk,vv in next, v do
t[#t+1] = dump(kk,vv,"\t\t")
end
t[#t+1] = "\t},"
@@ -5644,62 +7368,67 @@ function input.serialize(files)
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
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, 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
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
end
end
local data = {
type = dataname,
root = cachename,
- version = input.cacheversion,
+ version = resolvers.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)
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
os.remove(lucname)
end
- else
- input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
end
end
end
-function input.aux.load_data(instance,pathname,dataname,filename,makename) -- untested without cache overload
+function resolvers.load_data(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)
+ local blob = loadfile(filename .. ".luc") or loadfile(filename .. ".lua")
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)
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
instance[dataname][pathname] = data.content
else
- input.report("skipping",dataname,"for",pathname,"from",filename)
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
instance[dataname][pathname] = { }
instance.loaderror = true
end
- else
- input.report("skipping",dataname,"for",pathname,"from",filename)
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
end
end
@@ -5712,198 +7441,139 @@ end
-- 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
+function resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
end
end
end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
end
- instance[dataname][pathname] = t
else
- instance[dataname][pathname] = data
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
end
- else
- input.report("skipping","configuration file",filename)
- instance[dataname][pathname] = { }
- instance.loaderror = true
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
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]
+ instance.order[#instance.order+1] = instance.setup[pathname]
if instance.loaderror then break end
end
end
-function input.loadoldconfig(instance)
+function resolvers.loadoldconfig()
if not instance.renewcache then
for _, cnf in ipairs(instance.cnffiles) do
local dname = file.dirname(cnf)
- input.aux.load_configuration(instance,dname)
+ resolvers.load_data(dname,'configuration')
instance.order[#instance.order+1] = instance.configuration[dname]
if instance.loaderror then break end
end
end
- input.joinconfig(instance)
+ resolvers.joinconfig()
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*$")
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
if a and b then
- instance.expansions[a..'.'..b] = v
+ expansions[a..'.'..b] = v
else
- instance.expansions[k] = v
+ 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
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then 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
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
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)
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
if n > 0 or m > 0 then
- instance.expansions[k]= s
+ expansions[k]= s
end
end
if not busy then break end
end
- local homedir =
- instance.environment[(os.type == "windows" and 'USERPROFILE') or 'HOME'] or '~'
- for k,v in pairs(instance.expansions) do
- v = v:gsub("^~", homedir)
- instance.expansions[k] = v:gsub("\\", '/')
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
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
+function resolvers.variable(name)
+ return entry(instance.variables,name)
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)
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
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)
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,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
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
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)
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
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
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
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 -- no longer needed
-do
local done = { }
- function input.reset_extra_path(instance)
+ function resolvers.reset_extra_path()
local ep = instance.extra_paths
if not ep then
ep, done = { }, { }
@@ -5913,25 +7583,25 @@ do
end
end
- function input.register_extra_path(instance,paths,subpaths)
+ function resolvers.register_extra_path(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
+ for p in gmatch(paths,"[^,]+") do
-- we gmatch each step again, not that fast, but used seldom
- for s in subpaths:gmatch("[^,]+") do
+ for s in gmatch(subpaths,"[^,]+") do
local ps = p .. "/" .. s
if not done[ps] then
- ep[#ep+1] = input.clean_path(ps)
+ ep[#ep+1] = resolvers.clean_path(ps)
done[ps] = true
end
end
end
else
- for p in paths:gmatch("[^,]+") do
+ for p in gmatch(paths,"[^,]+") do
if not done[p] then
- ep[#ep+1] = input.clean_path(p)
+ ep[#ep+1] = resolvers.clean_path(p)
done[p] = true
end
end
@@ -5939,10 +7609,10 @@ do
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
+ for s in gmatch(subpaths,"[^,]+") do
local ps = ep[i] .. "/" .. s
if not done[ps] then
- ep[#ep+1] = input.clean_path(ps)
+ ep[#ep+1] = resolvers.clean_path(ps)
done[ps] = true
end
end
@@ -5958,311 +7628,196 @@ do
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
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
done[v] = true
new[#new+1] = v
+ else
+ break
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
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
end
- return new
end
+ return new
end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
if not str then
return ep or { }
elseif instance.savelists then
-- engine+progname hash
- str = str:gsub("%$","")
+ str = gsub(str,"%$","")
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)
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(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))
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(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("%$",""))
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
if tmp ~= "" then
- return input.expanded_path_list(instance,str)
+ return resolvers.expanded_path_list(str)
else
- return input.expanded_path_list(instance,tmp)
+ return resolvers.expanded_path_list(tmp)
end
end
-function input.expand_path_from_var(instance,str)
- return file.join_path(input.expanded_path_list_from_var(instance,str))
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
end
-function input.format_of_var(str)
- return input.formats[str] or input.formats[input.alternatives[str]] or ''
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
end
-function input.format_of_suffix(str)
- return input.suffixmap[file.extname(str)] or 'tex'
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
end
-function input.variable_of_format(str)
- return input.formats[str] or input.formats[input.alternatives[str]] or ''
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
end
-function input.var_of_format_or_suffix(str)
- local v = input.formats[str]
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
if v then
return v
end
- v = input.formats[input.alternatives[str]]
+ v = formats[alternatives[str]]
if v then
return v
end
- v = input.suffixmap[file.extname(str)]
+ v = suffixmap[file.extname(str)]
if v then
- return input.formats[isf]
+ return 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))
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.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 = { }
+resolvers.isreadable = { }
-function input.aux.is_readable(readable, name)
- if input.trace > 2 then
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
if readable then
- input.logger("+ readable", name)
+ logs.report("fileio","+ readable: %s",name)
else
- input.logger("- readable", name)
+ logs.report("fileio","- readable: %s", 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
+resolvers.isreadable.tex = resolvers.isreadable.file
-- name
-- name/name
-function input.aux.collect_files(instance,names)
+local function collect_files(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 ..")")
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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
- 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
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
end
- end
- if blobfile then
- if type(blobfile) == 'string' then
- if not dname or blobfile:find(dname) then
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
filelist[#filelist+1] = {
hash.type,
- file.join(blobpath,blobfile,bname), -- search
- input.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,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
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
end
end
end
@@ -6273,99 +7828,145 @@ function input.aux.collect_files(instance,names)
end
end
-function input.suffix_of_format(str)
- if input.suffixes[str] then
- return input.suffixes[str][1]
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
else
return ""
end
end
-function input.suffixes_of_format(str)
- if input.suffixes[str] then
- return input.suffixes[str]
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return 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
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
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
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
end
+ return (fakepaths[name] == 1)
end
--- split the next one up, better for jit
-
-function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
- local result = { }
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
local stamp = nil
- filename = input.normalize_name(filename) -- elsewhere
- filename = file.collapse_path(filename:gsub("\\","/")) -- elsewhere
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- 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)
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
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)
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
result = { filename }
else
- local forcedname, ok = "", false
- if file.extname(filename) == "" then
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
if instance.format == "" then
forcedname = filename .. ".tex"
- if input.is_readable.file(forcedname) then
- input.logger('! no suffix, forcing standard filetype tex')
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
result, ok = { forcedname }, true
end
else
- for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
forcedname = filename .. "." .. s
- if input.is_readable.file(forcedname) then
- input.logger('! no suffix, forcing format filetype', s)
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
result, ok = { forcedname }, true
break
end
end
end
end
- if not ok then
- input.logger('? qualified', filename)
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', filename)
end
end
else
@@ -6382,39 +7983,47 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
if ext == "" then
local forcedname = filename .. '.tex'
wantedfiles[#wantedfiles+1] = forcedname
- filetype = input.format_of_suffix(forcedname)
- input.logger('! forcing filetype',filetype)
+ filetype = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
else
- filetype = input.format_of_suffix(filename)
- input.logger('! using suffix based filetype',filetype)
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
end
else
if ext == "" then
- for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
wantedfiles[#wantedfiles+1] = filename .. "." .. s
end
end
filetype = instance.format
- input.logger('! using given filetype',filetype)
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
end
- local typespec = input.variable_of_format(filetype)
- local pathlist = input.expanded_path_list(instance,typespec)
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(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
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 filelist = collect_files(wantedfiles)
local fl = filelist and filelist[1]
if fl then
filename = fl[3]
@@ -6423,56 +8032,53 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
end
else
-- list search
- local filelist = input.aux.collect_files(instance,wantedfiles)
+ local filelist = collect_files(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
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
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("^!+", '')
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
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
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
local expr = "^" .. pathname
- -- input.debug('?',expr)
- for _, fl in ipairs(filelist) do
+ for k=1,#filelist do
+ local fl = filelist[k]
local f = fl[2]
- if f:find(expr) then
- -- input.debug('T',' '..f)
- if input.trace > 2 then
- input.logger('= found in hash',f)
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
end
--- todo, test for readable
result[#result+1] = fl[3]
- input.aux.register_in_trees(instance,f) -- for tracing used files
+ resolvers.register_in_trees(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
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
local fname = file.join(ppname,w)
- if input.is_readable.file(fname) then
- if input.trace > 2 then
- input.logger('= found by scanning',fname)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',fname)
end
result[#result+1] = fname
done = true
@@ -6492,8 +8098,8 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
end
end
end
- for k,v in pairs(result) do
- result[k] = file.collapse_path(v)
+ for k=1,#result do
+ result[k] = file.collapse_path(result[k])
end
if instance.remember then
instance.found[stamp] = result
@@ -6501,48 +8107,12 @@ function input.aux.find_file(instance,filename) -- todo : plugin (scanners, chec
return result
end
-input.aux._find_file_ = input.aux.find_file
+if not resolvers.concatinators then resolvers.concatinators = { } end
-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
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
-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)
+function resolvers.find_files(filename,filetype,mustexist)
if type(mustexist) == boolean then
-- all set
elseif type(filetype) == 'boolean' then
@@ -6551,18 +8121,26 @@ function input.find_files(instance,filename,filetype,mustexist)
filetype, mustexist = nil, false
end
instance.format = filetype or ''
- local t = input.aux.find_file(instance,filename,true)
+ local result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
instance.format = ''
- return t
+ return result
end
-function input.find_file(instance,filename,filetype,mustexist)
- return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
end
-function input.find_given_files(instance,filename)
+function resolvers.find_given_files(filename)
local bname, result = file.basename(filename), { }
- for k, hash in ipairs(instance.hashes) do
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
local files = instance.files[hash.tag]
local blist = files[bname]
if not blist then
@@ -6575,11 +8153,12 @@ function input.find_given_files(instance,filename)
end
if blist then
if type(blist) == 'string' then
- result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
+ result[#result+1] = resolvers.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 ""
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
if not instance.allresults then break end
end
end
@@ -6588,126 +8167,119 @@ function input.find_given_files(instance,filename)
return result
end
-function input.find_given_file(instance,filename)
- return (input.find_given_files(instance,filename)[1] or "")
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
end
-function input.find_wildcard_files(instance,filename) -- todo: remap:
+function resolvers.find_wildcard_files(filename) -- todo: remap:
local result = { }
local bname, dname = file.basename(filename), file.dirname(filename)
- local path = dname:gsub("^*/","")
- path = path:gsub("*",".*")
- path = path:gsub("-","%%-")
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
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
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
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 find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,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
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
if done and not allresults then break end
end
end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
return result
end
-function input.find_wildcard_file(instance,filename)
- return (input.find_wildcard_files(instance,filename)[1] or "")
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(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("<?xml version='1.0' standalone='yes'?>\n")
- f:write("<rl:job>\n")
- if jobname then
- f:write("\t<rl:name>" .. jobname .. "</rl:name>\n")
- end
- f:write("\t<rl:files>\n")
- for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
- f:write("\t\t<rl:file n='" .. instance.foundintrees[v] .. "'>" .. v .. "</rl:file>\n")
- end
- f:write("\t</rl:files>\n")
- f:write("</rl:usedfiles>\n")
- f:close()
- end
-end
-
-function input.automount(instance)
+function resolvers.automount()
-- 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)
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
end
-function input.for_files(instance, command, files, filetype, mustexist)
+function resolvers.for_files(command, files, filetype, mustexist)
if files and #files > 0 then
local function report(str)
- if input.verbose then
- input.report(str) -- has already verbose
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
else
print(str)
end
end
- if input.verbose then
+ if trace_verbose then
report('')
end
- for _, file in pairs(files) do
- local result = command(instance,file,filetype,mustexist)
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
if type(result) == 'string' then
report(result)
else
- for _,v in pairs(result) do
+ for _,v in ipairs(result) do
report(v)
end
end
@@ -6717,22 +8289,19 @@ 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)))
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.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)
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
-function input.aux.register_file(files, name, path)
+function resolvers.register_file(files, name, path)
if files[name] then
if type(files[name]) == 'string' then
files[name] = { files[name], path }
@@ -6744,165 +8313,77 @@ function input.aux.register_file(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)
+function resolvers.splitmethod(filename)
if not filename then
return { } -- safeguard
elseif type(filename) == "table" then
return filename -- already split
- elseif not filename:find("://") then
+ elseif not find(filename,"://") 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
+ for k, v in pairs(t) do -- pairs?
s[#s+1] = k .. "=" .. v
end
- return table.concat(s, sep or " | ")
+ return 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
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.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)
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
else
- return nil
+ return resolvers[what].tex(filename,filetype) -- todo: specification
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("//+","//"))
+function resolvers.clean_path(str)
if str then
- return ((str:gsub("\\","/")):gsub("^!+",""))
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
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))
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
end
end
-function input.do_with_var(name,func)
- func(input.aux.expanded_var(name))
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
end
-function input.with_files(instance,pattern,handle)
+function resolvers.with_files(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
+ for k,v in next, files do
+ if find(k,"^remap:") then
k = files[k]
v = files[k] -- chained
end
- if k:find(pattern) then
+ if find(k,pattern) then
if type(v) == "string" then
handle(blobtype,blobpath,v,k)
else
- for _,vv in pairs(v) do
+ for _,vv in pairs(v) do -- ipairs?
handle(blobtype,blobpath,vv,k)
end
end
@@ -6913,129 +8394,54 @@ function input.with_files(instance,pattern,handle)
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
+function resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
end
end
+ return nil, nil
end
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
+ else
+ b = toboolean(b)
+ return (b == nil and default) or b
+ end
+end
---~ print(table.serialize(input.aux.splitpathexpr("/usr/share/texmf-{texlive,tetex}", {})))
+texconfig.kpse_init = false
--- command line resolver:
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
---~ print(input.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
+-- for a while
-do
+input = resolvers
- 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
+end -- of closure
- input.resolve = resolve
+do -- create closure to overcome 200 locals limit
-end
-
-
-if not modules then modules = { } end modules ['luat-tmp'] = {
+if not modules then modules = { } end modules ['data-tmp'] = {
version = 1.001,
comment = "companion to luat-lib.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -7059,63 +8465,71 @@ being written at the same time is small. We also need to extend
luatools with a recache feature.</p>
--ldx]]--
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
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
+
+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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
end
end
- local cachepath = input.expanded_path_list(instance,"TEXMFCACHE")
- cachepath = cachepath and #cachepath > 0 and checkpath(cachepath[1])
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
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")
+ print("\nfatal error: there is no valid (writable) 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))
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
os.exit()
end
- function caches.temp(instance)
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
return cachepath
end
return cachepath
end
-function caches.configpath(instance)
- return table.concat(instance.cnffiles,";")
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
end
function caches.hashed(tree)
- return md5.hex((tree:lower()):gsub("[\\\/]+","/"))
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
end
-function caches.treehash(instance)
- local tree = caches.configpath(instance)
+function caches.treehash()
+ local tree = caches.configpath()
if not tree or tree == "" then
return false
else
@@ -7123,26 +8537,24 @@ function caches.treehash(instance)
end
end
-function caches.setpath(instance,...)
+function caches.setpath(...)
if not caches.path then
if not caches.path then
- caches.path = caches.temp(instance)
+ caches.path = caches.temp()
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
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
if not caches.path then
caches.path = '.'
end
- caches.path = input.clean_path(caches.path)
- if lfs and not table.is_empty({...}) then
+ caches.path = resolvers.clean_path(caches.path)
+ if not table.is_empty({...}) then
local pth = dir.mkdirs(caches.path,...)
return pth
end
@@ -7150,9 +8562,9 @@ function caches.setpath(instance,...)
return caches.path
end
-function caches.definepath(instance,category,subcategory)
+function caches.definepath(category,subcategory)
return function()
- return caches.setpath(instance,category,subcategory)
+ return caches.setpath(category,subcategory)
end
end
@@ -7164,39 +8576,202 @@ function caches.loaddata(path,name)
local tmaname, tmcname = caches.setluanames(path,name)
local loader = loadfile(tmcname) or loadfile(tmaname)
if loader then
- return loader()
+ loader = loader()
+ collectgarbage("step")
+ return loader
else
return false
end
end
-function caches.is_writable(filepath,filename)
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
local tmaname, tmcname = caches.setluanames(filepath,filename)
- return file.is_writable(tmaname)
+ return file.iswritable(tmaname)
end
-function caches.savedata(filepath,filename,data,raw) -- raw needed for file cache
+function caches.savedata(filepath,filename,data,raw)
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))
+ file.savedata(tmaname, table.serialize(data,'return',false,true,false)) -- no hex
else
- table.tofile (tmaname, data,'return',true,true) -- maybe not the last true
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
end
- utils.lua.compile(tmaname, tmcname)
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
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 tex and texconfig and (not texconfig.formatname or texconfig.formatname == "") and input and resolvers.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")
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-res'] = {
+ 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"
+}
+
+--~ print(resolvers.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
+
+local upper, lower, gsub = string.upper, string.lower, string.gsub
+
+local prefixes = { }
+
+prefixes.environment = function(str)
+ return resolvers.clean_path(os.getenv(str) or os.getenv(upper(str)) or os.getenv(lower(str)) or "")
+end
+
+prefixes.relative = function(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 resolvers.clean_path(str)
+end
+
+prefixes.locate = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path((fullname ~= "" and fullname) or str)
+end
+
+prefixes.filename = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.basename((fullname ~= "" and fullname) or str))
+end
+
+prefixes.pathname = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.dirname((fullname ~= "" and fullname) or str))
+end
+
+prefixes.env = prefixes.environment
+prefixes.rel = prefixes.relative
+prefixes.loc = prefixes.locate
+prefixes.kpse = prefixes.locate
+prefixes.full = prefixes.locate
+prefixes.file = prefixes.filename
+prefixes.path = prefixes.pathname
+
+local function _resolve_(method,target)
+ if prefixes[method] then
+ return prefixes[method](target)
+ else
+ return method .. ":" .. target
+ end
+end
+
+local function resolve(str)
+ if type(str) == "table" then
+ for k, v in pairs(str) do -- ipairs
+ str[k] = resolve(v) or v
+ end
+ elseif str and str ~= "" then
+ str = gsub(str,"([a-z]+):([^ \"\']*)",_resolve_)
+ end
+ return str
end
+resolvers.resolve = resolve
+
+if os.uname then
+
+ for k, v in pairs(os.uname()) do
+ if not prefixes[k] then
+ prefixes[k] = function() return v end
+ end
+ end
+
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
--[[ldx--
<p>Once we found ourselves defining similar cache constructs
several times, containers were introduced. Containers are used
@@ -7210,124 +8785,145 @@ table structures without bothering about the disk cache.</p>
<p>Examples of usage can be found in the font related code.</p>
--ldx]]--
-containers = { }
-containers.trace = false
+containers = containers or { }
-do -- local report
+containers.usecache = true
- 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
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%s: %s",tag,name or 'invalid')
end
+end
- local allocated = { }
+local allocated = { }
- -- tracing
+-- 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
+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 and caches.setpath and caches.setpath(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 and caches.iswritable(container.path, name)
+end
- function containers.is_usable(container, name)
- return container.enabled and caches.is_writable(container.path, name)
+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.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
+function containers.read(container,name)
+ if container.enabled and caches and not container.storage[name] and containers.usecache then
+ container.storage[name] = caches.loaddata(container.path,name)
+ if containers.is_valid(container,name) then
+ report(container,"loaded",name)
else
- return false
+ container.storage[name] = nil
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]
+ 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
+function containers.write(container, name, data)
+ if data then
+ data.cache_version = container.version
+ if container.enabled and caches 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
- return data
+ report(container,"stored",name)
+ container.storage[name] = data
end
+ return data
+end
- function containers.content(container,name)
- return container.storage[name]
- end
+function containers.content(container,name)
+ return container.storage[name]
+end
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
end
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") 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
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
-input.cachepath = nil
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
-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))
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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))
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(pathname))
else
if not filename or (filename == "") then
filename = dataname
@@ -7339,15 +8935,15 @@ end
-- we will make a better format, maybe something xml or just text or lua
-input.automounted = input.automounted or { }
+resolvers.automounted = resolvers.automounted or { }
-function input.automount(instance,usecache)
- local mountpaths = input.simplified_list(input.expansion(instance,'TEXMFMOUNT'))
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
if table.is_empty(mountpaths) and usecache then
- mountpaths = { caches.setpath(instance,"mount") }
+ mountpaths = { caches.setpath("mount") }
end
if not table.is_empty(mountpaths) then
- input.starttiming(instance)
+ statistics.starttiming(resolvers.instance)
for k, root in pairs(mountpaths) do
local f = io.open(root.."/url.tmi")
if f then
@@ -7356,96 +8952,72 @@ function input.automount(instance,usecache)
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)
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
end
end
end
f:close()
end
end
- input.stoptiming(instance)
+ statistics.stoptiming(resolvers.instance)
end
end
--- store info in format
+-- status info
-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)
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
-function input.storage.register(...)
- input.storage.data[#input.storage.data+1] = { ... }
-end
+-- experiment (code will move)
-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
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
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
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
else
- name = str
+ return "invalid status file"
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
+ return "missing status file"
end
- lua.bytecode[input.storage.max] = loadstring(code)
end
+ return true
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
+end -- of closure
-if not modules then modules = { } end modules ['luat-log'] = {
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-zip'] = {
version = 1.001,
comment = "companion to luat-lib.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -7453,127 +9025,556 @@ if not modules then modules = { } end modules ['luat-log'] = {
license = "see context related readme files"
}
---[[ldx--
-<p>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 <l n='xml'/> structured file. Actually, any logging that
-is hooked into callbacks will be \XML\ by default.</p>
---ldx]]--
-
-input = input or { }
-logs = logs or { }
-
---[[ldx--
-<p>This looks pretty ugly but we need to speed things up a bit.</p>
---ldx]]--
+local format, find = string.format, string.find
-logs.levels = {
- ['error'] = 1,
- ['warning'] = 2,
- ['info'] = 3,
- ['debug'] = 4
-}
+local trace_locating, trace_verbose = false, false
-logs.functions = {
- 'error', 'warning', 'info', 'debug', 'report',
- 'start', 'stop', 'push', 'pop'
-}
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trace_verbose = v end)
-logs.callbacks = {
- 'start_page_number',
- 'stop_page_number',
- 'report_output_pages',
- 'report_output_log'
-}
+zip = zip or { }
+zip.archives = zip.archives or { }
+zip.registeredfiles = zip.registeredfiles or { }
-logs.xml = logs.xml or { }
-logs.tex = logs.tex or { }
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+local locators, hashers, concatinators = resolvers.locators, resolvers.hashers, resolvers.concatinators
-logs.level = 0
+local archives = zip.archives
-do
- local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+-- zip:///oeps.zip?name=bla/bla.tex
+-- zip:///oeps.zip?tree=tex/texmf-local
- if texlua then
- write_nl = print
- write = io.write
+local function validzip(str) -- todo: use url splitter
+ if not find(str,"^zip://") then
+ return "zip:///" .. str
+ else
+ return str
end
+end
- function logs.xml.debug(category,str)
- if logs.level > 3 then write_nl(format("<d category='%s'>%s</d>",category,str)) end
+function zip.openarchive(name)
+ if not name or name == "" then
+ return nil
+ else
+ local arch = archives[name]
+ if not arch then
+ local full = resolvers.find_file(name) or ""
+ arch = (full ~= "" and zip.open(full)) or false
+ archives[name] = arch
+ end
+ return arch
end
- function logs.xml.info(category,str)
- if logs.level > 2 then write_nl(format("<i category='%s'>%s</i>",category,str)) end
+end
+
+function zip.closearchive(name)
+ if not name or (name == "" and archives[name]) then
+ zip.close(archives[name])
+ archives[name] = nil
end
- function logs.xml.warning(category,str)
- if logs.level > 1 then write_nl(format("<w category='%s'>%s</w>",category,str)) end
+end
+
+-- zip:///texmf.zip?tree=/tex/texmf
+-- zip:///texmf.zip?tree=/tex/texmf-local
+-- zip:///texmf-mine.zip?tree=/tex/texmf-projects
+
+function locators.zip(specification) -- where is this used? startup zips (untested)
+ specification = resolvers.splitmethod(specification)
+ local zipfile = specification.path
+ local zfile = zip.openarchive(name) -- tricky, could be in to be initialized tree
+ if trace_locating then
+ if zfile then
+ logs.report("fileio",'! zip locator, found: %s',specification.original)
+ else
+ logs.report("fileio",'? zip locator, not found: %s',specification.original)
+ end
end
- function logs.xml.error(category,str)
- if logs.level > 0 then write_nl(format("<e category='%s'>%s</e>",category,str)) end
+end
+
+function hashers.zip(tag,name)
+ if trace_verbose then
+ logs.report("fileio","loading zip file %s as %s",name,tag)
end
- function logs.xml.report(category,str)
- write_nl(format("<r category='%s'>%s</r>",category,str))
+ resolvers.usezipfile(format("%s?tree=%s",tag,name))
+end
+
+function concatinators.zip(tag,path,name)
+ if not path or path == "" then
+ return format('%s?name=%s',tag,name)
+ else
+ return format('%s?name=%s/%s',tag,path,name)
end
+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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+function resolvers.isreadable.zip(name)
+ return true
+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
+function finders.zip(specification,filetype)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'! zip finder, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ dfile = zfile:close()
+ if trace_locating then
+ logs.report("fileio",'+ zip finder, name: %s',q.name)
+ end
+ return specification.original
+ end
+ elseif trace_locating then
+ logs.report("fileio",'? zip finder, path %s',specification.path)
+ end
+ end
end
- function logs.tex.warning(category,str)
- if logs.level > 1 then write_nl(format("warning >> %s: %s",category,str)) end
+ if trace_locating then
+ logs.report("fileio",'- zip finder, name: %s',filename)
end
- function logs.tex.error(category,str)
- if logs.level > 0 then write_nl(format("error >> %s: %s" ,category,str)) end
+ return unpack(finders.notfound)
+end
+
+function openers.zip(specification)
+ local zipspecification = resolvers.splitmethod(specification)
+ if zipspecification.path then
+ local q = url.query(zipspecification.query)
+ if q.name then
+ local zfile = zip.openarchive(zipspecification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',zipspecification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_open(specification)
+ return openers.text_opener(specification,dfile,'zip')
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path %s',zipspecification.path)
+ end
+ end
end
- function logs.tex.report(category,str)
- write_nl(format("report >> %s: %s" ,category,str))
+ if trace_locating then
+ logs.report("fileio",'- zip opener, name: %s',filename)
end
+ return unpack(openers.notfound)
+end
- function logs.set_level(level)
- logs.level = logs.levels[level] or level
+function loaders.zip(specification)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_load(filename)
+ if trace_locating then
+ logs.report("fileio",'+ zip loader, name: %s',filename)
+ end
+ local s = dfile:read("*all")
+ dfile:close()
+ return true, s, #s
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path: %s',specification.path)
+ end
+ end
end
+ if trace_locating then
+ logs.report("fileio",'- zip loader, name: %s',filename)
+ end
+ return unpack(openers.notfound)
+end
- function logs.set_method(method)
- for _, v in pairs(logs.functions) do
- logs[v] = logs[method][v] or function() end
+-- zip:///somefile.zip
+-- zip:///somefile.zip?tree=texmf-local -> mount
+
+function resolvers.usezipfile(zipname)
+ zipname = validzip(zipname)
+ if trace_locating then
+ logs.report("fileio",'! zip use, file: %s',zipname)
+ end
+ local specification = resolvers.splitmethod(zipname)
+ local zipfile = specification.path
+ if zipfile and not zip.registeredfiles[zipname] then
+ local tree = url.query(specification.query).tree or ""
+ if trace_locating then
+ logs.report("fileio",'! zip register, file: %s',zipname)
end
- if callback and input[method] then
- for _, cb in pairs(logs.callbacks) do
- callback.register(cb, input[method][cb])
+ local z = zip.openarchive(zipfile)
+ if z then
+ local instance = resolvers.instance
+ if trace_locating then
+ logs.report("fileio","= zipfile, registering: %s",zipname)
end
+ statistics.starttiming(instance)
+ resolvers.prepend_hash('zip',zipname,zipfile)
+ resolvers.extend_texmf_var(zipname) -- resets hashes too
+ zip.registeredfiles[zipname] = z
+ instance.files[zipname] = resolvers.register_zip_file(z,tree or "")
+ statistics.stoptiming(instance)
+ elseif trace_locating then
+ logs.report("fileio","? zipfile, unknown: %s",zipname)
end
+ elseif trace_locating then
+ logs.report("fileio",'! zip register, no file: %s',zipname)
end
+end
- function logs.xml.start_page_number()
- write_nl(format("<p real='%s' page='%s' sub='%s'", tex.count[0], tex.count[1], tex.count[2]))
+function resolvers.register_zip_file(z,tree)
+ local files, filter = { }, ""
+ if tree == "" then
+ filter = "^(.+)/(.-)$"
+ else
+ filter = format("^%s/(.+)/(.-)$",tree)
+ end
+ if trace_locating then
+ logs.report("fileio",'= zip filter: %s',filter)
end
+ local register, n = resolvers.register_file, 0
+ for i in z:files() do
+ local path, name = i.filename:match(filter)
+ if path then
+ if name and name ~= '' then
+ register(files, name, path)
+ n = n + 1
+ else
+ -- directory
+ end
+ else
+ register(files, i.filename, '')
+ n = n + 1
+ end
+ end
+ logs.report("fileio",'= zip entries: %s',n)
+ return files
+end
- function logs.xml.stop_page_number()
- write("/>")
- write_nl("")
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-crl'] = {
+ 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"
+}
+
+curl = curl or { }
+
+curl.cached = { }
+curl.cachepath = caches.definepath("curl")
+
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+
+function curl.fetch(protocol, name)
+ local cachename = curl.cachepath() .. "/" .. name:gsub("[^%a%d%.]+","-")
+-- cachename = cachename:gsub("[\\/]", io.fileseparator)
+ cachename = cachename:gsub("[\\]", "/") -- cleanup
+ if not curl.cached[name] then
+ if not io.exists(cachename) then
+ curl.cached[name] = cachename
+ local command = "curl --silent --create-dirs --output " .. cachename .. " " .. name -- no protocol .. "://"
+ os.spawn(command)
+ end
+ if io.exists(cachename) then
+ curl.cached[name] = cachename
+ else
+ curl.cached[name] = ""
+ end
end
+ return curl.cached[name]
+end
- function logs.xml.report_output_pages(p,b)
- write_nl(format("<v k='pages' v='%s'/>", p))
- write_nl(format("<v k='bytes' v='%s'/>", b))
- write_nl("")
+function finders.curl(protocol,filename)
+ local foundname = curl.fetch(protocol, filename)
+ return finders.generic(protocol,foundname,filetype)
+end
+
+function openers.curl(protocol,filename)
+ return openers.generic(protocol,filename)
+end
+
+function loaders.curl(protocol,filename)
+ return loaders.generic(protocol,filename)
+end
+
+-- todo: metamethod
+
+function curl.install(protocol)
+ finders[protocol] = function (filename,filetype) return finders.curl(protocol,filename) end
+ openers[protocol] = function (filename) return openers.curl(protocol,filename) end
+ loaders[protocol] = function (filename) return loaders.curl(protocol,filename) end
+end
+
+curl.install('http')
+curl.install('https')
+curl.install('ftp')
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
+
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
+
+<p>How about just forgetting about them?</p>
+--ldx]]--
+
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
+
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
+
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
+
+local find = string.find
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
end
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
+ end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
+ end
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
+ end
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
+ end
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
+ end
+ else
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
+ end
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
+ end
+ end
+ end
+ end
+end
+
- function logs.xml.report_output_log()
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-tmf'] = {
+ 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"
+}
+
+-- loads *.tmf files in minimal tree roots (to be optimized and documented)
+
+function resolvers.check_environment(tree)
+ logs.simpleline()
+ 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'))
+ logs.simpleline()
+ logs.simple("preset : TEXPATH => %s", os.getenv('TEXPATH'))
+ logs.simple("preset : TEXOS => %s", os.getenv('TEXOS'))
+ logs.simple("preset : TEXMFOS => %s", os.getenv('TEXMFOS'))
+ logs.simple("preset : TMP => %s", os.getenv('TMP'))
+ logs.simple('')
+end
+
+function resolvers.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 resolvers.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
+ resolvers.check_environment(tree)
+ resolvers.load_environment(setuptex)
+ end
+ end
end
-logs.set_level('error')
-logs.set_method('tex')
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
if not modules then modules = { } end modules ['luat-sta'] = {
version = 1.001,
@@ -7582,6 +9583,8 @@ if not modules then modules = { } end modules ['luat-sta'] = {
license = "see context related readme files"
}
+-- this code is used in the updater
+
states = states or { }
states.data = states.data or { }
states.hash = states.hash or { }
@@ -7608,27 +9611,32 @@ 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
+ if type(d) == "table" 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
- d = dk
+ dkey, hkey = post, key
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
+ 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
+ elseif type(d) == "string" then
+ -- weird
+ states.data[tag], states.hash[tag] = value, value
end
- d[dkey], h[hkey] = value, value
end
end
@@ -7748,7 +9756,6 @@ end
--~ },
--~ }
-
--~ states.save("teststate", "update")
--~ states.load("teststate", "update")
@@ -7759,34 +9766,55 @@ end
--~ states.load("teststate", "update")
--~ print(states.get_by_tag("update","rsync.server","unknown"))
+
+end -- of closure
-- end library merge
-own = { }
+own = { } -- not local
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-md5.lua',
'l-dir.lua',
'l-boolean.lua',
- 'l-xml.lua',
+ 'l-math.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',
+ 'l-utils.lua',
+ 'l-aux.lua',
+-- 'l-xml.lua',
+ 'lxml-tab.lua',
+ 'lxml-pth.lua',
+ 'lxml-ent.lua',
+ 'lxml-mis.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+ 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+ 'data-zip.lua',
+ 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-tmf.lua', -- tree files
+ -- needed ?
+ 'luat-sta.lua', -- states
}
-- We need this hack till luatex is fixed.
@@ -7810,7 +9838,7 @@ 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()
+local function locate_libs()
for _, lib in pairs(own.libs) do
for _, pth in pairs(own.list) do
local filename = string.gsub(pth .. "/" .. lib,"\\","/")
@@ -7824,11 +9852,11 @@ function locate_libs()
end
end
-if not input then
+if not resolvers then
locate_libs()
end
-if not input then
+if not resolvers 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")
@@ -7837,225 +9865,71 @@ if not input then
os.exit()
end
-instance = input.reset()
-input.verbose = environment.argument("verbose") or false
-input.banner = 'MtxRun | '
-utils.report = input.report
+logs.setprogram('MTXrun',"TDS Runner Tool 1.22",environment.arguments["verbose"] or false)
-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
+local instance = resolvers.reset()
-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
+runners = runners or { } -- global
+messages = messages or { }
-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
+messages.help = [[
+--script run an mtx script (--noquotes)
+--execute run a script or program (--noquotes)
+--resolve resolve prefixed arguments
+--ctxlua run internally (using preloaded libs)
+--locate locate given filename
-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
+--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)
-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
+--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
-os.arch = os.arch or function()
- return os.resultof("uname -m") or "linux"
-end
+--verbose give a bit more info
+--engine=str target engine
+--progname=str format or backend
-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
+--edit launch editor with found file
+--launch (--all) launch files like manuals, assumes os support
--- it starts here
+--intern run script using built in libraries
-input.runners = { }
-input.runners.applications = { }
+--usekpse use kpse as fallback (when no mkiv and cache installed, often slower)
+--forcekpse force using kpse (handy when no mkiv and cache installed but less functionality)
+]]
-input.runners.applications.lua = "luatex --luaonly"
-input.runners.applications.pl = "perl"
-input.runners.applications.py = "python"
-input.runners.applications.rb = "ruby"
+runners.applications = {
+ ["lua"] = "luatex --luaonly",
+ ["luc"] = "luatex --luaonly",
+ ["pl"] = "perl",
+ ["py"] = "python",
+ ["rb"] = "ruby",
+}
-input.runners.suffixes = {
+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 },
+runners.registered = {
+ texexec = { 'texexec.rb', true }, -- context mkii runner (only tool not to be luafied)
+ texutil = { 'texutil.rb', true }, -- old perl based index sorter for mkii (old versions need it)
+ texfont = { 'texfont.pl', true }, -- perl script that makes mkii font metric files
+ texfind = { 'texfind.pl', false }, -- perltk based tex searching tool, mostly used at pragma
+ texshow = { 'texshow.pl', false }, -- perltk based context help system, will be luafied
+ -- texwork = { \texwork.pl', false }, -- perltk based editing environment, only used at pragma
makempy = { 'makempy.pl', true },
mptopdf = { 'mptopdf.pl', true },
- pstopdf = { 'pstopdf.rb', true },
+ pstopdf = { 'pstopdf.rb', true }, -- converts ps (and some more) images, does some cleaning (replaced)
- examplex = { 'examplex.rb', false },
+-- examplex = { 'examplex.rb', false },
concheck = { 'concheck.rb', false },
runtools = { 'runtools.rb', true },
@@ -8065,81 +9939,39 @@ input.runners.registered = {
rlxtools = { 'rlxtools.rb', true },
pdftools = { 'pdftools.rb', true },
mpstools = { 'mpstools.rb', true },
- exatools = { 'exatools.rb', true },
+-- exatools = { 'exatools.rb', true },
xmltools = { 'xmltools.rb', true },
- luatools = { 'luatools.lua', 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
+runners.launchers = {
+ windows = { },
+ unix = { }
+}
-function input.runners.prepare(instance)
+function runners.prepare()
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")
+ logs.simple("file '%s' is unchanged",checkname)
return "skip"
else
- report("file '" .. checkname .. "' is changed, processing started")
+ logs.simple("file '%s' is changed, processing started",checkname)
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")
+ logs.simple("file '%s' and '%s' have same age",oldname,newname)
return "skip"
else
- report("file '" .. newname .. "' is older than '" .. oldname .. "'")
+ logs.simple("file '%s' is older than '%s'",oldname,newname)
end
end
local tree = environment.argument('tree') or ""
@@ -8147,26 +9979,27 @@ function input.runners.prepare(instance)
tree = os.getenv('TEXMFSTART_TREE') or os.getenv('TEXMFSTARTTREE') or tree
end
if tree and tree ~= "" then
- input.load_tree(tree)
+ resolvers.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)
+ resolvers.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 .. "'")
+ if runpath and not lfs.chdir(runpath) then
+ logs.simple("unable to change to path '%s'",runpath)
return "error"
end
return "run"
end
-function input.runners.execute_script(instance,fullname,internal)
+function runners.execute_script(fullname,internal)
+ local noquote = environment.argument("noquotes")
if fullname and fullname ~= "" then
- local state = input.runners.prepare(instance)
+ local state = runners.prepare()
if state == 'error' then
return false
elseif state == 'skip' then
@@ -8183,41 +10016,44 @@ function input.runners.execute_script(instance,fullname,internal)
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]
+ if suffix == "" and runners.registered[name] and runners.registered[name][1] then
+ name = 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')
+ for _,s in pairs(runners.suffixes) do
+ result = resolvers.find_file(name .. "." .. s, 'texmfscripts')
if result ~= "" then
break
end
end
- elseif input.runners.applications[suffix] then
- result = input.find_file(instance, name, 'texmfscripts')
+ elseif runners.applications[suffix] then
+ result = resolvers.find_file(name, 'texmfscripts')
else
-- maybe look on path
- result = input.find_file(instance, name, 'other text files')
+ result = resolvers.find_file(name, 'other text files')
end
end
if result and result ~= "" then
+ local before, after = environment.split_arguments(fullname) -- already done
+ environment.arguments_before, environment.arguments_after = before, after
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)]
+ local binary = 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 command = result .. " " .. environment.reconstruct_commandline(after,noquote)
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
+ io.flush()
+ end
local code = os.exec(command) -- maybe spawn
return code == 0
end
@@ -8227,9 +10063,10 @@ function input.runners.execute_script(instance,fullname,internal)
return false
end
-function input.runners.execute_program(instance,fullname)
+function runners.execute_program(fullname)
+ local noquote = environment.argument("noquotes")
if fullname and fullname ~= "" then
- local state = input.runners.prepare(instance)
+ local state = runners.prepare()
if state == 'error' then
return false
elseif state == 'skip' then
@@ -8238,10 +10075,11 @@ function input.runners.execute_program(instance,fullname)
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")
+ local command = fullname .. " " .. (environment.reconstruct_commandline(after or "",noquote) or "")
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
io.flush()
local code = os.exec(command) -- (fullname,unpack(after)) does not work / maybe spawn
return code == 0
@@ -8250,82 +10088,93 @@ function input.runners.execute_program(instance,fullname)
return false
end
-function input.runners.handle_stubs(instance,create)
- local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer supported
+-- the --usekpse flag will fallback on kpse
+
+local windows_stub = '@echo off\013\010setlocal\013\010set ownpath=%%~dp0%%\013\010texlua "%%ownpath%%mtxrun.lua" --usekpse --execute %s %%*\013\010endlocal\013\010'
+local unix_stub = '#!/bin/sh\010mtxrun --usekpse --execute %s \"$@\"\010'
+
+function runners.handle_stubs(create)
+ local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer subpathssupported
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
+ if os.platform == "unix" then
unix = true
else
windows = true
end
end
- for _,v in pairs(input.runners.registered) do
+ for _,v in pairs(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")
+ io.savedata(file.join(stubpath,base..".bat"),string.format(windows_stub,name))
+ logs.simple("windows stub for '%s' created",base)
end
if unix then
- io.savedata(base, {"#!/bin/sh", command..' "$@"'}, "\010")
- input.report("unix stub for '" .. base .. "' created")
+ io.savedata(file.join(stubpath,base),string.format(unix_stub,name))
+ logs.simple("unix stub for '%s' created",base)
end
else
- if windows and (os.remove(base..'.bat') or os.remove(base..'.cmd')) then
- input.report("windows stub for '" .. base .. "' removed")
+ if windows and (os.remove(file.join(stubpath,base..'.bat')) or os.remove(file.join(stubpath,base..'.cmd'))) then
+ logs.simple("windows stub for '%s' removed", base)
end
- if unix and (os.remove(base) or os.remove(base..'.sh')) then
- input.report("unix stub for '" .. base .. "' removed")
+ if unix and (os.remove(file.join(stubpath,base)) or os.remove(file.join(stubpath,base..'.sh'))) then
+ logs.simple("unix stub for '%s' removed",base)
end
end
end
end
end
-function input.runners.resolve_string(instance,filename)
+function runners.resolve_string(filename)
if filename and filename ~= "" then
- input.runners.report_location(instance,input.resolve(instance,filename))
+ runners.report_location(resolvers.resolve(filename))
end
end
-function input.runners.locate_file(instance,filename)
+function runners.locate_file(filename)
+ -- differs from texmfstart where locate appends .com .exe .bat ... todo
if filename and filename ~= "" then
- input.runners.report_location(instance,input.find_given_file(instance,filename))
+ runners.report_location(resolvers.find_given_file(filename))
end
end
-function input.runners.locate_platform(instance)
- input.runners.report_location(instance,os.currentplatform())
+function runners.locate_platform()
+ runners.report_location(os.currentplatform())
end
-function input.runners.report_location(instance,result)
- if input.verbose then
- input.report("")
+function runners.report_location(result)
+ if logs.verbose then
+ logs.simpleline()
if result and result ~= "" then
- input.report(result)
+ logs.simple(result)
else
- input.report("not found")
+ logs.simple("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)
+function runners.edit_script(filename) -- we assume that vim is present on most systems
+ local editor = os.getenv("MTXRUN_EDITOR") or os.getenv("TEXMFSTART_EDITOR") or os.getenv("EDITOR") or 'vim'
+ local rest = resolvers.resolve(filename)
if rest ~= "" then
- os.launch(editor .. " " .. rest)
+ local command = editor .. " " .. rest
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("starting editor: %s",command)
+ logs.simple_line()
+ logs.simple_line()
+ end
+ os.launch(command)
end
end
-function input.runners.save_script_session(filename, list)
+function runners.save_script_session(filename, list)
local t = { }
for _, key in ipairs(list) do
t[key] = environment.arguments[key]
@@ -8333,7 +10182,7 @@ function input.runners.save_script_session(filename, list)
io.savedata(filename,table.serialize(t,true))
end
-function input.runners.load_script_session(filename)
+function runners.load_script_session(filename)
if lfs.isfile(filename) then
local t = io.loaddata(filename)
if t then
@@ -8346,15 +10195,10 @@ function input.runners.load_script_session(filename)
end
end
-input.runners.launchers = {
- windows = { },
- unix = { }
-}
-
-function input.launch(str)
+function resolvers.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 launchers = 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
@@ -8364,35 +10208,40 @@ function input.launch(str)
os.launch(str)
end
-function input.runners.launch_file(instance,filename)
+function runners.launch_file(filename)
instance.allresults = true
- input.verbose = true
+ logs.setverbose(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=")
+ logs.simple("provide name or --pattern=")
else
- local t = input.find_files(instance,pattern)
- -- local t = input.aux.find_file(instance,"*/" .. pattern,true)
+ local t = resolvers.find_files(pattern)
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern)
+ end
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern .. "*")
+ end
if t and #t > 0 then
if environment.arguments["all"] then
for _, v in pairs(t) do
- input.report("launching", v)
- input.launch(v)
+ logs.simple("launching %s", v)
+ resolvers.launch(v)
end
else
- input.report("launching", t[1])
- input.launch(t[1])
+ logs.simple("launching %s", t[1])
+ resolvers.launch(t[1])
end
else
- input.report("no match for", pattern)
+ logs.simple("no match for %s", pattern)
end
end
end
-function input.runners.execute_ctx_script(instance,filename,arguments)
+function runners.find_mtx_script(filename)
local function found(name)
local path = file.dirname(name)
if path and path ~= "" then
@@ -8402,72 +10251,121 @@ function input.runners.execute_ctx_script(instance,filename,arguments)
return io.exists(fullname) and fullname
end
end
- local suffix = ""
- if not filename:find("%.lua$") then suffix = ".lua" end
- local fullname = filename
- -- just <filename>
- fullname = filename .. suffix
- fullname = input.find_file(instance,fullname)
- -- mtx-<filename>
- if not fullname or fullname == "" then
- fullname = "mtx-" .. filename .. suffix
- fullname = found(fullname) or input.find_file(instance,fullname)
+ filename = file.addsuffix(filename,"lua")
+ local basename = file.removesuffix(file.basename(filename))
+ local suffix = file.extname(filename)
+ -- qualified path, raw name
+ local fullname = file.is_qualified_path(filename) and io.exists(filename) and filename
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- current path, raw name
+ fullname = "./" .. filename
+ fullname = io.exists(fullname) and fullname
+ if fullname and fullname ~= "" then
+ return fullname
end
- -- mtx-<filename>s
- if not fullname or fullname == "" then
- fullname = "mtx-" .. filename .. "s" .. suffix
- fullname = found(fullname) or input.find_file(instance,fullname)
+ -- context namespace, mtx-<filename>
+ fullname = "mtx-" .. filename
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
end
- -- mtx-<filename minus trailing s>
- if not fullname or fullname == "" then
- fullname = "mtx-" .. filename:gsub("s$","") .. suffix
- fullname = found(fullname) or input.find_file(instance,fullname)
+ -- context namespace, mtx-<filename>s
+ fullname = "mtx-" .. basename .. "s" .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
end
- -- that should do it
+ -- context namespace, mtx-<filename minus trailing s>
+ fullname = "mtx-" .. basename:gsub("s$","") .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
if fullname and fullname ~= "" then
- local state = input.runners.prepare(instance)
+ return fullname
+ end
+ -- context namespace, just <filename>
+ fullname = resolvers.find_file(filename)
+ return fullname
+end
+
+function runners.execute_ctx_script(filename,arguments)
+ local fullname = runners.find_mtx_script(filename) or ""
+ -- retyr after generate but only if --autogenerate
+ if fullname == "" and environment.argument("autogenerate") then -- might become the default
+ instance.renewcache = true
+ logs.setverbose(true)
+ resolvers.load()
+ --
+ fullname = runners.find_mtx_script(filename) or ""
+ end
+ -- that should do it
+ if fullname ~= "" then
+ local state = runners.prepare()
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
+ arg = { } for _,v in pairs(arguments) do arg[#arg+1] = resolvers.resolve(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)
+ runners.load_script_session(loadname)
end
filename = environment.files[1]
- if input.verbose then
- input.report("using script: " .. fullname)
+ if logs.verbose then
+ logs.simple("using script: %s\n",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 savename and runners.save_list and not table.is_empty(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)
+ runners.save_script_session(savename, runners.save_list)
end
return true
end
else
- input.verbose = true
- input.report("unknown script: " .. filename)
+ logs.setverbose(true)
+ if filename == "" then
+ logs.simple("unknown script, no name given")
+ local context = resolvers.find_file("mtx-context.lua")
+ if context ~= "" then
+ local result = dir.glob((string.gsub(context,"mtx%-context","mtx-*"))) -- () needed
+ local valid = { }
+ for _, scriptname in ipairs(result) do
+ scriptname = string.match(scriptname,".*mtx%-([^%-]-)%.lua")
+ if scriptname then
+ valid[#valid+1] = scriptname
+ end
+ end
+ if #valid > 0 then
+ logs.simple("known scripts: %s",table.concat(valid,", "))
+ end
+ end
+ else
+ filename = file.addsuffix(filename,"lua")
+ if file.is_qualified_path(filename) then
+ logs.simple("unknown script '%s'",filename)
+ else
+ logs.simple("unknown script '%s' or 'mtx-%s'",filename,filename)
+ end
+ end
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")
+function runners.timedrun(filename) -- just for me
+ if filename and filename ~= "" then
+ runners.timed(function() os.execute(filename) end)
end
- input.reportlines(message)
+end
+
+function runners.timed(action)
+ statistics.timed(action)
end
-- this is a bit dirty ... first we store the first filename and next we
@@ -8478,12 +10376,79 @@ local filename = environment.files[1] or ""
local ok = true
local before, after = environment.split_arguments(filename)
+environment.arguments_before, environment.arguments_after = before, after
+environment.initialize_arguments(before)
-input.runners.my_prepare_b(instance)
-before = input.resolve(instance,before) -- experimental here
-after = input.resolve(instance,after) -- experimental here
+instance.engine = environment.argument("engine") or 'luatex'
+instance.progname = environment.argument("progname") or 'context'
+instance.lsrmode = environment.argument("lsr") or false
+
+-- maybe the unset has to go to this level
+
+if environment.argument("usekpse") or environment.argument("forcekpse") then
+
+ os.setenv("engine","")
+ os.setenv("progname","")
+
+ local remapper = {
+ otf = "opentype fonts",
+ ttf = "truetype fonts",
+ ttc = "truetype fonts",
+ pfb = "type1 fonts",
+ other = "other text files",
+ }
+
+ local function kpse_initialized()
+ texconfig.kpse_init = true
+ local t = os.clock()
+ local k = kpse.original.new("luatex",instance.progname)
+ local dummy = k:find_file("mtxrun.lua") -- so that we're initialized
+ logs.simple("kpse fallback with progname '%s' initialized in %s seconds",instance.progname,os.clock()-t)
+ kpse_initialized = function() return k end
+ return k
+ end
+
+ local find_file = resolvers.find_file
+ local show_path = resolvers.show_path
+
+ if environment.argument("forcekpse") then
+
+ function resolvers.find_file(name,kind)
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ function resolvers.show_path(name)
+ return (kpse_initialized():show_path(name)) or ""
+ end
+
+ elseif environment.argument("usekpse") then
+
+ resolvers.load()
+
+ function resolvers.find_file(name,kind)
+ local found = find_file(name,kind) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ end
+ function resolvers.show_path(name)
+ local found = show_path(name) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():show_path(name)) or ""
+ end
+ end
+
+ end
+
+else
+
+ resolvers.load()
+
+end
-environment.initialize_arguments(before)
if environment.argument("selfmerge") then
-- embed used libraries
@@ -8492,61 +10457,56 @@ 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")
+ logs.setverbose(true)
+ resolvers.update_script(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
+ ok = runners.execute_script(filename,true)
+elseif environment.argument("script") or environment.argument("s") then
-- run a script by loading it (using libs), pass args
- ok = input.runners.execute_ctx_script(instance,filename,after)
+ ok = runners.execute_ctx_script(filename,after)
elseif environment.argument("execute") then
-- execute script
- ok = input.runners.execute_script(instance,filename)
+ ok = runners.execute_script(filename)
elseif environment.argument("direct") then
-- equals bin:
- ok = input.runners.execute_program(instance,filename)
+ ok = runners.execute_program(filename)
elseif environment.argument("edit") then
-- edit file
- input.runners.edit_script(instance,filename)
+ runners.edit_script(filename)
elseif environment.argument("launch") then
- input.runners.launch_file(instance,filename)
+ runners.launch_file(filename)
elseif environment.argument("make") then
-- make stubs
- input.runners.handle_stubs(instance,true)
+ runners.handle_stubs(true)
elseif environment.argument("remove") then
-- remove stub
- input.runners.handle_stubs(instance,false)
+ runners.handle_stubs(false)
elseif environment.argument("resolve") then
-- resolve string
- input.runners.resolve_string(instance,filename)
+ runners.resolve_string(filename)
elseif environment.argument("locate") then
-- locate file
- input.runners.locate_file(instance,filename)
+ runners.locate_file(filename)
elseif environment.argument("platform")then
-- locate platform
- input.runners.locate_platform(instance)
+ runners.locate_platform()
+elseif environment.argument("timedrun") then
+ -- locate platform
+ runners.timedrun(filename)
elseif environment.argument("help") or filename=='help' or filename == "" then
- input.help(banner,messages.help)
+ logs.help(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
+elseif filename:find("^bin:") then
+ ok = runners.execute_program(filename)
+else
+ ok = runners.execute_script(filename)
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
+if os.platform == "unix" then
io.write("\n")
end
+
+if ok == false then ok = 1 elseif ok == true then ok = 0 end
+
+os.exit(ok)
diff --git a/Master/texmf-dist/scripts/context/lua/x-ldx.lua b/Master/texmf-dist/scripts/context/lua/x-ldx.lua
index d9d4e6a72aa..af5c9c0c87e 100644
--- a/Master/texmf-dist/scripts/context/lua/x-ldx.lua
+++ b/Master/texmf-dist/scripts/context/lua/x-ldx.lua
@@ -150,38 +150,38 @@ function ldx.enhance(data) -- i need to use lpeg and then we can properly autoin
cod = cod:gsub("(%a+)",function(key)
local class = ldx.keywords.reserved[key]
if class then
- return "<ldx:key class='" .. class .. "'>" .. key .. "</ldx:key>"
+ return "<key class='" .. class .. "'>" .. key .. "</key>"
else
return key
end
end)
cod = cod:gsub("<<<<(%d+)>>>>", function(s)
- return "<ldx:dqs>" .. dqs[tonumber(s)] .. "</ldx:dqs>"
+ return "<dqs>" .. dqs[tonumber(s)] .. "</dqs>"
end)
cod = cod:gsub("<<(%d+)>>", function(s)
- return "<ldx:sqs>" .. sqs[tonumber(s)] .. "</ldx:sqs>"
+ return "<sqs>" .. sqs[tonumber(s)] .. "</sqs>"
end)
cod = cod:gsub("%[%[%[%[(%d+)%]%]%]%]", function(s)
return cmt[tonumber(s)]
end)
cod = cod:gsub("%[%[(%d+)%]%]", function(s)
- return "<ldx:com>" .. com[tonumber(s)] .. "</ldx:com>"
+ return "<com>" .. com[tonumber(s)] .. "</com>"
end)
cod = cod:gsub("##d##", "\\\"")
cod = cod:gsub("##s##", "\\\'")
if ldx.make_index then
local lines = cod:split("\n")
- local f = "(<ldx:key class='1'>function</ldx:key>)%s+([%w%.]+)%s*%("
+ local f = "(<key class='1'>function</key>)%s+([%w%.]+)%s*%("
for k,v in pairs(lines) do
-- functies
v = v:gsub(f,function(key, str)
- return "<ldx:function>" .. str .. "</ldx:function>("
+ return "<function>" .. str .. "</function>("
end)
-- variables
v = v:gsub("^([%w][%w%,%s]-)(=[^=])",function(str, rest)
local t = string.split(str, ",%s*")
for k,v in pairs(t) do
- t[k] = "<ldx:variable>" .. v .. "</ldx:variable>"
+ t[k] = "<variable>" .. v .. "</variable>"
end
return table.join(t,", ") .. rest
end)
@@ -222,19 +222,19 @@ function ldx.enhance(data) -- i need to use lpeg and then we can properly autoin
cod = cod:gsub("(%a+)",function(key)
local class = ldx.keywords.reserved[key]
if class then
- return "<ldx:key class='" .. class .. "'>" .. key .. "</ldx:key>"
+ return "<key class='" .. class .. "'>" .. key .. "</key>"
else
return key
end
end)
cod = cod:gsub("<s<<<(%d+)>>>s>", function(s)
- return "<ldx:sqs>" .. sqs[tonumber(s)] .. "</ldx:sqs>"
+ return "<sqs>" .. sqs[tonumber(s)] .. "</sqs>"
end)
cod = cod:gsub("<d<<<(%d+)>>>d>", function(s)
- return "<ldx:dqs>" .. dqs[tonumber(s)] .. "</ldx:dqs>"
+ return "<dqs>" .. dqs[tonumber(s)] .. "</dqs>"
end)
cod = cod:gsub("<c<<<(%d+)>>>c>", function(s)
- return "<ldx:com>" .. com[tonumber(s)] .. "</ldx:com>"
+ return "<com>" .. com[tonumber(s)] .. "</com>"
end)
cod = cod:gsub("<l<<<(%d+)>>>l>", function(s)
return cmt[tonumber(s)]
@@ -243,17 +243,17 @@ function ldx.enhance(data) -- i need to use lpeg and then we can properly autoin
cod = cod:gsub("##s##", "\\\'")
if ldx.make_index then
local lines = cod:split("\n")
- local f = "(<ldx:key class='1'>function</ldx:key>)%s+([%w%.]+)%s*%("
+ local f = "(<key class='1'>function</key>)%s+([%w%.]+)%s*%("
for k,v in pairs(lines) do
-- functies
v = v:gsub(f,function(key, str)
- return "<ldx:function>" .. str .. "</ldx:function>("
+ return "<function>" .. str .. "</function>("
end)
-- variables
v = v:gsub("^([%w][%w%,%s]-)(=[^=])",function(str, rest)
local t = string.split(str, ",%s*")
for k,v in pairs(t) do
- t[k] = "<ldx:variable>" .. v .. "</ldx:variable>"
+ t[k] = "<variable>" .. v .. "</variable>"
end
return table.join(t,", ") .. rest
end)
@@ -279,44 +279,46 @@ it possible to change the indentation afterwards.
function ldx.as_xml(data)
local t, cmode = { }, false
t[#t+1] = "<?xml version='1.0' standalone='yes'?>\n"
- t[#t+1] = "\n<ldx:document xmlns:ldx='http://www.pragma-ade.com/schemas/ldx.rng'>\n"
- for _,v in pairs(data) do
+ t[#t+1] = "\n<document xmlns:ldx='http://www.pragma-ade.com/schemas/ldx.rng' xmlns='http://www.pragma-ade.com/schemas/ldx.rng'>\n"
+ for _,v in pairs(data) do -- ldx: not needed
if v.code and not v.code:is_empty() then
- t[#t+1] = "\n<ldx:code>\n"
+ t[#t+1] = "\n<code>\n"
for k,v in pairs(v.code:split("\n")) do -- make this faster
local a, b = v:find("^(%s+)")
+ if v then v = v:gsub("[\n\r ]+$","") end
if a and b then
+ v = v:sub(b+1,#v)
if cmode then
- t[#t+1] = "<ldx:line comment='yes' n='" .. b .. "'>" .. v:sub(b+1,#v) .. "</ldx:line>\n"
+ t[#t+1] = "<line comment='yes' n='" .. b .. "'>" .. v .. "</line>\n"
else
- t[#t+1] = "<ldx:line n='" .. b .. "'>" .. v:sub(b+1,#v) .. "</ldx:line>\n"
+ t[#t+1] = "<line n='" .. b .. "'>" .. v .. "</line>\n"
end
elseif v:is_empty() then
if cmode then
- t[#t+1] = "<ldx:line comment='yes'/>\n"
+ t[#t+1] = "<line comment='yes'/>\n"
else
- t[#t+1] = "<ldx:line/>\n"
+ t[#t+1] = "<line/>\n"
end
elseif v:find("^%-%-%[%[") then
- t[#t+1] = "<ldx:line comment='yes'>" .. v .. "</ldx:line>\n"
+ t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
cmode= true
elseif v:find("^%]%]%-%-") then
- t[#t+1] = "<ldx:line comment='yes'>" .. v .. "</ldx:line>\n"
+ t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
cmode= false
elseif cmode then
- t[#t+1] = "<ldx:line comment='yes'>" .. v .. "</ldx:line>\n"
+ t[#t+1] = "<line comment='yes'>" .. v .. "</line>\n"
else
- t[#t+1] = "<ldx:line>" .. v .. "</ldx:line>\n"
+ t[#t+1] = "<line>" .. v .. "</line>\n"
end
end
- t[#t+1] = "</ldx:code>\n"
+ t[#t+1] = "</code>\n"
elseif v.comment then
- t[#t+1] = "\n<ldx:comment>\n" .. v.comment .. "\n</ldx:comment>\n"
+ t[#t+1] = "\n<comment>\n" .. v.comment .. "\n</comment>\n"
else
-- cannot happen
end
end
- t[#t+1] = "\n</ldx:document>\n"
+ t[#t+1] = "\n</document>\n"
return table.concat(t,"")
end
@@ -333,10 +335,10 @@ The next function wraps it all in one call:
--ldx]]--
function ldx.convert(luaname,ldxname)
- if not file.is_readable(luaname) then
+ if not file.isreadable(luaname) then
luaname = luaname .. ".lua"
end
- if file.is_readable(luaname) then
+ if file.isreadable(luaname) then
if not ldxname then
ldxname = file.replacesuffix(luaname,"ldx")
end
@@ -377,6 +379,8 @@ as the module to be used.
The main conversion call is:
--ldx]]--
+-- todo: assume usage of "mtxrun --script x-ldx", maybe make it mtx-ldx
+
if arg and arg[1] then
ldx.convert(arg[1],arg[2])
end
diff --git a/Master/texmf-dist/scripts/context/perl/cont_mis.pm b/Master/texmf-dist/scripts/context/perl/cont_mis.pm
deleted file mode 100644
index 6bd449bf045..00000000000
--- a/Master/texmf-dist/scripts/context/perl/cont_mis.pm
+++ /dev/null
@@ -1,69 +0,0 @@
-#D \module
-#D [ file=cont\_mis.pm,
-#D version=1999.05.05,
-#D title=General modules,
-#D subtitle=all kind of subs,
-#D author=Hans Hagen,
-#D date=\currentdate,
-#D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
-#C
-#C This module is part of the \CONTEXT\ macro||package and is
-#C therefore copyrighted by \PRAGMA. See licen-en.pdf for
-#C details.
-
-#D Not yet documented, source will be cleaned up.
-
-package cont_mis ;
-
-use strict ;
-
-my ($message, $separator, $content) ;
-
-format =
-@>>>>>>>>>>>>>>>>>>>>> @ @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-$message,$separator,$content
-.
-
-sub report
- { ($message, $separator, $content) = @_ ; write }
-
-sub crlf { print "\n" }
-sub banner { crlf ; report (shift , '/', shift) ; crlf }
-sub message { report (shift , ':', shift) }
-sub help { report (shift , ' ', shift) }
-sub status { message ('status' , shift) }
-sub warning { message ('warning' , shift) }
-sub error { message ('error' , shift) }
-sub continue { message ('' , shift) }
-
-sub hex_color
- { my ($r,$g,$b) = @_ ;
- if ($r>1) { $r=0xffff } else { $r = 0xffff*$r }
- if ($g>1) { $g=0xffff } else { $g = 0xffff*$g }
- if ($b>1) { $b=0xffff } else { $b = 0xffff*$b }
- local $_ = sprintf "%4x%4x%4x", $r, $g, $b ;
- s/ /0/go ;
- return $_ }
-
-sub InterfaceFound
- { local $_ = shift ;
- if (/^\%.*interface=(.*?)\b/)
- { return $1 }
- elsif (/\\(starttekst|stoptekst|startonderdeel)/)
- { return 'nl' }
- elsif (/\\(stelle|verwende|umgebung|benutze)/)
- { return 'de' }
- elsif (/\\(stel|gebruik|omgeving)/)
- { return 'nl' }
- elsif (/\\(use|setup|environment)/)
- { return 'en' }
- elsif (/(hoogte|breedte|letter)=/)
- { return 'nl' }
- elsif (/(height|width|style)=/)
- { return 'en' }
- elsif (/(hoehe|breite|schrift)=/)
- { return 'de' }
- else
- { return '' } }
-
-1;
diff --git a/Master/texmf-dist/scripts/context/perl/cont_set.pm b/Master/texmf-dist/scripts/context/perl/cont_set.pm
deleted file mode 100644
index 41c62e75440..00000000000
--- a/Master/texmf-dist/scripts/context/perl/cont_set.pm
+++ /dev/null
@@ -1,670 +0,0 @@
-#D \module
-#D [ file=cont\_set.pm,
-#D version=1999.04.01,
-#D title=General modules,
-#D subtitle=showing \CONTEXT\ commands,
-#D author=Hans Hagen,
-#D date=\currentdate,
-#D copyright={PRAGMA / Hans Hagen \& Ton Otten},
-#D suggestions={Tobias Burnus \& Taco Hoekater}]
-#C
-#C This module is part of the \CONTEXT\ macro||package and is
-#C therefore copyrighted by \PRAGMA. See licen-en.pdf for
-#C details.
-
-# todo: tacos speed patch
-
-#D As always: thanks to Taco and Tobias for testing this
-#D module and providing suggestions and code snippets as
-#D well as haunting bugs.
-
-package cont_set ;
-
-#D This module (package) deals with providing help information
-#D about the \CONTEXT\ commands. The data needed is derived
-#D from the setup files by \CONTEXT\ itself. The data is
-#D stored in files with suffix \type {tws} (tex work setup).
-#D This module introduces some subroutines:
-#D
-#D \starttabulatie[|Tl|p|]
-#D \NC \type {set\_setup\_interface} \NC sets the primary interface
-#D to search in \NC \NR
-#D \NC \type {set\_setup\_title} \NC sets the title of the main
-#D window title \NC \NR
-#D \NC \type {setups\_found} \NC locate the \type {tws} files
-#D using the \type {kpsewhich}
-#D program \NC \NR
-#D \NC \type {show\_setups} \NC allocate the radio buttons
-#D that can be used to select a
-#D command set \NC \NR
-#D \NC \type {load\_setup(filename)} \NC load the names \type {tws}
-#D file \NC \NR
-#D \NC \type {load\_setups} \NC all found command files can
-#D be loaded at once \NC \NR
-#D \NC \type {setup\_found(filename)} \NC this routine returns~1 when
-#D the file is loaded \NC \NR
-#D \NC \type {update\_setup} \NC when we browse the list with
-#D commands, this routine takes care
-#D of updating the text area \NC \NR
-#D \NC \type {change\_setup} \NC we can manually set the
-#D command set we want to browse,
-#D and this routine takes care of
-#D this \NC \NR
-#D \NC \type {show\_setup(command)} \NC context sensitive help can be
-#D provided by calling this sub \NC \NR
-#D \stoptabulatie
-#D
-#D First we load some packages and set some constants.
-
-use Tk ;
-use Tk::ROText ;
-use Config ;
-
-use strict;
-
-use subs qw/ update_setup / ;
-
-my $dosish = ($Config{'osname'} =~ /dos|win/i) ;
-my $default_size = $dosish ? 9 : 12 ;
-
-my $textfont = "Courier $default_size " ;
-my $userfont = "Courier $default_size italic" ;
-my $buttonfont = "Helvetica $default_size bold " ;
-
-unless ($dosish)
- { $textfont = "-adobe-courier-bold-r-normal--$default_size-120-75-75-m-70-iso8859-1" ;
- $userfont = "-adobe-courier-bold-o-normal--$default_size-120-75-75-m-70-iso8859-1" ;
- $buttonfont = "-adobe-helvetica-bold-r-normal--$default_size-120-75-75-p-69-iso8859-1" }
-
-my $s_vertical = 30 ;
-my $s_horizontal = 72 ;
-my $c_horizontal = 24 ;
-
-#D The main window is not resizable, but the text area and
-#D command list will have scrollbars.
-
-my %lw ; # stack of lists
-
-my $mw = MainWindow -> new ( -title => 'ConTeXt commands' ) ;
-
-$mw -> withdraw() ; $mw -> resizable ('y', 'y') ;
-
-sub SetupWindow { return $mw } ;
-
-my $bw = $mw -> Frame () ; # buttons
-my $tw = $mw -> Frame () ; # sw + fw
-my $fw = $tw -> Frame () ; # string + list
-
-my $request = $fw -> Entry ( -font => $textfont ,
- -background => 'ivory1' ,
- -width => $c_horizontal ) ;
-
-my $cw = $fw -> Scrolled ( 'Listbox' ,
- -scrollbars => 'e' ,
- -font => $textfont ,
- -width => $c_horizontal ,
- -selectbackground => 'gray' ,
- -background => 'ivory1' ,
- -selectmode => 'browse' ) ;
-
-$cw -> pack ( -side => 'bottom' , -fill => 'both' , -expand => 1 ) ;
-$request -> pack ( -side => 'top' , -fill => 'x' ) ;
-
-my $sw = $tw -> Scrolled ( 'ROText' ,
- -scrollbars => 'se' ,
- -height => $s_vertical ,
- -width => $s_horizontal ,
- -wrap => 'none' ,
- -background => 'ivory1' ,
- -font => $textfont ) ;
-
-
-#D And the whole bunch of widgets are packed in the main
-#D window.
-
-sub pack_them_all
- { $sw -> pack ( -side => 'left' , -fill => 'both' , -expand => 1 ) ;
- $fw -> pack ( -side => 'right' , -fill => 'y' , -expand => 0 ) ;
- $bw -> pack ( -side => 'top' , -fill => 'x' , -anchor => 'w' , -expand => 1 ) ;
- $tw -> pack ( -side => 'bottom', -fill => 'both' , -expand => 1 ) }
-
-sub unpack_them_all
- { }
-
-pack_them_all ;
-
-#D We scan for available setup files, with suffix \type {tws}.
-#D These should be somewhere on the system, grouped in one
-#D directory. At least the english file \type {cont-en.tws}
-#D should be found.
-
-my $tws_path = '' ;
-my @setup_files = ('cont-en.tws') ;
-my $setup_file = $setup_files[0] ;
-my $setup_interface = 'en' ;
-my $old_setup_file = '' ;
-
-sub set_setup_interface
- { $setup_interface = shift }
-
-sub set_setup_title
- { $mw -> configure ( -title => shift ) }
-
-sub setups_found
- { $tws_path = `kpsewhich --format="other text files" --progname=context cont-en.tws` ;
- $tws_path =~ s/cont-en\.tws.*// ;
- chop $tws_path ;
- @setup_files = glob ("$tws_path*.tws") ;
- if (@setup_files)
- { foreach (@setup_files) { s/\.tws// ; s/.*\/// }
- $setup_file = $setup_files[0] ;
- return 1 }
- else
- { return 0 } }
-
-#D A hide button
-
-sub show_hide_button
- { my $hb = $bw -> Button ( -text => "hide" ,
- -font => $buttonfont ,
- -command => \&hide_widget ) ;
- $hb -> pack ( -padx => '2p',
- -pady => '2p',
- -side => 'right' ) }
-
-sub hide_widget
- { $mw -> withdraw() }
-
-#D The setup files can be shown and chosen.
-
-sub show_setups
- { unpack_them_all ;
- foreach (@setup_files)
- { $lw{$_} = $bw -> Radiobutton ( -text => lc $_ ,
- -value => $_ ,
- -font => $buttonfont ,
- -selectcolor => 'ivory1' ,
- -indicatoron => 0 ,
- -command => \&change_setup ,
- -variable => \$setup_file ) ;
- $lw{$_} -> pack ( -padx => '2p',
- -pady => '2p',
- -side => 'left' ) }
- pack_them_all }
-
-$cw -> bind ('<B1-Motion>', \&update_setup ) ;
-$cw -> bind ('<1>' , \&update_setup ) ;
-$cw -> bind ('<Key>' , \&update_setup ) ;
-
-$sw -> tag ('configure', 'user' , -font => $userfont ) ;
-$sw -> tag ('configure', 'command' , -foreground => 'green3' ) ;
-$sw -> tag ('configure', 'variable' , -font => $userfont ) ;
-$sw -> tag ('configure', 'default' , -underline => 1 ) ;
-$sw -> tag ('configure', 'symbol' , -foreground => 'blue3' ) ;
-$sw -> tag ('configure', 'or' , -foreground => 'yellow3' ) ;
-$sw -> tag ('configure', 'argument' , -foreground => 'red3' ) ;
-$sw -> tag ('configure', 'par' , -lmargin1 => '4m' ,
- -lmargin2 => '6m' ) ;
-
-my %setups ;
-my %commands ;
-my %loadedsetups ;
-my %positions ;
-my %crosslinks ;
-
-my $current_setup = '' ;
-
-#D Setups are organized in files called \type {*.tws} and
-#D alike. Several files can be loaded simultaneously. When
-#D loading, we grab whole paragraphs. The variables and values
-#D belonging to a command, are stored in the hash table \type
-#D {setups}, one per language. The command templates are
-#D stored in \type {commands}.
-#D
-#D A \type {tws} file is generated by \CONTEXT\ from the setup
-#D definition files. Only \CONTEXT\ knows the current meaning
-#D of commands and keywords. The files are generating by
-#D simply saying something like:
-#D
-#D \starttypen
-#D texexec --interface=en setupd
-#D texexec --interface=de setupd
-#D texexec --interface=nl setupd
-#D texexec --interface=cz setupd
-#D texexec --interface=it setupd
-#D \stoptypen
-#D
-#D This results in files formatted as:
-#D
-#D \starttypen
-#D startsetup
-#D com:setupcolors
-#D typ:vars/
-#D var:state:start,stop,global,local:
-#D var:conversion:yes,no,always:
-#D var:reduction:yes,no:
-#D var:rgb:yes,no:
-#D var:cmyk:yes,no:
-#D stopsetup
-#D \stoptypen
-#D
-#D This format can be stored rather efficient and parsed rather
-#D fast. What more do we need.
-
-sub load_setup
- { my $filename = shift ;
- unless (keys %{$commands{$filename}})
- { local $/ = 'stopsetup' ; # in plaats van '' ivm unix ; (taco)
- $current_setup = '' ;
- if (open(SETUP, "$tws_path$filename.tws" ))
- { my $position = 0 ;
- while (<SETUP>)
- { chomp ;
- s/startsetup//mso ;
- s/stopsetup//mso ; # redundant geworden
- s/\r\n //gms ; # in plaats van s/ //gms ; (taco)
- s/com\:(.*?)\:\s(.*)//mso ;
- my $string = $1 ;
- my $command = $1 ;
- my $setup = $2 ;
- ++$position ;
- $string =~ s/(.*?)\<\<(.*?)\>\>(.*?)/$1$2$3/o ;
- $setups {$filename}{$string} = $setup ;
- $commands {$filename}{$string} = $command ;
- $positions {$filename}{$string} = $position ;
- $crosslinks{$filename}[$position] = $string }
- close (SETUP) } }
- my @list = sort {lc $a cmp lc $b} keys %{$commands{$filename}} ;
- $cw -> delete ('0.0', 'end') ;
- $cw -> insert ('end', @list) ;
- $cw -> selectionSet ('0.0', '0.0') ;
- $cw -> activate ('0.0') ;
- $setup_file = $filename ;
- update_setup }
-
-sub load_setups
- { foreach my $setup (@setup_files) { load_setup ($setup) } ;
- $mw -> deiconify() }
-
-#D The core of this module deals with transforming the
-#D definitions like shown earlier. Details on the format
-#D can be found in the file \type {setupd.tex}. We use the
-#D \type {Tk::Text} automatic hanging identation features.
-#D The next subs are examples of the kind you write once
-#D and never look at again.
-
-my @arguments = () ;
-my $nested_setup = 0 ;
-my $continue_setup = 0 ;
-my $argument = 0 ;
-my $stopsuffix = '' ;
-my $stopcommand = '' ;
-
-my %arg ;
-
-$arg {repeat} = '//n*/' ;
-$arg {arg} = 'argument/{/.../}' ;
-$arg {args} = 'argument/{/..,...,../}' ;
-$arg {dis} = 'argument/$$/.../$$' ;
-$arg {idx} = 'argument/{/.../}' ;
-$arg {idxs} = 'argument/{/..+...+../}' ;
-$arg {mat} = 'argument/$/...:$' ;
-$arg {nop} = '//.../' ;
-$arg {fil} = '//.../' ;
-$arg {pos} = 'symbol/(/.../)' ;
-$arg {poss} = 'symbol/(/...,.../)' ;
-$arg {sep} = 'command//\\\\/' ;
-$arg {ref} = 'symbol/[/ref/]' ;
-$arg {refs} = 'symbol/[/ref,../]' ;
-$arg {val} = 'symbol/[/.../]' ;
-$arg {vals} = 'symbol/[/..,...,../]' ;
-$arg {var} = 'symbol/[/..=../]' ;
-$arg {vars} = 'symbol/[/..,..=..,../]' ;
-$arg {cmd} = 'command//\cmd/' ;
-$arg {dest} = 'symbol/[/..ref/]' ;
-$arg {dests} = 'symbol/[/..,..refs,../]' ;
-$arg {trip} = 'symbol/[/x:y:z=/]' ;
-$arg {trips} = 'symbol/[/x:y:z=,../]' ;
-$arg {wrd} = 'argument/{/.../}' ;
-$arg {wrds} = 'argument/{/......./}' ;
-$arg {par} = 'command//\par/' ;
-$arg {stp} = '//stop/' ;
-$arg {par} = 'command///' ;
-
-sub show_command
- { my $command = shift ;
- local $_ = $commands{$setup_file}{$command} ;
- if ($command eq $_)
- { $sw -> insert ('end', "\\$command", 'command' ) }
- elsif (/(.*?)\<\<(.*?)\>\>(.*?)/o)
- { $sw -> insert ('end', "\\", 'command' ) ;
- if ($1) { $sw -> insert ('end', $1, 'command' ) }
- if ($2) { $sw -> insert ('end', $2, ['command','user'] ) }
- if ($3) { $sw -> insert ('end', $3, 'command' ) }
- $stopsuffix = $2 } }
-
-sub show_left_argument
- { local $_ = shift ;
- my @thearg = split (/\//, $arg{$arguments[$_]}) ;
- $sw -> insert ('end', $thearg[1], ['par',$thearg[0]] ) }
-
-sub show_middle_argument
- { local $_ = shift ;
- my @thearg = split (/\//, $arg{$arguments[$_]}) ;
- if ($thearg[1])
- { $sw -> insert ('end', $thearg[2], 'par' ) }
- else
- { $sw -> insert ('end', $thearg[2], ['par',$thearg[0]] ) } }
-
-sub show_right_argument
- { local $_ = shift ;
- my @thearg = split (/\//, $arg{$arguments[$_]}) ;
- $sw -> insert ('end', $thearg[3], ['par',$thearg[0]] ) ;
- ++$argument }
-
-sub show_reference
- { if (($nested_setup<=1)&&(defined($arguments[$argument])))
- { if ($arguments[$argument]=~/ref/)
- { $sw -> insert ('end', "\n" ) ;
- show_left_argument ($argument) ;
- show_middle_argument ($argument) ;
- show_right_argument ($argument) } } }
-
-sub show_stop_command
- { my $before_stop = shift ;
- if ($stopcommand)
- { if ($stopsuffix)
- { $sw -> insert ('end', '\\stop', 'command' ) ;
- $sw -> insert ('end', $stopsuffix, ['command','user'] ) }
- else
- { $sw -> insert ('end', $stopcommand, 'command' ) } } }
-
-sub show_whatever_left
- { while ($argument<@arguments)
- { $sw -> insert ('end', "\n" ) ;
- show_left_argument ($argument) ;
- show_middle_argument ($argument) ;
- show_right_argument ($argument) ;
- ++$argument }
- if ($stopcommand)
- { $sw -> insert ('end', "\n...\n...\n...\n", 'par') ;
- show_stop_command } }
-
-sub do_update_setup # type: 0=all 1=vars 2=vals
- { my ($command, $type) = @_ ;
- my $setup = $setups{$setup_file}{$command} ;
- my $default = '' ;
- my $key = '' ;
- my $meaning = '' ;
- my @values = () ;
- local $_ ;
- ++$nested_setup ;
- while ($setup=~/(typ|var|val|ivr|ivl)\:(.*?)\:\s/mgo)
- { $key = $1 ;
- $meaning = $2 ;
- if (($key=~/var/o)&&($type!=2))
- { $_ = $meaning ; s/(.*?)\:(.*?)\:(.*)//o ;
- if (($nested_setup>1)&&(!$2)) { next }
- $key = $1 ;
- if ($3) { $default = $3 } else { $default = '' }
- $_= $2 ; s/\s//go ; @values = split (/,/,$_) ;
- if ($continue_setup)
- { $sw -> insert ('end', ",\n ", 'par') }
- else
- { $continue_setup = 1 ;
- $sw -> insert ('end', "\n", 'par') ;
- show_left_argument($argument) }
- $sw -> insert ('end', $key , 'par' ) ;
- $sw -> insert ('end', '=', ['symbol','par'] ) ;
- #while (1)
- while (@values)
- { my $value = shift @values ;
- if ($value =~ /^\*/o)
- { $value =~ s/^\*//o ;
- $sw -> insert ('end', lc $value, ['variable','par'] ) }
- elsif ($value eq $default)
- { $sw -> insert ('end', $value, ['default','par'] ) }
- else
- { $sw -> insert ('end', $value, 'par' ) }
- if (@values)
- { $sw -> insert ('end', '|' , ['or','par'] ) }
- else
- { last } } }
- elsif (($key=~/val/o)&&($type!=1))
- { $_ = $meaning ; s/(.*)\:(.*)//o ;
- if (($nested_setup>1)&&(!$2)) { next }
- $_ = $1 ; s/\s//go ; @values = split (/,/,$_) ;
- if ($2) { $default = $2 } else { $default = '' }
- if ($continue_setup)
- { $continue_setup = 0 ;
- show_right_argument($argument) }
- $sw -> insert ('end', "\n" , 'par') ;
- show_left_argument($argument) ;
- #while (1)
- while (@values)
- { unless (@values) { last }
- my $value = shift (@values) ;
- if ($value =~ /^\*/o)
- { $value =~ s/^\*//o ;
- $sw -> insert ('end', lc $value, ['variable','par'] ) }
- elsif ($value eq $default)
- { $sw -> insert ('end', $value, ['default','par'] ) }
- else
- { $sw -> insert ('end', $value, 'par' ) }
- if (@values)
- { $sw -> insert ('end', ', ', 'par' ) }
- else
- { last } }
- show_right_argument($argument) }
- elsif ($key=~/typ/o)
- { if ($nested_setup==1)
- { show_command ($command) ;
- my $arguments = $meaning ;
- if ($arguments=~/stp/)
- { $_ = $command ;
- s/start(.*)/$1/o ;
- $stopcommand = "\\stop$_" ;
- $arguments =~ s/stp//go }
- @arguments = split (/\//,$arguments) ;
- if (@arguments)
- { for (my $i=0;$i<@arguments;$i++)
- { show_left_argument ($i) ;
- show_middle_argument ($i) ;
- show_right_argument ($i) }
- if ($stopcommand)
- { $sw -> insert ('end', ' ... ') ;
- show_stop_command }
- $sw -> insert ('end', "\n\n") ;
- show_command ($command) }
- $argument = 0 ;
- $continue_setup = 0 } }
- elsif ($key=~/ivr/o)
- { $meaning =~ s/(.*)\:(.*)//o ;
- do_update_setup ($1,1) }
- elsif ($key=~/ivl/o)
- { $meaning =~ s/(.*)\:(.*)//o ;
- do_update_setup ($1,2) }
- show_reference }
- --$nested_setup ;
- if (($continue_setup)&&(!$nested_setup))
- { show_right_argument ;
- show_whatever_left } }
-
-#D Now the real work is done, we only have to define a few
-#D housekeeping routines. The next sub adapts the text area
-#D to the current selected command and normally is bound to
-#D the list browsing commands.
-
-sub update_setup
- { $old_setup_file = $setup_file ;
- if (keys %{$commands{$setup_file}})
- { my $key ;
- unless ($cw->curselection)
- { $cw -> selectionSet('0.0','0.0') }
- $key = $cw -> get($cw->curselection) ;
- if ($current_setup ne $key)
- { $current_setup = $key ;
- $sw -> delete ('1.0', 'end' ) ;
- $nested_setup = 0 ;
- $argument = 0 ;
- $stopcommand = '' ;
- $stopsuffix = '' ;
- do_update_setup ($key,0) ;
- $mw -> raise ;
- $mw -> focus } } }
-
-#D In editors we want to provide context sensitive help
-#D information. The next sub first tries to locate the
-#D commands asked for in the setup data currently selected,
-#D and when not found takes a look at all the loaded files.
-
-sub show_setup
- { my $asked_for = shift ;
- unless ($asked_for) { return }
- my $found = 0 ;
- $asked_for =~ s/^\\// ;
- if ($setup_interface)
- { $found = 0 ;
- foreach my $name (@setup_files)
- { if (($name=~/\-$setup_interface/)&&(exists($commands{$name}{$asked_for})))
- { $found = 1 ;
- $setup_file = $name ;
- last } } }
- if (!($found)&&(exists($commands{$setup_file}{$asked_for})))
- { $found = 1 }
- else
- { $found = 0 ;
- foreach my $name (@setup_files)
- { if (exists($commands{$name}{$asked_for}))
- { $found = 1 ;
- $setup_file = $name ;
- last } } }
- if ($found)
- { my @list = sort {lc $a cmp lc $b} keys %{$commands{$setup_file}} ;
- $cw -> delete ('0.0', 'end') ;
- $cw -> insert ('end', @list) ;
- $found = 0 ;
- foreach (@list) { if ($_ eq $asked_for) { last } ++$found }
- my $index = "$found.0" ;
- $cw -> selectionSet ($index, $index) ;
- $cw -> activate ($index) ;
- $cw -> see ($index) ;
- update_setup ;
- $mw -> raise ;
- $mw -> focus } }
-
-#D Whenever a new set of commands is selected (by means of the
-#D buttons on top the screen) the list and text are to be
-#D updated.
-
-sub change_setup
- { my $command = '' ;
- if ($old_setup_file)
- { unless ($cw->curselection)
- { $cw -> selectionSet('0.0','0.0') }
- $command = $cw -> get($cw->curselection) ;
- my $position = $positions{$old_setup_file}{$command} ;
- $command = $crosslinks{$setup_file}[$position] }
- load_setup($setup_file) ;
- my @list = sort {lc $a cmp lc $b} keys %{$commands{$setup_file}} ;
- $cw -> delete ('0.0', 'end') ;
- $cw -> insert ('end', @list) ;
- if ($command)
- { show_setup($command) }
- else
- { $cw -> selectionClear ('0.0','end') ;
- $cw -> selectionSet ('0.0', '0.0') ;
- $cw -> see ('0.0') ;
- $cw -> activate ('0.0') }
- update_setup ;
- $mw -> raise ;
- $mw -> focus }
-
-#D Sometimes we want to make sure the dat is loaded indeed:
-
-sub setup_found
- { my $filename = shift ;
- if (-e "$tws_path$filename.tws")
- { $setup_file = $filename ;
- return 1 }
- else
- { return 0 } }
-
-#D The next feature is dedicated to Tobias, who suggested
-#D it, and Taco, who saw it as yet another proof of the
-#D speed of \PERL. It's also dedicated to Ton, who needs it
-#D for translating the big manual.
-
-sub handle_request
- { my $index = $cw -> index('end') ;
- unless ($index) { return }
- my $req = $request -> get ;
- unless ($req) { return }
- $req =~ s/\\//o ;
- $req =~ s/\s//go ;
- $request -> delete('0','end') ;
- $request -> insert('0',$req) ;
- unless ($req) { return }
- my ($l,$c) = split (/\./,$index) ;
- for (my $i=0;$i<=$l;$i++)
- { $index = "$i.0" ;
- my $str = $cw -> get ($index, $index) ;
- if ($str =~ /^$req/)
- { $cw -> selectionClear ('0.0','end') ;
- $cw -> selectionSet ($index, $index) ;
- $cw -> activate ($index) ;
- $cw -> see ($index) ;
- update_setup ;
- $mw -> raise ;
- $mw -> focus ;
- return } } }
-
-$request -> bind ('<Return>', sub { handle_request } ) ;
-
-sub insert_request
- { my ($self, $chr) = @_ ;
- if ($self ne $request)
- { $request -> insert ('end', $chr) }
- handle_request }
-
-foreach my $chr ('a'..'z','A'..'Z')
- { $mw -> bind ( "<KeyPress-$chr>", sub { insert_request(shift, $chr) } ) }
-
-$mw -> bind ( "<backslash>", sub { insert_request(shift, "\\") } ) ;
-
-sub delete_request
- { my $self = shift ;
- if ($self ne $request)
- { my $to = $request -> index ('end') ;
- my $from = $to - 1 ;
- if ($from<0) { $from = 0 }
- $request -> delete ($from,$to) }
- handle_request }
-
-$mw -> bind ( "<BackSpace>", sub { delete_request } ) ;
-
-sub new_request
- { $request -> delete (0,'end') ;
- handle_request }
-
-$mw -> bind ( "<space>", sub { new_request } ) ;
-
-#D Just in case:
-
-sub raise_setup
- { $mw -> raise }
-
-sub dont_exit
- { $mw -> protocol( 'WM_DELETE_WINDOW' => sub { } ) }
-
-#D An example use is:
-#D
-#D \starttypen
-#D load_setup ("cont-$nl") ;
-#D show_setup ('omlijnd') ;
-#D MainLoop () ;
-#D \stoptypen
-#D
-#D Now everything is done, we return 1:
-
-1 ;
diff --git a/Master/texmf-dist/scripts/context/perl/texexec.pl b/Master/texmf-dist/scripts/context/perl/texexec.pl
deleted file mode 100644
index fb564bbd5ed..00000000000
--- a/Master/texmf-dist/scripts/context/perl/texexec.pl
+++ /dev/null
@@ -1,3294 +0,0 @@
-eval '(exit $?0)' && eval 'exec perl -w -S $0 ${1+"$@"}' && eval 'exec perl -w -S $0 $argv:q'
- if 0 ;
-
-#D \module
-#D [ file=texexec.pl,
-#D version=2004.08.29,
-#D title=running \ConTeXt,
-#D subtitle=\TEXEXEC,
-#D author=Hans Hagen,
-#D date=\currentdate,
-#D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
-#C
-#C This module is part of the \CONTEXT\ macro||package and is
-#C therefore copyrighted by \PRAGMA. See readme.pdf for
-#C details.
-
-# Thanks to Tobias Burnus for the german translations.
-# Thanks to Thomas Esser for hooking it into web2c
-# Thanks to Taco Hoekwater for suggesting improvements
-# Thanks to Wybo Dekker for the advanced help interface and making it strict
-# Thanks to Fabrice Popineau for windows path trickery and fixes
-
-# (I still have to completely understand the help code -)
-
-#D We started with a hack provided by Thomas Esser. This
-#D expression replaces the unix specific line \type
-#D {#!/usr/bin/perl}.
-
-#D History has learned that writing wrappers like this is quite painful
-#D because of differences between platforms, changes in the tex command
-#D line flags (fmt), default behaviour (e.g. 8 bit), and the assumption
-#D that everyone runs the same tex and that distributers take care of
-#D everything. Well, the result is a messy script like this ... Sorry.
-
-use strict ;
-
-my $OriginalArgs = join(' ',@ARGV) ;
-
-#~ use warnings ; # strange warnings, todo
-
-# todo: second run of checksum of mp file with --nomprun changes
-# todo: warning if no args
-# todo: <<<< in messages
-# todo: cleanup
-
-use Cwd;
-use Time::Local;
-use Config;
-use Getopt::Long;
-use Class::Struct; # needed for help subsystem
-use FindBin;
-use File::Compare;
-use File::Temp;
-use Digest::MD5;
-
-#~ use IO::Handle; autoflush STDOUT 1;
-
-my %ConTeXtInterfaces; # otherwise problems with strict
-my %ResponseInterface; # since i dunno how to allocate else
-
-my %Help;
-
-#D In this script we will launch some programs and other
-#D scripts. \TEXEXEC\ uses an ini||file to sort out where
-#D those programs are stored. Two boolean variables keep
-#D track of the way to call the programs. In \TEXEXEC,
-#D \type {$dosish} keeps track of the operating system.
-#D It will be no surprise that Thomas Esser provided me
-#D the neccessary code to accomplish this.
-
-$ENV{"MPXCOMMAND"} = "0"; # otherwise loop
-
-my $TotalTime = time;
-
-# start random seed hack
-#
-# This hack is needed since tex has 1 minute resolution, so
-# we need to be smaller about 1440 (== 24*60 == tex's max time)
-# in which case (david a's) random calculator will overflow.
-
-# my ( $sec, $min, $rest ) = gmtime;
-# my $RandomSeed = $min * 60 + $sec;
-#
-# # i have to look up the mod function -)
-#
-# if ( $RandomSeed > 2880 ) { $RandomSeed -= 2880 }
-# if ( $RandomSeed > 1440 ) { $RandomSeed -= 1440 }
-
-my ($sec, $min) = gmtime;
-my $RandomSeed = ($min * 60 + $sec) % 2880; # else still overflow
-
-# See usage of $Random and $RandomSeed later on.
-#
-# end random seed hack
-
-my $dosish = ( $Config{'osname'} =~ /^(ms)?dos|^os\/2|^mswin/i );
-my $escapeshell = ( ($ENV{'SHELL'}) && ($ENV{'SHELL'} =~ m/sh/i ));
-
-my $TeXUtil = 'texutil';
-my $TeXExec = 'texexec';
-my $MetaFun = 'metafun';
-my $MpToPdf = 'mptopdf';
-
-$Getopt::Long::passthrough = 1; # no error message
-$Getopt::Long::autoabbrev = 1; # partial switch accepted
-
-my $AddEmpty = '';
-my $Alone = 0;
-my $Optimize = 0;
-my $ForceTeXutil = 0;
-my $Arrange = 0;
-my $BackSpace = '0pt';
-my $Background = '';
-my $CenterPage = 0;
-my $ConTeXtInterface = 'unknown';
-my $Convert = '';
-my $DoMPTeX = 0;
-my $DoMPXTeX = 0;
-my $EnterBatchMode = 0;
-my $EnterNonStopMode = 0;
-my $Environments = '';
-my $Modules = '';
-my $FastMode = 0;
-my $FinalMode = 0;
-my $Format = '';
-my $MpDoFormat = '';
-my $HelpAsked = 0;
-my $Version = 0;
-my $MainBodyFont = 'standard';
-my $MainLanguage = 'standard';
-my $MainResponse = 'standard';
-my $MakeFormats = 0;
-my $Markings = 0;
-my $Mode = '';
-my $NoArrange = 0;
-my $NoDuplex = 0;
-my $NOfRuns = 8;
-my $NoMPMode = 0;
-my $NoMPRun = 0;
-my $NoBanner = 0;
-my $AutoMPRun = 0;
-my $OutputFormat = 'standard';
-my $Pages = '';
-my $PageScale = '1000'; # == 1.0
-my $PaperFormat = 'standard';
-my $PaperOffset = '0pt';
-my $PassOn = '';
-my $PdfArrange = 0;
-my $PdfSelect = 0;
-my $PdfCombine = 0;
-my $PdfOpen = 0;
-my $PdfClose = 0;
-my $AutoPdf = 0;
-my $UseXPdf = 0;
-my $PrintFormat = 'standard';
-my $ProducePdfT = 0;
-my $ProducePdfM = 0;
-my $ProducePdfX = 0;
-my $ProducePdfXTX = 0;
-my $ProducePs = 0;
-my $Input = "";
-my $Result = '';
-my $Suffix = '';
-my $RunOnce = 0;
-my $Selection = '';
-my $Combination = '2*4';
-my $SilentMode = 0;
-my $TeXProgram = '';
-my $TeXTranslation = '';
-my $TextWidth = '0pt';
-my $TopSpace = '0pt';
-my $TypesetFigures = 0;
-my $ForceFullScreen = 0;
-my $ScreenSaver = 0;
-my $TypesetListing = 0;
-my $TypesetModule = 0;
-my $UseColor = 0;
-my $Verbose = 0;
-my $PdfCopy = 0;
-my $PdfTrim = 0;
-my $LogFile = "";
-my $MpyForce = 0;
-my $InpPath = "";
-my $AutoPath = 0;
-my $RunPath = "";
-my $Arguments = "";
-my $Pretty = 0;
-my $SetFile = "";
-my $TeXTree = "";
-my $TeXRoot = "";
-my $Purge = 0;
-my $Separation = "";
-my $ModeFile = "";
-my $GlobalFile = 0;
-my $AllPatterns = 0;
-my $ForceXML = 0;
-my $Random = 0;
-my $Filters = '';
-my $NoMapFiles = 0 ;
-my $Foxet = 0 ;
-my $TheEnginePath = 0 ;
-my $Paranoid = 0 ;
-my $NotParanoid = 0 ;
-my $BoxType = '' ;
-my $Local = '' ;
-
-my $TempDir = '' ;
-
-my $StartLine = 0 ;
-my $StartColumn = 0 ;
-my $EndLine = 0 ;
-my $EndColumn = 0 ;
-
-my $MpEngineSupport = 0 ; # not now, we also need to patch executemp in context itself
-
-# makempy :
-
-my $MakeMpy = '';
-
-&GetOptions(
- "arrange" => \$Arrange,
- "batch" => \$EnterBatchMode,
- "nonstop" => \$EnterNonStopMode,
- "color" => \$UseColor,
- "centerpage" => \$CenterPage,
- "convert=s" => \$Convert,
- "environments=s" => \$Environments,
- "usemodules=s" => \$Modules,
- "xml" => \$ForceXML,
- "xmlfilters=s" => \$Filters,
- "fast" => \$FastMode,
- "final" => \$FinalMode,
- "format=s" => \$Format,
- "mpformat=s" => \$MpDoFormat,
- "help" => \$HelpAsked,
- "version" => \$Version,
- "interface=s" => \$ConTeXtInterface,
- "language=s" => \$MainLanguage,
- "bodyfont=s" => \$MainBodyFont,
- "results=s" => \$Result,
- "response=s" => \$MainResponse,
- "make" => \$MakeFormats,
- "mode=s" => \$Mode,
- "module" => \$TypesetModule,
- "figures=s" => \$TypesetFigures,
- "fullscreen" => \$ForceFullScreen,
- "screensaver" => \$ScreenSaver,
- "listing" => \$TypesetListing,
- "mptex" => \$DoMPTeX,
- "mpxtex" => \$DoMPXTeX,
- "noarrange" => \$NoArrange,
- "nomp" => \$NoMPMode,
- "nomprun" => \$NoMPRun,
- "nobanner" => \$NoBanner,
- "automprun" => \$AutoMPRun,
- "once" => \$RunOnce,
- "output=s" => \$OutputFormat,
- "pages=s" => \$Pages,
- "paper=s" => \$PaperFormat,
- "paperformat=s" => \$PaperFormat,
- "passon=s" => \$PassOn,
- "path=s" => \$InpPath,
- "autopath" => \$AutoPath,
- "pdf" => \$ProducePdfT,
- "pdm" => \$ProducePdfM,
- "dpm" => \$ProducePdfM,
- "pdx" => \$ProducePdfX,
- "dpx" => \$ProducePdfX,
- "xtx" => \$ProducePdfXTX,
- "ps" => \$ProducePs,
- "pdfarrange" => \$PdfArrange,
- "pdfselect" => \$PdfSelect,
- "pdfcombine" => \$PdfCombine,
- "pdfcopy" => \$PdfCopy,
- "pdftrim" => \$PdfTrim,
- "scale=s" => \$PageScale,
- "selection=s" => \$Selection,
- "combination=s" => \$Combination,
- "noduplex" => \$NoDuplex,
- "offset=s" => \$PaperOffset,
- "paperoffset=s" => \$PaperOffset,
- "backspace=s" => \$BackSpace,
- "topspace=s" => \$TopSpace,
- "markings" => \$Markings,
- "textwidth=s" => \$TextWidth,
- "addempty=s" => \$AddEmpty,
- "background=s" => \$Background,
- "logfile=s" => \$LogFile,
- "print=s" => \$PrintFormat,
- "suffix=s" => \$Suffix,
- "runs=s" => \$NOfRuns,
- "silent" => \$SilentMode,
- "tex=s" => \$TeXProgram,
- "verbose" => \$Verbose,
- "alone" => \$Alone,
- "optimize" => \$Optimize,
- "texutil" => \$ForceTeXutil,
- "mpyforce" => \$MpyForce,
- "input=s" => \$Input,
- "arguments=s" => \$Arguments,
- "pretty" => \$Pretty,
- "setfile=s" => \$SetFile, # obsolete
- "purge" => \$Purge,
- #### yet undocumented #################
- "runpath=s" => \$RunPath,
- "random" => \$Random,
- "makempy=s" => \$MakeMpy,
- "allpatterns" => \$AllPatterns,
- "separation=s" => \$Separation,
- "textree=s" => \$TeXTree,
- "texroot=s" => \$TeXRoot,
- "translate=s" => \$TeXTranslation,
- "pdfclose" => \$PdfClose,
- "pdfopen" => \$PdfOpen,
- "autopdf" => \$AutoPdf,
- "xpdf" => \$UseXPdf,
- "modefile=s" => \$ModeFile, # additional modes file
- "globalfile" => \$GlobalFile,
- "nomapfiles" => \$NoMapFiles,
- "foxet" => \$Foxet,
- "engine" => \$TheEnginePath,
- "paranoid" => \$Paranoid,
- "notparanoid" => \$NotParanoid,
- "boxtype=s" => \$BoxType, # media art crop bleed trim
- "local" => \$Local,
- #### unix is unsafe (symlink viruses)
- "tempdir=s" => \$TempDir,
- #### experiment
- "startline=s" => \$StartLine,
- "startcolumn=s" => \$StartColumn,
- "endline=s" => \$EndLine,
- "endcolumn=s" => \$EndColumn
-); # don't check name
-
-if ($Foxet) {
- $ProducePdfT = 1 ;
- $ForceXML = 1 ;
- $Modules = "foxet" ;
- $Purge = 1 ;
-}
-
-# a set file (like blabla.bat) can set paths now
-
-if ( $SetFile ne "" ) { load_set_file( $SetFile, $Verbose ); $SetFile = "" }
-
-# later we will do a second attempt.
-
-$SIG{INT} = "IGNORE";
-
-if ( $ARGV[0] && $ARGV[0] =~ /\.mpx$/io ) { # catch -tex=.... bug in mpost
- $TeXProgram = '';
- $DoMPXTeX = 1;
- $NoMPMode = 1;
-}
-
-####
-
-if ($Version) {
- $Purge = 1 ;
- }
-
-####
-
-if ($Paranoid) {
- $ENV{shell_escape} = 'f' ;
- $ENV{openout_any} = 'p' ;
- $ENV{openin_any} = 'p' ;
-} elsif ($NotParanoid) {
- $ENV{shell_escape} = 't' ;
- $ENV{openout_any} = 'p' ;
- $ENV{openin_any} = 'a' ;
-}
-
-if (defined $ENV{openin_any} && $ENV{openin_any} eq 'p') {
- $Paranoid = 1 ; # extra test in order to set readlevel
-}
-
-if ((defined $ENV{shell_escape} && $ENV{shell_escape} eq 'f') ||
- (defined $ENV{SHELL_ESCAPE} && $ENV{SHELL_ESCAPE} eq 'f')) {
- $AutoMPRun = 1 ;
-}
-
-if ($ScreenSaver) {
- $ForceFullScreen = 1;
- $TypesetFigures = 'c';
- $ProducePdfT = 1;
- $Purge = 1;
-}
-
-if ( $DoMPTeX || $DoMPXTeX ) {
- $RunOnce = 1;
- $ProducePdfT = 0;
- $ProducePdfX = 0;
- $ProducePdfM = 0;
- $ProducePdfXTX = 0;
- $ProducePs = 0;
-}
-
-if ( $PdfArrange || $PdfSelect || $PdfCopy || $PdfTrim || $PdfCombine ) {
- $ProducePdfT = 1;
- $RunOnce = 1;
-}
-
-if ($ProducePdfT) { $OutputFormat = "pdftex" }
-elsif ($ProducePdfM) { $OutputFormat = "dvipdfm" }
-elsif ($ProducePdfX) { $OutputFormat = "dvipdfmx" }
-elsif ($ProducePdfXTX) { $OutputFormat = "xetex" }
-elsif ($ProducePs) { $OutputFormat = "dvips" }
-
-if ( $ProducePdfXTX ) {
- $TeXProgram = 'xetex' ; # ignore the default pdfetex engine
- $PassOn .= ' -no-pdf ' ; # Adam Lindsay's preference
-}
-
-if ($AutoPdf) {
- $PdfOpen = $PdfClose = 1 ;
-}
-
-my $PdfOpenCall = "" ;
-
-if ($PdfOpen) {
- $PdfOpenCall = "pdfopen --file" ;
-}
-
-if ($UseXPdf && !$dosish) {
- $PdfOpenCall = "xpdfopen" ;
-}
-
-# this is our hook into paranoid path extensions, assumes that
-# these three vars are part of path specs in texmf.cnf
-
-foreach my $i ('TXRESOURCES','MPRESOURCES','MFRESOURCES') {
- foreach my $j ($RunPath,$InpPath) {
- if ($j ne '') {
- if ($ENV{$i} ne '') {
- $ENV{$i} = $ENV{$i} . ',' . $j ;
- } else {
- $ENV{$i} = $j ;
- }
- }
- }
-}
-
-if ( $RunOnce || $Pages || $TypesetFigures || $TypesetListing ) { $NOfRuns = 1 }
-
-if ( ( $LogFile ne '' ) && ( $LogFile =~ /\w+\.log$/io ) ) {
- open( LOGFILE, ">$LogFile" );
- *STDOUT = *LOGFILE;
- *STDERR = *LOGFILE;
-}
-
-my $Program = " TeXExec 5.4.3 - ConTeXt / PRAGMA ADE 1997-2005";
-
-print "\n$Program\n\n";
-
-if ($Verbose) { print " current path : " . cwd . "\n" }
-
-my $pathslash = '/';
-if ( $FindBin::Bin =~ /\\/ ) { $pathslash = "\\" }
-my $cur_path = ".$pathslash";
-
-# we need to handle window's "Program Files" path (patch by Fabrice P)
-
-my $own_path = "$FindBin::Bin/";
-my $own_type = $FindBin::Script;
-my $own_quote = ( $own_path =~ m/^[^\"].* / ? "\"" : "" );
-my $own_stub = "";
-
-if ( $own_type =~ /(\.pl|perl)/oi ) { $own_stub = "perl " }
-
-if ( $own_type =~ /(\.(pl|bin|exe))$/io ) { $own_type = $1 }
-else { $own_type = '' }
-
-sub checked_path {
- my $path = shift;
- if ( ( defined($path) ) && ( $path ne '' ) ) {
- $path =~ s/[\/\\]/$pathslash/go;
- $path =~ s/[\/\\]*$//go;
- $path .= $pathslash;
- } else {
- $path = '';
- }
- return $path;
-}
-
-sub checked_file {
- my $path = shift;
- if ( ( defined($path) ) && ( $path ne '' ) ) {
- $path =~ s/[\/\\]/$pathslash/go;
- } else {
- $path = '';
- }
- return $path;
-}
-
-sub CheckPath {
- my ( $Key, $Value ) = @_;
- if ( ( $Value =~ /\// ) && ( $Value !~ /\;/ ) ) # no multipath test yet
- {
- $Value = checked_path($Value);
- unless ( -d $Value ) {
- print " error : $Key set to unknown path $Value\n";
- }
- }
-}
-
-# set <variable> to <value>
-# for <script> set <variable> to <value>
-# except for <script> set <variable> to <value>
-
-my $IniPath = '';
-
-#D The kpsewhich program is not available in all tex distributions, so
-#D we have to locate it before running it (as suggested by Thomas).
-
-my @paths;
-
-if ( $ENV{PATH} =~ /\;/ ) { @paths = split( /\;/, $ENV{PATH} ) }
-else { @paths = split( /\:/, $ENV{PATH} ) }
-
-my $kpsewhich = '';
-
-sub found_ini_file {
- my $suffix = shift ;
- #~ $IniPath = $0 ;
- #~ $IniPath ~= s/\.pl$//io ;
- #~ $IniPath = $InPath . ".'" + $suffix ;
- #~ if (-e $IniPath) {
- #~ }
- # not really needed to check on texmfscripts, better on own path
- print " locating ini file : kpsewhiching texexec.$suffix on scripts\n" if $Verbose ;
- my $IniPath = `$kpsewhich --format="texmfscripts" -progname=context texexec.$suffix` ;
- chomp($IniPath) ;
- if ($IniPath eq '') {
- print " locating ini file : kpsewhiching texexec.$suffix elsewhere\n" if $Verbose ;
- $IniPath = `$kpsewhich --format="other text files" -progname=context texexec.$suffix` ;
- chomp($IniPath) ;
- }
- return $IniPath ;
-}
-
-if ( $IniPath eq '' ) {
- foreach (@paths) {
- my $p = checked_path($_) . 'kpsewhich';
- if ( ( -e $p ) || ( -e $p . '.exe' ) ) {
- $kpsewhich = $p;
- # FP: catch spurious error messages here if there $p has
- # spaces and $own_quote is not set
- $kpsewhich = ($kpsewhich =~ m/^[^\"].* / ? "\"$kpsewhich\"" : "$kpsewhich") ;
- $IniPath = found_ini_file("ini");
- unless ( -e $IniPath ) { $IniPath = found_ini_file("rme") }
- last;
- }
- }
- if ($Verbose) {
- if ( $kpsewhich eq '' ) {
- print " locating ini file : kpsewhich not found in path\n";
- } elsif ( $IniPath eq '' ) {
- print " locating ini file : not found by kpsewhich\n";
- } else {
- if ( $IniPath =~ /rme/oi ) {
- print " locating ini file : not found by kpsewhich, using '.rme' file\n";
- } else {
- print " locating ini file : found by kpsewhich\n";
- }
- }
- }
-}
-
-#D Now, when we didn't find the \type {kpsewhich}, we have
-#D to revert to some other method. We could have said:
-#D
-#D \starttypen
-#D unless ($IniPath)
-#D { $IniPath = `perl texpath.pl texexec.ini` }
-#D \stoptypen
-#D
-#D But loading perl (for the second time) take some time. Instead of
-#D providing a module, which can introduce problems with loading, I
-#D decided to copy the code of \type {texpath} into this file.
-
-use File::Find;
-# use File::Copy ; no standard in perl
-
-my ( $ReportPath, $ReportName, $ReportFile ) = ( 0, 0, 1 );
-my ( $FileToLocate, $PathToStartOn ) = ( '', '' );
-my ( $LocatedPath, $LocatedName, $LocatedFile ) = ( '', '', '' );
-
-sub DoLocateFile { # we have to keep on pruning
- if ( lc $_ eq $FileToLocate ) {
- $LocatedPath = $File::Find::dir;
- $LocatedName = $_;
- $LocatedFile = $File::Find::name;
- }
- if ($LocatedName) { $File::Find::prune = 1 }
-}
-
-sub LocatedFile {
- $PathToStartOn = shift;
- $FileToLocate = lc shift;
- if ( $FileToLocate eq '' ) {
- $FileToLocate = $PathToStartOn;
- $PathToStartOn = $own_path;
- }
- ( $LocatedPath, $LocatedName, $LocatedFile ) = ( '', '', '' );
- if ( $FileToLocate ne '' ) {
- if ( -e $cur_path . $FileToLocate ) {
- $LocatedPath = $cur_path;
- $LocatedName = $FileToLocate;
- $LocatedFile = $cur_path . $FileToLocate;
- } else {
- $_ = checked_path($PathToStartOn);
- if ( -e $_ . $FileToLocate ) {
- $LocatedPath = $_;
- $LocatedName = $FileToLocate;
- $LocatedFile = $_ . $FileToLocate;
- } else {
- $_ = checked_path($PathToStartOn);
- if (/(.*?[\/\\]texmf[\/\\]).*/i) {
- my $SavedRoot = $1;
- File::Find::find( \&DoLocateFile,
- checked_path( $1 . 'context/' ) );
- unless ($LocatedFile) {
- File::Find::find( \&DoLocateFile, $SavedRoot );
- }
- } else {
- $_ = checked_path($_);
- File::Find::find( \&DoLocateFile, $_ );
- }
- }
- }
- }
- return ( $LocatedPath, $LocatedName, $LocatedFile );
-}
-
-#D So now we can say:
-
-unless ($IniPath) {
- ( $LocatedPath, $LocatedName, $IniPath ) =
- LocatedFile( $own_path, 'texexec.ini' );
- if ($Verbose) {
- if ( $IniPath eq '' ) {
- print " locating ini file : not found by searching\n";
- } else {
- print " locating ini file : found by searching\n";
- }
- }
-}
-
-#D The last resorts:
-
-unless ($IniPath) {
- if ( $ENV{TEXEXEC_INI_FILE} ) {
- $IniPath = checked_path( $ENV{TEXEXEC_INI_FILE} ) . 'texexec.ini';
- unless ( -e $IniPath ) { $IniPath = '' }
- }
- if ($Verbose) {
- if ( $IniPath eq '' ) {
- print " locating ini file : no environment variable set\n";
- } else {
- print " locating ini file : found by environment variable\n";
- }
- }
-}
-
-unless ($IniPath) {
- $IniPath = $own_path . 'texexec.ini';
- unless ( -e $IniPath ) { $IniPath = '' }
- if ($Verbose) {
- if ( $IniPath eq '' ) {
- print " locating ini file : not found in own path\n";
- } else {
- print " locating ini file : found in own path\n";
- }
- }
-}
-
-#D Now we're ready for loading the initialization file! We
-#D also define some non strict variables. Using \type {$Done}
-#D permits assignments.
-
-my %Done;
-
-unless ($IniPath) { $IniPath = 'texexec.ini' }
-
-if ( open( INI, $IniPath ) ) {
- if ($Verbose) { print " reading : $IniPath\n" }
- while (<INI>) {
- if ( !/^[a-zA-Z\s]/oi ) { }
- elsif (/except for\s+(\S+)\s+set\s+(\S+)\s*to\s*(.*)\s*/goi) {
- my $one = $1;
- my $two = $2;
- my $three = $3;
- if ( $one ne $Done{"TeXShell"} ) {
- $three =~ s/^[\'\"]//o;
- $three =~ s/[\'\"]$//o;
- $three =~ s/\s*$//o;
- if ($Verbose) {
- print " setting : '$two' to '$three' except for '$one'\n";
- }
- $Done{"$two"} = $three;
- CheckPath( $two, $three );
- }
- } elsif (/for\s+(\S+)\s+set\s+(\S+)\s*to\s*(.*)\s*/goi) {
- my $one = $1;
- my $two = $2;
- my $three = $3;
- $three =~ s/\s*$//o;
- if ( $one eq $Done{"TeXShell"} ) {
- $three =~ s/^[\'\"]//o;
- $three =~ s/[\'\"]$//o;
- if ($Verbose) {
- print
-" setting : '$two' to '$three' for '$one'\n";
- }
- $Done{"$two"} = $three;
- CheckPath( $two, $three );
- }
- } elsif (/set\s+(\S+)\s*to\s*(.*)\s*/goi) {
- my $one = $1;
- my $two = $2;
- unless ( defined( $Done{"$one"} ) ) {
- $two =~ s/^[\'\"]//o;
- $two =~ s/[\'\"]$//o;
- $two =~ s/\s*$//o;
- if ($Verbose) {
- print
- " setting : '$one' to '$two' for 'all'\n";
- }
- $Done{"$one"} = $two;
- CheckPath( $one, $two );
- }
- }
- }
- close(INI);
- if ($Verbose) { print "\n" }
-} elsif ($Verbose) {
- print
- " warning : $IniPath not found, did you read 'texexec.rme'?\n";
- exit 1;
-} else {
- print
- " warning : $IniPath not found, try 'texexec --verbose'\n";
- exit 1;
-}
-
-sub IniValue {
- my ( $Key, $Default ) = @_;
- if ( defined( $Done{$Key} ) ) { $Default = $Done{$Key} }
- if ($Default =~ /^(true|yes|on)$/io) {
- $Default = 1 ;
- } elsif ($Default =~ /^(false|no|off)$/io) {
- $Default = 0 ;
- }
- if ($Verbose) { print " used setting : $Key = $Default\n" }
- return $Default;
-}
-
-my $TeXShell = IniValue( 'TeXShell', '' );
-my $SetupPath = IniValue( 'SetupPath', '' );
-my $UserInterface = IniValue( 'UserInterface', 'en' );
-my $UsedInterfaces = IniValue( 'UsedInterfaces', 'en' );
-my $TeXFontsPath = IniValue( 'TeXFontsPath', '.' );
-my $MpExecutable = IniValue( 'MpExecutable', 'mpost' );
-my $MpToTeXExecutable = IniValue( 'MpToTeXExecutable', 'mpto' );
-my $DviToMpExecutable = IniValue( 'DviToMpExecutable', 'dvitomp' );
-my $TeXProgramPath = IniValue( 'TeXProgramPath', '' );
-my $TeXFormatPath = IniValue( 'TeXFormatPath', '' );
-my $ConTeXtPath = IniValue( 'ConTeXtPath', '' );
-my $TeXScriptsPath = IniValue( 'TeXScriptsPath', '' );
-my $TeXHashExecutable = IniValue( 'TeXHashExecutable', '' );
-my $TeXExecutable = IniValue( 'TeXExecutable', 'tex' );
-my $TeXVirginFlag = IniValue( 'TeXVirginFlag', '-ini' );
-my $TeXBatchFlag = IniValue( 'TeXBatchFlag', '-interaction=batchmode' );
-my $TeXNonStopFlag = IniValue( 'TeXNonStopFlag', '-interaction=nonstopmode' );
-my $MpBatchFlag = IniValue( 'MpBatchFlag', '-interaction=batchmode' );
-my $MpNonStopFlag = IniValue( 'MpNonStopFlag', '-interaction=nonstopmode' );
-my $TeXPassString = IniValue( 'TeXPassString', '' );
-my $TeXFormatFlag = IniValue( 'TeXFormatFlag', '' );
-my $MpFormatFlag = IniValue( 'MpFormatFlag', '' );
-my $MpVirginFlag = IniValue( 'MpVirginFlag', '-ini' );
-my $MpPassString = IniValue( 'MpPassString', '' );
-my $MpFormat = IniValue( 'MpFormat', $MetaFun );
-my $MpFormatPath = IniValue( 'MpFormatPath', $TeXFormatPath );
-my $UseEnginePath = IniValue( 'UseEnginePath', '');
-
-if ($TheEnginePath) { $UseEnginePath = 1 }
-
-# ok, let's force the engine; let's also forget about
-# fmtutil, since it does not support $engine subpaths
-# we will replace texexec anyway
-
-$UseEnginePath = 1 ;
-$Alone = 1 ;
-
-my $FmtLanguage = IniValue( 'FmtLanguage', '' );
-my $FmtBodyFont = IniValue( 'FmtBodyFont', '' );
-my $FmtResponse = IniValue( 'FmtResponse', '' );
-my $TcXPath = IniValue( 'TcXPath', '' );
-
-
-$SetFile = IniValue( 'SetFile', $SetFile );
-
-if ( ($Verbose) && ( $kpsewhich ne '' ) ) {
- print "\n";
- my $CnfFile = `$kpsewhich -progname=context texmf.cnf`;
- chomp($CnfFile);
- print " applications will use : $CnfFile\n";
-}
-
-if ( ($FmtLanguage) && ( $MainLanguage eq 'standard' ) ) {
- $MainLanguage = $FmtLanguage;
-}
-if ( ($FmtBodyFont) && ( $MainBodyFont eq 'standard' ) ) {
- $MainBodyFont = $FmtBodyFont;
-}
-if ( ($FmtResponse) && ( $MainResponse eq 'standard' ) ) {
- $MainResponse = $FmtResponse;
-}
-
-# new versions, > 2004 will have -fmt as switch
-
-if ( $TeXFormatFlag eq "" ) {
- if ($TeXProgram =~ /(etex|pdfetex)/) {
- $TeXFormatFlag = "-efmt=" ; # >=2004 -fmt=
- } elsif ($TeXProgram =~ /(eomega)/) {
- $TeXFormatFlag = "-eoft=" ; # >=2004 obsolete
- } elsif ($TeXProgram =~ /(aleph)/) {
- $TeXFormatFlag = "-fmt=" ;
- } else {
- $TeXFormatFlag = "-fmt=" ;
- }
-}
-
-if ( $MpFormatFlag eq "" ) {
- $MpFormatFlag = "-mem=" ;
-}
-
-if ($TeXProgram) { $TeXExecutable = $TeXProgram }
-
-my $fmtutil = '';
-
-# obsolete
-#
-# if ( $MakeFormats || $Verbose ) {
-# if ($Alone || $UseEnginePath) {
-# if ($Verbose) { print " generating format : not using fmtutil\n" }
-# } elsif ( $TeXShell =~ /tetex|fptex/i ) {
-# foreach (@paths) {
-# my $p = checked_path($_) . 'fmtutil';
-# if ( -e $p ) { $fmtutil = $p; last }
-# elsif ( -e $p . '.exe' ) { $fmtutil = $p . '.exe'; last }
-# }
-# $fmtutil = ($fmtutil =~ m/^[^\"].* / ? "\"$fmtutil\"" : "$fmtutil") ;
-# if ($Verbose) {
-# if ( $fmtutil eq '' ) {
-# print " locating fmtutil : not found in path\n";
-# } else {
-# print " locating fmtutil : $fmtutil\n";
-# }
-# }
-# }
-# }
-
-if ($Verbose) { print "\n" }
-
-unless ($TeXScriptsPath) { $TeXScriptsPath = $own_path }
-
-unless ($ConTeXtPath) { $ConTeXtPath = $TeXScriptsPath }
-
-if ( $ENV{"HOME"} ) {
- if ($SetupPath) { $SetupPath .= "," }
-# my $home = $ENV{"HOME"};
-# $home = ($home =~ m/^[^\"].* / ? "\"$home\"" : "$home") ;
-# $SetupPath .= $home;
- $SetupPath .= $ENV{"HOME"};
-}
-
-if ($TeXFormatPath) { $TeXFormatPath =~ s/[\/\\]$//; $TeXFormatPath .= '/' }
-if ($MpFormatPath) { $MpFormatPath =~ s/[\/\\]$//; $MpFormatPath .= '/' }
-if ($ConTeXtPath) { $ConTeXtPath =~ s/[\/\\]$//; $ConTeXtPath .= '/' }
-if ($SetupPath) { $SetupPath =~ s/[\/\\]$//; $SetupPath .= '/' }
-if ($TeXScriptsPath) { $TeXScriptsPath =~ s/[\/\\]$//; $TeXScriptsPath .= '/' }
-
-sub QuotePath {
- my ($path) = @_;
- my @l = split(",", $path);
- map { my $e = $_; $e = ($e =~ m/^[^\"].* / ? "\"$e\"" : "$e"); $_ = $e ;} @l;
- return join(",", @l);
-}
-
-$SetupPath = &QuotePath($SetupPath);
-
-$SetupPath =~ s/\\/\//go;
-
-my %OutputFormats;
-
-# the mother of all drivers
-
-$OutputFormats{dvips} = "dvips";
-
-# needs an update
-
-$OutputFormats{acrobat} = "acrobat";
-
-# the core drivers
-
-$OutputFormats{pdftex} = "pdftex"; $OutputFormats{pdf} = "pdftex";
-$OutputFormats{dvipdfm} = "dvipdfm"; $OutputFormats{dpm} = "dvipdfm";
-$OutputFormats{dvipdfmx} = "dvipdfmx"; $OutputFormats{dpx} = "dvipdfmx";
-$OutputFormats{xetex} = "xetex"; $OutputFormats{xtx} = "xetex";
-$OutputFormats{dvips} = "dvips"; $OutputFormats{ps} = "dvips";
-
-# kind of obsolete now that yandy is gone
-
-$OutputFormats{dvipsone} = "dvipsone";
-$OutputFormats{dviwindo} = "dviwindo";
-
-# it was never finished
-
-$OutputFormats{dviview} = "dviview";
-
-my @ConTeXtFormats = ( "nl", "en", "de", "fr", "cz", "uk", "it", "ro", "xx");
-
-sub SetInterfaces {
- my ( $short, $long, $full ) = @_;
- $ConTeXtInterfaces{$short} = $short;
- $ConTeXtInterfaces{$long} = $short;
- $ResponseInterface{$short} = $full;
- $ResponseInterface{$long} = $full;
-}
-
-#SetInterfaces ( "en" , "unknown" , "english" ) ;
-
-SetInterfaces( "nl", "dutch", "dutch" );
-SetInterfaces( "en", "english", "english" );
-SetInterfaces( "de", "german", "german" );
-SetInterfaces( "fr", "french", "french" );
-SetInterfaces( "cz", "czech", "czech" );
-SetInterfaces( "uk", "british", "english" );
-SetInterfaces( "it", "italian", "italian" );
-SetInterfaces( "no", "norwegian", "norwegian" );
-SetInterfaces( "ro", "romanian", "romanian" );
-
-# Sub-option
-
-struct Subopt => {
- desc => '$', # description
- vals => '%' # assignable values
-};
-
-# Main option
-
-struct Opt => {
- desc => '$', # desciption
- vals => '%', # assignable values
- subs => '%' # suboptions
-};
-
-my $helpdone = 0;
-
-sub print_subopt {
- my ( $k, $opt ) = @_;
- $~ = 'H3';
- write;
- for $k ( sort keys %{ $opt->vals } ) {
- print_val( $k, ${ $opt->vals }{$k} );
- }
- format H3 =
-@>>>>>>>>>>>>>>>>>>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-"--$k",$opt->desc
-.
-}
-
-sub print_val {
- my ( $k, $opt ) = @_;
- $~ = 'H2';
- write;
- format H2 =
- @<<<<<<<< : @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-$k,$opt
-.
-}
-
-# read all options
-
-my $recurse = -1 ;
-my $shorthelp;
-my @help;
-my @opts = <DATA>;
-while (@opts) {
- $_ = shift @opts;
- last if /^--+/;
- my ( $k, $v ) = split( /\s+/, $_, 2 ); # was \t
- $Help{$k} = read_options($v);
-}
-
-# read a main option plus its
-# description,
-# assignable values and
-# sub-options and their
-# description and
-# assignable values
-
-sub read_options {
- $recurse++;
- my $v = shift;
- chomp;
- my $opt = $recurse ? Subopt->new() : Opt->new();
- $opt->desc($v);
-
- while (@opts) {
- $_ = shift @opts;
- if (/^--+/) { unshift @opts, $_ if $recurse; last }
- if ( $recurse && !/^=/ ) { unshift @opts, $_; last }
- chomp;
- my ( $kk, $vv ) = split( /\s+/, $_, 2 ); # was \t
- $vv ||= '';
- if (/^=/) { $opt->vals( $kk, $vv ) }
- elsif ( !$recurse ) { $opt->subs( $kk, read_options($vv) ) }
- }
- $recurse--;
- $opt;
-}
-
-sub print_opt {
- my ( $k, $opt ) = @_;
- if ($helpdone) { $shorthelp or print "\n" }
- $helpdone = 1; # hh
- $~ = 'H1';
- write;
- return if $shorthelp < 0;
- for $k ( sort keys %{ $opt->vals } ) {
- print_val( $k, ${ $opt->vals }{$k} );
- }
- return if $shorthelp > 0;
-
- for $k ( sort keys %{ $opt->subs } ) {
- print_subopt( $k, ${ $opt->subs }{$k} );
- }
- format H1 =
-@>>>>>>>>>>>>>>>>>>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-"--$k",$opt->desc
-.
-}
-
-# help to help
-
-sub show_help_options {
- print # "\n" .
- " --help overview of all options and their values\n"
- . " --help all all about all options\n"
- . " --help short just the main options\n"
- . " --help mode ... pdf all about a few options\n"
- . " --help '*.pdf' all about options containing 'pdf'\n"
- . "\n"
- . " more info http://www.pragma-ade.com/general/manuals/mtexexec.pdf\n"
- . " http://www.ntg.nl/mailman/listinfo/ntg-context\n";
-}
-
-# determine what user wants to see
-
-if ($HelpAsked) {
- $shorthelp = 0;
- @help = ( sort keys %Help );
- if ( "@ARGV" eq "all" ) { # everything
- } elsif ( "@ARGV" eq "short" ) { # nearly everything
- $shorthelp--;
- } elsif ( "@ARGV" eq "help" ) { # help on help
- show_help_options;
- exit;
- } elsif (@ARGV) { # one or a few options, completely
- my @h = @ARGV;
- @help = ();
- for (@h) { # print "testing $_\n";
- # next if (/^[\*\?]/) ; # HH, else error
- if (/^[\*\?]/) { $_ = ".$_" } # HH, else error
- $Help{$_} and push( @help, $_ ) or do {
- my $unknown = $_;
- for ( keys %Help ) { /$unknown/ and push( @help, $_ ) }
- }
- }
- } else { # all main option and their assignable values
- $shorthelp++;
- }
-}
-
-sub show_help_info {
- map { print_opt( $_, $Help{$_} ) } @help;
-}
-
-# uncomment this to see the structure of a Help element:
-# print Dumper($Help{pdfselect});
-
-#### end of help system
-
-my $FinalRunNeeded = 0;
-
-sub MPJobName {
- my $JobName = shift;
- my $MPfile = shift;
- my $MPJobName = '';
- if ( -e "$JobName-$MPfile.mp" && -s "$JobName-$MPfile.mp" > 100 ) {
- $MPJobName = "$JobName-$MPfile.mp"
- } elsif ( -e "$MPfile.mp" && -s "$MPfile.mp" > 100 ) {
- $MPJobName = "$MPfile.mp"
- } else { $MPJobName = "" }
- return $MPJobName;
-}
-
-sub System {
- my $cmd = shift ;
- unless ( $dosish && ! $escapeshell ) {
- $cmd =~ s/([^\\])\&/$1\\\&/io ;
- }
- if ($Verbose) {
- print "\n$cmd\n\n" ;
- }
- system($cmd)
-}
-
-sub Pipe {
- my $cmd = shift ;
- unless ( $dosish && ! $escapeshell ) {
- $cmd =~ s/([^\\])\&/$1\\\&/io ;
- }
- if ($Verbose) {
- print "\n$cmd\n\n" ;
- }
- return `$cmd`
-}
-
-
-sub RunPerlScript {
- my ( $ScriptName, $Options ) = @_;
- my $cmd = '';
- $own_quote = ($own_path =~ m/^[^\"].* / ? "\"" : "") ;
- if ($Verbose) {
- $Options .= ' --verbose' ;
- }
- if ($dosish) {
- if ( -e "own_path$ScriptName$own_type" ) {
- $cmd =
-"$own_stub$own_quote$own_path$ScriptName$own_type$own_quote $Options";
- } elsif ( -e "$TeXScriptsPath$ScriptName$own_type" ) {
- $cmd =
-"$own_stub$own_quote$TeXScriptsPath$ScriptName$own_type$own_quote $Options";
- } else {
- $cmd = "";
- }
- } else {
- $cmd = "$ScriptName $Options";
- }
- unless ( $cmd eq "" ) {
- System($cmd) ;
- }
-}
-
-my $FullFormat = '';
-
-sub CheckOutputFormat {
- my $Ok = 1;
- if ( $OutputFormat ne 'standard' ) {
- my @OutputFormat = split( /,/, $OutputFormat );
- foreach my $F (@OutputFormat) {
- if ( defined( $OutputFormats{ lc $F } ) ) {
- my $OF = $OutputFormats{ lc $F };
- next if ( ",$FullFormat," =~ /\,$OF\,/ );
- if ($FullFormat) { $FullFormat .= "," }
- $FullFormat .= "$OutputFormats{lc $F}";
- } else {
- $Ok = 0;
- }
- }
- if ( !$Ok ) {
- print(" unknown output format : $OutputFormat\n");
- }
- }
- unless ($FullFormat) { $FullFormat = $OutputFormat }
-} # 'standard' to terminal
-
-sub MakeOptionFile {
- my ( $FinalRun, $FastDisabled, $JobName, $JobSuffix, $KindOfRun ) = @_;
- open( OPT, ">$JobName.top" );
- print OPT "\% $JobName.top\n";
- print OPT "\\unprotect\n";
- if ($EnterBatchMode) { print OPT "\\batchmode\n" }
- if ($EnterNonStopMode) { print OPT "\\nonstopmode\n" }
- if ($Paranoid) {
- print " paranoid file mode : very true\n";
- print OPT "\\def\\maxreadlevel{1}\n" ;
- }
- $ModeFile =~ s/\\/\//gio ; # do this at top of file
- $Result =~ s/\\/\//gio ; # do this at top of file
- if ( $ModeFile ne '' ) { print OPT "\\readlocfile{$ModeFile}{}{}" }
- if ( $Result ne '' ) { print OPT "\\setupsystem[file=$Result]\n" }
- elsif ($Suffix) { print OPT "\\setupsystem[file=$JobName$Suffix]\n" }
- if ( $InpPath ne "" ) {
- $InpPath =~ s/\\/\//go;
- $InpPath =~ s/\/$//go;
- print OPT "\\usepath[$InpPath]\n";
- }
- $MainLanguage = lc $MainLanguage;
- unless ( $MainLanguage eq "standard" ) {
- print OPT "\\setuplanguage[$MainLanguage]\n";
- }
- # can best become : \use...[mik] / [web]
- if ( $TeXShell =~ /MikTeX/io ) {
- print OPT "\\def\\MPOSTbatchswitch \{$MpBatchFlag\}";
- print OPT "\\def\\MPOSTnonstopswitch \{$MpNonStopFlag\}";
- print OPT "\\def\\MPOSTformatswitch \{$MpPassString $MpFormatFlag\}";
- }
- #
- if ( $FullFormat ne 'standard' ) {
- print OPT "\\setupoutput[$FullFormat]\n";
- }
- if ($UseColor) { print OPT "\\setupcolors[\\c!state=\\v!start]\n" }
- if ( $NoMPMode || $NoMPRun || $AutoMPRun ) {
- print OPT "\\runMPgraphicsfalse\n";
- }
- if ( ($FastMode) && ( !$FastDisabled ) ) { print OPT "\\fastmode\n" }
- if ($SilentMode) { print OPT "\\silentmode\n" }
- if ( $Separation ne "" ) {
- print OPT "\\setupcolors[\\c!split=$Separation]\n";
- }
- if ($SetupPath) { print OPT "\\setupsystem[\\c!directory=\{$SetupPath\}]\n" }
- if ($dosish) {
- print OPT "\\setupsystem[\\c!type=mswin]\n"
- } else { # no darwin handling in old texexec
- print OPT "\\setupsystem[\\c!type=unix]\n"
- }
- print OPT "\\setupsystem[\\c!n=$KindOfRun]\n";
- $_ = $PaperFormat;
- #unless (($PdfArrange)||($PdfSelect)||($PdfCombine)||($PdfCopy))
- unless ( ($PdfSelect) || ($PdfCombine) || ($PdfCopy) || ($PdfTrim) ) {
- if (/.4.3/goi) {
- print OPT "\\setuppapersize[A4][A3]\n" ;
- } elsif (/.5.4/goi) {
- print OPT "\\setuppapersize[A5][A4]\n" ;
- } elsif ( !/standard/ ) {
- s/x/\*/io;
- if (/\w+\d+/) { $_ = uc $_ }
- my ( $from, $to ) = split(/\*/);
- if ( $to eq "" ) { $to = $from }
- print OPT "\\setuppapersize[$from][$to]\n";
- }
- }
- if ( ( $PdfSelect || $PdfCombine || $PdfCopy || $PdfTrim || $PdfArrange )
- && ( $Background ne '' ) )
- {
- print " background graphic : $Background\n";
- print OPT "\\defineoverlay[whatever][{\\externalfigure[$Background][\\c!factor=\\v!max]}]\n";
- print OPT "\\setupbackgrounds[\\v!page][\\c!background=whatever]\n";
- }
- if ($CenterPage) {
- print OPT
- "\\setuplayout[\\c!location=\\v!middle,\\c!marking=\\v!on]\n";
- }
- if ($NoMapFiles) {
- print OPT "\\disablemapfiles\n";
- }
- if ($NoArrange) { print OPT "\\setuparranging[\\v!disable]\n" }
- elsif ( $Arrange || $PdfArrange ) {
- $FinalRunNeeded = 1;
- if ($FinalRun) {
- my $DupStr;
- if ($NoDuplex) { $DupStr = "" }
- else { $DupStr = ",\\v!doublesided" }
- if ( $PrintFormat eq '' ) {
- print OPT "\\setuparranging[\\v!normal]\n";
- } elsif ( $PrintFormat =~ /.*up/goi ) {
- print OPT "\\setuparranging[2UP,\\v!rotated$DupStr]\n";
- } elsif ( $PrintFormat =~ /.*down/goi ) {
- print OPT "\\setuparranging[2DOWN,\\v!rotated$DupStr]\n";
- } elsif ( $PrintFormat =~ /.*side/goi ) {
- print OPT "\\setuparranging[2SIDE,\\v!rotated$DupStr]\n";
- } else {
- print OPT "\\setuparranging[$PrintFormat]\n";
- }
- } else {
- print OPT "\\setuparranging[\\v!disable]\n";
- }
- }
- if ($Arguments) { print OPT "\\setupenv[$Arguments]\n" }
- if ($Input) { print OPT "\\setupsystem[inputfile=$Input]\n" }
- else { print OPT "\\setupsystem[inputfile=$JobName.$JobSuffix]\n" }
- if ($Random) { print OPT "\\setupsystem[\\c!random=$RandomSeed]\n" }
- if ($Mode) { print OPT "\\enablemode[$Mode]\n" }
- if ($Pages) {
- if ( lc $Pages eq "odd" ) {
- print OPT "\\chardef\\whichpagetoshipout=1\n";
- } elsif ( lc $Pages eq "even" ) {
- print OPT "\\chardef\\whichpagetoshipout=2\n";
- } else {
- my @Pages = split( /\,/, $Pages );
- $Pages = '';
- foreach my $page (@Pages) {
- if ( $page =~ /\:/ ) {
- my ( $from, $to ) = split( /\:/, $page );
- foreach ( my $i = $from ; $i <= $to ; $i++ ) {
- $Pages .= $i . ',';
- }
- } else {
- $Pages .= $page . ',';
- }
- }
- chop $Pages;
- print OPT "\\def\\pagestoshipout\{$Pages\}\n";
- }
- }
- print OPT "\\protect\n";
- if ( $Filters ne "" ) {
- foreach my $F ( split( /,/, $Filters ) ) {
- print OPT "\\useXMLfilter[$F]\n";
- }
- }
- if ( $Modules ne "" ) {
- foreach my $M ( split( /,/, $Modules ) ) {
- print OPT "\\usemodule[$M]\n";
- }
- }
- if ( $Environments ne "" ) {
- foreach my $E ( split( /,/, $Environments ) ) {
- print OPT "\\environment $E\n";
- }
- }
- close(OPT);
-}
-
-my $UserFileOk = 0;
-my @MainLanguages;
-my $AllLanguages = '';
-
-sub MakeUserFile {
- $UserFileOk = 0;
- if ($AllPatterns) {
- open( USR, ">cont-fmt.tex" );
- print USR "\\preloadallpatterns\n";
- } else {
- return
- if ( ( $MainLanguage eq 'standard' )
- && ( $MainBodyFont eq 'standard' ) );
- print " preparing user file : cont-fmt.tex\n";
- open( USR, ">cont-fmt.tex" );
- print USR "\\unprotect\n";
- $AllLanguages = $MainLanguage;
- if ( $MainLanguage ne 'standard' ) {
- @MainLanguages = split( /\,/, $MainLanguage );
- foreach (@MainLanguages) {
- print USR "\\installlanguage[\\s!$_][\\c!state=\\v!start]\n";
- }
- $MainLanguage = $MainLanguages[0];
- print USR "\\setupcurrentlanguage[\\s!$MainLanguage]\n";
- }
- if ( $MainBodyFont ne 'standard' ) {
- print USR "\\definetypescriptsynonym[cmr][$MainBodyFont]";
- print USR "\\definefilesynonym[font-cmr][font-$MainBodyFont]\n";
- }
- print USR "\\protect\n";
- }
- print USR "\\endinput\n";
- close(USR);
- ReportUserFile();
- print "\n";
- $UserFileOk = 1;
-}
-
-sub RemoveResponseFile { unlink "mult-def.tex" }
-
-sub MakeResponseFile {
- if ( $MainResponse eq 'standard' ) { RemoveResponseFile() }
- elsif ( !defined( $ResponseInterface{$MainResponse} ) ) {
- RemoveResponseFile();
- } else {
- my $MR = $ResponseInterface{$MainResponse};
- print " preparing interface file : mult-def.tex\n";
- print " response language : $MR\n";
- open( DEF, ">mult-def.tex" );
- print DEF "\\def\\currentresponses\{$MR\}\n\\endinput\n";
- close(DEF);
- }
-}
-
-sub RestoreUserFile {
- unlink "cont-fmt.log";
- rename "cont-fmt.tex", "cont-fmt.log";
- ReportUserFile();
-}
-
-sub ReportUserFile {
- return unless ($UserFileOk);
- print "\n";
- if ( $MainLanguage ne 'standard' ) {
- print " additional patterns : $AllLanguages\n";
- print " default language : $MainLanguage\n";
- }
- if ( $MainBodyFont ne 'standard' ) {
- print " default bodyfont : $MainBodyFont\n";
- }
-}
-
-sub CheckPositions { }
-
-my $ConTeXtVersion = "unknown";
-my $ConTeXtModes = '';
-
-sub ScanTeXPreamble {
- my ($FileName) = @_;
- open( TEX, $FileName );
- while (<TEX>) {
- chomp;
- if (/^\%.*/) {
- if (/tex=([a-z]*)/goi) { $TeXExecutable = $1 }
- if (/translat.*?=([\:\/0-9\-a-z]*)/goi) { $TeXTranslation = $1 }
- if (/program=([a-z]*)/goi) { $TeXExecutable = $1 }
- if (/output=([a-z\,\-]*)/goi) { $OutputFormat = $1 }
- if (/modes=([a-z\,\-]*)/goi) { $ConTeXtModes = $1 }
- if (/textree=([a-z\-]*)/goi) { $TeXTree = $1 }
- if (/texroot=([a-z\-]*)/goi) { $TeXRoot = $1 }
- if ( $ConTeXtInterface eq "unknown" ) {
-
- if (/format=([a-z]*)/goi) {
- $ConTeXtInterface = $ConTeXtInterfaces{$1};
- }
- if (/interface=([a-z]*)/goi) {
- $ConTeXtInterface = $ConTeXtInterfaces{"$1"};
- }
- }
- if (/version=([a-z]*)/goi) { $ConTeXtVersion = $1 }
- } else {
- last;
- }
- }
- close(TEX);
-
- # handy later on
-
- $ProducePdfT = ($OutputFormat eq "pdftex") ;
- $ProducePdfM = ($OutputFormat eq "dvipdfm") ;
- $ProducePdfX = ($OutputFormat eq "dvipdfmx") ;
- $ProducePdfXTX = ($OutputFormat eq "xetex") ;
- $ProducePs = ($OutputFormat eq "dvips") ;
-}
-
-sub ScanContent {
- my ($ConTeXtInput) = @_;
- open( TEX, $ConTeXtInput );
- while (<TEX>) {
- next if (/^\%/) ;
- if (
-/\\(starttekst|stoptekst|startonderdeel|startdocument|startoverzicht)/
- )
- {
- $ConTeXtInterface = "nl";
- last;
- } elsif (/\\(stelle|verwende|umgebung|benutze)/) {
- $ConTeXtInterface = "de";
- last;
- } elsif (/\\(stel|gebruik|omgeving)/) {
- $ConTeXtInterface = "nl";
- last;
- } elsif (/\\(use|setup|environment)/) {
- $ConTeXtInterface = "en";
- last;
- } elsif (/\\(usa|imposta|ambiente)/) {
- $ConTeXtInterface = "it";
- last;
- } elsif (/(height|width|style)=/) {
- $ConTeXtInterface = "en";
- last;
- } elsif (/(hoehe|breite|schrift)=/) {
- $ConTeXtInterface = "de";
- last;
- }
- # brr, can be \c!
- elsif (/(hoogte|breedte|letter)=/) { $ConTeXtInterface = "nl"; last }
- elsif (/(altezza|ampiezza|stile)=/) { $ConTeXtInterface = "it"; last }
- elsif (/externfiguur/) { $ConTeXtInterface = "nl"; last }
- elsif (/externalfigure/) { $ConTeXtInterface = "en"; last }
- elsif (/externeabbildung/) { $ConTeXtInterface = "de"; last }
- elsif (/figuraesterna/) { $ConTeXtInterface = "it"; last }
- }
- close(TEX);
-}
-
-if ( $ConTeXtInterfaces{$ConTeXtInterface} ) {
- $ConTeXtInterface = $ConTeXtInterfaces{$ConTeXtInterface};
-}
-
-my $Problems = my $Ok = 0;
-
-sub PrepRunTeX {
- my ( $JobName, $JobSuffix, $PipeString ) = @_;
- my $cmd;
- my $TeXProgNameFlag = '';
- if ( !$dosish ) # we assume tetex on linux
- {
- $TeXProgramPath = '';
- $TeXFormatPath = '';
- if ( !$TeXProgNameFlag
- && ( $Format =~ /^cont/ )
- && ( $TeXPassString !~ /progname/io ) )
- {
- $TeXProgNameFlag = "-progname=context";
- }
- }
- $own_quote = ($TeXProgramPath =~ m/^[^\"].* / ? "\"" : "") ;
- $cmd = join( ' ',
- "$own_quote$TeXProgramPath$TeXExecutable$own_quote",
- $TeXProgNameFlag, $TeXPassString, $PassOn, "" );
- if ($EnterBatchMode) { $cmd .= "$TeXBatchFlag " }
- if ($EnterNonStopMode) { $cmd .= "$TeXNonStopFlag " }
- if ( $TeXTranslation ne '' ) { $cmd .= "-translate-file=$TeXTranslation " }
- $cmd .= "$TeXFormatFlag$TeXFormatPath$Format $JobName.$JobSuffix $PipeString";
- return $cmd;
-}
-
-my $emergencyend = "" ;
-#~ my $emergencyend = "\\emergencyend" ;
-
-sub RunTeX {
- my ( $JobName, $JobSuffix ) = @_;
- my $StartTime = time;
- my $cmd = PrepRunTeX($JobName, $JobSuffix, '');
- if ($EnterBatchMode) {
- $Problems = System("$cmd $emergencyend");
- } else {
- $Problems = System("$cmd $emergencyend");
- }
- my $StopTime = time - $StartTime;
- print "\n return code : $Problems";
- print "\n run time : $StopTime seconds\n";
- return $Problems;
-}
-
-sub PushResult {
- my $File = shift;
- $File =~ s/\..*$//o;
- $Result =~ s/\..*$//o;
- if ( ( $Result ne '' ) && ( $Result ne $File ) ) {
- print " outputfile : $Result\n";
- unlink "texexec.tuo";
- rename "$File.tuo", "texexec.tuo";
- unlink "texexec.log";
- rename "$File.log", "texexec.log";
- unlink "texexec.dvi";
- rename "$File.dvi", "texexec.dvi";
- unlink "texexec.pdf";
- rename "$File.pdf", "texexec.pdf";
-
- if ( -e "$Result.tuo" ) {
- unlink "$File.tuo";
- rename "$Result.tuo", "$File.tuo";
- }
- }
- if ($Optimize) { unlink "$File.tuo" }
-}
-
-sub PopResult {
- my $File = shift;
- $File =~ s/\..*$//o;
- $Result =~ s/\..*$//o;
- if ( ( $Result ne '' ) && ( $Result ne $File ) ) {
- print " renaming : $File to $Result\n";
- unlink "$Result.tuo";
- rename "$File.tuo", "$Result.tuo";
- unlink "$Result.log";
- rename "$File.log", "$Result.log";
- unlink "$Result.dvi";
- rename "$File.dvi", "$Result.dvi";
- if ( -e "$File.dvi" ) { CopyFile( "$File.dvi", "$Result.dvi" ) }
- unlink "$Result.pdf";
- rename "$File.pdf", "$Result.pdf";
- if ( -e "$File.pdf" ) { CopyFile( "$File.pdf", "$Result.pdf" ) }
- return if ( $File ne "texexec" );
- rename "texexec.tuo", "$File.tuo";
- rename "texexec.log", "$File.log";
- rename "texexec.dvi", "$File.dvi";
- rename "texexec.pdf", "$File.pdf";
- }
-}
-
-sub RunTeXutil {
- my $StopRunning;
- my $JobName = shift;
- unlink "$JobName.tup";
- rename "$JobName.tuo", "$JobName.tup";
- print " sorting and checking : running texutil\n";
- my $TcXSwitch = '';
- if ( $TcXPath ne '' ) { $TcXSwitch = "--tcxpath=$TcXPath" }
- RunPerlScript( $TeXUtil, "--ref --ij --high $TcXPath $JobName" );
-
- if ( -e "$JobName.tuo" ) {
- CheckPositions($JobName);
- #~ print " utility file check : $JobName.tup <-> $JobName.tuo\n";
- $StopRunning = !compare( "$JobName.tup", "$JobName.tuo" );
- } else {
- $StopRunning = 1;
- } # otherwise potential loop
- if ( !$StopRunning ) {
- print "\n utility file analysis : another run needed\n";
- }
- return $StopRunning;
-}
-
-sub PurgeFiles {
- my $JobName = shift;
- print "\n purging files : $JobName\n";
- RunPerlScript( $TeXUtil, "--purge $JobName" );
- unlink( $Result . '.log' ) if ( -f $Result . '.log' );
-}
-
-sub RunTeXMP {
- my $JobName = shift;
- my $MPfile = shift;
- my $MPrundone = 0;
- my $MPJobName = MPJobName( $JobName, $MPfile );
- my $MPFoundJobName = "";
- if ( $MPJobName ne "" ) {
- if ( open( MP, "$MPJobName" ) ) {
- $_ = <MP>;
- chomp; # we should handle the prefix as well
- if (/^\%\s+translate.*?\=([\w\d\-]+)/io) { $TeXTranslation = $1 }
- if (/collected graphics of job \"(.+)\"/i) { $MPFoundJobName = $1 }
- close(MP);
- if ( $MPFoundJobName ne "" ) {
- if ( $JobName =~ /$MPFoundJobName$/i ) {
- if ( $MpExecutable ne '' ) {
- print
- " generating graphics : metaposting $MPJobName\n";
- my $ForceMpy = "";
- if ($MpyForce) { $ForceMpy = "--mpyforce" }
- my $ForceTCX = '';
- if ( $TeXTranslation ne '' ) {
- $ForceTCX = "--translate=$TeXTranslation ";
- }
- if ($EnterBatchMode) {
- RunPerlScript( $TeXExec,
-"$ForceTCX $ForceMpy --mptex --nomp --batch $MPJobName"
- );
- } elsif ($EnterNonStopMode) {
- RunPerlScript( $TeXExec,
-"$ForceTCX $ForceMpy --mptex --nomp --nonstop $MPJobName"
- );
- } else {
- RunPerlScript( $TeXExec,
- "$ForceTCX $ForceMpy --mptex --nomp $MPJobName"
- );
- }
- } else {
- print
- " generating graphics : metapost cannot be run\n";
- }
- $MPrundone = 1;
- }
- }
- }
- }
- return $MPrundone;
-}
-
-sub CopyFile { # agressive copy, works for open files like in gs
- my ( $From, $To ) = @_;
- return unless open( INP, "<$From" );
- binmode INP;
- return unless open( OUT, ">$To" );
- binmode OUT;
- while (<INP>) { print OUT $_ }
- close(INP);
- close(OUT);
-}
-
-#~ sub CheckMPChanges {
- #~ my $JobName = shift;
- #~ my $checksum = 0;
- #~ my $MPJobName = MPJobName( $JobName, "mpgraph" );
- #~ if ( open( MP, $MPJobName ) ) {
- #~ while (<MP>) {
- #~ unless (/random/oi) {
- #~ $checksum += do { unpack( "%32C*", <MP> ) % 65535 }
- #~ }
- #~ }
- #~ close(MP);
- #~ }
- #~ $MPJobName = MPJobName( $JobName, "mprun" );
- #~ if ( open( MP, $MPJobName ) ) {
- #~ while (<MP>) {
- #~ unless (/random/oi) {
- #~ $checksum += do { unpack( "%32C*", <MP> ) % 65535 }
- #~ }
- #~ }
- #~ close(MP);
- #~ }
- #~ print " mpgraph/mprun : $checksum\n";
- #~ return $checksum;
-#~ }
-
-sub CheckMPChanges {
- my $JobName = shift; my $str = '' ;
- my $MPJobName = MPJobName( $JobName, "mpgraph" );
- if ( open( MP, $MPJobName ) ) {
- $str .= do { local $/ ; <MP> ; } ;
- close(MP) ;
- }
- $MPJobName = MPJobName( $JobName, "mprun" );
- if ( open( MP, $MPJobName ) ) {
- $str .= do { local $/ ; <MP> ; } ;
- close(MP) ;
- }
- $str =~ s/^.*?random.*?$//oim ;
- return Digest::MD5::md5_hex($str) ;
-}
-
-#~ sub CheckTubChanges {
- #~ my $JobName = shift;
- #~ my $checksum = 0;
- #~ if ( open( TUB, "$JobName.tub" ) ) {
- #~ while (<TUB>) {
- #~ $checksum += do { unpack( "%32C*", <TUB> ) % 65535 }
- #~ }
- #~ close(TUB);
- #~ }
- #~ return $checksum;
-#~ }
-
-sub CheckTubChanges {
- my $JobName = shift; my $str = '' ;
- if ( open( TUB, "$JobName.tub" ) ) {
- $str = do { local $/ ; <TUB> ; } ;
- close(TUB);
- }
- return Digest::MD5::md5_hex($str);
-}
-
-
-my $DummyFile = 0;
-
-sub isXMLfile {
- my $Name = shift;
- if ( ($ForceXML) || ( $Name =~ /\.(xml|fo|fox)$/io ) ) { return 1 }
- else {
- open( XML, $Name );
- my $str = <XML>;
- close(XML);
- return ( $str =~ /\<\?xml /io );
- }
-}
-
-sub RunConTeXtFile {
- my ( $JobName, $JobSuffix ) = @_;
- if ($AutoPath) {
- if ($JobName =~ /^(.*)[\/\\](.*?)$/o) {
- $InpPath = $1 ;
- $JobName = $2 ;
- }
- }
- $JobName =~ s/\\/\//goi;
- $InpPath =~ s/\\/\//goi;
- my $OriSuffix = $JobSuffix;
- if ($JobSuffix =~ /\_fo$/i) {
- if (! -f $JobName) {
- print "stripping funny suffix : _fo\n";
- $JobName =~ s/\_fo$//io ;
- $JobSuffix =~ s/\_fo$//io ;
- $OriSuffix =~ s/\_fo$//io ;
- }
- }
- if (($dosish) && ($PdfClose)) {
- my $ok = System("pdfclose --file $JobName.pdf") if -e "$JobName.pdf" ;
- if (($Result ne '') && (-e "$Result.pdf")) {
- $ok = System("pdfclose --file $Result.pdf") ;
- }
- System("pdfclose --all") unless $ok ;
- }
- if ( -e "$JobName.$JobSuffix" ) {
- $DummyFile = ( ($ForceXML) || ( $JobSuffix =~ /(xml|fo|fox)/io ) );
- }
- # to be considered :
- # { $DummyFile = isXMLfile("$JobName.$JobSuffix") }
- elsif ( $InpPath ne "" ) {
- my @InpPaths = split( /,/, $InpPath );
- foreach my $rp (@InpPaths) {
- if ( -e "$rp/$JobName.$JobSuffix" ) { $DummyFile = 1; last }
- }
- }
- if ($DummyFile) {
- open( TMP, ">$JobName.run" );
- if ( ( $JobSuffix =~ /(xml|fo|fox)/io ) || $ForceXML ) {
- # scan xml preamble
- open(XML,"<$JobName.$JobSuffix") ;
- while (<XML>) {
- if (/\<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?\>/o) {
- my ($class, $key, $value, $rest) = ($1, $2, $3, $4) ;
- if ($class eq 'job') {
- if (($key eq 'mode') || ($key eq 'modes')) {
- print TMP "\\enablemode[$value]\n" ;
- } elsif (($key eq 'stylefile') || ($key eq 'environment')) {
- print TMP "\\environment $value\n" ;
- } elsif ($key eq 'module') {
- print TMP "\\usemodule[$value]\n" ;
- } elsif ($key eq 'interface') {
- $ConTeXtInterface = $value ;
- } elsif ($key eq 'control') {
- if ($rest == 'purge') { $Purge = 1 }
- }
- }
- } elsif (/\<[a-z]+/io) {
- last ;
- }
- }
- close(XML) ;
- if ( $Filters ne "" ) {
- print " using xml filters : $Filters\n";
- }
- print TMP "\\starttext\n";
- print TMP "\\processXMLfilegrouped{$JobName.$JobSuffix}\n";
- print TMP "\\stoptext\n";
- } else {
- print TMP "\\starttext\n";
- print TMP "\\processfile{$JobName.$JobSuffix}\n";
- print TMP "\\stoptext\n";
- }
- close(TMP);
- $JobSuffix = "run";
- }
- if ( ( -e "$JobName.$JobSuffix" ) || ($GlobalFile) ) {
- unless ($DummyFile) { # we don't need this for xml
- ScanTeXPreamble("$JobName.$JobSuffix");
- if ( $ConTeXtInterface eq "unknown" ) {
- ScanContent("$JobName.$JobSuffix");
- }
- }
- if ( $ConTeXtInterface eq "unknown" ) {
- $ConTeXtInterface = $UserInterface;
- }
- if ( $ConTeXtInterface eq "unknown" ) { $ConTeXtInterface = "en" }
- if ( $ConTeXtInterface eq "" ) { $ConTeXtInterface = "en" }
- CheckOutputFormat;
- my $StopRunning = 0;
- my $MPrundone = 0;
- if ( $Format eq '' ) { $Format = "cont-$ConTeXtInterface" }
- print " executable : $TeXProgramPath$TeXExecutable\n";
- print " format : $TeXFormatPath$Format\n";
- if ($InpPath) { print " source path : $InpPath\n" }
-
- if ($DummyFile) {
- print " dummy file : $JobName.$JobSuffix\n";
- }
- print " inputfile : $JobName\n";
- print " output : $FullFormat\n";
- print " interface : $ConTeXtInterface\n";
- if ( $TeXTranslation ne '' ) {
- print " translation : $TeXTranslation\n";
- }
- my $Options = '';
- if ($Random) { $Options .= " random" }
- if ($FastMode) { $Options .= " fast" }
- if ($FinalMode) { $Options .= " final" }
- if ($Verbose) { $Options .= " verbose" }
- if ($TypesetListing) { $Options .= " listing" }
- if ($TypesetModule) { $Options .= " module" }
- if ($TypesetFigures) { $Options .= " figures" }
- if ($MakeFormats) { $Options .= " make" }
- if ($RunOnce) { $Options .= " once" }
- if ($UseColor) { $Options .= " color" }
- if ($EnterBatchMode) { $Options .= " batch" }
- if ($EnterNonStopMode) { $Options .= " nonstop" }
- if ($NoMPMode) { $Options .= " nomp" }
- if ($CenterPage) { $Options .= " center" }
- if ($Arrange) { $Options .= " arrange" }
- if ($NoArrange) { $Options .= " no-arrange" }
- if ($Options) { print " options :$Options\n" }
- if ($ConTeXtModes) { print " possible modes : $ConTeXtModes\n" }
- if ($Mode) { print " current mode : $Mode\n" }
- else { print " current mode : none\n" }
- if ($Arguments) { print " arguments : $Arguments\n" }
- if ($Modules) { print " modules : $Modules\n" }
- if ($Environments) { print " environments : $Environments\n" }
- if ($Suffix) { $Result = "$JobName$Suffix" }
- PushResult($JobName);
- $Problems = 0;
- my $TeXRuns = 0;
-
- if ( ($PdfArrange) || ($PdfSelect) || ($RunOnce) ) {
- MakeOptionFile( 1, 1, $JobName, $OriSuffix, 3 );
- print "\n";
- $Problems = RunTeX( $JobName, $JobSuffix );
- if ($ForceTeXutil) { $Ok = RunTeXutil($JobName) }
- CopyFile( "$JobName.top", "$JobName.tmp" );
- unlink "$JobName.top"; # runtime option file
- PopResult($JobName);
- } else {
- while ( !$StopRunning && ( $TeXRuns < $NOfRuns ) && ( !$Problems ) )
- {
- ++$TeXRuns;
- if ( $TeXRuns == 1 ) {
- MakeOptionFile( 0, 0, $JobName, $OriSuffix, 1 );
- } else {
- MakeOptionFile( 0, 0, $JobName, $OriSuffix, 2 );
- }
- print " TeX run : $TeXRuns\n\n";
- my ( $mpchecksumbefore, $mpchecksumafter ) = ( '', '' );
- my ( $tubchecksumbefore, $tubchecksumafter ) = ( '', '' );
- if ($AutoMPRun) { $mpchecksumbefore = CheckMPChanges($JobName) }
- $tubchecksumbefore = CheckTubChanges($JobName) ;
- $Problems = RunTeX( $JobName, $JobSuffix );
- $tubchecksumafter = CheckTubChanges($JobName) ;
- if ($AutoMPRun) { $mpchecksumafter = CheckMPChanges($JobName) }
- if ( ( !$Problems ) && ( $NOfRuns > 1 ) ) {
- unless ( $NoMPMode ) {
- $MPrundone = RunTeXMP( $JobName, "mpgraph" );
- $MPrundone = RunTeXMP( $JobName, "mprun" );
- }
- $StopRunning = RunTeXutil($JobName);
- if ($AutoMPRun) {
- $StopRunning =
- ( $StopRunning
- && ( $mpchecksumafter eq $mpchecksumbefore ) );
- }
- $StopRunning =
- ( $StopRunning
- && ( $tubchecksumafter eq $tubchecksumbefore ) );
- }
- }
- if ( ( $NOfRuns == 1 ) && $ForceTeXutil ) {
- $Ok = RunTeXutil($JobName);
- }
- if ( ( !$Problems )
- && ( ( $FinalMode || $FinalRunNeeded ) )
- && ( $NOfRuns > 1 ) )
- {
- MakeOptionFile( 1, $FinalMode, $JobName, $OriSuffix, 4 );
- print " final TeX run : $TeXRuns\n\n";
- $Problems = RunTeX( $JobName, $JobSuffix );
- }
- CopyFile( "$JobName.top", "$JobName.tmp" );
- unlink "$JobName.tup"; # previous tuo file
- unlink "$JobName.top"; # runtime option file
- if ($ProducePdfX) {
- $ENV{'backend'} = $ENV{'progname'} = 'dvipdfm' ;
- $ENV{'TEXFONTMAPS'} = '.;$TEXMF/fonts/map/{dvipdfm,dvips,}//' ;
- System("dvipdfmx -d 4 $JobName") ;
- } elsif ($ProducePdfM) {
- $ENV{'backend'} = $ENV{'progname'} = 'dvipdfm' ;
- $ENV{'TEXFONTMAPS'} = '.;$TEXMF/fonts/map/{dvipdfm,dvips,}//' ;
- System("dvipdfm $JobName") ;
- } elsif ($ProducePdfXTX) {
- $ENV{'backend'} = $ENV{'progname'} = 'xetex' ;
- $ENV{'TEXFONTMAPS'} = '.;$TEXMF/fonts/map/{xetex,pdftex,dvips,}//' ;
- System("xdv2pdf $JobName.xdv") ;
- } elsif ($ProducePs) {
- $ENV{'backend'} = $ENV{'progname'} = 'dvips' ;
- $ENV{'TEXFONTMAPS'} = '.;$TEXMF/fonts/map/{dvips,pdftex,}//' ;
- # temp hack, some day there will be map file loading in specials
- my $mapfiles = '' ;
- if (-f "$JobName.tui") {
- open(TUI,"$JobName.tui") ;
- while (<TUI>) {
- if (/c \\usedmapfile\{.\}\{(.*?)\}/o) {
- $mapfiles .= "-u +$1 " ;
- }
- }
- close(TUI) ;
- }
- System("dvips $mapfiles $JobName.dvi") ;
- }
- PopResult($JobName);
- }
- if ($Purge) { PurgeFiles($JobName) }
- if ($DummyFile) # $JobSuffix == run
- {
- unlink "$JobName.$JobSuffix";
- }
- if ((!$Problems) && ($PdfOpen) && ($PdfOpenCall)) {
- if ($Result ne '') {
- System("$PdfOpenCall $Result.pdf") if -f "$Result.pdf"
- } else {
- System("$PdfOpenCall $JobName.pdf") if -f "$JobName.pdf"
- }
- }
- }
-}
-
-sub RunSomeTeXFile {
- my ( $JobName, $JobSuffix ) = @_;
- if ( -e "$JobName.$JobSuffix" ) {
- PushResult($JobName);
- print " executable : $TeXProgramPath$TeXExecutable\n";
- print " format : $TeXFormatPath$Format\n";
- print " inputfile : $JobName.$JobSuffix\n";
- $Problems = RunTeX( $JobName, $JobSuffix );
- PopResult($JobName);
- }
-}
-
-my $ModuleFile = "texexec";
-my $ListingFile = "texexec";
-my $FiguresFile = "texexec";
-my $ArrangeFile = "texexec";
-my $SelectFile = "texexec";
-my $CopyFile = "texexec";
-my $CombineFile = "texexec";
-
-sub RunModule {
- my @FileNames = sort @_;
- if ($FileNames[0]) {
- unless ( -e $FileNames[0] ) {
- my $Name = $FileNames[0];
- @FileNames = ( "$Name.tex", "$Name.mp", "$Name.pl", "$Name.pm" );
- }
- foreach my $FileName (@FileNames) {
- next unless -e $FileName;
- my ( $Name, $Suffix ) = split( /\./, $FileName );
- next unless $Suffix =~ /(tex|mp|pl|pm)/io;
- DoRunModule( $Name, $Suffix );
- }
- } else {
- print " module : no modules found\n\n";
- }
-}
-
-# the next one can be more efficient: directly process ted
-# file a la --use=abr-01,mod-01
-
-sub checktexformatpath {
- # engine support is either broken of not implemented in some
- # distributions, so we need to take care of it ourselves
- my $texformats ;
- if (defined($ENV{'TEXFORMATS'})) {
- $texformats = $ENV{'TEXFORMATS'} ;
- } else {
- $texformats = '' ;
- }
- if ($texformats eq '') {
- if ($UseEnginePath) {
- if ($dosish) {
- if ( $TeXShell =~ /MikTeX/io ) {
- $texformats = `kpsewhich --alias=$TeXExecutable --expand-var=\$TEXFORMATS` ;
- } else {
- $texformats = `kpsewhich --engine=$TeXExecutable --expand-var=\$TEXFORMATS` ;
- }
- } else {
- $texformats = `kpsewhich --engine=$TeXExecutable --expand-var=\\\$TEXFORMATS` ;
- }
- } else {
- if ($dosish) {
- $texformats = `kpsewhich --expand-var=\$TEXFORMATS` ;
- } else {
- $texformats = `kpsewhich --expand-var=\\\$TEXFORMATS` ;
- }
- }
- chomp($texformats) ;
- }
- if (($texformats !~ /web2c\/.*$TeXExecutable/) && ($texformats !~ /web2c[\/\\].*\$engine/i)) {
- $texformats =~ s/(web2c\/\{)(\,\})/$1\$engine$2/ ; # needed for empty engine flags
- if ($texformats !~ /web2c[\/\\].*\$ENGINE/) {
- $texformats =~ s/web2c/web2c\/{\$engine,}/ ; # needed for me
- }
- $ENV{'TEXFORMATS'} = $texformats ;
- print " fixing texformat path : $ENV{'TEXFORMATS'}\n";
- } else {
- print " using texformat path : $ENV{'TEXFORMATS'}\n" if ($Verbose) ;
- }
- if (! defined($ENV{'ENGINE'})) {
- if ($MpEngineSupport) {
- $ENV{'ENGINE'} .= $MpExecutable ;
- } ;
- $ENV{'ENGINE'} = $TeXExecutable ;
- print "fixing engine variable : $ENV{'ENGINE'}\n";
- }
-}
-
-sub DoRunModule {
- my ( $FileName, $FileSuffix ) = @_;
- RunPerlScript( $TeXUtil, "--documents $FileName.$FileSuffix" );
- print " module : $FileName\n\n";
- open( MOD, ">$ModuleFile.tex" );
- # we need to signal to texexec what interface to use
- open( TED, "$FileName.ted" );
- my $firstline = <TED>;
- close(TED);
- if ( $firstline =~ /interface=/ ) {
- print MOD $firstline ;
- } else {
- print MOD "% interface=en\n" ;
- }
- # so far
- print MOD "\\usemodule[abr-01,mod-01]\n";
- print MOD "\\def\\ModuleNumber{1}\n";
- print MOD "\\starttext\n";
- print MOD "\\readlocfile{$FileName.ted}{}{}\n";
- print MOD "\\stoptext\n";
- close(MOD);
- checktexformatpath ;
- RunConTeXtFile( $ModuleFile, "tex" );
- if ( $FileName ne $ModuleFile ) {
- foreach my $FileSuffix ( "dvi", "pdf", "tui", "tuo", "log" ) {
- unlink("$FileName.$FileSuffix");
- rename( "$ModuleFile.$FileSuffix", "$FileName.$FileSuffix" );
- }
- }
- unlink("$ModuleFile.tex");
-}
-
-sub RunFigures {
- my @Files = @_ ;
- $TypesetFigures = lc $TypesetFigures;
- return unless ( $TypesetFigures =~ /[abcd]/o );
- unlink "$FiguresFile.pdf";
- if (@Files) { RunPerlScript( $TeXUtil, "--figures @Files" ) }
- open( FIG, ">$FiguresFile.tex" );
- print FIG "% format=english\n";
- print FIG "\\setuplayout\n";
- print FIG " [topspace=1.5cm,backspace=1.5cm,\n";
- print FIG " header=1.5cm,footer=0pt,\n";
- print FIG " width=middle,height=middle]\n";
- if ($ForceFullScreen) {
- print FIG "\\setupinteraction\n";
- print FIG " [state=start]\n";
- print FIG "\\setupinteractionscreen\n";
- print FIG " [option=max]\n";
- }
- if ($BoxType ne '') {
- if ($BoxType !~ /box$/io) {
- $BoxType .= "box" ;
- }
- }
- print FIG "\\starttext\n";
- print FIG "\\showexternalfigures[alternative=$TypesetFigures,offset=$PaperOffset,size=$BoxType]\n";
- print FIG "\\stoptext\n";
- close(FIG);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $FiguresFile, "tex" );
- unlink('texutil.tuf') if ( -f 'texutil.tuf' );
-}
-
-sub CleanTeXFileName {
- my $str = shift;
- $str =~ s/([\$\_\#])/\\$1/go;
- $str =~ s/([\~])/\\string$1/go;
- return $str;
-}
-
-sub RunListing {
- my $FileName = my $CleanFileName = shift;
- my @FileNames = glob $FileName;
- return unless -f $FileNames[0];
- print " input file : $FileName\n";
- if ( $BackSpace eq "0pt" ) { $BackSpace = "1.5cm" }
- else { print " backspace : $BackSpace\n" }
- if ( $TopSpace eq "0pt" ) { $TopSpace = "1.5cm" }
- else { print " topspace : $TopSpace\n" }
- open( LIS, ">$ListingFile.tex" );
- print LIS "% format=english\n";
- print LIS "\\setupbodyfont[11pt,tt]\n";
- print LIS "\\setuplayout\n";
- print LIS " [topspace=$TopSpace,backspace=$BackSpace,\n";
- print LIS " header=0cm,footer=1.5cm,\n";
- print LIS " width=middle,height=middle]\n";
- print LIS "\\setuptyping[lines=yes]\n";
- if ($Pretty) { print LIS "\\setuptyping[option=color]\n" }
- print LIS "\\starttext\n";
-
- foreach $FileName (@FileNames) {
- $CleanFileName = lc CleanTeXFileName($FileName);
- print LIS "\\page\n";
- print LIS "\\setupfootertexts[\\tttf $CleanFileName][\\tttf \\pagenumber]\n";
- print LIS "\\typefile\{$FileName\}\n";
- }
- print LIS "\\stoptext\n";
- close(LIS);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $ListingFile, "tex" );
-}
-
-sub RunArrange {
- my @files = @_;
- print " backspace : $BackSpace\n";
- print " topspace : $TopSpace\n";
- print " paperoffset : $PaperOffset\n";
- if ( $AddEmpty eq '' ) { print " empty pages added : none\n" }
- else { print " empty pages added : $AddEmpty\n" }
- if ( $TextWidth eq '0pt' ) { print " textwidth : unknown\n" }
- else { print " textwidth : $TextWidth\n" }
- open( ARR, ">$ArrangeFile.tex" );
- print ARR "% format=english\n";
- print ARR "\\definepapersize\n";
- print ARR " [offset=$PaperOffset]\n";
- print ARR "\\setuplayout\n";
- print ARR " [backspace=$BackSpace,\n";
- print ARR " topspace=$TopSpace,\n";
-
- if ($Markings) {
- print ARR " marking=on,\n";
- print " cutmarkings : on\n";
- }
- print ARR " width=middle,\n";
- print ARR " height=middle,\n";
- print ARR " location=middle,\n";
- print ARR " header=0pt,\n";
- print ARR " footer=0pt]\n";
- if ($NoDuplex) { print " duplex : off\n" }
- else {
- print " duplex : on\n";
- print ARR "\\setuppagenumbering\n";
- print ARR " [alternative=doublesided]\n";
- }
- print ARR "\\starttext\n";
- foreach my $FileName (@files) {
- print " pdffile : $FileName\n";
- print ARR "\\insertpages\n [$FileName]";
- if ( $AddEmpty ne '' ) { print ARR "[$AddEmpty]" }
- print ARR "[width=$TextWidth]\n";
- }
- print ARR "\\stoptext\n";
- close(ARR);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $ModuleFile, "tex" );
-}
-
-sub RunSelect {
- my $FileName = shift;
- print " pdffile : $FileName\n";
- print " backspace : $BackSpace\n";
- print " topspace : $TopSpace\n";
- print " paperoffset : $PaperOffset\n";
- if ( $TextWidth eq '0pt' ) { print " textwidth : unknown\n" }
- else { print " textwidth : $TextWidth\n" }
- open( SEL, ">$SelectFile.tex" );
- print SEL "% format=english\n";
- print SEL "\\definepapersize\n";
- print SEL " [offset=$PaperOffset]\n";
- if ($PaperFormat =~ /fit/) {
- print SEL "\\getfiguredimensions[$FileName]\n" ;
- print SEL "\\expanded{\\definepapersize[fit][width=\\figurewidth,height=\\figureheight]}\n" ;
- print SEL "\\setuppapersize[fit][fit]\n";
- $PaperFormat = '' ; # avoid overloading in option file
- } elsif ( $PaperFormat ne 'standard' ) {
- $_ = $PaperFormat; # NO UPPERCASE !
- s/x/\*/io;
- my ( $from, $to ) = split(/\*/);
- if ( $to eq "" ) { $to = $from }
- print " papersize : $PaperFormat\n";
- print SEL "\\setuppapersize[$from][$to]\n";
- $PaperFormat = '' ; # avoid overloading in option file
- }
- #
- print SEL "\\setuplayout\n";
- print SEL " [backspace=$BackSpace,\n";
- print SEL " topspace=$TopSpace,\n";
- if ($Markings) {
- print SEL " marking=on,\n";
- print " cutmarkings : on\n";
- }
- print SEL " width=middle,\n";
- print SEL " height=middle,\n";
- print SEL " location=middle,\n";
- print SEL " header=0pt,\n";
- print SEL " footer=0pt]\n";
- print SEL "\\setupexternalfigures\n";
- print SEL " [directory=]\n";
- print SEL "\\starttext\n";
-
- if ( $Selection ne '' ) {
- print SEL "\\filterpages\n";
- print SEL " [$FileName][$Selection][width=$TextWidth]\n";
- }
- print SEL "\\stoptext\n";
- close(SEL);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $SelectFile, "tex" );
-}
-
-sub RunCopy {
- my $DoTrim = shift ;
- my @Files = @_ ;
- if ( $PageScale == 1000 ) {
- print " offset : $PaperOffset\n";
- } else {
- print " scale : $PageScale\n";
- if ( $PageScale < 10 ) { $PageScale = int( $PageScale * 1000 ) }
- }
- open( COP, ">$CopyFile.tex" );
- print COP "% format=english\n";
- print COP "\\starttext\n";
- for my $FileName (@Files) {
- print " pdffile : $FileName\n";
- print COP "\\getfiguredimensions\n";
- print COP " [$FileName]\n";
- print COP " [page=1";
- if ($DoTrim) {
- print COP ",\n size=trimbox";
- }
- print COP "]\n";
- print COP "\\definepapersize\n";
- print COP " [copy]\n";
- print COP " [width=\\naturalfigurewidth,\n";
- print COP " height=\\naturalfigureheight]\n";
- print COP "\\setuppapersize\n";
- print COP " [copy][copy]\n";
- print COP "\\setuplayout\n";
- print COP " [page]\n";
- print COP "\\setupexternalfigures\n";
- print COP " [directory=]\n";
- print COP "\\copypages\n";
- print COP " [$FileName]\n";
- print COP " [scale=$PageScale,\n";
- if ($Markings) {
- print COP " marking=on,\n";
- print " cutmarkings : on\n";
- }
- if ($DoTrim) {
- print COP " size=trimbox,\n";
- print " cropping to : trimbox\n";
- }
- print COP " offset=$PaperOffset]\n";
- }
- print COP "\\stoptext\n";
- close(COP);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $CopyFile, "tex" );
-}
-
-sub RunCombine {
- my @Files = @_;
- $Combination =~ s/x/\*/io;
- my ( $nx, $ny ) = split( /\*/, $Combination, 2 );
- return unless ( $nx && $ny );
- print " combination : $Combination\n";
- open( COM, ">$CombineFile.tex" );
- print COM "% format=english\n";
- if ( $PaperFormat ne 'standard' ) {
- $_ = $PaperFormat; # NO UPPERCASE !
- s/x/\*/io;
- my ( $from, $to ) = split(/\*/);
- if ( $to eq "" ) { $to = $from }
- print " papersize : $PaperFormat\n";
- print COM "\\setuppapersize[$from][$to]\n";
- }
- #
- if ( $PaperOffset eq '0pt' ) { $PaperOffset = '1cm' }
- print " paper offset : $PaperOffset\n";
- print COM "\\setuplayout\n";
- print COM " [topspace=$PaperOffset,\n";
- print COM " backspace=$PaperOffset,\n";
- print COM " header=0pt,\n";
- print COM " footer=1cm,\n";
- print COM " width=middle,\n";
- print COM " height=middle]\n";
-
- if ($NoBanner) {
- print COM "\\setuplayout\n";
- print COM " [footer=0cm]\n";
- }
- print COM "\\setupexternalfigures\n";
- print COM " [directory=]\n";
- print COM "\\starttext\n";
- for my $FileName (@Files) {
- next if ( $FileName =~ /^texexec/io );
- next if (($Result ne '') && ( $FileName =~ /^$Result/i ));
- print " pdffile : $FileName\n";
- my $CleanFileName = CleanTeXFileName($FileName);
- print COM "\\setupfootertexts\n";
- print COM " [\\tttf $CleanFileName\\quad\\quad\\currentdate\\quad\\quad\\pagenumber]\n";
- print COM "\\combinepages[$FileName][nx=$nx,ny=$ny]\n";
- print COM "\\page\n";
- }
- print COM "\\stoptext\n";
- close(COM);
- $ConTeXtInterface = "en";
- checktexformatpath ;
- RunConTeXtFile( $CombineFile, "tex" );
-}
-
-sub LocatedFormatPath { # watch out $engine is lowercase in kpse
- my $FormatPath = shift;
- my $EnginePath = shift;
- my $EngineDone = shift;
- if ($Local) {
- $FormatPath = '.' ; # for patrick
- } else {
- if ( ( $FormatPath eq '' ) && ( $kpsewhich ne '' ) ) {
- unless ($EngineDone) {
- my $str = $ENV{"TEXFORMATS"} ;
- $str =~ s/\$engine//io ;
- $ENV{"TEXFORMATS"} = $str ;
- }
- # expanded paths
- print " assuming engine : $EnginePath\n";
- if (($UseEnginePath)&&($EngineDone)) {
- if ( $TeXShell =~ /MikTeX/io ) {
- $FormatPath = `$kpsewhich --alias=$EnginePath --show-path=fmt` ;
- } else {
- $FormatPath = `$kpsewhich --engine=$EnginePath --show-path=fmt` ;
- }
- } else {
- $FormatPath = `$kpsewhich --show-path=fmt` ;
- }
- chomp($FormatPath) ;
- if ( ( $FormatPath ne '' ) && $Verbose ) {
- print "located formatpath (1) : $FormatPath\n";
- }
- # fall back
- if ($FormatPath eq '') {
- if (($UseEnginePath)&&($EngineDone)) {
- if ($dosish) {
- if ( $TeXShell =~ /MikTeX/io ) {
- $FormatPath = `$kpsewhich --alias=$EnginePath --expand-path=\$TEXFORMATS` ;
- } else {
- $FormatPath = `$kpsewhich --engine=$EnginePath --expand-path=\$TEXFORMATS` ;
- }
- } else {
- $FormatPath = `$kpsewhich --engine=$EnginePath --expand-path=\\\$TEXFORMATS` ;
- }
- }
- chomp($FormatPath) ;
- # either no enginepath or failed run
- if ($FormatPath eq '') {
- if ($dosish) {
- $FormatPath = `$kpsewhich --expand-path=\$TEXFORMATS` ;
- } else {
- $FormatPath = `$kpsewhich --expand-path=\\\$TEXFORMATS` ;
- }
- }
- chomp $FormatPath ;
- }
- chomp($FormatPath) ;
- if ( ( $FormatPath ne '' ) && $Verbose ) {
- print "located formatpath (2) : $FormatPath\n";
- }
- $FormatPath =~ s/\\/\//g ;
- if ($FormatPath ne '') {
- my @fpaths ;
- if ($dosish) {
- @fpaths = split(';', $FormatPath) ;
- } else {
- @fpaths = split(':', $FormatPath) ;
- }
- # take first writable unless current
- foreach my $fp (@fpaths) {
- # remove funny patterns
- $fp =~ s/\/+$// ;
- $fp =~ s/^!!// ;
- $fp =~ s/unsetengine/$EnginePath/ ;
- if (($fp ne '') && ($fp ne '.')) {
- # correct if needed
- # append engine unless engine is already there
- $fp =~ "$fp/$EnginePath" if ($fp =~ /[\\\/]$EnginePath[\\\/]*$/) ;
- # path may not yet be present
- # check if usable format path
- my $fpp = $fp ;
- $fpp =~ s/\/*$EnginePath\/*// ;
- if ((-d $fpp) && (-w $fpp)) {
- $FormatPath = $fpp ;
- last ;
- }
- }
- }
- }
- $FormatPath = '.' if (($FormatPath eq '') || (! -w $FormatPath)) ;
- if ( ( $FormatPath ne '' ) && $Verbose ) {
- print "located formatpath (3) : $FormatPath\n";
- }
- $FormatPath .= '/';
- }
- if ($UseEnginePath && $EngineDone && ($FormatPath ne '') && ($FormatPath !~ /$EnginePath\/$/)) {
- $FormatPath .= $EnginePath ;
- unless (-d $FormatPath) {
- mkdir $FormatPath ;
- }
- $FormatPath .= '/' ;
- }
- }
- print " using formatpath : $FormatPath\n" if $Verbose ;
- return $FormatPath;
-}
-
-sub RunOneFormat {
- my ($FormatName) = @_;
- my @TeXFormatPath;
- my $TeXPrefix = "";
- if ( ( $fmtutil ne "" ) && ( $FormatName !~ /metafun|mptopdf/io ) ) {
- # could not happen, not supported any more
- my $cmd = "$fmtutil --byfmt $FormatName";
- MakeUserFile; # this works only when the path is kept
- MakeResponseFile;
- $Problems = System("$cmd");
- RemoveResponseFile;
- RestoreUserFile;
- } else {
- $Problems = 1;
- }
- if ($Problems) {
- $Problems = 0;
- if ( $TeXExecutable =~ /etex|eetex|pdfetex|pdfeetex|pdfxtex|xpdfetex|eomega|aleph|xetex/io ) {
- $TeXPrefix = "*";
- }
- my $CurrentPath = cwd();
- my $TheTeXFormatPath = LocatedFormatPath($TeXFormatPath, $TeXExecutable,1);
- if ( $TheTeXFormatPath ne '' ) { chdir $TheTeXFormatPath }
- MakeUserFile;
- MakeResponseFile;
- $own_quote = ($TeXProgramPath =~ m/^[^\"].* / ? "\"" : "") ;
- my $cmd =
- "$own_quote$TeXProgramPath$TeXExecutable$own_quote $TeXVirginFlag "
- . "$TeXPassString $PassOn ${TeXPrefix}$FormatName";
- $Problems = System($cmd) ;
- RemoveResponseFile;
- RestoreUserFile;
-
- if ( ( $TheTeXFormatPath ne '' ) && ( $CurrentPath ne '' ) ) {
- print "\n";
- if ($UseEnginePath) {
- print " used engineformatpath : $TheTeXFormatPath\n";
- } else {
- print " used formatpath : $TheTeXFormatPath\n";
- }
- print "\n";
- chdir $CurrentPath;
- }
- }
-}
-
-sub RunFormats {
- my $ConTeXtFormatsPrefix;
- my $MetaFunDone = 0;
- if (@ARGV) { @ConTeXtFormats = @ARGV }
- elsif ( $UsedInterfaces ne '' ) {
- @ConTeXtFormats = split /[\,\s]/, $UsedInterfaces;
- }
- if ($Format) { @ConTeXtFormats = $Format; $ConTeXtFormatsPrefix = ''; }
- else { $ConTeXtFormatsPrefix = "cont-"; }
- if ( $TeXHashExecutable ne '' ) {
- unless ($FastMode) {
- $own_quote = ($TeXProgramPath =~ m/^[^\"].* / ? "\"" : "") ;
- my $cmd = "$own_quote$TeXProgramPath$TeXHashExecutable$own_quote";
- print "\n";
- print " TeX hash binary : $TeXProgramPath$TeXHashExecutable\n";
- print " comment : hashing may take a while ...\n";
- System($cmd);
- }
- }
- foreach my $Interface (@ConTeXtFormats) {
- if ( $Interface eq $MetaFun ) {
- RunMpFormat($MetaFun);
- $MetaFunDone = 1;
- } elsif ( $Interface eq $MpToPdf ) {
- if ( $TeXExecutable =~ /pdf/io ) { RunOneFormat("$MpToPdf") }
- } else {
- RunOneFormat("$ConTeXtFormatsPrefix$Interface");
- }
- }
- print "\n";
- print " TeX binary : $TeXProgramPath$TeXExecutable\n";
- print " format(s) : @ConTeXtFormats\n\n";
-}
-
-sub RunMpFormat {
- # engine is not supported by MP
- my $MpFormat = shift;
- return if ( $MpFormat eq '' );
- my $CurrentPath = cwd();
- my $TheMpFormatPath = LocatedFormatPath($MpFormatPath,$MpExecutable,$MpEngineSupport);
- if ( $TheMpFormatPath ne '' ) { chdir $TheMpFormatPath }
- $own_quote = ($MpExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- my $cmd =
- "$own_quote$MpExecutable$own_quote $MpVirginFlag $MpPassString $MpFormat";
- System($cmd ) ;
- if ( ( $TheMpFormatPath ne '' ) && ( $CurrentPath ne '' ) ) {
- print "\n";
- print " used formatpath : $TheMpFormatPath\n";
- print "\n";
- chdir $CurrentPath;
- }
-}
-
-
-my $dir = File::Temp::tempdir(CLEANUP=>1) ;
-my ($fh, $filename) = File::Temp::tempfile(DIR=>$dir, UNLINK=>1);
-
-sub RunFiles {
- my $currentpath = cwd() ;
- my $oldrunpath = $RunPath ;
- # new
- checktexformatpath ;
- # test if current path is writable
- if (! -w "$currentpath") {
- print " current path readonly : $currentpath\n";
- #
- # we cannot use the following because then the result will
- # also be removed and users will not know where to look
- #
- # $RunPath = File::Temp::tempdir(CLEANUP=>1) ;
- # if ($RunPath) {
- # print " using temp path : $RunPath\n";
- # } else {
- # print " problematic temp path : $currentpath\n";
- # exit ;
- # }
- #
- foreach my $d ($ENV{"TMPDIR"},$ENV{"TEMP"},$ENV{"TMP"},"/tmp") {
- if ($d && -e $d) { $RunPath = $d ; last ; }
- }
- if ($TempDir eq '') {
- print " provide temp path for : $RunPath\n";
- exit ;
- } elsif ($RunPath ne $oldrunpath) {
- chdir ($RunPath) ;
- unless (-e $TempDir) {
- print " creating texexec path : $TempDir\n";
- mkdir ("$TempDir", 077)
- }
- if (-e $TempDir) {
- $RunPath += $TempDir ;
- } else {
- # we abort this run because on unix an invalid tmp
- # path can be an indication of a infected system
- print " problematic temp path : $RunPath\n";
- exit ;
- }
- } else {
- print " no writable temp path : $RunPath\n";
- exit ;
- }
- }
- # test if we need to change paths
- if (($RunPath ne "") && (-w "$RunPath")) {
- print " changing to path : $RunPath\n";
- $InpPath = $currentpath ;
- chdir ($RunPath) ;
- }
- # start working
- if ($PdfArrange) {
- my @arrangedfiles = ();
- foreach my $JobName (@ARGV) {
- unless ( $JobName =~ /.*\.pdf$/oi ) {
- if ( -f "$JobName.pdf" ) { $JobName .= ".pdf" }
- else { $JobName .= ".PDF" }
- }
- push @arrangedfiles, $JobName;
- }
- if (@arrangedfiles) { RunArrange(@arrangedfiles) }
- } elsif ( ($PdfSelect) || ($PdfCopy) || ($PdfTrim) || ($PdfCombine) ) {
- my $JobName = $ARGV[0];
- if ( $JobName ne '' ) {
- unless ( $JobName =~ /.*\.pdf$/oi ) {
- if ( -f "$JobName.pdf" ) { $JobName .= ".pdf" }
- else { $JobName .= ".PDF" }
- }
- if ($PdfSelect) {
- RunSelect($JobName) ;
- } elsif ($PdfCopy) {
- # RunCopy($JobName) ;
- RunCopy(0,@ARGV) ;
- } elsif ($PdfTrim) {
- # RunCopy($JobName) ;
- RunCopy(1,@ARGV) ;
- } else {
- # RunCombine ($JobName) ;
- RunCombine(@ARGV);
- }
- }
- } elsif ($TypesetModule) {
- RunModule(@ARGV);
- } else {
- my $JobSuffix = "tex";
- foreach my $JobName (@ARGV) {
- next if ($JobName =~ /^\-/io) ;
- # start experiment - full name spec including suffix is prerequisite
- if (($StartLine>0) && ($EndLine>=$StartLine) && (-e $JobName)) {
- if (open(INP,$JobName) && open(OUT,'>texexec.tex')) {
- print " writing partial file : $JobName\n";
- my $Line = 1 ;
- my $Preamble = 1 ;
- while (my $str = <INP>) {
- if ($Preamble) {
- if ($str =~ /\\start(text|tekst|product|project|component)/io) {
- $Preamble = 0 ;
- } else {
- print OUT $str;
- }
- } elsif ($Line==$StartLine) {
- print OUT "\\starttext\n" ; # todo: multilingual
- print OUT $str ;
- } elsif ($Line==$EndLine) {
- print OUT $str ;
- print OUT "\\stoptext\n" ; # todo: multilingual
- last ;
- } elsif (($Line>$StartLine) && ($Line<$EndLine)) {
- print OUT $str ;
- }
- $Line += 1 ;
- }
- close(INP) ;
- close(OUT) ;
- $JobName = 'texexec.tex' ;
- print " using job name : $JobName\n";
- }
- }
- # end experiment
- if ( $JobName =~ s/\.(\w+)$//io ) { $JobSuffix = $1 }
- if ( ( $Format eq '' ) || ( $Format =~ /^cont.*/io ) ) {
- RunConTeXtFile( $JobName, $JobSuffix );
- } else {
- RunSomeTeXFile( $JobName, $JobSuffix );
- }
- unless ( -s "$JobName.log" ) { unlink("$JobName.log") }
- unless ( -s "$JobName.tui" ) { unlink("$JobName.tui") }
- }
- }
-}
-
-my $MpTmp = "tmpgraph"; # todo: prefix met jobname
-my $MpKep = "$MpTmp.kep"; # sub => MpTmp("kep")
-my $MpLog = "$MpTmp.log";
-my $MpBck = "$MpTmp.bck";
-my $MpTex = "$MpTmp.tex";
-my $MpDvi = "$MpTmp.dvi";
-
-my %mpbetex;
-
-sub RunMP { ###########
- if ( ($MpExecutable) && ($MpToTeXExecutable) && ($DviToMpExecutable) ) {
- foreach my $RawMpName (@ARGV) {
- my ( $MpName, $Rest ) = split( /\./, $RawMpName, 2 );
- my $MpFile = "$MpName.mp";
- if ( -e $MpFile
- and ( -s $MpFile > 25 ) ) # texunlink makes empty file
- {
- unlink "$MpName.mpt";
- doRunMP( $MpName, 0 );
- # test for graphics, new per 14/12/2000
- my $mpgraphics = checkMPgraphics($MpName);
- # test for labels
- my $mplabels = checkMPlabels($MpName);
- if ( $mpgraphics || $mplabels ) {
- doRunMP( $MpName, $mplabels );
- }
- }
- }
- }
-}
-
-my $mpochecksum = '';
-
-#~ sub checkMPgraphics { # also see makempy
- #~ my $MpName = shift;
- #~ if ( $MakeMpy ne '' ) { $MpName .= " --$MakeMpy " } # extra switches
- #~ if ($MpyForce) { $MpName .= " --force " } # dirty
- #~ else {
- #~ return 0 unless -s "$MpName.mpo" > 32;
- #~ return 0 unless ( open( MPO, "$MpName.mpo" ) );
- #~ $mpochecksum = do { local $/; unpack( "%32C*", <MPO> ) % 65535 };
- #~ close(MPO);
- #~ if ( open( MPY, "$MpName.mpy" ) ) {
- #~ my $str = <MPY>;
- #~ chomp $str;
- #~ close(MPY);
- #~ if ( $str =~ /^\%\s*mpochecksum\s*\:\s*(\d+)/o ) {
- #~ return 0 if ( ( $mpochecksum eq $1 ) && ( $mpochecksum ne 0 ) );
- #~ }
- #~ }
- #~ }
- #~ RunPerlScript( "makempy", "$MpName" );
- #~ print " second MP run needed : text graphics found\n";
- #~ return 1;
-#~ }
-
-sub checkMPgraphics { # also see makempy
- my $MpName = shift;
- if ( $MakeMpy ne '' ) { $MpName .= " --$MakeMpy " } # extra switches
- if ($MpyForce) { $MpName .= " --force " } # dirty
- else {
- return 0 unless -s "$MpName.mpo" > 32;
- return 0 unless ( open( MPO, "$MpName.mpo" ) );
- $mpochecksum = do { local $/; Digest::MD5::md5_hex(<MPO>) ; };
- close(MPO);
- if ( open( MPY, "$MpName.mpy" ) ) {
- my $str = <MPY>;
- chomp $str;
- close(MPY);
- if ( $str =~ /^\%\s*mpochecksum\s*\:\s*([a-fA-F0-9]+)/o ) {
- return 0 if ( ( $mpochecksum eq $1 ) && ( $mpochecksum ne '' ) );
- }
- }
- }
- RunPerlScript( "makempy", "$MpName" );
- print " second MP run needed : text graphics found\n";
- return 1;
-}
-
-sub checkMPlabels {
- my $MpName = shift;
- return 0 unless ((-f "$MpName.mpt") && ((-s "$MpName.mpt")>10) );
- return 0 unless open( MP, "$MpName.mpt" );
- my $n = 0;
- my $t = "" ;
- while (<MP>) {
- if (/% setup : (.*)/o) {
- $t = $1 ;
- } else {
- $t = "" ;
- }
- if (/% figure (\d+) : (.*)/o) {
- if ($t ne "") {
- $mpbetex{$1} .= "$t\n" ;
- $t = "" ;
- }
- $mpbetex{$1} .= "$2\n";
- ++$n ;
- }
- }
- close(MP);
- print " second MP run needed : $n tex labels found\n" if $n;
- return $n;
-}
-
-sub doMergeMP {
- # make sure that the verbatimtex ends up before btex etc
- my ($n,$str) = @_ ;
- if ($str =~ /(.*?)(verbatimtex.*?etex)\s*\;(.*)/mois) {
- return "beginfig($n)\;\n$1$2\;\n$mpbetex{$n}\n$3\;endfig\;\n" ;
- } else {
- return "beginfig($n)\;\n$mpbetex{$n}\n$str\;endfig\;\n" ;
- }
-}
-
-sub doRunMP { ###########
- my ( $MpName, $MergeBE ) = @_;
- my $TexFound = 0;
- my $MpFile = "$MpName.mp";
- if ( open( MP, $MpFile ) ) { # fails with %
- my $MPdata = "";
- while (<MP>) {
- unless (/^\%/) { $MPdata .= $_ }
- }
- $_ = $MPdata;
- close(MP);
-
- # save old file
- unlink($MpKep);
- return if ( -e $MpKep );
- rename( $MpFile, $MpKep );
- # check for tex stuff
-
- $TexFound = $MergeBE || /btex .*? etex/o;
-
- # shorten lines into new file if okay
- unless ( -e $MpFile ) {
- open( MP, ">$MpFile" );
- s/(btex.*?)\;(.*?etex)/$1\@\@\@$2/gmois;
- s/(\".*?)\;(.*?\")/$1\@\@\@$2/gmois; # added
- s/\;/\;\n/gmois;
- s/\n\n/\n/gmois;
- s/(btex.*?)\@\@\@(.*?etex)/$1\;$2/gmois;
- s/(\".*?)\@\@\@(.*?\")/$1\;$2/gmois; # added
- # merge labels
- if ($MergeBE) {
- # i hate this indirect (sub regexp) mess
- s/beginfig\s*\((\d+)\)\s*\;(.*?)endfig\s*\;/doMergeMP($1,$2)/gems ;
- }
- unless (/beginfig\s*\(\s*0\s*\)/gmois) {
- if (defined($mpbetex{0})) { # test added, warning
- print MP $mpbetex{0} ;
- }
- }
- print MP $_;
- print MP "\n" . "end" . "\n";
- close(MP);
- }
- if ($TexFound) {
- print " metapost to tex : $MpName\n";
- $own_quote = ($MpToTeXExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- $Problems =
- System("$own_quote$MpToTeXExecutable$own_quote $MpFile > $MpTex");
- if ( -e $MpTex && !$Problems ) {
- open( TMP, ">>$MpTex" );
- print TMP "\\end\{document\}\n"; # to be sure
- close(TMP);
- if ( ( $Format eq '' ) || ( $Format =~ /^cont.*/io ) ) {
- $OutputFormat = "dvips";
- RunConTeXtFile( $MpTmp, "tex" );
- } else {
- RunSomeTeXFile( $MpTmp, "tex" );
- }
- if ( -e $MpDvi && !$Problems ) {
- print " dvi to metapost : $MpName\n";
- $own_quote = ($DviToMpExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- $Problems = System("$own_quote$DviToMpExecutable$own_quote $MpDvi $MpName.mpx");
- }
- unlink $MpBck;
- rename $MpTex, $MpBck;
- unlink $MpDvi;
- }
- }
- print " metapost : $MpName\n";
- $own_quote = ($MpExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- my $cmd = "$own_quote$MpExecutable$own_quote";
- if ($EnterBatchMode) { $cmd .= " $MpBatchFlag " }
- if ($EnterNonStopMode) { $cmd .= " $MpNonStopFlag " }
- if ( ( $MpFormat ne '' ) && ( $MpFormat !~ /(plain|mpost)/oi ) ) {
- print " format : $MpFormat\n";
- $cmd .= " $MpPassString $MpFormatFlag$MpFormat ";
- }
- # prevent nameclash, experimental
- my $MpMpName = "$MpName";
- $Problems = System("$cmd $MpMpName");
- open( MPL, "$MpName.log" );
- while (<MPL>) # can be one big line unix under win
- {
- while (/^l\.(\d+)\s/gmois) {
- print " error in metapost run : $MpName.mp:$1\n";
- }
- }
- close(MPL) ;
- unlink "mptrace.tmp";
- rename( $MpFile, "mptrace.tmp" );
- if ( -e $MpKep ) {
- unlink($MpFile);
- rename( $MpKep, $MpFile );
- }
- }
-}
-
-sub RunMPX {
- my $MpName = shift;
- $MpName =~ s/\..*$//o;
- my $MpFile = $MpName . ".mp";
- if ( ($MpToTeXExecutable)
- && ($DviToMpExecutable)
- && ( -e $MpFile )
- && ( -s $MpFile > 5 )
- && open( MP, $MpFile ) )
- {
- local $/ = "\0777";
- $_ = <MP>;
- close(MP);
- if (/(btex|etex|verbatimtex)/mos) {
- print " generating mpx file : $MpName\n";
- $own_quote = ($MpToTeXExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- $Problems =
- System("$own_quote$MpToTeXExecutable$own_quote $MpFile > $MpTex");
- if ( -e $MpTex && !$Problems ) {
- open( TMP, ">>$MpTex" );
- print TMP "\\end\n"; # to be sure
- close(TMP);
- checktexformatpath ;
- if ( ( $Format eq '' ) || ( $Format =~ /^cont.*/io ) ) {
- RunConTeXtFile( $MpTmp, "tex" );
- } else {
- RunSomeTeXFile( $MpTmp, "tex" );
- }
- if ( -e $MpDvi && !$Problems ) {
- $own_quote = ($DviToMpExecutable =~ m/^[^\"].* / ? "\"" : "") ;
- $Problems =
- System("$own_quote$DviToMpExecutable$own_quote $MpDvi $MpName.mpx");
- }
- unlink $MpTex;
- unlink $MpDvi;
- }
- }
- }
-}
-
-sub load_set_file {
- my %new;
- my %old;
- my ( $file, $trace ) = @_;
- if ( open( BAT, $file ) ) {
- while (<BAT>) {
- chomp;
- if (/\s*SET\s+(.+?)\=(.+)\s*/io) {
- my ( $var, $val ) = ( $1, $2 );
- $val =~ s/\%(.+?)\%/$ENV{$1}/goi;
- unless ( defined( $old{$var} ) ) {
- if ( defined( $ENV{$var} ) ) { $old{$var} = $ENV{$var} }
- else { $old{$var} = "" }
- }
- $ENV{$var} = $new{$var} = $val;
- }
- }
- close(BAT);
- }
- if ($trace) {
- foreach my $key ( sort keys %new ) {
- if ( $old{$key} ne $new{$key} ) {
- print " changing env variable : '$key' from '$old{$key}' to '$new{$key}'\n";
- } elsif ( $old{$key} eq "" ) {
- print " setting env variable : '$key' to '$new{$key}'\n";
- } else {
- print " keeping env variable : '$key' at '$new{$key}'\n";
- }
- }
- print "\n";
- }
-}
-
-if ( $SetFile ne "" ) { load_set_file( $SetFile, $Verbose ) }
-
-sub check_texmf_root { }
-sub check_texmf_tree { }
-
-sub AnalyzeVersion
- { my $str = join("\n", @_) ;
- my ($texengine,$type) = ('unknown', 'unknown');
- open (LOG, "<texvers.log") ;
- while (<LOG>)
- { /^\s*This is (.*(pdf)?(|e|x)TeX.*?)$/o and $texengine = $1 ;
- /^\s*ConTeXt (.*int: ([a-z]+).*?)\s*$/o and $type = $1; }
- $type =~ s/ int: ([a-z]+)//;
- $texengine =~ s/ Version//;
- $texengine =~ s/ \(format.*$//;
- close (LOG);
- return ($texengine,$type) }
-
-sub show_version_info {
- my ($texengine,$type);
- open (TEX,">texvers.tex") ;
- print TEX "\\bye " ;
- close (TEX) ;
- my $texutil = `$TeXUtil --help`;
- $texutil =~ s/.*(TeXUtil[^\n]+)\n.*?$/$1/s;
- print " texexec :$Program\n" ;
- print " texutil : $texutil" ;
- my $contexttext = `$kpsewhich context.tex`;
- my $contextversion = "<not found>";
- if ($contexttext) {
- chop $contexttext;
- { local $/;
- open (IN,"<$contexttext");
- $contextversion = <IN>;
- close IN;
- }
- $contextversion =~ s/.*contextversion\{([0-9\.\:\s]+)\}.*/$1/s;
- }
- $EnterBatchMode = 1;
- $Format = 'cont-en';
- my $cmd = PrepRunTeX("texvers","tex",'') ;
- ($texengine,$type) = AnalyzeVersion(Pipe($cmd)) ;
- print " tex : $texengine\n" ;
- print " context : ver: $contextversion\n" ;
- print " cont-en : $type\n" ;
- foreach my $a (qw(cz de fr it nl ro uk xx)) {
- my $test = Pipe("$kpsewhich -format='fmt' cont-$a") ;
- if (defined $test && $test) {
- $Format = 'cont-' . $a;
- $cmd = PrepRunTeX("texvers","tex",'');
- ($texengine,$type) = AnalyzeVersion(Pipe($cmd)) ;
- print " cont-$a : $type\n" ;
- }
- }
- unlink <texvers.*>;
-}
-
-# the main thing
-
-if ($HelpAsked) {
- show_help_info
-} elsif ($Version) {
- show_version_info
-} elsif ($TypesetListing) {
- check_texmf_root;
- check_texmf_tree;
- RunListing(@ARGV);
-} elsif ($TypesetFigures) {
- check_texmf_root;
- check_texmf_tree;
- RunFigures(@ARGV);
-} elsif ($DoMPTeX) {
- check_texmf_root;
- check_texmf_tree;
- RunMP;
-} elsif ($DoMPXTeX) {
- check_texmf_root;
- check_texmf_tree;
- RunMPX( $ARGV[0] );
-} elsif ($MakeFormats) {
- check_texmf_root;
- check_texmf_tree;
- if ( $MpDoFormat ne '' ) {
- RunMpFormat($MpDoFormat) ;
- }
- else {
- RunFormats ;
- }
-} elsif (@ARGV) {
- check_texmf_root;
- check_texmf_tree;
- @ARGV = <@ARGV>;
- RunFiles;
-} elsif ( !$HelpAsked ) {
- show_help_options;
-}
-
-$TotalTime = time - $TotalTime;
-
-unless ($HelpAsked) { print "\n total run time : $TotalTime seconds\n" }
-
-print "\n" ;
-print " warning : use 'texmfstart texexec' instead\n" ;
-
-if ($Problems) { exit 1 }
-
-__DATA__
-arrange process and arrange
------------
-batch run in batch mode (don't pause)
------------
-nonstop run in non stop mode (don't pause)
------------
-centerpage center the page on the paper
------------
-color enable color (when not yet enabled)
------------
-usemodule load some modules first
-=name list of modules
------------
-xmlfilter apply XML filter
-=name list of filters
------------
-environment load some environments first
-=name list of environments
------------
-fast skip as much as possible
------------
-figures typeset figure directory
-=a room for corrections
-=b just graphics
-=c one (cropped) per page
-paperoffset room left at paper border
-fullscreen force full screen mode (pdf)
------------
-screensaver turn graphic file into a (pdf) full screen file
------------
-final add a final run without skipping
------------
-format fmt file
-=name format file (memory dump)
------------
-mpformat mem file
-=name format file (memory dump)
------------
-interface user interface
-=en English
-=nl Dutch
-=de German
-=fr French
-=cz Czech
-=uk British
-=it Italian
------------
-language main hyphenation language
-=xx standard abbreviation
------------
-listing produce a verbatim listing
-backspace inner margin of the page
-topspace top/bottom margin of the page
-pretty enable pretty printing
-color use color for pretty printing
------------
-make build format files
-language patterns to include
-bodyfont bodyfont to preload
-response response interface language
-format TeX format
-mpformat MetaPost format
-program TeX program
------------
-mode running mode
-=list modes to set
------------
-module typeset tex/pl/mp module
------------
-mptex run an MetaPost plus btex-etex cycle
------------
-mpxtex generate an MetaPost mpx file
------------
-noarrange process but ignore arrange
------------
-nomp don't run MetaPost at all
------------
-nomprun don't run MetaPost at runtime
------------
-automprun MetaPost at runtime when needed
------------
-once run TeX only once (no TeXUtil either)
------------
-output specials to use
-=pdftex Han The Thanh's pdf backend
-=dvips Tomas Rokicky's dvi to ps converter
-=dvipsone YandY's dvi to ps converter
-=dviwindo YandY's windows previewer
-=dvipdfm Mark Wicks' dvi to pdf converter
-=dvipdfmx Jin-Hwan Cho's extended dvipdfm
------------
-passon switches to pass to TeX (--src for MikTeX)
------------
-pages pages to output
-=odd odd pages
-=even even pages
-=x,y:z pages x and y to z
------------
-paper paper input and output format
-=a4a3 A4 printed on A3
-=a5a4 A5 printed on A4
------------
-path document source path
-=string path
------------
-pdf produce PDF directly using pdf(e)tex
------------
-pdfarrange arrange pdf pages
-paperoffset room left at paper border
-paper paper format
-noduplex single sided
-backspace inner margin of the page
-topspace top/bottom margin of the page
-markings add cutmarks
-background
-=string background graphic
-addempty add empty page after
-textwidth width of the original (one sided) text
------------
-pdfcombine combine pages to one page
-paperformat paper format
-combination n*m pages per page
-paperoffset room left at paper border
-nobanner no footerline
------------
-pdfcopy scale pages down/up
-scale new page scale
-paperoffset room left at paper border
-markings add cutmarks
-background
-=string background graphic
------------
-pdfselect select pdf pages
-selection pages to select
-=odd odd pages
-=even even pages
-=x,y:z pages x and y to z
-paperoffset room left at paper border
-paperformat paper format
-backspace inner margin of the page
-topspace top/bottom margin of the page
-markings add cutmarks
-background
-=string background graphic
-addempty add empty page after
-textwidth width of the original (one sided) text
------------
-print page imposition scheme
-=up 2 pages per sheet doublesided
-=down 2 rotated pages per sheet doublesided
------------
-result resulting file
-=name filename
------------
-input input file (if used)
-=name filename
------------
-suffix resulting file suffix
-=string suffix
------------
-runs maximum number of TeX runs
-=n number of runs
------------
-silent minimize (status) messages
------------
-tex TeX binary
-=name binary of executable
------------
-textree additional texmf tree to be used
-=path subpath of tex root
------------
-texroot root of tex trees
-=path tex root
------------
-verbose shows some additional info
------------
-help show this or more, e.g. '--help interface'
------------
-alone bypass utilities (e.g. fmtutil for non-standard fmts)
------------
-texutil force TeXUtil run
------------
-version display various version information
------------
-setfile load environment (batch) file
diff --git a/Master/texmf-dist/scripts/context/perl/texexec.rme b/Master/texmf-dist/scripts/context/perl/texexec.rme
deleted file mode 100644
index 43f142f9da4..00000000000
--- a/Master/texmf-dist/scripts/context/perl/texexec.rme
+++ /dev/null
@@ -1,169 +0,0 @@
-% == introduction ==
-%
-% This is 'texexec.ini', the file used by texexec to determine where
-% to find files, what TeX to use, what flags to pass, etc. Although
-% TeXexec tries to locate things itself, a little help is sometimes
-% needed. One can influence texexec by setting some variables. These
-% are only needed when the automatic determined settings fail.
-%
-% == interfacing ==
-%
-% UsedInterfaces nl,en the formats generated with --make
-% UserInterface nl the default format used
-%
-% == binaries ==
-%
-% TeXExecutable pdfetex the TeX binary to use
-% MpExecutable mpost the MetaPost binary to use
-% MpToTeXExecutable mpto the MetaPost to TeX converter
-% DviToMpExecutable dvitomp the DVI to MetaPost converter
-%
-% == Scripts ==
-%
-% DviSpecialScript dvispec the DVI special filter script
-%
-% == flags ==
-%
-% TeXFormatFlag -fmt= the format introducer
-% TeXVirginFlag -ini the format generation switch
-%
-% == paths ==
-%
-% TeXFormatPath texmf/... fmt files
-% ConTeXtPath texmf/tex/context/base sources
-% SetupPath texmf/tex/base/user cont-sys/usr file
-% TeXScriptsPath texmf/context/perltk scripts
-% TeXFontsPath texmf font files
-%
-% MpFormatPath TeXFormatPath mem files
-%
-% == the main shell setting ==
-%
-% As shown below, one can define his/her own sections. We default to
-% the teTeX/fpTeX web2c based settings.
-
-set TeXShell to tetex
-%set TeXShell to fptex
-%set TeXShell to miktex
-%set TeXShell to private
-
-% == setting up the variables ==
-%
-% Here are some general defaults. They can be overruled later.
-
-set UseEnginePath to true
-
-set UsedInterfaces to en nl metafun mptopdf
-set UserInterface to en
-
-set TeXExecutable to tex
-
-set MpExecutable to mpost
-set MpToTeXExecutable to mpto
-set DviToMpExecutable to dvitomp
-set DviSpecialScript to dvispec
-
-set MpFormat to metafun
-
-set TeXFormatFlag to &
-set MpFormatFlag to &
-
-% For teTeX the next settings will do.
-
-% -default-translate-file=cp8bit -file-line-error-style
-
-for tetex set TeXHashExecutable to mktexlsr
-for tetex set TeXExecutable to pdfetex
-for tetex set TeXVirginFlag to -ini
-for tetex set TeXPassString to -progname=context
-for tetex set TeXBatchFlag to -int=batchmode
-for tetex set TeXNonStopFlag to -int=nonstopmode
-for tetex set MpToTeXExecutable to mpto
-for tetex set MpVirginFlag to -ini
-for tetex set MpPassString to -progname=mpost
-for tetex set MpBatchFlag to -int=batchmode
-for tetex set MpNonStopFlag to -int=nonstopmode
-
-% These also apply to fpTeX.
-
-% -default-translate-file=cp8bit -file-line-error-style
-
-for fptex set TeXHashExecutable to mktexlsr
-for fptex set TeXExecutable to pdfetex
-for fptex set TeXVirginFlag to -ini
-for fptex set TeXPassString to -progname=context
-for fptex set TeXBatchFlag to -int=batchmode
-for fptex set TeXNonStopFlag to -int=nonstopmode
-for fptex set MpToTeXExecutable to mpto
-for fptex set MpVirginFlag to -ini
-for fptex set MpPassString to -progname=mpost
-for fptex set MpBatchFlag to -int=batchmode
-for fptex set MpNonStopFlag to -int=nonstopmode
-
-% a downward compatibility test (<7.5.3), next year we will use -8bit
-
-for tetex set TeXVirginFlag to -ini -translate-file=natural.tcx
-for fptex set TeXVirginFlag to -ini -translate-file=natural.tcx
-for tetex set MpVirginFlag to -ini -translate-file=natural.tcx
-for fptex set MpVirginFlag to -ini -translate-file=natural.tcx
-
-for tetex set TeXPassString to -progname=context -translate-file=natural.tcx
-for fptex set TeXPassString to -progname=context -translate-file=natural.tcx
-for tetex set MpPassString to -progname=mpost -translate-file=natural.tcx
-for fptex set MpPassString to -progname=mpost -translate-file=natural.tcx
-
-% MikTeX users probably have to set up some paths too.
-
-for miktex set TeXHashExecutable to initexmf --update-fndb
-for miktex set TeXExecutable to pdfetex
-for miktex set TeXVirginFlag to --initialize
-for miktex set TeXPassString to --alias=context --translate-file=natural.tcx
-for miktex set TeXBatchFlag to --interaction=batchmode
-for miktex set TeXNonStopFlag to --interaction=nonstopmode
-for miktex set MpToTeXExecutable to mpto
-for miktex set MpVirginFlag to --initialize
-for miktex set MpPassString to --alias=mpost --translate-file=natural.tcx
-for miktex set MpBatchFlag to --interaction=batchmode
-for miktex set MpNonStopFlag to --interaction=nonstopmode
-
-for miktex set TeXFormatFlag to --undump=
-for miktex set MpFormatFlag to --undump=
-
-% These are the settings used on some machines at PRAGMA ADE that
-% don't use the texmf tree. They can serve as an example for local
-% settings. Local settings should either be added to the previous
-% one, or go without the 'for' directives. Consider these
-% settings as an example.
-
-for private set UsedInterfaces to en nl
-for private set UserInterface to nl
-for private set ConTeXtPath to t:/sources/
-for private set SetupPath to t:/perl/
-for private set TeXScriptsPath to t:/perl/
-
-% == read this too ==
-%
-% If this file is called 'texexec.rme', copy it to 'texexec.ini',
-% check the settings above, change them according to your TeX
-% distribution, and say:
-%
-% texexec --verbose
-%
-% When set up properly, you should see your local settings fly by.
-% When these settings are ok, the next call should work:
-%
-% texexec --make
-%
-% and you should be able to process a file by saying
-%
-% texexec filename
-%
-% See 'mtexexec.pdf' for more information on the flags you can use with
-% 'texexec'. Also make sure you have the 'texutil' script installed in
-% the same path as 'texexec'.
-
-% Experimental
-%
-% set TcXPath to d:/tex/texmf/web2c
-% set FmtLanguage to pl
-% set FmtBodyFont to plr
diff --git a/Master/texmf-dist/scripts/context/perl/texutil.pl b/Master/texmf-dist/scripts/context/perl/texutil.pl
deleted file mode 100644
index ca364501584..00000000000
--- a/Master/texmf-dist/scripts/context/perl/texutil.pl
+++ /dev/null
@@ -1,2914 +0,0 @@
-eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q'
- if 0;
-
-#D \module
-#D [ file=texutil.pl,
-#D version=2003.09.16,
-#D title=pre- and postprocessing utilities,
-#D subtitle=\TEXUTIL,
-#D author=Hans Hagen,
-#D date=\currentdate,
-#D copyright={PRAGMA / Hans Hagen \& Ton Otten}]
-#C
-#C This module is part of the \CONTEXT\ macro||package and is
-#C therefore copyrighted by \PRAGMA. See licen-en.pdf for
-#C details.
-
-# much functionality will move to ctxtools, xmltools and pdftools; that way texutil
-# becomes limited to tui processing only, which is cleaner (also for taco's binary
-# version)
-
-# Thanks to Tobias Burnus for the german translations.
-# Thanks to Thomas Esser for hooking it into web2c
-# Thanks to Taco Hoekwater for making the file -w proof and some fixes
-# Thanks to Alex Knowles and friends for the right JPG specs
-# Thanks to Sebastian Rahtz for the eps to PDF method
-# Thanks to Fabrice Popineau for windows bin code
-
-#D We started with a hack provided by Thomas Esser. This
-#D expression replaces the unix specific line \type
-#D {#!/usr/bin/perl}.
-
-# undocumented:
-#
-# --analyze file.pdf : reports some statistics
-# --purge [jobname] : removes temporary files
-# --purgeall [jobname] : removes all temporary files
-
-#D This is \TEXUTIL, a utility program (script) to be used
-#D alongside the \CONTEXT\ macro package. This \PERL\ script is
-#D derived from the \MODULA\ version and uses slightly better
-#D algoritms for sanitizing \TEX\ specific (sub|)|strings.
-#D
-#D This implementation has some features not found in the
-#D binary version, like scanning illustrations other than \EPS.
-#D I would suggest to keep an eye on the version number:
-
-$Program = "TeXUtil 9.0.1 - ConTeXt / PRAGMA ADE 1992-2006" ;
-
-#D By the way, this is my first \PERL\ script, which means
-#D that it will be improved as soon as I find new and/or more
-#D suitable solutions in the \PERL\ manuals. As can be seen in
-#D the definition of \type{$Program}, this program is part of
-#D the \CONTEXT\ suite, and therefore can communicate with the
-#D users in english as well as some other languages. One can
-#D set his favourite language by saying something like:
-
-#D This one has a real old date, 1992. This is because it is
-#D a converted modula program that was used in the very early
-#D days of our \TEX\ history (macros for proper inclusion of
-#D graphics were among the first thatwe wrote).
-
-#D \starttypen
-#D perl texutil.pl --int=de --fig *.eps *.tif *.pdf *.png *.jpg
-#D \stoptypen
-#D
-#D or simpler:
-#D
-#D \starttypen
-#D perl texutil.pl --fig *.*
-#D \stoptypen
-
-#D Of course one can also say \type{--interface=nl}, which
-#D happens to be my native language.
-
-#D I won't go into too much detail on the algoritms used.
-#D The next few pages show the functionality as reported by the
-#D helpinformation and controled by command line arguments
-#D and can serve as additional documentation.
-
-#D \TEXUTIL\ can handle different tasks; which one is active
-#D depends on the command line arguments. These are handled by
-#D a \PERL\ system module. This means that, at least for the
-#D moment, there is no external control as provided by the
-#D \PRAGMA\ environment system.
-
-use Getopt::Long ;
-use FindBin ;
-
-#D We don't want error messages and accept partial switches,
-#D which saves users some typing.
-
-$Getopt::Long::passthrough = 1 ; # no error message
-$Getopt::Long::autoabbrev = 1 ; # partial switch accepted
-
-#D We also predefine the interface language and set a boolean
-#D that keeps track of unknown options. \voetnoot {This feature
-#D is still to be implemented.}
-
-$UserInterface = "en" ;
-$UnknownOptions = 0 ;
-$TcXPath = '' ;
-
-#D We need this for calling GS.
-
-use Config ;
-
-my $dosish = ($Config{'osname'} =~ /^(ms)?dos|^os\/2|^(ms|cyg)win/i) ;
-
-#D Here come the options:
-
-&GetOptions
- ("references" => \$ProcessReferences,
- "ij" => \$ProcessIJ,
- "high" => \$ProcessHigh,
- "quotes" => \$ProcessQuotes,
- "tcxpath=s" => \$TcXPath,
- "documents" => \$ProcessDocuments,
- "type=s" => \$ProcessType,
- "outputfile=s" => \$ProcessOutputFile,
- "sources" => \$ProcessSources,
- "setups" => \$ProcessSetups,
- "templates" => \$ProcessTemplates,
- "infos" => \$ProcessInfos,
- "figures" => \$ProcessFigures,
- "epspage" =>\$ProcessEpsPage,
- "epstopdf" =>\$ProcessEpsToPdf,
- "logfile" => \$ProcessLogFile,
- "box" =>\$ProcessBox,
- "hbox" =>\$ProcessHBox,
- "vbox" =>\$ProcessVBox,
- "criterium=f" =>\$ProcessCriterium,
- "unknown" =>\$ProcessUnknown,
- "purge" => \$PurgeFiles,
- "purgeall" => \$PurgeAllFiles,
- "analyze" => \$AnalyzeFile,
- "filter" => \$FilterPages,
- "help" => \$ProcessHelp,
- "silent" => \$ProcessSilent,
- "verbose" => \$ProcessVerbose,
- "interface=s" => \$UserInterface) ;
-
-# A bit old, this code, could be an array. Anyhow, we will
-# replace texutil soon.
-
-$InputFile = "@ARGV" ; # niet waterdicht
-
-#D We need some hacks to suppress terminal output. This
-#D piece of code is based on page~193 of "Programming Perl".
-
-$ProgramLog = "texutil.log" ;
-
-# Well, it seems that unix' symlinks are sensitive for being
-# hijacked. The assumption is that a known file can be a problem.
-# Of course when one knows that certains files are processed,
-# the names are always known and hijacking can always take
-# place. But let's use a slightly less predictable name here:
-#
-# if ((@ARGV[0]) && (@ARGV[0] ne "")) {
-# $ProgramLog = "@ARGV[0]-$ProgramLog" ;
-# } else {
-# # no need to be silent
-# $ProcessSilent = 0 ;
-# }
-#
-# or better, let's drop this feature, since i'm not in the mood
-# now to test hacks like this (i'll just wait till the age of
-# computer anarchy has ended).
-
-$ProgramLog = "/dev/null" ;
-
-# Maybe we should just write to the nul device. (In the rewritten
-# version I can treat unix more strick.)
-
-sub RedirectTerminal
- { open SAVEDSTDOUT, ">&STDOUT" ;
- open STDOUT, ">$ProgramLog" ;
- select STDOUT; $| = 1 }
-
-#D And, indeed:
-
-if ($ProcessSilent)
- { RedirectTerminal }
-else
- { $ProcessVerbose = 0 }
-
-#D We can temporary open the terminal channel.
-
-sub OpenTerminal
- { close STDOUT ;
- open STDOUT, ">&SAVEDSTDOUT" }
-
-sub CloseTerminal
- { open SAVEDSTDOUT, ">&STDOUT" ;
- open STDOUT, ">>$ProgramLog" ;
- select STDOUT; $| = 1 }
-
-#D By default wildcards are expanded into a list. The
-#D subroutine below is therefore only needed when no file or
-#D pattern is given.
-
-sub CompFileName
- { my ($a,$b) = @_ ;
- my ($fa,$sa) = split(/\./,$a) ;
- my ($fb,$sb) = split(/\./,$b) ;
- if (($sa =~ /^\d+$/o)&&($sb =~ /^\d+$/o))
- { $a = $fa . "." . sprintf("%10d",$sa) ; $a =~ s/\s/0/o ;
- $b = $fb . "." . sprintf("%10d",$sb) ; $b =~ s/\s/0/o }
- return (lc ($a) cmp lc ($b)) }
-
-sub CheckInputFiles
- { @UserSuppliedFiles = glob $_[0] ;
- @UserSuppliedFiles = sort { CompFileName($a,$b) } @UserSuppliedFiles }
-
-#D The next subroutine takes care of the optional output
-#D filename (e.g. for figure dimensions).
-
-$ProcessOutputFile = "" ;
-
-my $Rubish ;
-
-sub SetOutputFile
- { ($OutFilNam, $OutFilSuf) = split (/\./, $_[0], 2) ;
- unless ($ProcessOutputFile eq "")
- { $ProcessOutputFile .= "." . $OutFilSuf ;
- ($OutFilNam, $OutFilSuf, $Rubish) = split (/\./, $ProcessOutputFile , 3)}
- $OutputFile = $OutFilNam . "." . $OutFilSuf }
-
-#D Sometimes we need to split filenames.
-
-my ($FileName, $FileSuffix) = ("","") ;
-
-sub SplitFileName
- { my $Rubish = "" ;
- if ($_[0] =~ /^\.\//)
- { ($Rubish, $FileName) = split ( /^\.\//, $_[0], 2) }
- else
- { $FileName = $_[0] }
- return split (/\./, $FileName, 2) }
-
-#D In order to support multiple interfaces, we save the
-#D messages in a hash table. As a bonus we can get a quick
-#D overview of the messages we deal with.
-
-my %MS ;
-
-sub Report
- { foreach $_ (@_)
- { if (! defined $MS{$_})
- { print $_ }
- else
- { print $MS{$_} }
- print " " }
- print "\n" }
-
-#D The messages are saved in a hash table and are called
-#D by name. This contents of this table depends on the
-#D interface language in use.
-
-#D \startcompressdefinitions
-
-if ($UserInterface eq "nl")
-
- { # begin of dutch section
-
- $MS{"ProcessingReferences"} = "commando's, lijsten en indexen verwerken" ;
- $MS{"MergingReferences"} = "indexen samenvoegen" ;
- $MS{"GeneratingDocumentation"} = "ConTeXt documentatie file voorbereiden" ;
- $MS{"GeneratingSources"} = "ConTeXt broncode file genereren" ;
- $MS{"FilteringDefinitions"} = "ConTeXt definities filteren" ;
- $MS{"CopyingTemplates"} = "TeXEdit toets templates copieren" ;
- $MS{"CopyingInformation"} = "TeXEdit help informatie copieren" ;
- $MS{"GeneratingFigures"} = "figuur file genereren" ;
- $MS{"FilteringLogFile"} = "log file filteren (poor mans version)" ;
-
- $MS{"SortingIJ"} = "IJ sorteren onder Y" ;
- $MS{"ConvertingHigh"} = "hoge ASCII waarden converteren" ;
- $MS{"ProcessingQuotes"} = "characters met accenten afhandelen" ;
- $MS{"ForcingFileType"} = "filetype instellen" ;
- $MS{"UsingEps"} = "EPS files afhandelen" ;
- $MS{"UsingTif"} = "TIF files afhandelen" ;
- $MS{"UsingPdf"} = "PDF files afhandelen" ;
- $MS{"UsingPng"} = "PNG files afhandelen" ;
- $MS{"UsingJpg"} = "JPG files afhandelen" ;
- $MS{"EpsToPdf"} = "EPS converteren naar PDF";
- $MS{"EpsPage"} = "EPS pagina instellen";
- $MS{"FilteringBoxes"} = "overfull boxes filteren" ;
- $MS{"ApplyingCriterium"} = "criterium toepassen" ;
- $MS{"FilteringUnknown"} = "onbekende ... filteren" ;
-
- $MS{"NoInputFile"} = "geen invoer file opgegeven" ;
- $MS{"NoOutputFile"} = "geen uitvoer file gegenereerd" ;
- $MS{"EmptyInputFile"} = "lege invoer file" ;
- $MS{"NotYetImplemented"} = "nog niet beschikbaar" ;
-
- $MS{"Action"} = " actie :" ;
- $MS{"Option"} = " optie :" ;
- $MS{"Error"} = " fout :" ;
- $MS{"Remark"} = " opmerking :" ;
- $MS{"SystemCall"} = " systeemaanroep :" ;
- $MS{"BadSystemCall"} = " foute systeemaanroep :" ;
- $MS{"MissingSubroutine"} = " onbekende subroutine :" ;
-
- $MS{"EmbeddedFiles"} = " gebruikte files :" ;
- $MS{"BeginEndError"} = " b/e fout in :" ;
- $MS{"SynonymEntries"} = " aantal synoniemen :" ;
- $MS{"SynonymErrors"} = " fouten :" ;
- $MS{"RegisterEntries"} = " aantal ingangen :" ;
- $MS{"RegisterErrors"} = " fouten :" ;
- $MS{"PassedCommands"} = " aantal commando's :" ;
-
- $MS{"MultiPagePdfFile"} = " te veel pagina's :" ;
- $MS{"MissingMediaBox"} = " geen mediabox :" ;
- $MS{"MissingBoundingBox"} = " geen boundingbox :" ;
-
- $MS{"NOfDocuments"} = " documentatie blokken :" ;
- $MS{"NOfDefinitions"} = " definitie blokken :" ;
- $MS{"NOfSkips"} = " overgeslagen blokken :" ;
- $MS{"NOfSetups"} = " gecopieerde setups :" ;
- $MS{"NOfTemplates"} = " gecopieerde templates :" ;
- $MS{"NOfInfos"} = " gecopieerde helpinfos :" ;
- $MS{"NOfFigures"} = " verwerkte figuren :" ;
- $MS{"NOfBoxes"} = " te volle boxen :" ;
- $MS{"NOfUnknown"} = " onbekende ... :" ;
-
- $MS{"InputFile"} = " invoer file :" ;
- $MS{"OutputFile"} = " outvoer file :" ;
- $MS{"FileType"} = " type file :" ;
- $MS{"EpsFile"} = " eps file :" ;
- $MS{"PdfFile"} = " pdf file :" ;
- $MS{"TifFile"} = " tif file :" ;
- $MS{"PngFile"} = " png file :" ;
- $MS{"JpgFile"} = " jpg file :" ;
- $MS{"MPFile"} = " metapost file :" ;
-
- $MS{"LoadedFilter"} = " geladen filter :" ;
- $MS{"RemappedKeys"} = " onderschepte keys :" ;
- $MS{"WrongFilterPath"} = " fout filter pad :" ;
-
- $MS{"Overfull"} = "te vol" ;
- $MS{"Entries"} = "ingangen" ;
- $MS{"References"} = "verwijzingen" ;
-
- $MS{"PlugInInit"} = " plugin initialized :" ;
- $MS{"PlugInReport"} = " plugin report :" ;
-
- } # end of dutch section
-
-elsif ($UserInterface eq "de")
-
- { # begin of german section
-
- $MS{"ProcessingReferences"} = "Verarbeiten der Befehle, Listen und Register" ;
- $MS{"MergingReferences"} = "Register verschmelzen" ;
- $MS{"GeneratingDocumentation"} = "Vorbereiten der ConTeXt-Dokumentationsdatei" ;
- $MS{"GeneratingSources"} = "Erstellen einer nur Quelltext ConTeXt-Datei" ;
- $MS{"FilteringDefinitions"} = "Filtern der ConTeXt-Definitionen" ;
- $MS{"CopyingTemplates"} = "Kopieren der TeXEdit-Test-key-templates" ;
- $MS{"CopyingInformation"} = "Kopieren der TeXEdit-Hilfsinformation" ;
- $MS{"GeneratingFigures"} = "Erstellen einer Abb-Uebersichtsdatei" ;
- $MS{"FilteringLogFile"} = "Filtern der log-Datei" ;
-
- $MS{"SortingIJ"} = "Sortiere IJ nach Y" ;
- $MS{"ConvertingHigh"} = "Konvertiere hohe ASCII-Werte" ;
- $MS{"ProcessingQuotes"} = "Verarbeiten der Akzentzeichen" ;
- $MS{"ForcingFileType"} = "Dateityp einstellen" ;
- $MS{"UsingEps"} = "EPS-Dateien verarbeite" ;
- $MS{"UsingTif"} = "TIF-Dateien verarbeite" ;
- $MS{"UsingPdf"} = "PDF-Dateien verarbeite" ;
- $MS{"UsingPng"} = "PNG-Dateien verarbeite" ;
- $MS{"UsingJpg"} = "JPG-Dateien verarbeite" ;
- $MS{"EpsToPdf"} = "convert EPS to PDF";
- $MS{"EpsPage"} = "setup EPS page";
-
- $MS{"FilteringBoxes"} = "Filtern der ueberfuellten Boxen" ;
- $MS{"ApplyingCriterium"} = "Anwenden des uebervoll-Kriteriums" ;
- $MS{"FilteringUnknown"} = "Filter unbekannt ..." ;
-
- $MS{"NoInputFile"} = "Keine Eingabedatei angegeben" ;
- $MS{"NoOutputFile"} = "Keine Ausgabedatei generiert" ;
- $MS{"EmptyInputFile"} = "Leere Eingabedatei" ;
- $MS{"NotYetImplemented"} = "Noch nicht verfuegbar" ;
-
- $MS{"Action"} = " Aktion :" ;
- $MS{"Option"} = " Option :" ;
- $MS{"Error"} = " Fehler :" ;
- $MS{"Remark"} = " Anmerkung :" ;
- $MS{"SystemCall"} = " system call :" ;
- $MS{"BadSystemCall"} = " bad system call :" ;
- $MS{"MissingSubroutine"} = " missing subroutine :" ;
- $MS{"SystemCall"} = " Systemaufruf :" ;
- $MS{"BadSystemCall"} = " Fehlerhafter Aufruf :" ;
- $MS{"MissingSubroutine"} = " Fehlende Unterroutine :" ;
-
- $MS{"EmbeddedFiles"} = " Eingebettete Dateien :" ;
- $MS{"BeginEndError"} = " Beg./Ende-Fehler in :" ;
- $MS{"SynonymEntries"} = " Synonymeintraege :" ;
- $MS{"SynonymErrors"} = " Fehlerhafte Eintraege :" ;
- $MS{"RegisterEntries"} = " Registereintraege :" ;
- $MS{"RegisterErrors"} = " Fehlerhafte Eintraege :" ;
- $MS{"PassedCommands"} = " Verarbeite Befehle :" ;
-
- $MS{"MultiPagePdfFile"} = " zu viele Seiten :" ;
- $MS{"MissingMediaBox"} = " fehlende mediabox :" ;
- $MS{"MissingBoundingBox"} = " fehlende boundingbox :" ;
-
- $MS{"NOfDocuments"} = " Dokumentbloecke :" ;
- $MS{"NOfDefinitions"} = " Definitionsbloecke :" ;
- $MS{"NOfSkips"} = "Uebersprungene Bloecke :" ;
- $MS{"NOfSetups"} = " Kopierte setups :" ;
- $MS{"NOfTemplates"} = " Kopierte templates :" ;
- $MS{"NOfInfos"} = " Kopierte helpinfos :" ;
- $MS{"NOfFigures"} = " Verarbeitete Abb. :" ;
- $MS{"NOfBoxes"} = " Zu volle Boxen :" ;
- $MS{"NOfUnknown"} = " Unbekannt ... :" ;
-
- $MS{"InputFile"} = " Eingabedatei :" ;
- $MS{"OutputFile"} = " Ausgabedatei :" ;
- $MS{"FileType"} = " Dateityp :" ;
- $MS{"EpsFile"} = " eps-Datei :" ;
- $MS{"PdfFile"} = " pdf-Datei :" ;
- $MS{"TifFile"} = " tif-Datei :" ;
- $MS{"PngFile"} = " png-Datei :" ;
- $MS{"JpgFile"} = " jpg-Datei :" ;
- $MS{"MPFile"} = " metapost-Datei :" ;
-
- $MS{"LoadedFilter"} = " loaded filter :" ; # tobias
- $MS{"RemappedKeys"} = " remapped keys :" ; # tobias
- $MS{"WrongFilterPath"} = " wrong filter path :" ; # tobias
-
- $MS{"Overfull"} = "zu voll" ;
- $MS{"Entries"} = "Eintraege" ;
- $MS{"References"} = "Referenzen" ;
-
- $MS{"ExtraProgram"} = " extra program :" ;
- $MS{"PlugInInit"} = " plugin initialized :" ;
- $MS{"PlugInReport"} = " plugin report :" ;
-
- } # end of german section
-
-elsif ($UserInterface eq "it")
-
- { # begin of italian section
-
- $MS{"ProcessingReferences"} = "elaborazione di comandi, liste e registri" ;
- $MS{"MergingReferences"} = "fusione dei registri" ;
- $MS{"GeneratingDocumentation"} = "preparazione del file di documentazione ConTeXt" ;
- $MS{"GeneratingSources"} = "generazione del solo sorgente ConTeXt" ;
- $MS{"FilteringDefinitions"} = "filtraggio delle definizioni formali ConTeXt" ;
- $MS{"CopyingTemplates"} = "copia dei modelli rapidi di voci di TeXEdit" ;
- $MS{"CopyingInformation"} = "copia delle informazioni di aiuto di TeXEdit" ;
- $MS{"GeneratingFigures"} = "generazione del file di elengo delle figure" ;
- $MS{"FilteringLogFile"} = "filtraggio del file di log" ;
-
- $MS{"SortingIJ"} = "IJ elencato sotto Y" ;
- $MS{"ConvertingHigh"} = "conversione dei valori ASCII alti" ;
- $MS{"ProcessingQuotes"} = "elaborazione dei caratteri accentati" ;
- $MS{"ForcingFileType"} = "impostazine del tipo di file" ;
- $MS{"UsingEps"} = "elaborazione del file EPS" ;
- $MS{"UsingTif"} = "elaborazione del file TIF" ;
- $MS{"UsingPdf"} = "elaborazione del file PDF" ;
- $MS{"UsingPng"} = "elaborazione del file PNG" ;
- $MS{"UsingJpg"} = "elaborazione del file JPG" ;
- $MS{"EpsToPdf"} = "conversione da EPS a PDF";
- $MS{"EpsPage"} = "impostazione pagina EPS";
-
- $MS{"FilteringBoxes"} = "filtraggio delle overfull boxes" ;
- $MS{"ApplyingCriterium"} = "applicazione del criterio overfull" ;
- $MS{"FilteringUnknown"} = "filtraggio dei messaggi non conosciuti ..." ;
-
- $MS{"NoInputFile"} = "nessun file di input specificato" ;
- $MS{"NoOutputFile"} = "nessun file di output generato" ;
- $MS{"EmptyInputFile"} = "file di input vuoto" ;
- $MS{"NotYetImplemented"} = "non ancora disponibile" ;
-
- $MS{"Action"} = " azione :" ;
- $MS{"Option"} = " opzione :" ;
- $MS{"Error"} = " errore :" ;
- $MS{"Remark"} = " commento :" ;
- $MS{"SystemCall"} = " chiamata di sistema :" ;
- $MS{"BadSystemCall"} = "chiamata di sistema er :" ; # GB: Hans, I need more space!
- $MS{"MissingSubroutine"} = " subroutine mancante :" ;
-
- $MS{"EmbeddedFiles"} = " file inclusi :" ;
- $MS{"BeginEndError"} = " errore di i/f in :" ;
- $MS{"SynonymEntries"} = " voci di sinonimi :" ;
- $MS{"SynonymErrors"} = " voci errate :" ;
- $MS{"RegisterEntries"} = " voci di registro :" ;
- $MS{"RegisterErrors"} = " voci errate :" ;
- $MS{"PassedCommands"} = " comandi passati :" ;
-
- $MS{"MultiPagePdfFile"} = " troppe pagine :" ;
- $MS{"MissingMediaBox"} = " mediabox mancante :" ;
- $MS{"MissingBoundingBox"} = " boundingbox mancante :" ;
-
- $MS{"NOfDocuments"} = " blocchi di documento :" ;
- $MS{"NOfDefinitions"} = "blocchi di definizioni :" ;
- $MS{"NOfSkips"} = " blocchi saltati :" ;
- $MS{"NOfSetups"} = " impostazioni copiate :" ;
- $MS{"NOfTemplates"} = " modelli copiati :" ;
- $MS{"NOfInfos"} = " helpinfo copiati :" ;
- $MS{"NOfFigures"} = " figure elaborate :" ;
- $MS{"NOfBoxes"} = " overfull boxes :" ;
- $MS{"NOfUnknown"} = " sconosciuti ... :" ;
-
- $MS{"InputFile"} = " file di input :" ;
- $MS{"OutputFile"} = " file di output :" ;
- $MS{"FileType"} = " tipo di file :" ;
- $MS{"EpsFile"} = " file eps :" ;
- $MS{"PdfFile"} = " file pdf :" ;
- $MS{"TifFile"} = " file tif :" ;
- $MS{"PngFile"} = " file png :" ;
- $MS{"JpgFile"} = " file jpg :" ;
- $MS{"MPFile"} = " file metapost :" ;
-
- $MS{"LoadedFilter"} = " filtro caricato :" ;
- $MS{"RemappedKeys"} = " voci rimappate :" ;
- $MS{"WrongFilterPath"} = "percorso filtro errato :" ;
-
- $MS{"Overfull"} = "overfull" ;
- $MS{"Entries"} = "voci" ;
- $MS{"References"} = "riferimenti" ;
-
- $MS{"ExtraProgram"} = " extra program :" ;
- $MS{"PlugInInit"} = " plugin initialized :" ;
- $MS{"PlugInReport"} = " plugin report :" ;
-
- } # end of italian section
-
-else
-
- { # begin of english section
-
- $MS{"ProcessingReferences"} = "processing commands, lists and registers" ;
- $MS{"MergingReferences"} = "merging registers" ;
- $MS{"GeneratingDocumentation"} = "preparing ConTeXt documentation file" ;
- $MS{"GeneratingSources"} = "generating ConTeXt source only file" ;
- $MS{"FilteringDefinitions"} = "filtering formal ConTeXt definitions" ;
- $MS{"CopyingTemplates"} = "copying TeXEdit quick key templates" ;
- $MS{"CopyingInformation"} = "copying TeXEdit help information" ;
- $MS{"GeneratingFigures"} = "generating figure directory file" ;
- $MS{"FilteringLogFile"} = "filtering log file" ;
-
- $MS{"SortingIJ"} = "sorting IJ under Y" ;
- $MS{"ConvertingHigh"} = "converting high ASCII values" ;
- $MS{"ProcessingQuotes"} = "handling accented characters" ;
- $MS{"ForcingFileType"} = "setting up filetype" ;
- $MS{"UsingEps"} = "processing EPS-file" ;
- $MS{"UsingTif"} = "processing TIF-file" ;
- $MS{"UsingPdf"} = "processing PDF-file" ;
- $MS{"UsingPng"} = "processing PNG-file" ;
- $MS{"UsingJpg"} = "processing JPG-file" ;
- $MS{"EpsToPdf"} = "convert EPS to PDF";
- $MS{"EpsPage"} = "setup EPS page";
-
- $MS{"FilteringBoxes"} = "filtering overfull boxes" ;
- $MS{"ApplyingCriterium"} = "applying overfull criterium" ;
- $MS{"FilteringUnknown"} = "filtering unknown ..." ;
-
- $MS{"NoInputFile"} = "no input file given" ;
- $MS{"NoOutputFile"} = "no output file generated" ;
- $MS{"EmptyInputFile"} = "empty input file" ;
- $MS{"NotYetImplemented"} = "not yet available" ;
-
- $MS{"Action"} = " action :" ;
- $MS{"Option"} = " option :" ;
- $MS{"Error"} = " error :" ;
- $MS{"Remark"} = " remark :" ;
- $MS{"SystemCall"} = " system call :" ;
- $MS{"BadSystemCall"} = " bad system call :" ;
- $MS{"MissingSubroutine"} = " missing subroutine :" ;
-
- $MS{"EmbeddedFiles"} = " embedded files :" ;
- $MS{"BeginEndError"} = " b/e error in :" ;
- $MS{"SynonymEntries"} = " synonym entries :" ;
- $MS{"SynonymErrors"} = " bad entries :" ;
- $MS{"RegisterEntries"} = " register entries :" ;
- $MS{"RegisterErrors"} = " bad entries :" ;
- $MS{"PassedCommands"} = " passed commands :" ;
-
- $MS{"MultiPagePdfFile"} = " too many pages :" ;
- $MS{"MissingMediaBox"} = " missing mediabox :" ;
- $MS{"MissingBoundingBox"} = " missing boundingbox :" ;
-
- $MS{"NOfDocuments"} = " document blocks :" ;
- $MS{"NOfDefinitions"} = " definition blocks :" ;
- $MS{"NOfSkips"} = " skipped blocks :" ;
- $MS{"NOfSetups"} = " copied setups :" ;
- $MS{"NOfTemplates"} = " copied templates :" ;
- $MS{"NOfInfos"} = " copied helpinfos :" ;
- $MS{"NOfFigures"} = " processed figures :" ;
- $MS{"NOfBoxes"} = " overfull boxes :" ;
- $MS{"NOfUnknown"} = " unknown ... :" ;
-
- $MS{"InputFile"} = " input file :" ;
- $MS{"OutputFile"} = " output file :" ;
- $MS{"FileType"} = " file type :" ;
- $MS{"EpsFile"} = " eps file :" ;
- $MS{"PdfFile"} = " pdf file :" ;
- $MS{"TifFile"} = " tif file :" ;
- $MS{"PngFile"} = " png file :" ;
- $MS{"JpgFile"} = " jpg file :" ;
- $MS{"MPFile"} = " metapost file :" ;
-
- $MS{"LoadedFilter"} = " loaded filter :" ;
- $MS{"RemappedKeys"} = " remapped keys :" ;
- $MS{"WrongFilterPath"} = " wrong filter path :" ;
-
- $MS{"Overfull"} = "overfull" ;
- $MS{"Entries"} = "entries" ;
- $MS{"References"} = "references" ;
-
- $MS{"ExtraProgram"} = " extra program :" ;
- $MS{"PlugInInit"} = " plugin initialized :" ;
- $MS{"PlugInReport"} = " plugin report :" ;
-
- } # end of english section
-
-#D \stopcompressdefinitions
-
-#D Showing the banner (name and version of the program) and
-#D offering helpinfo is rather straightforward.
-
-sub ShowBanner
- { Report("\n $Program\n") }
-
-sub ShowHelpInfo
- { Report("HelpInfo") }
-
-#D The helpinfo is also saved in the hash table. This looks
-#D like a waste of energy and space, but the program gains
-#D readability.
-
-#D \startcompressdefinitions
-
-if ($UserInterface eq "nl")
-
- { # begin of dutch section
-
- $MS{"HelpInfo"} =
-
-" --references hulp file verwerken / tui->tuo \n" .
-" --ij : IJ als Y sorteren \n" .
-" --high : hoge ASCII waarden converteren \n" .
-" --quotes : quotes converteren \n" .
-" --tcxpath : tcx filter pad \n" .
-" \n" .
-" --purge(all) tijdelijke (klad) files verwijderen \n" .
-" \n" .
-" --documents documentatie file genereren / tex->ted \n" .
-" --sources broncode file genereren / tex->tes \n" .
-" --setups ConTeXt definities filteren / tex->texutil.tus \n" .
-" --templates TeXEdit templates filteren / tex->tud \n" .
-" --infos TeXEdit helpinfo filteren / tex->tud \n" .
-" \n" .
-" --figures eps figuren lijst genereren / *->texutil.tuf \n" .
-" --epspage : voorbereiden voor pdf \n" .
-" --epstopdf : omzetten naar pdf \n" .
-" \n" .
-" --logfile logfile filteren / log->$ProgramLog \n" .
-" --box : overfull boxes controleren \n" .
-" --criterium : overfull criterium in pt \n" .
-" --unknown :onbekende ... controleren \n" ;
-
- } # end of dutch section
-
-elsif ($UserInterface eq "de")
-
- { # begin of german section
-
- $MS{"HelpInfo"} =
-
-" --references Verarbeiten der Hilfsdatei / tui->tuo \n" .
-" --ij : Sortiere IJ als Y \n" .
-" --high : Konvertiere hohe ASCII-Werte \n" .
-" --quotes : Konvertiere akzentuierte Buchstaben \n" .
-" --tcxpath : tcx Filter Path \n" .
-" \n" .
-" --purge(all) entferne temporaere ConTeXt-Dateien \n" .
-" \n" .
-" --documents Erstelle Dokumentationsdatei / tex->ted \n" .
-" --sources Erstelle reine Quelltextdateien / tex->tes \n" .
-" --setups Filtere ConTeXt-Definitionen / tex->texutil.tus\n" .
-" --templates Filtere TeXEdit-templates / tex->tud \n" .
-" --infos Filtere TeXEdit-helpinfo / tex->tud \n" .
-" \n" .
-" --figures Erstelle eps-Abbildungsliste / *->texutil.tuf \n" .
-" --epspage : Bereite fuer pdf vor \n" .
-" --epstopdf : Konvertiere zu pdf \n" .
-" \n" .
-" --logfile Filtere log-Datei / log->$ProgramLog \n" .
-" --box : Ueberpruefe uebervolle Boxen \n" .
-" --criterium : Uebervoll-Kriterium in pt \n" .
-" --unknown : Ueberpruefe auf unbekannte ... \n" ;
-
- } # end of german section
-
-elsif ($UserInterface eq "it")
-
- { # begin of italian section GB: Hans, I need more space!
-
- $MS{"HelpInfo"} =
-
-" --references elabora file ausiliari / tui->tuo \n" .
-" --ij : elenca IJ come Y \n" .
-" --high : converti i valori ASCII alti \n" .
-" --quotes : converti caratteri accentati \n" .
-" --tcxpath : percorso del filtro tcx \n" .
-" \n" .
-" --purge(all) rimuovi i file temporanei ConTeXt \n" .
-" \n" .
-" --documents genera file di documentazione / tex->ted \n" .
-" --sources genera solo sorgente / tex->tes \n" .
-" --setups filtra definizioni ConTeXt / tex->texutil.tus \n" .
-" --templates filtra modelli TeXEdit / tex->tud \n" .
-" --infos filtra helpinfo TeXEdit / tex->tud \n" .
-" \n" .
-" --figures genera lista figure eps / *->texutil.tuf \n" .
-" --epspage : prepara per pdf \n" .
-" --epstopdf : converti in pdf \n" .
-" \n" .
-" --logfile filtra logfile / log->$ProgramLog \n" .
-" --box : controlla overful boxes \n" .
-" --criterium : criterio overfull in pt \n" .
-" --unknown : controlla sconosciuti ... \n" ;
-
- } # end of italian section
-
-else
-
- { # begin of english section
-
- $MS{"HelpInfo"} =
-
-" --references process auxiliary file / tui->tuo \n" .
-" --ij : sort IJ as Y \n" .
-" --high : convert high ASCII values \n" .
-" --quotes : convert quotes characters \n" .
-" --tcxpath : tcx filter path \n" .
-" \n" .
-" --purge(all) clean up temporary context files \n" .
-" \n" .
-" --documents generate documentation file / tex->ted \n" .
-" --sources generate source only file / tex->tes \n" .
-" --setups filter ConTeXt definitions / tex->texutil.tus \n" .
-" --templates filter TeXEdit templates / tex->tud \n" .
-" --infos filter TeXEdit helpinfo / tex->tud \n" .
-" \n" .
-" --figures generate eps figure list / *->texutil.tuf \n" .
-" --epspage : prepare for pdf \n" .
-" --epstopdf : convert to pdf \n" .
-" \n" .
-" --logfile filter logfile / log->$ProgramLog \n" .
-" --box : check overful boxes \n" .
-" --criterium : overfull criterium in pt \n" .
-" --unknown : check unknown ... \n" ;
-
- } # end of english section
-
-#D \stopcompressdefinitions
-
-#D In order to sort strings correctly, we have to sanitize
-#D them. This is especially needed when we include \TEX\
-#D commands, quotes characters and compound word placeholders.
-#D
-#D \startopsomming[opelkaar]
-#D \som \type{\name}: csnames are stripped
-#D \som \type{{}}: are removed
-#D \som \type{\"e}: and alike are translated into \type{"e} etc.
-#D \som \type{"e}: is translated into an \type{e} and \type{b} etc.
-#D \som \type{||}: becomes \type{-}
-#D \som \type{\-}: also becomes \type{-}
-#D \som \type{<*..>}: becomes \type{..} (internal XML entity)
-#D \stopopsomming
-#D
-#D Of course other accented characters are handled too. The
-#D appended string is responsible for decent sorting.
-#D
-#D \startPL
-#D $TargetString = SanitizedString ( $SourceString ) ;
-#D \stopPL
-#D
-#D The sort order depends on the ordering in array
-#D \type{$ASCII}:
-
-$ASCII{"^"} = "a" ; $ASCII{'"'} = "b" ; $ASCII{"`"} = "c" ;
-$ASCII{"'"} = "d" ; $ASCII{"~"} = "e" ; $ASCII{","} = "f" ;
-
-#sub SanitizedString
-# { my ($string) = $_[0] ;
-# if ($ProcessQuotes)
-# { $string =~ s/\\([\^\"\`\'\~\,])/$1/gio ;
-# $copied = $string ;
-# $copied =~ s/([\^\"\`\'\~\,])([a-zA-Z])/$ASCII{$1}/gio ;
-# $string =~ s/([\^\"\`\'\~\,])([a-zA-Z])/$2/gio ;
-# $string=$string.$copied }
-# $string =~ s/\\-|\|\|/\-/gio ;
-# $string =~ s/\\[a-zA-Z]*| |\{|\}//gio ;
-# return $string }
-
-#D YET UNDOCUMENTED
-
-my $SortN = 0 ; my @Filter ;
-
-# copied from texexec
-
-my @paths ;
-my $kpsewhich = '' ;
-my $pathslash = '/' ; if ($0 =~ /\\/) { $pathslash = "\\" }
-
-sub checked_path
- { my $path = shift ;
- if ((defined($path))&&($path ne ''))
- { $path =~ s/[\/\\]/$pathslash/go ;
- $path =~ s/[\/\\]*$//go ;
- $path .= $pathslash }
- else
- { $path = '' }
- return $path }
-
-if ($ENV{PATH} =~ /\;/)
- { @paths = split(/\;/,$ENV{PATH}) }
-else
- { @paths = split(/\:/,$ENV{PATH}) }
-
-# until here.
-
-sub InitializeKeys
- { my $filename = $ARGV[0] ;
- return unless (open(TEX,"$filename.tex")) ;
- for ($i=0;$i<=255;$i++)
- { $Filter[$i] = $i }
- if ($TcXPath eq '')
- { foreach (@paths)
- { my $p = checked_path($_) . 'kpsewhich' ;
- if ((-e $p)||(-e $p . '.exe'))
- { $kpsewhich = $p ; last } } }
- $kpsewhich = "\"$kpsewhich\"" if ($kpsewhich =~ m/^[^\"].* /) ;
- while (<TEX>)
- { chomp ;
- my $Filter ;
- if (/^\%/)
- { if (s/.*translat.*?=([\:\/0-9\-a-z]*)/$1/oi)
- { my $Translation = $_ ;
- if ($TcXPath ne '')
- { $TcXPath = checked_path($TcXPath) ;
- $Filter = "$TcXPath$pathslash$Translation.tcx" }
- elsif ($kpsewhich ne '')
- { $Filter = `$kpsewhich --format="web2c files" $Translation.tcx` ;
- chomp $Filter }
- else
- { last }
- if (open(ASC,$Filter))
- { Report ("LoadedFilter", $Translation) ;
- while (<ASC>)
- { if (/^(\d+)\s*(\d+)/)
- { $Filter[$2] = $1 } }
- close (ASC) }
- elsif ($TcXPath ne '')
- { Report ("WrongFilterPath", $TcXPath) }
- last } }
- else
- { last } }
- close (TEX) }
-
-sub HandleKey
- { ++$SortN ;
- $RestOfLine =~ s/\{(.*)\}/$1/o ;
- my ($lan, $enc, $str, $chr, $map, $alf) = split(/\}\s*\{/, $RestOfLine) ;
- if ($str =~ /^(\d+)/) { $str = ''.chr($Filter[$1]) }
- $map = chr(ord($MAP[$i])+128) ;
- $STR[$SortN] = $str ;
- $CHR[$SortN] = $chr ;
- $MAP[$SortN] = $map ;
-#print "$chr$map = $alf\n" ;
-# $ALF{"$chr$map"} = $alf }
- $ALF{"$map"} = $alf }
-
-sub FlushKeys
- { Report ("RemappedKeys", $SortN) }
-
-sub SanitizedString
- { my $string = my $original = shift ;
- if ($SortN)
- { my $copied = $string ;
- for ($i=1;$i<=$SortN;$i++)
- { my $s = $STR[$i] ;
- my $c = $CHR[$i] ;
- my $m = $MAP[$i] ;
- # print "[$i $s $c $m]\n" ;
- $string =~ s/($s)/$c/ge ;
- $copied =~ s/($s)/$m/ge }
- $string .= "\x00";
- $string .= $copied }
- elsif ($ProcessQuotes)
- { $string =~ s/\\([\^\"\`\'\~\,])/$1/gio ;
- $copied = $string ;
- $copied =~ s/([\^\"\`\'\~\,])([a-zA-Z])/$ASCII{$1}/gi ;
- $string =~ s/([\^\"\`\'\~\,])([a-zA-Z])/$2/gio ;
- $string .= "\x00";
- $string .= $copied }
-# new and very experimental, will change
-$string =~ s/\<\*(.*?)\>/\\$1 /go ; # reduce entities / will be table too
-$string =~ s/\\getXMLentity\s*\{(.*?)\}/$1/gio ; # {tex} => tex
-$string =~ s/\<[a-zA-Z\/].*?\>//go ; # remove elements
-# so far
- $string =~ s/\\-|\|\|/\-/gio ;
- $string =~ s/\\[a-zA-Z]*| |\{|\}//gio ; # ?
-#print "$original $string $copied\n" ;
- return $string }
-
-#D This subroutine looks a bit complicated, which is due to the
-#D fact that we want to sort for instance an accented \type{e}
-#D after the plain \type{e}, so the imaginary words
-#D
-#D \starttypen
-#D eerste
-#D \"eerste
-#D \"e\"erste
-#D eerst\"e
-#D \stoptypen
-#D
-#D come out in an acceptable order.
-
-#D We also have to deal with the typical \TEX\ sequences with
-#D the double \type{^}'s, like \type{^^45}. These hexadecimal
-#D coded characters are just converted.
-#D
-#D \startPL
-#D $TargetString = HighConverted ( $SourceString ) ;
-#D \stopPL
-
-sub HighConverted
- { my ($string) = $_[0] ;
- $string =~ s/\^\^([a-f0-9][a-f0-9])/chr hex($1)/geo ;
- return $string }
-
-#D \extras
-#D {references}
-#D
-#D \CONTEXT\ can handle many lists, registers (indexes),
-#D tables of whatever and references. This data is collected
-#D in one pass and processed in a second one. In between,
-#D relevant data is saved in the file \type{\jobname.tui}.
-#D This file also holds some additional information concerning
-#D second pass optimizations.
-#D
-#D The main task of \TEXUTIL\ is to sort lists and registers
-#D (indexes). The results are stored in again one file called
-#D \type{\jobname.tuo}.
-#D
-#D Just for debugging purposes the nesting of files loaded
-#D during the \CONTEXT\ run is stored. Of course this only
-#D applies to files that are handled by the \CONTEXT\ file
-#D structuring commands (projects, products, components and
-#D environments).
-#D
-#D We have to handle the entries:
-#D
-#D \starttypen
-#D f b {test}
-#D f e {test}
-#D \stoptypen
-#D
-#D and only report some status info at the end of the run.
-
-sub InitializeFiles
- { $NOfFiles = 0 ;
- $NOfBadFiles = 0 }
-
-sub HandleFile
- { $RestOfLine =~ s/.*\{(.*)\}/$1/gio ;
- ++$Files{$RestOfLine} }
-
-sub FlushFiles # hash needs to be sorted, else problem on macosx
- { print TUO "%\n" . "% $Program / Files\n" . "%\n" ;
- foreach $File (sort keys %Files)
- { print TUO "% $File ($Files{$File})\n" }
- print TUO "%\n" ;
- $NOfFiles = keys %Files ;
- Report("EmbeddedFiles", $NOfFiles) ;
- foreach $File (sort keys %Files)
- { unless (($Files{$File} % 2) eq 0)
- { ++$NOfBadFiles ;
- Report("BeginEndError", $File) } } }
-
-#D Commands don't need a special treatment. They are just
-#D copied. Such commands are tagged by a \type{c}, like:
-#D
-#D \starttypen
-#D c \thisisutilityversion{year.month.day}
-#D c \twopassentry{class}{key}{value}
-#D c \mainreference{prefix}{entry}{pagenumber}{realpage}{tag}
-#D c \listentry{category}{tag}{number}{title}{pagenumber}{realpage}
-#D c \initializevariable\realnumberofpages{number}
-#D \stoptypen
-#D
-#D For historic reasons we check for the presense of the
-#D backslash.
-
-my $NOfPositionsFound = 0 ;
-my $TotalNOfPositions = 0 ;
-my $TotalNOfMPgraphics = 0 ;
-
-my $SectionSeparator = ":" ;
-
-sub InitializeCommands
- { print TUO "%\n" . "% $Program / Commands\n" . "%\n" ;
- $NOfCommands = 0 }
-
-sub HandleCommand
- { ++$NOfCommands ;
- $RestOfLine =~ s/^\\//go ;
- if ($RestOfLine =~ /^pospxy/o)
- { ++$NOfPositionsFound }
- elsif ($RestOfLine =~ /^initializevariable\\totalnofpositions\{(.*)\}/o)
- { $TotalNOfPositions = $1 }
- elsif ($RestOfLine =~ /^initializevariable\\totalnofMPgraphics\{(.*)\}/o)
- { $TotalNOfMPgraphics = $1 }
-# todo: reg how to
-# elsif ($RestOfLine =~ /^thisissectionseparator\{(.*)\}/o)
-# { $SectionSeparator = $1 }
- elsif ($RestOfLine =~ /^thisisbytesequence\{(.*)\}/o)
- { $RestOfLine =~ s/\^//go }
- print TUO "\\$RestOfLine\n" }
-
-sub FlushCommands
- { Report ("PassedCommands", $NOfCommands) }
-
-#D Experimental: Extra
-#D
-#D s p : extra programs
-
-my @ExtraPrograms = () ;
-
-sub InitializeExtra
- { }
-
-sub HandleExtra
- { if ($RestOfLine =~ /(.)\s+(.*)\s*$/o)
- { if ($1 eq "p")
- { my $str = $2 ; $str =~ s/^\{(.*)\}$/$1/o ;
- push @ExtraPrograms,$str } } }
-
-sub FlushExtra
- { print TUO "%\n" . "% $Program / System\n" . "%\n" ;
- foreach $EP (@ExtraPrograms)
- { print TUO "% extra program : $EP\n" } }
-
-sub RunExtraPrograms
- { foreach $EP (@ExtraPrograms)
- { Report ("ExtraProgram", $EP) ;
- system($EP) } }
-
-#D Plugins
-#D
-#D test.pm:
-#D
-#D \starttypen
-#D see plugtest.pm
-#D \stoptypen
-#D
-#D utility format:
-#D
-#D \starttypen
-#D p u {name} {data} {data} ...
-#D \stoptypen
-
-my $pm_path ;
-
-BEGIN
- { ## $pm_path = `kpsewhich --format="other text files" --progname=context texutil.pl` ;
- ## chomp($pm_path) ;
- # $pm_path =~ s/texutil\.pl.*// }
- # $pm_path = $0 ;
- # $pm_path =~ s/\\/\//o ;
- # $pm_path =~ s/texutil\.pl.*//io ;
- ## $pm_path =~ s/(.*)texutil.*?$/$1/i ;
- $pm_path = "$FindBin::Bin/" ;
- if ($pm_path eq "") { $pm_path = "./" } }
-
-use lib $pm_path ;
-
-my %UserPlugIns ;
-
-sub HandlePlugIn
- { if ($RestOfLine =~ /\s*u\s*\{(.*?)\}\s*(.*)\s*/io)
- { my $tag = $1 ;
- my $arg = $2 ;
- if (! defined($UserPlugIns{$tag}))
- { $UserPlugIns{$tag} = 1 ;
- eval("use $tag") ;
- my $result = $tag->identify ;
- if ($result ne "")
- { Report ("PlugInInit", "$tag -> $result") }
- else
- { Report ("PlugInInit", $tag ) }
- $tag->initialize() }
- if (defined($UserPlugIns{$tag}))
- { $arg =~ s/\{(.*)\}/$1/o ;
- my @args = split(/\}\s*\{/o, $arg) ;
- $tag->handle(@args) } } }
-
-sub FlushPlugIns
- { foreach my $tag (keys %UserPlugIns)
- { my @report = $tag->report ;
- foreach $rep (@report)
- { my ($key,$val) = split (/\s*\:\s*/,$rep) ;
- if ($val ne "")
- { Report ("PlugInReport", "$tag -> $key -> $val") }
- else
- { Report ("PlugInReport", "$tag -> $key") } }
- $tag->process ;
- print TUO "%\n" . "% $Program / " . $tag->identify . "\n" . "%\n" ;
- foreach my $str ($tag->results)
- { print TUO "\\plugincommand\{$str\}\n" } } }
-
-#D Synonyms are a sort of key||value pairs and are used for
-#D ordered lists like abbreviations and units.
-#D
-#D \starttypen
-#D s e {class}{sanitized key}{key}{associated data}
-#D \stoptypen
-#D
-#D The sorted lists are saved as (surprise):
-#D
-#D \starttypen
-#D \synonymentry{class}{sanitized key}{key}{associated data}
-#D \stoptypen
-
-sub InitializeSynonyms
- { $NOfSynonyms = 0 ;
- $NOfBadSynonyms = 0 }
-
-#M \definieersynoniem [testname] [testnames] [\testmeaning]
-#M
-#M \stelsynoniemenin [testname] [criterium=alles]
-
-#D Let's first make clear what we can expect. Synonym
-#D entries look like:
-#D
-#D \startbuffer
-#D \testname [alpha] {\sl alpha} {a greek letter a}
-#D \testname {alpha} {another a}
-#D \testname [Beta] {\kap{beta}} {a greek letter b}
-#D \testname {beta} {indeed another b}
-#D \testname {gamma} {something alike g}
-#D \testname {delta} {just a greek d}
-#D \stopbuffer
-#D
-#D \typebuffer
-#D
-#D This not that spectacular list is to be sorted according
-#D to the keys (names). \haalbuffer
-
-sub HandleSynonym
- { ++$NOfSynonyms ;
- ($SecondTag, $RestOfLine) = split(/ /, $RestOfLine, 2) ;
- ($Class, $Key, $Entry, $Meaning) = split(/} \{/, $RestOfLine) ;
- chop $Meaning ;
- $Class = substr $Class, 1 ;
- if ($Entry eq "")
- { ++$NOfBadSynonyms }
- else
- { $SynonymEntry[$NOfSynonyms] =
- join ($JOIN,$Class,$Key,$Entry,$Meaning) } }
-
-#D Depending on the settings\voetnoot{One can call for
-#D all defined entries, call only the used ones, change
-#D layout, attach (funny) commands etc.} a list of
-#D {\em testnames} looks like:
-#D
-#D \plaatslijstmettestnames
-#D
-#D Watch the order in which these entries are sorted.
-
-sub FlushSynonyms
- { print TUO "%\n" . "% $Program / Synonyms\n" . "%\n" ;
- @SynonymEntry = sort { lc($a) cmp lc($b) } @SynonymEntry ;
- $NOfSaneSynonyms = 0 ;
- for ($n=1; $n<=$NOfSynonyms; $n++)
- { # check normally not needed
- if (($n==1)||($SynonymEntry[$n] ne $SynonymEntry[$n-1]))
- { ($Class, $Key, $Entry, $Meaning) =
- split(/$JOIN/, $SynonymEntry[$n]) ;
- ++$NOfSaneSynonyms ;
- print TUO "\\synonymentry{$Class}{$Key}{$Entry}{$Meaning}\n" } }
- Report("SynonymEntries", $NOfSynonyms, "->", $NOfSaneSynonyms, "Entries") ;
- if ($NOfBadSynonyms>0)
- { Report("SynonymErrors", $NOfBadSynonyms) } }
-
-#D Register entries need a bit more care, especially when they
-#D are nested. In the near future we will also handle page
-#D ranges.
-#D
-#D \starttypen
-#D r e {class}{tag}{sanitized key}{key}{pagenumber}{realpage}
-#D r s {class}{tag}{sanitized key}{key}{string}{pagenumber}
-#D r r {class}{tag}{sanitized key}{key}{string}{pagenumber}
-#D \stoptypen
-#D
-#D The last one indicates the start of a range.
-
-#D The first one is the normal entry, the second one concerns
-#D {\em see this or that} entries. Keys are sanitized, unless
-#D the user supplies a sanitized key. To save a lot of
-#D programming, all data concerning an entry is stored in one
-#D string. Subentries are specified as:
-#D
-#D \starttypen
-#D first&second&third
-#D first+second+third
-#D \stoptypen
-#D
-#D When these characters are needed for typesetting purposes, we
-#D can also use the first character to specify the separator:
-#D
-#D \starttypen
-#D &$x^2+y^2=r^2$
-#D +this \& that
-#D \stoptypen
-#D
-#D Subentries are first unpacked and next stored in a
-#D consistent way, which means that we can use both separators
-#D alongside each other. We leave it to the reader to sort
-#D out the dirty tricks.
-
-$SPLIT ="%%" ;
-$JOIN ="__" ;
-
-sub InitializeRegisters
- { $NOfEntries = 0 ;
- $NOfBadEntries = 0 }
-
-$ProcessType = "" ;
-
-$RegStat{"f"} = 1 ;
-$RegStat{"e"} = 2 ; # end up between from and to
-$RegStat{"t"} = 3 ;
-$RegStat{"s"} = 4 ;
-
-my $RegSep = "$SectionSeparator$SectionSeparator" ;
-
-sub HandleRegister # the } { makes sure that local {} is ok
- { ($SecondTag, $RestOfLine) = split(/ /, $RestOfLine, 2) ;
- ++$NOfEntries ;
- #~ if ($SecondTag eq "s")
- #~ { ($Class, $Location, $Key, $Entry, $SeeToo, $Page ) =
- #~ split(/} \{/, $RestOfLine) ;
- #~ chop $Page ;
- #~ $Class = substr $Class, 1 ;
- #~ $RealPage = 0 }
- #~ else
- #~ { ($Class, $Location, $Key, $Entry, $Page, $RealPage ) =
- #~ split(/} \{/, $RestOfLine) ;
- #~ chop $RealPage ;
- #~ $Class = substr $Class, 1 ;
- #~ $SeeToo = "" }
- if ($SecondTag eq "s")
- { if ($RestOfLine =~ /^\s*(.*?)\}\s\{(.*?)\}\s\{(.*?)\}\s\{(.*)\}\s\{(.*?)\}\s\{(.*?)\s*$/o)
- { ($Class, $Location, $Key, $Entry, $SeeToo, $Page ) = ($1,$2,$3,$4,$5,$6) ;
- chop $Page ;
- $Class = substr $Class, 1 ;
- $RealPage = 0 }
- else
- { return } }
- else
- { if ($RestOfLine =~ /^\s*(.*?)\}\s\{(.*?)\}\s\{(.*?)\}\s\{(.*)\}\s\{(.*?)\}\s\{(.*?)\s*$/o)
- { ($Class, $Location, $Key, $Entry, $Page, $RealPage ) = ($1,$2,$3,$4,$5,$6) ;
- chop $RealPage ;
- $Class = substr $Class, 1 ;
- $SeeToo = "" }
- else
- { return } }
- $_ = $Key ;
- if (/$RegSep/)
- { ($PageHow,$Key) = split (/$RegSep/) }
- else
- { $PageHow = "" }
- $_ = $Entry ;
- if (/$RegSep/)
- { ($TextHow,$Entry) = split (/$RegSep/) }
- else
- { $TextHow = "" }
- #
- if ($Key eq "")
- { $Key = SanitizedString($Entry) }
- # if ($SortMethod ne '')
- # { $ProcessHigh = 0 }
- if ($ProcessHigh)
- { $Key = HighConverted($Key) }
- $KeyTag = substr $Key, 0, 1 ;
- if ($KeyTag eq "&")
- { $Key =~ s/^\&//go ;
- $Key =~ s/([^\\])\&/$1$SPLIT/go }
- elsif ($KeyTag eq "+")
- { $Key =~ s/^\+//go ;
- $Key =~ s/([^\\])\+/$1$SPLIT/go }
- else
- { $Key =~ s/([^\\])\&/$1$SPLIT/go ;
- $Key =~ s/([^\\])\+/$1$SPLIT/go }
- $Key .= " " ; # so, "Word" comes for "Word Another Word"
- $EntryTag = substr $Entry, 0, 1 ;
- if ($EntryTag eq "&")
- { $Entry =~ s/^\&//go ;
- $Entry =~ s/([^\\])\&/$1$SPLIT/go }
- elsif ($EntryTag eq "+")
- { $Entry =~ s/^\+//go ;
- $Entry =~ s/([^\\])\+/$1$SPLIT/go }
- elsif ($KeyTag eq "&")
- { $Entry =~ s/([^\\])\&/$1$SPLIT/go }
- elsif ($KeyTag eq "+")
- { $Entry =~ s/([^\\])\+/$1$SPLIT/go }
- else
- { $Entry =~ s/([^\\])\&/$1$SPLIT/go ;
- $Entry =~ s/([^\\])\+/$1$SPLIT/go }
- $Key =~ s/^([^a-zA-Z])/ $1/go ;
- $Key =~ s/^\s*\{(.*)\}$SPLIT/$1$SPLIT/go ; ####### new
- $Entry =~ s/^\{(.*)\}$SPLIT/$1$SPLIT/go ; ###### new
- if ($ProcessIJ) { $Key =~ s/ij/yy/go }
- $LCKey = lc $Key ;
- $RegStatus = $RegStat{$SecondTag} ;
- $RealPageNumber= sprintf("%6i",$RealPage) ;
- $RegisterEntry[$NOfEntries] =
- join($JOIN,$Class,$LCKey,$Key,$Entry,$TextHow,$RegStatus,
- $RealPageNumber,$Location,$Page,$PageHow,$SeeToo) }
-
-#M \definieerregister [testentry] [testentries]
-
-#D The previous routine deals with entries like:
-#D
-#D \startbuffer
-#D \testentry {alpha}
-#D \testentry {beta}
-#D \testentry {gamma}
-#D \testentry {gamma}
-#D \testentry {delta}
-#D \testentry {epsilon}
-#D \testentry {alpha+first}
-#D \testentry {alpha+second}
-#D \testentry {alpha+second}
-#D \testentry {alpha+third}
-#D \testentry {alpha+second+one}
-#D \testentry {alpha+second+one}
-#D \testentry {alpha+second+two}
-#D \testentry {alpha+second+three}
-#D \testentry {gamma+first+one}
-#D \testentry {gamma+second}
-#D \testentry {gamma+second+one}
-#D
-#D \testentry {alpha+fourth}
-#D \testentry {&alpha&fourth}
-#D \testentry {+alpha+fourth}
-#D
-#D \testentry [alpha+fourth] {alpha+fourth}
-#D \testentry [&alpha&fourth&one] {&alpha&fourth&one}
-#D \testentry [+alpha+fourth+two] {&alpha&fourth&two}
-#D
-#D \testentry {\kap{alpha}+fifth}
-#D \testentry {\kap{alpha}+f\'ifth}
-#D \testentry {\kap{alpha}+f"ifth}
-#D
-#D \testentry [&betaformula] {&$a^2+b^2=c^2$}
-#D
-#D \testentry {zeta \& more}
-#D
-#D \testentry [pagehowto::key]{texthowto::entry}
-#D
-#D % a very special case, when key has , and is constructed
-#D
-#D \testentry [pagehowto::{key}]{texthowto::{entry}}
-#D
-#D \stopbuffer
-#D
-#D \typebuffer
-#D
-#D \haalbuffer After being sorted, these entries are
-#D turned into something \TEX\ using:
-
-$CollapseEntries = 0 ;
-
-$RegisterEntry[0] = ("") ;
-
-sub How
- { return "$TextHow$RegSep" . "$_[0]" }
-
-sub FlushSavedLine
- { if (($CollapseEntries)&&($SavedFrom ne ""))
- { if ($SavedTo ne "")
- { print TUO "\\registerfrom$SavedFrom" ;
- print TUO "\\registerto$SavedTo" }
- else
- { print TUO "\\registerpage$SavedFrom" } }
- $SavedHow = "" ;
- $SavedFrom = "" ;
- $SavedTo = "" ;
- $SavedEntry = "" }
-
-sub FlushRegisters
- { print TUO "%\n" . "% $Program / Registers\n" . "%\n" ;
- @RegisterEntry = sort { lc($a) cmp lc($b) } @RegisterEntry ;
- $NOfSaneEntries = 0 ;
- $NOfSanePages = 0 ;
- $LastPage = "" ;
- $LastRealPage = "" ;
- $AlfaClass = "" ;
- $Alfa = "" ;
- $PreviousA = "" ;
- $PreviousB = "" ;
- $PreviousC = "" ;
- $ActualA = "" ;
- $ActualB = "" ;
- $ActualC = "" ;
-
- $SavedFrom = "" ;
- $SavedTo = "" ;
- $SavedEntry = "" ;
- $SavedHow = "" ;
-
- for ($n=1 ; $n<=$NOfEntries ; ++$n)
- { ($Class, $LCKey, $Key, $Entry, $TextHow, $RegisterState,
- $RealPage, $Location, $Page, $PageHow, $SeeToo) =
- split(/$JOIN/, $RegisterEntry[$n]) ;
- $RealPage =~ s/^\s*//o ;
- $TestAlfa = lc substr $Key, 0, 1 ;
- #
- if ($SortN)
- { $AlfKey = $Key ;
- $AlfKey =~ s/(.).*\x00(.).*/$2/o ;
- if (defined($ALF{$AlfKey}))
- { $TestAlfa = $ALF{$AlfKey} } }
- #
- if ((lc $TestAlfa ne lc $Alfa) or ($AlfaClass ne $Class))
- { # $Alfa= lc substr $Key, 0, 1 ;
- $Alfa = $TestAlfa ;
- $AlfaClass = $Class ;
- if ($Alfa ne " ")
- { FlushSavedLine ;
- print TUO "\\registerentry{$Class}{$Alfa}\n" } }
- ($ActualA, $ActualB, $ActualC ) =
- split(/$SPLIT/, $Entry, 3) ;
- unless ($ActualA) { $ActualA = "" }
- unless ($ActualB) { $ActualB = "" }
- unless ($ActualC) { $ActualC = "" }
- if (How($ActualA) eq $PreviousA)
- { $ActualA = "" }
- else
- { $PreviousA = How($ActualA) ;
- $PreviousB = "" ;
- $PreviousC = "" }
- if (How($ActualB) eq $PreviousB)
- { $ActualB = "" }
- else
- { $PreviousB = How($ActualB) ;
- $PreviousC = "" }
- if (How($ActualC) eq $PreviousC)
- { $ActualC = "" }
- else
- { $PreviousC = How($ActualC) }
- $Copied = 0 ;
- if ($ActualA ne "")
- { FlushSavedLine ;
- print TUO "\\registerentrya{$Class}{$ActualA}\n" ;
- $Copied = 1 }
- if ($ActualB ne "")
- { FlushSavedLine ;
- print TUO "\\registerentryb{$Class}{$ActualB}\n" ;
- $Copied = 1 }
- if ($ActualC ne "")
- { FlushSavedLine ;
- print TUO "\\registerentryc{$Class}{$ActualC}\n" ;
- $Copied = 1 }
- if ($Copied)
- { $NOfSaneEntries++ }
- if ($RealPage eq 0)
- { FlushSavedLine ;
- print TUO "\\registersee{$Class}{$PageHow,$TextHow}{$SeeToo}{$Page}\n" ;
- $LastPage = $Page ;
- $LastRealPage = $RealPage }
- else {
-if (($SavedHow ne $PageHow) && ($PageHow ne "")) {
- # last valid page attribute counts
- $SavedHow = $PageHow ;
-}
- if (($Copied) || ! (($LastPage eq $Page) && ($LastRealPage eq $RealPage)))
- { # print "$LastPage / $Page // $LastRealPage / $RealPage\n" ;
- $NextEntry = "{$Class}{$PreviousA}{$PreviousB}{$PreviousC}{$PageHow,$TextHow}" ;
- #~ $SavedLine = "{$Class}{$PageHow,$TextHow}{$Location}{$Page}{$RealPage}\n" ;
- $SavedLine = "{$Class}{$SavedHow,$TextHow}{$Location}{$Page}{$RealPage}\n" ;
- if ($RegisterState eq $RegStat{"f"})
- { FlushSavedLine ;
- print TUO "\\registerfrom$SavedLine" }
- elsif ($RegisterState eq $RegStat{"t"})
- { FlushSavedLine ;
- print TUO "\\registerto$SavedLine" ; $SavedHow = '' ; }
- else
- { if ($CollapseEntries)
- { if ($SavedEntry ne $NextEntry)
- { $SavedFrom = $SavedLine }
- else
- { $SavedTo = $SavedLine }
- $SavedEntry = $NextEntry }
- else
- { print TUO "\\registerpage$SavedLine" ; $SavedHow = '' ; }
- }
- ++$NOfSanePages ;
- $LastPage = $Page ;
- $LastRealPage = $RealPage } } }
-
-FlushSavedLine ;
-
- Report("RegisterEntries", $NOfEntries, "->", $NOfSaneEntries, "Entries",
- $NOfSanePages, "References") ;
- if ($NOfBadEntries>0)
- { Report("RegisterErrors", $NOfBadEntries) } }
-
-#D As promised, we show the results:
-#D
-#D \plaatstestentry
-
-#D For debugging purposes we flush some status information. The
-#D faster machines become, the more important this section will
-#D be. The totals, when changed, force texexec to do a second pass.
-
-sub FlushData
- { print TUO "%\n" . "% $Program / Status\n" . "%\n" ;
- print TUO "" .
- "% embedded files : $NOfFiles ($NOfBadFiles errors)\n" .
- "% synonym entries : $NOfSynonyms ($NOfBadSynonyms errors)\n" .
- "% register entries : $NOfEntries ($NOfBadEntries errors)\n" .
- "% metapost graphics : $TotalNOfMPgraphics\n" .
- "% position commands : $TotalNOfPositions " ;
- if ($TotalNOfPositions)
- { if ($NOfPositionsFound)
- { print TUO "(resolved)\n" }
- else
- { print TUO "(unresolved)\n" } }
- else
- { print TUO "(not used)\n" } }
-
-#D The functionallity described on the previous few pages is
-#D called upon in the main routine:
-
-sub NormalHandleReferences
- { if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { unless (open (TUI, "$InputFile.tui"))
- { Report("Error", "EmptyInputFile", $InputFile) }
- else
- { Report("InputFile", "$InputFile.tui" ) ;
- unlink "$InputFile.tmp" ;
- rename "$InputFile.tuo", "$InputFile.tmp" ;
- Report("OutputFile", "$InputFile.tuo" ) ;
- open (TUO, ">$InputFile.tuo") ;
- print TUO "%\n" . "% $Program / Commands\n" . "%\n" ;
- while (<TUI>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- ($FirstTag, $RestOfLine) = split ' ', $SomeLine, 2 ;
- if ($FirstTag eq "c")
- { HandleCommand }
- elsif ($FirstTag eq "s")
- { HandleSynonym }
- elsif ($FirstTag eq "r")
- { HandleRegister }
- elsif ($FirstTag eq "f")
- { HandleFile }
- elsif ($FirstTag eq "k")
- { HandleKey }
- elsif ($FirstTag eq "e")
- { HandleExtra }
- elsif ($FirstTag eq "p")
- { HandlePlugIn }
- elsif ($FirstTag eq "q")
- { $ValidOutput = 0 ;
- last } }
- if ($ValidOutput)
- { FlushCommands ; # already done during pass
- FlushKeys ;
- FlushRegisters ;
- FlushSynonyms ;
- FlushPlugIns ;
- FlushFiles ;
- FlushData ;
- FlushExtra ;
- close (TUO) ;
- RunExtraPrograms }
- else
- { close (TUO) ;
- unlink "$InputFile.tuo" ;
- rename "$InputFile.tmp", "$InputFile.tuo" ;
- Report ("Remark", "NoOutputFile") } } } }
-
-my $Suffix ;
-
-sub MergerHandleReferences
- { unlink "texutil.tuo" ;
- Report("OutputFile", "texutil.tuo" ) ;
- open (TUO, ">texutil.tuo") ;
- foreach $InputFile (@ARGV)
- { ($InputFile, $Suffix) = split (/\./, $InputFile, 2) ;
- unless (open (TUI, "$InputFile.tui"))
- { Report("Error", "EmptyInputFile", $InputFile) }
- else
- { Report("InputFile", "$InputFile.tui" ) ;
- while (<TUI>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- ($FirstTag, $RestOfLine) = split ' ', $SomeLine, 2 ;
- if ($FirstTag eq "r")
- { HandleRegister } } } }
- if ($ValidOutput)
- { FlushRegisters ;
- close (TUO) }
- else
- { close (TUO) ;
- unlink "texutil.tuo" ;
- Report ("Remark", "NoOutputFile") } }
-
-# sub HandleReferences
-# { Report("Action", "ProcessingReferences") ;
-# if ($ProcessIJ )
-# { Report("Option", "SortingIJ") }
-# if ($ProcessHigh)
-# { Report("Option", "ConvertingHigh") }
-# if ($ProcessQuotes)
-# { Report("Option", "ProcessingQuotes") }
-# if ($InputFile eq "")
-# { Report("Error", "NoInputFile") }
-# else
-# { unless (open (TUI, "$InputFile.tui"))
-# { Report("Error", "EmptyInputFile", $InputFile) }
-# else
-# { Report("InputFile", "$InputFile.tui" ) ;
-# InitializeCommands ;
-# InitializeRegisters ;
-# InitializeSynonyms ;
-# InitializeFiles ;
-# $ValidOutput = 1 ;
-# unlink "$InputFile.tmp" ;
-# rename "$InputFile.tuo", "$InputFile.tmp" ;
-# Report("OutputFile", "$InputFile.tuo" ) ;
-# open (TUO, ">$InputFile.tuo") ;
-# while (<TUI>)
-# { $SomeLine = $_ ;
-# chomp $SomeLine ;
-# ($FirstTag, $RestOfLine) = split ' ', $SomeLine, 2 ;
-# if ($FirstTag eq "c")
-# { HandleCommand }
-# elsif ($FirstTag eq "s")
-# { HandleSynonym }
-# elsif ($FirstTag eq "r")
-# { HandleRegister }
-# elsif ($FirstTag eq "f")
-# { HandleFile }
-# elsif ($FirstTag eq "q")
-# { $ValidOutput = 0 ;
-# last } }
-# if ($ValidOutput)
-# { FlushCommands ; # already done during pass
-# FlushRegisters ;
-# FlushSynonyms ;
-# FlushFiles ;
-# FlushData ;
-# close (TUO) }
-# else
-# { close (TUO) ;
-# unlink "$InputFile.tuo" ;
-# rename "$InputFile.tmp", "$InputFile.tuo" ;
-# Report ("Remark", "NoOutputFile") } } } }
-
-sub HandleReferences
- { $Merging = @ARGV ;
- $Merging = ($Merging>1) ;
- if ($Merging)
- { Report("Action", "MergingReferences") }
- else
- { Report("Action", "ProcessingReferences") }
- if ($ProcessIJ )
- { Report("Option", "SortingIJ") }
- if ($ProcessHigh)
- { Report("Option", "ConvertingHigh") }
- if ($ProcessQuotes)
- { Report("Option", "ProcessingQuotes") }
- InitializeKeys ;
- InitializeCommands ;
- InitializeExtra ;
- InitializeRegisters ;
- InitializeSynonyms ;
- InitializeFiles ;
- $ValidOutput = 1 ;
- if ($Merging)
- { MergerHandleReferences }
- else
- { NormalHandleReferences } }
-
-# moved to ctxtools
-#
-# sub HandleDocuments
-# { my $files = @ARGV.join(' ') ; system("ctxtools $files") }
-
-#D \extras
-#D {documents}
-#D
-#D Documentation can be woven into a source file. The next
-#D routine generates a new, \TEX\ ready file with the
-#D documentation and source fragments properly tagged. The
-#D documentation is included as comment:
-#D
-#D \starttypen
-#D %D ...... some kind of documentation
-#D %M ...... macros needed for documenation
-#D %S B begin skipping
-#D %S E end skipping
-#D \stoptypen
-#D
-#D The most important tag is \type{%D}. Both \TEX\ and
-#D \METAPOST\ files use \type{%} as a comment chacacter, while
-#D \PERL\ uses \type{#}. Therefore \type{#D} is also handled.
-#D
-#D The generated file gets the suffix \type{ted} and is
-#D structured as:
-#D
-#D \starttypen
-#D \startmodule[type=suffix]
-#D \startdocumentation
-#D \stopdocumentation
-#D \startdefinition
-#D \stopdefinition
-#D \stopmodule
-#D \stoptypen
-#D
-#D Macro definitions specific to the documentation are not
-#D surrounded by start||stop commands. The suffix specifaction
-#D can be overruled at runtime, but defaults to the file
-#D extension. This specification can be used for language
-#D depended verbatim typesetting.
-
-my $skippingbang = 0 ;
-
-sub HandleDocuments
- { Report("Action", "GeneratingDocumentation") ;
- if ($ProcessType ne "")
- { Report("Option", "ForcingFileType", $ProcessType) }
- if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { CheckInputFiles ($InputFile) ;
- foreach $FullName (@UserSuppliedFiles)
- { ($FileName, $FileSuffix) = SplitFileName ($FullName) ;
- unless ($FileSuffix)
- { $FileSuffix = "tex" }
- unless (-f "$FileName.$FileSuffix")
- { next }
- unless (open (TEX, "$FileName.$FileSuffix"))
- { Report("Error", "EmptyInputFile", "$FileName.$FileSuffix" ) }
- else
- { Report("InputFile", "$FileName.$FileSuffix") ;
- Report("OutputFile", "$FileName.ted") ;
- open (TED, ">$FileName.ted") ;
- $NOfDocuments = 0 ;
- $NOfDefinitions = 0 ;
- $NOfSkips = 0 ;
- $SkipLevel = 0 ;
- $InDocument = 0 ;
- $InDefinition = 0 ;
- $skippingbang = 0 ;
- if ($ProcessType eq "")
- { $FileType=lc $FileSuffix }
- else
- { $FileType=lc $ProcessType }
- Report("FileType", $FileType) ;
- # we need to signal to texexec what interfaec to use
- my $firstline = <TEX> ;
- if ($firstline =~ /^\%.*interface\=/)
- { print TED $firstline }
- else
- { seek TEX, 0, 0 }
- # so far
- print TED "\\startmodule[type=$FileType]\n" ;
- while (<TEX>)
- { chomp ;
- s/\s*$//o ;
- if ($skippingbang)
- { $skippingbang = 0 }
- elsif (/^[%\#]D/)
- { if ($SkipLevel == 0)
- { if (length $_ < 3)
- {$SomeLine = "" }
- else # HH: added after that
- {$SomeLine = substr $_, 3 }
- if ($InDocument)
- { print TED "$SomeLine\n" }
- else
- { if ($InDefinition)
- { print TED "\\stopdefinition\n" ;
- $InDefinition = 0 }
- unless ($InDocument)
- { print TED "\n\\startdocumentation\n" }
- print TED "$SomeLine\n" ;
- $InDocument = 1 ;
- ++$NOfDocuments } } }
- elsif (/^[%\#]M/)
- { if ($SkipLevel == 0)
- { $SomeLine = substr $_, 3 ;
- print TED "$SomeLine\n" } }
- elsif (/^[%\%]S B]/)
- { ++$SkipLevel ;
- ++$NOfSkips }
- elsif (/^[%\%]S E]/)
- { --$SkipLevel }
- elsif (/^[%\#]/)
- { }
- elsif (/^eval \'\(exit \$\?0\)\' \&\& eval \'exec perl/o)
- { $skippingbang = 1 }
- elsif ($SkipLevel == 0)
- { $InLocalDocument = $InDocument ;
- $SomeLine = $_ ;
- if ($InDocument)
- { print TED "\\stopdocumentation\n" ;
- $InDocument = 0 }
- if (($SomeLine eq "") && ($InDefinition))
- { print TED "\\stopdefinition\n" ;
- $InDefinition = 0 }
- else
- { if ($InDefinition)
- { print TED "$SomeLine\n" }
- elsif ($SomeLine ne "")
- { print TED "\n" . "\\startdefinition\n" ;
- $InDefinition = 1 ;
- unless ($InLocalDocument)
- { ++$NOfDefinitions }
- print TED "$SomeLine\n" } } } }
- if ($InDocument)
- { print TED "\\stopdocumentation\n" }
- if ($InDefinition)
- { print TED "\\stopdefinition\n" }
- print TED "\\stopmodule\n" ;
- close (TED) ;
- unless (($NOfDocuments) || ($NOfDefinitions))
- { unlink "$FileName.ted" }
- Report ("NOfDocuments", $NOfDocuments) ;
- Report ("NOfDefinitions", $NOfDefinitions) ;
- Report ("NOfSkips", $NOfSkips) } } } }
-
-#D \extras
-#D {sources}
-#D
-#D Documented sources can be stripped of documentation and
-#D comments, although at the current processing speeds the
-#D overhead of skipping the documentation at run time is
-#D neglectable. Only lines beginning with a \type{%} are
-#D stripped. The stripped files gets the suffix \type{tes}.
-
-sub HandleSources
- { Report("Action", "GeneratingSources") ;
- if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { CheckInputFiles ($InputFile) ;
- foreach $FullName (@UserSuppliedFiles)
- { ($FileName, $FileSuffix) = SplitFileName ($FullName) ;
- unless ($FileSuffix)
- { $FileSuffix = "tex" }
- unless (-f "$FileName.$FileSuffix")
- { next }
- unless (open (TEX, "$FileName.$FileSuffix"))
- { Report("Error", "EmptyInputFile", "$FileName.$FileSuffix" ) }
- else
- { Report("InputFile", "$FileName.$FileSuffix") ;
- Report("OutputFile", "$FileName.tes") ;
- open (TES, ">$FileName.tes") ;
- $EmptyLineDone = 1 ;
- $FirstCommentDone = 0 ;
- while (<TEX>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- if ($SomeLine eq "")
- { unless ($FirstCommentDone)
- { $FirstCommentDone = 1 ;
- print TES
- "\n% further documentation is removed\n\n" ;
- $EmptyLineDone = 1 }
- unless ($EmptyLineDone)
- { print TES "\n" ;
- $EmptyLineDone = 1 } }
- elsif ($SomeLine =~ /^%/)
- { unless ($FirstCommentDone)
- { print TES "$SomeLine\n" ;
- $EmptyLineDone = 0 } }
- else
- { print TES "$SomeLine\n" ;
- $EmptyLineDone = 0 } }
- close (TES) ;
- unless ($FirstCommentDone)
- { unlink "$FileName.tes" } } } } }
-
-#D \extras
-#D {setups}
-#D
-#D All \CONTEXT\ commands are specified in a compact format
-#D that can be used to generate quick reference tables and
-#D cards. Such setups are preceded by \type{%S}. The setups
-#D are collected in the file \type{texutil.tus}.
-
-sub HandleSetups
- { Report("Action", "FilteringDefinitions" ) ;
- if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { SetOutputFile ("texutil.tus" ) ;
- Report("OutputFile", $OutputFile) ;
- open (TUS, ">$OutputFile") ; # always reset!
- $NOfSetups = 0 ;
- CheckInputFiles ($InputFile) ;
- foreach $FullName (@UserSuppliedFiles)
- { ($FileName, $FileSuffix) = SplitFileName ($FullName) ;
- unless ($FileSuffix)
- { $FileSuffix = "tex" }
- unless (-f "$FileName.$FileSuffix")
- { next }
- unless (open (TEX, "$FileName.$FileSuffix"))
- { Report("Error", "EmptyInputFile", "$FileName.$FileSuffix" ) }
- else
- { Report("InputFile", "$FileName.$FileSuffix") ;
- print TUS "%\n" . "% File : $FileName.$FileSuffix\n" . "%\n" ;
- while (<TEX>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- ($Tag, $RestOfLine) = split(/ /, $SomeLine, 2) ;
- if ($Tag eq "%S")
- { ++$NOfSetups ;
- while ($Tag eq "%S")
- { print TUS "$RestOfLine\n" ;
- $SomeLine = <TEX> ;
- chomp $SomeLine ;
- ($Tag, $RestOfLine) = split(/ /, $SomeLine, 2) }
- print TUS "\n" } } } }
- close (TUS) ;
- unless ($NOfSetups)
- { unlink $OutputFile }
- Report("NOfSetups", $NOfSetups) } }
-
-#D \extras
-#D {templates, infos}
-#D
-#D From the beginning, the \CONTEXT\ source files contained
-#D helpinfo and key||templates for \TEXEDIT. In fact, for a
-#D long time, this was the only documentation present. More
-#D and more typeset (interactive) documentation is replacing
-#D this helpinfo, but we still support the traditional method.
-#D This information is formatted like:
-#D
-#D \starttypen
-#D %I n=Struts
-#D %I c=\strut,\setnostrut,\setstrut,\toonstruts
-#D %I
-#D %I text
-#D %I ....
-#D %P
-#D %I text
-#D %I ....
-#D \stoptypen
-#D
-#D Templates look like:
-#D
-#D \starttypen
-#D %T n=kap
-#D %T m=kap
-#D %T a=k
-#D %T
-#D %T \kap{?}
-#D \stoptypen
-#D
-#D The key||value pairs stand for {\em name}, {\em mnemonic},
-#D {\em key}. This information is copied to files with the
-#D extension \type{tud}.
-
-sub HandleEditorCues
- { if ($ProcessTemplates)
- { Report("Action", "CopyingTemplates" ) }
- if ($ProcessInfos)
- {Report("Action", "CopyingInformation" ) }
- if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { CheckInputFiles ($InputFile) ;
- foreach $FullName (@UserSuppliedFiles)
- { ($FileName, $FileSuffix) = SplitFileName ($FullName) ;
- if ($FileSuffix eq "")
- { $FileSuffix = "tex" }
- unless (-f "$FileName.$FileSuffix")
- { next }
- unless (open (TEX, "$FileName.$FileSuffix"))
- { Report("Error", "EmptyInputFile", "$FileName.$FileSuffix" ) }
- else
- { Report("InputFile", "$FileName.$FileSuffix") ;
- Report("OutputFile", "$FileName.tud") ;
- open (TUD, ">$FileName.tud") ;
- $NOfTemplates = 0 ;
- $NOfInfos = 0 ;
- while (<TEX>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- ($Tag, $RestOfLine) = split(/ /, $SomeLine, 2) ;
- if (($Tag eq "%T") && ($ProcessTemplates))
- { ++$NOfTemplates ;
- while ($Tag eq "%T")
- { print TUD "$SomeLine\n" ;
- $SomeLine = <TEX> ;
- chomp $SomeLine ;
- ($Tag, $RestOfLine) = split(/ /, $SomeLine, 2) }
- print TUD "\n" }
- elsif (($Tag eq "%I") && ($ProcessInfos))
- { ++$NOfInfos ;
- while (($Tag eq "%I") || ($Tag eq "%P"))
- { print TUD "$SomeLine\n" ;
- $SomeLine = <TEX> ;
- chomp $SomeLine ;
- ($Tag, $RestOfLine) = split(/ /, $SomeLine, 2) }
- print TUD "\n" } }
- close (TUD) ;
- unless (($NOfTemplates) || ($NOfInfos))
- { unlink "$FileName.tud" }
- if ($ProcessTemplates)
- { Report("NOfTemplates", $NOfTemplates) }
- if ($ProcessInfos)
- { Report("NOfInfos", $NOfInfos) } } } } }
-
-#D \extras
-#D {figures}
-#D
-#D Directories can be scanned for illustrations in \EPS, \PDF,
-#D \TIFF, \PNG\ or \JPG\ format. The resulting file \type{texutil.tuf}
-#D contains entries like:
-#D
-#D \starttypen
-#D \thisisfigureversion{year.month.day}
-#D \presetfigure[file][...specifications...]
-#D \stoptypen
-#D
-#D where the specifications are:
-#D
-#D \starttypen
-#D [e=suffix,x=xoffset,y=yoffset,w=width,h=height,t=title,c=creator,s=size]
-#D \stoptypen
-#D
-#D This data can be used when determining dimensions and
-#D generate directories of illustrations.
-
-$DPtoCM = 2.54/72.0 ;
-$INtoCM = 2.54 ;
-
-sub SaveFigurePresets
- { my ($FNam, $FTyp, $FUni, $FXof, $FYof, $FWid, $FHei, $FTit, $FCre, $FSiz) = @_ ;
- if ($ProcessVerbose)
- { OpenTerminal ;
- if ($FUni)
- { print "n=$FNam t=$FTyp " .
- (sprintf "x=%1.3fcm y=%1.3fcm ", $FXof, $FYof) .
- (sprintf "w=%5.3fcm h=%5.3fcm\n", $FWid, $FHei) }
- else
- { print "n=$FNam t=$FTyp " .
- "x=${FXof}bp y=${FYof}bp " .
- "w=${FWid}bp h=${FHei}bp\n" }
- CloseTerminal }
- else
- { ++$NOfFigures ;
- $Figures[$NOfFigures] = "\\presetfigure[$FNam][e=$FTyp" ;
- if ($FUni)
- { $Figures[$NOfFigures] .= (sprintf ",w=%5.3fcm,h=%5.3fcm", $FWid, $FHei) }
- else
- { $Figures[$NOfFigures] .= ",w=${FWid}bp,h=${FHei}bp" }
- if (($FXof!=0)||($FYof!=0))
- { if ($FUni)
- { $Figures[$NOfFigures] .= (sprintf ",x=%1.3fcm,y=%1.3fcm", $FXof, $FYof) }
- else
- { $Figures[$NOfFigures] .= ",x=${FXof}bp,y=${FYof}bp" } }
- if ($FTit)
- { $Figures[$NOfFigures] .= ",t=\{$FTit\}" }
- if ($FCre)
- { $Figures[$NOfFigures] .= ",c=\{$FCre\}" }
- $Figures[$NOfFigures] .= ",s=$FSiz]" } }
-
-#D The \EPS\ to \PDF\ conversion pipe to \GHOSTSCRIPT\ is
-#D inspired by a script posted by Sebastian Ratz at the
-#D \PDFTEX\ mailing list. Watch the bounding box check, we
-#D use the values found in an earlier pass.
-
-sub ConvertEpsToEps
- { my ( $SuppliedFileName , $LLX, $LLY, $URX, $URY ) = @_ ;
- ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if ($ProcessEpsToPdf)
- { if ($dosish) { $gs = "gswin32c" } else { $gs = "gs" }
- unlink "$FileName.pdf" ;
- $GSCommandLine = "-q " .
- "-sDEVICE=pdfwrite " .
- "-dNOCACHE " .
- "-dUseFlateCompression=true " .
- "-dMaxSubsetPct=100 " .
- "-sOutputFile=$FileName.pdf " .
- "- -c " .
- "quit " ;
- open ( EPS, "| $gs $GSCommandLine") }
- elsif ($PDFReady)
- { return }
- else
- { open ( EPS, ">texutil.tmp" ) ;
- binmode EPS }
- open ( TMP , "$SuppliedFileName" ) ;
- binmode TMP ;
- $EpsBBOX = 0 ;
- $EpsWidth = $URX - $LLX ;
- $EpsHeight = $URY - $LLY ;
- $EpsXOffset = 0 - $LLX ;
- $EpsYOffset = 0 - $LLY ;
- while (<TMP>)
- { if (/%!PS/)
- { s/(.*)%!PS/%!PS/o ;
- print EPS $_ ;
- last } }
- while (<TMP>)
- { if ((!$PDFReady)&&(/^%%(HiResB|ExactB|B)oundingBox:/o))
- { unless ($EpsBBOX)
- { print EPS "%%PDFready: $Program\n" ;
- print EPS "%%BoundingBox: 0 0 $EpsWidth $EpsHeight\n" ;
- print EPS "<< /PageSize [$EpsWidth $EpsHeight] >> setpagedevice\n" ;
- print EPS "gsave $EpsXOffset $EpsYOffset translate\n" ;
- $EpsBBOX = 1 } }
- elsif (/^%%EOF/o) # when final: (/^%%(EOF|Trailer)/o)
- { last }
- elsif (/^%%Trailer/o)
- { last }
- else
- { print EPS $_ } }
- close ( TMP ) ;
- if (($EpsBBOX)&&(!$PDFReady))
- { print EPS "grestore\n%%EOF\n%%RestOfFileIgnored: $Program\n" ;
- close ( EPS ) ;
- Report ( "PdfFile", "$SuppliedFileName" ) ;
- unless ($ProcessEpsToPdf)
- { unlink "$SuppliedFileName" ;
- rename "texutil.tmp", "$SuppliedFileName" } }
- else
- { close (EPS) }
- unlink "texutil.tmp" }
-
-sub HandleEpsFigure
- { my ($SuppliedFileName) = @_ ;
- my ($Temp) = "" ;
- if (-f $SuppliedFileName)
- { ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if ($FileSuffix ne "")
- {#$Temp = $FileSuffix ;
- #$Temp =~ s/[0-9]//go ;
- #if ($Temp eq "")
- if ($FileSuffix =~ /^[0-9]+$/o)
- { $EpsFileName = $SuppliedFileName;
- Report ( "MPFile", "$SuppliedFileName" ) }
- elsif ((lc $FileSuffix ne "eps")&&(lc $FileSuffix ne "mps"))
- { return }
- else
- { $EpsFileName = $SuppliedFileName; # $FileName
- Report ( "EpsFile", "$SuppliedFileName" ) }
- $EpsTitle = "" ;
- $EpsCreator = "" ;
- open ( EPS , $SuppliedFileName ) ;
- binmode EPS ;
- $EpsSize = -s EPS ;
- $PDFReady = 0 ;
- $MPSFound = 0 ;
- $BBoxFound = 0 ;
- while (<EPS>)
- { $SomeLine = $_;
- chomp $SomeLine ;
- if (($BBoxFound) && ((substr $SomeLine,0,1) ne "%"))
- { last }
- if ($BBoxFound<2)
- { if ($SomeLine =~ /^%%BoundingBox:(?!\s+\(atend\))/io) # atend (th)
- { $EpsBBox = $SomeLine ; $BBoxFound = 1 ; next }
- elsif ($SomeLine =~ /^%%HiResBoundingBox:/io)
- { $EpsBBox = $SomeLine ; $BBoxFound = 2 ; next }
- elsif ($SomeLine =~ /^%%ExactBoundingBox:/io)
- { $EpsBBox = $SomeLine ; $BBoxFound = 3 ; next } }
- if ($SomeLine =~ /^%%PDFready:/io)
- { $PDFReady = 1 }
- elsif ($SomeLine =~ /^%%Creator:/io)
- { ($Tag, $EpsCreator) = split (/ /, $SomeLine, 2) ;
- if ($EpsCreator =~ /MetaPost/io)
- { $MPSFound = 1 } }
- elsif ($SomeLine =~ /^%%Title:/io)
- { ($Tag, $EpsTitle) = split (/ /, $SomeLine, 2) } }
- close ( EPS ) ;
- if ($BBoxFound)
- { ($Tag, $LLX, $LLY, $URX, $URY, $RestOfLine) = split (/ /, $EpsBBox, 6 ) ;
- $EpsHeight = ($URY-$LLY)*$DPtoCM ;
- $EpsWidth = ($URX-$LLX)*$DPtoCM ;
- $EpsXOffset = $LLX*$DPtoCM ;
- $EpsYOffset = $LLY*$DPtoCM ;
- if ($MPSFound)
- { $EpsType = "mps" }
- else
- { $EpsType = "eps" }
- SaveFigurePresets
- ( $EpsFileName, $EpsType, 1,
- $EpsXOffset, $EpsYOffset, $EpsWidth, $EpsHeight,
- $EpsTitle, $EpsCreator, $EpsSize ) ;
- if (($ProcessEpsPage) || ($ProcessEpsToPdf))
- { ConvertEpsToEps ( $SuppliedFileName, $LLX, $LLY, $URX, $URY ) } }
- else
- { Report ( "MissingBoundingBox", "$SuppliedFileName" ) } } } }
-
-#D The \PDF\ scanning does a similar job. This time we
-#D search for a mediabox. I could have shared some lines
-#D with the previous routines, but prefer readability.
-
-sub HandlePdfFigure
- { my ( $SuppliedFileName ) = @_ ;
- ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if (lc $FileSuffix ne "pdf")
- { return }
- else
- { $PdfFileName = $SuppliedFileName ;
- Report ( "PdfFile", "$SuppliedFileName" ) }
- open ( PDF , $SuppliedFileName ) ;
- binmode PDF ;
- $PdfSize = -s PDF ;
- $MediaBoxFound = 0 ;
- $MediaBox = 0 ;
- $PageFound = 0 ;
- $PagesFound = 0 ;
- while (<PDF>)
- { $SomeLine = $_ ;
- chomp ($SomeLine) ;
- if ($SomeLine =~ /\/Type\s*\/Pages/io)
- { $PagesFound = 1 }
- elsif ($SomeLine =~ /\/Type\s*\/Page/io)
- { ++$PageFound ;
- if ($PageFound>1) { last } }
- if (($PageFound)||($PagesFound))
- { if (($MediaBoxFound < 2) && ($SomeLine =~ /\/ArtBox\s*\[/io))
- { $MediaBoxFound = 3 ;
- $MediaBox = $SomeLine }
- elsif (($MediaBoxFound < 2) && ($SomeLine =~ /\/CropBox\s*\[/io))
- { $MediaBoxFound = 2 ;
- $MediaBox = $SomeLine }
- elsif (($MediaBoxFound == 0) && ($SomeLine =~ /\/MediaBox\s*\[/io))
- { $MediaBoxFound = 1 ;
- $MediaBox = $SomeLine } } }
- close ( PDF ) ;
- if ($PageFound>1)
- { Report ( "MultiPagePdfFile", "$SuppliedFileName" ) }
- if (($MediaBoxFound) && ($MediaBox))
- { my $D = "[0-9\-\.]" ;
- $MediaBox =~ /\/(Media|Crop|Art)Box\s*\[\s*($D+)\s*($D+)\s*($D+)\s*($D+)/o ;
- $LLX = $2 ; $LLY = $3 ; $URX = $4 ; $URY = $5 ;
- $PdfHeight = ($URY-$LLY)*$DPtoCM ;
- $PdfWidth = ($URX-$LLX)*$DPtoCM ;
- $PdfXOffset = $LLX*$DPtoCM ;
- $PdfYOffset = $LLY*$DPtoCM ;
- SaveFigurePresets
- ( $PdfFileName, "pdf", 1,
- $PdfXOffset, $PdfYOffset, $PdfWidth, $PdfHeight,
- "", "", $PdfSize ) }
- else
- { Report ( "MissingMediaBox", "$SuppliedFileName" ) } }
-
-#D A previous version of \TEXUTIL\ used \type{tifftags} or
-#D \type{tiffinfo} for collecting the dimensions. However,
-#D the current implementation does this job itself.
-
-sub TifGetByte
- { my $B = 0 ;
- read TIF, $B, 1 ;
- return ord($B) }
-
-sub TifGetShort
- { my $S = 0 ;
- read TIF, $S, 2 ;
- if ($TifLittleEndian)
- { return (unpack ("v", $S)) }
- else
- { return (unpack ("n", $S)) } }
-
-sub TifGetLong
- { my $L = 0 ;
- read TIF, $L, 4 ;
- if ($TifLittleEndian)
- { return (unpack ("V", $L)) }
- else
- { return (unpack ("N", $L)) } }
-
-sub TifGetRational
- { my ($N, $M) = (0,0) ;
- $N = TifGetLong ;
- $M = TifGetLong ;
- return $N/$M }
-
-sub TifGetAscii
- { my $S = "" ;
- --$TifValues;
- unless ($TifValues)
- { return "" }
- else
- { read TIF, $S, $TifValues ;
- return $S } }
-
-sub TifGetWhatever
- { if ($_[0]==1)
- { return TifGetByte }
- elsif ($_[0]==2)
- { return TifGetAscii }
- elsif ($_[0]==3)
- { return TifGetShort }
- elsif ($_[0]==4)
- { return TifGetLong }
- elsif ($_[0]==5)
- { return TifGetRational }
- else
- { return 0 } }
-
-sub TifGetChunk
- { seek TIF, $TifNextChunk, 0 ;
- $Length = TifGetShort ;
- $TifNextChunk += 2 ;
- for ($i=1; $i<=$Length; $i++)
- { seek TIF, $TifNextChunk, 0 ;
- $TifTag = TifGetShort ;
- $TifType = TifGetShort ;
- $TifValues = TifGetLong ;
- if ($TifTag==256)
- { $TifWidth = TifGetWhatever($TifType) }
- elsif ($TifTag==257)
- { $TifHeight = TifGetWhatever($TifType) }
- elsif ($TifTag==296)
- { $TifUnit = TifGetWhatever($TifType) }
- elsif ($TifTag==282)
- { seek TIF, TifGetLong, 0 ;
- $TifHRes = TifGetWhatever($TifType) }
- elsif ($TifTag==283)
- { seek TIF, TifGetLong, 0 ;
- $TifVRes = TifGetWhatever($TifType) }
- elsif ($TifTag==350)
- { seek TIF, TifGetLong, 0 ;
- $TifCreator = TifGetWhatever($TifType) }
- elsif ($TifTag==315)
- { seek TIF, TifGetLong, 0 ;
- $TifAuthor = TifGetWhatever($TifType) }
- elsif ($TifTag==269)
- { seek TIF, TifGetLong, 0 ;
- $TifTitle = TifGetWhatever($TifType) }
- $TifNextChunk += 12 }
- seek TIF, $TifNextChunk, 0 ;
- $TifNextChunk = TifGetLong ;
- return ($TifNextChunk>0) }
-
-sub HandleTifFigure
- { my ( $SuppliedFileName ) = @_ ;
- ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if (lc $FileSuffix ne "tif")
- { return }
- else
- { $TifFile = $SuppliedFileName ;
- if (open ( TIF, $TifFile )) { # { must be here, perl 5.003 bug
- Report ( "TifFile", "$SuppliedFileName" ) ;
- binmode TIF;
- $TifWidth = 0 ;
- $TifHeight = 0 ;
- $TifTitle = "" ;
- $TifAuthor = "" ;
- $TifCreator = "" ;
- $TifUnit = 0 ;
- $TifHRes = 1 ;
- $TifVRes = 1 ;
- $TifSize = -s TIF ;
- $TifByteOrder = "" ;
- seek TIF, 0, 0 ;
- read TIF, $TifByteOrder, 2 ;
- $TifLittleEndian = ($TifByteOrder eq "II") ;
- $TifTag = TifGetShort;
- unless ($TifTag == 42)
- { close ( TIF ) ;
- return }
- $TifNextChunk = TifGetLong ;
- while (TifGetChunk) { }
- if ($TifUnit==2)
- { $TifMult = $INtoCM }
- elsif ($TifUnit==3)
- { $TifMult = 1 }
- else
- { $TifMult = 72 }
- $TifWidth = ($TifWidth /$TifHRes)*$TifMult ;
- $TifHeight = ($TifHeight/$TifVRes)*$TifMult ;
- close ( TIF ) ;
- SaveFigurePresets
- ( $TifFile, "tif", $TifUnit,
- 0, 0, $TifWidth, $TifHeight,
- $TifTitle, $TifCreator, $TifSize ) } } }
-
-#D I first intended to use the public utility \type{pngmeta}
-#D (many thanks to Taco for compiling it), but using this
-#D utility to analyze lots of \PNG\ files, I tried to do a
-#D similar job in \PERL. Here are the results:
-
-my ($PngSize, $PngWidth, $PngHeight) = (0,0,0) ;
-my ($PngMult, $PngHRes, $PngVRes, $PngUnit) = (0,1,1,0) ;
-my ($PngFile, $PngTitle, $PngAuthor, $PngCreator) = ("","","") ;
-my ($PngNextChunk, $PngLength, $PngType) = (0,0,0) ;
-my ($PngKeyword, $PngDummy) = ("","") ;
-
-my $PngSignature = chr(137) . chr(80) . chr(78) . chr(71) .
- chr (13) . chr(10) . chr(26) . chr(10) ;
-sub PngGetByte
- { my ($B) = 0 ;
- read PNG, $B, 1 ;
- return (ord($B)) }
-
-sub PngGetLong
- { my ($L) = 0 ;
- read PNG, $L, 4 ;
- return (unpack("N", $L)) }
-
-sub PngGetChunk
- { if ($PngNextChunk<$PngSize)
- { seek PNG, $PngNextChunk, 0 ;
- $PngLength = PngGetLong ;
- $PngNextChunk = $PngNextChunk + $PngLength + 12 ;
- read PNG, $PngType, 4 ;
- if ($PngType eq "")
- { return 0 }
- elsif ($PngType eq "IEND")
- { return 0 }
- elsif ($PngType eq "IHDR")
- { $PngWidth = PngGetLong ;
- $PngHeight = PngGetLong }
- elsif ($PngType eq "pHYs")
- { $PngHRes = PngGetLong ;
- $PngVRes = PngGetLong ;
- read PNG, $PngUnit, 1 }
- elsif ($PngType eq "tEXt")
- { read PNG, $PngKeyword, $PngLength ;
- ($PngKeyword,$PngDummy) = split(/\x00/,$PngKeyword) ;
- if ( $PngKeyword eq "Title")
- { $PngTitle = $PngDummy }
- elsif ( $PngKeyword eq "Author")
- { $PngAuthor = $PngDummy }
- elsif ( $PngKeyword eq "Software")
- { $PngCreator = $PngDummy } }
- return 1 }
- else
- { return 0 } }
-
-sub HandlePngFigure
- { my ( $SuppliedFileName ) = @_ ;
- ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if (lc $FileSuffix ne "png")
- { return }
- else
- { $PngFile = $SuppliedFileName ;
- if (open ( PNG, $PngFile ))
- { Report ( "PngFile", "$SuppliedFileName" ) }
- $PngSize = 0 ;
- $PngWidth = 0 ;
- $PngHeight = 0 ;
- $PngTitle = "" ;
- $PngAuthor = "" ;
- $PngCreator = "" ;
- $PngUnit = 0 ;
- $PngVRes = 1 ;
- $PngHRes = 1 ;
- $PngSig = "" ;
- $PngSize = -s PNG ;
- binmode PNG ;
- seek PNG, 0, 0 ;
- read PNG, $PngSig, 8;
- unless ($PngSig eq $PngSignature)
- { close ( PNG ) ;
- return }
- $PngNextChunk = 8 ;
- while (PngGetChunk) { }
- $PngWidth = ($PngWidth /$PngVRes) ;
- $PngHeight = ($PngHeight/$PngHRes) ;
- close ( PNG ) ;
- SaveFigurePresets
- ( $PngFile, "png", $PngUnit,
- 0, 0, $PngWidth, $PngHeight,
- $PngTitle, $PngCreator, $PngSize ) } }
-
-#D Well, we also offer \JPG\ scanning (actually \JFIF)
-#D scanning. (I can recomend David Salomon's book on Data
-#D Compression to those interested in the internals of
-#D \JPG.)
-#D
-#D It took me some time to discover that the (sort of)
-#D reference document I used had a faulty byte position table.
-#D Nevertheless, when I was finaly able to grab the header,
-#D Piet van Oostrum pointer me to the \PERL\ script of Alex
-#D Knowles (and numerous other contributers), from which I
-#D could deduce what segment contained the dimensions.
-
-my ($JpgSize, $JpgWidth, $JpgHeight) = (0,0,0) ;
-my ($JpgMult, $JpgUnit, $JpgHRes, $JpgVRes) = (1,0,1,1) ;
-my ($JpgFile, $JpgVersion, $JpgDummy) = ("",0,"") ;
-my ($JpgSig, $JpgPos, $JpgLen, $JpgSoi, $JpgApp) = ("",0,0,0,0) ;
-
-my $JpgSignature = "JFIF" . chr(0) ;
-
-sub JpgGetByte
- { my ($B) = 0 ;
- read JPG, $B, 1 ;
- return ( ord($B) ) }
-
-sub JpgGetInteger
- { my ($I) = 0 ;
- read JPG, $I, 2 ;
- return (unpack("n", $I)) }
-
-sub HandleJpgFigure
- { my ($SuppliedFileName) = @_ ;
- ($FileName, $FileSuffix) = SplitFileName ($SuppliedFileName) ;
- if (lc $FileSuffix ne "jpg")
- { return }
- else
- { $JpgFile = $SuppliedFileName ;
- Report ( "JpgFile", "$SuppliedFileName" ) }
- open ( JPG, $JpgFile ) ;
- binmode JPG ;
- $JpgSignature = "JFIF" . chr(0) ;
- $JpgSize = -s JPG ;
- $JpgWidth = 0 ;
- $JpgHeight = 0 ;
- $JpgUnit = 0 ;
- $JpgVRes = 1 ;
- $JpgHRes = 1 ;
- seek JPG, 0, 0 ;
- read JPG, $JpgSig, 4 ;
- unless ($JpgSig eq chr(255).chr(216).chr(255).chr(224))
- { close ( JPG ) ;
- return }
- $JpgLen = JpgGetInteger;
- read JPG, $JpgSig, 5 ;
- unless ($JpgSig eq $JpgSignature)
- { close ( JPG ) ;
- return }
- $JpgUnit = JpgGetByte ;
- $JpgVersion = JpgGetInteger ;
- $JpgHRes = JpgGetInteger ;
- $JpgVRes = JpgGetInteger ;
- $JpgPos = $JpgLen + 4 ;
- $JpgSoi = 255 ;
- while ()
- { seek JPG, $JpgPos, 0 ;
- $JpgSoi = JpgGetByte ;
- $JpgApp = JpgGetByte ;
- $JpgLen = JpgGetInteger ;
- if ($JpgSoi!=255)
- { last }
- if (($JpgApp>=192) && ($JpgApp<=195)) # Found in the perl script.
- { $JpgDummy = JpgGetByte ; # Found in the perl script.
- $JpgHeight = JpgGetInteger ; # Found in the perl script.
- $JpgWidth = JpgGetInteger } # Found in the perl script.
- $JpgPos = $JpgPos + $JpgLen + 2 }
- close ( JPG ) ;
- if ($JpgUnit==1)
- { $JpgMult = $INtoCM }
- else
- { $JpgMult = 1 }
- $JpgHRes = 72 unless $JpgHRes>1 ;
- $JpgVRes = 72 unless $JpgVRes>1 ;
- $JpgWidth = ($JpgWidth/$JpgHRes)*$JpgMult ;
- $JpgHeight = ($JpgHeight/$JpgVRes)*$JpgMult ;
- close ( JPG ) ;
- SaveFigurePresets
- ( $JpgFile, "jpg", $JpgUnit,
- 0, 0, $JpgWidth, $JpgHeight,
- "", "", $JpgSize ) }
-
-#D Now we can handle figures!
-
-sub InitializeFigures
- { $NOfFigures = 0 }
-
-sub FlushFigures
- { SetOutputFile ("texutil.tuf") ;
- open ( TUF, ">$OutputFile" ) ;
- print TUF "%\n" . "% $Program / Figures\n" . "%\n" ;
- print TUF "\\thisisfigureversion\{1996.06.01\}\n" . "%\n" ;
- # a joins is nicer
- for ($n=1 ; $n<=$NOfFigures ; ++$n)
- { print TUF "$Figures[$n]%\n" }
- print TUF "\\endinput";
- close (TUF) ;
- if ($NOfFigures)
- { Report("OutputFile", $OutputFile ) }
- else
- { unlink $OutputFile }
- Report ( "NOfFigures", $NOfFigures ) }
-
-sub DoHandleFigures
- { my ($FigureSuffix, $FigureMethod) = @_ ;
- if ($InputFile eq "")
- { $InputFile = $FigureSuffix }
- CheckInputFiles ($InputFile) ;
- foreach $FileName (@UserSuppliedFiles)
- { &{$FigureMethod} ( $FileName ) } }
-
-sub HandleFigures
- { Report("Action", "GeneratingFigures" ) ;
- foreach $FileType (@ARGV)
- { if ($FileType=~/\.eps/io)
- { Report("Option", "UsingEps") ;
- if ($ProcessEpsToPdf) { Report("Option", "EpsToPdf") }
- if ($ProcessEpsPage) { Report("Option", "EpsPage") }
- last } }
- foreach $FileType (@ARGV)
- { if ($FileType=~/\.pdf/io)
- { Report("Option", "UsingPdf") ;
- last } }
- foreach $FileType (@ARGV)
- { if ($FileType=~/\.tif/io)
- { Report("Option", "UsingTif") ;
- #RunTifPrograms ;
- last } }
- foreach $FileType (@ARGV)
- { if ($FileType=~/\.png/io)
- { Report("Option", "UsingPng") ;
- last } }
- foreach $FileType (@ARGV)
- { if ($FileType=~/\.jpg/io)
- { Report("Option", "UsingJpg") ;
- last } }
- InitializeFigures ;
- DoHandleFigures ("eps", "HandleEpsFigure") ;
- DoHandleFigures ("pdf", "HandlePdfFigure") ;
- DoHandleFigures ("tif", "HandleTifFigure") ;
- DoHandleFigures ("png", "HandlePngFigure") ;
- DoHandleFigures ("jpg", "HandleJpgFigure") ;
- FlushFigures }
-
-#D \extras
-#D {logfiles}
-#D
-#D This (poor man's) log file scanning routine filters
-#D overfull box messages from a log file (\type{\hbox},
-#D \type{\vbox} or both). The collected problems are saved
-#D in \type{$ProgramLog}. One can specify a selection
-#D criterium.
-#D
-#D \CONTEXT\ reports unknown entities. These can also be
-#D filtered. When using fast computers, or when processing
-#D files in batch, one has to rely on the log files and/or
-#D this filter.
-
-$Unknown = "onbekende verwijzing|" .
- "unbekannte Referenz|" .
- "unknown reference|" .
- "dubbele verwijzing|" .
- "duplicate reference|" .
- "doppelte Referenz" ;
-
-sub FlushLogTopic
- { unless ($TopicFound)
- { $TopicFound = 1 ;
- print ALL "\n% File: $FileName.log\n\n" } }
-
-sub HandleLogFile
- { if ($ProcessBox)
- { Report("Option", "FilteringBoxes", "(\\vbox & \\hbox)") ;
- $Key = "[h|v]box" }
- elsif ($ProcessHBox)
- { Report("Option", "FilteringBoxes", "(\\hbox)") ;
- $Key = "hbox" ;
- $ProcessBox = 1 }
- elsif ($ProcessVBox)
- { Report("Option", "FilteringBoxes", "(\\vbox)") ;
- $Key = "vbox" ;
- $ProcessBox = 1 }
- if (($ProcessBox) && ($ProcessCriterium))
- { Report("Option", "ApplyingCriterium") }
- if ($ProcessUnknown)
- { Report("Option", "FilteringUnknown") }
- unless (($ProcessBox) || ($ProcessUnknown))
- { ShowHelpInfo ;
- return }
- Report("Action", "FilteringLogFile" ) ;
- if ($InputFile eq "")
- { Report("Error", "NoInputFile") }
- else
- { $NOfBoxes = 0 ;
- $NOfMatching = 0 ;
- $NOfUnknown = 0 ;
- SetOutputFile ($ProgramLog) ;
- Report("OutputFile", $OutputFile) ;
- CheckInputFiles ($InputFile) ;
- open ( ALL, ">$OutputFile" ) ;
- foreach $FullName (@UserSuppliedFiles)
- { ($FileName, $FileSuffix) = SplitFileName ($FullName) ;
- if (! open (LOG, "$FileName.log"))
- { Report("Error", "EmptyInputFile", "$FileName.$FileSuffix" ) }
- elsif (-e "$FileName.tex")
- { $TopicFound = 0 ;
- Report("InputFile", "$FileName.log") ;
- while (<LOG>)
- { $SomeLine = $_ ;
- chomp $SomeLine ;
- if (($ProcessBox) && ($SomeLine =~ /Overfull \\$Key/))
- { ++$NOfBoxes ;
- $SomePoints = $SomeLine ;
- $SomePoints =~ s/.*\((.*)pt.*/$1/ ;
- if ($SomePoints>=$ProcessCriterium)
- { ++$NOfMatching ;
- FlushLogTopic ;
- print ALL "$SomeLine\n" ;
- $SomeLine=<LOG> ;
- print ALL $SomeLine } }
- if (($ProcessUnknown) && ($SomeLine =~ /$Unknown/io))
- { ++$NOfUnknown ;
- FlushLogTopic ;
- print ALL "$SomeLine\n" } } } }
- close (ALL) ;
- unless (($NOfBoxes) ||($NOfUnknown))
- { unlink $OutputFile }
- if ($ProcessBox)
- { Report ( "NOfBoxes" , "$NOfBoxes", "->", $NOfMatching, "Overfull") }
- if ($ProcessUnknown)
- { Report ( "NOfUnknown", "$NOfUnknown") } } }
-
-#D Undocumented feature.
-#
-# obsolete, i.e now in ctxtools, so this will become:
-#
-# sub PurgeFiles {
-# if ($PurgeAllFiles) {
-# system("ctxtools --purge $ARGV[0]") ;
-# } else {
-# system("ctxtools --purge --all $ARGV[0]") ;
-# }
-
-my $removedfiles = 0 ;
-my $keptfiles = 0 ;
-my $persistentfiles = 0 ;
-my $reclaimedbytes = 0 ;
-
-sub RemoveContextFile
- { my $filename = shift ;
- my $filesize = -s $filename ;
- unlink $filename ;
- if (-e $filename)
- { ++$persistentfiles ;
- print " persistent : $filename\n" }
- else
- { ++$removedfiles ; $reclaimedbytes += $filesize ;
- print " removed : $filename\n" } }
-
-sub KeepContextFile
- { my $filename = shift ;
- ++$keptfiles ;
- print " kept : $filename\n" }
-
-my @dontaskprefixes = sort glob "mpx-*" ; push @dontaskprefixes ,
- ("tex-form.tex","tex-edit.tex","tex-temp.tex",
- "texexec.tex","texexec.tui","texexec.tuo",
- "texexec.ps","texexec.pdf","texexec.dvi",
- "cont-opt.tex","cont-opt.bak") ;
-my @dontasksuffixes =
- ("mpgraph.mp","mpgraph.mpd","mpgraph.mpo","mpgraph.mpy",
- "mprun.mp", "mprun.mpd", "mprun.mpo", "mprun.mpy",
- "xlscript.xsl") ;
-my @forsuresuffixes =
- ("tui","tup","ted","tes","top",
- "log","tmp","run","bck","rlg",
- "mpt","mpx","mpd","mpo") ;
-my @texonlysuffixes =
- ("dvi","ps","pdf") ;
-my @texnonesuffixes =
- ("tuo","tub","top") ;
-
-if ($PurgeAllFiles)
- { push @forsuresuffixes, @texnonesuffixes ; @texnonesuffixes = [] }
-
-sub PurgeFiles # no my in foreach
- { my $pattern = $ARGV[0] ;
- my $strippedname ;
- my $basename ;
- my @files = () ;
- if ($pattern eq '')
- { $pattern = "*.*" ;
- @files = glob $pattern }
- else
- { $pattern = $ARGV[0] . "-*.*" ;
- @files = glob $pattern ;
- $pattern = $ARGV[0] . ".*" ;
- push(@files,glob $pattern) }
- @files = sort @files ;
- print " purging files : $pattern\n\n" ;
- foreach $file (@dontaskprefixes)
- { if (-e $file)
- { RemoveContextFile($file) } }
- foreach $file (@dontasksuffixes)
- { if (-e $file)
- { RemoveContextFile($file) } }
- foreach $suffix (@dontasksuffixes)
- { foreach (@files)
- { if (/$suffix$/i)
- { RemoveContextFile($_) } } }
- foreach $suffix (@forsuresuffixes)
- { foreach (@files)
- { if (/\.$suffix$/i)
- { RemoveContextFile($_) } } }
- foreach $file (@files)
- { if ($file =~ /(.*?)\.\d+$/)
- { $basename = $1 ;
- if (($file =~ /mp(graph|run)/) || (-e "$basename.mp"))
- { RemoveContextFile($file) } } }
- foreach $suffix (@texnonesuffixes)
- { foreach (@files)
- { if (/(.*)\.$suffix$/i)
- { if ((-e "$1.tex")||(-e "$1.xml")||(-e "$1.fo"))
- { KeepContextFile($_) }
- else
- { $strippedname = $1 ;
- $strippedname =~ s/\-[a-z]$//io ;
- if ((-e "$strippedname.tex")||(-e "$strippedname.xml"))
- { KeepContextFile($_." (potential result file)") }
- else
- { RemoveContextFile($_) } } } } }
- if ($removedfiles||$keptfiles||$persistentfiles)
- { print "\n" }
- print " removed files : $removedfiles\n" ;
- print " kept files : $keptfiles\n" ;
- print " persistent files : $persistentfiles\n" ;
- print " reclaimed bytes : $reclaimedbytes\n" }
-
-#D Another undocumented feature.
-#
-# obsolete, i.e now in pdftools, so this will become:
-#
-# sub AnalyzeFile
-# { system("pdftools --analyze $ARGV[0]") }
-
-sub AnalyzeFile
- { my $filename = $ARGV[0] ;
- return unless (($filename =~ /\.pdf/)&&(-e $filename)) ;
- my $filesize = -s $filename ;
- print " analyzing file : $filename\n" ;
- print " file size : $filesize\n" ;
- open (PDF, $filename) ;
- binmode PDF ;
- my $Object = 0 ;
- my $Annot = 0 ;
- my $Link = 0 ;
- my $Widget = 0 ;
- my $Named = 0 ;
- my $Script = 0 ;
- my $Cross = 0 ;
- while (<PDF>)
- { while (/\d+\s+\d+\s+obj/go) { ++$Object } ;
- while (/\/Type\s*\/Annot/go) { ++$Annot } ;
- while (/\/GoToR\s*\/F/go) { ++$Cross } ;
- while (/\/Subtype\s*\/Link/go) { ++$Link } ;
- while (/\/Subtype\s*\/Widget/go) { ++$Widget } ;
- while (/\/S\s*\/Named/go) { ++$Named } ;
- while (/\/S\s*\/JavaScript/go) { ++$Script } }
- close (PDF) ;
- print " objects : $Object\n" ;
- print " annotations : $Annot\n" ;
- print " links : $Link ($Named named / $Script scripts / $Cross files)\n" ;
- print " widgets : $Widget\n" }
-
-# moved to ctxtools
-#
-# sub FilterPages
-# { system("ctxtools $ARGV{0]") }
-
-sub FilterPages # temp feature / no reporting
- { my $filename = $ARGV[0] ;
- return unless -f "$filename.pdf" ;
- my $old = '' ;
- my $n = 0 ;
- if (open(PDF,"<$filename.pdf") && open(TUO,">>$filename.tuo"))
- { binmode PDF ;
- while (<PDF>)
- { chomp ;
- if (($_ eq '/Type /Page') && ($old =~ /^(\d+)\s+0\s+obj/o))
- { ++$n ; $p = $1 ;
- print TUO "\\objectreference{PDFP}{$n}{$p}{$n}\n" }
- else
- { $old = $_ } }
- close(PDF) ;
- close(TUO) } }
-
- ShowBanner ;
-
-if ($UnknownOptions ) { ShowHelpInfo } # not yet done
-elsif ($ProcessReferences) { HandleReferences }
-elsif ($ProcessDocuments ) { HandleDocuments }
-elsif ($ProcessSources ) { HandleSources }
-elsif ($ProcessSetups ) { HandleSetups }
-elsif ($ProcessTemplates ) { HandleEditorCues }
-elsif ($ProcessInfos ) { HandleEditorCues }
-elsif ($ProcessFigures ) { HandleFigures }
-elsif ($ProcessLogFile ) { HandleLogFile }
-elsif ($PurgeFiles ) { PurgeFiles }
-elsif ($PurgeAllFiles ) { PurgeFiles }
-elsif ($AnalyzeFile ) { AnalyzeFile }
-elsif ($FilterPages ) { FilterPages }
-elsif ($ProcessHelp ) { ShowHelpInfo } # redundant
-else { ShowHelpInfo }
-
-print "\n" ;
-print " remark : 'texutil' is now part of 'texexec'\n" ;
-print " warning : use 'texmfstart texutil' instead\n" ;
-
-#D So far.
diff --git a/Master/texmf-dist/scripts/context/perl/utiplug.pm b/Master/texmf-dist/scripts/context/perl/utiplug.pm
deleted file mode 100644
index 2bc162e1e38..00000000000
--- a/Master/texmf-dist/scripts/context/perl/utiplug.pm
+++ /dev/null
@@ -1,30 +0,0 @@
-package utiplug ;
-
-my @data ;
-my @result ;
-
-sub utiplug::initialize
- { @data = () }
-
-sub utiplug::process
- { @data = sort @data ;
- for (my $i=0; $i<@data; $i++)
- { @result[$i] = "\\plugintest\{$i\}\{$data[$i]\}" } }
-
-sub utiplug::handle
- { my ($self,$text,$rest) = @_ ; push @data, $text }
-
-sub utiplug::identify
- { return "utiplug test plugin" }
-
-sub utiplug::report
- { my $keys = @data ;
- if ($keys)
- { return ("done", "keys:$keys") }
- else
- { return ("nothing done") } }
-
-sub utiplug::results
- { return @result }
-
-1 ;
diff --git a/Master/texmf-dist/scripts/context/ruby/base/exa.rb b/Master/texmf-dist/scripts/context/ruby/base/exa.rb
index 5a094351ec5..7ba990cf900 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/exa.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/exa.rb
@@ -3,8 +3,9 @@
# tex.setup.setuplayout.width.[integer|real|dimension|string|key]
# tex.[mp]var.whatever.width.[integer|real|dimension|string|key]
-require 'ftools'
-require 'md5'
+require 'fileutils'
+# require 'ftools'
+require 'digest/md5'
# this can become a lua thing
@@ -40,7 +41,7 @@ module ExaEncrypt
pre, password, post = $1, $2, $3
unless password =~ /MD5:/i then
done = true
- password = "MD5:" + MD5.new(password).hexdigest.upcase
+ password = "MD5:" + Digest::MD5.hexdigest(password).upcase
end
"#{pre}#{password}#{post}"
end
@@ -49,7 +50,7 @@ module ExaEncrypt
attributes, password = $1, $2
unless password =~ /^([0-9A-F][0-9A-F])+$/ then
done = true
- password = MD5.new(password).hexdigest.upcase
+ password = Digest::MD5.hexdigest(password).upcase
attributes = " encryption='md5'#{attributes}"
end
"<exa:password#{attributes}>#{password}</exa:password>"
diff --git a/Master/texmf-dist/scripts/context/ruby/base/file.rb b/Master/texmf-dist/scripts/context/ruby/base/file.rb
index 39bb7d46731..1aeac5fd697 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/file.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/file.rb
@@ -8,7 +8,8 @@
# info : j.hagen@xs4all.nl
# www : www.pragma-ade.com
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
class File
@@ -110,7 +111,7 @@ class File
def File.silentcopy(oldname,newname)
return if File.expand_path(oldname) == File.expand_path(newname)
- File.makedirs(File.dirname(newname)) rescue false
+ FileUtils.makedirs(File.dirname(newname)) rescue false
File.copy(oldname,newname) rescue false
end
@@ -123,7 +124,7 @@ class File
begin
File.rename(oldname,newname)
rescue
- File.makedirs(File.dirname(newname)) rescue false
+ FileUtils.makedirs(File.dirname(newname)) rescue false
File.copy(oldname,newname) rescue false
end
end
diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpse.rb b/Master/texmf-dist/scripts/context/ruby/base/kpse.rb
index 0e185b5b8d7..0f986878413 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/kpse.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/kpse.rb
@@ -13,6 +13,7 @@
# todo: web2c vs miktex module and include in kpse
require 'rbconfig'
+require 'fileutils'
# beware $engine is lowercase in kpse
#
@@ -185,6 +186,7 @@ module Kpse
return results
end
+
def Kpse.formatpaths
# maybe we should check for writeability
unless @@paths.key?('formatpaths') then
@@ -272,7 +274,7 @@ module Kpse
unless done then
formatpaths.each do |fp|
fpp = fp.sub(/#{engine}\/*$/o,'')
- File.makedirs(fpp) rescue false # maybe we don't have an path yet
+ FileUtils.makedirs(fpp) rescue false # maybe we don't have an path yet
if FileTest.directory?(fpp) && FileTest.writable?(fpp) then
# use this path
formatpath, done = fp.dup, true
@@ -285,12 +287,12 @@ module Kpse
end
end
# needed !
- File.makedirs(formatpath) rescue false
+ FileUtils.makedirs(formatpath) rescue false
# fall back to current path
formatpath = '.' if formatpath.empty? || ! FileTest.writable?(formatpath)
# append engine but prevent duplicates
formatpath = File.join(formatpath.sub(/\/*#{engine}\/*$/,''), engine) if enginepath
- File.makedirs(formatpath) rescue false
+ FileUtils.makedirs(formatpath) rescue false
setpath(engine,formatpath)
# ENV['engine'] = savedengine
end
diff --git a/Master/texmf-dist/scripts/context/ruby/base/kpsetrees.rb b/Master/texmf-dist/scripts/context/ruby/base/kpsetrees.rb
deleted file mode 100644
index 8e3fb20f854..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/base/kpsetrees.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'monitor'
-require 'base/kpsefast'
-
-class KpseTrees < Monitor
-
- def initialize
- @trees = Hash.new
- @monitor = Monitor.new
- end
-
- def choose(filenames,environment)
- current = filenames.join('|')
- # @monitor do
- unless @trees[current] then
- puts "loading tree #{current}"
- @trees[current] = KpseFast.new
- @trees[current].push_environment(environment)
- @trees[current].load_cnf(filenames)
- @trees[current].expand_variables
- @trees[current].load_lsr
- end
- puts "enabling tree #{current}"
- # end
- current
- end
-
- def set(tree,key,value)
- case key
- when 'progname' then @trees[tree].progname = value
- when 'engine' then @trees[tree].engine = value
- when 'format' then @trees[tree].format = value
- end
- end
- def load_cnf(tree)
- @trees[tree].load_cnf
- end
- def load_lsr(tree)
- @trees[tree].load_lsr
- end
- def expand_variables(tree)
- @trees[tree].expand_variables
- end
- def expand_braces(tree,str)
- @trees[tree].expand_braces(str)
- end
- def expand_path(tree,str)
- @trees[tree].expand_path(str)
- end
- def expand_var(tree,str)
- @trees[tree].expand_var(str)
- end
- def show_path(tree,str)
- @trees[tree].show_path(str)
- end
- def var_value(tree,str)
- @trees[tree].var_value(str)
- end
- def find_file(tree,filename)
- @trees[tree].find_file(filename)
- end
- def find_files(tree,filename,first)
- @trees[tree].find_files(filename,first)
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/base/state.rb b/Master/texmf-dist/scripts/context/ruby/base/state.rb
index 4b208812875..76ef50b2563 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/state.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/state.rb
@@ -1,4 +1,4 @@
-require "md5"
+require 'digest/md5'
# todo: register omissions per file
@@ -57,7 +57,7 @@ class FileState
begin
if FileTest.file?(filename) && (data = IO.read(filename)) then
data.gsub!(/\n.*?(#{[omit].flatten.join('|')}).*?\n/) do "\n" end if omit
- sum = MD5.new(data).hexdigest.upcase
+ sum = Digest::MD5.hexdigest(data).upcase
end
rescue
sum = ''
diff --git a/Master/texmf-dist/scripts/context/ruby/base/tex.rb b/Master/texmf-dist/scripts/context/ruby/base/tex.rb
index 582ff640c6e..23db7f1e872 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/tex.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/tex.rb
@@ -16,6 +16,8 @@
# report ?
+require 'fileutils'
+
require 'base/variables'
require 'base/kpse'
require 'base/system'
@@ -67,28 +69,26 @@ class TEX
include Variables
- @@texengines = Hash.new
- @@mpsengines = Hash.new
- @@backends = Hash.new
- @@mappaths = Hash.new
- @@runoptions = Hash.new
- @@tcxflag = Hash.new
- @@draftoptions = Hash.new
- @@texformats = Hash.new
- @@mpsformats = Hash.new
- @@prognames = Hash.new
- @@texmakestr = Hash.new
- @@texprocstr = Hash.new
- @@mpsmakestr = Hash.new
- @@mpsprocstr = Hash.new
-
- @@texmethods = Hash.new
- @@mpsmethods = Hash.new
-
- @@pdftex = 'pdftex' # new default, pdfetex is gone
-
- @@luafiles = "luafiles.tmp"
- @@luatarget = "lua/context"
+ @@texengines = Hash.new
+ @@mpsengines = Hash.new
+ @@backends = Hash.new
+ @@mappaths = Hash.new
+ @@runoptions = Hash.new
+ @@tcxflag = Hash.new
+ @@draftoptions = Hash.new
+ @@synctexcoptions = Hash.new
+ @@texformats = Hash.new
+ @@mpsformats = Hash.new
+ @@prognames = Hash.new
+ @@texmakestr = Hash.new
+ @@texprocstr = Hash.new
+ @@mpsmakestr = Hash.new
+ @@mpsprocstr = Hash.new
+ @@texmethods = Hash.new
+ @@mpsmethods = Hash.new
+ @@pdftex = 'pdftex' # new default, pdfetex is gone
+ @@luafiles = "luafiles.tmp"
+ @@luatarget = "lua/context"
@@platformslash = if System.unix? then "\\\\" else "\\" end
@@ -112,8 +112,8 @@ class TEX
['tex','standard'] .each do |b| @@mappaths[b] = 'dvips' end
['pdftex','pdfetex'] .each do |b| @@mappaths[b] = 'pdftex' end
- ['aleph','omega','xetex','petex'] .each do |b| @@mappaths[b] = 'dvipdfm' end
- ['dvipdfm', 'dvipdfmx', 'xdvipdfmx'] .each do |b| @@mappaths[b] = 'dvipdfm' end
+ ['aleph','omega','xetex','petex'] .each do |b| @@mappaths[b] = 'dvipdfmx' end
+ ['dvipdfm', 'dvipdfmx', 'xdvipdfmx'] .each do |b| @@mappaths[b] = 'dvipdfmx' end
['xdv','xdv2pdf'] .each do |b| @@mappaths[b] = 'dvips' end
# todo norwegian (no)
@@ -124,14 +124,16 @@ class TEX
['cont-de','de','german'] .each do |f| @@texformats[f] = 'cont-de' end
['cont-it','it','italian'] .each do |f| @@texformats[f] = 'cont-it' end
['cont-fr','fr','french'] .each do |f| @@texformats[f] = 'cont-fr' end
- ['cont-cz','cz','czech'] .each do |f| @@texformats[f] = 'cont-cz' end
+ ['cont-cs','cs','cont-cz','cz','czech'] .each do |f| @@texformats[f] = 'cont-cs' end
['cont-ro','ro','romanian'] .each do |f| @@texformats[f] = 'cont-ro' end
- ['cont-uk','uk','british'] .each do |f| @@texformats[f] = 'cont-uk' end
+ ['cont-gb','gb','cont-uk','uk','british'] .each do |f| @@texformats[f] = 'cont-gb' end
+ ['cont-pe','pe','persian'] .each do |f| @@texformats[f] = 'cont-pe' end
+ ['cont-xp','xp','experimental'] .each do |f| @@texformats[f] = 'cont-xp' end
['mptopdf'] .each do |f| @@texformats[f] = 'mptopdf' end
['latex'] .each do |f| @@texformats[f] = 'latex.ltx' end
- ['plain','mpost'] .each do |f| @@mpsformats[f] = 'plain' end
+ ['plain','mpost'] .each do |f| @@mpsformats[f] = 'mpost' end
['metafun','context','standard'] .each do |f| @@mpsformats[f] = 'metafun' end
['pdftex','pdfetex','aleph','omega','petex',
@@ -141,7 +143,8 @@ class TEX
['plain','default','standard','mptopdf'] .each do |f| @@texmethods[f] = 'plain' end
['cont-en','cont-nl','cont-de','cont-it',
- 'cont-fr','cont-cz','cont-ro','cont-uk'] .each do |f| @@texmethods[f] = 'context' end
+ 'cont-fr','cont-cs','cont-ro','cont-gb',
+ 'cont-pe','cont-xp'] .each do |f| @@texmethods[f] = 'context' end
['latex','pdflatex'] .each do |f| @@texmethods[f] = 'latex' end
['plain','default','standard'] .each do |f| @@mpsmethods[f] = 'plain' end
@@ -151,28 +154,31 @@ class TEX
@@mpsmakestr['plain'] = @@platformslash + "dump"
['cont-en','cont-nl','cont-de','cont-it',
- 'cont-fr','cont-cz','cont-ro','cont-uk'] .each do |f| @@texprocstr[f] = @@platformslash + "emergencyend" end
-
- @@runoptions['aleph'] = ['--8bit']
- @@runoptions['luatex'] = ['--file-line-error']
- @@runoptions['mpost'] = ['--8bit']
- @@runoptions['pdfetex'] = ['--8bit'] # obsolete
- @@runoptions['pdftex'] = ['--8bit'] # pdftex is now pdfetex
- # @@runoptions['petex'] = []
- @@runoptions['xetex'] = ['--8bit','-output-driver="xdvipdfmx -E -d 4 -V 5"']
+ 'cont-fr','cont-cs','cont-ro','cont-gb',
+ 'cont-pe','cont-xp'] .each do |f| @@texprocstr[f] = @@platformslash + "emergencyend" end
+
+ @@runoptions['aleph'] = ['--8bit']
+ @@runoptions['luatex'] = ['--file-line-error']
+ @@runoptions['mpost'] = ['--8bit']
+ @@runoptions['pdfetex'] = ['--8bit'] # obsolete
+ @@runoptions['pdftex'] = ['--8bit'] # pdftex is now pdfetex
+ # @@runoptions['petex'] = []
+ @@runoptions['xetex'] = ['--8bit','-output-driver="xdvipdfmx -E -d 4 -V 5"']
+ @@draftoptions['pdftex'] = ['--draftmode']
+ @@synctexcoptions['pdftex'] = ['--synctex=1']
+ @@synctexcoptions['luatex'] = ['--synctex=1']
+ @@synctexcoptions['xetex'] = ['--synctex=1']
@@tcxflag['aleph'] = true
@@tcxflag['luatex'] = false
- @@tcxflag['mpost'] = true
+ @@tcxflag['mpost'] = false
@@tcxflag['pdfetex'] = true
@@tcxflag['pdftex'] = true
@@tcxflag['petex'] = false
@@tcxflag['xetex'] = false
- @@draftoptions['pdftex'] = ['--draftmode']
-
- @@booleanvars = [
- 'batchmode', 'nonstopmode', 'fast', 'fastdisabled', 'silentmode', 'final',
+ @@mainbooleanvars = [
+ 'batchmode', 'nonstopmode', 'fast', 'final',
'paranoid', 'notparanoid', 'nobanner', 'once', 'allpatterns', 'draft',
'nompmode', 'nomprun', 'automprun', 'combine',
'nomapfiles', 'local',
@@ -184,19 +190,20 @@ class TEX
'globalfile', 'autopath',
'purge', 'purgeall', 'keep', 'autopdf', 'xpdf', 'simplerun', 'verbose',
'nooptionfile', 'nobackend', 'noctx', 'utfbom',
- 'mkii',
+ 'mkii','mkiv',
+ 'synctex',
]
- @@stringvars = [
+ @@mainstringvars = [
'modefile', 'result', 'suffix', 'response', 'path',
'filters', 'usemodules', 'environments', 'separation', 'setuppath',
'arguments', 'input', 'output', 'randomseed', 'modes', 'mode', 'filename',
'ctxfile', 'printformat', 'paperformat', 'paperoffset',
'timeout', 'passon'
]
- @@standardvars = [
+ @@mainstandardvars = [
'mainlanguage', 'bodyfont', 'language'
]
- @@knownvars = [
+ @@mainknownvars = [
'engine', 'distribution', 'texformats', 'mpsformats', 'progname', 'interface',
'runs', 'backend'
]
@@ -205,29 +212,31 @@ class TEX
@@extrastringvars = []
def booleanvars
- [@@booleanvars,@@extrabooleanvars].flatten.uniq
+ [@@mainbooleanvars,@@extrabooleanvars].flatten.uniq
end
def stringvars
- [@@stringvars,@@extrastringvars].flatten.uniq
+ [@@mainstringvars,@@extrastringvars].flatten.uniq
end
def standardvars
- [@@standardvars].flatten.uniq
+ [@@mainstandardvars].flatten.uniq
end
def knownvars
- [@@knownvars].flatten.uniq
+ [@@mainknownvars].flatten.uniq
end
def allbooleanvars
- [@@booleanvars,@@extrabooleanvars].flatten.uniq
+ [@@mainbooleanvars,@@extrabooleanvars].flatten.uniq
end
def allstringvars
- [@@stringvars,@@extrastringvars,@@standardvars,@@knownvars].flatten.uniq
+ [@@mainstringvars,@@extrastringvars,@@mainstandardvars,@@mainknownvars].flatten.uniq
end
def setextrastringvars(vars)
- @@extrastringvars << vars
+ # @@extrastringvars << vars -- problems in 1.9
+ @@extrastringvars = [@@extrastringvars,vars].flatten
end
def setextrabooleanvars(vars)
- @@extrabooleanvars << vars
+ # @@extrabooleanvars << vars -- problems in 1.9
+ @@extrabooleanvars = [@@extrabooleanvars,vars].flatten
end
# def jobvariables(names=nil)
@@ -378,6 +387,7 @@ class TEX
def runoptions(engine)
options = if getvariable('draft') then @@draftoptions[engine] else [] end
+ options = if getvariable('synctex') then @@synctexcoptions[engine] else [] end
begin
if str = getvariable('passon') then
options = [options,str.split(' ')].flatten
@@ -577,7 +587,7 @@ class TEX
if data = (IO.readlines(@@luafiles) rescue nil) then
report("compiling lua files (using #{File.expand_path(@@luafiles)})")
begin
- File.makedirs(@@luatarget) rescue false
+ FileUtils.makedirs(@@luatarget) rescue false
data.each do |line|
luafile = line.chomp
lucfile = File.basename(luafile).gsub(/\..*?$/,'') + ".luc"
@@ -640,10 +650,14 @@ class TEX
report("using tex format path #{texformatpath}")
Dir.chdir(texformatpath) rescue false
if FileTest.writable?(texformatpath) then
- if texformats.length > 0 then
- makeuserfile
- makeresponsefile
- end
+ # from now on we no longer support this; we load
+ # all patterns and if someone wants another
+ # interface language ... cook up a fmt or usr file
+ #
+ # if texformats.length > 0 then
+ # makeuserfile
+ # makeresponsefile
+ # end
if texengine == 'luatex' then
cleanupluafiles
texformats.each do |texformat|
@@ -679,7 +693,8 @@ class TEX
mpsformats.each do |mpsformat|
report("generating mps format #{mpsformat}")
progname = validprogname([getvariable('progname'),mpsformat,mpsengine])
- if not runcommand([quoted(mpsengine),prognameflag(progname),iniflag,tcxflag(mpsengine),runoptions(mpsengine),mpsformat,mpsmakeextras(mpsformat)]) then
+ # if not runcommand([quoted(mpsengine),prognameflag(progname),iniflag,tcxflag(mpsengine),runoptions(mpsengine),mpsformat,mpsmakeextras(mpsformat)]) then
+ if not runcommand([quoted(mpsengine),prognameflag(progname),iniflag,runoptions(mpsengine),mpsformat,mpsmakeextras(mpsformat)]) then
setvariable('error','no format made')
end
end
@@ -879,7 +894,7 @@ class TEX
def scantexpreamble(filename)
begin
- if FileTest.file?(filename) and tex = File.open(filename) then
+ if FileTest.file?(filename) and tex = File.open(filename,'rb') then
bomdone = false
while str = tex.gets and str.chomp! do
unless bomdone then
@@ -925,7 +940,7 @@ end
end
def scantexcontent(filename)
- if FileTest.file?(filename) and tex = File.open(filename) then
+ if FileTest.file?(filename) and tex = File.open(filename,'rb') then
while str = tex.gets do
case str.chomp
when /^\%/o then
@@ -1002,7 +1017,11 @@ end
tmp << "\\starttext\n"
if forcexml then
# tmp << checkxmlfile(rawname)
- tmp << "\\processXMLfilegrouped{#{rawname}}\n"
+ if getvariable('mkiv') then
+ tmp << "\\xmlprocess{\\xmldocument}{#{rawname}}{}\n"
+ else
+ tmp << "\\processXMLfilegrouped{#{rawname}}\n"
+ end
else
tmp << "\\processfile{#{rawname}}\n"
end
@@ -1056,7 +1075,7 @@ end
end
def checkxmlfile(rawname)
- if FileTest.file?(rawname) && (xml = File.open(rawname)) then
+ if FileTest.file?(rawname) && (xml = File.open(rawname,'rb')) then
xml.each do |line|
case line
when /<\?context\-directive\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*\?>/o then
@@ -1322,12 +1341,9 @@ class TEX
# local handies
opt << "\% #{topname}\n"
opt << "\\unprotect\n"
- if getvariable('utfbom') then
- opt << "\\enableregime[utf]"
- end
- opt << "\\setupsystem[\\c!n=#{kindofrun},\\c!m=#{currentrun}]\n"
- progname = validprogname(['metafun']) # [getvariable('progname'),mpsformat,mpsengine]
- opt << "\\def\\MPOSTformatswitch\{#{prognameflag(progname)} #{formatflag('mpost')}=\}\n"
+ #
+ # feedback and basic control
+ #
if getvariable('batchmode') then
opt << "\\batchmode\n"
end
@@ -1337,6 +1353,21 @@ class TEX
if getvariable('paranoid') then
opt << "\\def\\maxreadlevel{1}\n"
end
+ if getvariable('nomapfiles') then
+ opt << "\\disablemapfiles\n"
+ end
+ if getvariable('nompmode') || getvariable('nomprun') || getvariable('automprun') then
+ opt << "\\runMPgraphicsfalse\n"
+ end
+ if getvariable('utfbom') then
+ opt << "\\enableregime[utf]"
+ end
+ progname = validprogname(['metafun']) # [getvariable('progname'),mpsformat,mpsengine]
+ opt << "\\def\\MPOSTformatswitch\{#{prognameflag(progname)} #{formatflag('mpost')}=\}\n"
+ #
+ # process info
+ #
+ opt << "\\setupsystem[\\c!n=#{kindofrun},\\c!m=#{currentrun}]\n"
if (str = File.unixfied(getvariable('modefile'))) && ! str.empty? then
opt << "\\readlocfile{#{str}}{}{}\n"
end
@@ -1353,6 +1384,35 @@ class TEX
if (str = getvariable('mainlanguage').downcase) && ! str.empty? && ! str.standard? then
opt << "\\setuplanguage[#{str}]\n"
end
+ if (str = getvariable('arguments')) && ! str.empty? then
+ opt << "\\setupenv[#{str}]\n"
+ end
+ if (str = getvariable('setuppath')) && ! str.empty? then
+ opt << "\\setupsystem[\\c!directory=\{#{str}\}]\n"
+ end
+ if (str = getvariable('randomseed')) && ! str.empty? then
+ report("using randomseed #{str}")
+ opt << "\\setupsystem[\\c!random=#{str}]\n"
+ end
+ if (str = getvariable('input')) && ! str.empty? then
+ opt << "\\setupsystem[inputfile=#{str}]\n"
+ else
+ opt << "\\setupsystem[inputfile=#{rawname}]\n"
+ end
+ #
+ # modes
+ #
+ # we handle both "--mode" and "--modes", else "--mode" is mapped onto "--modefile"
+ if (str = getvariable('modes')) && ! str.empty? then
+ opt << "\\enablemode[#{str}]\n"
+ end
+ if (str = getvariable('mode')) && ! str.empty? then
+ opt << "\\enablemode[#{str}]\n"
+ end
+ #
+ # options
+ #
+ opt << "\\startsetups *runtime:options\n"
if str = validbackend(getvariable('backend')) then
opt << "\\setupoutput[#{str}]\n"
elsif str = validbackend(getvariable('output')) then
@@ -1361,21 +1421,9 @@ class TEX
if getvariable('color') then
opt << "\\setupcolors[\\c!state=\\v!start]\n"
end
- if getvariable('nompmode') || getvariable('nomprun') || getvariable('automprun') then
- opt << "\\runMPgraphicsfalse\n"
- end
- if getvariable('fast') && ! getvariable('fastdisabled') then
- opt << "\\fastmode\n"
- end
- if getvariable('silentmode') then
- opt << "\\silentmode\n"
- end
if (str = getvariable('separation')) && ! str.empty? then
opt << "\\setupcolors[\\c!split=#{str}]\n"
end
- if (str = getvariable('setuppath')) && ! str.empty? then
- opt << "\\setupsystem[\\c!directory=\{#{str}\}]\n"
- end
if (str = getvariable('paperformat')) && ! str.empty? && ! str.standard? then
if str =~ /^([a-z]+\d+)([a-z]+\d+)$/io then # A5A4 A4A3 A2A1 ...
opt << "\\setuppapersize[#{$1.upcase}][#{$2.upcase}]\n"
@@ -1392,9 +1440,6 @@ class TEX
if getvariable('centerpage') then
opt << "\\setuplayout[\\c!location=\\v!middle,\\c!marking=\\v!on]\n"
end
- if getvariable('nomapfiles') then
- opt << "\\disablemapfiles\n"
- end
if getvariable('noarrange') then
opt << "\\setuparranging[\\v!disable]\n"
elsif getvariable('arrange') then
@@ -1412,26 +1457,6 @@ class TEX
end
opt << "\\setuparranging[#{arrangement.flatten.join(',')}]\n" if arrangement.size > 0
end
- # we handle both "--mode" and "--modes", else "--mode" is
- # mapped onto "--modefile"
- if (str = getvariable('modes')) && ! str.empty? then
- opt << "\\enablemode[#{str}]\n"
- end
- if (str = getvariable('mode')) && ! str.empty? then
- opt << "\\enablemode[#{str}]\n"
- end
- if (str = getvariable('arguments')) && ! str.empty? then
- opt << "\\setupenv[#{str}]\n"
- end
- if (str = getvariable('randomseed')) && ! str.empty? then
- report("using randomseed #{str}")
- opt << "\\setupsystem[\\c!random=#{str}]\n"
- end
- if (str = getvariable('input')) && ! str.empty? then
- opt << "\\setupsystem[inputfile=#{str}]\n"
- else
- opt << "\\setupsystem[inputfile=#{rawname}]\n"
- end
if (str = getvariable('pages')) && ! str.empty? then
if str.downcase == 'odd' then
opt << "\\chardef\\whichpagetoshipout=1\n"
@@ -1452,14 +1477,18 @@ class TEX
opt << "\\def\\pagestoshipout\{#{pagelist.join(',')}\}\n";
end
end
- opt << "\\protect\n";
- # begin getvariable('modes' ).split(',').uniq.each do |e| opt << "\\enablemode [#{e}]\n" end ; rescue ; end
+ opt << "\\stopsetups\n"
+ #
+ # styles and modules
+ #
+ opt << "\\startsetups *runtime:modules\n"
begin getvariable('filters' ).split(',').uniq.each do |f| opt << "\\useXMLfilter[#{f}]\n" end ; rescue ; end
begin getvariable('usemodules' ).split(',').uniq.each do |m| opt << "\\usemodule [#{m}]\n" end ; rescue ; end
begin getvariable('environments').split(',').uniq.each do |e| opt << "\\environment #{e} \n" end ; rescue ; end
- # this will become:
- # begin getvariable('environments').split(',').uniq.each do |e| opt << "\\useenvironment[#{e}]\n" end ; rescue ; end
- opt << "\\endinput\n"
+ opt << "\\stopsetups\n"
+ #
+ opt << "\\protect \\endinput\n"
+ #
opt.close
else
report("unable to write option file #{topname}")
@@ -1572,7 +1601,9 @@ end
if mpsengine && mpsformat then
ENV["MPXCOMMAND"] = "0" unless mpx
progname = validprogname([getvariable('progname'),mpsformat,mpsengine])
- runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),tcxflag(mpsengine),runoptions(mpsengine),mpname,mpsprocextras(mpsformat)])
+ mpname.gsub!(/\.mp$/,"") # temp bug in mp
+ # runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),tcxflag(mpsengine),runoptions(mpsengine),mpname,mpsprocextras(mpsformat)])
+ runcommand([quoted(mpsengine),prognameflag(progname),formatflag(mpsengine,mpsformat),runoptions(mpsengine),mpname,mpsprocextras(mpsformat)])
true
else
false
@@ -1582,7 +1613,7 @@ end
def runtexmp(filename,filetype='',purge=true)
checktestversion
mpname = File.suffixed(filename,filetype,'mp')
- if File.atleast?(mpname,25) then
+ if File.atleast?(mpname,10) then
# first run needed
File.silentdelete(File.suffixed(mpname,'mpt'))
doruntexmp(mpname,nil,true,purge)
@@ -1620,7 +1651,7 @@ end
end
def runtexutil(filename=[], options=['--ref','--ij','--high'], old=false)
- filename.each do |fname|
+ [filename].flatten.each do |fname|
if old then
Kpse.runscript('texutil',fname,options)
else
@@ -1980,7 +2011,7 @@ end
end
if true then # autopurge
begin
- File.open(File.suffixed(rawbase, 'tuo')) do |f|
+ File.open(File.suffixed(rawbase, 'tuo'),'rb') do |f|
ok = 0
f.each do |line|
case ok
@@ -2058,7 +2089,7 @@ end
setvariable('mp.line','')
setvariable('mp.error','')
if mpdata = File.silentread(mpname) then
- mpdata.gsub!(/^\%.*\n/o,'')
+ # mpdata.gsub!(/^\%.*\n/o,'')
File.silentrename(mpname,mpcopy)
texfound = mergebe || (mpdata =~ /btex .*? etex/mo)
if mp = openedfile(mpname) then
@@ -2075,10 +2106,11 @@ end
mp << mergebe['0'] if mergebe.key?('0')
end
end
- mp << MPTools::splitmplines(mpdata)
- mp << "\n"
- mp << "end"
+ # mp << MPTools::splitmplines(mpdata)
+ mp << mpdata
mp << "\n"
+ # mp << "end"
+ # mp << "\n"
mp.close
end
processmpx(mpname,true,true,purge) if texfound
@@ -2090,7 +2122,10 @@ end
options = ''
end
# todo plain|mpost|metafun
- ok = runmp(mpname)
+ begin
+ ok = runmp(mpname)
+ rescue
+ end
if f = File.silentopen(File.suffixed(mpname,'log')) then
while str = f.gets do
if str =~ /^l\.(\d+)\s(.*?)\n/o then
@@ -2174,30 +2209,57 @@ end
end
def checkmpgraphics(mpname)
+ # in practice the checksums will differ because of multiple instances
+ # ok, we could save the mpy/mpo files by number, but not now
mpoptions = ''
if getvariable('makempy') then
mpoptions += " --makempy "
end
+ mponame = File.suffixed(mpname,'mpo')
+ mpyname = File.suffixed(mpname,'mpy')
+ pdfname = File.suffixed(mpname,'pdf')
+ tmpname = File.suffixed(mpname,'tmp')
if getvariable('mpyforce') || getvariable('forcempy') then
mpoptions += " --force "
else
- mponame = File.suffixed(mpname,'mpo')
- mpyname = File.suffixed(mpname,'mpy')
return false unless File.atleast?(mponame,32)
mpochecksum = FileState.new.checksum(mponame)
return false if mpochecksum.empty?
# where does the checksum get into the file?
# maybe let texexec do it?
# solution: add one if not present or update when different
- if f = File.silentopen(mpyname) then
- str = f.gets.chomp
- f.close
- if str =~ /^\%\s*mpochecksum\s*\:\s*(\d+)/o then
- return false if mpochecksum == $1
+ begin
+ mpydata = IO.read(mpyname)
+ if mpydata then
+ if mpydata =~ /^\%\s*mpochecksum\s*\:\s*([A-Z0-9]+)$/mo then
+ checksum = $1
+ if mpochecksum == checksum then
+ return false
+ end
+ end
+ end
+ rescue
+ # no file
+ end
+ end
+ # return Kpse.runscript('makempy',mpname)
+ # only pdftex
+ flags = ['--noctx','--process','--batch','--once']
+ result = runtexexec([mponame], flags, 1)
+ runcommand(["pstoedit","-ssp -dt -f mpost", pdfname,tmpname])
+ tmpdata = IO.read(tmpname)
+ if tmpdata then
+ if mpy = openedfile(mpyname) then
+ mpy << "% mpochecksum: #{mpochecksum}\n"
+ tmpdata.scan(/beginfig(.*?)endfig/mo) do |s|
+ mpy << "begingraphictextfig#{s}endgraphictextfig\n"
end
+ mpy.close()
end
end
- return Kpse.runscript('makempy',mpname)
+ File.silentdelete(tmpname)
+ File.silentdelete(pdfname)
+ return true
end
def checkmplabels(mpname)
diff --git a/Master/texmf-dist/scripts/context/ruby/base/texutil.rb b/Master/texmf-dist/scripts/context/ruby/base/texutil.rb
index 063f67f2d38..868e3ca1625 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/texutil.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/texutil.rb
@@ -475,18 +475,37 @@ class TeXUtil
@@debug = false
def initialize(t, c, k, d)
- @type, @command, @key, @sortkey, @data = t, c, k, k, d
+ @type, @command, @key, @sortkey, @data = t, c, k, c, d
end
attr_reader :type, :command, :key, :data
attr_reader :sortkey
attr_writer :sortkey
+ # def build(sorter)
+ # if @key then
+ # @sortkey = sorter.normalize(sorter.tokenize(@sortkey))
+ # @sortkey = sorter.remap(sorter.simplify(@key.downcase)) # ??
+ # if @sortkey.empty? then
+ # @sortkey = sorter.remap(@command.downcase)
+ # end
+ # else
+ # @key = ""
+ # @sortkey = ""
+ # end
+ # end
+
def build(sorter)
- @sortkey = sorter.normalize(sorter.tokenize(@sortkey))
- @sortkey = sorter.remap(sorter.simplify(@key.downcase)) # ??
- if @sortkey.empty? then
- @sortkey = sorter.remap(@command.downcase)
+ if @sortkey and not @sortkey.empty? then
+ @sortkey = sorter.normalize(sorter.tokenize(@sortkey))
+ @sortkey = sorter.remap(sorter.simplify(@sortkey.downcase)) # ??
+ end
+ if not @sortkey or @sortkey.empty? then
+ @sortkey = sorter.normalize(sorter.tokenize(@key))
+ @sortkey = sorter.remap(sorter.simplify(@sortkey.downcase)) # ??
+ end
+ if not @sortkey or @sortkey.empty? then
+ @sortkey = @key.dup
end
end
@@ -618,6 +637,9 @@ class TeXUtil
if special then
@sortkey = "#{@@specialsymbol}#{@sortkey}"
end
+ if @realpage == 0 then
+ @realpage = 999999
+ end
@sortkey = [
@sortkey.downcase,
@sortkey,
@@ -687,7 +709,6 @@ class TeXUtil
alphaclass, alpha = '', ''
@@savedhowto, @@savedfrom, @@savedto, @@savedentry = '', '', '', ''
if @@debug then
- # if true then
list.each do |entry|
handle << "% [#{entry.sortkey.gsub(/#{@@split}/o,'] [')}]\n"
end
@@ -773,7 +794,8 @@ class TeXUtil
copied = true
end
@nofentries += 1 if copied
- if entry.realpage.to_i == 0 then
+ # if entry.realpage.to_i == 0 then
+ if entry.realpage.to_i == 999999 then
Register.flushsavedline(handle)
handle << "\\registersee{#{entry.type}}{#{entry.pagehowto},#{entry.texthowto}}{#{entry.seetoo}}{#{entry.page}}%\n" ;
lastpage, lastrealpage = entry.page, entry.realpage
@@ -1006,7 +1028,7 @@ class TeXUtil
tuifile = File.suffixed(filename,'tui')
if FileTest.file?(tuifile) then
report("parsing file #{tuifile}")
- if f = open(tuifile) then
+ if f = File.open(tuifile,'rb') then
f.each do |line|
case line.chomp
when /^f (.*)$/o then @plugins.reader('MyFiles', $1.splitdata)
diff --git a/Master/texmf-dist/scripts/context/ruby/base/tool.rb b/Master/texmf-dist/scripts/context/ruby/base/tool.rb
index 5ccedfec1c1..abf0d5ed05c 100644
--- a/Master/texmf-dist/scripts/context/ruby/base/tool.rb
+++ b/Master/texmf-dist/scripts/context/ruby/base/tool.rb
@@ -24,10 +24,15 @@ module Tool
t = Time.now
u = t.usec.to_s % [1..2] [0..3]
pth = t.strftime("#{mainpath}%Y%m%d-%H%M%S-#{u}-#{Process.pid}")
- if pth == $constructedtempdir
- # sleep(0.01)
- retry
- end
+ #
+ # problems with 1.9
+ #
+ # if pth == $constructedtempdir
+ # # sleep(0.01)
+ # retry
+ # end
+ pth == $constructedtempdir
+ #
Dir.mkdir(pth) if create
$constructedtempdir = pth
return pth
@@ -216,7 +221,7 @@ module Tool
def Tool.checksuffix(old)
- return old unless test(?f,old)
+ return old unless FileTest.file?(old)
new = old
diff --git a/Master/texmf-dist/scripts/context/ruby/ctxtools.rb b/Master/texmf-dist/scripts/context/ruby/ctxtools.rb
index 00d2b494bb0..67baaab39c4 100644
--- a/Master/texmf-dist/scripts/context/ruby/ctxtools.rb
+++ b/Master/texmf-dist/scripts/context/ruby/ctxtools.rb
@@ -56,7 +56,8 @@ require 'base/file'
require 'rexml/document'
require 'net/http'
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
require 'kconv'
exit if defined?(REQUIRE2LIB)
@@ -222,7 +223,7 @@ class Commands
interfaces = @commandline.arguments
if interfaces.empty? then
- interfaces = ['en','cz','de','it','nl','ro','fr']
+ interfaces = ['en','cs','de','it','nl','ro','fr']
end
interfaces.each do |interface|
@@ -305,112 +306,112 @@ class Commands
end
-class Commands
-
- include CommandBase
-
- public
-
- def translateinterface
-
- # since we know what kind of file we're dealing with,
- # we do it quick and dirty instead of using rexml or
- # xslt
-
- interfaces = @commandline.arguments
-
- if interfaces.empty? then
- interfaces = ['cz','de','it','nl','ro','fr']
- else
- interfaces.delete('en')
- end
-
- interfaces.flatten.each do |interface|
-
- variables, constants, strings, list, data = Hash.new, Hash.new, Hash.new, '', ''
-
- keyfile, intfile, outfile = "keys-#{interface}.xml", "cont-en.xml", "cont-#{interface}.xml"
-
- report("generating #{keyfile}")
-
- begin
- one = "texexec --make --all #{interface}"
- two = "texexec --batch --silent --interface=#{interface} x-set-01"
- if @commandline.option("force") then
- system(one)
- system(two)
- elsif not system(two) then
- system(one)
- system(two)
- end
- rescue
- end
-
- unless File.file?(keyfile) then
- report("no #{keyfile} generated")
- next
- end
-
- report("loading #{keyfile}")
-
- begin
- list = IO.read(keyfile)
- rescue
- list = empty
- end
-
- if list.empty? then
- report("error in loading #{keyfile}")
- next
- end
-
- list.i_load('cd:variable', variables)
- list.i_load('cd:constant', constants)
- list.i_load('cd:command' , strings)
- # list.i_load('cd:element' , strings)
-
- report("loading #{intfile}")
-
- begin
- data = IO.read(intfile)
- rescue
- data = empty
- end
-
- if data.empty? then
- report("error in loading #{intfile}")
- next
- end
-
- report("translating interface en to #{interface}")
-
- data.i_translate('cd:string' , 'value', strings)
- data.i_translate('cd:variable' , 'value', variables)
- data.i_translate('cd:parameter', 'name' , constants)
- data.i_translate('cd:constant' , 'type' , variables)
- data.i_translate('cd:variable' , 'type' , variables)
- data.i_translate('cd:inherit' , 'name' , strings)
- # data.i_translate('cd:command' , 'name' , strings)
-
- data.gsub!(/(\<cd\:interface[^\>]*?language=")en(")/) do
- $1 + interface + $2
- end
-
- report("saving #{outfile}")
-
- begin
- if f = File.open(outfile, 'w') then
- f.write(data)
- f.close
- end
- rescue
- end
-
- end
-
- end
-
-end
+# class Commands
+#
+# include CommandBase
+#
+# public
+#
+# def translateinterface
+#
+# # since we know what kind of file we're dealing with,
+# # we do it quick and dirty instead of using rexml or
+# # xslt
+#
+# interfaces = @commandline.arguments
+#
+# if interfaces.empty? then
+# interfaces = ['cs','de','it','nl','ro','fr']
+# else
+# interfaces.delete('en')
+# end
+#
+# interfaces.flatten.each do |interface|
+#
+# variables, constants, strings, list, data = Hash.new, Hash.new, Hash.new, '', ''
+#
+# keyfile, intfile, outfile = "keys-#{interface}.xml", "cont-en.xml", "cont-#{interface}.xml"
+#
+# report("generating #{keyfile}")
+#
+# begin
+# one = "texexec --make --all #{interface}"
+# two = "texexec --batch --silent --interface=#{interface} x-set-01"
+# if @commandline.option("force") then
+# system(one)
+# system(two)
+# elsif not system(two) then
+# system(one)
+# system(two)
+# end
+# rescue
+# end
+#
+# unless File.file?(keyfile) then
+# report("no #{keyfile} generated")
+# next
+# end
+#
+# report("loading #{keyfile}")
+#
+# begin
+# list = IO.read(keyfile)
+# rescue
+# list = empty
+# end
+#
+# if list.empty? then
+# report("error in loading #{keyfile}")
+# next
+# end
+#
+# list.i_load('cd:variable', variables)
+# list.i_load('cd:constant', constants)
+# list.i_load('cd:command' , strings)
+# # list.i_load('cd:element' , strings)
+#
+# report("loading #{intfile}")
+#
+# begin
+# data = IO.read(intfile)
+# rescue
+# data = empty
+# end
+#
+# if data.empty? then
+# report("error in loading #{intfile}")
+# next
+# end
+#
+# report("translating interface en to #{interface}")
+#
+# data.i_translate('cd:string' , 'value', strings)
+# data.i_translate('cd:variable' , 'value', variables)
+# data.i_translate('cd:parameter', 'name' , constants)
+# data.i_translate('cd:constant' , 'type' , variables)
+# data.i_translate('cd:variable' , 'type' , variables)
+# data.i_translate('cd:inherit' , 'name' , strings)
+# # data.i_translate('cd:command' , 'name' , strings)
+#
+# data.gsub!(/(\<cd\:interface[^\>]*?language=")en(")/) do
+# $1 + interface + $2
+# end
+#
+# report("saving #{outfile}")
+#
+# begin
+# if f = File.open(outfile, 'w') then
+# f.write(data)
+# f.close
+# end
+# rescue
+# end
+#
+# end
+#
+# end
+#
+# end
class Commands
@@ -542,6 +543,7 @@ class Commands
"log", "tmp", "run", "bck", "rlg",
"mpt", "mpx", "mpd", "mpo", "mpb",
"ctl",
+ "pgf", "synctex.gz",
"tmp.md5", "tmp.out"
]
$texonlysuffixes = [
@@ -1219,14 +1221,15 @@ class Language
def located(filename)
begin
- fname = Kpse.found(filename, 'context')
- if FileTest.file?(fname) then
- report("using file #{fname}")
- return fname
- else
- report("file #{filename} is not present")
- return nil
+ ["context","plain","latex"].each do |name| # fallbacks needed for czech patterns
+ fname = Kpse.found(filename, name)
+ if FileTest.file?(fname) then
+ report("using file #{fname}")
+ return fname
+ end
end
+ report("file #{filename} is not present")
+ return nil
rescue
report("file #{filename} cannot be located using kpsewhich")
return nil
@@ -1311,9 +1314,9 @@ class Language
remap(/\"o/, "[odiaeresis]")
remap(/\"u/, "[udiaeresis]")
when 'fr' then
- demap(/\\n\{/, "\\keep{")
- remap(/\\ae/, "[adiaeresis]")
- remap(/\\oe/, "[odiaeresis]")
+ demap(/\\n\{/, "\\delete{")
+ remap(/\\ae/, "[aeligature]")
+ remap(/\\oe/, "[oeligature]")
when 'la' then
# \lccode`'=`' somewhere else, todo
demap(/\\c\{/, "\\delete{")
@@ -1324,7 +1327,7 @@ class Language
remap("a2|", "[greekalphaiotasub]")
remap("h2|", "[greeketaiotasub]")
remap("w2|", "[greekomegaiotasub]")
- remap(">2r1<2r", "[2ῤ1ῥ]")
+ remap(">2r1<2r", "[2ῤ1ῥ]")
remap(">a2n1wdu'", "[ἀ2ν1ωδύ]")
remap(">e3s2ou'", "[ἐ3σ2ού]")
# main conversion
@@ -1561,6 +1564,8 @@ class Language
remap(/\xC0/, "[cyrillicyu]")
remap(/\xD1/, "[cyrillicya]")
remap(/\xA3/, "[cyrillicyo]")
+ when 'tr' then
+ remap(/\^\^11/, "[dotlessi]")
else
end
@@ -1632,7 +1637,7 @@ class Commands
@@languagedata['ba' ] = [ 'ec' , ['bahyph.tex'] ]
@@languagedata['ca' ] = [ 'ec' , ['cahyph.tex'] ]
@@languagedata['cy' ] = [ 'ec' , ['cyhyph.tex'] ]
- @@languagedata['cz' ] = [ 'ec' , ['czhyphen.tex','czhyphen.ex'] ]
+ @@languagedata['cs' ] = [ 'ec' , ['czhyphen.tex','czhyphen.ex'] ]
@@languagedata['de' ] = [ 'ec' , ['dehyphn.tex'] ]
@@languagedata['deo'] = [ 'ec' , ['dehypht.tex'] ]
@@languagedata['da' ] = [ 'ec' , ['dkspecial.tex','dkcommon.tex'] ]
@@ -1644,7 +1649,7 @@ class Commands
# ghyphen.readme ghyph31.readme grphyph
@@languagedata['hr' ] = [ 'ec' , ['hrhyph.tex'] ]
@@languagedata['hu' ] = [ 'ec' , ['huhyphn.tex'] ]
- @@languagedata['en' ] = [ 'default' , [['ushyphmax.tex'],['ushyph.tex'],['hyphen.tex']] ]
+ @@languagedata['us' ] = [ 'default' , [['ushyphmax.tex'],['ushyph.tex'],['hyphen.tex']] ]
@@languagedata['us' ] = [ 'default' , [['ushyphmax.tex'],['ushyph.tex'],['hyphen.tex']] ]
# inhyph.tex
@@languagedata['is' ] = [ 'ec' , ['ishyph.tex'] ]
@@ -1664,7 +1669,7 @@ class Commands
# srhyphc.tex / cyrillic
@@languagedata['sv' ] = [ 'ec' , ['svhyph.tex'] ]
@@languagedata['tr' ] = [ 'ec' , ['tkhyph.tex'] ]
- @@languagedata['uk' ] = [ 'default' , [['ukhyphen.tex'],['ukhyph.tex']] ]
+ @@languagedata['gb' ] = [ 'default' , [['ukhyphen.tex'],['ukhyph.tex']] ]
# @@languagedata['ru' ] = [ 't2a' , ['ruhyphal.tex'] ] # t2a does not work
@@languagedata['ru' ] = [ 'cyr' , ['ruhyphal.tex'] ]
end
@@ -2276,6 +2281,7 @@ class TexDeps
report("loading files")
report('')
n = 0
+# try tex and mkiv
@files.each do |filename|
if File.file?(filename) and f = File.open(filename) then
defs, uses, l = 0, 0, 0
@@ -2739,7 +2745,7 @@ commandline.registeraction('jeditinterface' , 'generate jedit syntax files [-
commandline.registeraction('bbeditinterface' , 'generate bbedit syntax files [--pipe]')
commandline.registeraction('sciteinterface' , 'generate scite syntax files [--pipe]')
commandline.registeraction('rawinterface' , 'generate raw syntax files [--pipe]')
-commandline.registeraction('translateinterface', 'generate interface files (xml) [nl de ..]')
+# commandline.registeraction('translateinterface', 'generate interface files (xml) [nl de ..]')
commandline.registeraction('purgefiles' , 'remove temporary files [--all --recurse] [basename]')
commandline.registeraction('documentation' , 'generate documentation [--type=] [filename]')
commandline.registeraction('filterpages' ) # no help, hidden temporary feature
diff --git a/Master/texmf-dist/scripts/context/ruby/fcd_start.rb b/Master/texmf-dist/scripts/context/ruby/fcd_start.rb
index 348ac75ba83..28f407c761e 100644
--- a/Master/texmf-dist/scripts/context/ruby/fcd_start.rb
+++ b/Master/texmf-dist/scripts/context/ruby/fcd_start.rb
@@ -260,6 +260,7 @@ class FastCD
puts(Dir.pwd.gsub(/\\/o, '/'))
end
rescue
+ puts("some error")
end
end
@@ -272,6 +273,7 @@ class FastCD
else
f.puts("cd #{dir.gsub("\\",'/')}")
end
+ f.close
end
@result = dir
report("changing to #{dir}",true)
@@ -283,6 +285,7 @@ class FastCD
end
def choose(args=[])
+ offset = 97
unless @pattern.empty? then
begin
case @result.size
@@ -301,7 +304,7 @@ class FastCD
return
end
else
- index = answer[0] - ?a
+ index = answer[0] - offset
if dir = list[index] then
chdir(dir)
return
@@ -309,21 +312,27 @@ class FastCD
end
end
rescue
+ puts("some error")
end
loop do
print("\n")
list.each_index do |i|
+begin
if i < @@maxlength then
- puts("#{(i+?a).chr} #{list[i]}")
+ # puts("#{(i+?a).chr} #{list[i]}")
+ puts("#{(i+offset).chr} #{list[i]}")
else
puts("\n there are #{list.length-@@maxlength} entries more")
break
end
+rescue
+ puts("some error")
+end
end
print("\n>> ")
if answer = wait then
- if answer >= ?a and answer <= ?z then
- index = answer - ?a
+ if answer >= offset and answer <= offset+25 then
+ index = answer - offset
if dir = list[index] then
print("#{answer.chr} ")
chdir(dir)
@@ -350,7 +359,7 @@ class FastCD
end
end
rescue
- # report($!)
+ report($!)
end
end
end
diff --git a/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb b/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb
index cb3d016f4cb..6143c881232 100644
--- a/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb
+++ b/Master/texmf-dist/scripts/context/ruby/graphics/gs.rb
@@ -13,7 +13,8 @@
require 'base/variables'
require 'base/system'
-require 'ftools'
+require 'fileutils'
+# Require 'ftools'
class GhostScript
@@ -218,15 +219,15 @@ class GhostScript
rescue
report("job aborted due to some error: #{$!}")
begin
- File.delete(resultfile) if test(?e,resultfile)
+ File.delete(resultfile) if FileTest.file?(resultfile)
rescue
report("unable to delete faulty #{resultfile}")
end
ok = false
ensure
deleteprofile(getvariable('profile'))
- File.delete(@@pstempfile) if test(?e,@@pstempfile)
- File.delete(@@pdftempfile) if test(?e,@@pdftempfile)
+ File.delete(@@pstempfile) if FileTest.file?(@@pstempfile)
+ File.delete(@@pdftempfile) if FileTest.file?(@@pdftempfile)
end
return ok
end
@@ -243,13 +244,14 @@ class GhostScript
def pdfmethod? (str)
case method(str).to_i
- when 3, 4, 5 then return true
+ when 1, 3, 4, 5 then return true
end
return false
end
def pdfprefix (str)
case method(str).to_i
+ when 1 then return 'raw-'
when 4 then return 'lowres-'
when 5 then return 'normal-'
end
@@ -383,7 +385,7 @@ class GhostScript
debug('piping data')
unless pipebounded(tmp,eps) then
debug('something went wrong in the pipe')
- File.delete(outfile) if test(?e,outfile)
+ File.delete(outfile) if FileTest.file?(outfile)
end
debug('closing pipe')
eps.close_write
@@ -412,7 +414,7 @@ class GhostScript
unless ok then
begin
report('no output file due to error')
- File.delete(outfile) if test(?e,outfile)
+ File.delete(outfile) if FileTest.file?(outfile)
rescue
# debug("fatal error: #{$!}")
debug('file',outfile,'may be invalid')
@@ -421,7 +423,7 @@ class GhostScript
debug('deleting temp file')
begin
- File.delete(@@pstempfile) if test(?e,@@pstempfile)
+ File.delete(@@pstempfile) if FileTest.file?(@@pstempfile)
rescue
end
@@ -467,7 +469,7 @@ class GhostScript
# def convertcropped (inpfile, outfile)
# report("converting #{inpfile} cropped")
# do_convertbounded(inpfile, @@pdftempfile)
- # return unless test(?e,@@pdftempfile)
+ # return unless FileTest.file?(@@pdftempfile)
# arguments = " --offset=#{@offset} #{@@pdftempfile} #{outfile}"
# report("calling #{@@pdftrimwhite}")
# unless ok = System.run(@@pdftrimwhite,arguments) then
diff --git a/Master/texmf-dist/scripts/context/ruby/kpseclient.rb b/Master/texmf-dist/scripts/context/ruby/kpseclient.rb
deleted file mode 100755
index 547cd9feed8..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/kpseclient.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env ruby
-
-# require 'profile'
-
-n, t = 100, Time.now
-
-$: << File.dirname(File.expand_path($0))
-
-require 'base/kpseremote'
-
-kpse = KpseRemote.new # fast start (.25 sec)
-# kpse = KpseRemote::fetch # slow start (1-2 sec)
-
-# puts kpse.inspect
-
-exit unless kpse
-
-if true then
-
- n = 1
- loop do
- str = kpse.find_file('texnansi.enc')
- puts("#{n}: #{str}")
- n += 1
- sleep(1)
- end
-
-else
-
- print((Time.now-t).to_s + ' ')
- n.times do |i|
- str = kpse.find_file('texnansi.enc')
- if i == 0 then
- print(Time.now-t).to_s + ' '
- print str
- else
- print ' .'
- end
- end
- puts ' ' + ((Time.now-t)/n).to_s
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/kpseserver.rb b/Master/texmf-dist/scripts/context/ruby/kpseserver.rb
deleted file mode 100755
index b9fe2d8a6b8..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/kpseserver.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env ruby
-
-$: << File.dirname(File.expand_path($0))
-
-require 'base/kpseremote'
-
-KpseRemote::start_server
diff --git a/Master/texmf-dist/scripts/context/ruby/pdftools.rb b/Master/texmf-dist/scripts/context/ruby/pdftools.rb
index 23edfeca226..8ad74ec4f36 100644
--- a/Master/texmf-dist/scripts/context/ruby/pdftools.rb
+++ b/Master/texmf-dist/scripts/context/ruby/pdftools.rb
@@ -20,7 +20,8 @@ $: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.u
require 'base/switch'
require 'base/logger'
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
class File
diff --git a/Master/texmf-dist/scripts/context/ruby/rlxtools.rb b/Master/texmf-dist/scripts/context/ruby/rlxtools.rb
index 1617fcad4ae..6a5c5ca2098 100644
--- a/Master/texmf-dist/scripts/context/ruby/rlxtools.rb
+++ b/Master/texmf-dist/scripts/context/ruby/rlxtools.rb
@@ -19,7 +19,8 @@ require 'base/logger'
require 'base/system'
require 'base/kpse'
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
require 'rexml/document'
class Commands
@@ -111,7 +112,7 @@ class Commands
REXML::XPath.each(localsteps,tag) do |extras|
REXML::XPath.each(extras,"rl:value") do |value|
if name = value.attributes['name'] then
- substititute(value,variables[name.to_s] || '')
+ substitute(value,variables[name.to_s] || '')
end
end
end
@@ -144,7 +145,7 @@ class Commands
end
REXML::XPath.each(command,"rl:value") do |value|
if name = value.attributes['name'] then
- substititute(value,variables[name.to_s])
+ substitute(value,variables[name.to_s])
end
end
str = justtext(command.to_s)
@@ -203,7 +204,7 @@ class Commands
return str.strip
end
- def substititute(value,str='')
+ def substitute(value,str='')
if str then
begin
if value.attributes.key?('method') then
diff --git a/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb b/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb
index f3abfdfc703..6d7c7aba0a5 100644
--- a/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb
+++ b/Master/texmf-dist/scripts/context/ruby/rsfiltool.rb
@@ -18,7 +18,8 @@ end
# todo : split session stuff from xmpl/base into an xmpl/session module and "include xmpl/session" into base and here and ...
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
require 'xmpl/base'
require 'xmpl/switch'
require 'xmpl/request'
diff --git a/Master/texmf-dist/scripts/context/ruby/rslibtool.rb b/Master/texmf-dist/scripts/context/ruby/rslibtool.rb
deleted file mode 100644
index 7ae5efb647e..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/rslibtool.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# program : rslibtool
-# copyright : PRAGMA Publishing On Demand
-# version : 1.00 - 2002
-# author : Hans Hagen
-#
-# project : eXaMpLe
-# concept : Hans Hagen
-# info : j.hagen@xs4all.nl
-# www : www.pragma-pod.com / www.pragma-ade.com
-
-# --add --base=filename --path=directory pattern
-# --remove --base=filename --path=directory label
-# --sort --base=filename --path=directory
-# --purge --base=filename --path=directory
-# --dummy --base=filename
-# --namespace
-
-# rewrite
-
-unless defined? ownpath
- ownpath = $0.sub(/[\\\/]\w*?\.rb/i,'')
- $: << ownpath
-end
-
-require 'rslb/base'
-require 'xmpl/base'
-require 'xmpl/switch'
-
-session = Example.new('rslbtool', '1.0', 'PRAGMA POD')
-
-session.identify
-
-commandline = CommandLine.new
-
-commandline.registerflag('add')
-commandline.registerflag('remove')
-commandline.registerflag('delete')
-commandline.registerflag('sort')
-commandline.registerflag('purge')
-commandline.registerflag('dummy')
-commandline.registerflag('process')
-commandline.registerflag('namespace')
-
-commandline.registervalue('prefix')
-commandline.registervalue('base')
-commandline.registervalue('path')
-commandline.registervalue('result')
-commandline.registervalue('texexec')
-commandline.registervalue('zipalso')
-
-commandline.expand
-
-session.inherit(commandline)
-
-base = session.get('option.base')
-path = session.get('option.path')
-
-base = 'rslbtool.xml' if base.empty?
-
-# when path is given, assume that arg list is list of
-# suffixes, else assume it is a list of globbed filespec
-
-if path.empty?
- base += '.xml' unless base =~ /\..+$/
- list = commandline.arguments
-else
- Dir.chdir(File.dirname(path))
- list = Dir.glob("*.{#{commandline.arguments.join(',')}}")
-end
-
-begin
- reslib = Resource.new(base,session.get('option.namespace'))
- reslib.load(base)
-rescue
- session.error('problems with loading base')
- exit
-end
-
-unless session.get('option.texexec').empty?
- reslib.set_texexec(session.get('option.texexec'))
-end
-
-if session.get('option.add')
-
- session.report('adding records', list)
- reslib.add_figures(list,session.get('option.prefix'))
-
-elsif session.get('option.remove') or session.get('option.delete')
-
- session.report('removing records')
- reslib.delete_figures(list)
-
-elsif session.get('option.sort')
-
- session.report('sorting records')
- reslib.sort_figures()
-
-elsif session.get('option.purge')
-
- session.report('purging records')
- reslib.purge_figures()
-
-elsif session.get('option.dummy')
-
- session.report('creating dummy records')
- reslib.create_dummies(session.get('option.process'),session.get('option.result'),session.get('option.zipalso'))
-
-else
-
- session.warning('provide action')
-
-end
-
-reslib.save(base)
diff --git a/Master/texmf-dist/scripts/context/ruby/runtools.rb b/Master/texmf-dist/scripts/context/ruby/runtools.rb
index 9c504845ab1..5565748e272 100644
--- a/Master/texmf-dist/scripts/context/ruby/runtools.rb
+++ b/Master/texmf-dist/scripts/context/ruby/runtools.rb
@@ -1,5 +1,6 @@
require 'timeout'
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
require 'rbconfig'
class File
diff --git a/Master/texmf-dist/scripts/context/ruby/texexec.rb b/Master/texmf-dist/scripts/context/ruby/texexec.rb
index a09572c6caa..94b14e4283c 100644
--- a/Master/texmf-dist/scripts/context/ruby/texexec.rb
+++ b/Master/texmf-dist/scripts/context/ruby/texexec.rb
@@ -1,8 +1,12 @@
-banner = ['TeXExec', 'version 6.2.0', '1997-2006', 'PRAGMA ADE/POD']
+#!/usr/bin/env ruby
+#encoding: ASCII-8BIT
+
+banner = ['TeXExec', 'version 6.2.1', '1997-2009', 'PRAGMA ADE/POD']
$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
-require 'ftools' # needed ?
+require 'fileutils'
+# require 'ftools' # needed ?
require 'base/switch'
require 'base/logger'
@@ -277,6 +281,7 @@ class Commands
info = `pdfinfo #{filename}`
if info =~ /Pages:\s*(\d+)/ then
nofpages = $1.to_i
+ result = @commandline.checkedoption('result','texexec')
nofpages.times do |i|
if f = File.open(tempfile,"w") then
n = i + 1
@@ -285,8 +290,10 @@ class Commands
f << "\\externalfigure[#{filename}][object=no,page=#{n}]\n"
f << "\\stopTEXpage\\stoptext\n"
f.close
+ job.setvariable('result',"#{result}-#{n}")
job.setvariable('interface','english') # redundant
job.setvariable('simplerun',true)
+ job.setvariable('purge',true)
job.setvariable('files',[tempfile])
job.processtex
end
diff --git a/Master/texmf-dist/scripts/context/ruby/texmfstart.rb b/Master/texmf-dist/scripts/context/ruby/texmfstart.rb
index e43929213f5..97087c3ae40 100755
--- a/Master/texmf-dist/scripts/context/ruby/texmfstart.rb
+++ b/Master/texmf-dist/scripts/context/ruby/texmfstart.rb
@@ -1,4 +1,9 @@
#!/usr/bin/env ruby
+#encoding: ASCII-8BIT
+
+# We have removed the fast, server and client variants and no longer
+# provide the distributed 'serve trees' option. After all, we're now
+# using luatex.
# program : texmfstart
# copyright : PRAGMA Advanced Document Engineering
@@ -18,8 +23,6 @@
# Of couse I can make this into a nice class, which i'll undoubtely will
# do when I feel the need. In that case it will be part of a bigger game.
-# turning this into a service would be nice, so some day ...
-
# --locate => provides location
# --exec => exec instead of system
# --iftouched=a,b => only if timestamp a<>b
@@ -34,30 +37,17 @@
$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
require "rbconfig"
-require "md5"
+require "fileutils"
-# funny, selfmergs was suddenly broken to case problems
-
-# kpse_merge_done: require 'base/kpseremote'
-# kpse_merge_done: require 'base/kpsedirect'
-# kpse_merge_done: require 'base/kpsefast'
-# kpse_merge_done: require 'base/merge'
+require "digest/md5"
# kpse_merge_start
-# kpse_merge_file: 't:/ruby/base/kpsefast.rb'
-
-# module : base/kpsefast
-# copyright : PRAGMA Advanced Document Engineering
-# version : 2005
-# author : Hans Hagen
-#
-# project : ConTeXt / eXaMpLe
-# concept : Hans Hagen
-# info : j.hagen@xs4all.nl
-
-# todo: multiple cnf files
-#
+class File
+ def File::makedirs(*x)
+ FileUtils.makedirs(x)
+ end
+end
class String
@@ -106,1147 +96,6 @@ class File
end
-module KpseUtil
-
- # to be adapted, see loading cnf file
-
- @@texmftrees = ['texmf-local','texmf.local','../..','texmf'] # '../..' is for gwtex
- @@texmfcnf = 'texmf.cnf'
-
- def KpseUtil::identify
- # we mainly need to identify the local tex stuff and wse assume that
- # the texmfcnf variable is set; otherwise we need to expand the
- # TEXMF variable and that takes time since it may involve more
- ownpath = File.expand_path($0)
- if ownpath.gsub!(/texmf.*?$/o, '') then
- ENV['SELFAUTOPARENT'] = ownpath
- else
- ENV['SELFAUTOPARENT'] = '.' # fall back
- # may be too tricky:
- #
- # (ENV['PATH'] ||'').split_path.each do |p|
- # if p.gsub!(/texmf.*?$/o, '') then
- # ENV['SELFAUTOPARENT'] = p
- # break
- # end
- # end
- end
- filenames = Array.new
- if ENV['TEXMFCNF'] && ! ENV['TEXMFCNF'].empty? then
- ENV['TEXMFCNF'].to_s.split_path.each do |path|
- filenames << File.join(path,@@texmfcnf)
- end
- elsif ENV['SELFAUTOPARENT'] == '.' then
- filenames << File.join('.',@@texmfcnf)
- else
- @@texmftrees.each do |tree|
- filenames << File.join(ENV['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf)
- end
- end
- loop do
- busy = false
- filenames.collect! do |f|
- f.gsub(/\$([a-zA-Z0-9\_\-]+)/o) do
- if (! ENV[$1]) || (ENV[$1] == $1) then
- "$#{$1}"
- else
- busy = true
- ENV[$1]
- end
- end
- end
- break unless busy
- end
- filenames.delete_if do |f|
- ! FileTest.file?(f)
- end
- return filenames
- end
-
- def KpseUtil::environment
- Hash.new.merge(ENV)
- end
-
-end
-
-class KpseFast
-
- # formats are an incredible inconsistent mess
-
- @@suffixes = Hash.new
- @@formats = Hash.new
- @@suffixmap = Hash.new
-
- @@texmfcnf = 'texmf.cnf'
-
- @@suffixes['gf'] = ['.<resolution>gf'] # todo
- @@suffixes['pk'] = ['.<resolution>pk'] # todo
- @@suffixes['tfm'] = ['.tfm']
- @@suffixes['afm'] = ['.afm']
- @@suffixes['base'] = ['.base']
- @@suffixes['bib'] = ['.bib']
- @@suffixes['bst'] = ['.bst']
- @@suffixes['cnf'] = ['.cnf']
- @@suffixes['ls-R'] = ['ls-R', 'ls-r']
- @@suffixes['fmt'] = ['.fmt', '.efmt', '.efm', '.ofmt', '.ofm', '.oft', '.eofmt', '.eoft', '.eof', '.pfmt', '.pfm', '.epfmt', '.epf', '.xpfmt', '.xpf', '.afmt', '.afm']
- @@suffixes['map'] = ['.map']
- @@suffixes['mem'] = ['.mem']
- @@suffixes['mf'] = ['.mf']
- @@suffixes['mfpool'] = ['.pool']
- @@suffixes['mft'] = ['.mft']
- @@suffixes['mp'] = ['.mp']
- @@suffixes['mppool'] = ['.pool']
- @@suffixes['ocp'] = ['.ocp']
- @@suffixes['ofm'] = ['.ofm', '.tfm']
- @@suffixes['opl'] = ['.opl']
- @@suffixes['otp'] = ['.otp']
- @@suffixes['ovf'] = ['.ovf']
- @@suffixes['ovp'] = ['.ovp']
- @@suffixes['graphic/figure'] = ['.eps', '.epsi']
- @@suffixes['tex'] = ['.tex']
- @@suffixes['texpool'] = ['.pool']
- @@suffixes['PostScript header'] = ['.pro']
- @@suffixes['type1 fonts'] = ['.pfa', '.pfb']
- @@suffixes['vf'] = ['.vf']
- @@suffixes['ist'] = ['.ist']
- @@suffixes['truetype fonts'] = ['.ttf', '.ttc']
- @@suffixes['web'] = ['.web', '.ch']
- @@suffixes['cweb'] = ['.w', '.web', '.ch']
- @@suffixes['enc files'] = ['.enc']
- @@suffixes['cmap files'] = ['.cmap']
- @@suffixes['subfont definition files'] = ['.sfd']
- @@suffixes['lig files'] = ['.lig']
- @@suffixes['bitmap font'] = []
- @@suffixes['MetaPost support'] = []
- @@suffixes['TeX system documentation'] = []
- @@suffixes['TeX system sources'] = []
- @@suffixes['Troff fonts'] = []
- @@suffixes['dvips config'] = []
- @@suffixes['type42 fonts'] = []
- @@suffixes['web2c files'] = []
- @@suffixes['other text files'] = []
- @@suffixes['other binary files'] = []
- @@suffixes['misc fonts'] = []
- @@suffixes['opentype fonts'] = []
- @@suffixes['pdftex config'] = []
- @@suffixes['texmfscripts'] = []
-
- # replacements
-
- @@suffixes['fmt'] = ['.fmt']
- @@suffixes['type1 fonts'] = ['.pfa', '.pfb', '.pfm']
- @@suffixes['tex'] = ['.tex', '.xml']
- @@suffixes['texmfscripts'] = ['rb','lua','py','pl']
-
- @@suffixes.keys.each do |k| @@suffixes[k].each do |s| @@suffixmap[s] = k end end
-
- # TTF2TFMINPUTS
- # MISCFONTS
- # TEXCONFIG
- # DVIPDFMINPUTS
- # OTFFONTS
-
- @@formats['gf'] = ''
- @@formats['pk'] = ''
- @@formats['tfm'] = 'TFMFONTS'
- @@formats['afm'] = 'AFMFONTS'
- @@formats['base'] = 'MFBASES'
- @@formats['bib'] = ''
- @@formats['bst'] = ''
- @@formats['cnf'] = ''
- @@formats['ls-R'] = ''
- @@formats['fmt'] = 'TEXFORMATS'
- @@formats['map'] = 'TEXFONTMAPS'
- @@formats['mem'] = 'MPMEMS'
- @@formats['mf'] = 'MFINPUTS'
- @@formats['mfpool'] = 'MFPOOL'
- @@formats['mft'] = ''
- @@formats['mp'] = 'MPINPUTS'
- @@formats['mppool'] = 'MPPOOL'
- @@formats['ocp'] = 'OCPINPUTS'
- @@formats['ofm'] = 'OFMFONTS'
- @@formats['opl'] = 'OPLFONTS'
- @@formats['otp'] = 'OTPINPUTS'
- @@formats['ovf'] = 'OVFFONTS'
- @@formats['ovp'] = 'OVPFONTS'
- @@formats['graphic/figure'] = ''
- @@formats['tex'] = 'TEXINPUTS'
- @@formats['texpool'] = 'TEXPOOL'
- @@formats['PostScript header'] = 'TEXPSHEADERS'
- @@formats['type1 fonts'] = 'T1FONTS'
- @@formats['vf'] = 'VFFONTS'
- @@formats['ist'] = ''
- @@formats['truetype fonts'] = 'TTFONTS'
- @@formats['web'] = ''
- @@formats['cweb'] = ''
- @@formats['enc files'] = 'ENCFONTS'
- @@formats['cmap files'] = 'CMAPFONTS'
- @@formats['subfont definition files'] = 'SFDFONTS'
- @@formats['lig files'] = 'LIGFONTS'
- @@formats['bitmap font'] = ''
- @@formats['MetaPost support'] = ''
- @@formats['TeX system documentation'] = ''
- @@formats['TeX system sources'] = ''
- @@formats['Troff fonts'] = ''
- @@formats['dvips config'] = ''
- @@formats['type42 fonts'] = 'T42FONTS'
- @@formats['web2c files'] = 'WEB2C'
- @@formats['other text files'] = ''
- @@formats['other binary files'] = ''
- @@formats['misc fonts'] = ''
- @@formats['opentype fonts'] = 'OPENTYPEFONTS'
- @@formats['pdftex config'] = 'PDFTEXCONFIG'
- @@formats['texmfscripts'] = 'TEXMFSCRIPTS'
-
- attr_accessor :progname, :engine, :format, :rootpath, :treepath,
- :verbose, :remember, :scandisk, :diskcache, :renewcache
-
- @@cacheversion = '1'
-
- def initialize
- @rootpath = ''
- @treepath = ''
- @progname = 'kpsewhich'
- @engine = 'pdftex'
- @variables = Hash.new
- @expansions = Hash.new
- @files = Hash.new
- @found = Hash.new
- @kpsevars = Hash.new
- @lsrfiles = Array.new
- @cnffiles = Array.new
- @verbose = true
- @remember = true
- @scandisk = true
- @diskcache = true
- @renewcache = false
- @isolate = false
-
- @diskcache = false
- @cachepath = nil
- @cachefile = 'tmftools.log'
-
- @environment = ENV
- end
-
- def set(key,value)
- case key
- when 'progname' then @progname = value
- when 'engine' then @engine = value
- when 'format' then @format = value
- end
- end
-
- def push_environment(env)
- @environment = env
- end
-
- # {$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
- #
- # $SELFAUTOLOC : /usr/tex/bin/platform
- # $SELFAUTODIR : /usr/tex/bin
- # $SELFAUTOPARENT : /usr/tex
- #
- # since we live in scriptpath we need a slightly different method
-
- def load_cnf(filenames=nil)
- unless filenames then
- ownpath = File.expand_path($0)
- if ownpath.gsub!(/texmf.*?$/o, '') then
- @environment['SELFAUTOPARENT'] = ownpath
- else
- @environment['SELFAUTOPARENT'] = '.'
- end
- unless @treepath.empty? then
- unless @rootpath.empty? then
- @treepath = @treepath.split(',').collect do |p| File.join(@rootpath,p) end.join(',')
- end
- @environment['TEXMF'] = @treepath
- # only the first one
- @environment['TEXMFCNF'] = File.join(@treepath.split(',').first,'texmf/web2c')
- end
- unless @rootpath.empty? then
- @environment['TEXMFCNF'] = File.join(@rootpath,'texmf/web2c')
- @environment['SELFAUTOPARENT'] = @rootpath
- @isolate = true
- end
- filenames = Array.new
- if @environment['TEXMFCNF'] and not @environment['TEXMFCNF'].empty? then
- @environment['TEXMFCNF'].to_s.split_path.each do |path|
- filenames << File.join(path,@@texmfcnf)
- end
- elsif @environment['SELFAUTOPARENT'] == '.' then
- filenames << File.join('.',@@texmfcnf)
- else
- ['texmf-local','texmf'].each do |tree|
- filenames << File.join(@environment['SELFAUTOPARENT'],tree,'web2c',@@texmfcnf)
- end
- end
- end
- # <root>/texmf/web2c/texmf.cnf
- filenames = _expanded_path_(filenames)
- @rootpath = filenames.first
- 3.times do
- @rootpath = File.dirname(@rootpath)
- end
- filenames.collect! do |f|
- f.gsub("\\", '/')
- end
- filenames.each do |fname|
- if FileTest.file?(fname) and f = File.open(fname) then
- @cnffiles << fname
- while line = f.gets do
- loop do
- # concatenate lines ending with \
- break unless line.sub!(/\\\s*$/o) do
- f.gets || ''
- end
- end
- case line
- when /^[\%\#]/o then
- # comment
- when /^\s*(.*?)\s*\=\s*(.*?)\s*$/o then
- key, value = $1, $2
- unless @variables.key?(key) then
- value.sub!(/\%.*$/,'')
- value.sub!(/\~/, "$HOME")
- @variables[key] = value
- end
- @kpsevars[key] = true
- end
- end
- f.close
- end
- end
- end
-
- def load_lsr
- @lsrfiles = []
- simplified_list(expansion('TEXMF')).each do |p|
- ['ls-R','ls-r'].each do |f|
- filename = File.join(p,f)
- if FileTest.file?(filename) then
- @lsrfiles << [filename,File.size(filename)]
- break
- end
- end
- end
- @files = Hash.new
- if @diskcache then
- ['HOME','TEMP','TMP','TMPDIR'].each do |key|
- if @environment[key] then
- if FileTest.directory?(@environment[key]) then
- @cachepath = @environment[key]
- @cachefile = [@rootpath.gsub(/[^A-Z0-9]/io, '-').gsub(/\-+/,'-'),File.basename(@cachefile)].join('-')
- break
- end
- end
- end
- if @cachepath and not @renewcache and FileTest.file?(File.join(@cachepath,@cachefile)) then
- begin
- if f = File.open(File.join(@cachepath,@cachefile)) then
- cacheversion = Marshal.load(f)
- if cacheversion == @@cacheversion then
- lsrfiles = Marshal.load(f)
- if lsrfiles == @lsrfiles then
- @files = Marshal.load(f)
- end
- end
- f.close
- end
- rescue
- @files = Hash.new
- end
- end
- end
- return if @files.size > 0
- @lsrfiles.each do |filedata|
- filename, filesize = filedata
- filepath = File.dirname(filename)
- begin
- path = '.'
- data = IO.readlines(filename)
- if data[0].chomp =~ /% ls\-R \-\- filename database for kpathsea\; do not change this line\./io then
- data.each do |line|
- case line
- when /^[a-zA-Z0-9]/o then
- line.chomp!
- if @files[line] then
- @files[line] << path
- else
- @files[line] = [path]
- end
- when /^\.\/(.*?)\:$/o then
- path = File.join(filepath,$1)
- end
- end
- end
- rescue
- # sorry
- end
- end
- if @diskcache and @cachepath and f = File.open(File.join(@cachepath,@cachefile),'wb') then
- f << Marshal.dump(@@cacheversion)
- f << Marshal.dump(@lsrfiles)
- f << Marshal.dump(@files)
- f.close
- end
- end
-
- def expand_variables
- @expansions = Hash.new
- if @isolate then
- @variables['TEXMFCNF'] = @environment['TEXMFCNF'].dup
- @variables['SELFAUTOPARENT'] = @environment['SELFAUTOPARENT'].dup
- else
- @environment.keys.each do |e|
- if e =~ /^([a-zA-Z]+)\_(.*)\s*$/o then
- @expansions["#{$1}.#{$2}"] = (@environment[e] ||'').dup
- else
- @expansions[e] = (@environment[e] ||'').dup
- end
- end
- end
- @variables.keys.each do |k|
- @expansions[k] = @variables[k].dup unless @expansions[k]
- end
- loop do
- busy = false
- @expansions.keys.each do |k|
- @expansions[k].gsub!(/\$([a-zA-Z0-9\_\-]*)/o) do
- busy = true
- @expansions[$1] || ''
- end
- @expansions[k].gsub!(/\$\{([a-zA-Z0-9\_\-]*)\}/o) do
- busy = true
- @expansions[$1] || ''
- end
- end
- break unless busy
- end
- @expansions.keys.each do |k|
- @expansions[k] = @expansions[k].gsub("\\", '/')
- end
- end
-
- def variable(name='')
- (name and not name.empty? and @variables[name.sub('$','')]) or ''
- end
-
- def expansion(name='')
- (name and not name.empty? and @expansions[name.sub('$','')]) or ''
- end
-
- def variable?(name='')
- name and not name.empty? and @variables.key?(name.sub('$',''))
- end
-
- def expansion?(name='')
- name and not name.empty? and @expansions.key?(name.sub('$',''))
- end
-
- def simplified_list(str)
- lst = str.gsub(/^\{/o,'').gsub(/\}$/o,'').split(",")
- lst.collect do |l|
- l.sub(/^[\!]*/,'').sub(/[\/\\]*$/o,'')
- end
- end
-
- def original_variable(variable)
- if variable?("#{@progname}.#{variable}") then
- variable("#{@progname}.#{variable}")
- elsif variable?(variable) then
- variable(variable)
- else
- ''
- end
- end
-
- def expanded_variable(variable)
- if expansion?("#{variable}.#{@progname}") then
- expansion("#{variable}.#{@progname}")
- elsif expansion?(variable) then
- expansion(variable)
- else
- ''
- end
- end
-
- def original_path(filename='')
- _expanded_path_(original_variable(var_of_format_or_suffix(filename)).split(";"))
- end
-
- def expanded_path(filename='')
- _expanded_path_(expanded_variable(var_of_format_or_suffix(filename)).split(";"))
- end
-
- def _expanded_path_(pathlist)
- i, n = 0, 0
- pathlist.collect! do |mainpath|
- mainpath.gsub(/([\{\}])/o) do
- if $1 == "{" then
- i += 1 ; n = i if i > n ; "<#{i}>"
- else
- i -= 1 ; "</#{i+1}>"
- end
- end
- end
- n.times do |i|
- loop do
- more = false
- newlist = []
- pathlist.each do |path|
- unless path.sub!(/^(.*?)<(#{n-i})>(.*?)<\/\2>(.*?)$/) do
- pre, mid, post = $1, $3, $4
- mid.gsub!(/\,$/,',.')
- mid.split(',').each do |m|
- more = true
- if m == '.' then
- newlist << "#{pre}#{post}"
- else
- newlist << "#{pre}#{m}#{post}"
- end
- end
- end then
- newlist << path
- end
- end
- if more then
- pathlist = [newlist].flatten # copy -)
- else
- break
- end
- end
- end
- pathlist = pathlist.uniq.collect do |path|
- p = path
- # p.gsub(/^\/+/o) do '' end
- # p.gsub!(/(.)\/\/(.)/o) do "#{$1}/#{$2}" end
- # p.gsub!(/\/\/+$/o) do '//' end
- p.gsub!(/\/\/+/o) do '//' end
- p
- end
- pathlist
- end
-
- # todo: ignore case
-
- def var_of_format(str)
- @@formats[str] || ''
- end
-
- def var_of_suffix(str) # includes .
- if @@suffixmap.key?(str) then @@formats[@@suffixmap[str]] else '' end
- end
-
- def var_of_format_or_suffix(str)
- if @@formats.key?(str) then
- @@formats[str]
- elsif @@suffixmap.key?(File.extname(str)) then # extname includes .
- @@formats[@@suffixmap[File.extname(str)]] # extname includes .
- else
- ''
- end
- end
-
-end
-
-class KpseFast
-
- # test things
-
- def list_variables(kpseonly=true)
- @variables.keys.sort.each do |k|
- if kpseonly then
- puts("#{k} = #{@variables[k]}") if @kpsevars[k]
- else
- puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@variables[k]}")
- end
- end
- end
-
- def list_expansions(kpseonly=true)
- @expansions.keys.sort.each do |k|
- if kpseonly then
- puts("#{k} = #{@expansions[k]}") if @kpsevars[k]
- else
- puts("#{if @kpsevars[k] then 'K' else 'E' end} #{k} = #{@expansions[k]}")
- end
- end
- end
-
- def list_lsr
- puts("files = #{@files.size}")
- end
-
- def set_test_patterns
- @variables["KPSE_TEST_PATTERN_A"] = "foo/{1,2}/bar//"
- @variables["KPSE_TEST_PATTERN_B"] = "!!x{A,B{1,2}}y"
- @variables["KPSE_TEST_PATTERN_C"] = "x{A,B//{1,2}}y"
- @variables["KPSE_TEST_PATTERN_D"] = "x{A,B//{1,2,}}//y"
- end
-
- def show_test_patterns
- ['A','B','D'].each do |i|
- puts ""
- puts @variables ["KPSE_TEST_PATTERN_#{i}"]
- puts ""
- puts expand_path("KPSE_TEST_PATTERN_#{i}").split_path
- puts ""
- end
- end
-
-end
-
-class KpseFast
-
- # kpse stuff
-
- def expand_braces(str) # output variable and brace expansion of STRING.
- _expanded_path_(original_variable(str).split_path).join_path
- end
-
- def expand_path(str) # output complete path expansion of STRING.
- _expanded_path_(expanded_variable(str).split_path).join_path
- end
-
- def expand_var(str) # output variable expansion of STRING.
- expanded_variable(str)
- end
-
- def show_path(str) # output search path for file type NAME
- expanded_path(str).join_path
- end
-
- def var_value(str) # output the value of variable $STRING.
- original_variable(str)
- end
-
-end
-
-class KpseFast
-
- def _is_cnf_?(filename)
- filename == File.basename((@cnffiles.first rescue @@texmfcnf) || @@texmfcnf)
- end
-
- def find_file(filename)
- if _is_cnf_?(filename) then
- @cnffiles.first rescue ''
- else
- [find_files(filename,true)].flatten.first || ''
- end
- end
-
- def find_files(filename,first=false)
- if _is_cnf_?(filename) then
- result = @cnffiles.dup
- else
- if @remember then
- # stamp = "#{filename}--#{@format}--#{@engine}--#{@progname}"
- stamp = "#{filename}--#{@engine}--#{@progname}"
- return @found[stamp] if @found.key?(stamp)
- end
- pathlist = expanded_path(filename)
- result = []
- filelist = if @files.key?(filename) then @files[filename].uniq else nil end
- done = false
- if pathlist.size == 0 then
- if FileTest.file?(filename) then
- done = true
- result << '.'
- end
- else
- pathlist.each do |path|
- doscan = if path =~ /^\!\!/o then false else true end
- recurse = if path =~ /\/\/$/o then true else false end
- pathname = path.dup
- pathname.gsub!(/^\!+/o, '')
- done = false
- if not done and filelist then
- # checking for exact match
- if filelist.include?(pathname) then
- result << pathname
- done = true
- end
- if not done and recurse then
- # checking for fuzzy //
- pathname.gsub!(/\/+$/o, '/.*')
- # pathname.gsub!(/\/\//o,'/[\/]*/')
- pathname.gsub!(/\/\//o,'/.*?/')
- re = /^#{pathname}/
- filelist.each do |f|
- if re =~ f then
- result << f # duplicates will be filtered later
- done = true
- end
- break if done
- end
- end
- end
- if not done and doscan then
- # checking for path itself
- pname = pathname.sub(/\.\*$/,'')
- if not pname =~ /\*/o and FileTest.file?(File.join(pname,filename)) then
- result << pname
- done = true
- end
- end
- break if done and first
- end
- end
- if not done and @scandisk then
- pathlist.each do |path|
- pathname = path.dup
- unless pathname.gsub!(/^\!+/o, '') then # !! prevents scan
- recurse = pathname.gsub!(/\/+$/o, '')
- complex = pathname.gsub!(/\/\//o,'/*/')
- if recurse then
- if complex then
- if ok = File.glob_file("#{pathname}/**/#{filename}") then
- result << File.dirname(ok)
- done = true
- end
- elsif ok = File.locate_file(pathname,filename) then
- result << File.dirname(ok)
- done = true
- end
- elsif complex then
- if ok = File.glob_file("#{pathname}/#{filename}") then
- result << File.dirname(ok)
- done = true
- end
- elsif FileTest.file?(File.join(pathname,filename)) then
- result << pathname
- done = true
- end
- break if done and first
- end
- end
- end
- result = result.uniq.collect do |pathname|
- File.join(pathname,filename)
- end
- @found[stamp] = result if @remember
- end
- return result # redundant
- end
-
-end
-
-class KpseFast
-
- class FileData
- attr_accessor :tag, :name, :size, :date
- def initialize(tag=0,name=nil,size=nil,date=nil)
- @tag, @name, @size, @date = tag, name, size, date
- end
- def FileData.sizes(a)
- a.collect do |aa|
- aa.size
- end
- end
- def report
- case @tag
- when 1 then "deleted | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}"
- when 2 then "present | #{@size.to_s.rjust(8)} | #{@date.strftime('%m/%d/%Y %I:%M')} | #{@name}"
- when 3 then "obsolete | #{' '*8} | #{' '*16} | #{@name}"
- end
- end
- end
-
- def analyze_files(filter='',strict=false,sort='',delete=false)
- puts("command line = #{ARGV.join(' ')}")
- puts("number of files = #{@files.size}")
- puts("filter pattern = #{filter}")
- puts("loaded cnf files = #{@cnffiles.join(' ')}")
- puts('')
- if filter.gsub!(/^not:/,'') then
- def the_same(filter,filename)
- not filter or filter.empty? or /#{filter}/ !~ filename
- end
- else
- def the_same(filter,filename)
- not filter or filter.empty? or /#{filter}/ =~ filename
- end
- end
- @files.keys.each do |name|
- if @files[name].size > 1 then
- data = Array.new
- @files[name].each do |path|
- filename = File.join(path,name)
- # if not filter or filter.empty? or /#{filter}/ =~ filename then
- if the_same(filter,filename) then
- if FileTest.file?(filename) then
- if delete then
- data << FileData.new(1,filename,File.size(filename),File.mtime(filename))
- begin
- File.delete(filename) if delete
- rescue
- end
- else
- data << FileData.new(2,filename,File.size(filename),File.mtime(filename))
- end
- else
- # data << FileData.new(3,filename)
- end
- end
- end
- if data.length > 1 then
- if strict then
- # if data.collect do |d| d.size end.uniq! then
- # data.sort! do |a,b| b.size <=> a.size end
- # data.each do |d| puts d.report end
- # puts ''
- # end
- data.sort! do |a,b|
- if a.size and b.size then
- b.size <=> a.size
- else
- 0
- end
- end
- bunch = Array.new
- done = false
- data.each do |d|
- if bunch.size == 0 then
- bunch << d
- elsif bunch[0].size == d.size then
- bunch << d
- else
- if bunch.size > 1 then
- bunch.each do |b|
- puts b.report
- end
- done = true
- end
- bunch = [d]
- end
- end
- puts '' if done
- else
- case sort
- when 'size' then data.sort! do |a,b| a.size <=> b.size end
- when 'revsize' then data.sort! do |a,b| b.size <=> a.size end
- when 'date' then data.sort! do |a,b| a.date <=> b.date end
- when 'revdate' then data.sort! do |a,b| b.date <=> a.date end
- end
- data.each do |d| puts d.report end
- puts ''
- end
- end
- end
- end
- end
-
-end
-
-
- # k = KpseFast.new # (root)
- # k.set_test_patterns
- # k.load_cnf
- # k.expand_variables
- # k.load_lsr
-
- # k.show_test_patterns
-
- # puts k.list_variables
- # puts k.list_expansions
- # k.list_lsr
- # puts k.expansion("$TEXMF")
- # puts k.expanded_path("TEXINPUTS","context")
-
- # k.progname, k.engine, k.format = 'context', 'pdftex', 'tfm'
- # k.scandisk = false # == must_exist
- # k.expand_variables
-
- # 10.times do |i| puts k.find_file('texnansi-lmr10.tfm') end
-
- # puts "expand braces $TEXMF"
- # puts k.expand_braces("$TEXMF")
- # puts "expand path $TEXMF"
- # puts k.expand_path("$TEXMF")
- # puts "expand var $TEXMF"
- # puts k.expand_var("$TEXMF")
- # puts "expand path $TEXMF"
- # puts k.show_path('tfm')
- # puts "expand value $TEXINPUTS"
- # puts k.var_value("$TEXINPUTS")
- # puts "expand value $TEXINPUTS.context"
- # puts k.var_value("$TEXINPUTS.context")
-
- # exit
-
-
-
-# kpse_merge_file: 't:/ruby/base/kpse/trees.rb'
-
-require 'monitor'
-# kpse_merge_done: require 'base/kpsefast'
-
-class KpseTrees < Monitor
-
- def initialize
- @trees = Hash.new
- end
-
- def pattern(filenames)
- filenames.join('|').gsub(/\\+/o,'/').downcase
- end
-
- def choose(filenames,environment)
- current = pattern(filenames)
- load(filenames,environment) unless @trees[current]
- puts "enabling tree #{current}"
- current
- end
-
- def fetch(filenames,environment) # will send whole object !
- current = pattern(filenames)
- load(filenames,environment) unless @trees[current]
- puts "fetching tree #{current}"
- @trees[current]
- end
-
- def load(filenames,environment)
- current = pattern(filenames)
- puts "loading tree #{current}"
- @trees[current] = KpseFast.new
- @trees[current].push_environment(environment)
- @trees[current].load_cnf(filenames)
- @trees[current].expand_variables
- @trees[current].load_lsr
- end
-
- def set(tree,key,value)
- case key
- when 'progname' then @trees[tree].progname = value
- when 'engine' then @trees[tree].engine = value
- when 'format' then @trees[tree].format = value
- end
- end
- def get(tree,key)
- case key
- when 'progname' then @trees[tree].progname
- when 'engine' then @trees[tree].engine
- when 'format' then @trees[tree].format
- end
- end
-
- def load_cnf(tree)
- @trees[tree].load_cnf
- end
- def load_lsr(tree)
- @trees[tree].load_lsr
- end
- def expand_variables(tree)
- @trees[tree].expand_variables
- end
- def expand_braces(tree,str)
- @trees[tree].expand_braces(str)
- end
- def expand_path(tree,str)
- @trees[tree].expand_path(str)
- end
- def expand_var(tree,str)
- @trees[tree].expand_var(str)
- end
- def show_path(tree,str)
- @trees[tree].show_path(str)
- end
- def var_value(tree,str)
- @trees[tree].var_value(str)
- end
- def find_file(tree,filename)
- @trees[tree].find_file(filename)
- end
- def find_files(tree,filename,first)
- @trees[tree].find_files(filename,first)
- end
-
-end
-
-
-# kpse_merge_file: 't:/ruby/base/kpse/drb.rb'
-
-require 'drb'
-# kpse_merge_done: require 'base/kpse/trees'
-
-class KpseServer
-
- attr_accessor :port
-
- def initialize(port=7000)
- @port = port
- end
-
- def start
- puts "starting drb service at port #{@port}"
- DRb.start_service("druby://localhost:#{@port}", KpseTrees.new)
- trap(:INT) do
- DRb.stop_service
- end
- DRb.thread.join
- end
-
- def stop
- # todo
- end
-
-end
-
-class KpseClient
-
- attr_accessor :port
-
- def initialize(port=7000)
- @port = port
- @kpse = nil
- end
-
- def start
- # only needed when callbacks are used / slow, due to Socket::getaddrinfo
- # DRb.start_service
- end
-
- def object
- @kpse = DRbObject.new(nil,"druby://localhost:#{@port}")
- end
-
-end
-
-
-# SERVER_URI="druby://localhost:8787"
-#
-# # Start a local DRbServer to handle callbacks.
-# #
-# # Not necessary for this small example, but will be required
-# # as soon as we pass a non-marshallable object as an argument
-# # to a dRuby call.
-# DRb.start_service
-#
-
-
-# kpse_merge_file: 't:/ruby/base/kpseremote.rb'
-
-# kpse_merge_done: require 'base/kpsefast'
-
-case ENV['KPSEMETHOD']
- when /soap/o then # kpse_merge_done: require 'base/kpse/soap'
- when /drb/o then # kpse_merge_done: require 'base/kpse/drb'
- else # kpse_merge_done: require 'base/kpse/drb'
-end
-
-class KpseRemote
-
- @@port = ENV['KPSEPORT'] || 7000
- @@method = ENV['KPSEMETHOD'] || 'drb'
-
- def KpseRemote::available?
- @@method && @@port
- end
-
- def KpseRemote::start_server(port=nil)
- kpse = KpseServer.new(port || @@port)
- kpse.start
- end
-
- def KpseRemote::start_client(port=nil) # keeps object in server
- kpseclient = KpseClient.new(port || @@port)
- kpseclient.start
- kpse = kpseclient.object
- tree = kpse.choose(KpseUtil::identify, KpseUtil::environment)
- [kpse, tree]
- end
-
- def KpseRemote::fetch(port=nil) # no need for defining methods but slower, send whole object
- kpseclient = KpseClient.new(port || @@port)
- kpseclient.start
- kpseclient.object.fetch(KpseUtil::identify, KpseUtil::environment) rescue nil
- end
-
- def initialize(port=nil)
- if KpseRemote::available? then
- begin
- @kpse, @tree = KpseRemote::start_client(port)
- rescue
- @kpse, @tree = nil, nil
- end
- else
- @kpse, @tree = nil, nil
- end
- end
-
- def progname=(value)
- @kpse.set(@tree,'progname',value)
- end
- def format=(value)
- @kpse.set(@tree,'format',value)
- end
- def engine=(value)
- @kpse.set(@tree,'engine',value)
- end
-
- def progname
- @kpse.get(@tree,'progname')
- end
- def format
- @kpse.get(@tree,'format')
- end
- def engine
- @kpse.get(@tree,'engine')
- end
-
- def load
- @kpse.load(KpseUtil::identify, KpseUtil::environment)
- end
- def okay?
- @kpse && @tree
- end
- def set(key,value)
- @kpse.set(@tree,key,value)
- end
- def load_cnf
- @kpse.load_cnf(@tree)
- end
- def load_lsr
- @kpse.load_lsr(@tree)
- end
- def expand_variables
- @kpse.expand_variables(@tree)
- end
- def expand_braces(str)
- clean_name(@kpse.expand_braces(@tree,str))
- end
- def expand_path(str)
- clean_name(@kpse.expand_path(@tree,str))
- end
- def expand_var(str)
- clean_name(@kpse.expand_var(@tree,str))
- end
- def show_path(str)
- clean_name(@kpse.show_path(@tree,str))
- end
- def var_value(str)
- clean_name(@kpse.var_value(@tree,str))
- end
- def find_file(filename)
- clean_name(@kpse.find_file(@tree,filename))
- end
- def find_files(filename,first=false)
- # dodo: each filename
- @kpse.find_files(@tree,filename,first)
- end
-
- private
-
- def clean_name(str)
- str.gsub(/\\/,'/')
- end
-
-end
-
-
# kpse_merge_file: 't:/ruby/base/kpsedirect.rb'
class KpseDirect
@@ -1284,14 +133,11 @@ class KpseDirect
end
-
# kpse_merge_stop
-
-
$mswindows = Config::CONFIG['host_os'] =~ /mswin/
$separator = File::PATH_SEPARATOR
-$version = "2.0.3"
+$version = "2.1.0"
$ownpath = File.dirname($0)
if $mswindows then
@@ -1335,7 +181,7 @@ $predefined['ctxtools'] = 'ctxtools.rb'
$predefined['rlxtools'] = 'rlxtools.rb'
$predefined['pdftools'] = 'pdftools.rb'
$predefined['mpstools'] = 'mpstools.rb'
-$predefined['exatools'] = 'exatools.rb'
+# $predefined['exatools'] = 'exatools.rb'
$predefined['xmltools'] = 'xmltools.rb'
# $predefined['luatools'] = 'luatools.lua'
# $predefined['mtxtools'] = 'mtxtools.rb'
@@ -1412,22 +258,7 @@ def check_kpse
if $kpse then
# already done
else
- begin
- if KpseRemote::available? then
- $kpse = KpseRemote.new
- if $kpse.okay? then
- puts("kpse : remote") if $verbose
- else
- $kpse = KpseDirect.new
- puts("kpse : direct (forced)") if $verbose
- end
- else
- $kpse = KpseDirect.new
- puts("kpse : direct") if $verbose
- end
- rescue
- puts("kpse : direct (fallback)") if $verbose
- end
+ $kpse = KpseDirect.new
end
end
@@ -1488,18 +319,6 @@ end
class File
- # def File.needsupdate(oldname,newname)
- # begin
- # if $mswindows then
- # return File.stat(oldname).mtime > File.stat(newname).mtime
- # else
- # return File.stat(oldname).mtime != File.stat(newname).mtime
- # end
- # rescue
- # return true
- # end
- # end
-
@@update_eps = 1
def File.needsupdate(oldname,newname)
@@ -1543,33 +362,6 @@ class File
end
-# def hashed (arr=[])
- # arg = if arr.class == String then arr.split(' ') else arr.dup end
- # hsh = Hash.new
- # if arg.length > 0
- # hsh['arguments'] = ''
- # done = false
- # arg.each do |s|
- # if done then
- # if s =~ / / then
- # hsh['arguments'] += " \"#{s}\"" # maybe split on =
- # else
- # hsh['arguments'] += " #{s}"
- # end
- # else
- # kvl = s.split('=')
- # if kvl[0].sub!(/^\-+/,'') then
- # hsh[kvl[0]] = if kvl.length > 1 then kvl[1] else true end
- # else
- # hsh['file'] = s
- # done = true
- # end
- # end
- # end
- # end
- # return hsh
-# end
-
def hashed (arr=[])
arg = if arr.class == String then arr.split(' ') else arr.dup end
hsh = Hash.new
@@ -2129,7 +921,7 @@ def make(filename,windows=false,linux=false,remove=false)
return
elsif not remove then
if windows then
- ['bat','exe'].each do |suffix|
+ ['bat','cmd','exe'].each do |suffix|
if FileTest.file?("#{basename}.#{suffix}") then
report("windows stub '#{basename}.#{suffix}' skipped (already present)")
return
@@ -2197,7 +989,7 @@ def process(&block)
checkname = filename + ".md5"
oldchecksum, newchecksum = "old", "new"
begin
- newchecksum = MD5.new(IO.read(filename)).hexdigest.upcase
+ newchecksum = Digest::MD5.hexdigest(IO.read(filename)).upcase
rescue
newchecksum = "new"
else
@@ -2417,22 +1209,6 @@ def execute(arguments) # br global
elsif $selfcleanup then
output("ruby libraries are cleaned up") if SelfMerge::cleanup
return true
- elsif $serve then
- if ENV['KPSEMETHOD'] && ENV['KPSEPORT'] then
- # # kpse_merge_done: require 'base/kpseremote'
- begin
- KpseRemote::start_server
- rescue
- return false
- else
- return true
- end
- else
- usage
- puts("")
- puts("message : set 'KPSEMETHOD' and 'KPSEPORT' variables")
- return false
- end
elsif $help || ! $filename || $filename.empty? then
usage
loadtree($tree)
diff --git a/Master/texmf-dist/scripts/context/ruby/textools.rb b/Master/texmf-dist/scripts/context/ruby/textools.rb
index 442dc19240e..a5858c5caf5 100644
--- a/Master/texmf-dist/scripts/context/ruby/textools.rb
+++ b/Master/texmf-dist/scripts/context/ruby/textools.rb
@@ -20,7 +20,8 @@ $: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.u
require 'base/switch'
require 'base/logger'
-require 'ftools'
+require 'fileutils'
+# require 'ftools'
# Remark
#
diff --git a/Master/texmf-dist/scripts/context/ruby/www/admin.rb b/Master/texmf-dist/scripts/context/ruby/www/admin.rb
deleted file mode 100644
index 8983ca2b77c..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/admin.rb
+++ /dev/null
@@ -1,215 +0,0 @@
-require 'fileutils'
-
-require 'www/lib'
-require 'www/dir'
-require 'www/common'
-
-class WWW
-
- include Common
-
- # klopt nog niet, twee keer task met een verschillend doel
-
- def handle_exatask
- # case @session.check('task', request_variable('task'))
- task, options, option = @session.get('task'), @session.get('option').split(@@re_bar), request_variable('option')
- option = (options.first || '') if option.empty?
- case task
- when 'exaadmin'
- @session.set('status', 'admin') # admin: status|dir
- touch_session(@session.get('id'))
- if options.include?(option) then
- case option
- when 'status' then handle_exaadmin_status
- when 'dir' then handle_exaadmin_dir
- else handle_exaadmin_status
- end
- elsif option.empty? then
- message('Status', "unknown option")
- else
- message('Status', "option '#{option}' not permitted #{options.inspect}")
- end
- else
- message('Status', "unknown task '#{task}'")
- end
- end
-
- def handle_exaadmin
- if id = valid_session() then
- handle_exatask
- else
- message('Status', 'no login')
- end
- end
-
- def handle_exaadmin_dir
- check_template_file('exalogin','exalogin-template.htm')
- @interface.set('path:docroot', work_root)
- @interface.set('dir:uri', 'exaadmin') # forces the dir handler into cgi mode
- @interface.set('dir:task', 'exaadmin') # forces the dir handler into cgi mode
- @interface.set('dir:option', 'dir') # forces the dir handler into cgi mode
- filename = "#{@@session_prefix}#{request_variable('path')}"
- fullname = File.join(work_root,filename)
- if request_variable('path').empty? then
- handle_exaadmin_status
- elsif FileTest.directory?(fullname) then
- handle_dir(filename, [], false)
- elsif File.zero?(fullname) then
- message('Error', "The file '#{filename}' is empty")
- elsif File.size?(fullname) > (4 * 1024 * 1024) then
- if FileTest.file?(File.expand_path(File.join(cache_root,filename))) then
- str = "<br/><br/>Cached alternative: <a href=\"#{File.join('cache',filename)}\">#{File.basename(filename)}</a>"
- else
- str = ''
- end
- message('Error', "The file '#{filename}' is too big to serve over cgi." + str)
- else
- send_file(fullname)
- end
- end
-
- def handle_exaadmin_status
- check_template_file('exalogin','exalogin-template.htm')
- begin
- n, str, lines, list, start, most, least, cached = 0, '', '', Hash.new, Time.now, 0, 0, false
- filename = File.join(tmp_path(dirname),'sessions.rbd')
- begin
- File.open(filename) do |f|
- list = Marshal.load(f)
- end
- rescue
- cached, list = false, Hash.new
- else
- cached = true
- end
- files = Dir.glob("{#{work_roots.join(',')}}/#{@@session_prefix}*.ses")
- list.keys.each do |l|
- list.delete(l) unless files.include?(l) # slow
- end
- files.each do |f|
- ctime = File.ctime(f)
- stime = list[f][0] == ctime rescue 0
- unless ctime == stime then
- begin
- hash = load_session_file(f)
- rescue
- else
- list[f] = [ctime,hash]
- end
- end
- end
- begin
- File.open(filename,'w') do |f|
- f << Marshal.dump(list)
- end
- rescue
- # no save
- end
- begin
- keys = list.keys.sort do |a,b|
- case list[b][0] <=> list[a][0]
- when -1 then -1
- when +1 then +1
- else
- a <=> b
- end
- end
- rescue
- keys = list.keys.sort
- end
- totaltime, totaldone = 0.0, 0
- if keys.length > 0 then
- keys.each do |entry|
- s, t, session = entry, list[entry][0], list[entry][1]
- status = session['status'] || ''
- runtime = (session['runtime'] || '').to_f rescue 0
- starttime = (start.to_i-session['starttime'].to_i).to_s rescue ''
- requesttime = session['endtime'].to_i-session['starttime'].to_i rescue 0
- requesttime = if requesttime > 0 then requesttime.to_s else '' end
- if runtime > 0.0 then
- totaltime += runtime
- totaldone += 1
- if least > 0 then
- if runtime < least then least = runtime end
- else
- least = runtime
- end
- if most > 0 then
- if runtime > most then most = runtime end
- else
- most = runtime
- end
- end
- if status.empty? then
- # skip, garbage
- elsif status =~ /^(|exa)admin/o then
- # skip, useless
- else
- begin
- lines << "<tr>\n"
- lines << td("<a href=\"exaadmin?option=dir&path=#{session['id']}.dir\">#{session['id']}</a>")
- lines << td(status)
- lines << td(session['timeout'])
- lines << td(starttime)
- lines << td(session['runtime'])
- lines << td(requesttime)
- lines << td(t.strftime("%H:%M:%S %Y-%m-%d"))
- lines << td(session['domain'])
- lines << td(session['project'])
- lines << td(session['username'])
- lines << td(File.basename(File.dirname(s)))
- lines << "</tr>\n"
- rescue
- else
- n += 1
- end
- end
- end
- if n > 0 then
- str = "<table cellpadding='0'>\n"
- str << "<tr>\n"
- str << th('session identifier')
- str << th('status')
- str << th('timeout')
- str << th('time')
- str << th('runtime')
- str << th('total')
- str << th('modification&nbsp;time')
- str << th('domain')
- str << th('project')
- str << th('username')
- str << th('process')
- str << "</tr>\n"
- str << lines
- str << "</table>\n"
- end
- end
- rescue
- message('Status', "#{$!} There is currently no status available.", false, @@admin_refresh, 'exaadmin')
- else
- if n > 0 then
- # r = if n > 100 then 60 else @@admin_refresh.to_i end # scanning takes long
- r = @@admin_refresh
- average = "average = #{if totaldone > 0 then sprintf('%.02f',totaltime/totaldone) else '0' end} (#{sprintf('%.02f',least)} .. #{sprintf('%.02f',most)})"
- sessions = "sessions = #{n}"
- refresh = "refresh = #{r.to_s} sec"
- loadtime = "loadtime = #{sprintf('%.04f',Time.now-start)} sec"
- cached = if cached then "cached" else "not cached" end
- message("Status | #{sessions} | #{refresh} | #{loadtime} - #{cached} | #{average} |", str, false, r, 'exaadmin')
- else
- message('Status', "There are no sessions registered.", false, @@admin_refresh, 'exaadmin')
- end
- end
- end
-
- private
-
- def th(str)
- "<th align='left'>#{str}&nbsp;&nbsp;&nbsp;</th>\n"
- end
-
- def td(str)
- "<td><code>#{str || ''}&nbsp;&nbsp;&nbsp</code></td>\n"
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/www/common.rb b/Master/texmf-dist/scripts/context/ruby/www/common.rb
deleted file mode 100644
index 9c383229456..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/common.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-# We cannot chdir in threads because it is something
-# process wide, so we will run into problems with the
-# other threads. The same is true for the global ENV
-# pseudo hash, so we cannot communicate the runpath
-# via an anvironment either. This leaves texmfstart
-# in combination with a path directive and an tmf file.
-
-module Common # can be a mixin
-
- # we assume that the hash.subset method is defined
-
- @@re_texmfstart = /^(texmfstart|ruby\s*texmfstart.rb)\s*(.*)$/
- @@re_texmfpath = /^\-\-path\=/
-
- def command_string(path,command,log='')
- runner = "texmfstart --path=#{File.expand_path(path)}"
- if command =~ @@re_texmfstart then
- cmd, arg = $1, $2
- if arg =~ @@re_texmfpath then
- # there is already an --path (first switch)
- else
- command = "#{runner} #{arg}"
- end
- else
- command = "#{runner} bin:#{command}"
- end
- if log && ! log.empty? then
- return "#{command} 2>&1 > #{File.expand_path(File.join(path,log))}"
- else
- return command
- end
- end
-
- def set_os_vars
- begin
- ENV['TEXOS'] = ENV['TEXOS'] || platform
- rescue
- ENV['TEXOS'] = 'texmf-linux'
- else
- ENV['TEXOS'] = 'texmf-' + ENV['TEXOS'] unless ENV['TEXOS'] =~ /^texmf\-/
- ensure
- ENV['EXA:TEXOS'] = ENV['TEXOS']
- end
- end
-
- def set_environment(hash)
- set_os_vars
- paths = ENV['PATH'].split(File::PATH_SEPARATOR)
- hash.subset('binpath:').keys.each do |key|
- begin
- paths << File.expand_path(hash[key])
- rescue
- end
- end
- ENV['PATH'] = paths.uniq.join(File::PATH_SEPARATOR)
- hash.subset('path:').keys.each do |path|
- key, value = "EXA:#{path.upcase}", File.expand_path(hash[path])
- ENV[key] = value
- end
- end
-
- def save_environment(hash,path,filename='request.tmf')
- begin
- File.open(File.join(path,filename),'w') do |f|
- set_os_vars
- ['EXA:TEXOS','TEXOS'].each do |key|
- f.puts("#{key} = #{ENV[key]}")
- end
- hash.subset('binpath:').keys.each do |key|
- f.puts("PATH < #{File.expand_path(@interface.get(key))}")
- end
- hash.subset('path:').keys.each do |path|
- f.puts("EXA:#{path.upcase} = #{File.expand_path(@interface.get(path))}")
- end
- end
- rescue
- end
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/www/dir.rb b/Master/texmf-dist/scripts/context/ruby/www/dir.rb
deleted file mode 100644
index 115fd8911d9..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/dir.rb
+++ /dev/null
@@ -1,155 +0,0 @@
-require 'www/lib'
-
-# dir handling
-
-class WWW
-
- # borrowed code from webrick demo, patched
-
- @@dir_name_width = 25
-
- def handle_dir(dirpath=@variables.get('path'),hidden=[],showdirs=true)
- check_template_file('dir','text-template.htm')
- docroot = @interface.get('path:docroot')
- dirpath = dirpath || ''
- hidden = [] unless hidden
- local_path = dirpath.dup
- title, str = "Index of #{escaped(dirpath)}", ''
- begin
- local_path.gsub!(/[\/\\]+/,'/')
- local_path.gsub!(/\/$/, '')
- if local_path !~ /^(\.|\.\.|\/|[a-zA-Z]\:)$/io then # maybe also /...
- full_path = File.join(docroot,local_path)
- @interface.set('log:dir', full_path)
- begin
- list = Dir::entries(full_path)
- rescue
- str << "unable to parse #{local_path}"
- else
- if list then
- list.collect! do |name|
- if name =~ /^\.+/o then
- nil # no . and ..
- else
- st = (File::stat(File.join(docroot,local_path,name)) rescue nil)
- if st.nil? then
- [name, nil, -1, false]
- elsif st.directory? then
- if showdirs then [name + "/", st.mtime, -1, true] else nil end
- elsif hidden.length > 0 then
- if hidden.include?(name) then nil else [name, st.mtime, st.size, false] end
- else
- [name, st.mtime, st.size, false]
- end
- end
- end
- list.compact!
- n, m, s = @variables.get('n'), @variables.get('m'), @variables.get('s')
- if ! n.empty? then
- idx, d0 = 0, n
- elsif ! m.empty? then
- idx, d0 = 1, m
- elsif ! s.empty? then
- idx, d0 = 2, s
- else
- idx, d0 = 0, 'a'
- end
- d1 = if d0 == 'a' then 'd' else 'a' end
- if d0 == 'a' then
- list.sort! do |a,b| a[idx] <=> b[idx] end
- else
- list.sort! do |a,b| b[idx] <=> a[idx] end
- end
- u = dir_uri(@variables.get('path') || '.')
- str << "<div class='dir-view'>\n<pre>\n"
- str << "<a href=\"#{u}&n=#{d1}\">name</a>".ljust(49+u.length)
- str << "<a href=\"#{u}&m=#{d1}\">last modified</a>".ljust(41+u.length)
- str << "<a href=\"#{u}&s=#{d1}\">size</a>".rjust(31+u.length) << "\n" << "\n"
- # parent path
- if showdirs && ! hidden.include?('..') then
- dname = "parent directory"
- fname = "#{File.dirname(dirpath)}"
- time = File::mtime(File.join(docroot,local_path,"/.."))
- str << dir_entry(fname,dname,time,-1,true)
- str << "\n"
- end
- # directories
- done = false
- list.each do |name, time, size, dir|
- if dir then
- if name.size > @@dir_name_width then
- dname = name.sub(/^(.#{@@dir_name_width-2})(.*)/) do $1 + ".." end
- else
- dname = name
- end
- fname = "#{escaped(dirpath)}/#{escaped(name)}"
- str << dir_entry(fname,dname,time,size,dir)
- done = true
- end
- end
- str << "\n" if done
- # files
- list.each do |name, time, size, dir|
- unless dir then
- if name.size > @@dir_name_width then
- dname = name.sub(/^(.#{@@dir_name_width-2})(.*)/) do $1 + ".." end
- else
- dname = name
- end
- fname = "#{escaped(dirpath)}/#{escaped(name)}"
- str << dir_entry(fname,dname,time,size,dir)
- end
- end
- str << "\n"
- str << '</pre></div>'
- else
- str << 'no info'
- end
- end
- else
- str << 'no access'
- end
- rescue
- str << "error #{$!}<br/><pre>"
- str << $@.join("\n")
- str << "</pre>"
- end
- message(title,str)
- end
- def dir_uri(f='.')
- u, t, o = @interface.get('dir:uri'), @interface.get('dir:task'), @interface.get('dir:option') # takes precedence, in case we run under cgi control
- if u.empty? then
- u, t, o = @interface.get('process:uri'), '', ''
- elsif ! t.empty? then
- t = "task=#{t}&"
- o = "option=#{o}&"
- end
- if u && ! u.empty? then
- u = u.sub(/\?.*$/,'') # frozen string
- if f =~ /^\.+$/ then
- "#{u}?#{t}#{o}path="
- else
- "#{u}?#{t}#{o}path=#{f}"
- end
- else
- ''
- end
- end
-
- def dir_entry(fname,dname,time,size,dir=false)
- if dir then
- f = fname.sub(/\/+$/,'').sub(/^\/+/,'')
- s = "<a href=\"#{dir_uri(f)}\">#{dname}</a>"
- elsif ! @interface.get('dir:uri').empty? then # takes precedence, in case we run under cgi control
- s = "<a href=\"#{dir_uri(fname.gsub(/\/+/,'/'))}\">#{dname}</a>"
- else
- s = "<a href=\"#{fname.gsub(/\/+/,'/')}\">#{dname}</a>"
- end
- # s << " " * (30 - dname.size)
- s << " " * (@@dir_name_width + 5 - dname.size)
- s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
- s << (size >= 0 ? size.to_s : "-").rjust(12) << "\n"
- return s
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/www/exa.rb b/Master/texmf-dist/scripts/context/ruby/www/exa.rb
deleted file mode 100644
index 20a40fc7b66..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/exa.rb
+++ /dev/null
@@ -1,387 +0,0 @@
-require 'fileutils'
-require 'www/lib'
-require 'www/dir'
-require 'www/common'
-require 'www/admin'
-
-class WWW
-
- include Common
-
- def handle_exadefault
- check_template_file('exalogin','exalogin-template.htm')
- if id = logged_in_session(true) then
- finish_login
- else
- message('Error', 'No default login permitted.')
- end
- end
-
- def handle_exalogin
- check_template_file('exalogin','exalogin-template.htm')
- if id = logged_in_session(false) then
- finish_login
- else
- message('Error', 'No default login permitted.')
- end
- end
-
- def finish_login
- get_gui()
- filename, path, task = @session.get('gui'), @session.checked('path','.'), @session.get('task')
- if ! task.empty? then
- save_session
- handle_exatask
- elsif filename and not filename.empty? then
- save_session
- fullname = filename.gsub(/\.\./,'')
- fullname = File.join(path,filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'), filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'), path, filename) unless FileTest.file?(fullname)
- if FileTest.file?(fullname) then
- send_file(fullname,true)
- else
- message('Interface', 'Invalid interface request, no valid interface file.' )
- end
- else
- message('Interface', 'Invalid interface request, no default interface file.')
- end
- end
-
- def handle_exainterface()
- check_template_file('text','exalogin-template.htm')
- if id = valid_session() then
- filename = @interface.get('process:uri').to_s # kind of dup
- if ! filename.empty? && filename.sub!(/^.*\//,'') then
- path = @session.checked('path', '.')
- fullname = filename.gsub(/\.\./,'')
- fullname = File.join(path,filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'),filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'),path,filename) unless FileTest.file?(fullname)
- if FileTest.file?(fullname) then
- save_session
- send_file(fullname,true)
- else
- get_file(filename)
- filename, path = @session.get('gui'), @session.checked('path','.')
- if filename and not filename.empty? then
- save_session
- fullname = filename.gsub(/\.\./,'')
- fullname = File.join(path,filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'),filename) unless FileTest.file?(fullname)
- fullname = File.join(@interface.get('path:interfaces'),path,filename) unless FileTest.file?(fullname)
- send_file(fullname,true) if FileTest.file?(fullname)
- else
- message('Interface', 'Invalid interface request, no interface file.')
- end
- end
- else
- message('Interface', 'Invalid interface request, no resource file.')
- end
- else
- message('Interface', 'Invalid interface request, no login.')
- end
- end
-
- def handle_exarequest() # todo: check if request is 'command'
- check_template_file('exalogin','exalogin-template.htm')
- if id = client_session() then
- client = true
- @interface.set('log:kind', "remote client request: #{id}")
- elsif id = valid_session() then
- client = false
- @interface.set('log:kind', "remote browser request: #{id}")
- else
- client, id = false, nil
- @interface.set('log:kind', 'unknown kind of request')
- end
- if id then
- dir, tmp = dirname, tmp_path(dirname)
- requestname, replyname = 'request.exa', 'reply.exa'
- requestfile, replyfile = File.join(tmp,requestname), File.join(tmp,replyname)
- lockfile = File.join(dirname,lckname)
- action, filename, command, url, req = '', '', '', '', ''
- extract_sent_files(tmp)
- @variables.each do |key, value|
- case key
- when 'exa:request' then
- req = value.dup
- when 'exa:action' then
- action = value.dup
- # when 'exa:command' then
- # command = value.dup
- # when 'exa:url' then
- # url = value.dup
- when 'exa:filename' then
- filename = value.dup
- when 'exa:threshold' then
- @interface.set('process:threshold', value.dup)
- when /^fakename/o then
- @variables.set(key, File.basename(value))
- when /^filename\-/o then
- @variables.set(key, filename = File.basename(value))
- when /^dataname\-/o then
- @variables.set(key)
- else # remove varname- prefix from value
- @variables.set(key, @variables.get(key).sub(/#{key}\-/,''))
- end
- end
- @variables.check('exa:filename', filename)
- @variables.check('exa:action', action)
- if @variables.empty?('exa:filename') then
- @variables.set('exa:filename', @interface.get('log:attachments').split('|').first || '')
- end
- req.gsub!(/<exa:data\s*\/>/i, '')
- dat = "<exa:data>\n"
- @variables.each do |key, value|
- if ['password','exa:request'].include?(key) then
- # skip
- elsif ! value || value.empty? then
- dat << "<exa:variable label='#{key}'/>\n"
- else # todo: escape 'm
- dat << "<exa:variable label='#{key}'>#{value}</exa:variable>\n"
- end
- end
- dat << "</exa:data>\n"
- if req.empty? then
- req << "<?xml version='1.0' ?>\n"
- req << "<exa:request xmlns:exa='#{@@namespace}'>\n"
- req << "<exa:application>\n"
- req << "<exa:action>'#{action}</exa:action>\n" unless action.empty?
- # req << "<exa:command>'#{command}</exa:command>\n" unless command.empty?
- # req << "<exa:url>'#{url}</exa:url>\n" unless url.empty?
- req << "</exa:application>\n"
- req << "<exa:comment>constructed request</exa:comment>\n"
- req << dat
- req << "</exa:request>\n"
- else
- # better use rexml but slower
- if req =~ /<exa:request[^>]*>.*?\s*<exa:threshold>\s*(.*?)\s*<\/exa:threshold>\s*.*?<\/exa:request>/moi then
- threshold = $1
- unless threshold.empty? then
- @interface.set('process:threshold', threshold)
- @session.set('threshold', threshold)
- end
- end
- req.sub!(/(<exa:request[^>]*>.*?)\s*<exa:option>\s*\-\-action\=(.*?)\s*<\/exa:option>\s*(.*?<\/exa:request>)/moi) do
- pre, act, pos = $1, $2, $3
- action = act.sub(/\.exa$/,'') if action.empty?
- str = "#{pre}<exa:action>#{action}</exa:action>#{pos}"
- str.sub(/\s*<exa:command>.*?<\/exa:command>\s*/moi ,'')
- end
- req.sub!(/(<exa:request[^>]*>.*?)<exa:action>\s*(.*?)\s*<\/exa:action>(.*?<\/exa:request>)/moi) do
- pre, act, pos = $1, $2, $3
- action = act.sub(/\.exa$/,'') if action.empty?
- str = "#{pre}<exa:action>#{action}</exa:action>#{pos}"
- str.sub(/\s*<exa:command>.*?<\/exa:command>\s*/moi ,'')
- end
- unless req =~ /<exa:data>(.*?)<\/exa:data>/moi then
- req.sub!(/(<\/exa:request>)/) do dat + $1 end
- end
- end
- req.sub!(/<exa:filename>.*?<\/exa:filename>/moi, '')
- unless @variables.empty?('exa:filename') then
- req.sub!(/(<\/exa:application>)/moi) do
- "<exa:filename>#{@variables.get('exa:filename')}<\/exa:filename>" + $1
- end
- end
- @variables.set('exa:action', action)
- @interface.set("log:#{requestname}", req)
- begin
- File.open(requestfile,'w') do |f|
- f << req
- end
- rescue
- message('Error', 'There is a problem in handling this request (working path access).')
- return
- end
- File.delete(replyfile) rescue false
- @interface.set('log:action',action)
- get_command(action)
- logdata = ''
- begin
- command = @session.get('command')
- @interface.set('log:command',if command.empty? then '[no command]' else command end)
- if ! command.empty? then
- @session.set('starttime', Time.now.to_i.to_s) # can be variables and in save list
- if @interface.true?('process:background') then
- # background
- @session.set('status', 'running: background')
- @session.set('maxtime', @interface.get('process:timeout'))
- @session.set('threshold', @interface.get('process:threshold'))
- save_session
- timeout(@@watch_delay) do
- save_environment(@interface,tmp)
- begin
- starttime = File.mtime(@session_file)
- # crap
- loop do
- sleep(1)
- if starttime != File.mtime(@session_file) then
- break unless FileTest.file?(lockfile)
- end
- end
- rescue TimeoutError
- if client then
- send_reply()
- else
- message('Status', 'Processing your request takes a while',true,5,'exastatus')
- end
- return
- rescue
- end
- end
- if client then send_reply() else send_result() end
- else
- # foreground
- status = 'running: foreground'
- @session.set('status', status)
- @session.set('maxtime', @interface.get('process:timeout'))
- @session.set('threshold', @interface.get('process:threshold'))
- save_session
- timeout(@interface.get('process:timeout').to_i) do
- begin
- status = 'running: foreground'
- set_environment(@interface)
- save_environment(@interface,tmp)
- command = command_string(tmp,command)
- logdata = `#{command}`
- rescue TimeoutError
- status = 'running: timeout'
- logdata = "timeout: #{@interface.get('process:timeout')} seconds"
- rescue
- status = 'running: aborted'
- logdata = 'fatal runtime error'
- else
- @session.set('endtime', Time.now.to_i.to_s)
- status = 'running: finished'
- end
- end
- @session.set('status', status)
- save_session
- case @session.get('status')
- when 'running: finished' then
- if client then send_reply(logdata) else send_result(logdata) end
- when 'running: timeout' then
- message('Error', 'There is a problem in handling this request (timeout).')
- when 'running: aborted' then
- message('Error', 'There is a problem in handling this request (aborted).')
- else
- message('Error', 'There is a problem in handling this request (unknown).')
- end
- end
- else
- message('Error', 'There is a problem in handling this request (no runner).')
- end
- rescue
- message('Error', 'There is a problem in handling this request (no run).' + $!)
- end
- else
- message('Error', 'Invalid session.')
- end
- end
-
- def handle_exacommand() # shares code with exarequest
- check_template_file('exalogin','exalogin-template.htm')
- if id = client_session() then
- client = true
- @interface.set('log:kind', "remote client request: #{id}")
- elsif id = valid_session() then
- client = false
- @interface.set('log:kind', "remote browser request: #{id}")
- else
- client, id = false, nil
- @interface.set('log:kind', 'unknown kind of request')
- end
- if id then
- dir, tmp = dirname, tmp_path(dirname)
- requestname, replyname = 'request.exa', 'reply.exa'
- requestfile, replyfile = File.join(tmp,requestname), File.join(tmp,replyname)
- req, command, url = '', '', ''
- @variables.each do |key, value|
- case key
- when 'exa:request' then
- req = value.dup
- when 'exa:command' then
- command = value.dup
- when 'exa:threshold' then
- @interface.set('process:threshold', value.dup)
- when 'exa:url' then
- url = value.dup
- end
- end
- unless req.empty? then
- # better use rexml but slower / reuse these : command = filter_from_request('exa:command')
- if req =~ /<exa:request[^>]*>.*?\s*<exa:command>\s*(.*?)\s*<\/exa:command>\s*.*?<\/exa:request>/moi then
- command = $1
- end
- if req =~ /<exa:request[^>]*>.*?\s*<exa:url>\s*(.*?)\s*<\/exa:url>\s*.*?<\/exa:request>/moi then
- url = $1
- end
- if req =~ /<exa:request[^>]*>.*?\s*<exa:threshold>\s*(.*?)\s*<\/exa:threshold>\s*.*?<\/exa:request>/moi then
- threshold = $1
- unless threshold.empty? then
- @interface.set('process:threshold', threshold)
- @session.set('threshold', threshold)
- end
- end
- end
- @variables.check('exa:command', command)
- @variables.check('exa:url', url)
- File.delete(replyfile) rescue false
- case @variables.get('exa:command')
- when 'fetch' then
- if @variables.empty?('exa:url') then
- message('Error', "Problems with fetching, no file given")
- else
- # the action starts here
- filename = @variables.get('exa:url').to_s # kind of dup
- unless filename.empty? then
- get_path(filename) # also registers filename as url
- path = @session.checked('path', '')
- fullname = filename.gsub(/\.\./,'')
- fullname = File.join(path,fullname) unless path.empty?
- if FileTest.file?(fullname) then
- if client then
- send_url(fullname)
- else
- send_file(fullname,true)
- end
- @session.set('threshold', @interface.get('process:threshold'))
- @session.set('url',filename)
- save_session
- else
- message('Error', "Problems with fetching, unknown file #{fullname}.")
- # message('Error', "Problems with fetching, unknown file #{filename}.")
- end
- else
- message('Error', "Problems with fetching, invalid file #{filename}.")
- end
- # and ends here
- end
- else
- message('Error', "Invalid command #{command}.")
- end
- else
- message('Error', 'Invalid session.')
- end
- end
-
- def handle_exastatus
- get_cfg() # weird, needed for apache, but not for wwwserver
- if request_variable('id').empty? then
- if id = valid_session() then
- send_result()
- else
- message('Error', 'Invalid session.')
- end
- else
- if id = valid_session() then
- send_reply()
- else
- send_reply('invalid session')
- end
- end
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/www/lib.rb b/Master/texmf-dist/scripts/context/ruby/www/lib.rb
deleted file mode 100644
index b9a44c9f61c..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/lib.rb
+++ /dev/null
@@ -1,1405 +0,0 @@
-#!/usr/bin/env ruby
-
-# This is just a simple environment for remote processing of context
-# files. It's not a framework, nor an example of how that should be done.
-# Nowadays there are environments like Rails or Nitro. Maybe some day I'll
-# give one of them a try.
-
-# <META Http-Equiv="Cache-Control" Content="no-cache">
-# <META Http-Equiv="Pragma" Content="no-cache">
-# <META Http-Equiv="Expires" Content="0">
-
-# we make limited use of cgi methods because we also need to handle webrick
-
-# %var% as well as $(var) are supported
-
-# paths need to be expanded before they enter apache, since .. is not
-# handled by default
-
-require 'base/variables'
-
-require 'ftools'
-require 'fileutils'
-require 'tempfile'
-require 'timeout'
-require 'md5'
-require 'digest/md5'
-require 'cgi' # we also need escaping for webrick (could move it here)
-
-# beware, namespaces have to match !
-
-module XML
-
- def XML::element(tag,attributes=nil)
- if attributes.class == Hash then
- if block_given? then
- XML::element(tag,XML::attributes(attributes)) do yield end
- else
- XML::element(tag,XML::attributes(attributes))
- end
- else
- if block_given? then
- "<#{tag}#{if attributes && ! attributes.empty? then ' ' + attributes end}>#{yield}</#{tag}>"
- else
- "<#{tag}#{if attributes && ! attributes.empty? then ' ' + attributes end}/>"
- end
- end
- end
-
- def XML::attributes(hash)
- str = ''
- hash.each do |k,v|
- str << ' ' unless str.empty?
- if v =~ /\'/ then
- str << "#{k}=\"#{v}\""
- else
- str << "#{k}=\'#{v}\'"
- end
- end
- return str
- end
-
- def XML::create(version='1.0')
- "<?version='#{version}'?>#{yield || ''}"
- end
-
- def XML::line
- "\n"
- end
-
-end
-
-# str =
- # XML::create do
- # XML::element('test') do
- # XML::element('test') do
- # 'text a'
- # end +
- # XML::element('test',XML::attributes({'a'=>'b'})) do
- # XML::element('nested',XML::attributes({'a'=>'b'})) do
- # 'text b-1'
- # end +
- # XML::element('nested',XML::attributes({'a'=>'b'})) do
- # 'text b-2'
- # end
- # end +
- # XML::element('nested',{'a'=>'b'}) do
- # 'text c'
- # end
- # end
- # end
-
-class ExtendedHash
-
- DEFAULT = 'default'
-
- @@re_default = /^(default|)$/i
-
- def default?(key)
- self[key] =~ @@re_default rescue true # unset, empty or 'default'
- end
-
- def default(key)
- self[key] = DEFAULT
- end
-
- def match?(key,value)
- value == '*' || value == self[key]
- end
-
-
-end
-
-class WWW
-
- @@session_prefix = ''
- @@data_file = 'example.cfg'
- @@session_max_age = 60*60
- @@watch_delay = 30
- @@send_threshold = 2*1024*1024
- @@admin_refresh = 10
- @@namespace = "http://www.pragma-ade.com/schemas/example.rng"
-
- @@re_bar = /\s*\|\s*/
- @@re_lst = /\s*\,\s*/
- @@re_var_a = /\%(.*?)\%/
- @@re_var_b = /\$\((.*?)\)/
-
- attr_reader :variables
- attr_writer :variables
-
- @@paths = [
- 'configurations',
- 'data',
- 'distributions',
- 'documents',
- 'interfaces',
- 'logs',
- 'resources',
- 'runners',
- 'scripts',
- 'templates',
- 'work']
-
- @@re_true = /^\s*(YES|ON|TRUE|1)\s*$/io
- @@re_false = /^\s*(NO|OFF|FALSE|0)\s*$/io
-
- def initialize(webrick_daemon=nil,webrick_request=nil,webrick_response=nil)
- @session_id, @session_file = '', ''
- @cgi, @cgi_cookie = nil, nil
- @webrick_daemon, @webrick_request, @webrick_response = webrick_daemon, webrick_request, webrick_response
-
- @interface = ExtendedHash.new
- @variables = ExtendedHash.new
- @session = ExtendedHash.new
-
- @checked = false
-
- analyze_request()
- update_interface()
-
- @interface.set('template:message' , 'exalogin-template.htm')
- @interface.set('template:status' , 'exalogin-template.htm')
- @interface.set('template:login' , 'exalogin.htm')
- @interface.set('process:timeout' , @@session_max_age)
- @interface.set('process:threshold' , @@send_threshold)
- @interface.set('process:background', 'yes') # this demands a watchdog being active
- @interface.set('process:indirect' , 'no') # indirect download, no direct feed
- @interface.set('process:autologin' , 'yes') # provide default interface when applicable
- @interface.set('process:exaurl' , '') # this one will be used as replacement in templates
- @interface.set('trace:run' , 'no')
- @interface.set('trace:errors' , 'no')
- @interface.set('process:os' , platform)
- @interface.set('process:texos' , 'texmf-' + platform)
-
- @interface.set('trace:run' , 'yes') if (ENV['EXA_TRACE_RUN'] || '') =~ @@re_true
- @interface.set('trace:errors' , 'yes') if (ENV['EXA_TRACE_ERRORS'] || '') =~ @@re_true
-
- yield self if block_given?
- end
-
- def set(key,value)
- @interface.set(key,value)
- end
- def get(key)
- @interface.get(key,value)
- end
-
- def platform
- case RUBY_PLATFORM
- when /(mswin|bccwin|mingw|cygwin)/i then 'mswin'
- when /(linux)/i then 'linux'
- when /(netbsd|unix)/i then 'unix'
- when /(darwin|rhapsody|nextstep)/i then 'macosx'
- else 'unix'
- end
- end
-
- def check_cgi
- # when mod_ruby is used, we need to close
- # the cgi session explicitly
- unless @webrick_request then
- unless @cgi then
- @cgi = CGI.new('html4')
- at_exit do
- begin
- @cgi.close
- rescue
- end
- end
- end
- end
- end
-
- def request_variable(key)
- begin
- if @webrick_request then
- [@webrick_request.query[key]].flatten.first.to_s
- else
- check_cgi
- [@cgi.params[key]].flatten.first.to_s
- end
- rescue
- ''
- end
- end
-
- def request_cookie(key)
- begin
- if @cgi then
- if str = @cgi.cookies[key] then
- return str.first || ''
- end
- elsif @webrick_request then
- @webrick_request.cookies.flatten.each do |cookie|
- if cookie.name == key then
- return cookie.value unless cookie.value.empty?
- end
- end
- end
- rescue
- end
- return ''
- end
-
- def analyze_request
- if @webrick_request then
- @interface.set('path:docroot', @webrick_daemon.config[:DocumentRoot] || './documents')
- @interface.set('process:uri', @webrick_request.request_uri.to_s)
- # @interface.set('process.url', [@webrick_request.host,@webrick_request.request_port].join(':'))
- @cgi = nil
- @webrick_request.query.each do |key, value|
- # todo: filename
- @variables.set(key, [value].flatten.first)
- end
- else
- @interface.set('path:docroot', ENV['DOCUMENT_ROOT'] || './documents')
- @interface.set('process:uri', ENV['REQUEST_URI'] || '')
- # @interface.set('process.url', [ENV['SERVER_NAME'],ENV['SERVER:PORT']].join(':'))
- ARGV[0] = '' # get rid of terminal mode
- check_cgi
- # quite fragile, due to changes between 1.6 and 1.8
- @cgi.params.keys.each do |p|
- if @cgi[p].respond_to?(:original_filename) then
- @interface.set('log:method','post')
- if @cgi[p].original_filename && ! @cgi[p].original_filename.empty? then
- @variables.set(p, File.basename(@cgi[p].original_filename))
- else
- case @cgi.params[p].class
- when StringIO.class then @variables.set(p, @cgi[p].read)
- when Array.class then @variables.set(p, @cgi[p].first.to_s)
- when String.class then @variables.set(p, @cgi[p])
- when Tempfile.class then @variables.set(p, '[data blob]')
- end
- end
- else
- @interface.set('log:method','get') unless @interface.get('log:method') == 'post'
- @variables.set(p, [@cgi.params[p]].flatten.first.to_s)
- end
- end
- end
- @interface.set('path:root', File.dirname(@interface.get('path:docroot')))
- end
-
- # name in calling script takes precedence
- # one can set template:whatever as well
- # todo: in config
-
- def check_template_file(tag='',filename='exalogin-template.htm')
- @interface.set('file:template', filename) if @interface.get('file:template').empty?
- @interface.set('tag:template', tag)
- @interface.set('file:template', @interface.get('tag:template')) unless @interface.get('tag:template').empty?
- end
-
- def update_interface()
- root = @interface.get('path:docroot')
- @interface.set('path:docroot', File.expand_path("#{root}"))
- @@paths.each do |path|
- @interface.set("path:#{path}", File.expand_path("#{root}/../#{path}"))
- end
- @interface.set('file:template', @interface.get('tag:template')) unless @interface.get('tag:template').empty?
- end
-
- def indirect?(result)
- size = FileTest.size?(result) || 0
- @interface.true?('trace:errors') || @interface.true?('trace:run') || @interface.true?('process:indirect') ||
- ((! @interface.empty?('process:threshold')) && (size > @interface.get('process:threshold').to_i)) ||
- ((! @session.empty?('threshold')) && (size > @session.get('threshold').to_i))
- end
-
-end
-
-# files
-
-class WWW
-
- def sesname
- File.basename(@session_file)
- end
- def dirname
- File.basename(@session_file.sub(/ses$/,'dir'))
- end
- def lckname
- File.basename(@session_file.sub(/ses$/,'lck'))
- end
-
- def work_root(expand=true)
- p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
- if @interface.true?('process:background') then
- File.join(@interface.get('path:work'),'watch')
- else
- File.join(@interface.get('path:work'),'direct')
- end
- end
-
- def work_roots(expand=true)
- p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
- [File.join(@interface.get('path:work'),'watch'),File.join(@interface.get('path:work'),'direct')]
- end
-
- def cache_root(expand=true)
- p = if expand then File.expand_path(@interface.get('path:work')) else @interface.get('path:work') end
- File.join(@interface.get('path:work'),'cache')
- end
-
- def cleanup_path(dir)
- FileUtils::rm_r(pth) rescue false
- end
-
- def tmp_path(dir)
- @interface.set('path:templates', File.expand_path(@interface.get('path:templates'))) # to be sure; here ? ? ?
- pth = File.join(work_root,dir)
- File.makedirs(pth) rescue false
- pth
- end
-
- def locked?(lck)
- FileTest.file?(lck)
- end
-
-end
-
-# sessions
-
-class WWW
-
- @@session_tags = ['id','domain','project','username','password','gui','path','process','command','filename','action','status', 'starttime','endtime','runtime','task','option','threshold','url'].sort
- @@session_keep = ['id','domain','project','username','password','process'].sort
- @@session_reset = @@session_tags - @@session_keep
-
- def new_session()
- if @variables.empty?('exa:session') then
- @session_id = new_session_id
- else
- @session_id = @variables.get('exa:session')
- end
- if @session_id == 'default' then # ???
- @session_id = new_session_id
- end
- @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
- register_session
- return @session_id
- end
-
- def reset_session(all=false)
- (if all then @@session_tags else @@session_reset end).each do |k|
- @session.set(k)
- end
- end
-
- def valid_session
- @session_id = request_variable('id')
- if @session_id.empty? then
- begin
- if @cgi then
- if @session_id = @cgi.cookies['session_id'] then
- @session_id = @session_id.first || ''
- else
- @session_id = ''
- end
- elsif @webrick_request then
- @webrick_request.cookies.flatten.each do |cookie|
- if cookie.name == 'session_id' then
- unless cookie.value.empty? then
- @session_id = cookie.value
- # break
- end
- end
- end
- else
- @session_id = ''
- end
- rescue
- @interface.set('log:session',"[error in request #{$!}]")
- return false
- end
- end
- if @session_id.empty? then
- @interface.set('log:session','[no id, check work dir permissions]')
- return false
- else
- @interface.set('log:session',@session_id)
- load_session
- if ! @session.empty?('domain') && ! @session.empty?('project') && ! @session.empty?('username') then
- register_session
- return @session_id
- else
- return false
- end
- end
- end
-
- def touch_session(id=nil)
- begin
- t = Time.now
- File.utime(t,t,File.join(work_root,"#{@@session_prefix}#{id || @session_id}.ses")) rescue false
- rescue
- false
- end
- end
-
- def forced_session
- @session_id = new_session
- if @session_id.empty? then
- @interface.set('log:session','[no id, check work dir permissions]')
- return false
- else
- return check_session
- end
- end
-
- def client_session
- request, done = @variables.get('exa:request'), false
- request.sub!(/(^.*<exa:request[^>]*>.*?)\s*<exa:client>\s*(.*)\s*<\/exa:client>\s*(.*?<\/exa:request>.*$)/mio) do
- pre, client, post = $1, $2, $3
- client.scan(/<exa:(domain|project|username|password)>(.*?)<\/exa:\1>/mio) do
- @variables.set($1, $2)
- end
- done = true
- pre + post
- end
- if done then
- return forced_session
- else
- return nil
- end
- end
-
- def register_session
- if @cgi then
- @cgi_cookie = CGI::Cookie::new(
- 'name' => 'session_id',
- 'value' => @session_id,
- 'expires' => Time.now + @interface.get('process:timeout').to_i
- )
- # @cgi_cookie = CGI::Cookie::new('session_id',@session_id)
- elsif @webrick_response then
- if cookie = WEBrick::Cookie.new('session_id', @session_id) then
- cookie.expires = Time.now + @interface.get('process:timeout').to_i
- cookie.max_age = @interface.get('process:timeout').to_i
- cookie.comment = 'exa identifier'
- @webrick_response.cookies.clear
- @webrick_response.cookies << cookie
- end
- end
- end
-
- def new_session_id # taken from cgi
- md5 = Digest::MD5::new
- now = Time::now
- md5.update(now.to_s)
- md5.update(String(now.usec))
- md5.update(String(rand(0)))
- md5.update(String($$))
- md5.update('foobar')
- @new_session = true
- md5.hexdigest[0,32] # was 16
- end
-
- @@hide_passwords = true
- HIDDEN = 'hidden'
-
- def same_passwords(password) # password in cfg file
- if @@hide_passwords && (@session.get('password') == HIDDEN) && (@session_id == @session.get('id')) then
- # this condition is only true when a same session id is found and
- # the password is checked once and set to HIDDEN
- same = true
- elsif password =~ /^MD5:/ then
- # so, one cannot send a known encrypted password since it will be
- # encrypted twice then
- same = (password == "MD5:" + MD5.new(@session.get('password')).hexdigest.upcase)
- else
- if (@session.default?('domain') && @session.default?('project') && @session.default?('username')) then
- @session.default('password') # is this safe enough?
- end
- same = (password == @session.get('password'))
- end
- if @@hide_passwords && same then
- @session.set('password', HIDDEN)
- save_session # next time this session is ok anyway
- end
- return same
- end
-
- @@session_line = /^\s*(?![\#\%])(.*?)\s*\=\s*(.*?)\s*$/o
- @@session_begin = 'begin exa session'
- @@session_end = 'end exa session'
-
- def loaded_session_data(filename)
- begin
- if data = IO.readlines(filename) then
- return data if (data.first =~ /^[\#\%]\s*#{@@session_begin}/o) && (data.last =~ /^[\#\%]\s*#{@@session_end}/o)
- end
- rescue
- end
- return nil
- end
-
- def load_session()
- begin
- @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
- if data = loaded_session_data(@session_file) then
- data.each do |line|
- if line =~ @@session_line then
- @session.set($1, $2 || '')
- end
- end
- else
- return false
- end
- rescue
- return false
- else
- return true
- end
- end
-
- def load_session_file(filename)
- begin
- if data = loaded_session_data(filename) then
- session = Hash.new
- data.each do |line|
- if line =~ @@session_line then
- session[$1] = $2 || ''
- end
- end
- else
- Hash.new
- end
- rescue
- Hash.new
- else
- session
- end
- end
-
- def save_session
- begin
- unless @session_id.empty? then
- @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
- @session_file = File.join(work_root,"#{@@session_prefix}#{@session_id}.ses")
- File.open(@session_file,'w') do |f|
- f << "\# #{@@session_begin}\n"
- @@session_tags.each do |tag|
- if @session && @session.key?(tag) then
- if ! @session.get(tag).empty? then # no one liner, fails
- f << "#{tag}=#{@session.get(tag)}\n"
- end
- elsif @variables.key?(tag) && ! @variables.empty?(key) then
- f << "#{tag}=#{@variables.get(tag)}\n"
- end
- end
- @session.subset("ENV").keys.each do |tag|
- f << "#{tag}=#{@session.get(tag)}\n"
- end
- f << "\# #{@@session_end}\n"
- end
- end
- rescue
- return false
- else
- return true
- end
- end
-
- def logged_in_session(force_default=false)
- if force_default || (@variables.default?('domain') && @variables.default?('project') && @variables.default?('username')) then
- id = default_session
- else
- id = check_session
- end
- end
-
- def default_session
- if @interface.true?('process:autologin') then
- @variables.default('domain')
- @variables.default('project')
- @variables.default('username')
- @variables.default('password')
- check_session
- else
- @session_id = nil
- end
- end
-
- def check_session
- @session.set('domain', @variables.get('domain').downcase)
- @session.set('project', @variables.get('project').downcase)
- @session.set('username', @variables.get('username').downcase)
- @session.set('password', @variables.get('password').downcase)
- new_session
- @session.set('id', @session_id)
- save_session
- return @session_id
- end
-
- def delete_session(id=nil)
- File.delete(work_root,"#{@@session_prefix}#{id || @session_id}.ses") rescue false
- end
-
- def cleanup_sessions(max_age=nil)
- begin
- now, age = Time.now, (max_age||@interface.get('process:timeout')).to_i
- Dir.glob("{#{work_root},#{cache_root}/#{@@session_prefix}*").each do |s|
- begin
- if (now - File.mtime(s)) > age then
- if FileTest.directory?(s) then
- FileUtils::rm_r(s)
- else
- File.delete(s)
- end
- end
- rescue
- # maybe purged in the meantime
- end
- end
- rescue
- # maybe another process is busy
- end
- end
-
-end
-
-# templates
-
-class WWW
-
- def filled_template(title,text,showtime=false,refresh=0,refreshurl=nil)
- template = @interface.get("template:#{@interface.get('tag:template')}")
- template = @interface.get("template:status") if template.empty?
- fullname = File.join(@interface.get('path:templates'),template)
- @interface.set('log:templatename',template)
- @interface.set('log:templatefile',fullname)
- append_status(text)
- htmreply = ''
- if FileTest.file?(fullname) then
- begin
- htmreply = IO.read(fullname)
- rescue
- htmreply = ''
- end
- end
- if refresh>0 then
- if refreshurl then
- metadata = "<meta http-equiv='refresh' content='#{refresh};#{refreshurl}'>"
- else
- metadata = "<meta http-equiv='refresh' content='#{refresh}'>"
- end
- else
- metadata = ''
- end
- if ! htmreply || htmreply.empty? then
- # in head: <link rel='stylesheet' href='/exaresource/exastyle.css'>
- htmreply = <<-EOD
- <html>
- #{metadata}
- <head>
- <title>#{title}</title>
- </head>
- <body>
- <h2>#{title}</h2>
- <h4>#{Time.now}</h4>
- #{text}
- </body>
- </html>
- EOD
- else
- if showtime then
- exa_template = "<h1>#{title}</h1>\n<h2>#{Time.now}</h2>\n#{text}\n"
- else
- exa_template = "<h1>#{title}</h1>#{text}\n"
- end
- htmreply = replace_template_placeholder(htmreply,exa_template,metadata)
- end
- htmreply
- end
-
- def message(title,str='',showtime=false,refresh=0,refreshurl=nil)
- if @cgi then
- @cgi.out("cookie"=>[@cgi_cookie]) do
- filled_template(title,str,showtime,refresh,refreshurl)
- end
- elsif @webrick_response then
- @webrick_response['content-type'] = 'text/html'
- @webrick_response.body = filled_template(title,str,showtime,refresh,refreshurl)
- else
- filled_template(title,str,showtime,refresh,refreshurl)
- end
- end
-
- def plaintext(str)
- if @cgi then
- @cgi.out('cookie'=>[@cgi_cookie],'content-type'=>'text/plain') do
- str
- end
- elsif @webrick_response then
- @webrick_response['content-type'] = 'text/plain'
- @webrick_response.body = str
- else
- str
- end
- end
-
- def exareply(status='',url='',size='',comment='')
- exaurl = @interface.get('process:exaurl')
- str = "<?xml version='1.0'?>\n\n"
- str << "<exa:reply xmlns:exa='#{@@namespace}'>\n"
- str << " <exa:session>#{@session_id}</exa:session>\n" unless @session_id.empty?
- str << " <exa:status>#{status}</exa:status>\n" unless (status || '').empty?
- str << " <exa:url>#{exaurl}/#{url}</exa:url>\n" unless (url || '').empty?
- str << " <exa:size>#{size}</exa:size>\n" unless (size || '').empty?
- str << " <exa:comment>#{comment}</exa:comment>\n" unless (comment|| '').empty?
- str << "</exa:reply>\n"
- return str
- end
-
- def append_status(str='')
- if @interface.true?('trace:errors') then
- if $! && $@ then
- str << "<br/><br/><br/><em>Error:</em><br/><pre>#{$!}</pre><pre>"
- str << $@.join("\n")
- end
- str << '<br/><br/><br/>'
- str << status_data
- str << '<em>Paths</em><br/>'
- str << '<pre>'
- @interface.subset('path:').each do |k,v|
- if FileTest.directory?(v) then
- if FileTest.writable?(v) then
- str << "#{v} exists and is writable\n"
- else
- str << "#{v} is not writable\n"
- end
- else
- str << "#{v} does not exist\n"
- end
- end
- str << '</pre>'
- end
- str
- end
-
- def simpleurl(url)
- if url then url.sub(/(:80|:443)$/,'') else '' end
- end
-
- def replace_exa_placeholders(data)
- data.gsub(/([\"\'])\@exa\_([a-zA-Z0-9\-\_]+)\1/) do
- quot, key, value = $1, $2, ''
- begin
- value = @variables.get(key)
- rescue
- value = ''
- end
- quot + value + quot
- end
- end
-
- def replace_url_placeholder(data)
- data.gsub!(/(http:\/\/|\/+)*\@exa\_main\_url/, @interface.get('process:exaurl'))
- replace_exa_placeholders(data)
- end
-
- def replace_template_placeholder(data,template='',metadata='')
- data.gsub!(/(http:\/\/|\/+)*\@exa\_main\_url/, @interface.get('process:exaurl'))
- data.gsub!(/\@exa\_template/, template)
- data.gsub!(/\@exa\_metadata/, metadata)
- replace_exa_placeholders(data)
- end
-
- def escaped(str)
- str
- end
-
-end
-
-# send files
-
-class WWW
-
- def send_file(filename,parse=false) # this can take a lot of memory, look for alternative (fastcgi ?)
- begin
- if filename =~ /\.pdf$/ then
- mimetype, parse = 'application/pdf', false
- elsif filename =~ /\.(html|htm)$/ then
- mimetype, parse = 'text/html', true
- else
- mimetype, parse = 'text/plain', false
- end
- if FileTest.file?(filename) then
- if @webrick_response then
- begin
- @webrick_response['content-type'] = mimetype
- @webrick_response['content-length'] = FileTest.size?(filename)
- if parse then
- File.open(filename, 'rb') do |f|
- @webrick_response.body = replace_url_placeholder(f.read)
- end
- else
- @webrick_response.body = File.open(filename, 'rb')
- end
- rescue
- else
- return
- end
- elsif @cgi then
- begin
- # the following works ok, but stores the whole file in memory (see @cgi.out)
- #
- # File.open(filename, 'rb') do |f|
- # @cgi.out('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype) do
- # if parse then replace_url_placeholder(f.read) else f.read end
- # end
- # end
- if parse then
- File.open(filename, 'rb') do |f|
- @cgi.out('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype) do
- replace_url_placeholder(f.read)
- end
- end
- else
- @cgi.print(@cgi.header('cookie'=>[@cgi_cookie],'connection'=>'close', 'length'=>File.size(filename), 'type'=>mimetype))
- File.open(filename, 'rb') do |f|
- while str = f.gets do
- @cgi.print(str)
- end
- end
- end
- rescue
- else
- return
- end
- end
- end
- rescue
- end
- message('Error', "There is a problem with sending file #{File.basename(filename)}.")
- end
-
- def send_htmlfile(filename,parse=false)
- send_file(filename,parse)
- end
- def send_pdffile(filename) # this can take a lot of memory, look for alternative (fastcgi ?)
- send_file(filename,false)
- end
-
-end
-
-# tracing
-
-class WWW
-
- def show_vars(a=@variables,title='')
- if a && a.length > 0 then
- if title.empty? then
- str = ''
- else
- str = "<em>#{title}</em>"
- end
- str << "<br/><pre>\n"
- a.keys.sort.each do |k|
- if k && a[k] && ! a[k].empty? then
- if k == 'password' then
- val = if a[k] == 'default' then 'default' else '******' end
- else
- # str << "#{k} => #{a[k].sub(/^\s+/moi,'').sub(/\s+$/moi,'')}\n"
- val = a[k].to_s.strip
- val.gsub!("&","&amp;")
- val.gsub!("<","&lt;")
- val.gsub!(">","&gt;")
- val.gsub!("\n","\n ")
- end
- str << "#{k} => #{val}\n"
- end
- end
- str << "</pre><br/>\n"
- return str
- else
- return ''
- end
- end
-
- def status_data
- show_vars(@session , 'Session' ) +
- show_vars(@variables, 'Variables' ) +
- show_vars(@interface, 'Interface' ) +
- show_vars(ENV , 'Environment')
- end
-
- def report_status
- check_template_file('status')
- message('Status',status_data)
- end
-
-end
-
-# attachments
-
-class WWW
-
- def extract_sent_files(dir)
- files = Array.new
- if @cgi then
- @cgi.params.keys.each do |tag|
- begin
- if filename = @cgi[tag].original_filename then
- files << extract_file_content(dir,filename,@cgi[tag]) unless filename.empty?
- end
- rescue
- end
- end
- elsif @webrick_request then
- @webrick_request.query.keys.each do |tag|
- begin
- if filename = @webrick_request.query[tag].filename then
- files << extract_file_content(dir,filename,@webrick_request.query[tag]) unless filename.empty?
- end
- rescue
- end
- end
- end
- @interface.set('log:attachments', files.compact.uniq.join('|'))
- end
-
- def extract_file_content(dir,filename,data)
- filename = File.join(dir,File.basename(filename))
- begin
- @interface.set('log:attachclass', data.class.inspect)
- if data.class == Tempfile then
- begin
- File.copy(data.path,filename)
- rescue
- begin
- File.open(filename,'wb') do |f|
- File.open(data.path,'rb') do |g|
- while str = g.gets do
- f.write(str)
- end
- end
- end
- rescue
- @interface.set('log:attachstate', "saving tempfile #{filename} failed (#{$!})")
- else
- @interface.set('log:attachstate', "tempfile #{filename} has been saved")
- end
- else
- @interface.set('log:attachstate', "#{data.path} copied to #{filename}")
- end
- elsif data.class == String then
- begin
- File.open(filename,'wb') do |f|
- f.write(data)
- end
- rescue
- @interface.set('log:attachstate', "saving string #{filename} failed (#{$!})")
- else
- @interface.set('log:attachstate', "string #{filename} has been saved")
- end
- elsif data.class == StringIO then
- begin
- File.open(filename,'wb') do |f|
- f.write(data.read)
- end
- rescue
- @interface.set('log:attachstate', "saving stringio #{filename} failed (#{$!})")
- else
- @interface.set('log:attachstate', "stringio #{filename} has been saved")
- end
- else
- @interface.set('log:attachstate', "unknown attachment class #{data.class.to_s}")
- end
- rescue
- begin File.delete(filename) ; rescue ; end
- else
- begin File.delete(filename) if FileTest.size(filename) == 0 ; rescue ; end
- end
- return File.basename(filename)
- end
-
-end
-
-# configuration
-
-class WWW
-
- def interface_base_name(str)
- str.sub(/\.(pdf|htm|html)$/, '')
- end
-
- def located_interface_file(filename)
- ['configurations', 'runners', 'scripts'].each do |tag|
- datafile = File.join(@interface.get("path:#{tag}"),filename)
- if FileTest.file?(datafile+'.encrypted') then
- return datafile + '.encrypted'
- elsif FileTest.file?(datafile) then
- return datafile
- end
- end
- return nil
- end
-
- def load_interface_file(filename=@@data_file)
- reset_session() # no save yet
- if datafile = located_interface_file(filename) then
- nestedfiles = Array.new
- begin
- data = IO.read(datafile) || ''
- unless data.empty? then
- loop do # we need to load them recursively
- done = false
- data.gsub!(/^include\s*:\s*(.*?)\s*$/) do
- includedname, done = $1, true
- if nestedname = located_interface_file(includedname) then
- begin
- str = ("\n" + IO.read(nestedname) + "\n") || ''
- rescue
- nestedfiles << File.basename('-'+includedname)
- ''
- else
- nestedfiles << File.basename('+'+includedname)
- str
- end
- else
- nestedfiles << File.basename('-'+includedname)
- ''
- end
- end
- break unless done
- end
- end
- @interface.set('log:configurationfile', datafile + ' [' + nestedfiles.join(' ') + ']')
- return data
- rescue
- end
- end
- @interface.set('log:configurationfile', filename + ' [not loaded]')
- return nil
- end
-
- def fetch_session_interface_variables(data)
- data.scan(/^variable\s*:\s*(.*?)\s*\=\s*(.*?)\s*$/) do
- @interface.set($1, $2)
- end
- return true
- end
-
- def fetch_session_project_list(data)
- projectlist, permitted = Array.new, false
- data.scan(/^user\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*\,\s*(.*?)\s*$/) do
- domain, username, password, projects = $1, $2, $3, $4
- if @session.match?('domain',domain) && @session.match?('username',username) then
- if same_passwords(password) then
- projectlist, permitted = @interface.resolved(projects).split(@@re_bar), true
- break
- end
- end
- end
- if permitted then
- @interface.set('log:projectlist', '['+projectlist.join(' ')+']')
- if projectlist.length == 0 then
- return nil
- else
- return projectlist
- end
- else
- @interface.set('log:projectlist', '[no projects]')
- return nil
- end
- end
-
- def fetch_session_command(data)
- data.scan(/^process\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*$/) do
- domain, process, command = $1, $2, $3
- if @session.match?('domain',domain) && @session.match?('process',process) then
- @session.set('command', @interface.resolved(command))
- end
- end
- return @session.get('command')
- end
-
- def fetch_session_settings(data)
- data.scan(/^setting\s*:\s*(.*?)\s*\,\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*$/) do
- domain, process, variable, value = $1, $2, $3, $4
- if @session.match?('domain',domain) && @session.match?('process',process) then
- @interface.set(variable,value)
- end
- end
- end
-
- def get_command(action)
- # @session.set('action', action)
- # if @session.get('process') == 'none' then
- # @interface.set('log:child','yes')
- # @session.set('process', action)
- # end
- if data = load_interface_file() then
- fetch_session_interface_variables(data)
- if projectlist = fetch_session_project_list(data) then
- data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
- domain, project, gui, path, process = $1, $2, $3, $4, $5
- if @session.match?('domain',domain) then
- if @session.match?('project',project) then
- if projectlist.include?(project) then
- @session.set('process', @interface.resolved(process))
- # break # no, else we end up in the parent (e.g. examplap instead of impose)
- end
- elsif ! action.empty? && project == action then
- if projectlist.include?(action) then
- @session.set('process', @interface.resolved(process))
- # break # no, else we end up in the parent (e.g. examplap instead of impose)
- end
- end
- end
- end
- fetch_session_command(data)
- fetch_session_settings(data)
- end
- end
- return ! @session.nothing?('command')
- end
-
- def get_file(filename)
- @session.set('filename', filename)
- if data = load_interface_file() then
- fetch_session_interface_variables(data)
- if projectlist = fetch_session_project_list(data) then
- data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
- domain, project, gui, path, process = $1, $2, $3, $4, $5
- if @session.match?('domain',domain) then
- guilist = @interface.resolved(gui).split(@@re_bar)
- guilist.each do |g|
- if /#{filename}$/ =~ g then
- @session.set('gui', File.expand_path(@interface.resolved(g)))
- @session.set('path', File.expand_path(@interface.resolved(path)))
- @session.set('process', process)
- break # take first matching interface
- end
- end
- end
- end
- end
- end
- return ! (@session.nothing?('gui') && @session.nothing?('path') && @session.nothing?('process'))
- end
-
- def get_path(url='')
- if data = load_interface_file() then
- fetch_session_interface_variables(data)
- if projectlist = fetch_session_project_list(data) then
- data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
- domain, project, gui, path, process = $1, $2, $3, $4, $5
- if @session.match?('domain',domain) && @session.match?('project',project) then
- @session.set('url', url)
- @session.set('gui', '')
- @session.set('path', File.expand_path(@interface.resolved(path)))
- @session.set('process', '')
- end
- end
- end
- end
- return ! @session.nothing?('path')
- end
-
- def get_gui()
- if data = load_interface_file() then
- fetch_session_interface_variables(data)
- if projectlist = fetch_session_project_list(data) then
- data.scan(/^project\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*$/) do
- domain, project, gui, path, process = $1, $2, $3, $4, $5
- if @session.match?('domain',domain) && @session.match?('project',project) && projectlist.include?(project) then
- @session.set('gui', File.expand_path(@interface.resolved(gui)))
- @session.set('path', File.expand_path(@interface.resolved(path)))
- @session.set('process', process) unless process == 'none'
- break # take first matching interface
- end
- end
- data.scan(/^admin\s*:\s*(.*?)\s*\,\s*(.*?)\s*\=\s*(.*?)\s*\,\s*(.*?)\s*$/) do
- domain, project, task, option = $1, $2, $3, $4
- if @session.match?('domain',domain) && @session.match?('project',project) && projectlist.include?(project) then
- @session.set('task', task)
- @session.set('option', option)
- break # take first matching task
- end
- end
- end
- end
- return ! (@session.nothing?('gui') && @session.nothing?('path') && @session.nothing?('process'))
- end
-
- def get_cfg()
- if data = load_interface_file() then
- fetch_session_interface_variables(data)
- end
- end
-
-end
-
-class WWW
-
- def send_reply(logdata='')
- if @interface.true?('trace:run') then
- send_result(logdata)
- else
- dir, tmp = dirname, tmp_path(dirname)
- case @session.get('status')
- when 'running: finished' then
- resultname, replyname = 'result.pdf', 'reply.exa'
- replyfile = File.join(tmp,replyname)
- if FileTest.file?(replyfile) then
- begin
- data = IO.read(replyfile)
- resultname = if data =~ /<exa:output>(.*?)<\/exa:output>/ then $1 else resultname end
- rescue
- plaintext(exareply('error in reply'))
- return
- end
- end
- resultfile = File.join(tmp,resultname)
- if FileTest.file?(resultfile) then
- if indirect?(resultfile) then
- begin
- File.makedirs(File.join(cache_root,dir))
- FileUtils::mv(resultfile,File.join(cache_root,dir,resultname))
- rescue
- plaintext(exareply('unable to access cache'))
- else
- plaintext(exareply('big file', "cache/#{dir}/#{resultname}", "#{File.size?(resultfile)}"))
- end
- else
- send_file(resultfile)
- end
- else
- plaintext(exareply('no result'))
- end
- else # background, running, aborted
- plaintext(exareply(@session.get('status')))
- end
- end
- end
-
- def send_url(fullname)
- dir, tmp = dirname, tmp_path(dirname)
- resultname, replyname = 'result.pdf', 'reply.exa'
- replyfile = File.join(tmp,replyname)
- resultfile = File.join(tmp,resultname)
- targetname = File.join(cache_root,dir,resultname)
- # make sure that there is no target left in case of an
- # error; needed in case of given session name
- if FileTest.directory?(File.join(cache_root,dir)) then
- File.delete(targetname) rescue false
- end
- # now try to locate the file
- if FileTest.file?(fullname) then
- if indirect?(fullname) then
- begin
- # check if directory exists and (if so) delete left overs
- File.makedirs(File.join(cache_root,dir))
- File.delete(targetname) rescue false
- File.symlink(fullname,targetname) rescue message('Status',$!)
- unless FileTest.file?(targetname) then
- FileUtils::cp(fullname,targetname) rescue false
- end
- rescue
- plaintext(exareply('unable to access cache'))
- else
- plaintext(exareply('big file', "cache/#{dir}/#{resultname}", "#{File.size?(fullname)}"))
- end
- else
- send_file(fullname)
- end
- else
- message('Status', 'The file is not found')
- end
- end
-
- def send_result(logdata='')
- check_template_file('exalogin','exalogin-template.htm')
- dir, tmp = dirname, tmp_path(dirname)
- resultname, replyname, logname = 'result.pdf', 'reply.exa', 'log.htm'
- case @session.get('status')
- when 'running: background' then
- if st = @session.get('starttime') then # fuzzy
- st = Time.now.to_i if st.empty?
- if (Time.now.to_i - st.to_i) > @interface.get('process:timeout').to_i then
- message('Status', 'Your request has been aborted (timeout)',true)
- else
- message('Status', 'Your request is queued',true,5,'exastatus')
- end
- end
- when 'running: busy' then
- if st = @session.get('starttime') then # fuzzy
- st = Time.now.to_i if st.empty?
- if (Time.now.to_i - st.to_i) > @interface.get('process:timeout').to_i then
- message('Status', 'Your request has been aborted (timeout)',true)
- else
- message('Status', 'Your request is being processed',true,5,'exastatus')
- end
- end
- when 'running: aborted' then
- message('Status', 'Your request has been aborted (timeout)',true)
- when 'running: finished' then
- if @interface.true?('trace:run') then
- logfile = File.join(tmp,logname)
- begin
- if f = File.open(logname,'w') then
- if logdata.empty? then
- begin
- logdata = IO.read('www-watch.out')
- rescue
- logdata = 'no log data'
- end
- end
- f << filled_template('Log',"<pre>#{CGI::escapeHTML(logdata)}</pre>")
- f.close
- end
- rescue
- message('Error', '')
- end
- if FileTest.file?(logfile) then
- begin
- File.makedirs(File.join(cache_root,dir))
- FileUtils::mv(logfile,File.join(cache_root,dir,logname))
- rescue
- logdata = "<br/><br/>unable to access cache</a>"
- else
- logdata = "<br/><br/><a href='/cache/#{dir}/#{logname}'>#{logname}</a>"
- end
- else
- logdata = ''
- end
- else
- logdata = ''
- end
- # todo: generate reply.exa if no reply
- replyfile = File.join(tmp,replyname)
- if FileTest.file?(replyfile) then
- begin
- data = IO.read(replyfile)
- resultname = if data =~ /<exa:output>(.*?)<\/exa:output>/ then $1 else resultname end
- rescue
- message('Error','There is a problem in handling this request (invalid reply).')
- return
- end
- end
- resultfile = File.join(tmp,resultname)
- if FileTest.file?(resultfile) then
- if indirect?(resultfile) then
- begin
- File.makedirs(File.join(cache_root,dir))
- FileUtils::mv(resultfile,File.join(cache_root,dir,resultname))
- rescue
- str = "<br/><br/>unable to access cache</a>"
- else
- str = "<br/><br/><a href='/cache/#{dir}/#{resultname}'>#{resultname}</a>&nbsp;&nbsp;(#{File.size?(resultname)} bytes)"
- end
- message('Result', 'You can pick up the result here:' + str + logdata)
- else
- send_file(resultfile)
- end
- else
- message('Error', 'There is a problem in handling this request (no result file).' + logdata)
- end
- end
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/www/login.rb b/Master/texmf-dist/scripts/context/ruby/www/login.rb
deleted file mode 100644
index 1c88a97e6ad..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/www/login.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'www/lib'
-
-# basic login
-
-class WWW
-
- def handle_login()
- check_template_file('login','exalogin.htm')
- set('password', '')
- message('Login','')
- end
-
-end
diff --git a/Master/texmf-dist/scripts/context/ruby/wwwclient.rb b/Master/texmf-dist/scripts/context/ruby/wwwclient.rb
deleted file mode 100644
index d41541a095d..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/wwwclient.rb
+++ /dev/null
@@ -1,677 +0,0 @@
-#!/usr/bin/env ruby
-
-# a direct request is just passed on
-#
-# exaclient --direct --request=somerequest.exa --result=somefile.pdf
-#
-# in an extended request the filename in the template file is replaced by the filename
-# given on the command line; templates are located on the current path and at parent
-# directories (two levels); the filename is expanded to a full path
-#
-# exaclient --extend --template=tmicare-l-h.exa --file=somefile.xml --result=somefile.pdf
-#
-# a constructed request is build out of the provided filename and action; the filename is
-# expanded to a full path
-#
-# exaclient --construct --action=tmicare-s-h.exa --file=somefile.xml --result=somefile.pdf
-#
-# in all cases, the result is either determined by a switch or taken from a reply file
-
-banner = ['WWWClient', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
-
-$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
-
-require 'base/switch'
-require 'base/logger'
-
-require 'timeout'
-require 'thread'
-require 'rexml/document'
-require 'net/http'
-
-class File
-
- def File.backtracked(filename,level=3)
- if level > 0 && filename && ! filename.empty? then
- if FileTest.file?(filename) then
- filename
- else
- File.backtracked('../'+filename,level-1)
- end
- else
- filename
- end
- end
-
- def File.expanded(filename)
- File.expand_path(filename)
- end
-
-end
-
-class Commands
-
- include CommandBase
-
-end
-
-class Commands
-
- @@namespace = "xmlns:exa='http://www.pragma-ade.com/schemas/example.rng'"
- @@randchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "abcdefghijklmnopqrstuvwxyz"
-
- def traceback
- "(error: #{$!})" + "\n -- " + $@.join("\n >>")
- end
-
- def pdf(action,filename,enabled)
- if enabled && FileTest.file?(filename) then
- begin
- report("pdf action #{action} on #{filename}")
- case action
- when 'close' then system("pdfclose --all")
- when 'open' then system("pdfopen --file #{filename}")
- end
- rescue
- # forget about it
- end
- end
- end
-
- def status(replyfile,str) # when block, then ok
- begin
- # def status(*whatever)
- # end
- File.open(replyfile,'w') do |f|
- report("saving reply info in '#{replyfile}'")
- f.puts("<?xml version='1.0'?>\n\n")
- f.puts("<exa:reply #{@@namespace}>\n")
- if block_given? then
- f.puts(" <exa:status>ok</exa:status>\n")
- f.puts(" #{yield}\n")
- else
- f.puts(" <exa:status>error</exa:status>\n")
- end
- f.puts(" <exa:comment>" + str + "</exa:comment>\n")
- f.puts("</exa:reply>\n")
- f.close
- report("saving status: #{str}")
- end
- rescue
- report("saving reply info in '#{replyfile}' fails")
- ensure
- exit
- end
- exit # to be real sure
- end
-
-
- def boundary_string (length) # copied from webrick/utils
- rand_max = @@randchars.size
- ret = ""
- length.times do
- ret << @@randchars[rand(rand_max)]
- end
- ret.upcase
- end
-
-end
-
-class Commands
-
- @@connecttimeout = 10*60 # ten minutes
- @@processtimeout = 60*60 # an hour
- @@polldelay = 5 # 5 seconds
-
- def main
-
- datatemplate = @commandline.option('template')
- datafile = @commandline.option('file')
- dataaction = @commandline.option('action')
-
- if ! datatemplate.empty? then
- report("template '#{datatemplate}' specified without --construct")
- report("aborting")
- elsif ! dataaction.empty? then
- report("action data '#{dataaction}' specified without --construct or --extend")
- report("aborting")
- elsif ! datafile.empty? then
- report("action file '#{datafile}' specified without --construct or --extend")
- report("aborting")
- else
- report("assuming --direct")
- direct()
- end
-
- end
-
- def construct
-
- requestfile = @commandline.option('request')
- replyfile = @commandline.option('reply')
-
- datatemplate = @commandline.option('template')
- datafile = @commandline.option('file')
- dataaction = @commandline.option('action')
-
- domain = @commandline.option('domain')
- project = @commandline.option('project')
- username = @commandline.option('username')
- password = @commandline.option('password')
-
- threshold = @commandline.option('threshold')
-
- datablob = ''
-
- begin
- datablob = IO.read(datatemplate)
- rescue
- datablob = ''
- else
- begin
- request = REXML::Document.new(datablob)
- if e = REXML::XPath.match(request.root,"/exa:request/exa:data") then
- datablob = e.to_s.chomp
- end
- rescue
- datablob = ''
- end
- end
-
- begin
- File.open(requestfile,'w') do |f|
- f.puts "<?xml version='1.0'?>\n"
- f.puts "<exa:request #{@@namespace}>\n"
- f.puts " <exa:application>\n"
- f.puts " <exa:action>#{dataaction}</exa:action>\n" unless dataaction.empty?
- f.puts " <exa:filename>#{datafile}</exa:filename>\n" unless datafile.empty?
- f.puts " <exa:threshold>#{threshold}</exa:threshold>\n" unless threshold.empty?
- f.puts " </exa:application>\n"
- f.puts " <exa:client>\n"
- f.puts " <exa:domain>#{domain}</exa:domain>\n"
- f.puts " <exa:project>#{project}</exa:project>\n"
- f.puts " <exa:username>#{username}</exa:username>\n"
- f.puts " <exa:password>#{password}</exa:password>\n"
- f.puts " </exa:client>\n"
- if datablob.empty? then
- f.puts " <exa:data/>\n"
- else
- f.puts " #{datablob.chomp}\n"
- end
- f.puts "</exa:request>"
- end
- rescue
- status(replyfile,"unable to create '#{requestfile}'")
- end
-
- direct()
-
- end
-
- def extend
-
- requestfile = @commandline.option('request')
- replyfile = @commandline.option('reply')
-
- datatemplate = @commandline.option('template')
- datafile = @commandline.option('file')
- dataaction = @commandline.option('action')
-
- threshold = @commandline.option('threshold')
-
- if datatemplate.empty? then
- status(replyfile,"invalid data template '#{datatemplate}'")
- else
- begin
- if FileTest.file?(datatemplate) && oldrequest = IO.read(datatemplate) then
- request, done = REXML::Document.new(oldrequest), false
- if ! threshold.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:threshold") then
- e.text, done = threshold, true
- end
- if ! dataaction.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:action") then
- e.text, done = dataaction, true
- end
- if ! datafile.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application/exa:filename") then
- e.text, done = datafile, true
- end
- #
- if ! threshold.empty? && e = REXML::XPath.match(request.root,"/exa:request/exa:application") then
- e = e.add_element('exa:threshold')
- e.add_text(threshold.to_s)
- done = true
- end
- #
- report("nothing replaced in template file") unless done
- begin
- File.open(requestfile,'w') do |f|
- f.puts(newrequest.to_s)
- end
- rescue
- status(replyfile,"unable to create '#{requestfile}'")
- end
- else
- status(replyfile,"unable to read data template '#{datatemplate}'")
- end
- rescue
- status(replyfile,"unable to handle data template '#{datatemplate}'")
- end
- end
-
- direct()
-
- end
-
- def direct
-
- requestpath = @commandline.option('path')
- requestfile = @commandline.option('request')
- replyfile = @commandline.option('reply')
- resultfile = @commandline.option('result')
- datatemplate = @commandline.option('template')
- datafile = @commandline.option('file')
- threshold = @commandline.option('threshold')
- address = @commandline.option('address')
- port = @commandline.option('port')
- session_id = @commandline.option('session')
- exaurl = @commandline.option('exaurl')
-
- exaurl = "/#{exaurl}" unless exaurl =~ /^\//
-
- address.sub!(/^http\:\/\//io) do
- ''
- end
- address.sub!(/\:(\d+)$/io) do
- port = $1
- ''
- end
-
- autopdf = @commandline.option('autopdf')
-
- dialogue = nil
-
- resultfile.sub!(/\.[a-z]+?$/, '') # don't overwrite the source
-
- unless requestpath.empty? then
- begin
- if FileTest.directory?(requestpath) then
- if Dir.chdir(requestpath) then
- report("gone to path '#{requestpath}'")
- else
- status(replyfile,"unable to go to path '#{requestpath}")
- end
- else
- status(replyfile,"unable to locate '#{requestpath}'")
- end
- rescue
- status(replyfile,"unable to handle '#{requestpath}'")
- end
- end
-
- datafile = File.expand_path(datafile) unless datafile.empty?
- datatemplate = File.backtracked(datatemplate,3) unless datatemplate.empty?
-
- # request must be valid
-
- status(replyfile,'no request file') if requestfile.empty?
- status(replyfile,"invalid request file '#{requestfile}'") unless FileTest.file?(requestfile)
-
- begin
- request = IO.readlines(requestfile).join('')
- request = REXML::Document.new(request)
- status(replyfile,'invalid request (no request)') unless request.root.fully_expanded_name=='exa:request'
- status(replyfile,'invalid request (no application block)') unless request.elements['exa:request'].elements['exa.application'] == nil # explicit nil test needed
- rescue REXML::ParseException
- status(replyfile,'invalid request (invalid xml file)')
- rescue
- status(replyfile,'invalid request (invalid file)')
- else
- report("using request file '#{requestfile}'")
- end
-
- # request can force session_id
-
- if session_id && session_id.empty? then
- begin
- id = request.elements['exa:request'].elements['exa:application'].elements['exa:session'].text
- rescue Exception
- id = ''
- ensure
- if id && ! id.empty? then
- session_id = id
- end
- end
- end
-
- # request can overload reply name
-
- begin
- rreplyfile = request.elements['exa:request'].elements['exa:application'].elements['exa:output'].text
- rescue Exception
- rreplyfile = nil
- ensure
- if rreplyfile && ! rreplyfile.empty? then
- replyfile = rreplyfile
- report("reply file '#{replyfile} set by request'")
- else
- report("using reply file '#{replyfile}'")
- end
- end
-
- # request can overload result name
-
- begin
- rresultfile = request.elements['exa:request'].elements['exa:application'].elements['exa:result']
- rescue Exception
- rresultfile = nil
- ensure
- if rresultfile && ! rresultfile.empty? then
- resultfile = rresultfile
- report("result file '#{resultfile}' set by request")
- else
- report("using result file '#{resultfile}'")
- end
- end
-
- # try to connect to server
-
- start_time = Time.now
-
- processtimeout = begin @commandline.option('timeout').to_i rescue @@processtimeout end
- processtimeout = @@processtimeout if processtimeout == 0 # 'xx'.to_i => 0
-
- dialogue = start_dialogue(address, port, processtimeout)
-
- if dialogue then
- # continue
- else
- status(replyfile,'no connection')
- end
-
- # post request
-
- timeout (@@processtimeout-10) do # -10 so that we run into this one first
- begin
- report("posting request of type '#{exaurl}'")
- report("using session id '#{session_id}'") if session_id && ! session_id.empty?
- firstline, chunks, total = nil, 0, 0
- body, boundary, crlf = '', boundary_string(32), "\x0d\x0a"
- body << '--' + boundary + crlf
- body << "Content-Disposition: form-data; name=\"exa:request\""
- body << crlf
- body << "Content-Type: text/plain"
- body << crlf + crlf
- body << request.to_s
- body << crlf + '--' + boundary + crlf
-if session_id && ! session_id.empty? then
- body << "Content-Disposition: form-data; name=\"exa:session\""
- body << "Content-Type: text/plain"
- body << crlf + crlf
- body << session_id
- body << crlf + '--' + boundary + crlf
-end
- begin
- File.open(datafile,'rb') do |df|
- body << "Content-Disposition: form-data; name=\"filename\""
- body << "Content-Type: text/plain"
- body << crlf + crlf
- body << datafile
- body << crlf + '--' + boundary + crlf
- body << "Content-Disposition: form-data; name=\"fakename\" ; filename=\"#{datafile}\""
- body << "Content-Type: application/octetstream"
- body << "Content-Transfer-Encoding: binary"
- body << crlf + crlf
- body << df.read
- body << crlf + '--' + boundary + '--' + crlf
- end
- rescue
- # skip
- end
- headers = Hash.new
- headers['content-type'] = "multipart/form-data; boundary=#{boundary}"
- headers['content-length'] = body.length.to_s
- begin
- File.open(resultfile,'wb') do |rf|
- begin
- # firstline is max 1024 but ok for reply
- dialogue.post(exaurl,body,headers) do |str|
- if ! firstline || firstline.empty? then
- report('receiving result') if total == 0
- firstline = str
- end
- total += 1
- rf.write(str)
- end
- rescue
- report("forced close #{traceback}")
- end
- end
- rescue
- status(replyfile,'cannot open file')
- end
- begin
- File.delete(resultfile) if File.zero?(resultfile)
- rescue
- end
- unless FileTest.file?(resultfile) then
- report("deleting empty resultfile")
- begin
- File.delete(resultfile)
- rescue
- # nice try, an error anyway
- end
- status(replyfile,'empty file')
- else
- n, id, status = 0, '', ''
- loop do
- again = false
- if ! dialogue then
- again = true
- elsif firstline =~ /(\<exa:reply)/moi then
- begin
- reply = REXML::Document.new(firstline)
- id = (REXML::XPath.match(reply.root,"/exa:reply/exa:session/text()") || '').to_s
- status = (REXML::XPath.match(reply.root,"/exa:reply/exa:status/text()") || '').to_s
- rescue
- report("error in parsing reply #{traceback}")
- break
- else
- report("status: #{status}")
- if (status =~ /^running\s*\:\s*(background|busy)$/i) && (! id.empty?) then
- report("waiting for status reply (#{n*@@polldelay})")
- again = true
- end
- end
- end
- if again then
- n += 1
- sleep(@@polldelay) # todo: duplicate when n > 1
- unless dialogue then
- report('reestablishing connection')
- dialogue = start_dialogue(address, port, processtimeout)
- end
- if dialogue then
- begin
- File.open(resultfile,'wb') do |rf|
- begin
- body = "id=#{id}"
- headers = Hash.new
- headers['content-type'] = "application/x-www-form-urlencoded"
- headers['content-length'] = body.length.to_s
- total, firstline = 0, ''
- dialogue.post("/exastatus",body,headers) do |str|
- if ! firstline || firstline.empty? then
- firstline = str
- end
- total += 1
- rf.write(str)
- end
- rescue
- report("forced close #{traceback}")
- dialogue = nil
- again = true
- end
- end
- begin
- File.delete(resultfile) if File.zero?(resultfile)
- rescue
- end
- rescue
- report("error in opening file #{traceback}")
- status(replyfile,'cannot open file')
- end
- else
- report("unable to make a connection")
- status(replyfile,'unable to make a connection') # exit
- end
- else
- break
- end
- end
- case firstline
- when /<\?xml\s*version=.*?\?>\s*<exa:reply/moi then
- begin
- File.delete(replyfile) if FileTest.file?(replyfile)
- resultfile = replyfile if File.rename(resultfile,replyfile)
- rescue
- end
- report("reply saved in '#{resultfile}'")
- when /\%PDF\-/io then
- report("done, file #{resultfile}, type pdf, #{total} chunks, #{File.size? rescue 0} bytes")
- if resultfile =~ /\.pdf$/i then
- report("file identified as 'pdf'")
- elsif resultfile =~ /\..*$/o
- report("result file suffix should be 'pdf'")
- else
- newresultfile = resultfile + '.pdf'
- newresultfile.sub!(/\.pdf\.pdf/io, '.pdf')
- pdf('close',newresultfile,autopdf)
- begin
- File.delete(newresultfile) if FileTest.file?(newresultfile)
- resultfile = newresultfile if File.rename(resultfile,newresultfile)
- rescue
- report("adding 'pdf' suffix to result name failed")
- else
- report("'pdf' suffix added to result name")
- end
- end
- report("result saved in '#{resultfile}'")
- pdf('open',resultfile,autopdf)
- status(replyfile,'ok') do
- "<exa:filename>#{resultfile}</exa:filename>"
- end
- when /html/io then
- report("done, file #{resultfile}, type html, #{total} chunks, #{File.size? rescue 0} bytes")
- if resultfile =~ /\.(htm|html)$/i then
- report("file identified as 'html'")
- elsif resultfile =~ /\..*$/o
- report("result file suffix should be 'htm'")
- else
- newresultfile = resultfile + '.htm'
- begin
- File.delete(newresultfile) if FileTest.file?(newresultfile)
- resultfile = newresultfile if File.rename(resultfile,newresultfile)
- rescue
- report("adding 'htm' suffix to result name failed")
- else
- report("'htm' suffix added to result name")
- end
- end
- report("result saved in '#{resultfile}'")
- status(replyfile,'ok') do
- "<exa:filename>#{resultfile}</exa:filename>"
- end
- else
- report("no result file, first line #{firstline}")
- status(replyfile,'no result file')
- end
- end
- rescue TimeoutError
- report("aborted due to time out")
- status(replyfile,'time out')
- rescue
- report("aborted due to some problem #{traceback}")
- status(replyfile,"no answer #{traceback}")
- end
- end
-
- begin
- report("run time: #{Time.now-start_time} seconds")
- rescue
- end
-
- end
-
- def start_dialogue(address, port, processtimeout)
- timeout(@@connecttimeout) do
- report("trying to connect to #{address}:#{port}")
- begin
- begin
- if dialogue = Net::HTTP.new(address, port) then
- # dialogue.set_debug_output $stderr
- dialogue.read_timeout = processtimeout # set this before start
- if dialogue.start then
- report("connected to #{address}:#{port}, timeout: #{processtimeout}")
- else
- retry
- end
- else
- retry
- end
- rescue
- sleep(2)
- retry
- else
- return dialogue
- end
- rescue TimeoutError
- return nil
- rescue
- return nil
- end
- end
- end
-
-end
-
-logger = Logger.new(banner.shift)
-commandline = CommandLine.new
-
-commandline.registerflag('autopdf')
-
-commandline.registervalue('path' , '')
-
-commandline.registervalue('request' , 'request.exa')
-commandline.registervalue('reply' , 'reply.exa')
-commandline.registervalue('result' , 'result')
-
-commandline.registervalue('template' , '')
-commandline.registervalue('file' , '')
-commandline.registervalue('action' , '')
-commandline.registervalue('timeout' , '')
-
-commandline.registervalue('domain' , 'default')
-commandline.registervalue('project' , 'default')
-commandline.registervalue('username' , 'guest')
-commandline.registervalue('password' , 'anonymous')
-commandline.registervalue('exaurl' , 'exarequest')
-commandline.registervalue('threshold' , '0')
-commandline.registervalue('session' , '')
-
-commandline.registervalue('address' , 'localhost')
-commandline.registervalue('port' , '80')
-
-commandline.registeraction('direct' , '[--path --request --reply --result --autopdf]')
-commandline.registeraction('construct', '[--path --request --reply --result --autopdf] --file --action')
-commandline.registeraction('extend' , '[--path --request --reply --result --autopdf] --file --action --template')
-
-commandline.registeraction('direct')
-commandline.registeraction('construct')
-commandline.registeraction('extend')
-
-commandline.registerflag('verbose')
-commandline.registeraction('help')
-commandline.registeraction('version')
-
-commandline.expand
-
-Commands.new(commandline,logger,banner).send(commandline.action || 'main')
diff --git a/Master/texmf-dist/scripts/context/ruby/wwwserver.rb b/Master/texmf-dist/scripts/context/ruby/wwwserver.rb
deleted file mode 100644
index 13d5d13125c..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/wwwserver.rb
+++ /dev/null
@@ -1,293 +0,0 @@
-#!/usr/bin/env ruby
-
-banner = ['WWWServer', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
-
-$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
-
-require 'base/switch'
-require 'base/logger'
-
-require 'monitor'
-
-# class WWW < Monitor
-# end
-# class Server < Monitor
-# end
-
-require 'www/lib'
-require 'www/dir'
-require 'www/login'
-require 'www/exa'
-
-require 'tempfile'
-require 'ftools'
-require 'webrick'
-
-class Server
-
- attr_accessor :document_root, :work_path, :logs_path, :port_number, :exa_url, :verbose, :trace, :direct
-
- def initialize(logger)
- @httpd = nil
- @document_root = ''
- @work_path = ''
- @logs_path = ''
- @port_number = 8061
- @exa_url = 'http://localhost:8061'
- @logger = logger
- @n_of_clients = 500
- @request_timeout = 5*60
- @verbose = false
- @trace = false
- @direct = false
- end
-
- def report(str)
- @logger.report(str) if @logger
- end
-
- def setup
- if @document_root.empty? then
- rootpath = File.expand_path($0)
- @document_root = File.expand_path(File.join(File.dirname(rootpath),'..','documents'))
- unless FileTest.directory?(@document_root) then # todo: optional
- loop do
- prevpath = rootpath.dup
- rootpath = File.dirname(rootpath)
- if prevpath == rootpath then
- break
- else
- checkpath = File.join(rootpath,'documents')
- # report("locating: #{checkpath}")
- if FileTest.directory?(checkpath) then
- @document_root = checkpath
- break
- else
- checkpath = File.join(rootpath,'docroot/documents')
- # report("locating: #{checkpath}")
- if FileTest.directory?(checkpath) then
- @document_root = checkpath
- break
- end
- end
- end
- end
- end
- end
- @document_root = File.join(Dir.pwd, 'documents') unless FileTest.directory?(@document_root)
- unless FileTest.directory?(@document_root) then
- report("invalid document root: #{@document_root}")
- exit
- else
- report("using document root: #{@document_root}")
- end
- #
- @work_path = File.expand_path(File.join(@document_root,'..','work')) if @work_path.empty?
- # begin File.makedirs(@work_path) ; rescue ; end # no, let's auto-temp
- if ! FileTest.directory?(@work_path) || ! FileTest.writable?(@work_path) then
- @work_path = File.expand_path(File.join(Dir.tmpdir,'exaserver','work'))
- begin File.makedirs(@logs_path) ; rescue ; end
- end
- report("using work path: #{@work_path}")
- #
- @logs_path = File.expand_path(File.join(@document_root,'..','logs')) if @logs_path.empty?
- # begin File.makedirs(@logs_path) ; rescue ; end # no, let's auto-temp
- if ! FileTest.directory?(@logs_path) || ! FileTest.writable?(@logs_path) then
- @logs_path = File.expand_path(File.join(Dir.tmpdir,'exaserver','logs'))
- begin File.makedirs(@logs_path) ; rescue ; end
- end
- report("using log path: #{@logs_path}")
- #
- if @logs_path.empty? then
- @logfile = $stderr
- @accfile = $stderr
- else
- @logfile = File.join(@logs_path,'exa-info.log')
- @accfile = File.join(@logs_path,'exa-access.log')
- begin File.delete(@logfile) ; rescue ; end
- begin File.delete(@accfile) ; rescue ; end
- end
- #
- begin
- @httpd = WEBrick::HTTPServer.new(
- :DocumentRoot => @document_root,
- :DocumentRootOptions => { :FancyIndexing => false },
- :DirectoryIndex => ['index.html','index.htm','showcase.pdf'],
- :Port => @port_number.to_i,
- :Logger => WEBrick::Log.new(@logfile, WEBrick::Log::INFO), # DEBUG
- :RequestTimeout => @request_timeout,
- :MaxClients => @n_of_clients,
- :AccessLog => [
- [ @accfile, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
- [ @accfile, WEBrick::AccessLog::REFERER_LOG_FORMAT ],
- [ @accfile, WEBrick::AccessLog::AGENT_LOG_FORMAT ],
- # :CGIPathEnv => ENV["PATH"] # PATH environment variable for CGI.
- ]
- )
- rescue
- report("starting server at port: #{@port_number} failed")
- exit
- else
- report("running server at port: #{@port_number}")
- end
-
- begin
- #
- @httpd.mount_proc("/dir") do |request,reply|
- report("accepting /dir") if @verbose
- web_session(request,reply).handle_dir
- end
- @httpd.mount_proc("/login") do |request,reply|
- report("accepting /login") if @verbose
- web_session(request,reply).handle_login
- end
- @httpd.mount("/cache", WEBrick::HTTPServlet::FileHandler, File.join(@work_path,'cache'))
- # @httpd.mount_proc("/cache") do |request,reply|
- # WEBrick::HTTPServlet::FileHandler(@httpd,@work_path) # not ok
- # end
- @httpd.mount_proc("/exalogin") do |request,reply|
- report("accepting /exalogin") if @verbose
- web_session(request,reply).handle_exalogin
- end
- @httpd.mount_proc("/exadefault") do |request,reply|
- report("accepting /exadefault") if @verbose
- web_session(request,reply).handle_exadefault
- end
- @httpd.mount_proc("/exainterface") do |request,reply|
- report("accepting /exainterface") if @verbose
- web_session(request,reply).handle_exainterface
- end
- @httpd.mount_proc("/exarequest") do |request,reply|
- report("accepting /exarequest") if @verbose
- web_session(request,reply).handle_exarequest
- end
- @httpd.mount_proc("/exacommand") do |request,reply|
- report("accepting /exacommand") if @verbose
- web_session(request,reply).handle_exacommand
- end
- @httpd.mount_proc("/exastatus") do |request,reply|
- report("accepting /exastatus") if @verbose
- web_session(request,reply).handle_exastatus
- end
- @httpd.mount_proc("/exaadmin") do |request,reply|
- report("accepting /exaadmin") if @verbose
- web_session(request,reply).handle_exaadmin
- end
- #
- rescue
- report("problem in starting server: #{$!}")
- end
- [:INT, :TERM, :EXIT].each do |signal|
- trap(signal) do
- @httpd.shutdown
- end
- end
- end
-
- def start
- unless @httpd then
- setup
- @httpd.start
- end
- end
-
- def stop
- @httpd.shutdown if @httpd
- end
-
- def restart
- stop
- start
- end
-
- private
-
- def web_session(request,reply)
- www = WWW.new(@httpd,request,reply)
- www.set('path:work', @work_path)
- www.set('path:logs', @logs_path)
- www.set('path:root', File.dirname(@document_root))
- www.set('process:exaurl', @exa_url)
- www.set('trace:errors','yes') if @trace
- www.set('process:background', 'no') if @direct
- return www
- end
-
-end
-
-class Commands
-
- include CommandBase
-
- def start
- if server = setup then server.start end
- end
-
- def stop
- if server = setup then server.stop end
- end
-
- def restart
- if server = setup then server.restart end
- end
-
- private
-
- def setup
- server = Server.new(logger)
- server.document_root = @commandline.option('root')
- server.verbose = @commandline.option('verbose')
- if @commandline.option('forcetemp') then
- server.work_path = Dir.tmpdir + '/exa/work'
- server.logs_path = Dir.tmpdir + '/exa/logs'
- [server.work_path,server.logs_path].each do |d|
- begin
- File.makedirs(d) unless FileTest.directory?(d)
- rescue
- report("unable to create #{d}")
- exit
- end
- unless FileTest.writable?(d) then
- report("unable to access #{d}")
- exit
- end
- end
- else
- server.work_path = @commandline.option('work')
- server.logs_path = @commandline.option('logs')
- end
- server.port_number = @commandline.option('port')
- server.exa_url = @commandline.option('url')
- server.trace = @commandline.option('trace')
- server.direct = @commandline.option('direct')
- return server
- end
-
-end
-
-logger = Logger.new(banner.shift)
-commandline = CommandLine.new
-
-commandline.registervalue('root' , '')
-commandline.registervalue('work' , '')
-commandline.registervalue('logs' , '')
-commandline.registervalue('address', 'localhost')
-commandline.registervalue('port' , '8061')
-commandline.registervalue('url' , 'http://localhost:8061')
-
-commandline.registeraction('start' , 'start the server [--root --forcetemp --work --logs --address --port --url]')
-commandline.registeraction('stop' , 'stop the server')
-commandline.registeraction('restart', 'restart the server')
-
-commandline.registerflag('forcetemp')
-commandline.registerflag('direct')
-commandline.registerflag('verbose')
-commandline.registerflag('trace')
-
-commandline.registeraction('help')
-commandline.registeraction('version')
-
-commandline.expand
-
-Commands.new(commandline,logger,banner).send(commandline.action || 'start')
-
diff --git a/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb b/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb
deleted file mode 100644
index 0faa45aec44..00000000000
--- a/Master/texmf-dist/scripts/context/ruby/wwwwatch.rb
+++ /dev/null
@@ -1,497 +0,0 @@
-#!/usr/bin/env ruby
-
-banner = ['WWWWatch', 'version 1.0.0', '2003-2006', 'PRAGMA ADE/POD']
-
-$: << File.expand_path(File.dirname($0)) ; $: << File.join($:.last,'lib') ; $:.uniq!
-
-require 'base/switch'
-require 'base/logger'
-
-require 'www/common'
-
-require 'monitor'
-require 'fileutils'
-require 'ftools'
-require 'tempfile'
-require 'timeout'
-require 'thread'
-
-class Watch < Monitor
-
- include Common
-
- @@session_prefix = ''
- @@check_factor = 4
- @@process_timeout = 1*60*60
- @@fast_wait_loop = false
-
- @@session_line = /^\s*(?![\#\%])(.*?)\s*\=\s*(.*?)\s*$/o
- @@session_begin = 'begin exa session'
- @@session_end = 'end exa session'
-
- attr_accessor :root_path, :work_path, :create, :cache_path, :delay, :max_threads, :max_age, :verbose
-
- def initialize(logger) # we need to register all @vars here becase of the monitor
- @threads = Hash.new
- @files = Array.new
- @stats = Hash.new
- @skips = Hash.new
- @root_path = ''
- @work_path = Dir.tmpdir
- @cache_path = @work_path
- @last_action = Time.now
- @delay = 1
- @max_threads = 5
- @max_age = @@process_timeout
- @logger = logger
- @verbose = false
- @create = false
- @onlyonerun = false
- # [:INT, :TERM, :EXIT].each do |signal|
- # trap(signal) do
- # kill
- # exit # rescue false
- # end
- # end
- # at_exit do
- # kill
- # end
- end
-
- def trace
- if @verbose && @logger then
- @logger.report("exception: #{$!})")
- $@.each do |t|
- @logger.report(">> #{t}")
- end
- end
- end
-
- def report(str)
- @logger.report(str) if @logger
- end
-
- def setup
- @threads = Hash.new
- @files = Array.new
- @stats = Hash.new
- @skips = Hash.new
- @root_path = File.expand_path(File.join(File.dirname(Dir.pwd),'.')) if @root_path.empty?
- @work_path = File.expand_path(File.join(@root_path,'work','watch')) if @work_path.empty?
- # @cache_path = File.expand_path(File.join(@root_path,'work','cache')) if @cache_path.empty?
- @cache_path = File.expand_path(File.join(File.dirname(@work_path),'cache')) if @cache_path.empty?
- if @create then
- begin File.makedirs(@work_path) ; rescue ; end
- begin File.makedirs(@cache_path) ; rescue ; end
- end
- unless File.writable?(@work_path) then
- @work_path = File.expand_path(File.join(Dir.tmpdir,'work','watch'))
- if @create then
- begin File.makedirs(@work_path) ; rescue ; end
- end
- end
- unless File.writable?(@cache_path) then
- @cache_path = File.expand_path(File.join(Dir.tmpdir,'work','cache'))
- if @create then
- begin File.makedirs(@cache_path) ; rescue ; end
- end
- end
- unless File.writable?(@work_path) then
- puts "no valid work path: #{@work_path}"
- exit! rescue false # no checking, no at_exit done
- end
- unless File.writable?(@cache_path) then
- puts "no valid cache path: #{@cache_path}" ; # no reason to exit
- end
- @last_action = Time.now
- report("watching path #{@work_path}") if @verbose
- end
-
- def lock(lck)
- begin
- report("watchdog: locking #{lck}") if @verbose
- File.open(lck,'w') do |f|
- f << Time.now
- end
- rescue
- trace
- end
- end
-
- def unlock(lck)
- begin
- report("watchdog: unlocking #{lck}") if @verbose
- File.delete(lck)
- rescue
- trace
- end
- end
-
- def kill
- @threads.each do |t|
- t.kill rescue false
- end
- end
-
- def restart
- @files = Array.new
- @skips = Hash.new
- @stats = Hash.new
- kill # threads
- end
-
- def collect
- begin
- @files = Array.new
- Dir.glob("#{@work_path}/#{@@session_prefix}*.ses").each do |sessionfile|
- sessionfile = File.expand_path(sessionfile)
- begin
- if @threads.key?(sessionfile) then
- # leave alone
- elsif (Time.now - File.mtime(sessionfile)) > @max_age.to_i then
- # delete
- FileUtils::rm_r(sessionfile) rescue false
- FileUtils::rm_r(sessionfile.sub(/ses$/,'dir')) rescue false
- FileUtils::rm_r(sessionfile.sub(/ses$/,'lck')) rescue false
- begin
- FileUtils::rm_r(File.join(@cache_path, File.basename(sessionfile.sub(/ses$/,'dir'))))
- rescue
- report("watchdog: problems in cache cleanup #{$!}") # if @verbose
- end
- @stats.delete(sessionfile) rescue false
- @skips.delete(sessionfile) rescue false
- report("watchdog: removing session #{sessionfile}") if @verbose
- elsif ! @skips.key?(sessionfile) then
- @files << sessionfile
- report("watchdog: checking session #{sessionfile}") if @verbose
- end
- rescue
- # maybe purged in the meantime
- end
- end
- rescue
- if File.directory?(@work_path) then
- @files = Array.new
- else
- # maybe dir is deleted (manual cleanup)
- restart
- end
- end
- begin
- Dir.glob("#{@cache_path}/*.dir").each do |dirname|
- begin
- if (Time.now - File.mtime(dirname)) > @max_age.to_i then
- begin
- FileUtils::rm_r(dirname)
- rescue
- report("watchdog: problems in cache cleanup #{$!}") # if @verbose
- end
- end
- rescue
- # maybe purged in the meantime
- end
- end
- rescue
- end
- end
-
- def purge
- begin
- Dir.glob("#{@work_path}/#{@@session_prefix}*").each do |sessionfile|
- sessionfile = File.expand_path(sessionfile)
- begin
- if (Time.now - File.mtime(sessionfile)) > @max_age.to_i then
- begin
- if FileTest.directory?(sessionfile) then
- FileUtils::rm_r(sessionfile)
- else
- File.delete(sessionfile)
- end
- rescue
- end
- begin
- @stats.delete(sessionfile)
- @skips.delete(sessionfile)
- rescue
- end
- report("watchdog: purging session #{sessionfile}") if @verbose
- end
- rescue
- # maybe purged in the meantime
- end
- end
- rescue
- end
- end
-
- def loaded_session_data(filename)
- begin
- if data = IO.readlines(filename) then
- return data if (data.first =~ /^[\#\%]\s*#{@@session_begin}/o) && (data.last =~ /^[\#\%]\s*#{@@session_end}/o)
- end
- rescue
- trace
- end
- return nil
- end
-
- def load(sessionfile)
- # we assume that we get an exception when the file is locked
- begin
- if data = loaded_session_data(sessionfile) then
- report("watchdog: loading session #{sessionfile}") if @verbose
- vars = Hash.new
- data.each do |line|
- begin
- if line.chomp =~ /^(.*?)\s*\=\s*(.*?)\s*$/o then
- key, value = $1, $2
- vars[key] = value
- end
- rescue
- end
- end
- return vars
- else
- return nil
- end
- rescue
- trace
- return nil
- end
- end
-
- def save(sessionfile, vars)
- begin
- report("watchdog: saving session #{sessionfile}") if @verbose
- if @stats.key?(sessionfile) then
- @stats[sessionfile] = File.mtime(sessionfile)
- elsif @stats[sessionfile] == File.mtime(sessionfile) then
- else
- # construct data first
- str = "\# #{@@session_begin}\n"
- for k,v in vars do
- str << "#{k}=#{v}\n"
- end
- str << "\# #{@@session_end}\n"
- # save as fast as possible
- File.open(sessionfile,'w') do |f|
- f.puts(str)
- end
- end
- rescue
- report("watchdog: unable to save session #{sessionfile}") if @verbose
- trace
- return false
- else
- return true
- end
- end
-
- def launch
- begin
- @files.each do |sessionfile|
- if @threads.length < @max_threads then
- begin
- if ! @skips.key?(sessionfile) && (vars = load(sessionfile)) then
- if (id = vars['id']) && vars['status'] then
- if vars['status'] == 'running: background' then
- @last_action = Time.now
- @threads[sessionfile] = Thread.new(vars, sessionfile) do |vars, sessionfile|
- begin
- report("watchdog: starting thread #{sessionfile}") if @verbose
- dir = File.expand_path(sessionfile.sub(/ses$/,'dir'))
- lck = File.expand_path(sessionfile.sub(/ses$/,'lck'))
- start_of_run = Time.now
- start_of_job = start_of_run.dup
- max_time = @max_age
- begin
- start_of_job = vars['starttime'].to_i || start_of_run
- start_of_job = start_of_run if start_of_job == 0
- rescue
- start_of_job = Time.now
- end
- begin
- max_runtime = vars['maxtime'].to_i || @max_age
- max_runtime = @max_age if max_runtime == 0
- max_runtime = max_runtime - (Time.now.to_i - start_of_job.to_i)
- rescue
- max_runtime = @max_age
- end
- lock(lck)
- if max_runtime > 0 then
- command = vars['command'] || ''
- if ! command.empty? then
- vars['status'] = 'running: busy'
- vars['timeout'] = max_runtime.to_s
- save(sessionfile,vars)
- timeout(max_runtime) do
- begin
- command = command_string(dir,command,'process.log')
- report("watchdog: #{command}") if @verbose
- system(command)
- rescue TimeoutError
- vars['status'] = 'running: timeout'
- rescue
- trace
- vars['status'] = 'running: aborted'
- else
- vars['status'] = 'running: finished'
- vars['runtime'] = sprintf("%.02f",(Time.now - start_of_run))
- vars['endtime'] = Time.now.to_i.to_s
- end
- end
- else
- vars['status'] = 'running: aborted' # no command
- end
- else
- vars['status'] = 'running: aborted' # not enough time
- end
- save(sessionfile,vars)
- unlock(lck)
- report("watchdog: ending thread #{sessionfile}") if @verbose
- @threads.delete(sessionfile)
- rescue
- trace
- end
- end
- else
- report("watchdog: skipping - id (#{vars['id']}) / status (#{vars['status']})") if @verbose
- end
- if @onlyonerun then
- @skips[sessionfile] = true
- else
- @skips.delete(sessionfile)
- end
- else
- # not yet ok
- end
- else
- # maybe a lock
- end
- rescue
- trace
- end
- else
- break
- end
- end
- rescue
- trace
- end
- end
-
- def wait
- begin
- # report(Time.now.to_s) if @verbose
- loop do
- @threads.delete_if do |k,v|
- begin
- v == nil || v.stop?
- rescue
- true
- else
- false
- end
- end
- if @threads.length == @max_threads then
- if @delay > @max_threads then
- sleep(@delay)
- else
- sleep(@max_threads)
- end
- break if @@fast_wait_loop
- else
- sleep(@delay)
- break
- end
- end
- rescue
- trace
- end
- end
-
- def check
- begin
- time = Time.now
- if (time - @last_action) > @@check_factor*@max_age then
- report("watchdog: cleanup") if @verbose
- @stats = Hash.new
- @last_action = time
- kill
- end
- rescue
- trace
- end
- end
-
- def cycle
- loop do
- begin
- collect
- launch
- wait
- check
- rescue
- trace
- report("watchdog: some problem, restarting loop")
- end
- end
- end
-
-end
-
-class Commands
-
- include CommandBase
-
- def watch
- if watch = setup then
- watch.cycle
- else
- report("provide valid work path")
- end
- end
- def main
- watch
- end
-
- private
-
- def setup
- if watch = Watch.new(logger) then
- watch.root_path = @commandline.option('root')
- watch.work_path = @commandline.option('work')
- watch.cache_path = @commandline.option('cache')
- watch.create = @commandline.option('create')
- watch.verbose = @commandline.option('verbose')
- begin
- watch.max_threads = @commandline.option('threads').to_i
- rescue
- watch.max_threads = 5
- end
- watch.setup
- end
- return watch
- end
-
-end
-
-logger = Logger.new(banner.shift)
-commandline = CommandLine.new
-
-commandline.registervalue('root', '')
-commandline.registervalue('work', '')
-commandline.registervalue('cache', '')
-commandline.registervalue('threads', '5')
-
-commandline.registerflag('create')
-
-commandline.registeraction('watch', '[--work=path] [--root=path] [--create]')
-
-commandline.registerflag('verbose')
-commandline.registeraction('help')
-commandline.registeraction('version')
-
-commandline.expand
-
-Commands.new(commandline,logger,banner).send(commandline.action || 'main')
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat
index 13614a1d98d..f502b6750bd 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/ctxtools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart ctxtools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute ctxtools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat
deleted file mode 100755
index 36230a528f4..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/mswin/exatools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart exatools.rb %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/luatools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/luatools.bat
deleted file mode 100755
index 383bcb113b2..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/mswin/luatools.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart luatools.lua %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/luatools.lua b/Master/texmf-dist/scripts/context/stubs/mswin/luatools.lua
new file mode 100644
index 00000000000..433d1b8dc0a
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/luatools.lua
@@ -0,0 +1,7267 @@
+#!/usr/bin/env texlua
+
+if not modules then modules = { } end modules ['luatools'] = {
+ version = 1.001,
+ comment = "companion to context.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format = string.format
+
+-- one can make a stub:
+--
+-- #!/bin/sh
+-- env LUATEXDIR=/....../texmf/scripts/context/lua texlua luatools.lua "$@"
+
+-- Although this script is part of the ConTeXt distribution it is
+-- relatively indepent of ConTeXt. The same is true for some of
+-- the luat files. We may may make them even less dependent in
+-- the future. As long as Luatex is under development the
+-- interfaces and names of functions may change.
+
+-- For the sake of independence we optionally can merge the library
+-- code here. It's too much code, but that does not harm. Much of the
+-- library code is used elsewhere. We don't want dependencies on
+-- Lua library paths simply because these scripts are located in the
+-- texmf tree and not in some Lua path. Normally this merge is not
+-- needed when texmfstart is used, or when the proper stub is used or
+-- when (windows) suffix binding is active.
+
+texlua = true
+
+-- begin library merge
+
+
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
+
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
+
+if not string.split then
+
+ -- this will be overloaded by a faster lpeg variant
+
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+ end
+
+end
+
+local chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+string.chr_to_esc = chr_to_esc
+
+function string:esc() -- variant 2
+ return (gsub(self,"(.)",chr_to_esc))
+end
+
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
+end
+
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in gmatch(self,pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return sub(self,1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (gsub(self,"^%s*(.-)%s*$", "%1"))
+end
+
+function string:is_empty()
+ return not find(find,"%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = gsub(self,pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+local chr_to_hex, hex_to_chr = { }, { }
+
+for i=0,255 do
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], hex_to_chr[h] = h, c
+end
+
+function string:to_hex()
+ return (gsub(self or "","(.)",chr_to_hex))
+end
+
+function string:from_hex()
+ return (gsub(self or "","(..)",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, byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+-- we can use format for this (neg n)
+
+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) -- tonumber
+ return find(str,"^[%-%+]?[%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 find(self,"=") then
+ local t = { }
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return nil
+ end
+end
+
+local patterns_escapes = {
+ ["-"] = "%-",
+ ["."] = "%.",
+ ["+"] = "%+",
+ ["*"] = "%*",
+ ["%"] = "%%",
+ ["("] = "%)",
+ [")"] = "%)",
+ ["["] = "%[",
+ ["]"] = "%]",
+}
+
+function string:pattesc()
+ return (gsub(self,".",patterns_escapes))
+end
+
+function string:tohash()
+ local t = { }
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
+ t[s] = true
+ end
+ return t
+end
+
+local pattern = lpeg.Ct(lpeg.C(1)^0)
+
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
+
+--~ 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 P { P(pattern) + 1 * lpeg.V(1) }
+end
+
+function lpeg.startswith(pattern) --slightly adapted
+ return P(pattern)
+end
+
+function lpeg.splitter(pattern, action)
+ return (((1-P(pattern))^1)/action+1)^0
+end
+
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
+local newline = crlf + cr + lf
+local spacing = space^0 * newline
+
+local empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
+local content = (empty + nonempty)^1
+
+local capture = Ct(content^0)
+
+function string:splitlines()
+ return capture:match(self)
+end
+
+lpeg.linebyline = content -- better make a sublibrary
+
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
+
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c:match(self)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
+
+table.join = table.concat
+
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
+function table.strip(tab)
+ local lst = { }
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
+
+local function sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in next, 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
+ sort(srt,compare)
+ else
+ sort(srt)
+ end
+ return srt
+end
+
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
+ end
+ sort(srt)
+ return srt
+end
+
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
+function table.append(t, list)
+ for _,v in next, list do
+ insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in next, list do
+ 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 next, 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 next, 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
+
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
+ end
+ end
+ -- optional second arg
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
+ return new
+ else
+ return { }
+ end
+end
+
+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 next, 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.fastcopy = fastcopy
+table.copy = copy
+
+-- 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 next, 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
+
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
+ end
+ end
+ return h
+end
+
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
+ else
+ tt[#tt+1] = tostring(v) -- tostring not needed
+ end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+end
+
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
+ else
+ handle(format("%s[%q]={",depth,name))
+ end
+ else
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
+ else
+ handle(format("%s %q,",depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
+ end
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
+ else
+ handle(format("%s [%s]=%q,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
+ else
+ handle(format("%s [%s]={},",depth,k))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
+ else
+ handle(format("%s [%q]={},",depth,k))
+ end
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%s,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
+ end
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
+ end
+ end
+ --~ end
+ end
+ end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
+
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
+
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
+ end
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
+
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+end
+
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+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,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ end
+ f:close()
+ end
+end
+
+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
+
+-- the next three may disappear
+
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
+end
+
+function table.insert_before_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
+ end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
+ end
+end
+
+function table.insert_after_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
+ end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
+ end
+end
+
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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 then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+end
+
+table.are_equal = are_equal
+table.identical = identical
+
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ end
+ end
+end
+
+function table.contains(t, v)
+ if t then
+ for i=1, #t do
+ if t[i] == v then
+ return i
+ 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 next, 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] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
+end
+
+function table.reverse_hash(h)
+ local r = { }
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
+ 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
+
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ end
+
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
+
+local byte = string.byte
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
+ if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
+ local data = f:read('*all')
+ -- garbagecollector.check(data)
+ 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()
+ return true
+ else
+ return false
+ 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
+
+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
+
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
+
+local format = string.format
+
+number = number or { }
+
+-- a,b,c,d,e,f = number.toset(100101)
+
+function number.toset(n)
+ return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
+end
+
+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
+
+local one = lpeg.C(1-lpeg.S(''))^1
+
+function number.toset(n)
+ return one:match(tostring(n))
+end
+
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
+
+set = set or { }
+
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
+
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
+ end
+ end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
+ end
+end
+
+function set.totable(n)
+ if n == 0 then
+ return { }
+ else
+ return tabs[n] or { }
+ end
+end
+
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
+ end
+ return concat(t," ")
+ 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
+
+--~ 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'))
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
+
+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 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
+
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
+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()))
+
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+end
+
+local platform
+
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
+ end
+ return platform
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
+
+-- needs a cleanup
+
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
+
+function file.removesuffix(filename)
+ return (gsub(filename,"%.[%a%d]+$",""))
+end
+
+function file.addsuffix(filename, suffix)
+ if not find(filename,"%.[%a%d]+$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+end
+
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
+end
+
+function file.basename(name)
+ return match(name,"^.+[/\\](.-)$") or name
+end
+
+function file.nameonly(name)
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
+end
+
+function file.extname(name)
+ return match(name,"^.+%.([^/\\]-)$") or ""
+end
+
+file.suffix = file.extname
+
+--~ 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 = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ a, b = match(pth,"^(//)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ return (gsub(pth,"//+","/"))
+end
+
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
+end
+
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
+end
+
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
+
+-- todo: lpeg
+
+function file.split_path(str)
+ local t = { }
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
+ if name ~= "" then
+ t[#t+1] = gsub(name,"\001",":")
+ end
+ end
+ return t
+end
+
+function file.join_path(tab)
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
+
+function file.collapse_path(str)
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
+
+function file.robustname(str)
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+function file.copy(oldname,newname)
+ file.savedata(newname,io.loaddata(oldname))
+end
+
+-- lpeg variants, slightly faster, not always
+
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
+
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-url'] = {
+ 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"
+}
+
+local char, gmatch = string.char, string.gmatch
+local tonumber, type = tonumber, type
+
+-- from the spec (on the web):
+--
+-- foo://example.com:8042/over/there?name=ferret#nose
+-- \_/ \______________/\_________/ \_________/ \__/
+-- | | | | |
+-- scheme authority path query fragment
+-- | _____________________|__
+-- / \ / \
+-- urn:example:animal:ferret:nose
+
+url = url or { }
+
+local function tochar(s)
+ return char(tonumber(s,16))
+end
+
+local colon, qmark, hash, slash, percent, endofstring = lpeg.P(":"), lpeg.P("?"), lpeg.P("#"), lpeg.P("/"), lpeg.P("%"), lpeg.P(-1)
+
+local hexdigit = lpeg.R("09","AF","af")
+local plus = lpeg.P("+")
+local escaped = (plus / " ") + (percent * lpeg.C(hexdigit * hexdigit) / tochar)
+
+local scheme = lpeg.Cs((escaped+(1-colon-slash-qmark-hash))^0) * colon + lpeg.Cc("")
+local authority = slash * slash * lpeg.Cs((escaped+(1- slash-qmark-hash))^0) + lpeg.Cc("")
+local path = slash * lpeg.Cs((escaped+(1- qmark-hash))^0) + lpeg.Cc("")
+local query = qmark * lpeg.Cs((escaped+(1- hash))^0) + lpeg.Cc("")
+local fragment = hash * lpeg.Cs((escaped+(1- endofstring))^0) + lpeg.Cc("")
+
+local parser = lpeg.Ct(scheme * authority * path * query * fragment)
+
+function url.split(str)
+ return (type(str) == "string" and parser:match(str)) or str
+end
+
+function url.hashed(str)
+ local s = url.split(str)
+ return {
+ scheme = (s[1] ~= "" and s[1]) or "file",
+ authority = s[2],
+ path = s[3],
+ query = s[4],
+ fragment = s[5],
+ original = str
+ }
+end
+
+function url.filename(filename)
+ local t = url.hashed(filename)
+ return (t.scheme == "file" and t.path:gsub("^/([a-zA-Z])([:|])/)","%1:")) or filename
+end
+
+function url.query(str)
+ if type(str) == "string" then
+ local t = { }
+ for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return str
+ end
+end
+
+--~ print(url.filename("file:///c:/oeps.txt"))
+--~ print(url.filename("c:/oeps.txt"))
+--~ print(url.filename("file:///oeps.txt"))
+--~ print(url.filename("file:///etc/test.txt"))
+--~ print(url.filename("/oeps.txt"))
+
+--~ from the spec on the web (sort of):
+--~
+--~ function test(str)
+--~ print(table.serialize(url.hashed(str)))
+--~ end
+--~
+--~ test("%56pass%20words")
+--~ test("file:///c:/oeps.txt")
+--~ test("file:///c|/oeps.txt")
+--~ test("file:///etc/oeps.txt")
+--~ test("file://./etc/oeps.txt")
+--~ test("file:////etc/oeps.txt")
+--~ test("ftp://ftp.is.co.za/rfc/rfc1808.txt")
+--~ test("http://www.ietf.org/rfc/rfc2396.txt")
+--~ test("ldap://[2001:db8::7]/c=GB?objectClass?one#what")
+--~ test("mailto:John.Doe@example.com")
+--~ test("news:comp.infosystems.www.servers.unix")
+--~ test("tel:+1-816-555-1212")
+--~ test("telnet://192.0.2.16:80/")
+--~ test("urn:oasis:names:specification:docbook:dtd:xml:4.1.2")
+--~ test("/etc/passwords")
+--~ test("http://www.pragma-ade.com/spaced%20name")
+
+--~ test("zip:///oeps/oeps.zip#bla/bla.tex")
+--~ test("zip:///oeps/oeps.zip?bla/bla.tex")
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
+
+local type = type
+local find, gmatch = string.find, string.gmatch
+
+dir = dir or { }
+
+-- optimizing for no string.find (*) does not save time
+
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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 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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ 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 = find(base,"%*%*")
+ 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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
+ if recurse then
+ globfiles(path .. "/" .. name,recurse,func,files)
+ end
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
+ files[#files+1] = path .. "/" .. name
+ end
+ end
+ 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 gmatch(last,"[^/]+") 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 find(last,"^/") 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 find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") 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 gmatch(str,"[^/]+") 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 find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
+ end
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
+ end
+
+end
+
+dir.makedirs = dir.mkdirs
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
+
+local type, tonumber = type, tonumber
+
+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" or str == "t"
+ 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" or str == "t" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-unicode'] = {
+ 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"
+}
+
+utf = utf or unicode.utf8
+
+local concat, utfchar, utfgsub = table.concat, utf.char, utf.gsub
+local char, byte, find, bytepairs = string.char, string.byte, string.find, string.bytepairs
+
+unicode = unicode or { }
+
+-- 0 EF BB BF UTF-8
+-- 1 FF FE UTF-16-little-endian
+-- 2 FE FF UTF-16-big-endian
+-- 3 FF FE 00 00 UTF-32-little-endian
+-- 4 00 00 FE FF UTF-32-big-endian
+
+unicode.utfname = {
+ [0] = 'utf-8',
+ [1] = 'utf-16-le',
+ [2] = 'utf-16-be',
+ [3] = 'utf-32-le',
+ [4] = 'utf-32-be'
+}
+
+function unicode.utftype(f) -- \000 fails !
+ local str = f:read(4)
+ if not str then
+ f:seek('set')
+ return 0
+ elseif find(str,"^%z%z\254\255") then
+ return 4
+ elseif find(str,"^\255\254%z%z") then
+ return 3
+ elseif find(str,"^\254\255") then
+ f:seek('set',2)
+ return 2
+ elseif find(str,"^\255\254") then
+ f:seek('set',2)
+ return 1
+ elseif find(str,"^\239\187\191") then
+ f:seek('set',3)
+ return 0
+ else
+ f:seek('set')
+ return 0
+ end
+end
+
+function unicode.utf16_to_utf8(str, endian) -- maybe a gsub is faster or an lpeg
+ local result, tmp, n, m, p = { }, { }, 0, 0, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = utfchar(n)
+ p = 0
+ end
+ end
+ for l,r in bytepairs(str) do
+ if r then
+ if endian then
+ n = l*256 + r
+ else
+ n = r*256 + l
+ end
+ if m > 0 then
+ n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
+ m = 0
+ doit()
+ elseif n >= 0xD800 and n <= 0xDBFF then
+ m = n
+ else
+ doit()
+ end
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = concat(tmp)
+ end
+ return result
+end
+
+function unicode.utf32_to_utf8(str, endian)
+ local result = { }
+ local tmp, n, m, p = { }, 0, -1, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = utfchar(n)
+ p = 0
+ end
+ end
+ for a,b in bytepairs(str) do
+ if a and b then
+ if m < 0 then
+ if endian then
+ m = a*256*256*256 + b*256*256
+ else
+ m = b*256 + a
+ end
+ else
+ if endian then
+ n = m + a*256 + b
+ else
+ n = m + b*256*256*256 + a*256*256
+ end
+ m = -1
+ doit()
+ end
+ else
+ break
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = concat(tmp)
+ end
+ return result
+end
+
+local function little(c)
+ local b = byte(c) -- b = c:byte()
+ if b < 0x10000 then
+ return char(b%256,b/256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1%256,b1/256,b2%256,b2/256)
+ end
+end
+
+local function big(c)
+ local b = byte(c)
+ if b < 0x10000 then
+ return char(b/256,b%256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1/256,b1%256,b2/256,b2%256)
+ end
+end
+
+function unicode.utf8_to_utf16(str,littleendian)
+ if littleendian then
+ return char(255,254) .. utfgsub(str,".",little)
+ else
+ return char(254,255) .. utfgsub(str,".",big)
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
+ 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"
+}
+
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
+
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
+
+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
+
+utils.merger.strip_comment = true
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ utils.report("reading merge from %s",name)
+ data = f:read("*all")
+ f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ utils.report("saving merge from %s",name)
+ 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
+
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
+function utils.merger._self_libs_(libs,list)
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ local foundpath = nil
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
+ else
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
+ end
+ end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
+ 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
+
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if strip ~= false then
+ command = "-s " .. command
+ end
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
+ else
+ return (pattern_a/set)^1
+ end
+end
+
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
+
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
+
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
+
+aux.settings_to_array_pattern = pattern
+
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
+
+local function set(t,v)
+ t[#t+1] = v
+end
+
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
+
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
+
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
+
+-- temporary here
+
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
+
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
+
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
+
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
+
+debugger = debugger or { }
+
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
+ end
+ end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
+
+-- two
+
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
+
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
+ else
+ function debugger.tracing() return false end ; return false
+ end
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
+trackers = trackers or { }
+
+local data, done = { }, { }
+
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
+ end
+ end
+ end
+end
+
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
+ end
+end
+
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
+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
+
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
+
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
+
+function environment.initialize_arguments(arg)
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ arguments[flag] = true
+ else
+ files[#files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.setargument(name,value)
+ environment.arguments[name] = value
+end
+
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
+ if name:find(v) then
+ return 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,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+ else
+ return ""
+ end
+end
+
+if arg then
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
+ arg = { } -- prevent duplicate handling
+
+end
+
+-- weird place ... depends on a not yet loaded module
+
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
+
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
+
+environment.loadedluacode = loadfile -- can be overloaded
+
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
+
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
+
+-- the next ones can use the previous ones / combine
+
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
+
+local format = string.format
+
+local statusinfo, n, registered = { }, 0, { }
+
+statistics = statistics or { }
+
+statistics.enable = true
+statistics.threshold = 0.05
+
+-- timing functions
+
+local clock = os.gettimeofday or os.clock
+
+local notimer
+
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
+
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
+ end
+ end
+ end
+ return 0
+end
+
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
+
+-- general function
+
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
+ end
+ end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
+ end
+end
+
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
+end
+
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
+end
+
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
+end
+
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
+end
+
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
+ else
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
+ else
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
+ else
+ write(format("[%s",real))
+ end
+ else
+ write("[-")
+ end
+end
+
+function logs.tex.stop_page_number()
+ write("]")
+end
+
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
+ else
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
+ else
+ write_nl("<r/>")
+ end
+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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
+
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
+
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
+
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
+
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
+ end
+end
+
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
+ end
+ end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
+end
+
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
+ end
+
+ return newinstance
+
+end
+
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
+end
+
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
+ end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
+end
+
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
+
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
+end
+
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(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 = resolvers.bare_variable(e)
+ end
+ ie[key] = value
+ end
+ return value or ""
+end
+
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
+--
+
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
+ end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
+ else
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
+ end
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
+ end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
+ else
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
+ end
+ end
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
+ end
+ return resolvers.ownpath
+end
+
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
+ else
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
+ end
+ end
+ identify_own = function() end
+end
+
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
+ end
+ end
+ end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
+ end
+end
+
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
+ local f = io.open(lname)
+ if f then -- this will go
+ f:close()
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ else
+ f = io.open(fname)
+ if f then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
+ 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 = gsub(line,"\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
+ else
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
+ end
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+-- database loading
+
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
+ if instance.loaderror then
+ resolvers.loadlists()
+ resolvers.savefiles()
+ end
+ else
+ resolvers.loadlists()
+ if instance.renewcache then
+ resolvers.savefiles()
+ end
+ end
+end
+
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
+ else
+ -- weird
+ end
+ resolvers.expand_variables()
+ reset_hashes()
+end
+
+-- locators
+
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
+ end
+end
+
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
+end
+
+function resolvers.locators.tex(specification)
+ if specification and specification ~= '' and lfs.isdir(specification) then
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
+ end
+end
+
+-- hashers
+
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
+end
+
+function resolvers.loadfiles()
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.hashdatabase(hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
+end
+
+-- generators:
+
+function resolvers.loadlists()
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.generatedatabase(hash.tag)
+ end
+end
+
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
+end
+
+-- starting with . or .. etc or funny char
+
+local weird = lpeg.P(".")^1 + lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
+ end
+ end
+ end
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ end
+ end
+ end
+ end
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ end
+end
+
+-- savers, todo
+
+function resolvers.savefiles()
+ resolvers.save_data('files')
+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 resolvers.splitconfig()
+ 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 resolvers.joinconfig()
+ for i,c in ipairs(instance.order) do
+ for k,v in pairs(c) do -- ipairs?
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function resolvers.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function resolvers.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
+ local t, h = { }, { }
+ for _,vv in ipairs(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
+ ie[k] = t
+ else
+ ie[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
+end
+
+resolvers.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 resolvers.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 function dump(k,v,m) -- could be moved inline
+ 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(sortedkeys(files)) do -- ipairs
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
+ 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 next, files do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in next, 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
+
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, instance[dataname] do
+ local name = (makename or file.join)(cachename,dataname)
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = resolvers.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
+ os.remove(lucname)
+ end
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
+ end
+ end
+end
+
+function resolvers.load_data(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 .. ".luc") or loadfile(filename .. ".lua")
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = data.content
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,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 resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
+ end
+ end
+ end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance.order[#instance.order+1] = instance.setup[pathname]
+ if instance.loaderror then break end
+ end
+end
+
+function resolvers.loadoldconfig()
+ if not instance.renewcache then
+ for _, cnf in ipairs(instance.cnffiles) do
+ local dname = file.dirname(cnf)
+ resolvers.load_data(dname,'configuration')
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ if instance.loaderror then break end
+ end
+ end
+ resolvers.joinconfig()
+end
+
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
+ if a and b then
+ expansions[a..'.'..b] = v
+ else
+ expansions[k] = v
+ end
+ end
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
+ end
+ while true do
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
+ if n > 0 or m > 0 then
+ expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
+ end
+end
+
+function resolvers.variable(name)
+ return entry(instance.variables,name)
+end
+
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
+end
+
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,name)
+end
+
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
+end
+
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
+end
+
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
+end
+
+do -- no longer needed
+
+ local done = { }
+
+ function resolvers.reset_extra_path()
+ 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 resolvers.register_extra_path(paths,subpaths)
+ local ep = instance.extra_paths or { }
+ local n = #ep
+ if paths and paths ~= "" then
+ if subpaths and subpaths ~= "" then
+ for p in gmatch(paths,"[^,]+") do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in gmatch(subpaths,"[^,]+") do
+ local ps = p .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ else
+ for p in gmatch(paths,"[^,]+") do
+ if not done[p] then
+ ep[#ep+1] = resolvers.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 gmatch(subpaths,"[^,]+") do
+ local ps = ep[i] .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.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
+
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
+ done[v] = true
+ new[#new+1] = v
+ else
+ break
+ end
+ end
+ end
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
+ end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
+ if not str then
+ return ep or { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = gsub(str,"%$","")
+ if not instance.lists[str] then -- cached
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(lst))
+ end
+end
+
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
+ if tmp ~= "" then
+ return resolvers.expanded_path_list(str)
+ else
+ return resolvers.expanded_path_list(tmp)
+ end
+end
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
+end
+
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
+end
+
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
+ if v then
+ return v
+ end
+ v = formats[alternatives[str]]
+ if v then
+ return v
+ end
+ v = suffixmap[file.extname(str)]
+ if v then
+ return formats[isf]
+ end
+ return ''
+end
+
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.split_path(ori))
+ return file.join_path(pth)
+end
+
+resolvers.isreadable = { }
+
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
+ if readable then
+ logs.report("fileio","+ readable: %s",name)
+ else
+ logs.report("fileio","- readable: %s", name)
+ end
+ end
+ return readable
+end
+
+resolvers.isreadable.tex = resolvers.isreadable.file
+
+-- name
+-- name/name
+
+local function collect_files(names)
+ local filelist = { }
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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 find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
+ end
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,bname) -- result
+ }
+ end
+ end
+ end
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
+ end
+ end
+ end
+ if #filelist > 0 then
+ return filelist
+ else
+ return nil
+ end
+end
+
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return suffixes[str]
+ else
+ return {}
+ end
+end
+
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (fakepaths[name] == 1)
+end
+
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
+ local stamp = nil
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- elsewhere
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
+ return instance.found[stamp]
+ end
+ end
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
+ result = { filename }
+ else
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
+ result, ok = { forcedname }, true
+ end
+ else
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ forcedname = filename .. "." .. s
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', 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 = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
+ else
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
+ end
+ else
+ if ext == "" then
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ wantedfiles[#wantedfiles+1] = filename .. "." .. s
+ end
+ end
+ filetype = instance.format
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
+ end
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only / todo == wildcard
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 = collect_files(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 = collect_files(wantedfiles)
+ local doscan, recurse
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ end
+ -- a bit messy ... esp the doscan setting here
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
+ local expr = "^" .. pathname
+ for k=1,#filelist do
+ local fl = filelist[k]
+ local f = fl[2]
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
+ end
+ --- todo, test for readable
+ result[#result+1] = fl[3]
+ resolvers.register_in_trees(f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all / also zips
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
+ local fname = file.join(ppname,w)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',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=1,#result do
+ result[k] = file.collapse_path(result[k])
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+if not resolvers.concatinators then resolvers.concatinators = { } end
+
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
+
+function resolvers.find_files(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 result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
+ instance.format = ''
+ return result
+end
+
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
+end
+
+function resolvers.find_given_files(filename)
+ local bname, result = file.basename(filename), { }
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ 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] = resolvers.concatinators[hash.type](hash.tag,blist,bname) or ""
+ if not instance.allresults then break end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+end
+
+function resolvers.find_wildcard_files(filename) -- todo: remap:
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
+ local files, allresults, done = instance.files, instance.allresults, false
+ if find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ end
+ else
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
+ return result
+end
+
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(filename)[1] or "")
+end
+
+-- main user functions
+
+function resolvers.automount()
+ -- implemented later
+end
+
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
+end
+
+function resolvers.for_files(command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if trace_verbose then
+ report('')
+ end
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in ipairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.format_of_var(str)))
+end
+
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
+
+function resolvers.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
+
+function resolvers.splitmethod(filename)
+ if not filename then
+ return { } -- safeguard
+ elseif type(filename) == "table" then
+ return filename -- already split
+ elseif not find(filename,"://") then
+ return { scheme="file", path = filename, original=filename } -- quick hack
+ else
+ return url.hashed(filename)
+ end
+end
+
+function table.sequenced(t,sep) -- temp here
+ local s = { }
+ for k, v in pairs(t) do -- pairs?
+ s[#s+1] = k .. "=" .. v
+ end
+ return concat(s, sep or " | ")
+end
+
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.splitmethod(filename)) or filename -- no or { }, let it bomb
+ local scheme = specification.scheme
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
+ else
+ return resolvers[what].tex(filename,filetype) -- todo: specification
+ end
+end
+
+function resolvers.clean_path(str)
+ if str then
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
+ else
+ return nil
+ end
+end
+
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
+ end
+end
+
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
+end
+
+function resolvers.with_files(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 next, files do
+ if find(k,"^remap:") then
+ k = files[k]
+ v = files[k] -- chained
+ end
+ if find(k,pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do -- ipairs?
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
+ end
+ end
+ return nil, nil
+end
+
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
+ else
+ b = toboolean(b)
+ return (b == nil and default) or b
+ end
+end
+
+texconfig.kpse_init = false
+
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
+
+-- for a while
+
+input = resolvers
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-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--
+<p>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.</p>
+
+</code>
+TEXMFCACHE=$TMP;$TEMP;$TMPDIR;$TEMPDIR;$HOME;$TEXMFVAR;$VARTEXMF;.
+</code>
+
+<p>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.</p>
+--ldx]]--
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
+caches = caches 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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
+ end
+ end
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
+ if not cachepath then
+ print("\nfatal error: there is no valid (writable) cache path defined\n")
+ os.exit()
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
+ os.exit()
+ end
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
+ return cachepath
+ end
+ return cachepath
+end
+
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
+end
+
+function caches.hashed(tree)
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
+end
+
+function caches.treehash()
+ local tree = caches.configpath()
+ if not tree or tree == "" then
+ return false
+ else
+ return caches.hashed(tree)
+ end
+end
+
+function caches.setpath(...)
+ if not caches.path then
+ if not caches.path then
+ caches.path = caches.temp()
+ end
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
+ if not caches.path then
+ caches.path = '.'
+ end
+ caches.path = resolvers.clean_path(caches.path)
+ if 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(category,subcategory)
+ return function()
+ return caches.setpath(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
+ loader = loader()
+ collectgarbage("step")
+ return loader
+ else
+ return false
+ end
+end
+
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
+ local tmaname, tmcname = caches.setluanames(filepath,filename)
+ return file.iswritable(tmaname)
+end
+
+function caches.savedata(filepath,filename,data,raw)
+ 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',false,true,false)) -- no hex
+ else
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
+ end
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
+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 input and resolvers.instance then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end -- or luc
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+--[[ldx--
+<p>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).</p>
+
+<p>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.</p>
+
+<p>Examples of usage can be found in the font related code.</p>
+--ldx]]--
+
+containers = containers or { }
+
+containers.usecache = true
+
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%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 and caches.setpath and caches.setpath(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 and caches.iswritable(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 caches and not container.storage[name] and containers.usecache 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 and caches 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
+
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
+
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
+
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(cachename))
+ else
+ return file.join(cachename,dataname)
+ end
+ end)
+end
+
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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
+
+resolvers.automounted = resolvers.automounted or { }
+
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { caches.setpath("mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ statistics.starttiming(resolvers.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
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ statistics.stoptiming(resolvers.instance)
+ end
+end
+
+-- status info
+
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
+
+-- experiment (code will move)
+
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
+ end
+end
+
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
+ else
+ return "invalid status file"
+ end
+ else
+ return "missing status file"
+ end
+ end
+ return true
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
+
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
+
+<p>How about just forgetting about them?</p>
+--ldx]]--
+
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
+
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
+
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
+
+local find = string.find
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
+ end
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
+ end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
+ end
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
+ end
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
+ end
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
+ end
+ else
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
+ end
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
+ end
+ end
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-lst'] = {
+ 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"
+}
+
+-- used in mtxrun
+
+local find, concat, upper, format = string.find, table.concat, string.upper, string.format
+
+resolvers.listers = resolvers.listers or { }
+
+local function tabstr(str)
+ if type(str) == 'table' then
+ return concat(str," | ")
+ else
+ return str
+ end
+end
+
+local function list(list,report)
+ local instance = resolvers.instance
+ local pat = upper(pattern or "","")
+ local report = report or texio.write_nl
+ for _,key in pairs(table.sortedkeys(list)) do
+ if instance.pattern == "" or find(upper(key),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ report(format("%s=%s",key,tabstr(list[key])))
+ end
+ else
+ report(format('%s %s=%s',(instance.kpsevars[key] and 'K') or 'E',key,tabstr(list[key])))
+ end
+ end
+ end
+end
+
+function resolvers.listers.variables () list(resolvers.instance.variables ) end
+function resolvers.listers.expansions() list(resolvers.instance.expansions) end
+
+function resolvers.listers.configurations(report)
+ local report = report or texio.write_nl
+ local instance = resolvers.instance
+ for _,key in ipairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or find(key,instance.pattern) then
+ report(format("%s\n",key))
+ for i,c in ipairs(instance.order) do
+ local str = c[key]
+ if str then
+ report(format("\t%s\t%s",i,str))
+ end
+ end
+ report("")
+ end
+ end
+end
+
+
+end -- of closure
+-- end library merge
+
+-- We initialize some characteristics of this program. We need to
+-- do this before we load the libraries, else own.name will not be
+-- properly set (handy for selfcleaning the file). It's an ugly
+-- looking piece of code.
+
+own = { }
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-number.lua',
+ 'l-set.lua',
+ 'l-os.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-url.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+ 'l-unicode.lua',
+ 'l-math.lua',
+ 'l-utils.lua',
+ 'l-aux.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+-- 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-lst.lua', -- lister
+}
+
+-- We need this hack till luatex is fixed.
+
+if arg and arg[0] == 'luatex' 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 resolvers then
+ locate_libs()
+end
+
+if not resolvers then
+ print("")
+ print("Luatools is unable to start up due to lack of libraries. You may")
+ print("try to run 'lua luatools.lua --selfmerge' in the path where this")
+ print("script is located (normally under ..../scripts/context/lua) which")
+ print("will make luatools library independent.")
+ os.exit()
+end
+
+logs.setprogram('LuaTools',"TDS Management Tool 1.31",environment.arguments["verbose"] or false)
+
+local instance = resolvers.reset()
+
+resolvers.defaultlibs = { -- not all are needed (this will become: context.lus (lua spec)
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-boolean.lua',
+ 'l-number.lua',
+ 'l-unicode.lua',
+ 'l-os.lua',
+ 'l-io.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-url.lua',
+ 'l-dir.lua',
+ 'l-utils.lua',
+ 'l-dimen.lua',
+ 'trac-inf.lua',
+ 'trac-tra.lua',
+ 'trac-log.lua',
+ 'luat-env.lua', -- here ?
+ 'data-res.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-tmp.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-pre.lua',
+ 'data-tex.lua',
+ 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-clr.lua',
+ 'data-lua.lua',
+ 'data-ctx.lua',
+ 'luat-fio.lua',
+ 'luat-cnf.lua',
+}
+
+instance.engine = environment.arguments["engine"] or 'luatex'
+instance.progname = environment.arguments["progname"] or 'context'
+instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
+instance.lualibs = environment.arguments["lualibs"] or table.concat(resolvers.defaultlibs,",")
+instance.allresults = environment.arguments["all"] or false
+instance.pattern = environment.arguments["pattern"] or nil
+instance.sortdata = environment.arguments["sort"] or false
+instance.kpseonly = not environment.arguments["all"] or false
+instance.my_format = environment.arguments["format"] or instance.format
+
+if type(instance.pattern) == 'boolean' then
+ logs.simple("invalid pattern specification")
+ instance.pattern = nil
+end
+
+if environment.arguments["trace"] then resolvers.settrace(environment.arguments["trace"]) end
+
+runners = runners or { }
+messages = messages or { }
+
+messages.no_ini_file = [[
+There is no lua initialization file found. This file can be forced by the
+"--progname" directive, or specified with "--luaname", or it is derived
+automatically from the formatname (aka jobname). It may be that you have
+to regenerate the file database using "luatools --generate".
+]]
+
+messages.help = [[
+--generate generate file database
+--variables show configuration variables
+--expansions show expanded variables
+--configurations show configuration order
+--expand-braces expand complex variable
+--expand-path expand variable (resolve paths)
+--expand-var expand variable (resolve references)
+--show-path show path expansion of ...
+--var-value report value of variable
+--find-file report file location
+--find-path report path of file
+--make or --ini make luatex format
+--run or --fmt= run luatex format
+--luafile=str lua inifile (default is <progname>.lua)
+--lualibs=list libraries to assemble (optional when --compile)
+--compile assemble and compile lua inifile
+--verbose give a bit more info
+--all show all found files
+--sort sort cached data
+--engine=str target engine
+--progname=str format or backend
+--pattern=str filter variables
+]]
+
+function runners.make_format(texname)
+ local instance = resolvers.instance
+ if texname and texname ~= "" then
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ if path and lfs then
+ lfs.chdir(path)
+ end
+ end
+ local barename = texname:gsub("%.%a+$","")
+ if barename == texname then
+ texname = texname .. ".tex"
+ end
+ local fullname = resolvers.find_files(texname)[1] or ""
+ if fullname == "" then
+ logs.simple("no tex file with name: %s",texname)
+ else
+ local luaname, lucname, luapath, lualibs = "", "", "", { }
+ -- the following is optional, since context.lua can also
+ -- handle this collect and compile business
+ if environment.arguments["compile"] then
+ if luaname == "" then luaname = barename end
+ logs.simple("creating initialization file: %s",luaname)
+ luapath = file.dirname(luaname)
+ if luapath == "" then
+ luapath = file.dirname(texname)
+ end
+ if luapath == "" then
+ luapath = file.dirname(resolvers.find_files(texname)[1] or "")
+ end
+ lualibs = string.split(instance.lualibs,",")
+ luaname = file.basename(barename .. ".lua")
+ lucname = file.basename(barename .. ".luc")
+ -- todo: when this fails, we can just copy the merged libraries from
+ -- luatools since they are normally the same, at least for context
+ if lualibs[1] then
+ local firstlib = file.join(luapath,lualibs[1])
+ if not lfs.isfile(firstlib) then
+ local foundname = resolvers.find_files(lualibs[1])[1]
+ if foundname then
+ logs.simple("located library path: %s",luapath)
+ luapath = file.dirname(foundname)
+ end
+ end
+ end
+ logs.simple("using library path: %s",luapath)
+ logs.simple("using lua libraries: %s",table.join(lualibs," "))
+ utils.merger.selfcreate(lualibs,luapath,luaname)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ if utils.lua.compile(luaname,lucname,false,strip) and io.exists(lucname) then
+ luaname = lucname
+ logs.simple("using compiled initialization file: %s",lucname)
+ else
+ logs.simple("using uncompiled initialization file: %s",luaname)
+ end
+ else
+ for _, v in pairs({instance.luaname, instance.progname, barename}) do
+ v = string.gsub(v..".lua","%.lua%.lua$",".lua")
+ if v and (v ~= "") then
+ luaname = resolvers.find_files(v)[1] or ""
+ if luaname ~= "" then
+ break
+ end
+ end
+ end
+ end
+ if environment.arguments["noluc"] then
+ luaname = luaname:gsub("%.luc$",".lua") -- make this an option
+ end
+ if luaname == "" then
+ if logs.verbose then
+ logs.simplelines(messages.no_ini_file)
+ logs.simple("texname : %s",texname)
+ logs.simple("luaname : %s",instance.luaname)
+ logs.simple("progname: %s",instance.progname)
+ logs.simple("barename: %s",barename)
+ end
+ else
+ logs.simple("using lua initialization file: %s",luaname)
+ local mp = dir.glob(file.removesuffix(file.basename(luaname)).."-*.mem")
+ if mp and #mp > 0 then
+ for _, name in ipairs(mp) do
+ logs.simple("removing related mplib format %s", file.basename(name))
+ os.remove(name)
+ end
+ end
+ local flags = {
+ "--ini",
+ "--lua=" .. string.quote(luaname)
+ }
+ local bs = (os.platform == "unix" and "\\\\") or "\\" -- todo: make a function
+ local command = "luatex ".. table.concat(flags," ") .. " " .. string.quote(fullname) .. " " .. bs .. "dump"
+ logs.simple("running command: %s\n",command)
+ os.spawn(command)
+ -- todo: do a dummy run that generates the related metafun and mfplain formats
+ end
+ end
+ else
+ logs.simple("no tex file given")
+ end
+end
+
+function runners.run_format(name,data,more)
+ -- hm, rather old code here; we can now use the file.whatever functions
+ if name and (name ~= "") then
+ local barename = name:gsub("%.%a+$","")
+ local fmtname = ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ barename = fmtname:gsub("%.%a+$","")
+ if fmtname == "" then
+ logs.simple("no format with name: %s",name)
+ else
+ local luaname = barename .. ".luc"
+ local f = io.open(luaname)
+ if not f then
+ luaname = barename .. ".lua"
+ f = io.open(luaname)
+ end
+ if f then
+ f:close()
+ local command = "luatex --fmt=" .. string.quote(barename) .. " --lua=" .. string.quote(luaname) .. " " .. string.quote(data) .. " " .. (more ~= "" and string.quote(more) or "")
+ logs.simple("running command: %s",command)
+ os.spawn(command)
+ else
+ logs.simple("using format name: %s",fmtname)
+ logs.simple("no luc/lua with name: %s",barename)
+ end
+ end
+ end
+end
+
+local ok = true
+
+-- private option --noluc for testing errors in the stub
+
+if environment.arguments["find-file"] then
+ resolvers.load()
+ instance.format = environment.arguments["format"] or instance.format
+ if instance.pattern then
+ instance.allresults = true
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
+ else
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
+ end
+elseif environment.arguments["find-path"] then
+ resolvers.load()
+ local path = resolvers.find_file(environment.files[1], instance.my_format)
+ if logs.verbose then
+ logs.simple(file.dirname(path))
+ else
+ print(file.dirname(path))
+ end
+elseif environment.arguments["run"] then
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.files[1] or "",environment.files[2] or "",environment.files[3] or "")
+elseif environment.arguments["fmt"] then
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.arguments["fmt"], environment.files[1] or "",environment.files[2] or "")
+elseif environment.arguments["expand-braces"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_braces, environment.files)
+elseif environment.arguments["expand-path"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_path, environment.files)
+elseif environment.arguments["expand-var"] or environment.arguments["expand-variable"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_var, environment.files)
+elseif environment.arguments["show-path"] or environment.arguments["path-value"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.show_path, environment.files)
+elseif environment.arguments["var-value"] or environment.arguments["show-value"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.var_value, environment.files)
+elseif environment.arguments["format-path"] then
+ resolvers.load()
+ logs.simple(caches.setpath("format"))
+elseif instance.pattern then -- brrr
+ resolvers.load()
+ instance.format = environment.arguments["format"] or instance.format
+ instance.allresults = true
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
+elseif environment.arguments["generate"] then
+ instance.renewcache = true
+ logs.setverbose(true)
+ resolvers.load()
+elseif environment.arguments["make"] or environment.arguments["ini"] or environment.arguments["compile"] then
+ resolvers.load()
+ logs.setverbose(true)
+ runners.make_format(environment.files[1] or "")
+elseif environment.arguments["selfmerge"] then
+ utils.merger.selfmerge(own.name,own.libs,own.list)
+elseif environment.arguments["selfclean"] then
+ utils.merger.selfclean(own.name)
+elseif environment.arguments["selfupdate"] then
+ resolvers.load()
+ logs.setverbose(true)
+ resolvers.update_script(own.name,"luatools")
+elseif environment.arguments["variables"] or environment.arguments["show-variables"] then
+ resolvers.load("nofiles")
+ resolvers.listers.variables()
+elseif environment.arguments["expansions"] or environment.arguments["show-expansions"] then
+ resolvers.load("nofiles")
+ resolvers.listers.expansions()
+elseif environment.arguments["configurations"] or environment.arguments["show-configurations"] then
+ resolvers.load("nofiles")
+ resolvers.listers.configurations()
+elseif environment.arguments["help"] or (environment.files[1]=='help') or (#environment.files==0) then
+ logs.help(messages.help)
+else
+ resolvers.load()
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
+end
+
+if logs.verbose then
+ logs.simpleline()
+ logs.simple("runtime: %0.3f seconds",os.runtime())
+end
+
+if os.platform == "unix" then
+ io.write("\n")
+end
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat b/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat
index 9622d96a5f3..d02bbf7ca7e 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/makempy.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart makempy.pl %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute makempy.pl %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/metatex.cmd b/Master/texmf-dist/scripts/context/stubs/mswin/metatex.cmd
new file mode 100644
index 00000000000..858f28f8fd5
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/metatex.cmd
@@ -0,0 +1,5 @@
+@echo off
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --script metatex %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat
index 10ec3f1d687..8fa0c9639c2 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/mpstools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart mpstools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute mpstools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat b/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat
index 757ea58f9ce..3cab9669f7e 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/mptopdf.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart mptopdf.pl %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute mptopdf.pl %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua b/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua
new file mode 100644
index 00000000000..82d1edecbc5
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua
@@ -0,0 +1,10512 @@
+#!/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 control.
+
+-- 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]
+
+texlua = true
+
+-- begin library merge
+
+
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
+
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
+
+if not string.split then
+
+ -- this will be overloaded by a faster lpeg variant
+
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+ end
+
+end
+
+local chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+string.chr_to_esc = chr_to_esc
+
+function string:esc() -- variant 2
+ return (gsub(self,"(.)",chr_to_esc))
+end
+
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
+end
+
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in gmatch(self,pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return sub(self,1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (gsub(self,"^%s*(.-)%s*$", "%1"))
+end
+
+function string:is_empty()
+ return not find(find,"%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = gsub(self,pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+local chr_to_hex, hex_to_chr = { }, { }
+
+for i=0,255 do
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], hex_to_chr[h] = h, c
+end
+
+function string:to_hex()
+ return (gsub(self or "","(.)",chr_to_hex))
+end
+
+function string:from_hex()
+ return (gsub(self or "","(..)",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, byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+-- we can use format for this (neg n)
+
+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) -- tonumber
+ return find(str,"^[%-%+]?[%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 find(self,"=") then
+ local t = { }
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return nil
+ end
+end
+
+local patterns_escapes = {
+ ["-"] = "%-",
+ ["."] = "%.",
+ ["+"] = "%+",
+ ["*"] = "%*",
+ ["%"] = "%%",
+ ["("] = "%)",
+ [")"] = "%)",
+ ["["] = "%[",
+ ["]"] = "%]",
+}
+
+function string:pattesc()
+ return (gsub(self,".",patterns_escapes))
+end
+
+function string:tohash()
+ local t = { }
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
+ t[s] = true
+ end
+ return t
+end
+
+local pattern = lpeg.Ct(lpeg.C(1)^0)
+
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
+
+--~ 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 P { P(pattern) + 1 * lpeg.V(1) }
+end
+
+function lpeg.startswith(pattern) --slightly adapted
+ return P(pattern)
+end
+
+function lpeg.splitter(pattern, action)
+ return (((1-P(pattern))^1)/action+1)^0
+end
+
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
+local newline = crlf + cr + lf
+local spacing = space^0 * newline
+
+local empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
+local content = (empty + nonempty)^1
+
+local capture = Ct(content^0)
+
+function string:splitlines()
+ return capture:match(self)
+end
+
+lpeg.linebyline = content -- better make a sublibrary
+
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
+
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c:match(self)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
+
+table.join = table.concat
+
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
+function table.strip(tab)
+ local lst = { }
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
+
+local function sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in next, 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
+ sort(srt,compare)
+ else
+ sort(srt)
+ end
+ return srt
+end
+
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
+ end
+ sort(srt)
+ return srt
+end
+
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
+function table.append(t, list)
+ for _,v in next, list do
+ insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in next, list do
+ 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 next, 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 next, 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
+
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
+ end
+ end
+ -- optional second arg
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
+ return new
+ else
+ return { }
+ end
+end
+
+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 next, 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.fastcopy = fastcopy
+table.copy = copy
+
+-- 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 next, 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
+
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
+ end
+ end
+ return h
+end
+
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
+ else
+ tt[#tt+1] = tostring(v) -- tostring not needed
+ end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+end
+
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
+ else
+ handle(format("%s[%q]={",depth,name))
+ end
+ else
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
+ else
+ handle(format("%s %q,",depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
+ end
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
+ else
+ handle(format("%s [%s]=%q,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
+ else
+ handle(format("%s [%s]={},",depth,k))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
+ else
+ handle(format("%s [%q]={},",depth,k))
+ end
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%s,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
+ end
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
+ end
+ end
+ --~ end
+ end
+ end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
+
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
+
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
+ end
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
+
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+end
+
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+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,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ end
+ f:close()
+ end
+end
+
+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
+
+-- the next three may disappear
+
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
+end
+
+function table.insert_before_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
+ end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
+ end
+end
+
+function table.insert_after_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
+ end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
+ end
+end
+
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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 then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+end
+
+table.are_equal = are_equal
+table.identical = identical
+
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ end
+ end
+end
+
+function table.contains(t, v)
+ if t then
+ for i=1, #t do
+ if t[i] == v then
+ return i
+ 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 next, 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] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
+end
+
+function table.reverse_hash(h)
+ local r = { }
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
+ 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
+
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ end
+
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
+
+local byte = string.byte
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
+ if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
+ local data = f:read('*all')
+ -- garbagecollector.check(data)
+ 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()
+ return true
+ else
+ return false
+ 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
+
+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
+
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
+
+local format = string.format
+
+number = number or { }
+
+-- a,b,c,d,e,f = number.toset(100101)
+
+function number.toset(n)
+ return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
+end
+
+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
+
+local one = lpeg.C(1-lpeg.S(''))^1
+
+function number.toset(n)
+ return one:match(tostring(n))
+end
+
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
+
+set = set or { }
+
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
+
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
+ end
+ end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
+ end
+end
+
+function set.totable(n)
+ if n == 0 then
+ return { }
+ else
+ return tabs[n] or { }
+ end
+end
+
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
+ end
+ return concat(t," ")
+ 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
+
+--~ 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'))
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
+
+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 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
+
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
+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()))
+
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+end
+
+local platform
+
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
+ end
+ return platform
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
+
+-- needs a cleanup
+
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
+
+function file.removesuffix(filename)
+ return (gsub(filename,"%.[%a%d]+$",""))
+end
+
+function file.addsuffix(filename, suffix)
+ if not find(filename,"%.[%a%d]+$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+end
+
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
+end
+
+function file.basename(name)
+ return match(name,"^.+[/\\](.-)$") or name
+end
+
+function file.nameonly(name)
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
+end
+
+function file.extname(name)
+ return match(name,"^.+%.([^/\\]-)$") or ""
+end
+
+file.suffix = file.extname
+
+--~ 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 = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ a, b = match(pth,"^(//)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ return (gsub(pth,"//+","/"))
+end
+
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
+end
+
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
+end
+
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
+
+-- todo: lpeg
+
+function file.split_path(str)
+ local t = { }
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
+ if name ~= "" then
+ t[#t+1] = gsub(name,"\001",":")
+ end
+ end
+ return t
+end
+
+function file.join_path(tab)
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
+
+function file.collapse_path(str)
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
+
+function file.robustname(str)
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+function file.copy(oldname,newname)
+ file.savedata(newname,io.loaddata(oldname))
+end
+
+-- lpeg variants, slightly faster, not always
+
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
+
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
+
+local type = type
+local find, gmatch = string.find, string.gmatch
+
+dir = dir or { }
+
+-- optimizing for no string.find (*) does not save time
+
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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 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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ 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 = find(base,"%*%*")
+ 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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
+ if recurse then
+ globfiles(path .. "/" .. name,recurse,func,files)
+ end
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
+ files[#files+1] = path .. "/" .. name
+ end
+ end
+ 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 gmatch(last,"[^/]+") 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 find(last,"^/") 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 find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") 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 gmatch(str,"[^/]+") 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 find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
+ end
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
+ end
+
+end
+
+dir.makedirs = dir.mkdirs
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
+
+local type, tonumber = type, tonumber
+
+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" or str == "t"
+ 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" or str == "t" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
+ 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"
+}
+
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
+
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
+
+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
+
+utils.merger.strip_comment = true
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ utils.report("reading merge from %s",name)
+ data = f:read("*all")
+ f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ utils.report("saving merge from %s",name)
+ 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
+
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
+function utils.merger._self_libs_(libs,list)
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ local foundpath = nil
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
+ else
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
+ end
+ end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
+ 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
+
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if strip ~= false then
+ command = "-s " .. command
+ end
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
+ else
+ return (pattern_a/set)^1
+ end
+end
+
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
+
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
+
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
+
+aux.settings_to_array_pattern = pattern
+
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
+
+local function set(t,v)
+ t[#t+1] = v
+end
+
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
+
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
+
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
+
+-- temporary here
+
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
+
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
+
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-tab'] = {
+ 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"
+}
+
+--[[ldx--
+<p>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 trickery but was less optimized to we
+went this route. First we had a find based parser, now we have an <l n='lpeg'/> based one.
+The find based parser can be found in l-xml-edu.lua along with other older code.</p>
+
+<p>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 <l n='context'/> we also need
+this module for process management, like handling <l n='ctx'/> and <l n='rlx'/>
+files.</p>
+
+<typing>
+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)
+</typing>
+
+<p>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.</p>
+--ldx]]--
+
+xml = xml or { }
+
+--~ local xml = xml
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, setmetatable = type, next, setmetatable
+local format, lower, find = string.format, string.lower, string.find
+
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers.</p>
+--ldx]]--
+
+local trace_remap = false
+
+if trackers then
+ trackers.register("xml.remap", function(v) trace_remap = v end)
+end
+
+function xml.settrace(str,value)
+ if str == "remap" then
+ trace_remap = value or false
+ end
+end
+
+--[[ldx--
+<p>First a hack to enable namespace resolving. A namespace is characterized by
+a <l n='url'/>. The following function associates a namespace prefix with a
+pattern. We use <l n='lpeg'/>, 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.</p>
+--ldx]]--
+
+xml.xmlns = xml.xmlns or { }
+
+local check = lpeg.P(false)
+local parse = check
+
+--[[ldx--
+<p>The next function associates a namespace prefix with an <l n='url'/>. This
+normally happens independent of parsing.</p>
+
+<typing>
+xml.registerns("mml","mathml")
+</typing>
+--ldx]]--
+
+function xml.registerns(namespace, pattern) -- pattern can be an lpeg
+ check = check + lpeg.C(lpeg.P(lower(pattern))) / namespace
+ parse = lpeg.P { lpeg.P(check) + 1 * lpeg.V(1) }
+end
+
+--[[ldx--
+<p>The next function also registers a namespace, but this time we map a
+given namespace prefix onto a registered one, using the given
+<l n='url'/>. This used for attributes like <t>xmlns:m</t>.</p>
+
+<typing>
+xml.checkns("m","http://www.w3.org/mathml")
+</typing>
+--ldx]]--
+
+function xml.checkns(namespace,url)
+ local ns = parse:match(lower(url))
+ if ns and namespace ~= ns then
+ xml.xmlns[namespace] = ns
+ end
+end
+
+--[[ldx--
+<p>Next we provide a way to turn an <l n='url'/> into a registered
+namespace. This used for the <t>xmlns</t> attribute.</p>
+
+<typing>
+resolvedns = xml.resolvens("http://www.w3.org/mathml")
+</typing>
+
+This returns <t>mml</t>.
+--ldx]]--
+
+function xml.resolvens(url)
+ return parse:match(lower(url)) or ""
+end
+
+--[[ldx--
+<p>A namespace in an element can be remapped onto the registered
+one efficiently by using the <t>xml.xmlns</t> table.</p>
+--ldx]]--
+
+--[[ldx--
+<p>This version uses <l n='lpeg'/>. 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 <l n='xml'/> files that
+took 12.5 seconds to load (1.5 for file io and the rest for tree building). With
+the <l n='lpeg'/> implementation we got that down to less 7.3 seconds. Loading the 14
+<l n='context'/> interface definition files (2.6 meg) went down from 1.05 seconds to 0.55.</p>
+
+<p>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
+<l n='lpeg'/> code to it.</p>
+
+<typing>
+<!DOCTYPE Something PUBLIC "... ..." "..." [ ... ] >
+<!DOCTYPE Something PUBLIC "... ..." "..." >
+<!DOCTYPE Something SYSTEM "... ..." [ ... ] >
+<!DOCTYPE Something SYSTEM "... ..." >
+<!DOCTYPE Something [ ... ] >
+<!DOCTYPE Something >
+</typing>
+
+<p>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:</p>
+
+<typing>
+local x = xml.convert(somestring)
+</typing>
+
+<p>An optional second boolean argument tells this function not to create a root
+element.</p>
+--ldx]]--
+
+xml.strip_cm_and_dt = false -- an extra global flag, in case we have many includes
+
+-- not just one big nested table capture (lpeg overflow)
+
+local nsremap, resolvens = 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 cleanup and #value > 0 then
+ value = cleanup(value) -- new
+ end
+ 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
+ dt[0] = top
+ if toclose.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)
+ if at.xmlns then
+ remove(xmlns)
+ end
+ at = { }
+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: " .. gsub(txt,"([ \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)
+
+local 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("<!ELEMENT") * (1-close)^0 * close
+local entitydoctype = optionalspace * P("<!ENTITY") * somespace * (doctypename * somespace * value)/entity * optionalspace * close
+local publicdoctype = doctypename * somespace * P("PUBLIC") * somespace * value * somespace * value * somespace
+local systemdoctype = doctypename * somespace * P("SYSTEM") * somespace * value * somespace
+local definitiondoctype= doctypename * somespace * beginset * P(elementdoctype + entitydoctype)^0 * optionalspace * endset
+local simpledoctype = (1-close)^1 -- * balanced^0
+local somedoctype = C((somespace * (publicdoctype + systemdoctype + definitiondoctype + simpledoctype) * optionalspace)^0)
+
+local instruction = (spacing * begininstruction * someinstruction * endinstruction) / function(...) add_special("@pi@",...) end
+local comment = (spacing * begincomment * somecomment * endcomment ) / function(...) add_special("@cm@",...) end
+local cdata = (spacing * begincdata * somecdata * endcdata ) / function(...) add_special("@cd@",...) end
+local doctype = (spacing * begindoctype * somedoctype * enddoctype ) / function(...) add_special("@dt@",...) end
+
+-- nicer but slower:
+--
+-- local instruction = (lpeg.Cc("@pi@") * spacing * begininstruction * someinstruction * endinstruction) / add_special
+-- local comment = (lpeg.Cc("@cm@") * spacing * begincomment * somecomment * endcomment ) / add_special
+-- local cdata = (lpeg.Cc("@cd@") * spacing * begincdata * somecdata * endcdata ) / add_special
+-- local doctype = (lpeg.Cc("@dt@") * spacing * begindoctype * somedoctype * enddoctype ) / add_special
+
+local trailer = space^0 * (justtext/set_message)^0
+
+-- comment + emptyelement + text + cdata + instruction + V("parent"), -- 6.5 seconds on 40 MB database file
+-- text + comment + emptyelement + cdata + instruction + V("parent"), -- 5.8
+-- text + V("parent") + emptyelement + comment + cdata + instruction, -- 5.5
+
+local grammar = P { "preamble",
+ preamble = utfbom^0 * instruction^0 * (doctype + comment + instruction)^0 * V("parent") * trailer,
+ parent = beginelement * V("children")^0 * endelement,
+ children = text + V("parent") + emptyelement + comment + cdata + instruction,
+}
+
+-- todo: xml.new + properties like entities and strip and such (store in root)
+
+function xml.convert(data, no_root, strip_cm_and_dt, given_entities) -- maybe use table met k/v (given_entities may disapear)
+ strip = strip_cm_and_dt or xml.strip_cm_and_dt
+ stack, top, at, xmlns, errorstr, result, entities = {}, {}, {}, {}, nil, nil, given_entities or {}
+ stack[#stack+1] = top
+ top.dt = { }
+ dt = top.dt
+ if not data or data == "" then
+ errorstr = "empty xml file"
+ elseif not grammar:match(data) then
+ errorstr = "invalid xml file"
+ else
+ errorstr = ""
+ end
+ if errorstr and errorstr ~= "" then
+ result = { dt = { { ns = "", tg = "error", dt = { errorstr }, at={}, er = true } }, error = true }
+ setmetatable(stack, mt)
+ if xml.error_handler then xml.error_handler("load",errorstr) end
+ else
+ result = stack[1]
+ end
+ if not no_root then
+ result = { special = true, ns = "", tg = '@rt@', dt = result.dt, at={}, entities = entities }
+ setmetatable(result, mt)
+ local rdt = result.dt
+ for k=1,#rdt do
+ local v = rdt[k]
+ if type(v) == "table" and not v.special then -- always table -)
+ result.ri = k -- rootindex
+ break
+ end
+ end
+ end
+ return result
+end
+
+--[[ldx--
+<p>Packaging data in an xml like table is done with the following
+function. Maybe it will go away (when not used).</p>
+--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 logs.report) or print
+
+--[[ldx--
+<p>We cannot load an <l n='lpeg'/> from a filehandle so we need to load
+the whole file first. The function accepts a string representing
+a filename or a file handle.</p>
+--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--
+<p>When we inject new elements, we need to convert strings to
+valid trees, which is what the next function does.</p>
+--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--
+<p>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!</p>
+--ldx]]--
+
+local 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
+
+--[[ldx--
+<p>In <l n='context'/> 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.</p>
+--ldx]]--
+
+-- todo: add <?xml version='1.0' standalone='yes'?> 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
+ local etg, edt = e.tg, e.dt
+ local spc = specialconverter and specialconverter[etg]
+ if spc then
+ 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("<!--%s-->",edt[1]))
+ handle("<!--" .. edt[1] .. "-->")
+ elseif etg == "@cd@" then
+ -- handle(format("<![CDATA[%s]]>",edt[1]))
+ handle("<![CDATA[" .. edt[1] .. "]]>")
+ elseif etg == "@dt@" then
+ -- handle(format("<!DOCTYPE %s>",edt[1]))
+ handle("<!DOCTYPE " .. edt[1] .. ">")
+ 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 next, eat do
+ ats[#ats+1] = format('%s=%q',k,attributeconverter(v))
+ end
+ else
+ for k,v in next, eat do
+ ats[#ats+1] = format('%s=%q',k,v)
+ end
+ end
+ end
+ if ern and trace_remap and ern ~= ens then
+ 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
+ local ei = edt[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
+ else
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
+ end
+ 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
+ local ei = e[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
+ else
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
+ end
+ 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=1,#dt do
+ local v = dt[k]
+ if type(v) == "table" and v.special and v.tg == "@pi" and find(v.dt,"xml.*version=") then
+ found = true
+ break
+ end
+ end
+ if not found then
+ insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } )
+ insert(dt, 2, "\n" )
+ end
+ end
+end
+
+--[[ldx--
+<p>At the cost of some 25% runtime overhead you can first convert the tree to a string
+and then handle the lot.</p>
+--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) -- brrr, slow (direct printing is faster)
+ return concat(result,"")
+ end
+ end
+ return ""
+end
+
+--[[ldx--
+<p>The next function operated on the content only and needs a handle function
+that accepts a string.</p>
+--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--
+<p>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):</p>
+
+<lines>
+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
+</lines>
+
+<p>The save function is given below.</p>
+--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--
+<p>A few helpers:</p>
+--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
+
+function xml.isempty(root, pattern)
+ if pattern == "" or pattern == "*" then
+ pattern = nil
+ end
+ if pattern then
+ -- todo
+ return false
+ else
+ return not root or not root.dt or #root.dt == 0 or root.dt == ""
+ end
+end
+
+--[[ldx--
+<p>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:</p>
+
+<typing>
+dt[k] = xml.empty() or xml.empty(dt,k)
+</typing>
+--ldx]]--
+
+function xml.empty(dt,k)
+ if dt and k then
+ dt[k] = ""
+ return dt[k]
+ else
+ return ""
+ end
+end
+
+--[[ldx--
+<p>The next helper assigns a tree (or string). Usage:</p>
+
+<typing>
+dt[k] = xml.assign(root) or xml.assign(dt,k,root)
+</typing>
+--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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-pth'] = {
+ 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"
+}
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, lower, gmatch, gsub, find = string.format, string.lower, string.gmatch, string.gsub, string.find
+
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers. Here we overload a previously defined
+function.</p>
+--ldx]]--
+
+local trace_lpath = false
+
+if trackers then
+ trackers.register("xml.lpath", function(v) trace_lpath = v end)
+end
+
+local settrace = xml.settrace -- lxml-tab
+
+function xml.settrace(str,value)
+ if str == "lpath" then
+ trace_lpath = value or false
+ else
+ settrace(str,value) -- lxml-tab
+ end
+end
+
+--[[ldx--
+<p>We've now arrived at an intersting part: accessing the tree using a subset
+of <l n='xpath'/> and since we're not compatible we call it <l n='lpath'/>. We
+will explain more about its usage in other documents.</p>
+--ldx]]--
+
+local lpathcalls = 0 -- statistics
+local lpathcached = 0 -- statistics
+
+xml.functions = xml.functions or { }
+xml.expressions = xml.expressions or { }
+
+local functions = xml.functions
+local expressions = xml.expressions
+
+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",
+}
+
+-- a rather dumb lpeg
+
+local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
+
+-- instead of using functions we just parse a few names which saves a call
+-- later on
+
+local lp_position = P("position()") / "ps"
+local lp_index = P("index()") / "id"
+local lp_text = P("text()") / "tx"
+local lp_name = P("name()") / "(ns~='' and ns..':'..tg)" -- "((rt.ns~='' and rt.ns..':'..rt.tg) or '')"
+local lp_tag = P("tag()") / "tg" -- (rt.tg or '')
+local lp_ns = P("ns()") / "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_lua_function = C(R("az","AZ","--","__")^1 * (P(".") * R("az","AZ","--","__")^1)^1) * P("(") / function(t) -- todo: better . handling
+ return t .. "("
+end
+
+local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t) -- todo: better . handling
+ if expressions[t] then
+ return "expressions." .. t .. "("
+ else
+ return "expressions.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) -- lpeg.P{"("*C(((1-S("()"))+V(1))^0)*")"}
+
+-- if we use a dedicated namespace then we don't need to pass rt and k
+
+local lp_special = (C(P("name")+P("text")+P("tag"))) * value / function(t,s)
+ if expressions[t] then
+ if s then
+ return "expressions." .. t .. "(r,k," .. s ..")"
+ else
+ return "expressions." .. t .. "(r,k)"
+ end
+ else
+ return "expressions.error(" .. t .. ")"
+ end
+end
+
+local converter = lpeg.Cs ( (
+ lp_position +
+ lp_index +
+ lp_text + lp_name + -- fast one
+ lp_special +
+ lp_noequal + lp_doequal +
+ lp_attribute +
+ lp_lua_function +
+ lp_function +
+1 )^1 )
+
+-- expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1
+
+local template = [[
+ return function(expressions,r,d,k,e,dt,ns,tg,id,ps)
+ local at, tx = e.at or { }, dt[1] or ""
+ return %s
+ end
+]]
+
+local function make_expression(str)
+ str = converter:match(str)
+ return str, loadstring(format(template,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 = 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 + -- brrr, not here !
+ 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 +
+ many + any +
+ 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
+ insert(map, 1, { 16 })
+ end
+ -- print(gsub(table.serialize(map),"[ \n]+"," "))
+ return map
+ end
+ end
+end
+
+local cache = { }
+
+function xml.lpath(pattern,trace)
+ lpathcalls = lpathcalls + 1
+ if type(pattern) == "string" then
+ local result = cache[pattern]
+ if result == nil then -- can be false which is valid -)
+ result = compose(pattern)
+ cache[pattern] = result
+ lpathcached = lpathcached + 1
+ end
+ if trace or trace_lpath then
+ xml.lshow(result)
+ end
+ return result
+ else
+ return pattern
+ end
+end
+
+function xml.cached_patterns()
+ return cache
+end
+
+-- we run out of locals (limited to 200)
+--
+-- local fallbackreport = (texio and texio.write) or io.write
+
+function xml.lshow(pattern,report)
+-- report = report or fallbackreport
+ report = report or (texio and texio.write) or io.write
+ 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=1,#lp do
+ local v = lp[k]
+ 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
+ local report = (type(t[#t]) == "function" and t[#t]) or (texio and texio.write) or io.write
+ if e == nil then
+ report("<!-- no element -->\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
+
+--[[ldx--
+<p>An <l n='lpath'/> 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:</p>
+
+<lines>
+<t>r</t> : the root element of the data table
+<t>d</t> : the data table of the result
+<t>t</t> : the index in the data table of the result
+</lines>
+
+<p> Access to the root and data table makes it possible to construct insert and delete
+functions.</p>
+--ldx]]--
+
+local functions = xml.functions
+local expressions = xml.expressions
+
+expressions.contains = string.find
+expressions.find = string.find
+expressions.upper = string.upper
+expressions.lower = string.lower
+expressions.number = tonumber
+expressions.boolean = toboolean
+
+expressions.oneof = function(s,...) -- slow
+ local t = {...} for i=1,#t do if s == t[i] then return true end end return false
+end
+
+expressions.error = function(str)
+ xml.error_handler("unknown function in lpath expression",str or "?")
+ 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(d,k,n) -- ns + tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 1
+ end
+ end
+ end
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ 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
+
+functions.tag = function(d,k,n) -- only tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 1
+ end
+ end
+ end
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ break
+ else
+ n = n - 1
+ end
+ end
+ end
+ end
+ return (found and found.tg) or ""
+end
+
+expressions.text = functions.text
+expressions.name = functions.name
+expressions.tag = functions.tag
+
+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
+ local hsh = { } -- this will slooow down the lot
+ 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
+ -- we can optimize this for simple searches, but it probably does not pay off
+ hsh[tg] = (hsh[tg] or 0) + 1
+ 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](expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1)
+ 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
+
+--[[ldx--
+<p>Next come all kind of locators and manipulators. The most generic function here
+is <t>xml.filter(root,pattern)</t>. All registers functions in the filters namespace
+can be path of a search path, as in:</p>
+
+<typing>
+local r, d, k = xml.filter(root,"/a/b/c/position(4)"
+</typing>
+--ldx]]--
+
+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[gsub(arguments,"^([\"\'])(.*)%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--
+<p>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.</p>
+--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 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--
+<p>The following functions collect elements and texts.</p>
+--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--
+<p>Often using an iterators looks nicer in the code than passing handler
+functions. The <l n='lua'/> book describes how to use coroutines for that
+purpose (<url href='http://www.lua.org/pil/9.3.html'/>). This permits
+code like:</p>
+
+<typing>
+for r, d, k in xml.elements(xml.load('text.xml'),"title") do
+ print(d[k])
+end
+</typing>
+
+<p>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:</p>
+
+<typing>
+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
+</typing>
+
+<p>We use the function variants in the filters.</p>
+--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--
+<p>We've now arrives at the functions that manipulate the tree.</p>
+--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
+ insert(d,k,element) -- untested
+--~ elseif element.dt then
+--~ for _,v in ipairs(element.dt) do -- i added
+--~ insert(d,k,v)
+--~ k = k + 1
+--~ end
+--~ end
+ else
+ local edt = element.dt
+ if edt then
+ for i=1,#edt do
+ insert(d,k,edt[i])
+ k = k + 1
+ end
+ 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] = 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 gmatch(attribute or "href","([^|]+)") 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, nolines) -- strips all leading and trailing space !
+ 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" then
+
+ if str == "" then
+ -- stripped
+ else
+ if nolines then
+ str = gsub(str,"[ \n\r\t]+"," ")
+ end
+ if str == "" then
+ -- stripped
+ else
+ t[#t+1] = str
+ end
+ end
+ else
+ t[#t+1] = str
+ end
+ end
+ d[k].dt = t
+ end
+ end)
+end
+
+local function rename_space(root, oldspace, newspace) -- fast variant
+ local ndt = #root.dt
+ 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_space(edt, oldspace, newspace)
+ end
+ end
+ end
+end
+
+xml.rename_space = rename_space
+
+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
+
+--[[ldx--
+<p>Here are a few synonyms.</p>
+--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--
+<p>The following helper functions best belong to the <t>lmxl-ini</t>
+module. Some are here because we need then in the <t>mk</t>
+document and other manuals, others came up when playing with
+this module. Since this module is also used in <l n='mtxrun'/> we've
+put them here instead of loading mode modules there then needed.</p>
+--ldx]]--
+
+function xml.gsub(t,old,new)
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "string" then
+ dt[k] = gsub(v,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 = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
+
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
+
+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("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> 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
+
+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
+
+function xml.statistics()
+ return {
+ lpathcalls = lpathcalls,
+ lpathcached = lpathcached,
+ }
+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([[
+--~ <a>
+--~ <b n='01'>01</b>
+--~ <b n='02'>02</b>
+--~ <b n='03'>03</b>
+--~ <b n='04'>OK</b>
+--~ <b n='05'>05</b>
+--~ <b n='06'>06</b>
+--~ <b n='07'>ALSO OK</b>
+--~ </a>
+--~ ]])
+
+--~ xml.settrace("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 = [[
+--~ <?xml version="1.0" encoding="utf-8"?>
+--~ <story line='mojca'>
+--~ <windows>my secret</mouse>
+--~ </story>
+--~ ]]
+
+--~ x = xml.convert([[
+--~ <a><b n='01'>01</b><b n='02'>02</b><x>xx</x><b n='03'>03</b><b n='04'>OK</b></a>
+--~ ]])
+--~ 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)"))
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-ent'] = {
+ 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"
+}
+
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub, find = string.format, string.gsub, string.find
+local utfchar = unicode.utf8.char
+
+--[[ldx--
+<p>We provide (at least here) two entity handlers. The more extensive
+resolver consults a hash first, tries to convert to <l n='utf'/> next,
+and finaly calls a handler when defines. When this all fails, the
+original entity is returned.</p>
+--ldx]]--
+
+xml.entities = xml.entities or { } -- xml.entity_handler == function
+
+function xml.entity_handler(e)
+ return format("[%s]",e)
+end
+
+local function toutf(s)
+ return utfchar(tonumber(s,16))
+end
+
+local 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 find(dk,"&#x.-;") then
+ d[k] = gsub(dk,"&#x(.-);",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 find(e,"^#x") then
+ return utfchar(tonumber(e:sub(3),16))
+ elseif find(e,"^#") then
+ return utfchar(tonumber(e:sub(2)))
+ else
+ local ee = xml.entities[e] -- we cannot shortcut this one (is reloaded)
+ if ee then
+ return ee
+ else
+ local h = xml.entity_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 find(dk,"&.-;") then
+ d[k] = gsub(dk,"&(.-);",resolve)
+ end
+ else
+ resolve_entities(dk)
+ end
+ end
+ end
+end
+
+xml.resolve_entities = resolve_entities
+
+function xml.utfize_text(str)
+ if find(str,"&#") then
+ return (gsub(str,"&#x(.-);",toutf))
+ else
+ return str
+ end
+end
+
+function xml.resolve_text_entities(str) -- maybe an lpeg. maybe resolve inline
+ if find(str,"&") then
+ return (gsub(str,"&(.-);",resolve))
+ else
+ return str
+ end
+end
+
+function xml.show_text_entities(str)
+ if find(str,"&") then
+ return (gsub(str,"&(.-);","[%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 next, documententities do
+ allentities[k] = v
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-mis'] = {
+ 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"
+}
+
+local concat = table.concat
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub = string.format, string.gsub
+
+--[[ldx--
+<p>The following helper functions best belong to the <t>lmxl-ini</t>
+module. Some are here because we need then in the <t>mk</t>
+document and other manuals, others came up when playing with
+this module. Since this module is also used in <l n='mtxrun'/> we've
+put them here instead of loading mode modules there then needed.</p>
+--ldx]]--
+
+function xml.gsub(t,old,new)
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "string" then
+ dt[k] = gsub(v,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 = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
+
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
+
+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("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> oeps </oeps> oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
+
+local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
+
+xml.escaped_pattern = escaped
+xml.unescaped_pattern = unescaped
+xml.cleansed_pattern = cleansed
+
+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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
+
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
+
+debugger = debugger or { }
+
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
+ end
+ end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
+
+-- two
+
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
+
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
+ else
+ function debugger.tracing() return false end ; return false
+ end
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
+trackers = trackers or { }
+
+local data, done = { }, { }
+
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
+ end
+ end
+ end
+end
+
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
+ end
+end
+
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
+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
+
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
+
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
+
+function environment.initialize_arguments(arg)
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ arguments[flag] = true
+ else
+ files[#files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.setargument(name,value)
+ environment.arguments[name] = value
+end
+
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
+ if name:find(v) then
+ return 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,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+ else
+ return ""
+ end
+end
+
+if arg then
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
+ arg = { } -- prevent duplicate handling
+
+end
+
+-- weird place ... depends on a not yet loaded module
+
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
+
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
+
+environment.loadedluacode = loadfile -- can be overloaded
+
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
+
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
+
+-- the next ones can use the previous ones / combine
+
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
+
+local format = string.format
+
+local statusinfo, n, registered = { }, 0, { }
+
+statistics = statistics or { }
+
+statistics.enable = true
+statistics.threshold = 0.05
+
+-- timing functions
+
+local clock = os.gettimeofday or os.clock
+
+local notimer
+
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
+
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
+ end
+ end
+ end
+ return 0
+end
+
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
+
+-- general function
+
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
+ end
+ end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
+ end
+end
+
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
+end
+
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
+end
+
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
+end
+
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
+end
+
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
+ else
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
+ else
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
+ else
+ write(format("[%s",real))
+ end
+ else
+ write("[-")
+ end
+end
+
+function logs.tex.stop_page_number()
+ write("]")
+end
+
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
+ else
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
+ else
+ write_nl("<r/>")
+ end
+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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
+
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
+
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
+
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
+
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
+ end
+end
+
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
+ end
+ end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
+end
+
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
+ end
+
+ return newinstance
+
+end
+
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
+end
+
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
+ end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
+end
+
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
+
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
+end
+
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(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 = resolvers.bare_variable(e)
+ end
+ ie[key] = value
+ end
+ return value or ""
+end
+
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
+--
+
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
+ end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
+ else
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
+ end
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
+ end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
+ else
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
+ end
+ end
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
+ end
+ return resolvers.ownpath
+end
+
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
+ else
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
+ end
+ end
+ identify_own = function() end
+end
+
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
+ end
+ end
+ end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
+ end
+end
+
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
+ local f = io.open(lname)
+ if f then -- this will go
+ f:close()
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ else
+ f = io.open(fname)
+ if f then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
+ 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 = gsub(line,"\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
+ else
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
+ end
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+-- database loading
+
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
+ if instance.loaderror then
+ resolvers.loadlists()
+ resolvers.savefiles()
+ end
+ else
+ resolvers.loadlists()
+ if instance.renewcache then
+ resolvers.savefiles()
+ end
+ end
+end
+
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
+ else
+ -- weird
+ end
+ resolvers.expand_variables()
+ reset_hashes()
+end
+
+-- locators
+
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
+ end
+end
+
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
+end
+
+function resolvers.locators.tex(specification)
+ if specification and specification ~= '' and lfs.isdir(specification) then
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
+ end
+end
+
+-- hashers
+
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
+end
+
+function resolvers.loadfiles()
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.hashdatabase(hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
+end
+
+-- generators:
+
+function resolvers.loadlists()
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.generatedatabase(hash.tag)
+ end
+end
+
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
+end
+
+-- starting with . or .. etc or funny char
+
+local weird = lpeg.P(".")^1 + lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
+ end
+ end
+ end
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ end
+ end
+ end
+ end
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ end
+end
+
+-- savers, todo
+
+function resolvers.savefiles()
+ resolvers.save_data('files')
+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 resolvers.splitconfig()
+ 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 resolvers.joinconfig()
+ for i,c in ipairs(instance.order) do
+ for k,v in pairs(c) do -- ipairs?
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function resolvers.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function resolvers.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
+ local t, h = { }, { }
+ for _,vv in ipairs(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
+ ie[k] = t
+ else
+ ie[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
+end
+
+resolvers.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 resolvers.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 function dump(k,v,m) -- could be moved inline
+ 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(sortedkeys(files)) do -- ipairs
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
+ 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 next, files do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in next, 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
+
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, instance[dataname] do
+ local name = (makename or file.join)(cachename,dataname)
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = resolvers.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
+ os.remove(lucname)
+ end
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
+ end
+ end
+end
+
+function resolvers.load_data(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 .. ".luc") or loadfile(filename .. ".lua")
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = data.content
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,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 resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
+ end
+ end
+ end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance.order[#instance.order+1] = instance.setup[pathname]
+ if instance.loaderror then break end
+ end
+end
+
+function resolvers.loadoldconfig()
+ if not instance.renewcache then
+ for _, cnf in ipairs(instance.cnffiles) do
+ local dname = file.dirname(cnf)
+ resolvers.load_data(dname,'configuration')
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ if instance.loaderror then break end
+ end
+ end
+ resolvers.joinconfig()
+end
+
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
+ if a and b then
+ expansions[a..'.'..b] = v
+ else
+ expansions[k] = v
+ end
+ end
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
+ end
+ while true do
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
+ if n > 0 or m > 0 then
+ expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
+ end
+end
+
+function resolvers.variable(name)
+ return entry(instance.variables,name)
+end
+
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
+end
+
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,name)
+end
+
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
+end
+
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
+end
+
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
+end
+
+do -- no longer needed
+
+ local done = { }
+
+ function resolvers.reset_extra_path()
+ 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 resolvers.register_extra_path(paths,subpaths)
+ local ep = instance.extra_paths or { }
+ local n = #ep
+ if paths and paths ~= "" then
+ if subpaths and subpaths ~= "" then
+ for p in gmatch(paths,"[^,]+") do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in gmatch(subpaths,"[^,]+") do
+ local ps = p .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ else
+ for p in gmatch(paths,"[^,]+") do
+ if not done[p] then
+ ep[#ep+1] = resolvers.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 gmatch(subpaths,"[^,]+") do
+ local ps = ep[i] .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.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
+
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
+ done[v] = true
+ new[#new+1] = v
+ else
+ break
+ end
+ end
+ end
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
+ end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
+ if not str then
+ return ep or { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = gsub(str,"%$","")
+ if not instance.lists[str] then -- cached
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(lst))
+ end
+end
+
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
+ if tmp ~= "" then
+ return resolvers.expanded_path_list(str)
+ else
+ return resolvers.expanded_path_list(tmp)
+ end
+end
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
+end
+
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
+end
+
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
+ if v then
+ return v
+ end
+ v = formats[alternatives[str]]
+ if v then
+ return v
+ end
+ v = suffixmap[file.extname(str)]
+ if v then
+ return formats[isf]
+ end
+ return ''
+end
+
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.split_path(ori))
+ return file.join_path(pth)
+end
+
+resolvers.isreadable = { }
+
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
+ if readable then
+ logs.report("fileio","+ readable: %s",name)
+ else
+ logs.report("fileio","- readable: %s", name)
+ end
+ end
+ return readable
+end
+
+resolvers.isreadable.tex = resolvers.isreadable.file
+
+-- name
+-- name/name
+
+local function collect_files(names)
+ local filelist = { }
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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 find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
+ end
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,bname) -- result
+ }
+ end
+ end
+ end
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
+ end
+ end
+ end
+ if #filelist > 0 then
+ return filelist
+ else
+ return nil
+ end
+end
+
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return suffixes[str]
+ else
+ return {}
+ end
+end
+
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (fakepaths[name] == 1)
+end
+
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
+ local stamp = nil
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- elsewhere
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
+ return instance.found[stamp]
+ end
+ end
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
+ result = { filename }
+ else
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
+ result, ok = { forcedname }, true
+ end
+ else
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ forcedname = filename .. "." .. s
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', 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 = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
+ else
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
+ end
+ else
+ if ext == "" then
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ wantedfiles[#wantedfiles+1] = filename .. "." .. s
+ end
+ end
+ filetype = instance.format
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
+ end
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only / todo == wildcard
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 = collect_files(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 = collect_files(wantedfiles)
+ local doscan, recurse
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ end
+ -- a bit messy ... esp the doscan setting here
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
+ local expr = "^" .. pathname
+ for k=1,#filelist do
+ local fl = filelist[k]
+ local f = fl[2]
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
+ end
+ --- todo, test for readable
+ result[#result+1] = fl[3]
+ resolvers.register_in_trees(f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all / also zips
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
+ local fname = file.join(ppname,w)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',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=1,#result do
+ result[k] = file.collapse_path(result[k])
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+if not resolvers.concatinators then resolvers.concatinators = { } end
+
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
+
+function resolvers.find_files(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 result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
+ instance.format = ''
+ return result
+end
+
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
+end
+
+function resolvers.find_given_files(filename)
+ local bname, result = file.basename(filename), { }
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ 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] = resolvers.concatinators[hash.type](hash.tag,blist,bname) or ""
+ if not instance.allresults then break end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+end
+
+function resolvers.find_wildcard_files(filename) -- todo: remap:
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
+ local files, allresults, done = instance.files, instance.allresults, false
+ if find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ end
+ else
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
+ return result
+end
+
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(filename)[1] or "")
+end
+
+-- main user functions
+
+function resolvers.automount()
+ -- implemented later
+end
+
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
+end
+
+function resolvers.for_files(command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if trace_verbose then
+ report('')
+ end
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in ipairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.format_of_var(str)))
+end
+
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
+
+function resolvers.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
+
+function resolvers.splitmethod(filename)
+ if not filename then
+ return { } -- safeguard
+ elseif type(filename) == "table" then
+ return filename -- already split
+ elseif not find(filename,"://") then
+ return { scheme="file", path = filename, original=filename } -- quick hack
+ else
+ return url.hashed(filename)
+ end
+end
+
+function table.sequenced(t,sep) -- temp here
+ local s = { }
+ for k, v in pairs(t) do -- pairs?
+ s[#s+1] = k .. "=" .. v
+ end
+ return concat(s, sep or " | ")
+end
+
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.splitmethod(filename)) or filename -- no or { }, let it bomb
+ local scheme = specification.scheme
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
+ else
+ return resolvers[what].tex(filename,filetype) -- todo: specification
+ end
+end
+
+function resolvers.clean_path(str)
+ if str then
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
+ else
+ return nil
+ end
+end
+
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
+ end
+end
+
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
+end
+
+function resolvers.with_files(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 next, files do
+ if find(k,"^remap:") then
+ k = files[k]
+ v = files[k] -- chained
+ end
+ if find(k,pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do -- ipairs?
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
+ end
+ end
+ return nil, nil
+end
+
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
+ else
+ b = toboolean(b)
+ return (b == nil and default) or b
+ end
+end
+
+texconfig.kpse_init = false
+
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
+
+-- for a while
+
+input = resolvers
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-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--
+<p>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.</p>
+
+</code>
+TEXMFCACHE=$TMP;$TEMP;$TMPDIR;$TEMPDIR;$HOME;$TEXMFVAR;$VARTEXMF;.
+</code>
+
+<p>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.</p>
+--ldx]]--
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
+caches = caches 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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
+ end
+ end
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
+ if not cachepath then
+ print("\nfatal error: there is no valid (writable) cache path defined\n")
+ os.exit()
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
+ os.exit()
+ end
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
+ return cachepath
+ end
+ return cachepath
+end
+
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
+end
+
+function caches.hashed(tree)
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
+end
+
+function caches.treehash()
+ local tree = caches.configpath()
+ if not tree or tree == "" then
+ return false
+ else
+ return caches.hashed(tree)
+ end
+end
+
+function caches.setpath(...)
+ if not caches.path then
+ if not caches.path then
+ caches.path = caches.temp()
+ end
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
+ if not caches.path then
+ caches.path = '.'
+ end
+ caches.path = resolvers.clean_path(caches.path)
+ if 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(category,subcategory)
+ return function()
+ return caches.setpath(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
+ loader = loader()
+ collectgarbage("step")
+ return loader
+ else
+ return false
+ end
+end
+
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
+ local tmaname, tmcname = caches.setluanames(filepath,filename)
+ return file.iswritable(tmaname)
+end
+
+function caches.savedata(filepath,filename,data,raw)
+ 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',false,true,false)) -- no hex
+ else
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
+ end
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
+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 input and resolvers.instance then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end -- or luc
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-res'] = {
+ 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"
+}
+
+--~ print(resolvers.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
+
+local upper, lower, gsub = string.upper, string.lower, string.gsub
+
+local prefixes = { }
+
+prefixes.environment = function(str)
+ return resolvers.clean_path(os.getenv(str) or os.getenv(upper(str)) or os.getenv(lower(str)) or "")
+end
+
+prefixes.relative = function(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 resolvers.clean_path(str)
+end
+
+prefixes.locate = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path((fullname ~= "" and fullname) or str)
+end
+
+prefixes.filename = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.basename((fullname ~= "" and fullname) or str))
+end
+
+prefixes.pathname = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.dirname((fullname ~= "" and fullname) or str))
+end
+
+prefixes.env = prefixes.environment
+prefixes.rel = prefixes.relative
+prefixes.loc = prefixes.locate
+prefixes.kpse = prefixes.locate
+prefixes.full = prefixes.locate
+prefixes.file = prefixes.filename
+prefixes.path = prefixes.pathname
+
+local function _resolve_(method,target)
+ if prefixes[method] then
+ return prefixes[method](target)
+ else
+ return method .. ":" .. target
+ end
+end
+
+local function resolve(str)
+ if type(str) == "table" then
+ for k, v in pairs(str) do -- ipairs
+ str[k] = resolve(v) or v
+ end
+ elseif str and str ~= "" then
+ str = gsub(str,"([a-z]+):([^ \"\']*)",_resolve_)
+ end
+ return str
+end
+
+resolvers.resolve = resolve
+
+if os.uname then
+
+ for k, v in pairs(os.uname()) do
+ if not prefixes[k] then
+ prefixes[k] = function() return v end
+ end
+ end
+
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+--[[ldx--
+<p>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).</p>
+
+<p>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.</p>
+
+<p>Examples of usage can be found in the font related code.</p>
+--ldx]]--
+
+containers = containers or { }
+
+containers.usecache = true
+
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%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 and caches.setpath and caches.setpath(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 and caches.iswritable(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 caches and not container.storage[name] and containers.usecache 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 and caches 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
+
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
+
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
+
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(cachename))
+ else
+ return file.join(cachename,dataname)
+ end
+ end)
+end
+
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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
+
+resolvers.automounted = resolvers.automounted or { }
+
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { caches.setpath("mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ statistics.starttiming(resolvers.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
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ statistics.stoptiming(resolvers.instance)
+ end
+end
+
+-- status info
+
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
+
+-- experiment (code will move)
+
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
+ end
+end
+
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
+ else
+ return "invalid status file"
+ end
+ else
+ return "missing status file"
+ end
+ end
+ return true
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-zip'] = {
+ 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"
+}
+
+local format, find = string.format, string.find
+
+local trace_locating, trace_verbose = false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trace_verbose = v end)
+
+zip = zip or { }
+zip.archives = zip.archives or { }
+zip.registeredfiles = zip.registeredfiles or { }
+
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+local locators, hashers, concatinators = resolvers.locators, resolvers.hashers, resolvers.concatinators
+
+local archives = zip.archives
+
+-- zip:///oeps.zip?name=bla/bla.tex
+-- zip:///oeps.zip?tree=tex/texmf-local
+
+local function validzip(str) -- todo: use url splitter
+ if not find(str,"^zip://") then
+ return "zip:///" .. str
+ else
+ return str
+ end
+end
+
+function zip.openarchive(name)
+ if not name or name == "" then
+ return nil
+ else
+ local arch = archives[name]
+ if not arch then
+ local full = resolvers.find_file(name) or ""
+ arch = (full ~= "" and zip.open(full)) or false
+ archives[name] = arch
+ end
+ return arch
+ end
+end
+
+function zip.closearchive(name)
+ if not name or (name == "" and archives[name]) then
+ zip.close(archives[name])
+ archives[name] = nil
+ end
+end
+
+-- zip:///texmf.zip?tree=/tex/texmf
+-- zip:///texmf.zip?tree=/tex/texmf-local
+-- zip:///texmf-mine.zip?tree=/tex/texmf-projects
+
+function locators.zip(specification) -- where is this used? startup zips (untested)
+ specification = resolvers.splitmethod(specification)
+ local zipfile = specification.path
+ local zfile = zip.openarchive(name) -- tricky, could be in to be initialized tree
+ if trace_locating then
+ if zfile then
+ logs.report("fileio",'! zip locator, found: %s',specification.original)
+ else
+ logs.report("fileio",'? zip locator, not found: %s',specification.original)
+ end
+ end
+end
+
+function hashers.zip(tag,name)
+ if trace_verbose then
+ logs.report("fileio","loading zip file %s as %s",name,tag)
+ end
+ resolvers.usezipfile(format("%s?tree=%s",tag,name))
+end
+
+function concatinators.zip(tag,path,name)
+ if not path or path == "" then
+ return format('%s?name=%s',tag,name)
+ else
+ return format('%s?name=%s/%s',tag,path,name)
+ end
+end
+
+function resolvers.isreadable.zip(name)
+ return true
+end
+
+function finders.zip(specification,filetype)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'! zip finder, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ dfile = zfile:close()
+ if trace_locating then
+ logs.report("fileio",'+ zip finder, name: %s',q.name)
+ end
+ return specification.original
+ end
+ elseif trace_locating then
+ logs.report("fileio",'? zip finder, path %s',specification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip finder, name: %s',filename)
+ end
+ return unpack(finders.notfound)
+end
+
+function openers.zip(specification)
+ local zipspecification = resolvers.splitmethod(specification)
+ if zipspecification.path then
+ local q = url.query(zipspecification.query)
+ if q.name then
+ local zfile = zip.openarchive(zipspecification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',zipspecification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_open(specification)
+ return openers.text_opener(specification,dfile,'zip')
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path %s',zipspecification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip opener, name: %s',filename)
+ end
+ return unpack(openers.notfound)
+end
+
+function loaders.zip(specification)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_load(filename)
+ if trace_locating then
+ logs.report("fileio",'+ zip loader, name: %s',filename)
+ end
+ local s = dfile:read("*all")
+ dfile:close()
+ return true, s, #s
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path: %s',specification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip loader, name: %s',filename)
+ end
+ return unpack(openers.notfound)
+end
+
+-- zip:///somefile.zip
+-- zip:///somefile.zip?tree=texmf-local -> mount
+
+function resolvers.usezipfile(zipname)
+ zipname = validzip(zipname)
+ if trace_locating then
+ logs.report("fileio",'! zip use, file: %s',zipname)
+ end
+ local specification = resolvers.splitmethod(zipname)
+ local zipfile = specification.path
+ if zipfile and not zip.registeredfiles[zipname] then
+ local tree = url.query(specification.query).tree or ""
+ if trace_locating then
+ logs.report("fileio",'! zip register, file: %s',zipname)
+ end
+ local z = zip.openarchive(zipfile)
+ if z then
+ local instance = resolvers.instance
+ if trace_locating then
+ logs.report("fileio","= zipfile, registering: %s",zipname)
+ end
+ statistics.starttiming(instance)
+ resolvers.prepend_hash('zip',zipname,zipfile)
+ resolvers.extend_texmf_var(zipname) -- resets hashes too
+ zip.registeredfiles[zipname] = z
+ instance.files[zipname] = resolvers.register_zip_file(z,tree or "")
+ statistics.stoptiming(instance)
+ elseif trace_locating then
+ logs.report("fileio","? zipfile, unknown: %s",zipname)
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! zip register, no file: %s',zipname)
+ end
+end
+
+function resolvers.register_zip_file(z,tree)
+ local files, filter = { }, ""
+ if tree == "" then
+ filter = "^(.+)/(.-)$"
+ else
+ filter = format("^%s/(.+)/(.-)$",tree)
+ end
+ if trace_locating then
+ logs.report("fileio",'= zip filter: %s',filter)
+ end
+ local register, n = resolvers.register_file, 0
+ for i in z:files() do
+ local path, name = i.filename:match(filter)
+ if path then
+ if name and name ~= '' then
+ register(files, name, path)
+ n = n + 1
+ else
+ -- directory
+ end
+ else
+ register(files, i.filename, '')
+ n = n + 1
+ end
+ end
+ logs.report("fileio",'= zip entries: %s',n)
+ return files
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-crl'] = {
+ 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"
+}
+
+curl = curl or { }
+
+curl.cached = { }
+curl.cachepath = caches.definepath("curl")
+
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+
+function curl.fetch(protocol, name)
+ local cachename = curl.cachepath() .. "/" .. name:gsub("[^%a%d%.]+","-")
+-- cachename = cachename:gsub("[\\/]", io.fileseparator)
+ cachename = cachename:gsub("[\\]", "/") -- cleanup
+ if not curl.cached[name] then
+ if not io.exists(cachename) then
+ curl.cached[name] = cachename
+ local command = "curl --silent --create-dirs --output " .. cachename .. " " .. name -- no protocol .. "://"
+ os.spawn(command)
+ end
+ if io.exists(cachename) then
+ curl.cached[name] = cachename
+ else
+ curl.cached[name] = ""
+ end
+ end
+ return curl.cached[name]
+end
+
+function finders.curl(protocol,filename)
+ local foundname = curl.fetch(protocol, filename)
+ return finders.generic(protocol,foundname,filetype)
+end
+
+function openers.curl(protocol,filename)
+ return openers.generic(protocol,filename)
+end
+
+function loaders.curl(protocol,filename)
+ return loaders.generic(protocol,filename)
+end
+
+-- todo: metamethod
+
+function curl.install(protocol)
+ finders[protocol] = function (filename,filetype) return finders.curl(protocol,filename) end
+ openers[protocol] = function (filename) return openers.curl(protocol,filename) end
+ loaders[protocol] = function (filename) return loaders.curl(protocol,filename) end
+end
+
+curl.install('http')
+curl.install('https')
+curl.install('ftp')
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
+
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
+
+<p>How about just forgetting about them?</p>
+--ldx]]--
+
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
+
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
+
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
+
+local find = string.find
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
+ end
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
+ end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
+ end
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
+ end
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
+ end
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
+ end
+ else
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
+ end
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
+ end
+ end
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-tmf'] = {
+ 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"
+}
+
+-- loads *.tmf files in minimal tree roots (to be optimized and documented)
+
+function resolvers.check_environment(tree)
+ logs.simpleline()
+ 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'))
+ logs.simpleline()
+ logs.simple("preset : TEXPATH => %s", os.getenv('TEXPATH'))
+ logs.simple("preset : TEXOS => %s", os.getenv('TEXOS'))
+ logs.simple("preset : TEXMFOS => %s", os.getenv('TEXMFOS'))
+ logs.simple("preset : TMP => %s", os.getenv('TMP'))
+ logs.simple('')
+end
+
+function resolvers.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 resolvers.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
+ resolvers.check_environment(tree)
+ resolvers.load_environment(setuptex)
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this code is used in the updater
+
+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
+ if type(d) == "table" 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
+ elseif type(d) == "string" then
+ -- weird
+ states.data[tag], states.hash[tag] = value, value
+ end
+ 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 -- of closure
+-- end library merge
+
+own = { } -- not local
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-number.lua',
+ 'l-set.lua',
+ 'l-os.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+ 'l-math.lua',
+-- 'l-unicode.lua',
+-- 'l-tex.lua',
+ 'l-utils.lua',
+ 'l-aux.lua',
+-- 'l-xml.lua',
+ 'lxml-tab.lua',
+ 'lxml-pth.lua',
+ 'lxml-ent.lua',
+ 'lxml-mis.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+ 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+ 'data-zip.lua',
+ 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-tmf.lua', -- tree files
+ -- needed ?
+ 'luat-sta.lua', -- states
+}
+
+-- 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")
+
+local 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 resolvers then
+ locate_libs()
+end
+
+if not resolvers 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
+
+logs.setprogram('MTXrun',"TDS Runner Tool 1.22",environment.arguments["verbose"] or false)
+
+local instance = resolvers.reset()
+
+runners = runners or { } -- global
+messages = messages or { }
+
+messages.help = [[
+--script run an mtx script (--noquotes)
+--execute run a script or program (--noquotes)
+--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 like manuals, assumes os support
+
+--intern run script using built in libraries
+
+--usekpse use kpse as fallback (when no mkiv and cache installed, often slower)
+--forcekpse force using kpse (handy when no mkiv and cache installed but less functionality)
+]]
+
+runners.applications = {
+ ["lua"] = "luatex --luaonly",
+ ["luc"] = "luatex --luaonly",
+ ["pl"] = "perl",
+ ["py"] = "python",
+ ["rb"] = "ruby",
+}
+
+runners.suffixes = {
+ 'rb', 'lua', 'py', 'pl'
+}
+
+runners.registered = {
+ texexec = { 'texexec.rb', true }, -- context mkii runner (only tool not to be luafied)
+ texutil = { 'texutil.rb', true }, -- old perl based index sorter for mkii (old versions need it)
+ texfont = { 'texfont.pl', true }, -- perl script that makes mkii font metric files
+ texfind = { 'texfind.pl', false }, -- perltk based tex searching tool, mostly used at pragma
+ texshow = { 'texshow.pl', false }, -- perltk based context help system, will be luafied
+ -- texwork = { \texwork.pl', false }, -- perltk based editing environment, only used at pragma
+
+ makempy = { 'makempy.pl', true },
+ mptopdf = { 'mptopdf.pl', true },
+ pstopdf = { 'pstopdf.rb', true }, -- converts ps (and some more) images, does some cleaning (replaced)
+
+-- 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 }
+}
+
+runners.launchers = {
+ windows = { },
+ unix = { }
+}
+
+function runners.prepare()
+ local checkname = environment.argument("ifchanged")
+ if checkname and checkname ~= "" then
+ local oldchecksum = file.loadchecksum(checkname)
+ local newchecksum = file.checksum(checkname)
+ if oldchecksum == newchecksum then
+ logs.simple("file '%s' is unchanged",checkname)
+ return "skip"
+ else
+ logs.simple("file '%s' is changed, processing started",checkname)
+ 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
+ logs.simple("file '%s' and '%s' have same age",oldname,newname)
+ return "skip"
+ else
+ logs.simple("file '%s' is older than '%s'",oldname,newname)
+ 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
+ resolvers.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
+ resolvers.load_tree(e)
+ end
+ end
+ local runpath = environment.argument("path")
+ if runpath and not lfs.chdir(runpath) then
+ logs.simple("unable to change to path '%s'",runpath)
+ return "error"
+ end
+ return "run"
+end
+
+function runners.execute_script(fullname,internal)
+ local noquote = environment.argument("noquotes")
+ if fullname and fullname ~= "" then
+ local state = runners.prepare()
+ 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 runners.registered[name] and runners.registered[name][1] then
+ name = runners.registered[name][1]
+ suffix = file.extname(name)
+ end
+ if suffix == "" then
+ -- loop over known suffixes
+ for _,s in pairs(runners.suffixes) do
+ result = resolvers.find_file(name .. "." .. s, 'texmfscripts')
+ if result ~= "" then
+ break
+ end
+ end
+ elseif runners.applications[suffix] then
+ result = resolvers.find_file(name, 'texmfscripts')
+ else
+ -- maybe look on path
+ result = resolvers.find_file(name, 'other text files')
+ end
+ end
+ if result and result ~= "" then
+ local before, after = environment.split_arguments(fullname) -- already done
+ environment.arguments_before, environment.arguments_after = before, after
+ if internal then
+ arg = { } for _,v in pairs(after) do arg[#arg+1] = v end
+ dofile(result)
+ else
+ local binary = runners.applications[file.extname(result)]
+ if binary and binary ~= "" then
+ result = binary .. " " .. result
+ end
+ local command = result .. " " .. environment.reconstruct_commandline(after,noquote)
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
+ io.flush()
+ end
+ local code = os.exec(command) -- maybe spawn
+ return code == 0
+ end
+ end
+ end
+ end
+ return false
+end
+
+function runners.execute_program(fullname)
+ local noquote = environment.argument("noquotes")
+ if fullname and fullname ~= "" then
+ local state = runners.prepare()
+ 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 or "",noquote) or "")
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
+ io.flush()
+ local code = os.exec(command) -- (fullname,unpack(after)) does not work / maybe spawn
+ return code == 0
+ end
+ end
+ return false
+end
+
+-- the --usekpse flag will fallback on kpse
+
+local windows_stub = '@echo off\013\010setlocal\013\010set ownpath=%%~dp0%%\013\010texlua "%%ownpath%%mtxrun.lua" --usekpse --execute %s %%*\013\010endlocal\013\010'
+local unix_stub = '#!/bin/sh\010mtxrun --usekpse --execute %s \"$@\"\010'
+
+function runners.handle_stubs(create)
+ local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer subpathssupported
+ 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 os.platform == "unix" then
+ unix = true
+ else
+ windows = true
+ end
+ end
+ for _,v in pairs(runners.registered) do
+ local name, doit = v[1], v[2]
+ if doit then
+ local base = string.gsub(file.basename(name), "%.(.-)$", "")
+ if create then
+ if windows then
+ io.savedata(file.join(stubpath,base..".bat"),string.format(windows_stub,name))
+ logs.simple("windows stub for '%s' created",base)
+ end
+ if unix then
+ io.savedata(file.join(stubpath,base),string.format(unix_stub,name))
+ logs.simple("unix stub for '%s' created",base)
+ end
+ else
+ if windows and (os.remove(file.join(stubpath,base..'.bat')) or os.remove(file.join(stubpath,base..'.cmd'))) then
+ logs.simple("windows stub for '%s' removed", base)
+ end
+ if unix and (os.remove(file.join(stubpath,base)) or os.remove(file.join(stubpath,base..'.sh'))) then
+ logs.simple("unix stub for '%s' removed",base)
+ end
+ end
+ end
+ end
+end
+
+function runners.resolve_string(filename)
+ if filename and filename ~= "" then
+ runners.report_location(resolvers.resolve(filename))
+ end
+end
+
+function runners.locate_file(filename)
+ -- differs from texmfstart where locate appends .com .exe .bat ... todo
+ if filename and filename ~= "" then
+ runners.report_location(resolvers.find_given_file(filename))
+ end
+end
+
+function runners.locate_platform()
+ runners.report_location(os.currentplatform())
+end
+
+function runners.report_location(result)
+ if logs.verbose then
+ logs.simpleline()
+ if result and result ~= "" then
+ logs.simple(result)
+ else
+ logs.simple("not found")
+ end
+ else
+ io.write(result)
+ end
+end
+
+function runners.edit_script(filename) -- we assume that vim is present on most systems
+ local editor = os.getenv("MTXRUN_EDITOR") or os.getenv("TEXMFSTART_EDITOR") or os.getenv("EDITOR") or 'vim'
+ local rest = resolvers.resolve(filename)
+ if rest ~= "" then
+ local command = editor .. " " .. rest
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("starting editor: %s",command)
+ logs.simple_line()
+ logs.simple_line()
+ end
+ os.launch(command)
+ end
+end
+
+function 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 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
+
+function resolvers.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 = 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 runners.launch_file(filename)
+ instance.allresults = true
+ logs.setverbose(true)
+ local pattern = environment.arguments["pattern"]
+ if not pattern or pattern == "" then
+ pattern = filename
+ end
+ if not pattern or pattern == "" then
+ logs.simple("provide name or --pattern=")
+ else
+ local t = resolvers.find_files(pattern)
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern)
+ end
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern .. "*")
+ end
+ if t and #t > 0 then
+ if environment.arguments["all"] then
+ for _, v in pairs(t) do
+ logs.simple("launching %s", v)
+ resolvers.launch(v)
+ end
+ else
+ logs.simple("launching %s", t[1])
+ resolvers.launch(t[1])
+ end
+ else
+ logs.simple("no match for %s", pattern)
+ end
+ end
+end
+
+function runners.find_mtx_script(filename)
+ 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
+ filename = file.addsuffix(filename,"lua")
+ local basename = file.removesuffix(file.basename(filename))
+ local suffix = file.extname(filename)
+ -- qualified path, raw name
+ local fullname = file.is_qualified_path(filename) and io.exists(filename) and filename
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- current path, raw name
+ fullname = "./" .. filename
+ fullname = io.exists(fullname) and fullname
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename>
+ fullname = "mtx-" .. filename
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename>s
+ fullname = "mtx-" .. basename .. "s" .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename minus trailing s>
+ fullname = "mtx-" .. basename:gsub("s$","") .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, just <filename>
+ fullname = resolvers.find_file(filename)
+ return fullname
+end
+
+function runners.execute_ctx_script(filename,arguments)
+ local fullname = runners.find_mtx_script(filename) or ""
+ -- retyr after generate but only if --autogenerate
+ if fullname == "" and environment.argument("autogenerate") then -- might become the default
+ instance.renewcache = true
+ logs.setverbose(true)
+ resolvers.load()
+ --
+ fullname = runners.find_mtx_script(filename) or ""
+ end
+ -- that should do it
+ if fullname ~= "" then
+ local state = runners.prepare()
+ 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] = resolvers.resolve(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")
+ runners.load_script_session(loadname)
+ end
+ filename = environment.files[1]
+ if logs.verbose then
+ logs.simple("using script: %s\n",fullname)
+ end
+ dofile(fullname)
+ local savename = environment.arguments['save']
+ if savename and runners.save_list and not table.is_empty(runners.save_list or { }) then
+ if type(savename) ~= "string" then savename = file.basename(fullname) end
+ savename = file.replacesuffix(savename,"cfg")
+ runners.save_script_session(savename, runners.save_list)
+ end
+ return true
+ end
+ else
+ logs.setverbose(true)
+ if filename == "" then
+ logs.simple("unknown script, no name given")
+ local context = resolvers.find_file("mtx-context.lua")
+ if context ~= "" then
+ local result = dir.glob((string.gsub(context,"mtx%-context","mtx-*"))) -- () needed
+ local valid = { }
+ for _, scriptname in ipairs(result) do
+ scriptname = string.match(scriptname,".*mtx%-([^%-]-)%.lua")
+ if scriptname then
+ valid[#valid+1] = scriptname
+ end
+ end
+ if #valid > 0 then
+ logs.simple("known scripts: %s",table.concat(valid,", "))
+ end
+ end
+ else
+ filename = file.addsuffix(filename,"lua")
+ if file.is_qualified_path(filename) then
+ logs.simple("unknown script '%s'",filename)
+ else
+ logs.simple("unknown script '%s' or 'mtx-%s'",filename,filename)
+ end
+ end
+ return false
+ end
+end
+
+function runners.timedrun(filename) -- just for me
+ if filename and filename ~= "" then
+ runners.timed(function() os.execute(filename) end)
+ end
+end
+
+function runners.timed(action)
+ statistics.timed(action)
+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)
+environment.arguments_before, environment.arguments_after = before, after
+environment.initialize_arguments(before)
+
+instance.engine = environment.argument("engine") or 'luatex'
+instance.progname = environment.argument("progname") or 'context'
+instance.lsrmode = environment.argument("lsr") or false
+
+-- maybe the unset has to go to this level
+
+if environment.argument("usekpse") or environment.argument("forcekpse") then
+
+ os.setenv("engine","")
+ os.setenv("progname","")
+
+ local remapper = {
+ otf = "opentype fonts",
+ ttf = "truetype fonts",
+ ttc = "truetype fonts",
+ pfb = "type1 fonts",
+ other = "other text files",
+ }
+
+ local function kpse_initialized()
+ texconfig.kpse_init = true
+ local t = os.clock()
+ local k = kpse.original.new("luatex",instance.progname)
+ local dummy = k:find_file("mtxrun.lua") -- so that we're initialized
+ logs.simple("kpse fallback with progname '%s' initialized in %s seconds",instance.progname,os.clock()-t)
+ kpse_initialized = function() return k end
+ return k
+ end
+
+ local find_file = resolvers.find_file
+ local show_path = resolvers.show_path
+
+ if environment.argument("forcekpse") then
+
+ function resolvers.find_file(name,kind)
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ function resolvers.show_path(name)
+ return (kpse_initialized():show_path(name)) or ""
+ end
+
+ elseif environment.argument("usekpse") then
+
+ resolvers.load()
+
+ function resolvers.find_file(name,kind)
+ local found = find_file(name,kind) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ end
+ function resolvers.show_path(name)
+ local found = show_path(name) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():show_path(name)) or ""
+ end
+ end
+
+ end
+
+else
+
+ resolvers.load()
+
+end
+
+
+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
+ logs.setverbose(true)
+ resolvers.update_script(own.name,"mtxrun")
+elseif environment.argument("ctxlua") or environment.argument("internal") then
+ -- run a script by loading it (using libs)
+ ok = runners.execute_script(filename,true)
+elseif environment.argument("script") or environment.argument("s") then
+ -- run a script by loading it (using libs), pass args
+ ok = runners.execute_ctx_script(filename,after)
+elseif environment.argument("execute") then
+ -- execute script
+ ok = runners.execute_script(filename)
+elseif environment.argument("direct") then
+ -- equals bin:
+ ok = runners.execute_program(filename)
+elseif environment.argument("edit") then
+ -- edit file
+ runners.edit_script(filename)
+elseif environment.argument("launch") then
+ runners.launch_file(filename)
+elseif environment.argument("make") then
+ -- make stubs
+ runners.handle_stubs(true)
+elseif environment.argument("remove") then
+ -- remove stub
+ runners.handle_stubs(false)
+elseif environment.argument("resolve") then
+ -- resolve string
+ runners.resolve_string(filename)
+elseif environment.argument("locate") then
+ -- locate file
+ runners.locate_file(filename)
+elseif environment.argument("platform")then
+ -- locate platform
+ runners.locate_platform()
+elseif environment.argument("timedrun") then
+ -- locate platform
+ runners.timedrun(filename)
+elseif environment.argument("help") or filename=='help' or filename == "" then
+ logs.help(messages.help)
+ -- execute script
+elseif filename:find("^bin:") then
+ ok = runners.execute_program(filename)
+else
+ ok = runners.execute_script(filename)
+end
+
+if os.platform == "unix" then
+ io.write("\n")
+end
+
+if ok == false then ok = 1 elseif ok == true then ok = 0 end
+
+os.exit(ok)
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mtxtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/mtxtools.bat
index cb0b1e39e47..2e0fd35080d 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/mtxtools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/mtxtools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart mtxtools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute mtxtools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mtxworks.cmd b/Master/texmf-dist/scripts/context/stubs/mswin/mtxworks.cmd
new file mode 100644
index 00000000000..322d9464dd5
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/mtxworks.cmd
@@ -0,0 +1 @@
+mtxrun --script texworks --start
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat
index 71662d3f766..94c8cf3bd79 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/pdftools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart pdftools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute pdftools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/pdftrimwhite.bat b/Master/texmf-dist/scripts/context/stubs/mswin/pdftrimwhite.bat
deleted file mode 100755
index 432aaf27557..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/mswin/pdftrimwhite.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart pdftrimwhite.pl %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat b/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat
index 091b0d4d7bd..ebc3cb690a7 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/pstopdf.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart pstopdf.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute pstopdf.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat
index e5cdca25009..5069c935f3c 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/rlxtools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart rlxtools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute rlxtools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat
index 37df398ab37..2f89413d436 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/runtools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart runtools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute runtools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat
index 44615dbd918..6e8b80aabce 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart texexec.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute texexec.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texexec.cmd b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.cmd
new file mode 100644
index 00000000000..acbe41fd82b
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/texexec.cmd
@@ -0,0 +1,5 @@
+@echo off
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute texexec.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texfind.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texfind.bat
deleted file mode 100755
index 1e481b71408..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/mswin/texfind.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart texfind %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat
index 9dd50309ff3..498d4d85b51 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/texfont.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart texfont.pl %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute texfont.pl %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texmfstart.cmd b/Master/texmf-dist/scripts/context/stubs/mswin/texmfstart.cmd
new file mode 100644
index 00000000000..47a10cc54cd
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/texmfstart.cmd
@@ -0,0 +1,5 @@
+@echo off
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texshow.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texshow.bat
deleted file mode 100755
index 938e62700a2..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/mswin/texshow.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-@echo off
-texmfstart texshow.pl %*
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat
index 4fc5c549255..b4cdfa1b332 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/textools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart textools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute textools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat b/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat
index 61e90ca6509..8b718640dcf 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/texutil.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart texutil.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute texutil.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat
index a7053043d22..1f5d8022280 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/tmftools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart tmftools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute tmftools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat b/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat
index 9721095b298..41f4ec30465 100755
--- a/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat
+++ b/Master/texmf-dist/scripts/context/stubs/mswin/xmltools.bat
@@ -1,2 +1,5 @@
@echo off
-texmfstart xmltools.rb %*
+setlocal
+set ownpath=%~dp0%
+texlua "%ownpath%mtxrun.lua" --usekpse --execute xmltools.rb %*
+endlocal
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/context b/Master/texmf-dist/scripts/context/stubs/unix/context
index c7341904f10..fa62ba8d109 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/context
+++ b/Master/texmf-dist/scripts/context/stubs/unix/context
@@ -1,3 +1,2 @@
#!/bin/sh
-
mtxrun --script context "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/ctxtools b/Master/texmf-dist/scripts/context/stubs/unix/ctxtools
index 84e47bbee3c..4658a345a8c 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/ctxtools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/ctxtools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart ctxtools.rb "$@"
+mtxrun --usekpse --execute ctxtools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/exatools b/Master/texmf-dist/scripts/context/stubs/unix/exatools
deleted file mode 100755
index 50ff0f07e46..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/unix/exatools
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-texmfstart exatools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/luatools b/Master/texmf-dist/scripts/context/stubs/unix/luatools
new file mode 100644
index 00000000000..433d1b8dc0a
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/unix/luatools
@@ -0,0 +1,7267 @@
+#!/usr/bin/env texlua
+
+if not modules then modules = { } end modules ['luatools'] = {
+ version = 1.001,
+ comment = "companion to context.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local format = string.format
+
+-- one can make a stub:
+--
+-- #!/bin/sh
+-- env LUATEXDIR=/....../texmf/scripts/context/lua texlua luatools.lua "$@"
+
+-- Although this script is part of the ConTeXt distribution it is
+-- relatively indepent of ConTeXt. The same is true for some of
+-- the luat files. We may may make them even less dependent in
+-- the future. As long as Luatex is under development the
+-- interfaces and names of functions may change.
+
+-- For the sake of independence we optionally can merge the library
+-- code here. It's too much code, but that does not harm. Much of the
+-- library code is used elsewhere. We don't want dependencies on
+-- Lua library paths simply because these scripts are located in the
+-- texmf tree and not in some Lua path. Normally this merge is not
+-- needed when texmfstart is used, or when the proper stub is used or
+-- when (windows) suffix binding is active.
+
+texlua = true
+
+-- begin library merge
+
+
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
+
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
+
+if not string.split then
+
+ -- this will be overloaded by a faster lpeg variant
+
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+ end
+
+end
+
+local chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+string.chr_to_esc = chr_to_esc
+
+function string:esc() -- variant 2
+ return (gsub(self,"(.)",chr_to_esc))
+end
+
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
+end
+
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in gmatch(self,pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return sub(self,1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (gsub(self,"^%s*(.-)%s*$", "%1"))
+end
+
+function string:is_empty()
+ return not find(find,"%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = gsub(self,pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+local chr_to_hex, hex_to_chr = { }, { }
+
+for i=0,255 do
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], hex_to_chr[h] = h, c
+end
+
+function string:to_hex()
+ return (gsub(self or "","(.)",chr_to_hex))
+end
+
+function string:from_hex()
+ return (gsub(self or "","(..)",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, byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+-- we can use format for this (neg n)
+
+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) -- tonumber
+ return find(str,"^[%-%+]?[%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 find(self,"=") then
+ local t = { }
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return nil
+ end
+end
+
+local patterns_escapes = {
+ ["-"] = "%-",
+ ["."] = "%.",
+ ["+"] = "%+",
+ ["*"] = "%*",
+ ["%"] = "%%",
+ ["("] = "%)",
+ [")"] = "%)",
+ ["["] = "%[",
+ ["]"] = "%]",
+}
+
+function string:pattesc()
+ return (gsub(self,".",patterns_escapes))
+end
+
+function string:tohash()
+ local t = { }
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
+ t[s] = true
+ end
+ return t
+end
+
+local pattern = lpeg.Ct(lpeg.C(1)^0)
+
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
+
+--~ 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 P { P(pattern) + 1 * lpeg.V(1) }
+end
+
+function lpeg.startswith(pattern) --slightly adapted
+ return P(pattern)
+end
+
+function lpeg.splitter(pattern, action)
+ return (((1-P(pattern))^1)/action+1)^0
+end
+
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
+local newline = crlf + cr + lf
+local spacing = space^0 * newline
+
+local empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
+local content = (empty + nonempty)^1
+
+local capture = Ct(content^0)
+
+function string:splitlines()
+ return capture:match(self)
+end
+
+lpeg.linebyline = content -- better make a sublibrary
+
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
+
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c:match(self)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
+
+table.join = table.concat
+
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
+function table.strip(tab)
+ local lst = { }
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
+
+local function sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in next, 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
+ sort(srt,compare)
+ else
+ sort(srt)
+ end
+ return srt
+end
+
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
+ end
+ sort(srt)
+ return srt
+end
+
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
+function table.append(t, list)
+ for _,v in next, list do
+ insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in next, list do
+ 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 next, 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 next, 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
+
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
+ end
+ end
+ -- optional second arg
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
+ return new
+ else
+ return { }
+ end
+end
+
+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 next, 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.fastcopy = fastcopy
+table.copy = copy
+
+-- 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 next, 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
+
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
+ end
+ end
+ return h
+end
+
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
+ else
+ tt[#tt+1] = tostring(v) -- tostring not needed
+ end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+end
+
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
+ else
+ handle(format("%s[%q]={",depth,name))
+ end
+ else
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
+ else
+ handle(format("%s %q,",depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
+ end
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
+ else
+ handle(format("%s [%s]=%q,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
+ else
+ handle(format("%s [%s]={},",depth,k))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
+ else
+ handle(format("%s [%q]={},",depth,k))
+ end
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%s,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
+ end
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
+ end
+ end
+ --~ end
+ end
+ end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
+
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
+
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
+ end
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
+
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+end
+
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+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,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ end
+ f:close()
+ end
+end
+
+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
+
+-- the next three may disappear
+
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
+end
+
+function table.insert_before_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
+ end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
+ end
+end
+
+function table.insert_after_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
+ end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
+ end
+end
+
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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 then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+end
+
+table.are_equal = are_equal
+table.identical = identical
+
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ end
+ end
+end
+
+function table.contains(t, v)
+ if t then
+ for i=1, #t do
+ if t[i] == v then
+ return i
+ 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 next, 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] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
+end
+
+function table.reverse_hash(h)
+ local r = { }
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
+ 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
+
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ end
+
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
+
+local byte = string.byte
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
+ if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
+ local data = f:read('*all')
+ -- garbagecollector.check(data)
+ 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()
+ return true
+ else
+ return false
+ 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
+
+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
+
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
+
+local format = string.format
+
+number = number or { }
+
+-- a,b,c,d,e,f = number.toset(100101)
+
+function number.toset(n)
+ return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
+end
+
+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
+
+local one = lpeg.C(1-lpeg.S(''))^1
+
+function number.toset(n)
+ return one:match(tostring(n))
+end
+
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
+
+set = set or { }
+
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
+
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
+ end
+ end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
+ end
+end
+
+function set.totable(n)
+ if n == 0 then
+ return { }
+ else
+ return tabs[n] or { }
+ end
+end
+
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
+ end
+ return concat(t," ")
+ 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
+
+--~ 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'))
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
+
+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 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
+
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
+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()))
+
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+end
+
+local platform
+
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
+ end
+ return platform
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
+
+-- needs a cleanup
+
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
+
+function file.removesuffix(filename)
+ return (gsub(filename,"%.[%a%d]+$",""))
+end
+
+function file.addsuffix(filename, suffix)
+ if not find(filename,"%.[%a%d]+$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+end
+
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
+end
+
+function file.basename(name)
+ return match(name,"^.+[/\\](.-)$") or name
+end
+
+function file.nameonly(name)
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
+end
+
+function file.extname(name)
+ return match(name,"^.+%.([^/\\]-)$") or ""
+end
+
+file.suffix = file.extname
+
+--~ 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 = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ a, b = match(pth,"^(//)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ return (gsub(pth,"//+","/"))
+end
+
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
+end
+
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
+end
+
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
+
+-- todo: lpeg
+
+function file.split_path(str)
+ local t = { }
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
+ if name ~= "" then
+ t[#t+1] = gsub(name,"\001",":")
+ end
+ end
+ return t
+end
+
+function file.join_path(tab)
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
+
+function file.collapse_path(str)
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
+
+function file.robustname(str)
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+function file.copy(oldname,newname)
+ file.savedata(newname,io.loaddata(oldname))
+end
+
+-- lpeg variants, slightly faster, not always
+
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
+
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-url'] = {
+ 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"
+}
+
+local char, gmatch = string.char, string.gmatch
+local tonumber, type = tonumber, type
+
+-- from the spec (on the web):
+--
+-- foo://example.com:8042/over/there?name=ferret#nose
+-- \_/ \______________/\_________/ \_________/ \__/
+-- | | | | |
+-- scheme authority path query fragment
+-- | _____________________|__
+-- / \ / \
+-- urn:example:animal:ferret:nose
+
+url = url or { }
+
+local function tochar(s)
+ return char(tonumber(s,16))
+end
+
+local colon, qmark, hash, slash, percent, endofstring = lpeg.P(":"), lpeg.P("?"), lpeg.P("#"), lpeg.P("/"), lpeg.P("%"), lpeg.P(-1)
+
+local hexdigit = lpeg.R("09","AF","af")
+local plus = lpeg.P("+")
+local escaped = (plus / " ") + (percent * lpeg.C(hexdigit * hexdigit) / tochar)
+
+local scheme = lpeg.Cs((escaped+(1-colon-slash-qmark-hash))^0) * colon + lpeg.Cc("")
+local authority = slash * slash * lpeg.Cs((escaped+(1- slash-qmark-hash))^0) + lpeg.Cc("")
+local path = slash * lpeg.Cs((escaped+(1- qmark-hash))^0) + lpeg.Cc("")
+local query = qmark * lpeg.Cs((escaped+(1- hash))^0) + lpeg.Cc("")
+local fragment = hash * lpeg.Cs((escaped+(1- endofstring))^0) + lpeg.Cc("")
+
+local parser = lpeg.Ct(scheme * authority * path * query * fragment)
+
+function url.split(str)
+ return (type(str) == "string" and parser:match(str)) or str
+end
+
+function url.hashed(str)
+ local s = url.split(str)
+ return {
+ scheme = (s[1] ~= "" and s[1]) or "file",
+ authority = s[2],
+ path = s[3],
+ query = s[4],
+ fragment = s[5],
+ original = str
+ }
+end
+
+function url.filename(filename)
+ local t = url.hashed(filename)
+ return (t.scheme == "file" and t.path:gsub("^/([a-zA-Z])([:|])/)","%1:")) or filename
+end
+
+function url.query(str)
+ if type(str) == "string" then
+ local t = { }
+ for k, v in gmatch(str,"([^&=]*)=([^&=]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return str
+ end
+end
+
+--~ print(url.filename("file:///c:/oeps.txt"))
+--~ print(url.filename("c:/oeps.txt"))
+--~ print(url.filename("file:///oeps.txt"))
+--~ print(url.filename("file:///etc/test.txt"))
+--~ print(url.filename("/oeps.txt"))
+
+--~ from the spec on the web (sort of):
+--~
+--~ function test(str)
+--~ print(table.serialize(url.hashed(str)))
+--~ end
+--~
+--~ test("%56pass%20words")
+--~ test("file:///c:/oeps.txt")
+--~ test("file:///c|/oeps.txt")
+--~ test("file:///etc/oeps.txt")
+--~ test("file://./etc/oeps.txt")
+--~ test("file:////etc/oeps.txt")
+--~ test("ftp://ftp.is.co.za/rfc/rfc1808.txt")
+--~ test("http://www.ietf.org/rfc/rfc2396.txt")
+--~ test("ldap://[2001:db8::7]/c=GB?objectClass?one#what")
+--~ test("mailto:John.Doe@example.com")
+--~ test("news:comp.infosystems.www.servers.unix")
+--~ test("tel:+1-816-555-1212")
+--~ test("telnet://192.0.2.16:80/")
+--~ test("urn:oasis:names:specification:docbook:dtd:xml:4.1.2")
+--~ test("/etc/passwords")
+--~ test("http://www.pragma-ade.com/spaced%20name")
+
+--~ test("zip:///oeps/oeps.zip#bla/bla.tex")
+--~ test("zip:///oeps/oeps.zip?bla/bla.tex")
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
+
+local type = type
+local find, gmatch = string.find, string.gmatch
+
+dir = dir or { }
+
+-- optimizing for no string.find (*) does not save time
+
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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 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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ 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 = find(base,"%*%*")
+ 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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
+ if recurse then
+ globfiles(path .. "/" .. name,recurse,func,files)
+ end
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
+ files[#files+1] = path .. "/" .. name
+ end
+ end
+ 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 gmatch(last,"[^/]+") 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 find(last,"^/") 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 find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") 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 gmatch(str,"[^/]+") 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 find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
+ end
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
+ end
+
+end
+
+dir.makedirs = dir.mkdirs
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
+
+local type, tonumber = type, tonumber
+
+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" or str == "t"
+ 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" or str == "t" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-unicode'] = {
+ 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"
+}
+
+utf = utf or unicode.utf8
+
+local concat, utfchar, utfgsub = table.concat, utf.char, utf.gsub
+local char, byte, find, bytepairs = string.char, string.byte, string.find, string.bytepairs
+
+unicode = unicode or { }
+
+-- 0 EF BB BF UTF-8
+-- 1 FF FE UTF-16-little-endian
+-- 2 FE FF UTF-16-big-endian
+-- 3 FF FE 00 00 UTF-32-little-endian
+-- 4 00 00 FE FF UTF-32-big-endian
+
+unicode.utfname = {
+ [0] = 'utf-8',
+ [1] = 'utf-16-le',
+ [2] = 'utf-16-be',
+ [3] = 'utf-32-le',
+ [4] = 'utf-32-be'
+}
+
+function unicode.utftype(f) -- \000 fails !
+ local str = f:read(4)
+ if not str then
+ f:seek('set')
+ return 0
+ elseif find(str,"^%z%z\254\255") then
+ return 4
+ elseif find(str,"^\255\254%z%z") then
+ return 3
+ elseif find(str,"^\254\255") then
+ f:seek('set',2)
+ return 2
+ elseif find(str,"^\255\254") then
+ f:seek('set',2)
+ return 1
+ elseif find(str,"^\239\187\191") then
+ f:seek('set',3)
+ return 0
+ else
+ f:seek('set')
+ return 0
+ end
+end
+
+function unicode.utf16_to_utf8(str, endian) -- maybe a gsub is faster or an lpeg
+ local result, tmp, n, m, p = { }, { }, 0, 0, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = utfchar(n)
+ p = 0
+ end
+ end
+ for l,r in bytepairs(str) do
+ if r then
+ if endian then
+ n = l*256 + r
+ else
+ n = r*256 + l
+ end
+ if m > 0 then
+ n = (m-0xD800)*0x400 + (n-0xDC00) + 0x10000
+ m = 0
+ doit()
+ elseif n >= 0xD800 and n <= 0xDBFF then
+ m = n
+ else
+ doit()
+ end
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = concat(tmp)
+ end
+ return result
+end
+
+function unicode.utf32_to_utf8(str, endian)
+ local result = { }
+ local tmp, n, m, p = { }, 0, -1, 0
+ -- lf | cr | crlf / (cr:13, lf:10)
+ local function doit()
+ if n == 10 then
+ if p ~= 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = 0
+ end
+ elseif n == 13 then
+ result[#result+1] = concat(tmp)
+ tmp = { }
+ p = n
+ else
+ tmp[#tmp+1] = utfchar(n)
+ p = 0
+ end
+ end
+ for a,b in bytepairs(str) do
+ if a and b then
+ if m < 0 then
+ if endian then
+ m = a*256*256*256 + b*256*256
+ else
+ m = b*256 + a
+ end
+ else
+ if endian then
+ n = m + a*256 + b
+ else
+ n = m + b*256*256*256 + a*256*256
+ end
+ m = -1
+ doit()
+ end
+ else
+ break
+ end
+ end
+ if #tmp > 0 then
+ result[#result+1] = concat(tmp)
+ end
+ return result
+end
+
+local function little(c)
+ local b = byte(c) -- b = c:byte()
+ if b < 0x10000 then
+ return char(b%256,b/256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1%256,b1/256,b2%256,b2/256)
+ end
+end
+
+local function big(c)
+ local b = byte(c)
+ if b < 0x10000 then
+ return char(b/256,b%256)
+ else
+ b = b - 0x10000
+ local b1, b2 = b/1024 + 0xD800, b%1024 + 0xDC00
+ return char(b1/256,b1%256,b2/256,b2%256)
+ end
+end
+
+function unicode.utf8_to_utf16(str,littleendian)
+ if littleendian then
+ return char(255,254) .. utfgsub(str,".",little)
+ else
+ return char(254,255) .. utfgsub(str,".",big)
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
+ 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"
+}
+
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
+
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
+
+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
+
+utils.merger.strip_comment = true
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ utils.report("reading merge from %s",name)
+ data = f:read("*all")
+ f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ utils.report("saving merge from %s",name)
+ 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
+
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
+function utils.merger._self_libs_(libs,list)
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ local foundpath = nil
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
+ else
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
+ end
+ end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
+ 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
+
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if strip ~= false then
+ command = "-s " .. command
+ end
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
+ else
+ return (pattern_a/set)^1
+ end
+end
+
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
+
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
+
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
+
+aux.settings_to_array_pattern = pattern
+
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
+
+local function set(t,v)
+ t[#t+1] = v
+end
+
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
+
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
+
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
+
+-- temporary here
+
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
+
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
+
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
+
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
+
+debugger = debugger or { }
+
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
+ end
+ end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
+
+-- two
+
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
+
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
+ else
+ function debugger.tracing() return false end ; return false
+ end
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
+trackers = trackers or { }
+
+local data, done = { }, { }
+
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
+ end
+ end
+ end
+end
+
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
+ end
+end
+
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
+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
+
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
+
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
+
+function environment.initialize_arguments(arg)
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ arguments[flag] = true
+ else
+ files[#files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.setargument(name,value)
+ environment.arguments[name] = value
+end
+
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
+ if name:find(v) then
+ return 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,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+ else
+ return ""
+ end
+end
+
+if arg then
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
+ arg = { } -- prevent duplicate handling
+
+end
+
+-- weird place ... depends on a not yet loaded module
+
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
+
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
+
+environment.loadedluacode = loadfile -- can be overloaded
+
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
+
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
+
+-- the next ones can use the previous ones / combine
+
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
+
+local format = string.format
+
+local statusinfo, n, registered = { }, 0, { }
+
+statistics = statistics or { }
+
+statistics.enable = true
+statistics.threshold = 0.05
+
+-- timing functions
+
+local clock = os.gettimeofday or os.clock
+
+local notimer
+
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
+
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
+ end
+ end
+ end
+ return 0
+end
+
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
+
+-- general function
+
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
+ end
+ end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
+ end
+end
+
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
+end
+
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
+end
+
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
+end
+
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
+end
+
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
+ else
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
+ else
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
+ else
+ write(format("[%s",real))
+ end
+ else
+ write("[-")
+ end
+end
+
+function logs.tex.stop_page_number()
+ write("]")
+end
+
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
+ else
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
+ else
+ write_nl("<r/>")
+ end
+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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
+
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
+
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
+
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
+
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
+ end
+end
+
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
+ end
+ end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
+end
+
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
+ end
+
+ return newinstance
+
+end
+
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
+end
+
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
+ end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
+end
+
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
+
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
+end
+
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(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 = resolvers.bare_variable(e)
+ end
+ ie[key] = value
+ end
+ return value or ""
+end
+
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
+--
+
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
+ end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
+ else
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
+ end
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
+ end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
+ else
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
+ end
+ end
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
+ end
+ return resolvers.ownpath
+end
+
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
+ else
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
+ end
+ end
+ identify_own = function() end
+end
+
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
+ end
+ end
+ end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
+ end
+end
+
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
+ local f = io.open(lname)
+ if f then -- this will go
+ f:close()
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ else
+ f = io.open(fname)
+ if f then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
+ 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 = gsub(line,"\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
+ else
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
+ end
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+-- database loading
+
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
+ if instance.loaderror then
+ resolvers.loadlists()
+ resolvers.savefiles()
+ end
+ else
+ resolvers.loadlists()
+ if instance.renewcache then
+ resolvers.savefiles()
+ end
+ end
+end
+
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
+ else
+ -- weird
+ end
+ resolvers.expand_variables()
+ reset_hashes()
+end
+
+-- locators
+
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
+ end
+end
+
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
+end
+
+function resolvers.locators.tex(specification)
+ if specification and specification ~= '' and lfs.isdir(specification) then
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
+ end
+end
+
+-- hashers
+
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
+end
+
+function resolvers.loadfiles()
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.hashdatabase(hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
+end
+
+-- generators:
+
+function resolvers.loadlists()
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.generatedatabase(hash.tag)
+ end
+end
+
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
+end
+
+-- starting with . or .. etc or funny char
+
+local weird = lpeg.P(".")^1 + lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
+ end
+ end
+ end
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ end
+ end
+ end
+ end
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ end
+end
+
+-- savers, todo
+
+function resolvers.savefiles()
+ resolvers.save_data('files')
+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 resolvers.splitconfig()
+ 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 resolvers.joinconfig()
+ for i,c in ipairs(instance.order) do
+ for k,v in pairs(c) do -- ipairs?
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function resolvers.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function resolvers.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
+ local t, h = { }, { }
+ for _,vv in ipairs(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
+ ie[k] = t
+ else
+ ie[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
+end
+
+resolvers.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 resolvers.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 function dump(k,v,m) -- could be moved inline
+ 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(sortedkeys(files)) do -- ipairs
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
+ 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 next, files do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in next, 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
+
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, instance[dataname] do
+ local name = (makename or file.join)(cachename,dataname)
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = resolvers.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
+ os.remove(lucname)
+ end
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
+ end
+ end
+end
+
+function resolvers.load_data(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 .. ".luc") or loadfile(filename .. ".lua")
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = data.content
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,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 resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
+ end
+ end
+ end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance.order[#instance.order+1] = instance.setup[pathname]
+ if instance.loaderror then break end
+ end
+end
+
+function resolvers.loadoldconfig()
+ if not instance.renewcache then
+ for _, cnf in ipairs(instance.cnffiles) do
+ local dname = file.dirname(cnf)
+ resolvers.load_data(dname,'configuration')
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ if instance.loaderror then break end
+ end
+ end
+ resolvers.joinconfig()
+end
+
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
+ if a and b then
+ expansions[a..'.'..b] = v
+ else
+ expansions[k] = v
+ end
+ end
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
+ end
+ while true do
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
+ if n > 0 or m > 0 then
+ expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
+ end
+end
+
+function resolvers.variable(name)
+ return entry(instance.variables,name)
+end
+
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
+end
+
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,name)
+end
+
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
+end
+
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
+end
+
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
+end
+
+do -- no longer needed
+
+ local done = { }
+
+ function resolvers.reset_extra_path()
+ 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 resolvers.register_extra_path(paths,subpaths)
+ local ep = instance.extra_paths or { }
+ local n = #ep
+ if paths and paths ~= "" then
+ if subpaths and subpaths ~= "" then
+ for p in gmatch(paths,"[^,]+") do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in gmatch(subpaths,"[^,]+") do
+ local ps = p .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ else
+ for p in gmatch(paths,"[^,]+") do
+ if not done[p] then
+ ep[#ep+1] = resolvers.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 gmatch(subpaths,"[^,]+") do
+ local ps = ep[i] .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.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
+
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
+ done[v] = true
+ new[#new+1] = v
+ else
+ break
+ end
+ end
+ end
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
+ end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
+ if not str then
+ return ep or { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = gsub(str,"%$","")
+ if not instance.lists[str] then -- cached
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(lst))
+ end
+end
+
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
+ if tmp ~= "" then
+ return resolvers.expanded_path_list(str)
+ else
+ return resolvers.expanded_path_list(tmp)
+ end
+end
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
+end
+
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
+end
+
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
+ if v then
+ return v
+ end
+ v = formats[alternatives[str]]
+ if v then
+ return v
+ end
+ v = suffixmap[file.extname(str)]
+ if v then
+ return formats[isf]
+ end
+ return ''
+end
+
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.split_path(ori))
+ return file.join_path(pth)
+end
+
+resolvers.isreadable = { }
+
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
+ if readable then
+ logs.report("fileio","+ readable: %s",name)
+ else
+ logs.report("fileio","- readable: %s", name)
+ end
+ end
+ return readable
+end
+
+resolvers.isreadable.tex = resolvers.isreadable.file
+
+-- name
+-- name/name
+
+local function collect_files(names)
+ local filelist = { }
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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 find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
+ end
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,bname) -- result
+ }
+ end
+ end
+ end
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
+ end
+ end
+ end
+ if #filelist > 0 then
+ return filelist
+ else
+ return nil
+ end
+end
+
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return suffixes[str]
+ else
+ return {}
+ end
+end
+
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (fakepaths[name] == 1)
+end
+
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
+ local stamp = nil
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- elsewhere
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
+ return instance.found[stamp]
+ end
+ end
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
+ result = { filename }
+ else
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
+ result, ok = { forcedname }, true
+ end
+ else
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ forcedname = filename .. "." .. s
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', 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 = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
+ else
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
+ end
+ else
+ if ext == "" then
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ wantedfiles[#wantedfiles+1] = filename .. "." .. s
+ end
+ end
+ filetype = instance.format
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
+ end
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only / todo == wildcard
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 = collect_files(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 = collect_files(wantedfiles)
+ local doscan, recurse
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ end
+ -- a bit messy ... esp the doscan setting here
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
+ local expr = "^" .. pathname
+ for k=1,#filelist do
+ local fl = filelist[k]
+ local f = fl[2]
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
+ end
+ --- todo, test for readable
+ result[#result+1] = fl[3]
+ resolvers.register_in_trees(f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all / also zips
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
+ local fname = file.join(ppname,w)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',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=1,#result do
+ result[k] = file.collapse_path(result[k])
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+if not resolvers.concatinators then resolvers.concatinators = { } end
+
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
+
+function resolvers.find_files(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 result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
+ instance.format = ''
+ return result
+end
+
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
+end
+
+function resolvers.find_given_files(filename)
+ local bname, result = file.basename(filename), { }
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ 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] = resolvers.concatinators[hash.type](hash.tag,blist,bname) or ""
+ if not instance.allresults then break end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+end
+
+function resolvers.find_wildcard_files(filename) -- todo: remap:
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
+ local files, allresults, done = instance.files, instance.allresults, false
+ if find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ end
+ else
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
+ return result
+end
+
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(filename)[1] or "")
+end
+
+-- main user functions
+
+function resolvers.automount()
+ -- implemented later
+end
+
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
+end
+
+function resolvers.for_files(command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if trace_verbose then
+ report('')
+ end
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in ipairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.format_of_var(str)))
+end
+
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
+
+function resolvers.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
+
+function resolvers.splitmethod(filename)
+ if not filename then
+ return { } -- safeguard
+ elseif type(filename) == "table" then
+ return filename -- already split
+ elseif not find(filename,"://") then
+ return { scheme="file", path = filename, original=filename } -- quick hack
+ else
+ return url.hashed(filename)
+ end
+end
+
+function table.sequenced(t,sep) -- temp here
+ local s = { }
+ for k, v in pairs(t) do -- pairs?
+ s[#s+1] = k .. "=" .. v
+ end
+ return concat(s, sep or " | ")
+end
+
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.splitmethod(filename)) or filename -- no or { }, let it bomb
+ local scheme = specification.scheme
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
+ else
+ return resolvers[what].tex(filename,filetype) -- todo: specification
+ end
+end
+
+function resolvers.clean_path(str)
+ if str then
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
+ else
+ return nil
+ end
+end
+
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
+ end
+end
+
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
+end
+
+function resolvers.with_files(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 next, files do
+ if find(k,"^remap:") then
+ k = files[k]
+ v = files[k] -- chained
+ end
+ if find(k,pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do -- ipairs?
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
+ end
+ end
+ return nil, nil
+end
+
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
+ else
+ b = toboolean(b)
+ return (b == nil and default) or b
+ end
+end
+
+texconfig.kpse_init = false
+
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
+
+-- for a while
+
+input = resolvers
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-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--
+<p>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.</p>
+
+</code>
+TEXMFCACHE=$TMP;$TEMP;$TMPDIR;$TEMPDIR;$HOME;$TEXMFVAR;$VARTEXMF;.
+</code>
+
+<p>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.</p>
+--ldx]]--
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
+caches = caches 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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
+ end
+ end
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
+ if not cachepath then
+ print("\nfatal error: there is no valid (writable) cache path defined\n")
+ os.exit()
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
+ os.exit()
+ end
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
+ return cachepath
+ end
+ return cachepath
+end
+
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
+end
+
+function caches.hashed(tree)
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
+end
+
+function caches.treehash()
+ local tree = caches.configpath()
+ if not tree or tree == "" then
+ return false
+ else
+ return caches.hashed(tree)
+ end
+end
+
+function caches.setpath(...)
+ if not caches.path then
+ if not caches.path then
+ caches.path = caches.temp()
+ end
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
+ if not caches.path then
+ caches.path = '.'
+ end
+ caches.path = resolvers.clean_path(caches.path)
+ if 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(category,subcategory)
+ return function()
+ return caches.setpath(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
+ loader = loader()
+ collectgarbage("step")
+ return loader
+ else
+ return false
+ end
+end
+
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
+ local tmaname, tmcname = caches.setluanames(filepath,filename)
+ return file.iswritable(tmaname)
+end
+
+function caches.savedata(filepath,filename,data,raw)
+ 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',false,true,false)) -- no hex
+ else
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
+ end
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
+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 input and resolvers.instance then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end -- or luc
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+--[[ldx--
+<p>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).</p>
+
+<p>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.</p>
+
+<p>Examples of usage can be found in the font related code.</p>
+--ldx]]--
+
+containers = containers or { }
+
+containers.usecache = true
+
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%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 and caches.setpath and caches.setpath(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 and caches.iswritable(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 caches and not container.storage[name] and containers.usecache 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 and caches 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
+
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
+
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
+
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(cachename))
+ else
+ return file.join(cachename,dataname)
+ end
+ end)
+end
+
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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
+
+resolvers.automounted = resolvers.automounted or { }
+
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { caches.setpath("mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ statistics.starttiming(resolvers.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
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ statistics.stoptiming(resolvers.instance)
+ end
+end
+
+-- status info
+
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
+
+-- experiment (code will move)
+
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
+ end
+end
+
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
+ else
+ return "invalid status file"
+ end
+ else
+ return "missing status file"
+ end
+ end
+ return true
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
+
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
+
+<p>How about just forgetting about them?</p>
+--ldx]]--
+
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
+
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
+
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
+
+local find = string.find
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
+ end
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
+ end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
+ end
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
+ end
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
+ end
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
+ end
+ else
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
+ end
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
+ end
+ end
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-lst'] = {
+ 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"
+}
+
+-- used in mtxrun
+
+local find, concat, upper, format = string.find, table.concat, string.upper, string.format
+
+resolvers.listers = resolvers.listers or { }
+
+local function tabstr(str)
+ if type(str) == 'table' then
+ return concat(str," | ")
+ else
+ return str
+ end
+end
+
+local function list(list,report)
+ local instance = resolvers.instance
+ local pat = upper(pattern or "","")
+ local report = report or texio.write_nl
+ for _,key in pairs(table.sortedkeys(list)) do
+ if instance.pattern == "" or find(upper(key),pat) then
+ if instance.kpseonly then
+ if instance.kpsevars[key] then
+ report(format("%s=%s",key,tabstr(list[key])))
+ end
+ else
+ report(format('%s %s=%s',(instance.kpsevars[key] and 'K') or 'E',key,tabstr(list[key])))
+ end
+ end
+ end
+end
+
+function resolvers.listers.variables () list(resolvers.instance.variables ) end
+function resolvers.listers.expansions() list(resolvers.instance.expansions) end
+
+function resolvers.listers.configurations(report)
+ local report = report or texio.write_nl
+ local instance = resolvers.instance
+ for _,key in ipairs(table.sortedkeys(instance.kpsevars)) do
+ if not instance.pattern or (instance.pattern=="") or find(key,instance.pattern) then
+ report(format("%s\n",key))
+ for i,c in ipairs(instance.order) do
+ local str = c[key]
+ if str then
+ report(format("\t%s\t%s",i,str))
+ end
+ end
+ report("")
+ end
+ end
+end
+
+
+end -- of closure
+-- end library merge
+
+-- We initialize some characteristics of this program. We need to
+-- do this before we load the libraries, else own.name will not be
+-- properly set (handy for selfcleaning the file). It's an ugly
+-- looking piece of code.
+
+own = { }
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-number.lua',
+ 'l-set.lua',
+ 'l-os.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-url.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+ 'l-unicode.lua',
+ 'l-math.lua',
+ 'l-utils.lua',
+ 'l-aux.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+-- 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-lst.lua', -- lister
+}
+
+-- We need this hack till luatex is fixed.
+
+if arg and arg[0] == 'luatex' 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 resolvers then
+ locate_libs()
+end
+
+if not resolvers then
+ print("")
+ print("Luatools is unable to start up due to lack of libraries. You may")
+ print("try to run 'lua luatools.lua --selfmerge' in the path where this")
+ print("script is located (normally under ..../scripts/context/lua) which")
+ print("will make luatools library independent.")
+ os.exit()
+end
+
+logs.setprogram('LuaTools',"TDS Management Tool 1.31",environment.arguments["verbose"] or false)
+
+local instance = resolvers.reset()
+
+resolvers.defaultlibs = { -- not all are needed (this will become: context.lus (lua spec)
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-boolean.lua',
+ 'l-number.lua',
+ 'l-unicode.lua',
+ 'l-os.lua',
+ 'l-io.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-url.lua',
+ 'l-dir.lua',
+ 'l-utils.lua',
+ 'l-dimen.lua',
+ 'trac-inf.lua',
+ 'trac-tra.lua',
+ 'trac-log.lua',
+ 'luat-env.lua', -- here ?
+ 'data-res.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-tmp.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-pre.lua',
+ 'data-tex.lua',
+ 'data-bin.lua',
+-- 'data-zip.lua',
+-- 'data-clr.lua',
+ 'data-lua.lua',
+ 'data-ctx.lua',
+ 'luat-fio.lua',
+ 'luat-cnf.lua',
+}
+
+instance.engine = environment.arguments["engine"] or 'luatex'
+instance.progname = environment.arguments["progname"] or 'context'
+instance.luaname = environment.arguments["luafile"] or "" -- environment.ownname or ""
+instance.lualibs = environment.arguments["lualibs"] or table.concat(resolvers.defaultlibs,",")
+instance.allresults = environment.arguments["all"] or false
+instance.pattern = environment.arguments["pattern"] or nil
+instance.sortdata = environment.arguments["sort"] or false
+instance.kpseonly = not environment.arguments["all"] or false
+instance.my_format = environment.arguments["format"] or instance.format
+
+if type(instance.pattern) == 'boolean' then
+ logs.simple("invalid pattern specification")
+ instance.pattern = nil
+end
+
+if environment.arguments["trace"] then resolvers.settrace(environment.arguments["trace"]) end
+
+runners = runners or { }
+messages = messages or { }
+
+messages.no_ini_file = [[
+There is no lua initialization file found. This file can be forced by the
+"--progname" directive, or specified with "--luaname", or it is derived
+automatically from the formatname (aka jobname). It may be that you have
+to regenerate the file database using "luatools --generate".
+]]
+
+messages.help = [[
+--generate generate file database
+--variables show configuration variables
+--expansions show expanded variables
+--configurations show configuration order
+--expand-braces expand complex variable
+--expand-path expand variable (resolve paths)
+--expand-var expand variable (resolve references)
+--show-path show path expansion of ...
+--var-value report value of variable
+--find-file report file location
+--find-path report path of file
+--make or --ini make luatex format
+--run or --fmt= run luatex format
+--luafile=str lua inifile (default is <progname>.lua)
+--lualibs=list libraries to assemble (optional when --compile)
+--compile assemble and compile lua inifile
+--verbose give a bit more info
+--all show all found files
+--sort sort cached data
+--engine=str target engine
+--progname=str format or backend
+--pattern=str filter variables
+]]
+
+function runners.make_format(texname)
+ local instance = resolvers.instance
+ if texname and texname ~= "" then
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ if path and lfs then
+ lfs.chdir(path)
+ end
+ end
+ local barename = texname:gsub("%.%a+$","")
+ if barename == texname then
+ texname = texname .. ".tex"
+ end
+ local fullname = resolvers.find_files(texname)[1] or ""
+ if fullname == "" then
+ logs.simple("no tex file with name: %s",texname)
+ else
+ local luaname, lucname, luapath, lualibs = "", "", "", { }
+ -- the following is optional, since context.lua can also
+ -- handle this collect and compile business
+ if environment.arguments["compile"] then
+ if luaname == "" then luaname = barename end
+ logs.simple("creating initialization file: %s",luaname)
+ luapath = file.dirname(luaname)
+ if luapath == "" then
+ luapath = file.dirname(texname)
+ end
+ if luapath == "" then
+ luapath = file.dirname(resolvers.find_files(texname)[1] or "")
+ end
+ lualibs = string.split(instance.lualibs,",")
+ luaname = file.basename(barename .. ".lua")
+ lucname = file.basename(barename .. ".luc")
+ -- todo: when this fails, we can just copy the merged libraries from
+ -- luatools since they are normally the same, at least for context
+ if lualibs[1] then
+ local firstlib = file.join(luapath,lualibs[1])
+ if not lfs.isfile(firstlib) then
+ local foundname = resolvers.find_files(lualibs[1])[1]
+ if foundname then
+ logs.simple("located library path: %s",luapath)
+ luapath = file.dirname(foundname)
+ end
+ end
+ end
+ logs.simple("using library path: %s",luapath)
+ logs.simple("using lua libraries: %s",table.join(lualibs," "))
+ utils.merger.selfcreate(lualibs,luapath,luaname)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ if utils.lua.compile(luaname,lucname,false,strip) and io.exists(lucname) then
+ luaname = lucname
+ logs.simple("using compiled initialization file: %s",lucname)
+ else
+ logs.simple("using uncompiled initialization file: %s",luaname)
+ end
+ else
+ for _, v in pairs({instance.luaname, instance.progname, barename}) do
+ v = string.gsub(v..".lua","%.lua%.lua$",".lua")
+ if v and (v ~= "") then
+ luaname = resolvers.find_files(v)[1] or ""
+ if luaname ~= "" then
+ break
+ end
+ end
+ end
+ end
+ if environment.arguments["noluc"] then
+ luaname = luaname:gsub("%.luc$",".lua") -- make this an option
+ end
+ if luaname == "" then
+ if logs.verbose then
+ logs.simplelines(messages.no_ini_file)
+ logs.simple("texname : %s",texname)
+ logs.simple("luaname : %s",instance.luaname)
+ logs.simple("progname: %s",instance.progname)
+ logs.simple("barename: %s",barename)
+ end
+ else
+ logs.simple("using lua initialization file: %s",luaname)
+ local mp = dir.glob(file.removesuffix(file.basename(luaname)).."-*.mem")
+ if mp and #mp > 0 then
+ for _, name in ipairs(mp) do
+ logs.simple("removing related mplib format %s", file.basename(name))
+ os.remove(name)
+ end
+ end
+ local flags = {
+ "--ini",
+ "--lua=" .. string.quote(luaname)
+ }
+ local bs = (os.platform == "unix" and "\\\\") or "\\" -- todo: make a function
+ local command = "luatex ".. table.concat(flags," ") .. " " .. string.quote(fullname) .. " " .. bs .. "dump"
+ logs.simple("running command: %s\n",command)
+ os.spawn(command)
+ -- todo: do a dummy run that generates the related metafun and mfplain formats
+ end
+ end
+ else
+ logs.simple("no tex file given")
+ end
+end
+
+function runners.run_format(name,data,more)
+ -- hm, rather old code here; we can now use the file.whatever functions
+ if name and (name ~= "") then
+ local barename = name:gsub("%.%a+$","")
+ local fmtname = ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ barename = fmtname:gsub("%.%a+$","")
+ if fmtname == "" then
+ logs.simple("no format with name: %s",name)
+ else
+ local luaname = barename .. ".luc"
+ local f = io.open(luaname)
+ if not f then
+ luaname = barename .. ".lua"
+ f = io.open(luaname)
+ end
+ if f then
+ f:close()
+ local command = "luatex --fmt=" .. string.quote(barename) .. " --lua=" .. string.quote(luaname) .. " " .. string.quote(data) .. " " .. (more ~= "" and string.quote(more) or "")
+ logs.simple("running command: %s",command)
+ os.spawn(command)
+ else
+ logs.simple("using format name: %s",fmtname)
+ logs.simple("no luc/lua with name: %s",barename)
+ end
+ end
+ end
+end
+
+local ok = true
+
+-- private option --noluc for testing errors in the stub
+
+if environment.arguments["find-file"] then
+ resolvers.load()
+ instance.format = environment.arguments["format"] or instance.format
+ if instance.pattern then
+ instance.allresults = true
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
+ else
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
+ end
+elseif environment.arguments["find-path"] then
+ resolvers.load()
+ local path = resolvers.find_file(environment.files[1], instance.my_format)
+ if logs.verbose then
+ logs.simple(file.dirname(path))
+ else
+ print(file.dirname(path))
+ end
+elseif environment.arguments["run"] then
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.files[1] or "",environment.files[2] or "",environment.files[3] or "")
+elseif environment.arguments["fmt"] then
+ resolvers.load("nofiles") -- ! no need for loading databases
+ logs.setverbose(true)
+ runners.run_format(environment.arguments["fmt"], environment.files[1] or "",environment.files[2] or "")
+elseif environment.arguments["expand-braces"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_braces, environment.files)
+elseif environment.arguments["expand-path"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_path, environment.files)
+elseif environment.arguments["expand-var"] or environment.arguments["expand-variable"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.expand_var, environment.files)
+elseif environment.arguments["show-path"] or environment.arguments["path-value"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.show_path, environment.files)
+elseif environment.arguments["var-value"] or environment.arguments["show-value"] then
+ resolvers.load("nofiles")
+ resolvers.for_files(resolvers.var_value, environment.files)
+elseif environment.arguments["format-path"] then
+ resolvers.load()
+ logs.simple(caches.setpath("format"))
+elseif instance.pattern then -- brrr
+ resolvers.load()
+ instance.format = environment.arguments["format"] or instance.format
+ instance.allresults = true
+ resolvers.for_files(resolvers.find_files, { instance.pattern }, instance.my_format)
+elseif environment.arguments["generate"] then
+ instance.renewcache = true
+ logs.setverbose(true)
+ resolvers.load()
+elseif environment.arguments["make"] or environment.arguments["ini"] or environment.arguments["compile"] then
+ resolvers.load()
+ logs.setverbose(true)
+ runners.make_format(environment.files[1] or "")
+elseif environment.arguments["selfmerge"] then
+ utils.merger.selfmerge(own.name,own.libs,own.list)
+elseif environment.arguments["selfclean"] then
+ utils.merger.selfclean(own.name)
+elseif environment.arguments["selfupdate"] then
+ resolvers.load()
+ logs.setverbose(true)
+ resolvers.update_script(own.name,"luatools")
+elseif environment.arguments["variables"] or environment.arguments["show-variables"] then
+ resolvers.load("nofiles")
+ resolvers.listers.variables()
+elseif environment.arguments["expansions"] or environment.arguments["show-expansions"] then
+ resolvers.load("nofiles")
+ resolvers.listers.expansions()
+elseif environment.arguments["configurations"] or environment.arguments["show-configurations"] then
+ resolvers.load("nofiles")
+ resolvers.listers.configurations()
+elseif environment.arguments["help"] or (environment.files[1]=='help') or (#environment.files==0) then
+ logs.help(messages.help)
+else
+ resolvers.load()
+ resolvers.for_files(resolvers.find_files, environment.files, instance.my_format)
+end
+
+if logs.verbose then
+ logs.simpleline()
+ logs.simple("runtime: %0.3f seconds",os.runtime())
+end
+
+if os.platform == "unix" then
+ io.write("\n")
+end
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/makempy b/Master/texmf-dist/scripts/context/stubs/unix/makempy
index 4bf7a1af230..34892b28460 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/makempy
+++ b/Master/texmf-dist/scripts/context/stubs/unix/makempy
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart makempy.pl "$@"
+mtxrun --usekpse --execute makempy.pl "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/metatex b/Master/texmf-dist/scripts/context/stubs/unix/metatex
new file mode 100644
index 00000000000..f0c6b65d42f
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/unix/metatex
@@ -0,0 +1,2 @@
+#!/bin/sh
+mtxrun --script metatex "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mpstools b/Master/texmf-dist/scripts/context/stubs/unix/mpstools
index b4c8f634548..1a64d90b03f 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/mpstools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/mpstools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart mpstools.rb "$@"
+mtxrun --usekpse --execute mpstools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mptopdf b/Master/texmf-dist/scripts/context/stubs/unix/mptopdf
index 980a3123d1a..f57a8b7a792 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/mptopdf
+++ b/Master/texmf-dist/scripts/context/stubs/unix/mptopdf
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart mptopdf.pl "$@"
+mtxrun --usekpse --execute mptopdf.pl "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mtxrun b/Master/texmf-dist/scripts/context/stubs/unix/mtxrun
new file mode 100644
index 00000000000..82d1edecbc5
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/unix/mtxrun
@@ -0,0 +1,10512 @@
+#!/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 control.
+
+-- 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]
+
+texlua = true
+
+-- begin library merge
+
+
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-string'] = {
+ 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"
+}
+
+local sub, gsub, find, match, gmatch, format, char, byte, rep = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep
+
+if not string.split then
+
+ -- this will be overloaded by a faster lpeg variant
+
+ function string:split(pattern)
+ if #self > 0 then
+ local t = { }
+ for s in gmatch(self..pattern,"(.-)"..pattern) do
+ t[#t+1] = s
+ end
+ return t
+ else
+ return { }
+ end
+ end
+
+end
+
+local chr_to_esc = {
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["^"] = "%^", ["$"] = "%$",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ ["{"] = "%{", ["}"] = "%}"
+}
+
+string.chr_to_esc = chr_to_esc
+
+function string:esc() -- variant 2
+ return (gsub(self,"(.)",chr_to_esc))
+end
+
+function string:unquote()
+ return (gsub(self,"^([\"\'])(.*)%1$","%2"))
+end
+
+function string:quote() -- we could use format("%q")
+ return '"' .. self:unquote() .. '"'
+end
+
+function string:count(pattern) -- variant 3
+ local n = 0
+ for _ in gmatch(self,pattern) do
+ n = n + 1
+ end
+ return n
+end
+
+function string:limit(n,sentinel)
+ if #self > n then
+ sentinel = sentinel or " ..."
+ return sub(self,1,(n-#sentinel)) .. sentinel
+ else
+ return self
+ end
+end
+
+function string:strip()
+ return (gsub(self,"^%s*(.-)%s*$", "%1"))
+end
+
+function string:is_empty()
+ return not find(find,"%S")
+end
+
+function string:enhance(pattern,action)
+ local ok, n = true, 0
+ while ok do
+ ok = false
+ self = gsub(self,pattern, function(...)
+ ok, n = true, n + 1
+ return action(...)
+ end)
+ end
+ return self, n
+end
+
+local chr_to_hex, hex_to_chr = { }, { }
+
+for i=0,255 do
+ local c, h = char(i), format("%02X",i)
+ chr_to_hex[c], hex_to_chr[h] = h, c
+end
+
+function string:to_hex()
+ return (gsub(self or "","(.)",chr_to_hex))
+end
+
+function string:from_hex()
+ return (gsub(self or "","(..)",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, byte(str:sub(index,index))
+ end
+ function string:bytes()
+ return nextbyte, self, 0
+ end
+
+end
+
+-- we can use format for this (neg n)
+
+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) -- tonumber
+ return find(str,"^[%-%+]?[%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 find(self,"=") then
+ local t = { }
+ for k,v in gmatch(self,"(%a+)=([^%,]*)") do
+ t[k] = v
+ end
+ return t
+ else
+ return nil
+ end
+end
+
+local patterns_escapes = {
+ ["-"] = "%-",
+ ["."] = "%.",
+ ["+"] = "%+",
+ ["*"] = "%*",
+ ["%"] = "%%",
+ ["("] = "%)",
+ [")"] = "%)",
+ ["["] = "%[",
+ ["]"] = "%]",
+}
+
+function string:pattesc()
+ return (gsub(self,".",patterns_escapes))
+end
+
+function string:tohash()
+ local t = { }
+ for s in gmatch(self,"([^, ]+)") do -- lpeg
+ t[s] = true
+ end
+ return t
+end
+
+local pattern = lpeg.Ct(lpeg.C(1)^0)
+
+function string:totable()
+ return pattern:match(self)
+end
+
+--~ for _, str in ipairs {
+--~ "1234567123456712345671234567",
+--~ "a\tb\tc",
+--~ "aa\tbb\tcc",
+--~ "aaa\tbbb\tccc",
+--~ "aaaa\tbbbb\tcccc",
+--~ "aaaaa\tbbbbb\tccccc",
+--~ "aaaaaa\tbbbbbb\tcccccc",
+--~ } do print(string.tabtospace(str)) end
+
+function string.tabtospace(str,tab)
+ -- we don't handle embedded newlines
+ while true do
+ local s = find(str,"\t")
+ if s then
+ if not tab then tab = 7 end -- only when found
+ local d = tab-(s-1)%tab
+ if d > 0 then
+ str = gsub(str,"\t",rep(" ",d),1)
+ else
+ str = gsub(str,"\t","",1)
+ end
+ else
+ break
+ end
+ end
+ return str
+end
+
+function string:compactlong() -- strips newlines and leading spaces
+ self = gsub(self,"[\n\r]+ *","")
+ self = gsub(self,"^ *","")
+ return self
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-lpeg'] = {
+ 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"
+}
+
+local P, S, Ct, C, Cs, Cc = lpeg.P, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
+
+--~ 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 P { P(pattern) + 1 * lpeg.V(1) }
+end
+
+function lpeg.startswith(pattern) --slightly adapted
+ return P(pattern)
+end
+
+function lpeg.splitter(pattern, action)
+ return (((1-P(pattern))^1)/action+1)^0
+end
+
+-- variant:
+
+--~ local parser = lpeg.Ct(lpeg.splitat(newline))
+
+local crlf = P("\r\n")
+local cr = P("\r")
+local lf = P("\n")
+local space = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
+local newline = crlf + cr + lf
+local spacing = space^0 * newline
+
+local empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
+local content = (empty + nonempty)^1
+
+local capture = Ct(content^0)
+
+function string:splitlines()
+ return capture:match(self)
+end
+
+lpeg.linebyline = content -- better make a sublibrary
+
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps->what->more")) -- oeps what more
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps->what->more")) -- oeps what->more
+--~ local p = lpeg.splitat("->",false) print(p:match("oeps")) -- oeps
+--~ local p = lpeg.splitat("->",true) print(p:match("oeps")) -- oeps
+
+local splitters_s, splitters_m = { }, { }
+
+local function splitat(separator,single)
+ local splitter = (single and splitters_s[separator]) or splitters_m[separator]
+ if not splitter then
+ separator = P(separator)
+ if single then
+ local other, any = C((1 - separator)^0), P(1)
+ splitter = other * (separator * C(any^0) + "")
+ splitters_s[separator] = splitter
+ else
+ local other = C((1 - separator)^0)
+ splitter = other * (separator * other)^0
+ splitters_m[separator] = splitter
+ end
+ end
+ return splitter
+end
+
+lpeg.splitat = splitat
+
+local cache = { }
+
+function string:split(separator)
+ local c = cache[separator]
+ if not c then
+ c = Ct(splitat(separator))
+ cache[separator] = c
+ end
+ return c:match(self)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-table'] = {
+ 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"
+}
+
+table.join = table.concat
+
+local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
+local format, find, gsub, lower, dump = string.format, string.find, string.gsub, string.lower, string.dump
+local getmetatable, setmetatable = getmetatable, setmetatable
+local type, next, tostring, ipairs = type, next, tostring, ipairs
+
+function table.strip(tab)
+ local lst = { }
+ for i=1,#tab do
+ local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
+ if s == "" then
+ -- skip this one
+ else
+ lst[#lst+1] = s
+ end
+ end
+ return lst
+end
+
+local function compare(a,b)
+ return (tostring(a) < tostring(b))
+end
+
+local function sortedkeys(tab)
+ local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ for key,_ in next, 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
+ sort(srt,compare)
+ else
+ sort(srt)
+ end
+ return srt
+end
+
+local function sortedhashkeys(tab) -- fast one
+ local srt = { }
+ for key,_ in next, tab do
+ srt[#srt+1] = key
+ end
+ sort(srt)
+ return srt
+end
+
+table.sortedkeys = sortedkeys
+table.sortedhashkeys = sortedhashkeys
+
+function table.sortedpairs(t)
+ local s = sortedhashkeys(t) -- maybe just sortedkeys
+ local n = 0
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+end
+
+function table.append(t, list)
+ for _,v in next, list do
+ insert(t,v)
+ end
+end
+
+function table.prepend(t, list)
+ for k,v in next, list do
+ 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 next, 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 next, 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
+
+local function fastcopy(old) -- fast one
+ if old then
+ local new = { }
+ for k,v in next, old do
+ if type(v) == "table" then
+ new[k] = fastcopy(v) -- was just table.copy
+ else
+ new[k] = v
+ end
+ end
+ -- optional second arg
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
+ return new
+ else
+ return { }
+ end
+end
+
+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 next, 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.fastcopy = fastcopy
+table.copy = copy
+
+-- 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 next, 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
+
+function table.tohash(t,value)
+ local h = { }
+ if t then
+ if value == nil then value = true end
+ for _, v in next, t do -- no ipairs here
+ h[v] = value
+ end
+ end
+ return h
+end
+
+function table.fromhash(t)
+ local h = { }
+ for k, v in next, t do -- no ipairs here
+ if v then h[#h+1] = k end
+ end
+ return h
+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")
+
+table.serialize_functions = true
+table.serialize_compact = true
+table.serialize_inline = true
+
+local noquotes, hexify, handle, reduce, compact, inline, functions
+
+local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+ 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
+ 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
+}
+
+local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _,v in next, 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" then
+ if hexify then
+ tt[#tt+1] = format("0x%04X",v)
+ else
+ tt[#tt+1] = tostring(v) -- tostring not needed
+ end
+ elseif tv == "boolean" then
+ tt[#tt+1] = tostring(v)
+ elseif tv == "string" then
+ tt[#tt+1] = format("%q",v)
+ else
+ tt = nil
+ break
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+end
+
+-- Because this is a core function of mkiv I moved some function calls
+-- inline.
+--
+-- twice as fast in a test:
+--
+-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
+
+local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ depth = depth .. " "
+ if indexed then
+ handle(format("%s{",depth))
+ elseif name then
+ --~ handle(format("%s%s={",depth,key(name)))
+ if type(name) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s[0x%04X]={",depth,name))
+ else
+ handle(format("%s[%s]={",depth,name))
+ end
+ elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
+ else
+ handle(format("%s[%q]={",depth,name))
+ end
+ else
+ handle(format("%s{",depth))
+ end
+ end
+ if root and next(root) then
+ local first, last = nil, 0 -- #root cannot be trusted here
+ if compact then
+ -- NOT: for k=1,#root do (we need to quit at nil)
+ for k,v in ipairs(root) do -- can we use next?
+ if not first then first = k end
+ last = last + 1
+ end
+ end
+ local sk = sortedkeys(root)
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ --~ if v == root then
+ -- circular
+ --~ else
+ local t = type(v)
+ if compact and first and type(k) == "number" and k >= first and k <= last then
+ if t == "number" then
+ if hexify then
+ handle(format("%s 0x%04X,",depth,v))
+ else
+ handle(format("%s %s,",depth,v))
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ handle(format("%s %s,",depth,v))
+ else
+ handle(format("%s %q,",depth,v))
+ end
+ elseif t == "table" then
+ if not next(v) then
+ handle(format("%s {},",depth))
+ elseif inline then -- and #t > 0
+ local st = simple_table(v)
+ if st then
+ handle(format("%s { %s },",depth,concat(st,", ")))
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ elseif t == "boolean" then
+ handle(format("%s %s,",depth,tostring(v)))
+ elseif t == "function" then
+ if functions then
+ handle(format('%s loadstring(%q),',depth,dump(v)))
+ else
+ handle(format('%s "function",',depth))
+ end
+ else
+ handle(format("%s %q,",depth,tostring(v)))
+ end
+ elseif k == "__p__" then -- parent
+ if false then
+ handle(format("%s __p__=nil,",depth))
+ end
+ elseif t == "number" then
+ --~ if hexify then
+ --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
+ --~ else
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ --~ end
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ if hexify then
+ handle(format("%s %s=0x%04X,",depth,k,v))
+ else
+ handle(format("%s %s=%s,",depth,k,v))
+ end
+ else
+ if hexify then
+ handle(format("%s [%q]=0x%04X,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ end
+ elseif t == "string" then
+ if reduce and (find(v,"^[%-%+]?[%d]-%.?[%d+]$") == 1) then
+ --~ handle(format("%s %s=%s,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,v))
+ else
+ handle(format("%s [%s]=%s,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,v))
+ else
+ handle(format("%s [%q]=%s,",depth,k,v))
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),v))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,v))
+ else
+ handle(format("%s [%s]=%q,",depth,k,v))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,v))
+ else
+ handle(format("%s [%q]=%q,",depth,k,v))
+ end
+ end
+ elseif t == "table" then
+ if not next(v) then
+ --~ handle(format("%s %s={},",depth,key(k)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={},",depth,k))
+ else
+ handle(format("%s [%s]={},",depth,k))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={},",depth,k))
+ else
+ handle(format("%s [%q]={},",depth,k))
+ end
+ elseif inline then
+ local st = simple_table(v)
+ if st then
+ --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
+ else
+ handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ else
+ do_serialize(v,k,depth,level+1)
+ end
+ elseif t == "boolean" then
+ --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%s,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%s,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ end
+ elseif t == "function" then
+ if functions then
+ --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
+ else
+ handle(format("%s [%q]=loadstring(%q),",depth,k,dump(v)))
+ end
+ end
+ else
+ --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
+ if type(k) == "number" then -- or find(k,"^%d+$") then
+ if hexify then
+ handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%s]=%q,",depth,k,tostring(v)))
+ end
+ elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
+ handle(format("%s %s=%q,",depth,k,tostring(v)))
+ else
+ handle(format("%s [%q]=%q,",depth,k,tostring(v)))
+ end
+ end
+ --~ end
+ end
+ end
+ if level > 0 then
+ handle(format("%s},",depth))
+ end
+end
+
+-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
+-- faster (0.03 on 1.00 for zapfino.tma)
+
+local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
+ noquotes = _noquotes
+ hexify = _hexify
+ handle = _handle or print
+ reduce = _reduce or false
+ compact = table.serialize_compact
+ inline = compact and table.serialize_inline
+ functions = table.serialize_functions
+ local tname = type(name)
+ if tname == "string" then
+ if name == "return" then
+ handle("return {")
+ else
+ handle(name .. "={")
+ end
+ elseif tname == "number" then
+ if hexify then
+ handle(format("[0x%04X]={",name))
+ else
+ handle("[" .. name .. "]={")
+ end
+ elseif tname == "boolean" then
+ if name then
+ handle("return {")
+ else
+ handle("{")
+ end
+ else
+ handle("t={")
+ end
+ if root and next(root) then
+ do_serialize(root,name,"",0,indexed)
+ end
+ handle("}")
+end
+
+--~ name:
+--~
+--~ true : return { }
+--~ false : { }
+--~ nil : t = { }
+--~ string : string = { }
+--~ 'return' : return { }
+--~ number : [number] = { }
+
+function table.serialize(root,name,reduce,noquotes,hexify)
+ local t = { }
+ local function flush(s)
+ t[#t+1] = s
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ return concat(t,"\n")
+end
+
+function table.tohandle(handle,root,name,reduce,noquotes,hexify)
+ serialize(root,name,handle,reduce,noquotes,hexify)
+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,hexify)
+ local f = io.open(filename,'w')
+ if f then
+ 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,reduce,noquotes,hexify)
+ f:write(concat(t,"\n"),"\n")
+ else
+ local function flush(s)
+ f:write(s,"\n")
+ end
+ serialize(root,name,flush,reduce,noquotes,hexify)
+ end
+ f:close()
+ end
+end
+
+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
+
+-- the next three may disappear
+
+function table.remove_value(t,value) -- todo: n
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ remove(t,i)
+ -- remove all, so no: return
+ end
+ end
+ end
+end
+
+function table.insert_before_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i,str)
+ return
+ end
+ end
+ end
+ insert(t,1,str)
+ elseif value then
+ insert(t,1,value)
+ end
+end
+
+function table.insert_after_value(t,value,str)
+ if str then
+ if value then
+ for i=1,#t do
+ if t[i] == value then
+ insert(t,i+1,str)
+ return
+ end
+ end
+ end
+ t[#t+1] = str
+ elseif value then
+ t[#t+1] = value
+ end
+end
+
+local function are_equal(a,b,n,m) -- indexed
+ if a and b and #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 then
+ -- same
+ elseif type(ai)=="table" and type(bi)=="table" then
+ if not are_equal(ai,bi) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+ else
+ return false
+ end
+end
+
+local function identical(a,b) -- assumes same structure
+ for ka, va in next, a do
+ local vb = b[k]
+ if va == vb then
+ -- same
+ elseif type(va) == "table" and type(vb) == "table" then
+ if not identical(va,vb) then
+ return false
+ end
+ else
+ return false
+ end
+ end
+ return true
+end
+
+table.are_equal = are_equal
+table.identical = identical
+
+-- maybe also make a combined one
+
+function table.compact(t)
+ if t then
+ for k,v in next, t do
+ if not next(v) then
+ t[k] = nil
+ end
+ end
+ end
+end
+
+function table.contains(t, v)
+ if t then
+ for i=1, #t do
+ if t[i] == v then
+ return i
+ 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 next, 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] = format("0x%04X",t[i]) end
+ return concat(tt,seperator or " ")
+end
+
+function table.reverse_hash(h)
+ local r = { }
+ for k,v in next, h do
+ r[v] = lower(gsub(k," ",""))
+ 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
+
+--~ function table.keys(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return k
+--~ end
+
+--~ function table.keys_as_string(t)
+--~ local k = { }
+--~ for k,_ in next, t do
+--~ k[#k+1] = k
+--~ end
+--~ return concat(k,"")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-io'] = {
+ 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"
+}
+
+local byte = string.byte
+
+if string.find(os.getenv("PATH"),";") then
+ io.fileseparator, io.pathseparator = "\\", ";"
+else
+ io.fileseparator, io.pathseparator = "/" , ":"
+end
+
+function io.loaddata(filename,textmode)
+ local f = io.open(filename,(textmode and 'r') or 'rb')
+ if f then
+ -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
+ local data = f:read('*all')
+ -- garbagecollector.check(data)
+ 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()
+ return true
+ else
+ return false
+ 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
+
+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
+
+local nextbyte = {
+ [4] = function(f)
+ local a, b, c, d = f:read(1,1,1,1)
+ if d then
+ return byte(a), byte(b), byte(c), byte(d)
+ else
+ return nil, nil, nil, nil
+ end
+ end,
+ [2] = function(f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(a), byte(b)
+ else
+ return nil, nil
+ end
+ end,
+ [1] = function (f)
+ local a = f:read(1)
+ if a then
+ return byte(a)
+ else
+ return nil
+ end
+ end,
+ [-2] = function (f)
+ local a, b = f:read(1,1)
+ if b then
+ return byte(b), byte(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 byte(d), byte(c), byte(b), byte(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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-number'] = {
+ 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"
+}
+
+local format = string.format
+
+number = number or { }
+
+-- a,b,c,d,e,f = number.toset(100101)
+
+function number.toset(n)
+ return (tostring(n)):match("(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)")
+end
+
+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
+
+local one = lpeg.C(1-lpeg.S(''))^1
+
+function number.toset(n)
+ return one:match(tostring(n))
+end
+
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-set'] = {
+ 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"
+}
+
+set = set or { }
+
+local nums = { }
+local tabs = { }
+local concat = table.concat
+local next, type = next, type
+
+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 next, t do
+ if v then
+ -- why bother about the leading space
+ s = s .. " " .. k
+ end
+ end
+ local n = nums[s]
+ if not n then
+ n = #tabs + 1
+ tabs[n] = t
+ nums[s] = n
+ end
+ return n
+ else
+ return 0
+ end
+end
+
+function set.totable(n)
+ if n == 0 then
+ return { }
+ else
+ return tabs[n] or { }
+ end
+end
+
+function set.tolist(n)
+ if n == 0 or not tabs[n] then
+ return ""
+ else
+ local t = { }
+ for k, v in next, tabs[n] do
+ if v then
+ t[#t+1] = k
+ end
+ end
+ return concat(t," ")
+ 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
+
+--~ 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'))
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-os'] = {
+ version = 1.001,
+ comment = "companion to luat-lub.tex",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+local find = string.find
+
+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 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
+
+local startuptime = os.gettimeofday()
+
+function os.runtime()
+ return os.gettimeofday() - startuptime
+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()))
+
+os.arch = os.arch or function()
+ local a = os.resultof("uname -m") or "linux"
+ os.arch = function()
+ return a
+ end
+ return a
+end
+
+local platform
+
+function os.currentplatform(name,default)
+ if not platform then
+ local name = os.name or os.platform or name -- os.name is built in, os.platform is mine
+ if not name then
+ platform = default or "linux"
+ elseif name == "windows" or name == "mswin" or name == "win32" or name == "msdos" then
+ if os.getenv("PROCESSOR_ARCHITECTURE") == "AMD64" then
+ platform = "mswin-64"
+ else
+ platform = "mswin"
+ end
+ else
+ local architecture = os.arch()
+ if name == "linux" then
+ if find(architecture,"x86_64") then
+ platform = "linux-64"
+ elseif find(architecture,"ppc") then
+ platform = "linux-ppc"
+ else
+ platform = "linux"
+ end
+ elseif name == "macosx" then
+ if find(architecture,"i386") then
+ platform = "osx-intel"
+ else
+ platform = "osx-ppc"
+ end
+ elseif name == "sunos" then
+ if find(architecture,"sparc") then
+ platform = "solaris-sparc"
+ else -- if architecture == 'i86pc'
+ platform = "solaris-intel"
+ end
+ elseif name == "freebsd" then
+ if find(architecture,"amd64") then
+ platform = "freebsd-amd64"
+ else
+ platform = "freebsd"
+ end
+ else
+ platform = default or name
+ end
+ end
+ function os.currentplatform()
+ return platform
+ end
+ end
+ return platform
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-file'] = {
+ 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"
+}
+
+-- needs a cleanup
+
+file = file or { }
+
+local concat = table.concat
+local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub
+
+function file.removesuffix(filename)
+ return (gsub(filename,"%.[%a%d]+$",""))
+end
+
+function file.addsuffix(filename, suffix)
+ if not find(filename,"%.[%a%d]+$") then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+end
+
+function file.replacesuffix(filename, suffix)
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+end
+
+function file.dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
+end
+
+function file.basename(name)
+ return match(name,"^.+[/\\](.-)$") or name
+end
+
+function file.nameonly(name)
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
+end
+
+function file.extname(name)
+ return match(name,"^.+%.([^/\\]-)$") or ""
+end
+
+file.suffix = file.extname
+
+--~ 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 = concat({...},"/")
+ pth = gsub(pth,"\\","/")
+ local a, b = match(pth,"^(.*://)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ a, b = match(pth,"^(//)(.*)$")
+ if a and b then
+ return a .. gsub(b,"//+","/")
+ end
+ return (gsub(pth,"//+","/"))
+end
+
+function file.iswritable(name)
+ local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+ return a and a.permissions:sub(2,2) == "w"
+end
+
+function file.isreadable(name)
+ local a = lfs.attributes(name)
+ return a and a.permissions:sub(1,1) == "r"
+end
+
+file.is_readable = file.isreadable
+file.is_writable = file.iswritable
+
+-- todo: lpeg
+
+function file.split_path(str)
+ local t = { }
+ str = gsub(str,"\\", "/")
+ str = gsub(str,"(%a):([;/])", "%1\001%2")
+ for name in gmatch(str,"([^;:]+)") do
+ if name ~= "" then
+ t[#t+1] = gsub(name,"\001",":")
+ end
+ end
+ return t
+end
+
+function file.join_path(tab)
+ return concat(tab,io.pathseparator) -- can have trailing //
+end
+
+function file.collapse_path(str)
+ str = gsub(str,"/%./","/")
+ local n, m = 1, 1
+ while n > 0 or m > 0 do
+ str, n = gsub(str,"[^/%.]+/%.%.$","")
+ str, m = gsub(str,"[^/%.]+/%.%./","")
+ end
+ str = gsub(str,"([^/])/$","%1")
+ str = gsub(str,"^%./","")
+ str = gsub(str,"/%.$","")
+ if str == "" then str = "." end
+ return str
+end
+
+--~ print(file.collapse_path("a/./b/.."))
+--~ print(file.collapse_path("a/aa/../b/bb"))
+--~ print(file.collapse_path("a/../.."))
+--~ print(file.collapse_path("a/.././././b/.."))
+--~ print(file.collapse_path("a/./././b/.."))
+--~ print(file.collapse_path("a/b/c/../.."))
+
+function file.robustname(str)
+ return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
+end
+
+file.readdata = io.loaddata
+file.savedata = io.savedata
+
+function file.copy(oldname,newname)
+ file.savedata(newname,io.loaddata(oldname))
+end
+
+-- lpeg variants, slightly faster, not always
+
+--~ local period = lpeg.P(".")
+--~ local slashes = lpeg.S("\\/")
+--~ local noperiod = 1-period
+--~ local noslashes = 1-slashes
+--~ local name = noperiod^1
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+
+--~ function file.extname(name)
+--~ return pattern:match(name) or ""
+--~ end
+
+--~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+
+--~ function file.removesuffix(name)
+--~ return pattern:match(name)
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+
+--~ function file.basename(name)
+--~ return pattern:match(name) or name
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+
+--~ function file.dirname(name)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2)
+--~ else
+--~ return ""
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.addsuffix(name, suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+
+--~ function file.replacesuffix(name,suffix)
+--~ local p = pattern:match(name)
+--~ if p then
+--~ return name:sub(1,p-2) .. "." .. suffix
+--~ else
+--~ return name .. "." .. suffix
+--~ end
+--~ end
+
+--~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+
+--~ function file.nameonly(name)
+--~ local a, b = pattern:match(name)
+--~ if b then
+--~ return name:sub(a,b-2)
+--~ elseif a then
+--~ return name:sub(a)
+--~ else
+--~ return name
+--~ end
+--~ end
+
+--~ local test = file.extname
+--~ local test = file.basename
+--~ local test = file.dirname
+--~ local test = file.addsuffix
+--~ local test = file.replacesuffix
+--~ local test = file.nameonly
+
+--~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
+--~ print(2,test("./../b/c/abd.def.xxx","!!!"))
+--~ print(3,test("a/b/c/abd.def.xxx","!!!"))
+--~ print(4,test("a/b/c/def.xxx","!!!"))
+--~ print(5,test("a/b/c/def","!!!"))
+--~ print(6,test("def","!!!"))
+--~ print(7,test("def.xxx","!!!"))
+
+--~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
+
+-- also rewrite previous
+
+local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
+local separator = lpeg.P("://")
+
+local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
+local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+-- ./name ../name /name c: :// name/name
+
+function file.is_qualified_path(filename)
+ return qualified:match(filename)
+end
+
+function file.is_rootbased_path(filename)
+ return rootbased:match(filename)
+end
+
+local slash = lpeg.S("\\/")
+local period = lpeg.P(".")
+local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
+local path = lpeg.C(((1-slash)^0 * slash)^0)
+local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
+local base = lpeg.C((1-suffix)^0)
+
+local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+
+function file.splitname(str) -- returns drive, path, base, suffix
+ return pattern:match(str)
+end
+
+-- function test(t) for k, v in pairs(t) do print(v, "=>", file.splitname(v)) end end
+--
+-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
+-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
+-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
+-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-md5'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- This also provides file checksums and checkers.
+
+local gsub, format, byte = string.gsub, string.format, string.byte
+
+local function convert(str,fmt)
+ return (gsub(md5.sum(str),".",function(chr) return format(fmt,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
+
+--~ if not md5.HEX then
+--~ local function remap(chr) return format("%02X",byte(chr)) end
+--~ function md5.HEX(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.hex then
+--~ local function remap(chr) return format("%02x",byte(chr)) end
+--~ function md5.hex(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+--~ if not md5.dec then
+--~ local function remap(chr) return format("%03i",byte(chr)) end
+--~ function md5.dec(str) return (gsub(md5.sum(str),".",remap)) end
+--~ end
+
+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.checksum(name)
+ if md5 then
+ local data = io.loaddata(name)
+ if data then
+ return md5.HEX(data)
+ end
+ end
+ return nil
+end
+
+function file.loadchecksum(name)
+ if md5 then
+ local data = io.loaddata(name .. ".md5")
+ return data and data:gsub("%s","")
+ end
+ return nil
+end
+
+function file.savechecksum(name, checksum)
+ if not checksum then checksum = file.checksum(name) end
+ if checksum then
+ io.savedata(name .. ".md5",checksum)
+ return checksum
+ end
+ return nil
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-dir'] = {
+ 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"
+}
+
+local type = type
+local find, gmatch = string.find, string.gmatch
+
+dir = dir or { }
+
+-- optimizing for no string.find (*) does not save time
+
+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 find(path,"/$") then path = path .. '/' end
+ for name in scanner do
+ local full = path .. name
+ local mode = attributes(full,'mode')
+ if mode == 'file' then
+ if find(full,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 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=1,#str do
+ glob(str[s],t)
+ end
+ return t
+ elseif lfs.isfile(str) then
+ local t = t or { }
+ t[#t+1] = str
+ 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 = find(base,"%*%*")
+ 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 find(name,s) end
+ end
+ files = files or { }
+ for name in walkdir(path) do
+ if find(name,"^%.") then
+ --- skip
+ else
+ local mode = attributes(name,'mode')
+ if mode == "directory" then
+ if recurse then
+ globfiles(path .. "/" .. name,recurse,func,files)
+ end
+ elseif mode == "file" then
+ if func then
+ if func(name) then
+ files[#files+1] = path .. "/" .. name
+ end
+ else
+ files[#files+1] = path .. "/" .. name
+ end
+ end
+ 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 gmatch(last,"[^/]+") 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 find(last,"^/") 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 find(str,"^/") then
+ pth = "/"
+ for s in gmatch(str,"[^/]+") 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 gmatch(str,"[^/]+") 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 find(str,"^/") then
+ str = lfs.currentdir() .. "/" .. str
+ end
+ str = str:gsub("//","/")
+ str = str:gsub("/%./","/")
+ return str
+ end
+
+end
+
+dir.makedirs = dir.mkdirs
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-boolean'] = {
+ 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"
+}
+
+boolean = boolean or { }
+
+local type, tonumber = type, tonumber
+
+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" or str == "t"
+ 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" or str == "t" then
+ return true
+ elseif str == "false" or str == "no" or str == "off" or str == "f" then
+ return false
+ end
+ end
+ return nil
+end
+
+function boolean.alwaystrue()
+ return true
+end
+
+function boolean.falsetrue()
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-math'] = {
+ 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"
+}
+
+local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
+
+if not math.round then
+ function math.round(x)
+ return floor(x + 0.5)
+ end
+end
+
+if not math.div then
+ function math.div(n,m)
+ return floor(n/m)
+ end
+end
+
+if not math.mod then
+ function math.mod(n,m)
+ return n % m
+ end
+end
+
+local pipi = 2*math.pi/360
+
+function math.sind(d)
+ return sin(d*pipi)
+end
+
+function math.cosd(d)
+ return cos(d*pipi)
+end
+
+function math.tand(d)
+ return tan(d*pipi)
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-utils'] = {
+ 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"
+}
+
+-- hm, quite unreadable
+
+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
+
+utils.merger.strip_comment = true
+
+function utils.merger._self_load_(name)
+ local f, data = io.open(name), ""
+ if f then
+ utils.report("reading merge from %s",name)
+ data = f:read("*all")
+ f:close()
+ else
+ utils.report("unknown file to merge %s",name)
+ end
+ if data and utils.merger.strip_comment then
+ -- saves some 20K
+ data = data:gsub("%-%-~[^\n\r]*[\r\n]", "")
+ end
+ return data or ""
+end
+
+function utils.merger._self_save_(name, data)
+ if data ~= "" then
+ local f = io.open(name,'w')
+ if f then
+ utils.report("saving merge from %s",name)
+ 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
+
+--~ stripper:
+--~
+--~ data = string.gsub(data,"%-%-~[^\n]*\n","")
+--~ data = string.gsub(data,"\n\n+","\n")
+
+function utils.merger._self_libs_(libs,list)
+ local result, f, frozen = { }, nil, false
+ result[#result+1] = "\n"
+ if type(libs) == 'string' then libs = { libs } end
+ if type(list) == 'string' then list = { list } end
+ local foundpath = nil
+ for _, lib in ipairs(libs) do
+ for _, pth in ipairs(list) do
+ pth = string.gsub(pth,"\\","/") -- file.clean_path
+ utils.report("checking library path %s",pth)
+ local name = pth .. "/" .. lib
+ if lfs.isfile(name) then
+ foundpath = pth
+ end
+ end
+ if foundpath then break end
+ end
+ if foundpath then
+ utils.report("using library path %s",foundpath)
+ local right, wrong = { }, { }
+ for _, lib in ipairs(libs) do
+ local fullname = foundpath .. "/" .. lib
+ if lfs.isfile(fullname) then
+ -- right[#right+1] = lib
+ utils.report("merging library %s",fullname)
+ result[#result+1] = "do -- create closure to overcome 200 locals limit"
+ result[#result+1] = io.loaddata(fullname,true)
+ result[#result+1] = "end -- of closure"
+ else
+ -- wrong[#wrong+1] = lib
+ utils.report("no library %s",fullname)
+ end
+ end
+ if #right > 0 then
+ utils.report("merged libraries: %s",table.concat(right," "))
+ end
+ if #wrong > 0 then
+ utils.report("skipped libraries: %s",table.concat(wrong," "))
+ end
+ else
+ utils.report("no valid library path found")
+ 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
+
+function utils.lua.compile(luafile, lucfile, cleanup, strip) -- defaults: cleanup=false strip=true
+ -- utils.report("compiling",luafile,"into",lucfile)
+ os.remove(lucfile)
+ local command = "-o " .. string.quote(lucfile) .. " " .. string.quote(luafile)
+ if strip ~= false then
+ command = "-s " .. command
+ end
+ local done = (os.spawn("texluac " .. command) == 0) or (os.spawn("luac " .. command) == 0)
+ if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+ -- utils.report("removing",luafile)
+ os.remove(luafile)
+ end
+ return done
+end
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['l-aux'] = {
+ 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"
+}
+
+aux = aux or { }
+
+local concat, format, gmatch = table.concat, string.format, string.gmatch
+local tostring, type = tostring, type
+
+local space = lpeg.P(' ')
+local equal = lpeg.P("=")
+local comma = lpeg.P(",")
+local lbrace = lpeg.P("{")
+local rbrace = lpeg.P("}")
+local nobrace = 1 - (lbrace+rbrace)
+local nested = lpeg.P{ lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
+local spaces = space^0
+
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+
+local key = lpeg.C((1-equal-comma)^1)
+local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
+local pattern_c = (space+comma)^0 * (key * equal * value)
+
+local key = lpeg.C((1-space-equal-comma)^1)
+local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
+
+-- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
+
+local hash = { }
+
+local function set(key,value) -- using Carg is slower here
+ hash[key] = value
+end
+
+local pattern_a_s = (pattern_a/set)^1
+local pattern_b_s = (pattern_b/set)^1
+local pattern_c_s = (pattern_c/set)^1
+
+aux.settings_to_hash_pattern_a = pattern_a_s
+aux.settings_to_hash_pattern_b = pattern_b_s
+aux.settings_to_hash_pattern_c = pattern_c_s
+
+function aux.make_settings_to_hash_pattern(set,how)
+ if how == "strict" then
+ return (pattern_c/set)^1
+ elseif how == "tolerant" then
+ return (pattern_b/set)^1
+ else
+ return (pattern_a/set)^1
+ end
+end
+
+function aux.settings_to_hash(str)
+ if str and str ~= "" then
+ hash = { }
+ if moretolerant then
+ pattern_b_s:match(str)
+ else
+ pattern_a_s:match(str)
+ end
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_tolerant(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_b_s:match(str)
+ return hash
+ else
+ return { }
+ end
+end
+
+function aux.settings_to_hash_strict(str)
+ if str and str ~= "" then
+ hash = { }
+ pattern_c_s:match(str)
+ return next(hash) and hash
+ else
+ return nil
+ end
+end
+
+local seperator = comma * space^0
+local value = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
+local pattern = lpeg.Ct(value*(seperator*value)^0)
+
+-- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
+
+aux.settings_to_array_pattern = pattern
+
+function aux.settings_to_array(str)
+ if not str or str == "" then
+ return { }
+ else
+ return pattern:match(str)
+ end
+end
+
+local function set(t,v)
+ t[#t+1] = v
+end
+
+local value = lpeg.P(lpeg.Carg(1)*value) / set
+local pattern = value*(seperator*value)^0 * lpeg.Carg(1)
+
+function aux.add_settings_to_array(t,str)
+ return pattern:match(str, nil, t)
+end
+
+function aux.hash_to_string(h,separator,yes,no,strict,omit)
+ if h then
+ local t, s = { }, table.sortedkeys(h)
+ omit = omit and table.tohash(omit)
+ for i=1,#s do
+ local key = s[i]
+ if not omit or not omit[key] then
+ local value = h[key]
+ if type(value) == "boolean" then
+ if yes and no then
+ if value then
+ t[#t+1] = key .. '=' .. yes
+ elseif not strict then
+ t[#t+1] = key .. '=' .. no
+ end
+ elseif value or not strict then
+ t[#t+1] = key .. '=' .. tostring(value)
+ end
+ else
+ t[#t+1] = key .. '=' .. value
+ end
+ end
+ end
+ return concat(t,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.array_to_string(a,separator)
+ if a then
+ return concat(a,separator or ",")
+ else
+ return ""
+ end
+end
+
+function aux.settings_to_set(str,t)
+ t = t or { }
+ for s in gmatch(str,"%s*([^,]+)") do
+ t[s] = true
+ end
+ return t
+end
+
+-- temporary here
+
+function aux.getparameters(self,class,parentclass,settings)
+ local sc = self[class]
+ if not sc then
+ sc = table.clone(self[parent])
+ self[class] = sc
+ end
+ aux.add_settings_to_array(sc, settings)
+end
+
+-- temporary here
+
+local digit = lpeg.R("09")
+local period = lpeg.P(".")
+local zero = lpeg.P("0")
+
+--~ local finish = lpeg.P(-1)
+--~ local nodigit = (1-digit) + finish
+--~ local case_1 = (period * zero^1 * #nodigit)/"" -- .000
+--~ local case_2 = (period * (1-(zero^0/"") * #nodigit)^1 * (zero^0/"") * nodigit) -- .010 .10 .100100
+
+local trailingzeros = zero^0 * -digit -- suggested by Roberto R
+local case_1 = period * trailingzeros / ""
+local case_2 = period * (digit - trailingzeros)^1 * (trailingzeros / "")
+
+local number = digit^1 * (case_1 + case_2)
+local stripper = lpeg.Cs((number + 1)^0)
+
+--~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
+--~ collectgarbage("collect")
+--~ str = string.rep(sample,10000)
+--~ local ts = os.clock()
+--~ stripper:match(str)
+--~ print(#str, os.clock()-ts, stripper:match(sample))
+
+function aux.strip_zeros(str)
+ return stripper:match(str)
+end
+
+function aux.definetable(target) -- defines undefined tables
+ local composed, t = nil, { }
+ for name in gmatch(target,"([^%.]+)") do
+ if composed then
+ composed = composed .. "." .. name
+ else
+ composed = name
+ end
+ t[#t+1] = format("%s = %s or { }",composed,composed)
+ end
+ return concat(t,"\n")
+end
+
+function aux.accesstable(target)
+ local t = _G
+ for name in gmatch(target,"([^%.]+)") do
+ t = t[name]
+ end
+ return t
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-tab'] = {
+ 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"
+}
+
+--[[ldx--
+<p>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 trickery but was less optimized to we
+went this route. First we had a find based parser, now we have an <l n='lpeg'/> based one.
+The find based parser can be found in l-xml-edu.lua along with other older code.</p>
+
+<p>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 <l n='context'/> we also need
+this module for process management, like handling <l n='ctx'/> and <l n='rlx'/>
+files.</p>
+
+<typing>
+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)
+</typing>
+
+<p>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.</p>
+--ldx]]--
+
+xml = xml or { }
+
+--~ local xml = xml
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, setmetatable = type, next, setmetatable
+local format, lower, find = string.format, string.lower, string.find
+
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers.</p>
+--ldx]]--
+
+local trace_remap = false
+
+if trackers then
+ trackers.register("xml.remap", function(v) trace_remap = v end)
+end
+
+function xml.settrace(str,value)
+ if str == "remap" then
+ trace_remap = value or false
+ end
+end
+
+--[[ldx--
+<p>First a hack to enable namespace resolving. A namespace is characterized by
+a <l n='url'/>. The following function associates a namespace prefix with a
+pattern. We use <l n='lpeg'/>, 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.</p>
+--ldx]]--
+
+xml.xmlns = xml.xmlns or { }
+
+local check = lpeg.P(false)
+local parse = check
+
+--[[ldx--
+<p>The next function associates a namespace prefix with an <l n='url'/>. This
+normally happens independent of parsing.</p>
+
+<typing>
+xml.registerns("mml","mathml")
+</typing>
+--ldx]]--
+
+function xml.registerns(namespace, pattern) -- pattern can be an lpeg
+ check = check + lpeg.C(lpeg.P(lower(pattern))) / namespace
+ parse = lpeg.P { lpeg.P(check) + 1 * lpeg.V(1) }
+end
+
+--[[ldx--
+<p>The next function also registers a namespace, but this time we map a
+given namespace prefix onto a registered one, using the given
+<l n='url'/>. This used for attributes like <t>xmlns:m</t>.</p>
+
+<typing>
+xml.checkns("m","http://www.w3.org/mathml")
+</typing>
+--ldx]]--
+
+function xml.checkns(namespace,url)
+ local ns = parse:match(lower(url))
+ if ns and namespace ~= ns then
+ xml.xmlns[namespace] = ns
+ end
+end
+
+--[[ldx--
+<p>Next we provide a way to turn an <l n='url'/> into a registered
+namespace. This used for the <t>xmlns</t> attribute.</p>
+
+<typing>
+resolvedns = xml.resolvens("http://www.w3.org/mathml")
+</typing>
+
+This returns <t>mml</t>.
+--ldx]]--
+
+function xml.resolvens(url)
+ return parse:match(lower(url)) or ""
+end
+
+--[[ldx--
+<p>A namespace in an element can be remapped onto the registered
+one efficiently by using the <t>xml.xmlns</t> table.</p>
+--ldx]]--
+
+--[[ldx--
+<p>This version uses <l n='lpeg'/>. 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 <l n='xml'/> files that
+took 12.5 seconds to load (1.5 for file io and the rest for tree building). With
+the <l n='lpeg'/> implementation we got that down to less 7.3 seconds. Loading the 14
+<l n='context'/> interface definition files (2.6 meg) went down from 1.05 seconds to 0.55.</p>
+
+<p>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
+<l n='lpeg'/> code to it.</p>
+
+<typing>
+<!DOCTYPE Something PUBLIC "... ..." "..." [ ... ] >
+<!DOCTYPE Something PUBLIC "... ..." "..." >
+<!DOCTYPE Something SYSTEM "... ..." [ ... ] >
+<!DOCTYPE Something SYSTEM "... ..." >
+<!DOCTYPE Something [ ... ] >
+<!DOCTYPE Something >
+</typing>
+
+<p>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:</p>
+
+<typing>
+local x = xml.convert(somestring)
+</typing>
+
+<p>An optional second boolean argument tells this function not to create a root
+element.</p>
+--ldx]]--
+
+xml.strip_cm_and_dt = false -- an extra global flag, in case we have many includes
+
+-- not just one big nested table capture (lpeg overflow)
+
+local nsremap, resolvens = 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 cleanup and #value > 0 then
+ value = cleanup(value) -- new
+ end
+ 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
+ dt[0] = top
+ if toclose.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)
+ if at.xmlns then
+ remove(xmlns)
+ end
+ at = { }
+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: " .. gsub(txt,"([ \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)
+
+local 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("<!ELEMENT") * (1-close)^0 * close
+local entitydoctype = optionalspace * P("<!ENTITY") * somespace * (doctypename * somespace * value)/entity * optionalspace * close
+local publicdoctype = doctypename * somespace * P("PUBLIC") * somespace * value * somespace * value * somespace
+local systemdoctype = doctypename * somespace * P("SYSTEM") * somespace * value * somespace
+local definitiondoctype= doctypename * somespace * beginset * P(elementdoctype + entitydoctype)^0 * optionalspace * endset
+local simpledoctype = (1-close)^1 -- * balanced^0
+local somedoctype = C((somespace * (publicdoctype + systemdoctype + definitiondoctype + simpledoctype) * optionalspace)^0)
+
+local instruction = (spacing * begininstruction * someinstruction * endinstruction) / function(...) add_special("@pi@",...) end
+local comment = (spacing * begincomment * somecomment * endcomment ) / function(...) add_special("@cm@",...) end
+local cdata = (spacing * begincdata * somecdata * endcdata ) / function(...) add_special("@cd@",...) end
+local doctype = (spacing * begindoctype * somedoctype * enddoctype ) / function(...) add_special("@dt@",...) end
+
+-- nicer but slower:
+--
+-- local instruction = (lpeg.Cc("@pi@") * spacing * begininstruction * someinstruction * endinstruction) / add_special
+-- local comment = (lpeg.Cc("@cm@") * spacing * begincomment * somecomment * endcomment ) / add_special
+-- local cdata = (lpeg.Cc("@cd@") * spacing * begincdata * somecdata * endcdata ) / add_special
+-- local doctype = (lpeg.Cc("@dt@") * spacing * begindoctype * somedoctype * enddoctype ) / add_special
+
+local trailer = space^0 * (justtext/set_message)^0
+
+-- comment + emptyelement + text + cdata + instruction + V("parent"), -- 6.5 seconds on 40 MB database file
+-- text + comment + emptyelement + cdata + instruction + V("parent"), -- 5.8
+-- text + V("parent") + emptyelement + comment + cdata + instruction, -- 5.5
+
+local grammar = P { "preamble",
+ preamble = utfbom^0 * instruction^0 * (doctype + comment + instruction)^0 * V("parent") * trailer,
+ parent = beginelement * V("children")^0 * endelement,
+ children = text + V("parent") + emptyelement + comment + cdata + instruction,
+}
+
+-- todo: xml.new + properties like entities and strip and such (store in root)
+
+function xml.convert(data, no_root, strip_cm_and_dt, given_entities) -- maybe use table met k/v (given_entities may disapear)
+ strip = strip_cm_and_dt or xml.strip_cm_and_dt
+ stack, top, at, xmlns, errorstr, result, entities = {}, {}, {}, {}, nil, nil, given_entities or {}
+ stack[#stack+1] = top
+ top.dt = { }
+ dt = top.dt
+ if not data or data == "" then
+ errorstr = "empty xml file"
+ elseif not grammar:match(data) then
+ errorstr = "invalid xml file"
+ else
+ errorstr = ""
+ end
+ if errorstr and errorstr ~= "" then
+ result = { dt = { { ns = "", tg = "error", dt = { errorstr }, at={}, er = true } }, error = true }
+ setmetatable(stack, mt)
+ if xml.error_handler then xml.error_handler("load",errorstr) end
+ else
+ result = stack[1]
+ end
+ if not no_root then
+ result = { special = true, ns = "", tg = '@rt@', dt = result.dt, at={}, entities = entities }
+ setmetatable(result, mt)
+ local rdt = result.dt
+ for k=1,#rdt do
+ local v = rdt[k]
+ if type(v) == "table" and not v.special then -- always table -)
+ result.ri = k -- rootindex
+ break
+ end
+ end
+ end
+ return result
+end
+
+--[[ldx--
+<p>Packaging data in an xml like table is done with the following
+function. Maybe it will go away (when not used).</p>
+--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 logs.report) or print
+
+--[[ldx--
+<p>We cannot load an <l n='lpeg'/> from a filehandle so we need to load
+the whole file first. The function accepts a string representing
+a filename or a file handle.</p>
+--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--
+<p>When we inject new elements, we need to convert strings to
+valid trees, which is what the next function does.</p>
+--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--
+<p>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!</p>
+--ldx]]--
+
+local 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
+
+--[[ldx--
+<p>In <l n='context'/> 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.</p>
+--ldx]]--
+
+-- todo: add <?xml version='1.0' standalone='yes'?> 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
+ local etg, edt = e.tg, e.dt
+ local spc = specialconverter and specialconverter[etg]
+ if spc then
+ 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("<!--%s-->",edt[1]))
+ handle("<!--" .. edt[1] .. "-->")
+ elseif etg == "@cd@" then
+ -- handle(format("<![CDATA[%s]]>",edt[1]))
+ handle("<![CDATA[" .. edt[1] .. "]]>")
+ elseif etg == "@dt@" then
+ -- handle(format("<!DOCTYPE %s>",edt[1]))
+ handle("<!DOCTYPE " .. edt[1] .. ">")
+ 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 next, eat do
+ ats[#ats+1] = format('%s=%q',k,attributeconverter(v))
+ end
+ else
+ for k,v in next, eat do
+ ats[#ats+1] = format('%s=%q',k,v)
+ end
+ end
+ end
+ if ern and trace_remap and ern ~= ens then
+ 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
+ local ei = edt[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
+ else
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
+ end
+ 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
+ local ei = e[i]
+ if type(ei) == "string" then
+ if textconverter then
+ handle(textconverter(ei))
+ else
+ handle(ei)
+ end
+ else
+ serialize(ei,handle,textconverter,attributeconverter,specialconverter,nocommands)
+ end
+ 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=1,#dt do
+ local v = dt[k]
+ if type(v) == "table" and v.special and v.tg == "@pi" and find(v.dt,"xml.*version=") then
+ found = true
+ break
+ end
+ end
+ if not found then
+ insert(dt, 1, { special=true, ns="", tg="@pi@", dt = { "xml version='1.0' standalone='yes'"} } )
+ insert(dt, 2, "\n" )
+ end
+ end
+end
+
+--[[ldx--
+<p>At the cost of some 25% runtime overhead you can first convert the tree to a string
+and then handle the lot.</p>
+--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) -- brrr, slow (direct printing is faster)
+ return concat(result,"")
+ end
+ end
+ return ""
+end
+
+--[[ldx--
+<p>The next function operated on the content only and needs a handle function
+that accepts a string.</p>
+--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--
+<p>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):</p>
+
+<lines>
+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
+</lines>
+
+<p>The save function is given below.</p>
+--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--
+<p>A few helpers:</p>
+--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
+
+function xml.isempty(root, pattern)
+ if pattern == "" or pattern == "*" then
+ pattern = nil
+ end
+ if pattern then
+ -- todo
+ return false
+ else
+ return not root or not root.dt or #root.dt == 0 or root.dt == ""
+ end
+end
+
+--[[ldx--
+<p>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:</p>
+
+<typing>
+dt[k] = xml.empty() or xml.empty(dt,k)
+</typing>
+--ldx]]--
+
+function xml.empty(dt,k)
+ if dt and k then
+ dt[k] = ""
+ return dt[k]
+ else
+ return ""
+ end
+end
+
+--[[ldx--
+<p>The next helper assigns a tree (or string). Usage:</p>
+
+<typing>
+dt[k] = xml.assign(root) or xml.assign(dt,k,root)
+</typing>
+--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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-pth'] = {
+ 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"
+}
+
+local concat, remove, insert = table.concat, table.remove, table.insert
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, lower, gmatch, gsub, find = string.format, string.lower, string.gmatch, string.gsub, string.find
+
+--[[ldx--
+<p>This module can be used stand alone but also inside <l n='mkiv'/> in
+which case it hooks into the tracker code. Therefore we provide a few
+functions that set the tracers. Here we overload a previously defined
+function.</p>
+--ldx]]--
+
+local trace_lpath = false
+
+if trackers then
+ trackers.register("xml.lpath", function(v) trace_lpath = v end)
+end
+
+local settrace = xml.settrace -- lxml-tab
+
+function xml.settrace(str,value)
+ if str == "lpath" then
+ trace_lpath = value or false
+ else
+ settrace(str,value) -- lxml-tab
+ end
+end
+
+--[[ldx--
+<p>We've now arrived at an intersting part: accessing the tree using a subset
+of <l n='xpath'/> and since we're not compatible we call it <l n='lpath'/>. We
+will explain more about its usage in other documents.</p>
+--ldx]]--
+
+local lpathcalls = 0 -- statistics
+local lpathcached = 0 -- statistics
+
+xml.functions = xml.functions or { }
+xml.expressions = xml.expressions or { }
+
+local functions = xml.functions
+local expressions = xml.expressions
+
+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",
+}
+
+-- a rather dumb lpeg
+
+local P, S, R, C, V, Cc = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.Cc
+
+-- instead of using functions we just parse a few names which saves a call
+-- later on
+
+local lp_position = P("position()") / "ps"
+local lp_index = P("index()") / "id"
+local lp_text = P("text()") / "tx"
+local lp_name = P("name()") / "(ns~='' and ns..':'..tg)" -- "((rt.ns~='' and rt.ns..':'..rt.tg) or '')"
+local lp_tag = P("tag()") / "tg" -- (rt.tg or '')
+local lp_ns = P("ns()") / "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_lua_function = C(R("az","AZ","--","__")^1 * (P(".") * R("az","AZ","--","__")^1)^1) * P("(") / function(t) -- todo: better . handling
+ return t .. "("
+end
+
+local lp_function = C(R("az","AZ","--","__")^1) * P("(") / function(t) -- todo: better . handling
+ if expressions[t] then
+ return "expressions." .. t .. "("
+ else
+ return "expressions.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) -- lpeg.P{"("*C(((1-S("()"))+V(1))^0)*")"}
+
+-- if we use a dedicated namespace then we don't need to pass rt and k
+
+local lp_special = (C(P("name")+P("text")+P("tag"))) * value / function(t,s)
+ if expressions[t] then
+ if s then
+ return "expressions." .. t .. "(r,k," .. s ..")"
+ else
+ return "expressions." .. t .. "(r,k)"
+ end
+ else
+ return "expressions.error(" .. t .. ")"
+ end
+end
+
+local converter = lpeg.Cs ( (
+ lp_position +
+ lp_index +
+ lp_text + lp_name + -- fast one
+ lp_special +
+ lp_noequal + lp_doequal +
+ lp_attribute +
+ lp_lua_function +
+ lp_function +
+1 )^1 )
+
+-- expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1
+
+local template = [[
+ return function(expressions,r,d,k,e,dt,ns,tg,id,ps)
+ local at, tx = e.at or { }, dt[1] or ""
+ return %s
+ end
+]]
+
+local function make_expression(str)
+ str = converter:match(str)
+ return str, loadstring(format(template,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 = 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 + -- brrr, not here !
+ 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 +
+ many + any +
+ 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
+ insert(map, 1, { 16 })
+ end
+ -- print(gsub(table.serialize(map),"[ \n]+"," "))
+ return map
+ end
+ end
+end
+
+local cache = { }
+
+function xml.lpath(pattern,trace)
+ lpathcalls = lpathcalls + 1
+ if type(pattern) == "string" then
+ local result = cache[pattern]
+ if result == nil then -- can be false which is valid -)
+ result = compose(pattern)
+ cache[pattern] = result
+ lpathcached = lpathcached + 1
+ end
+ if trace or trace_lpath then
+ xml.lshow(result)
+ end
+ return result
+ else
+ return pattern
+ end
+end
+
+function xml.cached_patterns()
+ return cache
+end
+
+-- we run out of locals (limited to 200)
+--
+-- local fallbackreport = (texio and texio.write) or io.write
+
+function xml.lshow(pattern,report)
+-- report = report or fallbackreport
+ report = report or (texio and texio.write) or io.write
+ 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=1,#lp do
+ local v = lp[k]
+ 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
+ local report = (type(t[#t]) == "function" and t[#t]) or (texio and texio.write) or io.write
+ if e == nil then
+ report("<!-- no element -->\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
+
+--[[ldx--
+<p>An <l n='lpath'/> 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:</p>
+
+<lines>
+<t>r</t> : the root element of the data table
+<t>d</t> : the data table of the result
+<t>t</t> : the index in the data table of the result
+</lines>
+
+<p> Access to the root and data table makes it possible to construct insert and delete
+functions.</p>
+--ldx]]--
+
+local functions = xml.functions
+local expressions = xml.expressions
+
+expressions.contains = string.find
+expressions.find = string.find
+expressions.upper = string.upper
+expressions.lower = string.lower
+expressions.number = tonumber
+expressions.boolean = toboolean
+
+expressions.oneof = function(s,...) -- slow
+ local t = {...} for i=1,#t do if s == t[i] then return true end end return false
+end
+
+expressions.error = function(str)
+ xml.error_handler("unknown function in lpath expression",str or "?")
+ 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(d,k,n) -- ns + tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 1
+ end
+ end
+ end
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ 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
+
+functions.tag = function(d,k,n) -- only tg
+ local found = false
+ n = n or 0
+ if not k then
+ -- not found
+ elseif n == 0 then
+ local dk = d[k]
+ found = dk and (type(dk) == "table") and dk
+ elseif n < 0 then
+ for i=k-1,1,-1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == -1 then
+ found = di
+ break
+ else
+ n = n + 1
+ end
+ end
+ end
+ else
+ for i=k+1,#d,1 do
+ local di = d[i]
+ if type(di) == "table" then
+ if n == 1 then
+ found = di
+ break
+ else
+ n = n - 1
+ end
+ end
+ end
+ end
+ return (found and found.tg) or ""
+end
+
+expressions.text = functions.text
+expressions.name = functions.name
+expressions.tag = functions.tag
+
+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
+ local hsh = { } -- this will slooow down the lot
+ 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
+ -- we can optimize this for simple searches, but it probably does not pay off
+ hsh[tg] = (hsh[tg] or 0) + 1
+ 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](expressions,root,rootdt,k,e,edt,ns,tg,idx,hsh[tg] or 1)
+ 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
+
+--[[ldx--
+<p>Next come all kind of locators and manipulators. The most generic function here
+is <t>xml.filter(root,pattern)</t>. All registers functions in the filters namespace
+can be path of a search path, as in:</p>
+
+<typing>
+local r, d, k = xml.filter(root,"/a/b/c/position(4)"
+</typing>
+--ldx]]--
+
+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[gsub(arguments,"^([\"\'])(.*)%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--
+<p>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.</p>
+--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 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--
+<p>The following functions collect elements and texts.</p>
+--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--
+<p>Often using an iterators looks nicer in the code than passing handler
+functions. The <l n='lua'/> book describes how to use coroutines for that
+purpose (<url href='http://www.lua.org/pil/9.3.html'/>). This permits
+code like:</p>
+
+<typing>
+for r, d, k in xml.elements(xml.load('text.xml'),"title") do
+ print(d[k])
+end
+</typing>
+
+<p>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:</p>
+
+<typing>
+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
+</typing>
+
+<p>We use the function variants in the filters.</p>
+--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--
+<p>We've now arrives at the functions that manipulate the tree.</p>
+--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
+ insert(d,k,element) -- untested
+--~ elseif element.dt then
+--~ for _,v in ipairs(element.dt) do -- i added
+--~ insert(d,k,v)
+--~ k = k + 1
+--~ end
+--~ end
+ else
+ local edt = element.dt
+ if edt then
+ for i=1,#edt do
+ insert(d,k,edt[i])
+ k = k + 1
+ end
+ 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] = 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 gmatch(attribute or "href","([^|]+)") 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, nolines) -- strips all leading and trailing space !
+ 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" then
+
+ if str == "" then
+ -- stripped
+ else
+ if nolines then
+ str = gsub(str,"[ \n\r\t]+"," ")
+ end
+ if str == "" then
+ -- stripped
+ else
+ t[#t+1] = str
+ end
+ end
+ else
+ t[#t+1] = str
+ end
+ end
+ d[k].dt = t
+ end
+ end)
+end
+
+local function rename_space(root, oldspace, newspace) -- fast variant
+ local ndt = #root.dt
+ 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_space(edt, oldspace, newspace)
+ end
+ end
+ end
+end
+
+xml.rename_space = rename_space
+
+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
+
+--[[ldx--
+<p>Here are a few synonyms.</p>
+--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--
+<p>The following helper functions best belong to the <t>lmxl-ini</t>
+module. Some are here because we need then in the <t>mk</t>
+document and other manuals, others came up when playing with
+this module. Since this module is also used in <l n='mtxrun'/> we've
+put them here instead of loading mode modules there then needed.</p>
+--ldx]]--
+
+function xml.gsub(t,old,new)
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "string" then
+ dt[k] = gsub(v,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 = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
+
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
+
+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("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> 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
+
+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
+
+function xml.statistics()
+ return {
+ lpathcalls = lpathcalls,
+ lpathcached = lpathcached,
+ }
+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([[
+--~ <a>
+--~ <b n='01'>01</b>
+--~ <b n='02'>02</b>
+--~ <b n='03'>03</b>
+--~ <b n='04'>OK</b>
+--~ <b n='05'>05</b>
+--~ <b n='06'>06</b>
+--~ <b n='07'>ALSO OK</b>
+--~ </a>
+--~ ]])
+
+--~ xml.settrace("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 = [[
+--~ <?xml version="1.0" encoding="utf-8"?>
+--~ <story line='mojca'>
+--~ <windows>my secret</mouse>
+--~ </story>
+--~ ]]
+
+--~ x = xml.convert([[
+--~ <a><b n='01'>01</b><b n='02'>02</b><x>xx</x><b n='03'>03</b><b n='04'>OK</b></a>
+--~ ]])
+--~ 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)"))
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-ent'] = {
+ 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"
+}
+
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub, find = string.format, string.gsub, string.find
+local utfchar = unicode.utf8.char
+
+--[[ldx--
+<p>We provide (at least here) two entity handlers. The more extensive
+resolver consults a hash first, tries to convert to <l n='utf'/> next,
+and finaly calls a handler when defines. When this all fails, the
+original entity is returned.</p>
+--ldx]]--
+
+xml.entities = xml.entities or { } -- xml.entity_handler == function
+
+function xml.entity_handler(e)
+ return format("[%s]",e)
+end
+
+local function toutf(s)
+ return utfchar(tonumber(s,16))
+end
+
+local 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 find(dk,"&#x.-;") then
+ d[k] = gsub(dk,"&#x(.-);",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 find(e,"^#x") then
+ return utfchar(tonumber(e:sub(3),16))
+ elseif find(e,"^#") then
+ return utfchar(tonumber(e:sub(2)))
+ else
+ local ee = xml.entities[e] -- we cannot shortcut this one (is reloaded)
+ if ee then
+ return ee
+ else
+ local h = xml.entity_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 find(dk,"&.-;") then
+ d[k] = gsub(dk,"&(.-);",resolve)
+ end
+ else
+ resolve_entities(dk)
+ end
+ end
+ end
+end
+
+xml.resolve_entities = resolve_entities
+
+function xml.utfize_text(str)
+ if find(str,"&#") then
+ return (gsub(str,"&#x(.-);",toutf))
+ else
+ return str
+ end
+end
+
+function xml.resolve_text_entities(str) -- maybe an lpeg. maybe resolve inline
+ if find(str,"&") then
+ return (gsub(str,"&(.-);",resolve))
+ else
+ return str
+ end
+end
+
+function xml.show_text_entities(str)
+ if find(str,"&") then
+ return (gsub(str,"&(.-);","[%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 next, documententities do
+ allentities[k] = v
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['lxml-mis'] = {
+ 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"
+}
+
+local concat = table.concat
+local type, next, tonumber, tostring, setmetatable, loadstring = type, next, tonumber, tostring, setmetatable, loadstring
+local format, gsub = string.format, string.gsub
+
+--[[ldx--
+<p>The following helper functions best belong to the <t>lmxl-ini</t>
+module. Some are here because we need then in the <t>mk</t>
+document and other manuals, others came up when playing with
+this module. Since this module is also used in <l n='mtxrun'/> we've
+put them here instead of loading mode modules there then needed.</p>
+--ldx]]--
+
+function xml.gsub(t,old,new)
+ local dt = t.dt
+ if dt then
+ for k=1,#dt do
+ local v = dt[k]
+ if type(v) == "string" then
+ dt[k] = gsub(v,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 = { ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['"'] = '&quot;' }
+--~ xml.unescapes = { } for k,v in pairs(xml.escapes) do xml.unescapes[v] = k end
+
+--~ function xml.escaped (str) return (gsub(str,"(.)" , xml.escapes )) end
+--~ function xml.unescaped(str) return (gsub(str,"(&.-;)", xml.unescapes)) end
+--~ function xml.cleansed (str) return (gsub(str,"<.->" , '' )) end -- "%b<>"
+
+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("<")/"&lt;" + S(">")/"&gt;" + S("&")/"&amp;" + 1)^0)
+local normal = (1 - S("<&>"))^0
+local special = P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;"
+local escaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 1000 * "oeps&lt; oeps&gt; oeps&amp;" : gsub:lpeg == 0153:0280:0151:0080 (last one by roberto)
+
+-- unescaped = Cs((S("&lt;")/"<" + S("&gt;")/">" + S("&amp;")/"&" + 1)^0)
+-- unescaped = Cs((((P("&")/"") * (P("lt")/"<" + P("gt")/">" + P("amp")/"&") * (P(";")/"")) + 1)^0)
+local normal = (1 - S"&")^0
+local special = P("&lt;")/"<" + P("&gt;")/">" + P("&amp;")/"&"
+local unescaped = Cs(normal * (special * normal)^0)
+
+-- 100 * 5000 * "oeps <oeps bla='oeps' foo='bar'> oeps </oeps> oeps " : gsub:lpeg == 623:501 msec (short tags, less difference)
+
+local cleansed = Cs(((P("<") * (1-P(">"))^0 * P(">"))/"" + 1)^0)
+
+xml.escaped_pattern = escaped
+xml.unescaped_pattern = unescaped
+xml.cleansed_pattern = cleansed
+
+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
+
+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
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-tra'] = {
+ 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"
+}
+
+-- the <anonymous> tag is kind of generic and used for functions that are not
+-- bound to a variable, like node.new, node.copy etc (contrary to for instance
+-- node.has_attribute which is bound to a has_attribute local variable in mkiv)
+
+debugger = debugger or { }
+
+local counters = { }
+local names = { }
+local getinfo = debug.getinfo
+local format, find, lower, gmatch = string.format, string.find, string.lower, string.gmatch
+
+-- one
+
+local function hook()
+ local f = getinfo(2,"f").func
+ local n = getinfo(2,"Sn")
+-- if n.what == "C" and n.name then print (n.namewhat .. ': ' .. n.name) end
+ if f then
+ local cf = counters[f]
+ if cf == nil then
+ counters[f] = 1
+ names[f] = n
+ else
+ counters[f] = cf + 1
+ end
+ end
+end
+local function getname(func)
+ local n = names[func]
+ if n then
+ if n.what == "C" then
+ return n.name or '<anonymous>'
+ else
+ -- source short_src linedefined what name namewhat nups func
+ local name = n.name or n.namewhat or n.what
+ if not name or name == "" then name = "?" end
+ return format("%s : %s : %s", n.short_src or "unknown source", n.linedefined or "--", name)
+ end
+ else
+ return "unknown"
+ end
+end
+function debugger.showstats(printer,threshold)
+ printer = printer or texio.write or print
+ threshold = threshold or 0
+ local total, grandtotal, functions = 0, 0, 0
+ printer("\n") -- ugly but ok
+ -- table.sort(counters)
+ for func, count in pairs(counters) do
+ if count > threshold then
+ local name = getname(func)
+ if not name:find("for generator") then
+ printer(format("%8i %s", count, name))
+ total = total + count
+ end
+ end
+ grandtotal = grandtotal + count
+ functions = functions + 1
+ end
+ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+end
+
+-- two
+
+--~ local function hook()
+--~ local n = getinfo(2)
+--~ if n.what=="C" and not n.name then
+--~ local f = tostring(debug.traceback())
+--~ local cf = counters[f]
+--~ if cf == nil then
+--~ counters[f] = 1
+--~ names[f] = n
+--~ else
+--~ counters[f] = cf + 1
+--~ end
+--~ end
+--~ end
+--~ function debugger.showstats(printer,threshold)
+--~ printer = printer or texio.write or print
+--~ threshold = threshold or 0
+--~ local total, grandtotal, functions = 0, 0, 0
+--~ printer("\n") -- ugly but ok
+--~ -- table.sort(counters)
+--~ for func, count in pairs(counters) do
+--~ if count > threshold then
+--~ printer(format("%8i %s", count, func))
+--~ total = total + count
+--~ end
+--~ grandtotal = grandtotal + count
+--~ functions = functions + 1
+--~ end
+--~ printer(format("functions: %s, total: %s, grand total: %s, threshold: %s\n", functions, total, grandtotal, threshold))
+--~ end
+
+-- rest
+
+function debugger.savestats(filename,threshold)
+ local f = io.open(filename,'w')
+ if f then
+ debugger.showstats(function(str) f:write(str) end,threshold)
+ f:close()
+ end
+end
+
+function debugger.enable()
+ debug.sethook(hook,"c")
+end
+
+function debugger.disable()
+ debug.sethook()
+--~ counters[debug.getinfo(2,"f").func] = nil
+end
+
+function debugger.tracing()
+ local n = tonumber(os.env['MTX.TRACE.CALLS']) or tonumber(os.env['MTX_TRACE_CALLS']) or 0
+ if n > 0 then
+ function debugger.tracing() return true end ; return true
+ else
+ function debugger.tracing() return false end ; return false
+ end
+end
+
+--~ debugger.enable()
+
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+--~ print(math.sin(1*.5))
+
+--~ debugger.disable()
+
+--~ print("")
+--~ debugger.showstats()
+--~ print("")
+--~ debugger.showstats(print,3)
+
+trackers = trackers or { }
+
+local data, done = { }, { }
+
+local function set(what,value)
+ if type(what) == "string" then
+ what = aux.settings_to_array(what)
+ end
+ for i=1,#what do
+ local w = what[i]
+ for d, f in next, data do
+ if done[d] then
+ -- prevent recursion due to wildcards
+ elseif find(d,w) then
+ done[d] = true
+ for i=1,#f do
+ f[i](value)
+ end
+ end
+ end
+ end
+end
+
+local function reset()
+ for d, f in next, data do
+ for i=1,#f do
+ f[i](false)
+ end
+ end
+end
+
+function trackers.register(what,...)
+ what = lower(what)
+ local w = data[what]
+ if not w then
+ w = { }
+ data[what] = w
+ end
+ for _, fnc in next, { ... } do
+ local typ = type(fnc)
+ if typ == "function" then
+ w[#w+1] = fnc
+ elseif typ == "string" then
+ w[#w+1] = function(value) set(fnc,value,nesting) end
+ end
+ end
+end
+
+function trackers.enable(what)
+ done = { }
+ set(what,true)
+end
+
+function trackers.disable(what)
+ done = { }
+ if not what or what == "" then
+ trackers.reset(what)
+ else
+ set(what,false)
+ end
+end
+
+function trackers.reset(what)
+ done = { }
+ reset()
+end
+
+function trackers.list() -- pattern
+ local list = table.sortedkeys(data)
+ local user, system = { }, { }
+ for l=1,#list do
+ local what = list[l]
+ if find(what,"^%*") then
+ system[#system+1] = what
+ else
+ user[#user+1] = what
+ end
+ end
+ return user, system
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-env'] = {
+ 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"
+}
+
+-- A former version provided functionality for non embeded core
+-- scripts i.e. runtime library loading. Given the amount of
+-- Lua code we use now, this no longer makes sense. Much of this
+-- evolved before bytecode arrays were available and so a lot of
+-- code has disappeared already.
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+local format = string.format
+
+-- precautions
+
+os.setlocale(nil,nil) -- useless feature and even dangerous in luatex
+
+function os.setlocale()
+ -- no way you can mess with it
+end
+
+-- dirty tricks
+
+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
+
+if profiler and os.env["MTX_PROFILE_RUN"] == "YES" then
+ profiler.start("luatex-profile.log")
+end
+
+-- environment
+
+environment = environment or { }
+environment.arguments = { }
+environment.files = { }
+environment.sortedflags = nil
+
+if not environment.jobname or environment.jobname == "" then if tex then environment.jobname = tex.jobname end end
+if not environment.version or environment.version == "" then environment.version = "unknown" end
+if not environment.jobname then environment.jobname = "unknown" end
+
+function environment.initialize_arguments(arg)
+ local arguments, files = { }, { }
+ environment.arguments, environment.files, environment.sortedflags = arguments, files, nil
+ for index, argument in pairs(arg) do
+ if index > 0 then
+ local flag, value = argument:match("^%-+(.+)=(.-)$")
+ if flag then
+ arguments[flag] = string.unquote(value or "")
+ else
+ flag = argument:match("^%-+(.+)")
+ if flag then
+ arguments[flag] = true
+ else
+ files[#files+1] = argument
+ end
+ end
+ end
+ end
+ environment.ownname = environment.ownname or arg[0] or 'unknown.lua'
+end
+
+function environment.setargument(name,value)
+ environment.arguments[name] = value
+end
+
+-- todo: defaults, better checks e.g on type (boolean versus string)
+--
+-- tricky: too many hits when we support partials unless we add
+-- a registration of arguments so from now on we have 'partial'
+
+function environment.argument(name,partial)
+ local arguments, sortedflags = environment.arguments, environment.sortedflags
+ if arguments[name] then
+ return arguments[name]
+ elseif partial then
+ if not sortedflags then
+ sortedflags = { }
+ for _,v in pairs(table.sortedkeys(arguments)) do
+ sortedflags[#sortedflags+1] = "^" .. v
+ end
+ environment.sortedflags = sortedflags
+ end
+ -- example of potential clash: ^mode ^modefile
+ for _,v in ipairs(sortedflags) do
+ if name:find(v) then
+ return 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,noquote)
+ arg = arg or environment.original_arguments
+ if noquote and #arg == 1 then
+ local a = arg[1]
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ return a
+ elseif next(arg) then
+ local result = { }
+ for _,a in ipairs(arg) do -- ipairs 1 .. #n
+ a = resolvers.resolve(a)
+ a = a:unquote()
+ a = a:gsub('"','\\"') -- tricky
+ if a:find(" ") then
+ result[#result+1] = a:quote()
+ else
+ result[#result+1] = a
+ end
+ end
+ return table.join(result," ")
+ else
+ return ""
+ end
+end
+
+if arg then
+
+ -- new, reconstruct quoted snippets (maybe better just remnove the " then and add them later)
+ local newarg, instring = { }, false
+
+ for index, argument in ipairs(arg) do
+ if argument:find("^\"") then
+ newarg[#newarg+1] = argument:gsub("^\"","")
+ if not argument:find("\"$") then
+ instring = true
+ end
+ elseif argument:find("\"$") then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument:gsub("\"$","")
+ instring = false
+ elseif instring then
+ newarg[#newarg] = newarg[#newarg] .. " " .. argument
+ else
+ newarg[#newarg+1] = argument
+ end
+ end
+ for i=1,-5,-1 do
+ newarg[i] = arg[i]
+ end
+
+ environment.initialize_arguments(newarg)
+ environment.original_arguments = newarg
+ environment.raw_arguments = arg
+
+ arg = { } -- prevent duplicate handling
+
+end
+
+-- weird place ... depends on a not yet loaded module
+
+function environment.texfile(filename)
+ return resolvers.find_file(filename,'tex')
+end
+
+function environment.luafile(filename)
+ local resolved = resolvers.find_file(filename,'tex') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ resolved = resolvers.find_file(filename,'texmfscripts') or ""
+ if resolved ~= "" then
+ return resolved
+ end
+ return resolvers.find_file(filename,'luatexlibs') or ""
+end
+
+environment.loadedluacode = loadfile -- can be overloaded
+
+--~ function environment.loadedluacode(name)
+--~ if os.spawn("texluac -s -o texluac.luc " .. name) == 0 then
+--~ local chunk = loadstring(io.loaddata("texluac.luc"))
+--~ os.remove("texluac.luc")
+--~ return chunk
+--~ else
+--~ environment.loadedluacode = loadfile -- can be overloaded
+--~ return loadfile(name)
+--~ end
+--~ end
+
+function environment.luafilechunk(filename) -- used for loading lua bytecode in the format
+ filename = file.replacesuffix(filename, "lua")
+ local fullname = environment.luafile(filename)
+ if fullname and fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading file %s", fullname)
+ end
+ return environment.loadedluacode(fullname)
+ else
+ if trace_verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ return nil
+ end
+end
+
+-- the next ones can use the previous ones / combine
+
+function environment.loadluafile(filename, version)
+ local lucname, luaname, chunk
+ local basename = file.removesuffix(filename)
+ if basename == filename then
+ lucname, luaname = basename .. ".luc", basename .. ".lua"
+ else
+ lucname, luaname = nil, basename -- forced suffix
+ end
+ -- when not overloaded by explicit suffix we look for a luc file first
+ local fullname = (lucname and environment.luafile(lucname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ end
+ if chunk then
+ assert(chunk)()
+ if version then
+ -- we check of the version number of this chunk matches
+ local v = version -- can be nil
+ if modules and modules[filename] then
+ v = modules[filename].version -- new method
+ elseif versions and versions[filename] then
+ v = versions[filename] -- old method
+ end
+ if v == version then
+ return true
+ else
+ if trace_verbose then
+ logs.report("fileio","version mismatch for %s: lua=%s, luc=%s", filename, v, version)
+ end
+ environment.loadluafile(filename)
+ end
+ else
+ return true
+ end
+ end
+ fullname = (luaname and environment.luafile(luaname)) or ""
+ if fullname ~= "" then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fullname)
+ end
+ chunk = loadfile(fullname) -- this way we don't need a file exists check
+ if not chunk then
+ if verbose then
+ logs.report("fileio","unknown file %s", filename)
+ end
+ else
+ assert(chunk)()
+ return true
+ end
+ end
+ return false
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['trac-inf'] = {
+ 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"
+}
+
+local format = string.format
+
+local statusinfo, n, registered = { }, 0, { }
+
+statistics = statistics or { }
+
+statistics.enable = true
+statistics.threshold = 0.05
+
+-- timing functions
+
+local clock = os.gettimeofday or os.clock
+
+local notimer
+
+function statistics.hastimer(instance)
+ return instance and instance.starttime
+end
+
+function statistics.starttiming(instance)
+ if not instance then
+ notimer = { }
+ instance = notimer
+ end
+ local it = instance.timing
+ if not it then
+ it = 0
+ end
+ if it == 0 then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+ instance.timing = it + 1
+end
+
+function statistics.stoptiming(instance, report)
+ if not instance then
+ instance = notimer
+ end
+ if instance then
+ local it = instance.timing
+ if it > 1 then
+ instance.timing = it - 1
+ else
+ 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
+ statistics.report("load time %0.3f",loadtime)
+ end
+ instance.timing = 0
+ return loadtime
+ end
+ end
+ end
+ return 0
+end
+
+function statistics.elapsedtime(instance)
+ if not instance then
+ instance = notimer
+ end
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function statistics.elapsedindeed(instance)
+ if not instance then
+ instance = notimer
+ end
+ local t = (instance and instance.loadtime) or 0
+ return t > statistics.threshold
+end
+
+-- general function
+
+function statistics.register(tag,fnc)
+ if statistics.enable and type(fnc) == "function" then
+ local rt = registered[tag] or (#statusinfo + 1)
+ statusinfo[rt] = { tag, fnc }
+ registered[tag] = rt
+ if #tag > n then n = #tag end
+ end
+end
+
+function statistics.show(reporter)
+ if statistics.enable then
+ if not reporter then reporter = function(tag,data,n) texio.write_nl(tag .. " " .. data) end end
+ -- this code will move
+ local register = statistics.register
+ register("luatex banner", function()
+ return string.lower(status.banner)
+ end)
+ register("control sequences", function()
+ return format("%s of %s", status.cs_count, status.hash_size+status.hash_extra)
+ end)
+ register("callbacks", function()
+ local total, indirect = status.callbacks or 0, status.indirect_callbacks or 0
+ return format("direct: %s, indirect: %s, total: %s", total-indirect, indirect, total)
+ end)
+ register("current memory usage", statistics.memused)
+ register("runtime",statistics.runtime)
+-- --
+ for i=1,#statusinfo do
+ local s = statusinfo[i]
+ local r = s[2]()
+ if r then
+ reporter(s[1],r,n)
+ end
+ end
+ texio.write_nl("") -- final newline
+ statistics.enable = false
+ end
+end
+
+function statistics.show_job_stat(tag,data,n)
+ texio.write_nl(format("%-15s: %s - %s","mkiv lua stats",tag:rpadd(n," "),data))
+end
+
+function statistics.memused() -- no math.round yet -)
+ local round = math.round or math.floor
+ return format("%s MB (ctx: %s MB)",round(collectgarbage("count")/1000), round(status.luastate_bytes/1000000))
+end
+
+if statistics.runtime then
+ -- already loaded and set
+elseif luatex and luatex.starttime then
+ statistics.starttime = luatex.starttime
+ statistics.loadtime = 0
+ statistics.timing = 0
+else
+ statistics.starttiming(statistics)
+end
+
+function statistics.runtime()
+ statistics.stoptiming(statistics)
+ return statistics.formatruntime(statistics.elapsedtime(statistics))
+end
+
+function statistics.formatruntime(runtime)
+ return format("%s seconds", statistics.elapsedtime(statistics))
+end
+
+function statistics.timed(action,report)
+ local timer = { }
+ report = report or logs.simple
+ statistics.starttiming(timer)
+ action()
+ statistics.stoptiming(timer)
+ report("total runtime: %s",statistics.elapsedtime(timer))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this is old code that needs an overhaul
+
+local write_nl, write, format = texio.write_nl or print, texio.write or io.write, string.format
+local texcount = tex and tex.count
+
+if texlua then
+ write_nl = print
+ write = io.write
+end
+
+--[[ldx--
+<p>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 <l n='xml'/> structured file. Actually, any logging that
+is hooked into callbacks will be \XML\ by default.</p>
+--ldx]]--
+
+logs = logs or { }
+logs.xml = logs.xml or { }
+logs.tex = logs.tex or { }
+
+--[[ldx--
+<p>This looks pretty ugly but we need to speed things up a bit.</p>
+--ldx]]--
+
+logs.moreinfo = [[
+more information about ConTeXt and the tools that come with it can be found at:
+
+maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
+webpage : http://www.pragma-ade.nl / http://tex.aanhet.net
+wiki : http://contextgarden.net
+]]
+
+logs.levels = {
+ ['error'] = 1,
+ ['warning'] = 2,
+ ['info'] = 3,
+ ['debug'] = 4,
+}
+
+logs.functions = {
+ 'report', 'start', 'stop', 'push', 'pop', 'line', 'direct',
+ 'start_run', 'stop_run',
+ 'start_page_number', 'stop_page_number',
+ 'report_output_pages', 'report_output_log',
+ 'report_tex_stat', 'report_job_stat',
+ 'show_open', 'show_close', 'show_load',
+}
+
+logs.tracers = {
+}
+
+logs.level = 0
+logs.mode = string.lower((os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"))
+
+function logs.set_level(level)
+ logs.level = logs.levels[level] or level
+end
+
+function logs.set_method(method)
+ for _, v in next, logs.functions do
+ logs[v] = logs[method][v] or function() end
+ end
+end
+
+-- tex logging
+
+function logs.tex.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(category .. " | " .. format(fmt,...))
+ else
+ write_nl(category .. " |")
+ end
+end
+
+function logs.tex.line(fmt,...) -- new
+ if fmt then
+ write_nl(format(fmt,...))
+ else
+ write_nl("")
+ end
+end
+
+function logs.tex.start_page_number()
+ local real, user, sub = texcount.realpageno, texcount.userpageno, texcount.subpageno
+ if real > 0 then
+ if user > 0 then
+ if sub > 0 then
+ write(format("[%s.%s.%s",real,user,sub))
+ else
+ write(format("[%s.%s",real,user))
+ end
+ else
+ write(format("[%s",real))
+ end
+ else
+ write("[-")
+ end
+end
+
+function logs.tex.stop_page_number()
+ write("]")
+end
+
+logs.tex.report_job_stat = statistics.show_job_stat
+
+-- xml logging
+
+function logs.xml.report(category,fmt,...) -- new
+ if fmt then
+ write_nl(format("<r category='%s'>%s</r>",category,format(fmt,...)))
+ else
+ write_nl(format("<r category='%s'/>",category))
+ end
+end
+function logs.xml.line(fmt,...) -- new
+ if fmt then
+ write_nl(format("<r>%s</r>",format(fmt,...)))
+ else
+ write_nl("<r/>")
+ end
+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.xml.pop () if logs.level > 0 then tw(" -->" ) end end
+
+function logs.xml.start_run()
+ write_nl("<?xml version='1.0' standalone='yes'?>")
+ write_nl("<job>") -- xmlns='www.pragma-ade.com/luatex/schemas/context-job.rng'
+ write_nl("")
+end
+
+function logs.xml.stop_run()
+ write_nl("</job>")
+end
+
+function logs.xml.start_page_number()
+ write_nl(format("<p real='%s' page='%s' sub='%s'", texcount.realpageno, texcount.userpageno, texcount.subpageno))
+end
+
+function logs.xml.stop_page_number()
+ write("/>")
+ write_nl("")
+end
+
+function logs.xml.report_output_pages(p,b)
+ write_nl(format("<v k='pages' v='%s'/>", p))
+ write_nl(format("<v k='bytes' v='%s'/>", b))
+ write_nl("")
+end
+
+function logs.xml.report_output_log()
+end
+
+function logs.xml.report_tex_stat(k,v)
+ texiowrite_nl("log","<v k='"..k.."'>"..tostring(v).."</v>")
+end
+
+local level = 0
+
+function logs.xml.show_open(name)
+ level = level + 1
+ texiowrite_nl(format("<f l='%s' n='%s'>",level,name))
+end
+
+function logs.xml.show_close(name)
+ texiowrite("</f> ")
+ level = level - 1
+end
+
+function logs.xml.show_load(name)
+ texiowrite_nl(format("<f l='%s' n='%s'/>",level+1,name))
+end
+
+--
+
+local name, banner = 'report', 'context'
+
+local function report(category,fmt,...)
+ if fmt then
+ write_nl(format("%s | %s: %s",name,category,format(fmt,...)))
+ elseif category then
+ write_nl(format("%s | %s",name,category))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+local function simple(fmt,...)
+ if fmt then
+ write_nl(format("%s | %s",name,format(fmt,...)))
+ else
+ write_nl(format("%s |",name))
+ end
+end
+
+function logs.setprogram(_name_,_banner_,_verbose_)
+ name, banner = _name_, _banner_
+ if _verbose_ then
+ trackers.enable("resolvers.verbose")
+ end
+ logs.set_method("tex")
+ logs.report = report -- also used in libraries
+ logs.simple = simple -- only used in scripts !
+ if utils then
+ utils.report = simple
+ end
+ logs.verbose = _verbose_
+end
+
+function logs.setverbose(what)
+ if what then
+ trackers.enable("resolvers.verbose")
+ else
+ trackers.disable("resolvers.verbose")
+ end
+ logs.verbose = what or false
+end
+
+function logs.extendbanner(_banner_,_verbose_)
+ banner = banner .. " | ".. _banner_
+ if _verbose_ ~= nil then
+ logs.setverbose(what)
+ end
+end
+
+logs.verbose = false
+logs.report = logs.tex.report
+logs.simple = logs.tex.report
+
+function logs.reportlines(str) -- todo: <lines></lines>
+ for line in str:gmatch("(.-)[\n\r]") do
+ logs.report(line)
+ end
+end
+
+function logs.reportline() -- for scripts too
+ logs.report()
+end
+
+logs.simpleline = logs.reportline
+
+function logs.help(message,option)
+ logs.report(banner)
+ logs.reportline()
+ logs.reportlines(message)
+ local moreinfo = logs.moreinfo or ""
+ if moreinfo ~= "" and option ~= "nomoreinfo" then
+ logs.reportline()
+ logs.reportlines(moreinfo)
+ end
+end
+
+logs.set_level('error')
+logs.set_method('tex')
+
+function logs.system(whereto,process,jobname,category,...)
+ for i=1,10 do
+ local f = io.open(whereto,"a")
+ if f then
+ f:write(format("%s %s => %s => %s => %s\r",os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)))
+ f:close()
+ break
+ else
+ sleep(0.1)
+ end
+ end
+end
+
+--~ local syslogname = "oeps.xxx"
+--~
+--~ for i=1,10 do
+--~ logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
+--~ end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ version = 1.001,
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+ comment = "companion to luat-lib.tex",
+}
+
+-- After a few years using the code the large luat-inp.lua file
+-- has been split up a bit. In the process some functionality was
+-- dropped:
+--
+-- * support for reading lsr files
+-- * selective scanning (subtrees)
+-- * some public auxiliary functions were made private
+--
+-- 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 lib is multi-purpose and can be loaded again later on so that
+-- additional functionality becomes available. We will split thislogs.report("fileio",
+-- module in components once we're done with prototyping. This is the
+-- first code I wrote for LuaTeX, so it needs some cleanup. Before changing
+-- something in this module one can best check with Taco or Hans first; there
+-- is some nasty trickery going on that relates to traditional kpse support.
+
+-- 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!
+
+local format, gsub, find, lower, upper, match, gmatch = string.format, string.gsub, string.find, string.lower, string.upper, string.match, string.gmatch
+local concat, insert, sortedkeys = table.concat, table.insert, table.sortedkeys
+local next, type = next, type
+
+local trace_locating, trace_detail, trace_verbose = false, false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+trackers.register("resolvers.detail", function(v) trace_detail = v trackers.enable("resolvers.verbose,resolvers.detail") end)
+
+if not resolvers then
+ resolvers = {
+ suffixes = { },
+ formats = { },
+ dangerous = { },
+ suffixmap = { },
+ alternatives = { },
+ locators = { }, -- locate databases
+ hashers = { }, -- load databases
+ generators = { }, -- generate databases
+ }
+end
+
+local resolvers = resolvers
+
+resolvers.locators .notfound = { nil }
+resolvers.hashers .notfound = { nil }
+resolvers.generators.notfound = { nil }
+
+resolvers.cacheversion = '1.0.1'
+resolvers.cnfname = 'texmf.cnf'
+resolvers.luaname = 'texmfcnf.lua'
+resolvers.homedir = os.env[os.platform == "windows" and 'USERPROFILE'] or os.env['HOME'] or '~'
+resolvers.cnfdefault = '{$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}'
+
+local dummy_path_expr = "^!*unset/*$"
+
+local formats = resolvers.formats
+local suffixes = resolvers.suffixes
+local dangerous = resolvers.dangerous
+local suffixmap = resolvers.suffixmap
+local alternatives = resolvers.alternatives
+
+formats['afm'] = 'AFMFONTS' suffixes['afm'] = { 'afm' }
+formats['enc'] = 'ENCFONTS' suffixes['enc'] = { 'enc' }
+formats['fmt'] = 'TEXFORMATS' suffixes['fmt'] = { 'fmt' }
+formats['map'] = 'TEXFONTMAPS' suffixes['map'] = { 'map' }
+formats['mp'] = 'MPINPUTS' suffixes['mp'] = { 'mp' }
+formats['ocp'] = 'OCPINPUTS' suffixes['ocp'] = { 'ocp' }
+formats['ofm'] = 'OFMFONTS' suffixes['ofm'] = { 'ofm', 'tfm' }
+formats['otf'] = 'OPENTYPEFONTS' suffixes['otf'] = { 'otf' } -- 'ttf'
+formats['opl'] = 'OPLFONTS' suffixes['opl'] = { 'opl' }
+formats['otp'] = 'OTPINPUTS' suffixes['otp'] = { 'otp' }
+formats['ovf'] = 'OVFFONTS' suffixes['ovf'] = { 'ovf', 'vf' }
+formats['ovp'] = 'OVPFONTS' suffixes['ovp'] = { 'ovp' }
+formats['tex'] = 'TEXINPUTS' suffixes['tex'] = { 'tex' }
+formats['tfm'] = 'TFMFONTS' suffixes['tfm'] = { 'tfm' }
+formats['ttf'] = 'TTFONTS' suffixes['ttf'] = { 'ttf', 'ttc', 'dfont' }
+formats['pfb'] = 'T1FONTS' suffixes['pfb'] = { 'pfb', 'pfa' }
+formats['vf'] = 'VFFONTS' suffixes['vf'] = { 'vf' }
+
+formats['fea'] = 'FONTFEATURES' suffixes['fea'] = { 'fea' }
+formats['cid'] = 'FONTCIDMAPS' suffixes['cid'] = { 'cid', 'cidmap' }
+
+formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
+suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
+
+formats ['lua'] = 'LUAINPUTS' -- new
+suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
+
+-- backward compatible ones
+
+alternatives['map files'] = 'map'
+alternatives['enc files'] = 'enc'
+alternatives['cid files'] = 'cid'
+alternatives['fea files'] = 'fea'
+alternatives['opentype fonts'] = 'otf'
+alternatives['truetype fonts'] = 'ttf'
+alternatives['truetype collections'] = 'ttc'
+alternatives['truetype dictionary'] = 'dfont'
+alternatives['type1 fonts'] = 'pfb'
+
+-- obscure ones
+
+formats ['misc fonts'] = ''
+suffixes['misc fonts'] = { }
+
+formats ['sfd'] = 'SFDFONTS'
+suffixes ['sfd'] = { 'sfd' }
+alternatives['subfont definition files'] = 'sfd'
+
+-- In practice we will work within one tds tree, but i want to keep
+-- the option open to build tools that look at multiple trees, which is
+-- why we keep the tree specific data in a table. We used to pass the
+-- instance but for practical pusposes we now avoid this and use a
+-- instance variable.
+
+-- here we catch a few new thingies (todo: add these paths to context.tmf)
+--
+-- FONTFEATURES = .;$TEXMF/fonts/fea//
+-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
+
+-- we always have one instance active
+
+resolvers.instance = resolvers.instance or nil -- the current one (slow access)
+local instance = resolvers.instance or nil -- the current one (fast access)
+
+function resolvers.newinstance()
+
+ -- store once, freeze and faster (once reset we can best use
+ -- instance.environment) maybe better have a register suffix
+ -- function
+
+ for k, v in next, suffixes do
+ for i=1,#v do
+ local vi = v[i]
+ if vi then
+ suffixmap[vi] = k
+ end
+ end
+ end
+
+ -- because vf searching is somewhat dangerous, we want to prevent
+ -- too liberal searching esp because we do a lookup on the current
+ -- path anyway; only tex (or any) is safe
+
+ for k, v in next, formats do
+ dangerous[k] = true
+ end
+ dangerous.tex = nil
+
+ -- the instance
+
+ local newinstance = {
+ rootpath = '',
+ treepath = '',
+ progname = 'context',
+ engine = 'luatex',
+ format = '',
+ environment = { },
+ variables = { },
+ expansions = { },
+ files = { },
+ remap = { },
+ configuration = { },
+ setup = { },
+ order = { },
+ found = { },
+ foundintrees = { },
+ kpsevars = { },
+ hashes = { },
+ cnffiles = { },
+ luafiles = { },
+ lists = { },
+ remember = true,
+ diskcache = true,
+ renewcache = false,
+ scandisk = true,
+ cachepath = nil,
+ loaderror = false,
+ sortdata = false,
+ savelists = true,
+ cleanuppaths = true,
+ allresults = false,
+ pattern = nil, -- lists
+ data = { }, -- only for loading
+ force_suffixes = true,
+ fakepaths = { },
+ }
+
+ local ne = newinstance.environment
+
+ for k,v in next, os.env do
+ ne[k] = resolvers.bare_variable(v)
+ end
+
+ return newinstance
+
+end
+
+function resolvers.setinstance(someinstance)
+ instance = someinstance
+ resolvers.instance = someinstance
+ return someinstance
+end
+
+function resolvers.reset()
+ return resolvers.setinstance(resolvers.newinstance())
+end
+
+local function reset_hashes()
+ instance.lists = { }
+ instance.found = { }
+end
+
+local function check_configuration() -- not yet ok, no time for debugging now
+ local ie, iv = instance.environment, instance.variables
+ local function fix(varname,default)
+ local proname = varname .. "." .. instance.progname or "crap"
+ local p, v = ie[proname], ie[varname] or iv[varname]
+ if not ((p and p ~= "") or (v and v ~= "")) then
+ iv[varname] = default -- or environment?
+ end
+ end
+ local name = os.name
+ if name == "windows" then
+ fix("OSFONTDIR", "c:/windows/fonts//")
+ elseif name == "macosx" then
+ fix("OSFONTDIR", "$HOME/Library/Fonts//;/Library/Fonts//;/System/Library/Fonts//")
+ else
+ -- bad luck
+ end
+ fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS") -- no progname, hm
+ fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
+ fix("LUATEXLIBS" , ".;$TEXMF/luatex/lua//")
+end
+
+function resolvers.bare_variable(str) -- assumes str is a string
+ return (gsub(str,"\s*([\"\']?)(.+)%1\s*", "%2"))
+end
+
+function resolvers.settrace(n) -- no longer number but: 'locating' or 'detail'
+ if n then
+ trackers.disable("resolvers.*")
+ trackers.enable("resolvers."..n)
+ end
+end
+
+resolvers.settrace(os.getenv("MTX.resolvers.TRACE") or os.getenv("MTX_INPUT_TRACE"))
+
+function resolvers.osenv(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 = resolvers.bare_variable(e)
+ end
+ ie[key] = value
+ end
+ return value or ""
+end
+
+function resolvers.env(key)
+ return instance.environment[key] or resolvers.osenv(key)
+end
+
+--
+
+local function expand_vars(lst) -- simple vars
+ local variables, env = instance.variables, resolvers.env
+ local function resolve(a)
+ return variables[a] or env(a)
+ end
+ for k=1,#lst do
+ lst[k] = gsub(lst[k],"%$([%a%d%_%-]+)",resolve)
+ end
+end
+
+local function expanded_var(var) -- simple vars
+ local function resolve(a)
+ return instance.variables[a] or resolvers.env(a)
+ end
+ return (gsub(var,"%$([%a%d%_%-]+)",resolve))
+end
+
+local function entry(entries,name)
+ if name and (name ~= "") then
+ name = gsub(name,'%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = resolvers.env(name)
+ if result then
+ instance.variables[name] = result
+ resolvers.expand_variables()
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+
+local function is_entry(entries,name)
+ if name and name ~= "" then
+ name = gsub(name,'%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+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}}
+-- {$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,.local,}/web2c}
+
+-- 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
+
+local function splitpathexpr(str, t, validate)
+ -- no need for further optimization as it is only called a
+ -- few times, we can use lpeg for the sub; we could move
+ -- the local functions outside the body
+ t = t or { }
+ str = gsub(str,",}",",@}")
+ str = gsub(str,"{,","{@,")
+ -- str = "@" .. str .. "@"
+ local ok, done
+ local function do_first(a,b)
+ local t = { }
+ for s in gmatch(b,"[^,]+") do t[#t+1] = a .. s end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_second(a,b)
+ local t = { }
+ for s in gmatch(a,"[^,]+") do t[#t+1] = s .. b end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_both(a,b)
+ local t = { }
+ for sa in gmatch(a,"[^,]+") do
+ for sb in gmatch(b,"[^,]+") do
+ t[#t+1] = sa .. sb
+ end
+ end
+ return "{" .. concat(t,",") .. "}"
+ end
+ local function do_three(a,b,c)
+ return a .. b.. c
+ end
+ while true do
+ done = false
+ while true do
+ str, ok = gsub(str,"([^{},]+){([^{}]+)}",do_first)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}([^{},]+)",do_second)
+ if ok > 0 then done = true else break end
+ end
+ while true do
+ str, ok = gsub(str,"{([^{}]+)}{([^{}]+)}",do_both)
+ if ok > 0 then done = true else break end
+ end
+ str, ok = gsub(str,"({[^{}]*){([^{}]+)}([^{}]*})",do_three)
+ if ok > 0 then done = true end
+ if not done then break end
+ end
+ str = gsub(str,"[{}]", "")
+ str = gsub(str,"@","")
+ if validate then
+ for s in gmatch(str,"[^,]+") do
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end
+ else
+ for s in gmatch(str,"[^,]+") do
+ t[#t+1] = s
+ end
+ end
+ return t
+end
+
+local function expanded_path_from_list(pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for k=1,#pathlist do
+ if find(pathlist[k],"[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ local function validate(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not find(s,dummy_path_expr) and s
+ end
+ for k=1,#pathlist do
+ splitpathexpr(pathlist[k],newlist,validate)
+ end
+ else
+ for k=1,#pathlist do
+ for p in gmatch(pathlist[k],"([^,]+)") do
+ p = file.collapse_path(p)
+ if p ~= "" then newlist[#newlist+1] = p end
+ end
+ end
+ end
+ return newlist
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in default variable
+--
+-- also we now follow the stupid route: if not set then just assume *one*
+-- cnf file under texmf (i.e. distribution)
+
+resolvers.ownpath = resolvers.ownpath or nil
+resolvers.ownbin = resolvers.ownbin or arg[-2] or arg[-1] or arg[0] or "luatex"
+resolvers.autoselfdir = true -- false may be handy for debugging
+
+function resolvers.getownpath()
+ if not resolvers.ownpath then
+ if resolvers.autoselfdir and os.selfdir then
+ resolvers.ownpath = os.selfdir
+ else
+ local binary = resolvers.ownbin
+ if os.platform == "windows" then
+ binary = file.replacesuffix(binary,"exe")
+ end
+ for p in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do
+ local b = file.join(p,binary)
+ if lfs.isfile(b) then
+ -- we assume that after changing to the path the currentdir function
+ -- resolves to the real location and use this side effect here; this
+ -- trick is needed because on the mac installations use symlinks in the
+ -- path instead of real locations
+ local olddir = lfs.currentdir()
+ if lfs.chdir(p) then
+ local pp = lfs.currentdir()
+ if trace_verbose and p ~= pp then
+ logs.report("fileio","following symlink %s to %s",p,pp)
+ end
+ resolvers.ownpath = pp
+ lfs.chdir(olddir)
+ else
+ if trace_verbose then
+ logs.report("fileio","unable to check path %s",p)
+ end
+ resolvers.ownpath = p
+ end
+ break
+ end
+ end
+ end
+ if not resolvers.ownpath then resolvers.ownpath = '.' end
+ end
+ return resolvers.ownpath
+end
+
+local own_places = { "SELFAUTOLOC", "SELFAUTODIR", "SELFAUTOPARENT", "TEXMFCNF" }
+
+local function identify_own()
+ local ownpath = resolvers.getownpath() or lfs.currentdir()
+ local ie = instance.environment
+ if ownpath then
+ if resolvers.env('SELFAUTOLOC') == "" then os.env['SELFAUTOLOC'] = file.collapse_path(ownpath) end
+ if resolvers.env('SELFAUTODIR') == "" then os.env['SELFAUTODIR'] = file.collapse_path(ownpath .. "/..") end
+ if resolvers.env('SELFAUTOPARENT') == "" then os.env['SELFAUTOPARENT'] = file.collapse_path(ownpath .. "/../..") end
+ else
+ logs.report("fileio","error: unable to locate ownpath")
+ os.exit()
+ end
+ if resolvers.env('TEXMFCNF') == "" then os.env['TEXMFCNF'] = resolvers.cnfdefault end
+ if resolvers.env('TEXOS') == "" then os.env['TEXOS'] = resolvers.env('SELFAUTODIR') end
+ if resolvers.env('TEXROOT') == "" then os.env['TEXROOT'] = resolvers.env('SELFAUTOPARENT') end
+ if trace_verbose then
+ for i=1,#own_places do
+ local v = own_places[i]
+ logs.report("fileio","variable %s set to %s",v,resolvers.env(v) or "unknown")
+ end
+ end
+ identify_own = function() end
+end
+
+function resolvers.identify_cnf()
+ if #instance.cnffiles == 0 then
+ -- fallback
+ identify_own()
+ -- the real search
+ resolvers.expand_variables()
+ local t = resolvers.split_path(resolvers.env('TEXMFCNF'))
+ t = expanded_path_from_list(t)
+ expand_vars(t) -- redundant
+ local function locate(filename,list)
+ for i=1,#t do
+ local ti = t[i]
+ local texmfcnf = file.collapse_path(file.join(ti,filename))
+ if lfs.isfile(texmfcnf) then
+ list[#list+1] = texmfcnf
+ end
+ end
+ end
+ locate(resolvers.luaname,instance.luafiles)
+ locate(resolvers.cnfname,instance.cnffiles)
+ end
+end
+
+local function load_cnf_file(fname)
+ fname = resolvers.clean_path(fname)
+ local lname = file.replacesuffix(fname,'lua')
+ local f = io.open(lname)
+ if f then -- this will go
+ f:close()
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ resolvers.load_data(dname,'configuration',lname and file.basename(lname))
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ else
+ f = io.open(fname)
+ if f then
+ if trace_verbose then
+ logs.report("fileio","loading %s", fname)
+ end
+ 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 = gsub(line,"\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not find(line,"^[%%#]") then
+ local l = gsub(line,"%s*%%.*$","")
+ local k, v = match(l,"%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ v = gsub(v,"[%%#].*",'')
+ data[k] = gsub(v,"~","$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s", fname)
+ end
+ end
+end
+
+local function collapse_cnf_data() -- potential optimization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in next, 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] = resolvers.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function resolvers.load_cnf()
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ load_cnf_file(fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ if trace_verbose then
+ logs.report("fileio","no cnf files found (TEXMFCNF may not be set/known)")
+ end
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadoldconfig(instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ resolvers.saveoldconfig()
+ end
+ else
+ loadoldconfigdata()
+ if instance.renewcache then
+ resolvers.saveoldconfig()
+ end
+ end
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+function resolvers.load_lua()
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = file.collapse_path(gsub(fname,"\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = file.collapse_path(instance.rootpath)
+ resolvers.loadnewconfig()
+ collapse_cnf_data()
+ end
+ check_configuration()
+end
+
+-- database loading
+
+function resolvers.load_hash()
+ resolvers.locatelists()
+ if instance.diskcache and not instance.renewcache then
+ resolvers.loadfiles()
+ if instance.loaderror then
+ resolvers.loadlists()
+ resolvers.savefiles()
+ end
+ else
+ resolvers.loadlists()
+ if instance.renewcache then
+ resolvers.savefiles()
+ end
+ end
+end
+
+function resolvers.append_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash append: %s",tag)
+ end
+ insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.prepend_hash(type,tag,name)
+ if trace_locating then
+ logs.report("fileio","= hash prepend: %s",tag)
+ end
+ insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function resolvers.extend_texmf_var(specification) -- crap, we could better prepend the hash
+-- local t = resolvers.expanded_path_list('TEXMF') -- full expansion
+ local t = resolvers.split_path(resolvers.env('TEXMF'))
+ insert(t,1,specification)
+ local newspec = concat(t,";")
+ if instance.environment["TEXMF"] then
+ instance.environment["TEXMF"] = newspec
+ elseif instance.variables["TEXMF"] then
+ instance.variables["TEXMF"] = newspec
+ else
+ -- weird
+ end
+ resolvers.expand_variables()
+ reset_hashes()
+end
+
+-- locators
+
+function resolvers.locatelists()
+ for _, path in ipairs(resolvers.clean_path_list('TEXMF')) do
+ if trace_verbose then
+ logs.report("fileio","locating list of %s",path)
+ end
+ resolvers.locatedatabase(file.collapse_path(path))
+ end
+end
+
+function resolvers.locatedatabase(specification)
+ return resolvers.methodhandler('locators', specification)
+end
+
+function resolvers.locators.tex(specification)
+ if specification and specification ~= '' and lfs.isdir(specification) then
+ if trace_locating then
+ logs.report("fileio",'! tex locator found: %s',specification)
+ end
+ resolvers.append_hash('file',specification,filename)
+ elseif trace_locating then
+ logs.report("fileio",'? tex locator not found: %s',specification)
+ end
+end
+
+-- hashers
+
+function resolvers.hashdatabase(tag,name)
+ return resolvers.methodhandler('hashers',tag,name)
+end
+
+function resolvers.loadfiles()
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.hashdatabase(hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function resolvers.hashers.tex(tag,name)
+ resolvers.load_data(tag,'files')
+end
+
+-- generators:
+
+function resolvers.loadlists()
+ for _, hash in ipairs(instance.hashes) do
+ resolvers.generatedatabase(hash.tag)
+ end
+end
+
+function resolvers.generatedatabase(specification)
+ return resolvers.methodhandler('generators', specification)
+end
+
+-- starting with . or .. etc or funny char
+
+local weird = lpeg.P(".")^1 + lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+
+function resolvers.generators.tex(specification)
+ local tag = specification
+ if trace_verbose then
+ logs.report("fileio","scanning path %s",specification)
+ end
+ 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 function action(path)
+ local full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if not weird:match(name) then
+ local mode = attributes(full..name,'mode')
+ if mode == 'file' then
+ if path then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ else -- probably unique anyway
+ files[name] = path
+ local lower = lower(name)
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
+ end
+ end
+ end
+ elseif mode == 'directory' then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ end
+ end
+ end
+ end
+ action()
+ if trace_verbose then
+ logs.report("fileio","%s files found on %s directories with %s uppercase remappings",n,m,r)
+ end
+end
+
+-- savers, todo
+
+function resolvers.savefiles()
+ resolvers.save_data('files')
+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 resolvers.splitconfig()
+ 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 resolvers.joinconfig()
+ for i,c in ipairs(instance.order) do
+ for k,v in pairs(c) do -- ipairs?
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function resolvers.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function resolvers.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+
+function resolvers.splitexpansions()
+ local ie = instance.expansions
+ for k,v in next, ie do
+ local t, h = { }, { }
+ for _,vv in ipairs(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
+ ie[k] = t
+ else
+ ie[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function resolvers.saveoldconfig()
+ resolvers.splitconfig()
+ resolvers.save_data('configuration')
+ resolvers.joinconfig()
+end
+
+resolvers.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 resolvers.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 function dump(k,v,m) -- could be moved inline
+ 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(sortedkeys(files)) do -- ipairs
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sortedkeys(fk)) do -- ipairs
+ 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 next, files do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in next, 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
+
+function resolvers.save_data(dataname, makename) -- untested without cache overload
+ for cachename, files in next, instance[dataname] do
+ local name = (makename or file.join)(cachename,dataname)
+ local luaname, lucname = name .. ".lua", name .. ".luc"
+ if trace_verbose then
+ logs.report("fileio","preparing %s for %s",dataname,cachename)
+ end
+ for k, v in next, files do
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = resolvers.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local ok = io.savedata(luaname,resolvers.serialize(data))
+ if ok then
+ if trace_verbose then
+ logs.report("fileio","%s saved in %s",dataname,luaname)
+ end
+ if utils.lua.compile(luaname,lucname,false,true) then -- no cleanup but strip
+ if trace_verbose then
+ logs.report("fileio","%s compiled to %s",dataname,lucname)
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","compiling failed for %s, deleting file %s",dataname,lucname)
+ end
+ os.remove(lucname)
+ end
+ elseif trace_verbose then
+ logs.report("fileio","unable to save %s in %s (access error)",dataname,luaname)
+ end
+ end
+end
+
+function resolvers.load_data(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 .. ".luc") or loadfile(filename .. ".lua")
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == resolvers.cacheversion then
+ if trace_verbose then
+ logs.report("fileio","loading %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = data.content
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,filename)
+ end
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping %s for %s from %s",dataname,pathname,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 resolvers.resetconfig()
+ identify_own()
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function resolvers.loadnewconfig()
+ for _, cnf in ipairs(instance.luafiles) do
+ local pathname = file.dirname(cnf)
+ local filename = file.join(pathname,resolvers.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ if trace_verbose then
+ logs.report("fileio","loading configuration file %s",filename)
+ end
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in next, data do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in next, v do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
+ end
+ end
+ end
+ instance['setup'][pathname] = t
+ else
+ instance['setup'][pathname] = data
+ end
+ else
+ if trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance['setup'][pathname] = { }
+ instance.loaderror = true
+ end
+ elseif trace_verbose then
+ logs.report("fileio","skipping configuration file %s",filename)
+ end
+ instance.order[#instance.order+1] = instance.setup[pathname]
+ if instance.loaderror then break end
+ end
+end
+
+function resolvers.loadoldconfig()
+ if not instance.renewcache then
+ for _, cnf in ipairs(instance.cnffiles) do
+ local dname = file.dirname(cnf)
+ resolvers.load_data(dname,'configuration')
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ if instance.loaderror then break end
+ end
+ end
+ resolvers.joinconfig()
+end
+
+function resolvers.expand_variables()
+ local expansions, environment, variables = { }, instance.environment, instance.variables
+ local env = resolvers.env
+ instance.expansions = expansions
+ if instance.engine ~= "" then environment['engine'] = instance.engine end
+ if instance.progname ~= "" then environment['progname'] = instance.progname end
+ for k,v in next, environment do
+ local a, b = match(k,"^(%a+)%_(.*)%s*$")
+ if a and b then
+ expansions[a..'.'..b] = v
+ else
+ expansions[k] = v
+ end
+ end
+ for k,v in next, environment do -- move environment to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ for k,v in next, variables do -- move variables to expansions
+ if not expansions[k] then expansions[k] = v end
+ end
+ local busy = false
+ local function resolve(a)
+ busy = true
+ return expansions[a] or env(a)
+ end
+ while true do
+ busy = false
+ for k,v in next, expansions do
+ local s, n = gsub(v,"%$([%a%d%_%-]+)",resolve)
+ local s, m = gsub(s,"%$%{([%a%d%_%-]+)%}",resolve)
+ if n > 0 or m > 0 then
+ expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in next, expansions do
+ expansions[k] = gsub(v,"\\", '/')
+ end
+end
+
+function resolvers.variable(name)
+ return entry(instance.variables,name)
+end
+
+function resolvers.expansion(name)
+ return entry(instance.expansions,name)
+end
+
+function resolvers.is_variable(name)
+ return is_entry(instance.variables,name)
+end
+
+function resolvers.is_expansion(name)
+ return is_entry(instance.expansions,name)
+end
+
+function resolvers.unexpanded_path_list(str)
+ local pth = resolvers.variable(str)
+ local lst = resolvers.split_path(pth)
+ return expanded_path_from_list(lst)
+end
+
+function resolvers.unexpanded_path(str)
+ return file.join_path(resolvers.unexpanded_path_list(str))
+end
+
+do -- no longer needed
+
+ local done = { }
+
+ function resolvers.reset_extra_path()
+ 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 resolvers.register_extra_path(paths,subpaths)
+ local ep = instance.extra_paths or { }
+ local n = #ep
+ if paths and paths ~= "" then
+ if subpaths and subpaths ~= "" then
+ for p in gmatch(paths,"[^,]+") do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in gmatch(subpaths,"[^,]+") do
+ local ps = p .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ else
+ for p in gmatch(paths,"[^,]+") do
+ if not done[p] then
+ ep[#ep+1] = resolvers.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 gmatch(subpaths,"[^,]+") do
+ local ps = ep[i] .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = resolvers.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
+
+local function made_list(instance,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=1,#list do
+ local v = list[k]
+ if not done[v] then
+ if find(v,"^[%.%/]$") then
+ done[v] = true
+ new[#new+1] = v
+ else
+ break
+ end
+ end
+ end
+ -- first the extra paths
+ for k=1,#ep do
+ local v = ep[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k=1,#list do
+ local v = list[k]
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
+ end
+end
+
+function resolvers.clean_path_list(str)
+ local t = resolvers.expanded_path_list(str)
+ if t then
+ for i=1,#t do
+ t[i] = file.collapse_path(resolvers.clean_path(t[i]))
+ end
+ end
+ return t
+end
+
+function resolvers.expand_path(str)
+ return file.join_path(resolvers.expanded_path_list(str))
+end
+
+function resolvers.expanded_path_list(str)
+ if not str then
+ return ep or { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = gsub(str,"%$","")
+ if not instance.lists[str] then -- cached
+ local lst = made_list(instance,resolvers.split_path(resolvers.expansion(str)))
+ instance.lists[str] = expanded_path_from_list(lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = resolvers.split_path(resolvers.expansion(str))
+ return made_list(instance,expanded_path_from_list(lst))
+ end
+end
+
+function resolvers.expanded_path_list_from_var(str) -- brrr
+ local tmp = resolvers.var_of_format_or_suffix(gsub(str,"%$",""))
+ if tmp ~= "" then
+ return resolvers.expanded_path_list(str)
+ else
+ return resolvers.expanded_path_list(tmp)
+ end
+end
+
+function resolvers.expand_path_from_var(str)
+ return file.join_path(resolvers.expanded_path_list_from_var(str))
+end
+
+function resolvers.format_of_var(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+function resolvers.format_of_suffix(str)
+ return suffixmap[file.extname(str)] or 'tex'
+end
+
+function resolvers.variable_of_format(str)
+ return formats[str] or formats[alternatives[str]] or ''
+end
+
+function resolvers.var_of_format_or_suffix(str)
+ local v = formats[str]
+ if v then
+ return v
+ end
+ v = formats[alternatives[str]]
+ if v then
+ return v
+ end
+ v = suffixmap[file.extname(str)]
+ if v then
+ return formats[isf]
+ end
+ return ''
+end
+
+function resolvers.expand_braces(str) -- output variable and brace expansion of STRING
+ local ori = resolvers.variable(str)
+ local pth = expanded_path_from_list(resolvers.split_path(ori))
+ return file.join_path(pth)
+end
+
+resolvers.isreadable = { }
+
+function resolvers.isreadable.file(name)
+ local readable = lfs.isfile(name) -- brrr
+ if trace_detail then
+ if readable then
+ logs.report("fileio","+ readable: %s",name)
+ else
+ logs.report("fileio","- readable: %s", name)
+ end
+ end
+ return readable
+end
+
+resolvers.isreadable.tex = resolvers.isreadable.file
+
+-- name
+-- name/name
+
+local function collect_files(names)
+ local filelist = { }
+ for k=1,#names do
+ local fname = names[k]
+ if trace_detail then
+ logs.report("fileio","? blobpath asked: %s",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or find(dname,"^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ local hashes = instance.hashes
+ for h=1,#hashes do
+ local hash = hashes[h]
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if trace_detail then
+ logs.report("fileio",'? blobpath do: %s (%s)',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 find(blobfile,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
+ end
+ else
+ for kk=1,#blobfile do
+ local vv = blobfile[kk]
+ if not dname or find(vv,dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,vv,bname), -- search
+ resolvers.concatinators[hash.type](blobpath,vv,bname) -- result
+ }
+ end
+ end
+ end
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! blobpath no: %s (%s)',blobpath,bname)
+ end
+ end
+ end
+ if #filelist > 0 then
+ return filelist
+ else
+ return nil
+ end
+end
+
+function resolvers.suffix_of_format(str)
+ if suffixes[str] then
+ return suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function resolvers.suffixes_of_format(str)
+ if suffixes[str] then
+ return suffixes[str]
+ else
+ return {}
+ end
+end
+
+function resolvers.register_in_trees(name)
+ if not find(name,"^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+-- split the next one up for readability (bu this module needs a cleanup anyway)
+
+local function can_be_dir(name) -- can become local
+ local fakepaths = instance.fakepaths
+ if not fakepaths[name] then
+ if lfs.isdir(name) then
+ fakepaths[name] = 1 -- directory
+ else
+ fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (fakepaths[name] == 1)
+end
+
+local function collect_instance_files(filename,collected) -- todo : plugin (scanners, checkers etc)
+ local result = collected or { }
+ local stamp = nil
+ filename = file.collapse_path(filename) -- elsewhere
+ filename = file.collapse_path(gsub(filename,"\\","/")) -- elsewhere
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ if trace_locating then
+ logs.report("fileio",'! remembered: %s',filename)
+ end
+ return instance.found[stamp]
+ end
+ end
+ if not dangerous[instance.format or "?"] then
+ if resolvers.isreadable.file(filename) then
+ if trace_detail then
+ logs.report("fileio",'= found directly: %s',filename)
+ end
+ instance.found[stamp] = { filename }
+ return { filename }
+ end
+ end
+ if find(filename,'%*') then
+ if trace_locating then
+ logs.report("fileio",'! wildcard: %s', filename)
+ end
+ result = resolvers.find_wildcard_files(filename)
+ elseif file.is_qualified_path(filename) then
+ if resolvers.isreadable.file(filename) then
+ if trace_locating then
+ logs.report("fileio",'! qualified: %s', filename)
+ end
+ result = { filename }
+ else
+ local forcedname, ok, suffix = "", false, file.extname(filename)
+ if suffix == "" then -- why
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing standard filetype: tex')
+ end
+ result, ok = { forcedname }, true
+ end
+ else
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ forcedname = filename .. "." .. s
+ if resolvers.isreadable.file(forcedname) then
+ if trace_locating then
+ logs.report("fileio",'! no suffix, forcing format filetype: %s', s)
+ end
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok and suffix ~= "" then
+ -- try to find in tree (no suffix manipulation), here we search for the
+ -- matching last part of the name
+ local basename = file.basename(filename)
+ local pattern = (filename .. "$"):gsub("([%.%-])","%%%1")
+ local savedformat = instance.format
+ local format = savedformat or ""
+ if format == "" then
+ instance.format = resolvers.format_of_suffix(suffix)
+ end
+ if not format then
+ instance.format = "othertextfiles" -- kind of everything, maybe texinput is better
+ end
+ --
+ local resolved = collect_instance_files(basename)
+ if #result == 0 then
+ local lowered = lower(basename)
+ if filename ~= lowered then
+ resolved = collect_instance_files(lowered)
+ end
+ end
+ resolvers.format = savedformat
+ --
+ for r=1,#resolved do
+ local rr = resolved[r]
+ if rr:find(pattern) then
+ result[#result+1], ok = rr, true
+ end
+ end
+ -- a real wildcard:
+ --
+ -- if not ok then
+ -- local filelist = collect_files({basename})
+ -- for f=1,#filelist do
+ -- local ff = filelist[f][3] or ""
+ -- if ff:find(pattern) then
+ -- result[#result+1], ok = ff, true
+ -- end
+ -- end
+ -- end
+ end
+ if not ok and trace_locating then
+ logs.report("fileio",'? qualified: %s', 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 = resolvers.format_of_suffix(forcedname)
+ if trace_locating then
+ logs.report("fileio",'! forcing filetype: %s',filetype)
+ end
+ else
+ filetype = resolvers.format_of_suffix(filename)
+ if trace_locating then
+ logs.report("fileio",'! using suffix based filetype: %s',filetype)
+ end
+ end
+ else
+ if ext == "" then
+ local suffixes = resolvers.suffixes_of_format(instance.format)
+ for _, s in next, suffixes do
+ wantedfiles[#wantedfiles+1] = filename .. "." .. s
+ end
+ end
+ filetype = instance.format
+ if trace_locating then
+ logs.report("fileio",'! using given filetype: %s',filetype)
+ end
+ end
+ local typespec = resolvers.variable_of_format(filetype)
+ local pathlist = resolvers.expanded_path_list(typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only / todo == wildcard
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ logs.report("fileio",'? filetype: %s',filetype or '?')
+ logs.report("fileio",'? wanted files: %s',concat(wantedfiles," | "))
+ end
+ for k=1,#wantedfiles do
+ local fname = wantedfiles[k]
+ if fname and resolvers.isreadable.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 = collect_files(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 = collect_files(wantedfiles)
+ local doscan, recurse
+ if trace_detail then
+ logs.report("fileio",'? filename: %s',filename)
+ end
+ -- a bit messy ... esp the doscan setting here
+ for k=1,#pathlist do
+ local path = pathlist[k]
+ if find(path,"^!!") then doscan = false else doscan = true end
+ if find(path,"//$") then recurse = true else recurse = false end
+ local pathname = gsub(path,"^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = gsub(pathname,"([%-%.])","%%%1") -- this also influences
+ pathname = gsub(pathname,"/+$", '/.*') -- later usage of pathname
+ pathname = gsub(pathname,"//", '/.-/') -- not ok for /// but harmless
+ local expr = "^" .. pathname
+ for k=1,#filelist do
+ local fl = filelist[k]
+ local f = fl[2]
+ if find(f,expr) then
+ if trace_detail then
+ logs.report("fileio",'= found in hash: %s',f)
+ end
+ --- todo, test for readable
+ result[#result+1] = fl[3]
+ resolvers.register_in_trees(f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all / also zips
+ if resolvers.splitmethod(pathname).scheme == 'file' then -- ?
+ local pname = gsub(pathname,"%.%*$",'')
+ if not find(pname,"%*") then
+ local ppname = gsub(pname,"/+$","")
+ if can_be_dir(ppname) then
+ for k=1,#wantedfiles do
+ local w = wantedfiles[k]
+ local fname = file.join(ppname,w)
+ if resolvers.isreadable.file(fname) then
+ if trace_detail then
+ logs.report("fileio",'= found by scanning: %s',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=1,#result do
+ result[k] = file.collapse_path(result[k])
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+if not resolvers.concatinators then resolvers.concatinators = { } end
+
+resolvers.concatinators.tex = file.join
+resolvers.concatinators.file = resolvers.concatinators.tex
+
+function resolvers.find_files(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 result = collect_instance_files(filename)
+ if #result == 0 then
+ local lowered = lower(filename)
+ if filename ~= lowered then
+ return collect_instance_files(lowered)
+ end
+ end
+ instance.format = ''
+ return result
+end
+
+function resolvers.find_file(filename,filetype,mustexist)
+ return (resolvers.find_files(filename,filetype,mustexist)[1] or "")
+end
+
+function resolvers.find_given_files(filename)
+ local bname, result = file.basename(filename), { }
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ 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] = resolvers.concatinators[hash.type](hash.tag,blist,bname) or ""
+ if not instance.allresults then break end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ result[#result+1] = resolvers.concatinators[hash.type](hash.tag,vv,bname) or ""
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function resolvers.find_given_file(filename)
+ return (resolvers.find_given_files(filename)[1] or "")
+end
+
+local function doit(path,blist,bname,tag,kind,result,allresults)
+ local done = false
+ if blist and kind then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if find(lower(blist),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk=1,#blist do
+ local vv = blist[kk]
+ if find(lower(vv),path) then
+ result[#result+1] = resolvers.concatinators[kind](tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+end
+
+function resolvers.find_wildcard_files(filename) -- todo: remap:
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = gsub(dname,"^*/","")
+ path = gsub(path,"*",".*")
+ path = gsub(path,"-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = gsub(name,"*",".*")
+ name = gsub(name,"-","%%-")
+ path = lower(path)
+ name = lower(name)
+ local files, allresults, done = instance.files, instance.allresults, false
+ if find(name,"%*") then
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ for kk, hh in next, files[hash.tag] do
+ if not find(kk,"^remap:") then
+ if find(lower(kk),name) then
+ if doit(path,hh,kk,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ end
+ else
+ local hashes = instance.hashes
+ for k=1,#hashes do
+ local hash = hashes[k]
+ local tag, kind = hash.tag, hash.type
+ if doit(path,files[tag][bname],bname,tag,kind,result,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ -- we can consider also searching the paths not in the database, but then
+ -- we end up with a messy search (all // in all path specs)
+ return result
+end
+
+function resolvers.find_wildcard_file(filename)
+ return (resolvers.find_wildcard_files(filename)[1] or "")
+end
+
+-- main user functions
+
+function resolvers.automount()
+ -- implemented later
+end
+
+function resolvers.load(option)
+ statistics.starttiming(instance)
+ resolvers.resetconfig()
+ resolvers.identify_cnf()
+ resolvers.load_lua()
+ resolvers.expand_variables()
+ resolvers.load_cnf()
+ resolvers.expand_variables()
+ if option ~= "nofiles" then
+ resolvers.load_hash()
+ resolvers.automount()
+ end
+ statistics.stoptiming(instance)
+end
+
+function resolvers.for_files(command, files, filetype, mustexist)
+ if files and #files > 0 then
+ local function report(str)
+ if trace_verbose then
+ logs.report("fileio",str) -- has already verbose
+ else
+ print(str)
+ end
+ end
+ if trace_verbose then
+ report('')
+ end
+ for _, file in ipairs(files) do
+ local result = command(file,filetype,mustexist)
+ if type(result) == 'string' then
+ report(result)
+ else
+ for _,v in ipairs(result) do
+ report(v)
+ end
+ end
+ end
+ end
+end
+
+-- strtab
+
+resolvers.var_value = resolvers.variable -- output the value of variable $STRING.
+resolvers.expand_var = resolvers.expansion -- output variable expansion of STRING.
+
+function resolvers.show_path(str) -- output search path for file type NAME
+ return file.join_path(resolvers.expanded_path_list(resolvers.format_of_var(str)))
+end
+
+-- resolvers.find_file(filename)
+-- resolvers.find_file(filename, filetype, mustexist)
+-- resolvers.find_file(filename, mustexist)
+-- resolvers.find_file(filename, filetype)
+
+function resolvers.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
+
+function resolvers.splitmethod(filename)
+ if not filename then
+ return { } -- safeguard
+ elseif type(filename) == "table" then
+ return filename -- already split
+ elseif not find(filename,"://") then
+ return { scheme="file", path = filename, original=filename } -- quick hack
+ else
+ return url.hashed(filename)
+ end
+end
+
+function table.sequenced(t,sep) -- temp here
+ local s = { }
+ for k, v in pairs(t) do -- pairs?
+ s[#s+1] = k .. "=" .. v
+ end
+ return concat(s, sep or " | ")
+end
+
+function resolvers.methodhandler(what, filename, filetype) -- ...
+ local specification = (type(filename) == "string" and resolvers.splitmethod(filename)) or filename -- no or { }, let it bomb
+ local scheme = specification.scheme
+ if resolvers[what][scheme] then
+ if trace_locating then
+ logs.report("fileio",'= handler: %s -> %s -> %s',specification.original,what,table.sequenced(specification))
+ end
+ return resolvers[what][scheme](filename,filetype) -- todo: specification
+ else
+ return resolvers[what].tex(filename,filetype) -- todo: specification
+ end
+end
+
+function resolvers.clean_path(str)
+ if str then
+ str = gsub(str,"\\","/")
+ str = gsub(str,"^!+","")
+ str = gsub(str,"^~",resolvers.homedir)
+ return str
+ else
+ return nil
+ end
+end
+
+function resolvers.do_with_path(name,func)
+ for _, v in pairs(resolvers.expanded_path_list(name)) do -- pairs?
+ func("^"..resolvers.clean_path(v))
+ end
+end
+
+function resolvers.do_with_var(name,func)
+ func(expanded_var(name))
+end
+
+function resolvers.with_files(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 next, files do
+ if find(k,"^remap:") then
+ k = files[k]
+ v = files[k] -- chained
+ end
+ if find(k,pattern) then
+ if type(v) == "string" then
+ handle(blobtype,blobpath,v,k)
+ else
+ for _,vv in pairs(v) do -- ipairs?
+ handle(blobtype,blobpath,vv,k)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+function resolvers.locate_format(name)
+ local barename, fmtname = name:gsub("%.%a+$",""), ""
+ if resolvers.usecache then
+ local path = file.join(caches.setpath("formats")) -- maybe platform
+ fmtname = file.join(path,barename..".fmt") or ""
+ end
+ if fmtname == "" then
+ fmtname = resolvers.find_files(barename..".fmt")[1] or ""
+ end
+ fmtname = resolvers.clean_path(fmtname)
+ if fmtname ~= "" then
+ local barename = file.removesuffix(fmtname)
+ local luaname, lucname, luiname = barename .. ".lua", barename .. ".luc", barename .. ".lui"
+ if lfs.isfile(luiname) then
+ return barename, luiname
+ elseif lfs.isfile(lucname) then
+ return barename, lucname
+ elseif lfs.isfile(luaname) then
+ return barename, luaname
+ end
+ end
+ return nil, nil
+end
+
+function resolvers.boolean_variable(str,default)
+ local b = resolvers.expansion(str)
+ if b == "" then
+ return default
+ else
+ b = toboolean(b)
+ return (b == nil and default) or b
+ end
+end
+
+texconfig.kpse_init = false
+
+kpse = { original = kpse } setmetatable(kpse, { __index = function(k,v) return resolvers[v] end } )
+
+-- for a while
+
+input = resolvers
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-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--
+<p>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.</p>
+
+</code>
+TEXMFCACHE=$TMP;$TEMP;$TMPDIR;$TEMPDIR;$HOME;$TEXMFVAR;$VARTEXMF;.
+</code>
+
+<p>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.</p>
+--ldx]]--
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+
+caches = caches 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.tree = false
+caches.paths = caches.paths or nil
+caches.force = false
+caches.defaults = { "TEXMFCACHE", "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
+
+function caches.temp()
+ local cachepath = nil
+ local function check(list,isenv)
+ if not cachepath then
+ for k=1,#list do
+ local v = list[k]
+ cachepath = (isenv and (os.env[v] or "")) or v or ""
+ if cachepath == "" then
+ -- next
+ else
+ cachepath = resolvers.clean_path(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then -- lfs.attributes(cachepath,"mode") == "directory"
+ break
+ elseif caches.force or io.ask(format("\nShould I create the cache path %s?",cachepath), "no", { "yes", "no" }) == "yes" then
+ dir.mkdirs(cachepath)
+ if lfs.isdir(cachepath) and file.iswritable(cachepath) then
+ break
+ end
+ end
+ end
+ cachepath = nil
+ end
+ end
+ end
+ check(resolvers.clean_path_list("TEXMFCACHE") or { })
+ check(caches.defaults,true)
+ if not cachepath then
+ print("\nfatal error: there is no valid (writable) cache path defined\n")
+ os.exit()
+ elseif not lfs.isdir(cachepath) then -- lfs.attributes(cachepath,"mode") ~= "directory"
+ print(format("\nfatal error: cache path %s is not a directory\n",cachepath))
+ os.exit()
+ end
+ cachepath = file.collapse_path(cachepath)
+ function caches.temp()
+ return cachepath
+ end
+ return cachepath
+end
+
+function caches.configpath()
+ return table.concat(resolvers.instance.cnffiles,";")
+end
+
+function caches.hashed(tree)
+ return md5.hex(gsub(lower(tree),"[\\\/]+","/"))
+end
+
+function caches.treehash()
+ local tree = caches.configpath()
+ if not tree or tree == "" then
+ return false
+ else
+ return caches.hashed(tree)
+ end
+end
+
+function caches.setpath(...)
+ if not caches.path then
+ if not caches.path then
+ caches.path = caches.temp()
+ end
+ caches.path = resolvers.clean_path(caches.path) -- to be sure
+ caches.tree = caches.tree or caches.treehash()
+ 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
+ if not caches.path then
+ caches.path = '.'
+ end
+ caches.path = resolvers.clean_path(caches.path)
+ if 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(category,subcategory)
+ return function()
+ return caches.setpath(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
+ loader = loader()
+ collectgarbage("step")
+ return loader
+ else
+ return false
+ end
+end
+
+--~ function caches.loaddata(path,name)
+--~ local tmaname, tmcname = caches.setluanames(path,name)
+--~ return dofile(tmcname) or dofile(tmaname)
+--~ end
+
+function caches.iswritable(filepath,filename)
+ local tmaname, tmcname = caches.setluanames(filepath,filename)
+ return file.iswritable(tmaname)
+end
+
+function caches.savedata(filepath,filename,data,raw)
+ 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',false,true,false)) -- no hex
+ else
+ table.tofile(tmaname, data,'return',false,true,false) -- maybe not the last true
+ end
+ local cleanup = resolvers.boolean_variable("PURGECACHE", false)
+ local strip = resolvers.boolean_variable("LUACSTRIP", true)
+ utils.lua.compile(tmaname, tmcname, cleanup, strip)
+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 input and resolvers.instance then
+ if not texconfig.luaname then texconfig.luaname = "cont-en.lua" end -- or luc
+ texconfig.formatname = caches.setpath("formats") .. "/" .. gsub(texconfig.luaname,"%.lu.$",".fmt")
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-res'] = {
+ 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"
+}
+
+--~ print(resolvers.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
+
+local upper, lower, gsub = string.upper, string.lower, string.gsub
+
+local prefixes = { }
+
+prefixes.environment = function(str)
+ return resolvers.clean_path(os.getenv(str) or os.getenv(upper(str)) or os.getenv(lower(str)) or "")
+end
+
+prefixes.relative = function(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 resolvers.clean_path(str)
+end
+
+prefixes.locate = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path((fullname ~= "" and fullname) or str)
+end
+
+prefixes.filename = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.basename((fullname ~= "" and fullname) or str))
+end
+
+prefixes.pathname = function(str)
+ local fullname = resolvers.find_given_file(str) or ""
+ return resolvers.clean_path(file.dirname((fullname ~= "" and fullname) or str))
+end
+
+prefixes.env = prefixes.environment
+prefixes.rel = prefixes.relative
+prefixes.loc = prefixes.locate
+prefixes.kpse = prefixes.locate
+prefixes.full = prefixes.locate
+prefixes.file = prefixes.filename
+prefixes.path = prefixes.pathname
+
+local function _resolve_(method,target)
+ if prefixes[method] then
+ return prefixes[method](target)
+ else
+ return method .. ":" .. target
+ end
+end
+
+local function resolve(str)
+ if type(str) == "table" then
+ for k, v in pairs(str) do -- ipairs
+ str[k] = resolve(v) or v
+ end
+ elseif str and str ~= "" then
+ str = gsub(str,"([a-z]+):([^ \"\']*)",_resolve_)
+ end
+ return str
+end
+
+resolvers.resolve = resolve
+
+if os.uname then
+
+ for k, v in pairs(os.uname()) do
+ if not prefixes[k] then
+ prefixes[k] = function() return v end
+ end
+ end
+
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-inp'] = {
+ 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"
+}
+
+resolvers.finders = resolvers.finders or { }
+resolvers.openers = resolvers.openers or { }
+resolvers.loaders = resolvers.loaders or { }
+
+resolvers.finders.notfound = { nil }
+resolvers.openers.notfound = { nil }
+resolvers.loaders.notfound = { false, nil, 0 }
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-out'] = {
+ 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"
+}
+
+outputs = outputs or { }
+
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-con'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_cache = false trackers.register("resolvers.cache", function(v) trace_cache = v end)
+local trace_containers = false trackers.register("resolvers.containers", function(v) trace_containers = v end)
+local trace_storage = false trackers.register("resolvers.storage", function(v) trace_storage = v end)
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+--[[ldx--
+<p>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).</p>
+
+<p>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.</p>
+
+<p>Examples of usage can be found in the font related code.</p>
+--ldx]]--
+
+containers = containers or { }
+
+containers.usecache = true
+
+local function report(container,tag,name)
+ if trace_cache or trace_containers then
+ logs.report(format("%s cache",container.subcategory),"%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 and caches.setpath and caches.setpath(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 and caches.iswritable(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 caches and not container.storage[name] and containers.usecache 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 and caches 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
+
+function containers.cleanname(name)
+ return (gsub(lower(name),"[^%w%d]+","-"))
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-use'] = {
+ 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"
+}
+
+local format, lower, gsub = string.format, string.lower, string.gsub
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+local trace_locating = false trackers.register("resolvers.locating", function(v) trace_locating = v trackers.enable("resolvers.verbose") end)
+
+-- since we want to use the cache instead of the tree, we will now
+-- reimplement the saver.
+
+local save_data = resolvers.save_data
+local load_data = resolvers.load_data
+
+resolvers.cachepath = nil -- public, for tracing
+resolvers.usecache = true -- public, for tracing
+
+function resolvers.save_data(dataname)
+ save_data(dataname, function(cachename,dataname)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.cachepath(),caches.hashed(cachename))
+ else
+ return file.join(cachename,dataname)
+ end
+ end)
+end
+
+function resolvers.load_data(pathname,dataname,filename)
+ load_data(pathname,dataname,filename,function(dataname,filename)
+ resolvers.usecache = not toboolean(resolvers.expansion("CACHEINTDS") or "false",true)
+ if resolvers.usecache then
+ resolvers.cachepath = resolvers.cachepath or caches.definepath("trees")
+ return file.join(resolvers.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
+
+resolvers.automounted = resolvers.automounted or { }
+
+function resolvers.automount(usecache)
+ local mountpaths = resolvers.clean_path_list(resolvers.expansion('TEXMFMOUNT'))
+ if table.is_empty(mountpaths) and usecache then
+ mountpaths = { caches.setpath("mount") }
+ end
+ if not table.is_empty(mountpaths) then
+ statistics.starttiming(resolvers.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
+ if trace_locating then
+ logs.report("fileio","mounting %s",line)
+ end
+ table.insert(resolvers.automounted,line)
+ resolvers.usezipfile(line)
+ end
+ end
+ end
+ f:close()
+ end
+ end
+ statistics.stoptiming(resolvers.instance)
+ end
+end
+
+-- status info
+
+statistics.register("used config path", function() return caches.configpath() end)
+statistics.register("used cache path", function() return caches.temp() or "?" end)
+
+-- experiment (code will move)
+
+function statistics.save_fmt_status(texname,formatbanner,sourcefile) -- texname == formatname
+ local enginebanner = status.list().banner
+ if formatbanner and enginebanner and sourcefile then
+ local luvname = file.replacesuffix(texname,"luv")
+ local luvdata = {
+ enginebanner = enginebanner,
+ formatbanner = formatbanner,
+ sourcehash = md5.hex(io.loaddata(resolvers.find_file(sourcefile)) or "unknown"),
+ sourcefile = sourcefile,
+ }
+ io.savedata(luvname,table.serialize(luvdata,true))
+ end
+end
+
+function statistics.check_fmt_status(texname)
+ local enginebanner = status.list().banner
+ if enginebanner and texname then
+ local luvname = file.replacesuffix(texname,"luv")
+ if lfs.isfile(luvname) then
+ local luv = dofile(luvname)
+ if luv and luv.sourcefile then
+ local sourcehash = md5.hex(io.loaddata(resolvers.find_file(luv.sourcefile)) or "unknown")
+ if luv.enginebanner and luv.enginebanner ~= enginebanner then
+ return "engine mismatch"
+ end
+ if luv.sourcehash and luv.sourcehash ~= sourcehash then
+ return "source mismatch"
+ end
+ else
+ return "invalid status file"
+ end
+ else
+ return "missing status file"
+ end
+ end
+ return true
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-zip'] = {
+ 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"
+}
+
+local format, find = string.format, string.find
+
+local trace_locating, trace_verbose = false, false
+
+trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+trackers.register("resolvers.locating", function(v) trace_locating = v trace_verbose = v end)
+
+zip = zip or { }
+zip.archives = zip.archives or { }
+zip.registeredfiles = zip.registeredfiles or { }
+
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+local locators, hashers, concatinators = resolvers.locators, resolvers.hashers, resolvers.concatinators
+
+local archives = zip.archives
+
+-- zip:///oeps.zip?name=bla/bla.tex
+-- zip:///oeps.zip?tree=tex/texmf-local
+
+local function validzip(str) -- todo: use url splitter
+ if not find(str,"^zip://") then
+ return "zip:///" .. str
+ else
+ return str
+ end
+end
+
+function zip.openarchive(name)
+ if not name or name == "" then
+ return nil
+ else
+ local arch = archives[name]
+ if not arch then
+ local full = resolvers.find_file(name) or ""
+ arch = (full ~= "" and zip.open(full)) or false
+ archives[name] = arch
+ end
+ return arch
+ end
+end
+
+function zip.closearchive(name)
+ if not name or (name == "" and archives[name]) then
+ zip.close(archives[name])
+ archives[name] = nil
+ end
+end
+
+-- zip:///texmf.zip?tree=/tex/texmf
+-- zip:///texmf.zip?tree=/tex/texmf-local
+-- zip:///texmf-mine.zip?tree=/tex/texmf-projects
+
+function locators.zip(specification) -- where is this used? startup zips (untested)
+ specification = resolvers.splitmethod(specification)
+ local zipfile = specification.path
+ local zfile = zip.openarchive(name) -- tricky, could be in to be initialized tree
+ if trace_locating then
+ if zfile then
+ logs.report("fileio",'! zip locator, found: %s',specification.original)
+ else
+ logs.report("fileio",'? zip locator, not found: %s',specification.original)
+ end
+ end
+end
+
+function hashers.zip(tag,name)
+ if trace_verbose then
+ logs.report("fileio","loading zip file %s as %s",name,tag)
+ end
+ resolvers.usezipfile(format("%s?tree=%s",tag,name))
+end
+
+function concatinators.zip(tag,path,name)
+ if not path or path == "" then
+ return format('%s?name=%s',tag,name)
+ else
+ return format('%s?name=%s/%s',tag,path,name)
+ end
+end
+
+function resolvers.isreadable.zip(name)
+ return true
+end
+
+function finders.zip(specification,filetype)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'! zip finder, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ dfile = zfile:close()
+ if trace_locating then
+ logs.report("fileio",'+ zip finder, name: %s',q.name)
+ end
+ return specification.original
+ end
+ elseif trace_locating then
+ logs.report("fileio",'? zip finder, path %s',specification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip finder, name: %s',filename)
+ end
+ return unpack(finders.notfound)
+end
+
+function openers.zip(specification)
+ local zipspecification = resolvers.splitmethod(specification)
+ if zipspecification.path then
+ local q = url.query(zipspecification.query)
+ if q.name then
+ local zfile = zip.openarchive(zipspecification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',zipspecification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_open(specification)
+ return openers.text_opener(specification,dfile,'zip')
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path %s',zipspecification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip opener, name: %s',filename)
+ end
+ return unpack(openers.notfound)
+end
+
+function loaders.zip(specification)
+ specification = resolvers.splitmethod(specification)
+ if specification.path then
+ local q = url.query(specification.query)
+ if q.name then
+ local zfile = zip.openarchive(specification.path)
+ if zfile then
+ if trace_locating then
+ logs.report("fileio",'+ zip starter, path: %s',specification.path)
+ end
+ local dfile = zfile:open(q.name)
+ if dfile then
+ logs.show_load(filename)
+ if trace_locating then
+ logs.report("fileio",'+ zip loader, name: %s',filename)
+ end
+ local s = dfile:read("*all")
+ dfile:close()
+ return true, s, #s
+ end
+ elseif trace_locating then
+ logs.report("fileio",'- zip starter, path: %s',specification.path)
+ end
+ end
+ end
+ if trace_locating then
+ logs.report("fileio",'- zip loader, name: %s',filename)
+ end
+ return unpack(openers.notfound)
+end
+
+-- zip:///somefile.zip
+-- zip:///somefile.zip?tree=texmf-local -> mount
+
+function resolvers.usezipfile(zipname)
+ zipname = validzip(zipname)
+ if trace_locating then
+ logs.report("fileio",'! zip use, file: %s',zipname)
+ end
+ local specification = resolvers.splitmethod(zipname)
+ local zipfile = specification.path
+ if zipfile and not zip.registeredfiles[zipname] then
+ local tree = url.query(specification.query).tree or ""
+ if trace_locating then
+ logs.report("fileio",'! zip register, file: %s',zipname)
+ end
+ local z = zip.openarchive(zipfile)
+ if z then
+ local instance = resolvers.instance
+ if trace_locating then
+ logs.report("fileio","= zipfile, registering: %s",zipname)
+ end
+ statistics.starttiming(instance)
+ resolvers.prepend_hash('zip',zipname,zipfile)
+ resolvers.extend_texmf_var(zipname) -- resets hashes too
+ zip.registeredfiles[zipname] = z
+ instance.files[zipname] = resolvers.register_zip_file(z,tree or "")
+ statistics.stoptiming(instance)
+ elseif trace_locating then
+ logs.report("fileio","? zipfile, unknown: %s",zipname)
+ end
+ elseif trace_locating then
+ logs.report("fileio",'! zip register, no file: %s',zipname)
+ end
+end
+
+function resolvers.register_zip_file(z,tree)
+ local files, filter = { }, ""
+ if tree == "" then
+ filter = "^(.+)/(.-)$"
+ else
+ filter = format("^%s/(.+)/(.-)$",tree)
+ end
+ if trace_locating then
+ logs.report("fileio",'= zip filter: %s',filter)
+ end
+ local register, n = resolvers.register_file, 0
+ for i in z:files() do
+ local path, name = i.filename:match(filter)
+ if path then
+ if name and name ~= '' then
+ register(files, name, path)
+ n = n + 1
+ else
+ -- directory
+ end
+ else
+ register(files, i.filename, '')
+ n = n + 1
+ end
+ end
+ logs.report("fileio",'= zip entries: %s',n)
+ return files
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-crl'] = {
+ 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"
+}
+
+curl = curl or { }
+
+curl.cached = { }
+curl.cachepath = caches.definepath("curl")
+
+local finders, openers, loaders = resolvers.finders, resolvers.openers, resolvers.loaders
+
+function curl.fetch(protocol, name)
+ local cachename = curl.cachepath() .. "/" .. name:gsub("[^%a%d%.]+","-")
+-- cachename = cachename:gsub("[\\/]", io.fileseparator)
+ cachename = cachename:gsub("[\\]", "/") -- cleanup
+ if not curl.cached[name] then
+ if not io.exists(cachename) then
+ curl.cached[name] = cachename
+ local command = "curl --silent --create-dirs --output " .. cachename .. " " .. name -- no protocol .. "://"
+ os.spawn(command)
+ end
+ if io.exists(cachename) then
+ curl.cached[name] = cachename
+ else
+ curl.cached[name] = ""
+ end
+ end
+ return curl.cached[name]
+end
+
+function finders.curl(protocol,filename)
+ local foundname = curl.fetch(protocol, filename)
+ return finders.generic(protocol,foundname,filetype)
+end
+
+function openers.curl(protocol,filename)
+ return openers.generic(protocol,filename)
+end
+
+function loaders.curl(protocol,filename)
+ return loaders.generic(protocol,filename)
+end
+
+-- todo: metamethod
+
+function curl.install(protocol)
+ finders[protocol] = function (filename,filetype) return finders.curl(protocol,filename) end
+ openers[protocol] = function (filename) return openers.curl(protocol,filename) end
+ loaders[protocol] = function (filename) return loaders.curl(protocol,filename) end
+end
+
+curl.install('http')
+curl.install('https')
+curl.install('ftp')
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['luat-kps'] = {
+ version = 1.001,
+ comment = "companion to luatools.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--[[ldx--
+<p>This file is used when we want the input handlers to behave like
+<type>kpsewhich</type>. What to do with the following:</p>
+
+<typing>
+{$SELFAUTOLOC,$SELFAUTODIR,$SELFAUTOPARENT}{,{/share,}/texmf{-local,}/web2c}
+$SELFAUTOLOC : /usr/tex/bin/platform
+$SELFAUTODIR : /usr/tex/bin
+$SELFAUTOPARENT : /usr/tex
+</typing>
+
+<p>How about just forgetting about them?</p>
+--ldx]]--
+
+local suffixes = resolvers.suffixes
+local formats = resolvers.formats
+
+suffixes['gf'] = { '<resolution>gf' }
+suffixes['pk'] = { '<resolution>pk' }
+suffixes['base'] = { 'base' }
+suffixes['bib'] = { 'bib' }
+suffixes['bst'] = { 'bst' }
+suffixes['cnf'] = { 'cnf' }
+suffixes['mem'] = { 'mem' }
+suffixes['mf'] = { 'mf' }
+suffixes['mfpool'] = { 'pool' }
+suffixes['mft'] = { 'mft' }
+suffixes['mppool'] = { 'pool' }
+suffixes['graphic/figure'] = { 'eps', 'epsi' }
+suffixes['texpool'] = { 'pool' }
+suffixes['PostScript header'] = { 'pro' }
+suffixes['ist'] = { 'ist' }
+suffixes['web'] = { 'web', 'ch' }
+suffixes['cweb'] = { 'w', 'web', 'ch' }
+suffixes['cmap files'] = { 'cmap' }
+suffixes['lig files'] = { 'lig' }
+suffixes['bitmap font'] = { }
+suffixes['MetaPost support'] = { }
+suffixes['TeX system documentation'] = { }
+suffixes['TeX system sources'] = { }
+suffixes['dvips config'] = { }
+suffixes['type42 fonts'] = { }
+suffixes['web2c files'] = { }
+suffixes['other text files'] = { }
+suffixes['other binary files'] = { }
+suffixes['opentype fonts'] = { 'otf' }
+
+suffixes['fmt'] = { 'fmt' }
+suffixes['texmfscripts'] = { 'rb','lua','py','pl' }
+
+suffixes['pdftex config'] = { }
+suffixes['Troff fonts'] = { }
+
+suffixes['ls-R'] = { }
+
+--[[ldx--
+<p>If you wondered abou tsome of the previous mappings, how about
+the next bunch:</p>
+--ldx]]--
+
+formats['bib'] = ''
+formats['bst'] = ''
+formats['mft'] = ''
+formats['ist'] = ''
+formats['web'] = ''
+formats['cweb'] = ''
+formats['MetaPost support'] = ''
+formats['TeX system documentation'] = ''
+formats['TeX system sources'] = ''
+formats['Troff fonts'] = ''
+formats['dvips config'] = ''
+formats['graphic/figure'] = ''
+formats['ls-R'] = ''
+formats['other text files'] = ''
+formats['other binary files'] = ''
+
+formats['gf'] = ''
+formats['pk'] = ''
+formats['base'] = 'MFBASES'
+formats['cnf'] = ''
+formats['mem'] = 'MPMEMS'
+formats['mf'] = 'MFINPUTS'
+formats['mfpool'] = 'MFPOOL'
+formats['mppool'] = 'MPPOOL'
+formats['texpool'] = 'TEXPOOL'
+formats['PostScript header'] = 'TEXPSHEADERS'
+formats['cmap files'] = 'CMAPFONTS'
+formats['type42 fonts'] = 'T42FONTS'
+formats['web2c files'] = 'WEB2C'
+formats['pdftex config'] = 'PDFTEXCONFIG'
+formats['texmfscripts'] = 'TEXMFSCRIPTS'
+formats['bitmap font'] = ''
+formats['lig files'] = 'LIGFONTS'
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-aux'] = {
+ 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"
+}
+
+local find = string.find
+
+local trace_verbose = false trackers.register("resolvers.verbose", function(v) trace_verbose = v end)
+
+function resolvers.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
+ local scriptpath = "scripts/context/lua"
+ newname = file.addsuffix(newname,"lua")
+ local oldscript = resolvers.clean_path(oldname)
+ if trace_verbose then
+ logs.report("fileio","to be replaced old script %s", oldscript)
+ end
+ local newscripts = resolvers.find_files(newname) or { }
+ if #newscripts == 0 then
+ if trace_verbose then
+ logs.report("fileio","unable to locate new script")
+ end
+ else
+ for i=1,#newscripts do
+ local newscript = resolvers.clean_path(newscripts[i])
+ if trace_verbose then
+ logs.report("fileio","checking new script %s", newscript)
+ end
+ if oldscript == newscript then
+ if trace_verbose then
+ logs.report("fileio","old and new script are the same")
+ end
+ elseif not find(newscript,scriptpath) then
+ if trace_verbose then
+ logs.report("fileio","new script should come from %s",scriptpath)
+ end
+ elseif not (find(oldscript,file.removesuffix(newname).."$") or find(oldscript,newname.."$")) then
+ if trace_verbose then
+ logs.report("fileio","invalid new script name")
+ end
+ else
+ local newdata = io.loaddata(newscript)
+ if newdata then
+ if trace_verbose then
+ logs.report("fileio","old script content replaced by new content")
+ end
+ io.savedata(oldscript,newdata)
+ break
+ elseif trace_verbose then
+ logs.report("fileio","unable to load new script")
+ end
+ end
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+if not modules then modules = { } end modules ['data-tmf'] = {
+ 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"
+}
+
+-- loads *.tmf files in minimal tree roots (to be optimized and documented)
+
+function resolvers.check_environment(tree)
+ logs.simpleline()
+ 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'))
+ logs.simpleline()
+ logs.simple("preset : TEXPATH => %s", os.getenv('TEXPATH'))
+ logs.simple("preset : TEXOS => %s", os.getenv('TEXOS'))
+ logs.simple("preset : TEXMFOS => %s", os.getenv('TEXMFOS'))
+ logs.simple("preset : TMP => %s", os.getenv('TMP'))
+ logs.simple('')
+end
+
+function resolvers.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 resolvers.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
+ resolvers.check_environment(tree)
+ resolvers.load_environment(setuptex)
+ end
+ end
+end
+
+
+end -- of closure
+
+do -- create closure to overcome 200 locals limit
+
+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"
+}
+
+-- this code is used in the updater
+
+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
+ if type(d) == "table" 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
+ elseif type(d) == "string" then
+ -- weird
+ states.data[tag], states.hash[tag] = value, value
+ end
+ 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 -- of closure
+-- end library merge
+
+own = { } -- not local
+
+own.libs = { -- todo: check which ones are really needed
+ 'l-string.lua',
+ 'l-lpeg.lua',
+ 'l-table.lua',
+ 'l-io.lua',
+ 'l-number.lua',
+ 'l-set.lua',
+ 'l-os.lua',
+ 'l-file.lua',
+ 'l-md5.lua',
+ 'l-dir.lua',
+ 'l-boolean.lua',
+ 'l-math.lua',
+-- 'l-unicode.lua',
+-- 'l-tex.lua',
+ 'l-utils.lua',
+ 'l-aux.lua',
+-- 'l-xml.lua',
+ 'lxml-tab.lua',
+ 'lxml-pth.lua',
+ 'lxml-ent.lua',
+ 'lxml-mis.lua',
+ 'trac-tra.lua',
+ 'luat-env.lua',
+ 'trac-inf.lua',
+ 'trac-log.lua',
+ 'data-res.lua',
+ 'data-tmp.lua',
+ 'data-pre.lua',
+ 'data-inp.lua',
+ 'data-out.lua',
+ 'data-con.lua',
+ 'data-use.lua',
+-- 'data-tex.lua',
+-- 'data-bin.lua',
+ 'data-zip.lua',
+ 'data-crl.lua',
+-- 'data-lua.lua',
+ 'data-kps.lua', -- so that we can replace kpsewhich
+ 'data-aux.lua', -- updater
+ 'data-tmf.lua', -- tree files
+ -- needed ?
+ 'luat-sta.lua', -- states
+}
+
+-- 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")
+
+local 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 resolvers then
+ locate_libs()
+end
+
+if not resolvers 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
+
+logs.setprogram('MTXrun',"TDS Runner Tool 1.22",environment.arguments["verbose"] or false)
+
+local instance = resolvers.reset()
+
+runners = runners or { } -- global
+messages = messages or { }
+
+messages.help = [[
+--script run an mtx script (--noquotes)
+--execute run a script or program (--noquotes)
+--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 like manuals, assumes os support
+
+--intern run script using built in libraries
+
+--usekpse use kpse as fallback (when no mkiv and cache installed, often slower)
+--forcekpse force using kpse (handy when no mkiv and cache installed but less functionality)
+]]
+
+runners.applications = {
+ ["lua"] = "luatex --luaonly",
+ ["luc"] = "luatex --luaonly",
+ ["pl"] = "perl",
+ ["py"] = "python",
+ ["rb"] = "ruby",
+}
+
+runners.suffixes = {
+ 'rb', 'lua', 'py', 'pl'
+}
+
+runners.registered = {
+ texexec = { 'texexec.rb', true }, -- context mkii runner (only tool not to be luafied)
+ texutil = { 'texutil.rb', true }, -- old perl based index sorter for mkii (old versions need it)
+ texfont = { 'texfont.pl', true }, -- perl script that makes mkii font metric files
+ texfind = { 'texfind.pl', false }, -- perltk based tex searching tool, mostly used at pragma
+ texshow = { 'texshow.pl', false }, -- perltk based context help system, will be luafied
+ -- texwork = { \texwork.pl', false }, -- perltk based editing environment, only used at pragma
+
+ makempy = { 'makempy.pl', true },
+ mptopdf = { 'mptopdf.pl', true },
+ pstopdf = { 'pstopdf.rb', true }, -- converts ps (and some more) images, does some cleaning (replaced)
+
+-- 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 }
+}
+
+runners.launchers = {
+ windows = { },
+ unix = { }
+}
+
+function runners.prepare()
+ local checkname = environment.argument("ifchanged")
+ if checkname and checkname ~= "" then
+ local oldchecksum = file.loadchecksum(checkname)
+ local newchecksum = file.checksum(checkname)
+ if oldchecksum == newchecksum then
+ logs.simple("file '%s' is unchanged",checkname)
+ return "skip"
+ else
+ logs.simple("file '%s' is changed, processing started",checkname)
+ 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
+ logs.simple("file '%s' and '%s' have same age",oldname,newname)
+ return "skip"
+ else
+ logs.simple("file '%s' is older than '%s'",oldname,newname)
+ 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
+ resolvers.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
+ resolvers.load_tree(e)
+ end
+ end
+ local runpath = environment.argument("path")
+ if runpath and not lfs.chdir(runpath) then
+ logs.simple("unable to change to path '%s'",runpath)
+ return "error"
+ end
+ return "run"
+end
+
+function runners.execute_script(fullname,internal)
+ local noquote = environment.argument("noquotes")
+ if fullname and fullname ~= "" then
+ local state = runners.prepare()
+ 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 runners.registered[name] and runners.registered[name][1] then
+ name = runners.registered[name][1]
+ suffix = file.extname(name)
+ end
+ if suffix == "" then
+ -- loop over known suffixes
+ for _,s in pairs(runners.suffixes) do
+ result = resolvers.find_file(name .. "." .. s, 'texmfscripts')
+ if result ~= "" then
+ break
+ end
+ end
+ elseif runners.applications[suffix] then
+ result = resolvers.find_file(name, 'texmfscripts')
+ else
+ -- maybe look on path
+ result = resolvers.find_file(name, 'other text files')
+ end
+ end
+ if result and result ~= "" then
+ local before, after = environment.split_arguments(fullname) -- already done
+ environment.arguments_before, environment.arguments_after = before, after
+ if internal then
+ arg = { } for _,v in pairs(after) do arg[#arg+1] = v end
+ dofile(result)
+ else
+ local binary = runners.applications[file.extname(result)]
+ if binary and binary ~= "" then
+ result = binary .. " " .. result
+ end
+ local command = result .. " " .. environment.reconstruct_commandline(after,noquote)
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
+ io.flush()
+ end
+ local code = os.exec(command) -- maybe spawn
+ return code == 0
+ end
+ end
+ end
+ end
+ return false
+end
+
+function runners.execute_program(fullname)
+ local noquote = environment.argument("noquotes")
+ if fullname and fullname ~= "" then
+ local state = runners.prepare()
+ 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 or "",noquote) or "")
+ logs.simpleline()
+ logs.simple("executing: %s",command)
+ logs.simpleline()
+ logs.simpleline()
+ io.flush()
+ local code = os.exec(command) -- (fullname,unpack(after)) does not work / maybe spawn
+ return code == 0
+ end
+ end
+ return false
+end
+
+-- the --usekpse flag will fallback on kpse
+
+local windows_stub = '@echo off\013\010setlocal\013\010set ownpath=%%~dp0%%\013\010texlua "%%ownpath%%mtxrun.lua" --usekpse --execute %s %%*\013\010endlocal\013\010'
+local unix_stub = '#!/bin/sh\010mtxrun --usekpse --execute %s \"$@\"\010'
+
+function runners.handle_stubs(create)
+ local stubpath = environment.argument('stubpath') or '.' -- 'auto' no longer subpathssupported
+ 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 os.platform == "unix" then
+ unix = true
+ else
+ windows = true
+ end
+ end
+ for _,v in pairs(runners.registered) do
+ local name, doit = v[1], v[2]
+ if doit then
+ local base = string.gsub(file.basename(name), "%.(.-)$", "")
+ if create then
+ if windows then
+ io.savedata(file.join(stubpath,base..".bat"),string.format(windows_stub,name))
+ logs.simple("windows stub for '%s' created",base)
+ end
+ if unix then
+ io.savedata(file.join(stubpath,base),string.format(unix_stub,name))
+ logs.simple("unix stub for '%s' created",base)
+ end
+ else
+ if windows and (os.remove(file.join(stubpath,base..'.bat')) or os.remove(file.join(stubpath,base..'.cmd'))) then
+ logs.simple("windows stub for '%s' removed", base)
+ end
+ if unix and (os.remove(file.join(stubpath,base)) or os.remove(file.join(stubpath,base..'.sh'))) then
+ logs.simple("unix stub for '%s' removed",base)
+ end
+ end
+ end
+ end
+end
+
+function runners.resolve_string(filename)
+ if filename and filename ~= "" then
+ runners.report_location(resolvers.resolve(filename))
+ end
+end
+
+function runners.locate_file(filename)
+ -- differs from texmfstart where locate appends .com .exe .bat ... todo
+ if filename and filename ~= "" then
+ runners.report_location(resolvers.find_given_file(filename))
+ end
+end
+
+function runners.locate_platform()
+ runners.report_location(os.currentplatform())
+end
+
+function runners.report_location(result)
+ if logs.verbose then
+ logs.simpleline()
+ if result and result ~= "" then
+ logs.simple(result)
+ else
+ logs.simple("not found")
+ end
+ else
+ io.write(result)
+ end
+end
+
+function runners.edit_script(filename) -- we assume that vim is present on most systems
+ local editor = os.getenv("MTXRUN_EDITOR") or os.getenv("TEXMFSTART_EDITOR") or os.getenv("EDITOR") or 'vim'
+ local rest = resolvers.resolve(filename)
+ if rest ~= "" then
+ local command = editor .. " " .. rest
+ if logs.verbose then
+ logs.simpleline()
+ logs.simple("starting editor: %s",command)
+ logs.simple_line()
+ logs.simple_line()
+ end
+ os.launch(command)
+ end
+end
+
+function 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 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
+
+function resolvers.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 = 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 runners.launch_file(filename)
+ instance.allresults = true
+ logs.setverbose(true)
+ local pattern = environment.arguments["pattern"]
+ if not pattern or pattern == "" then
+ pattern = filename
+ end
+ if not pattern or pattern == "" then
+ logs.simple("provide name or --pattern=")
+ else
+ local t = resolvers.find_files(pattern)
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern)
+ end
+ if not t or #t == 0 then
+ t = resolvers.find_files("*/" .. pattern .. "*")
+ end
+ if t and #t > 0 then
+ if environment.arguments["all"] then
+ for _, v in pairs(t) do
+ logs.simple("launching %s", v)
+ resolvers.launch(v)
+ end
+ else
+ logs.simple("launching %s", t[1])
+ resolvers.launch(t[1])
+ end
+ else
+ logs.simple("no match for %s", pattern)
+ end
+ end
+end
+
+function runners.find_mtx_script(filename)
+ 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
+ filename = file.addsuffix(filename,"lua")
+ local basename = file.removesuffix(file.basename(filename))
+ local suffix = file.extname(filename)
+ -- qualified path, raw name
+ local fullname = file.is_qualified_path(filename) and io.exists(filename) and filename
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- current path, raw name
+ fullname = "./" .. filename
+ fullname = io.exists(fullname) and fullname
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename>
+ fullname = "mtx-" .. filename
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename>s
+ fullname = "mtx-" .. basename .. "s" .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, mtx-<filename minus trailing s>
+ fullname = "mtx-" .. basename:gsub("s$","") .. "." .. suffix
+ fullname = found(fullname) or resolvers.find_file(fullname)
+ if fullname and fullname ~= "" then
+ return fullname
+ end
+ -- context namespace, just <filename>
+ fullname = resolvers.find_file(filename)
+ return fullname
+end
+
+function runners.execute_ctx_script(filename,arguments)
+ local fullname = runners.find_mtx_script(filename) or ""
+ -- retyr after generate but only if --autogenerate
+ if fullname == "" and environment.argument("autogenerate") then -- might become the default
+ instance.renewcache = true
+ logs.setverbose(true)
+ resolvers.load()
+ --
+ fullname = runners.find_mtx_script(filename) or ""
+ end
+ -- that should do it
+ if fullname ~= "" then
+ local state = runners.prepare()
+ 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] = resolvers.resolve(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")
+ runners.load_script_session(loadname)
+ end
+ filename = environment.files[1]
+ if logs.verbose then
+ logs.simple("using script: %s\n",fullname)
+ end
+ dofile(fullname)
+ local savename = environment.arguments['save']
+ if savename and runners.save_list and not table.is_empty(runners.save_list or { }) then
+ if type(savename) ~= "string" then savename = file.basename(fullname) end
+ savename = file.replacesuffix(savename,"cfg")
+ runners.save_script_session(savename, runners.save_list)
+ end
+ return true
+ end
+ else
+ logs.setverbose(true)
+ if filename == "" then
+ logs.simple("unknown script, no name given")
+ local context = resolvers.find_file("mtx-context.lua")
+ if context ~= "" then
+ local result = dir.glob((string.gsub(context,"mtx%-context","mtx-*"))) -- () needed
+ local valid = { }
+ for _, scriptname in ipairs(result) do
+ scriptname = string.match(scriptname,".*mtx%-([^%-]-)%.lua")
+ if scriptname then
+ valid[#valid+1] = scriptname
+ end
+ end
+ if #valid > 0 then
+ logs.simple("known scripts: %s",table.concat(valid,", "))
+ end
+ end
+ else
+ filename = file.addsuffix(filename,"lua")
+ if file.is_qualified_path(filename) then
+ logs.simple("unknown script '%s'",filename)
+ else
+ logs.simple("unknown script '%s' or 'mtx-%s'",filename,filename)
+ end
+ end
+ return false
+ end
+end
+
+function runners.timedrun(filename) -- just for me
+ if filename and filename ~= "" then
+ runners.timed(function() os.execute(filename) end)
+ end
+end
+
+function runners.timed(action)
+ statistics.timed(action)
+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)
+environment.arguments_before, environment.arguments_after = before, after
+environment.initialize_arguments(before)
+
+instance.engine = environment.argument("engine") or 'luatex'
+instance.progname = environment.argument("progname") or 'context'
+instance.lsrmode = environment.argument("lsr") or false
+
+-- maybe the unset has to go to this level
+
+if environment.argument("usekpse") or environment.argument("forcekpse") then
+
+ os.setenv("engine","")
+ os.setenv("progname","")
+
+ local remapper = {
+ otf = "opentype fonts",
+ ttf = "truetype fonts",
+ ttc = "truetype fonts",
+ pfb = "type1 fonts",
+ other = "other text files",
+ }
+
+ local function kpse_initialized()
+ texconfig.kpse_init = true
+ local t = os.clock()
+ local k = kpse.original.new("luatex",instance.progname)
+ local dummy = k:find_file("mtxrun.lua") -- so that we're initialized
+ logs.simple("kpse fallback with progname '%s' initialized in %s seconds",instance.progname,os.clock()-t)
+ kpse_initialized = function() return k end
+ return k
+ end
+
+ local find_file = resolvers.find_file
+ local show_path = resolvers.show_path
+
+ if environment.argument("forcekpse") then
+
+ function resolvers.find_file(name,kind)
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ function resolvers.show_path(name)
+ return (kpse_initialized():show_path(name)) or ""
+ end
+
+ elseif environment.argument("usekpse") then
+
+ resolvers.load()
+
+ function resolvers.find_file(name,kind)
+ local found = find_file(name,kind) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():find_file(resolvers.clean_path(name),(kind ~= "" and (remapper[kind] or kind)) or "tex") or "") or ""
+ end
+ end
+ function resolvers.show_path(name)
+ local found = show_path(name) or ""
+ if found ~= "" then
+ return found
+ else
+ return (kpse_initialized():show_path(name)) or ""
+ end
+ end
+
+ end
+
+else
+
+ resolvers.load()
+
+end
+
+
+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
+ logs.setverbose(true)
+ resolvers.update_script(own.name,"mtxrun")
+elseif environment.argument("ctxlua") or environment.argument("internal") then
+ -- run a script by loading it (using libs)
+ ok = runners.execute_script(filename,true)
+elseif environment.argument("script") or environment.argument("s") then
+ -- run a script by loading it (using libs), pass args
+ ok = runners.execute_ctx_script(filename,after)
+elseif environment.argument("execute") then
+ -- execute script
+ ok = runners.execute_script(filename)
+elseif environment.argument("direct") then
+ -- equals bin:
+ ok = runners.execute_program(filename)
+elseif environment.argument("edit") then
+ -- edit file
+ runners.edit_script(filename)
+elseif environment.argument("launch") then
+ runners.launch_file(filename)
+elseif environment.argument("make") then
+ -- make stubs
+ runners.handle_stubs(true)
+elseif environment.argument("remove") then
+ -- remove stub
+ runners.handle_stubs(false)
+elseif environment.argument("resolve") then
+ -- resolve string
+ runners.resolve_string(filename)
+elseif environment.argument("locate") then
+ -- locate file
+ runners.locate_file(filename)
+elseif environment.argument("platform")then
+ -- locate platform
+ runners.locate_platform()
+elseif environment.argument("timedrun") then
+ -- locate platform
+ runners.timedrun(filename)
+elseif environment.argument("help") or filename=='help' or filename == "" then
+ logs.help(messages.help)
+ -- execute script
+elseif filename:find("^bin:") then
+ ok = runners.execute_program(filename)
+else
+ ok = runners.execute_script(filename)
+end
+
+if os.platform == "unix" then
+ io.write("\n")
+end
+
+if ok == false then ok = 1 elseif ok == true then ok = 0 end
+
+os.exit(ok)
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mtxtools b/Master/texmf-dist/scripts/context/stubs/unix/mtxtools
index 2922e0b4674..3803c1c6f4e 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/mtxtools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/mtxtools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart mtxtools.rb "$@"
+mtxrun --usekpse --execute mtxtools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mtxworks b/Master/texmf-dist/scripts/context/stubs/unix/mtxworks
new file mode 100644
index 00000000000..ef8f230c3a3
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/unix/mtxworks
@@ -0,0 +1,2 @@
+#!/bin/sh
+mtxrun --script texworks --start
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/pdftools b/Master/texmf-dist/scripts/context/stubs/unix/pdftools
index 92ee803a868..da7bd64cf2a 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/pdftools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/pdftools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart pdftools.rb "$@"
+mtxrun --usekpse --execute pdftools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/pdftrimwhite b/Master/texmf-dist/scripts/context/stubs/unix/pdftrimwhite
deleted file mode 100755
index 00b5f525aec..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/unix/pdftrimwhite
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-texmfstart pdftrimwhite.pl "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/pstopdf b/Master/texmf-dist/scripts/context/stubs/unix/pstopdf
index 5b38ed426cb..059812cceac 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/pstopdf
+++ b/Master/texmf-dist/scripts/context/stubs/unix/pstopdf
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart pstopdf.rb "$@"
+mtxrun --usekpse --execute pstopdf.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/rlxtools b/Master/texmf-dist/scripts/context/stubs/unix/rlxtools
index 41cea40fc09..d01987b3c1f 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/rlxtools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/rlxtools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart rlxtools.rb "$@"
+mtxrun --usekpse --execute rlxtools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/runtools b/Master/texmf-dist/scripts/context/stubs/unix/runtools
index ff9a333791b..e21c1a24405 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/runtools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/runtools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart runtools.rb "$@"
+mtxrun --usekpse --execute runtools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texexec b/Master/texmf-dist/scripts/context/stubs/unix/texexec
index 2158172906a..083e500c69c 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/texexec
+++ b/Master/texmf-dist/scripts/context/stubs/unix/texexec
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart texexec.rb "$@"
+mtxrun --usekpse --execute texexec.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texfind b/Master/texmf-dist/scripts/context/stubs/unix/texfind
deleted file mode 100755
index c054bdf5218..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/unix/texfind
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-texmfstart texfind "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texfont b/Master/texmf-dist/scripts/context/stubs/unix/texfont
index a91f786e3f8..bc811a640af 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/texfont
+++ b/Master/texmf-dist/scripts/context/stubs/unix/texfont
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart texfont.pl "$@"
+mtxrun --usekpse --execute texfont.pl "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texmfstart b/Master/texmf-dist/scripts/context/stubs/unix/texmfstart
new file mode 100644
index 00000000000..1799b357915
--- /dev/null
+++ b/Master/texmf-dist/scripts/context/stubs/unix/texmfstart
@@ -0,0 +1,2 @@
+#!/bin/sh
+mtxrun --usekpse "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texshow b/Master/texmf-dist/scripts/context/stubs/unix/texshow
deleted file mode 100755
index afd62c339b3..00000000000
--- a/Master/texmf-dist/scripts/context/stubs/unix/texshow
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-texmfstart texshow.pl "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/textools b/Master/texmf-dist/scripts/context/stubs/unix/textools
index 7445eac371c..76087ca5729 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/textools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/textools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart textools.rb "$@"
+mtxrun --usekpse --execute textools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/texutil b/Master/texmf-dist/scripts/context/stubs/unix/texutil
index 607154af059..f5d9b6f1d81 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/texutil
+++ b/Master/texmf-dist/scripts/context/stubs/unix/texutil
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart texutil.rb "$@"
+mtxrun --usekpse --execute texutil.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/tmftools b/Master/texmf-dist/scripts/context/stubs/unix/tmftools
index 7531a966390..48d32f0fd3a 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/tmftools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/tmftools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart tmftools.rb "$@"
+mtxrun --usekpse --execute tmftools.rb "$@"
diff --git a/Master/texmf-dist/scripts/context/stubs/unix/xmltools b/Master/texmf-dist/scripts/context/stubs/unix/xmltools
index 03086d0436b..a673d1e7a03 100755
--- a/Master/texmf-dist/scripts/context/stubs/unix/xmltools
+++ b/Master/texmf-dist/scripts/context/stubs/unix/xmltools
@@ -1,2 +1,2 @@
#!/bin/sh
-texmfstart xmltools.rb "$@"
+mtxrun --usekpse --execute xmltools.rb "$@"