-- filename : luat-inp.lua
-- comment : companion to luat-lib.tex
-- author : Hans Hagen, PRAGMA-ADE, Hasselt NL
-- copyright: PRAGMA ADE / ConTeXt Development Team
-- license : see context related readme files
-- This lib is multi-purpose and can be loaded again later on so that
-- additional functionality becomes available. We will split this
-- module in components when we're done with prototyping.
-- TODO: os.getenv -> os.env[]
-- TODO: instances.[hashes,cnffiles,configurations,522] -> ipairs (alles check, sneller)
-- TODO: check escaping in find etc, too much, too slow
-- This is the first code I wrote for LuaTeX, so it needs some cleanup.
-- To be considered: hash key lowercase, first entry in table filename
-- (any case), rest paths (so no need for optimization). Or maybe a
-- separate table that matches lowercase names to mixed case when
-- present. In that case the lower() cases can go away. I will do that
-- only when we run into problems with names ... well ... Iwona-Regular.
-- Beware, loading and saving is overloaded in luat-tmp!
if not versions then versions = { } end versions['luat-inp'] = 1.001
if not environment then environment = { } end
if not file then file = { } end
if environment.aleph_mode == nil then environment.aleph_mode = true end -- temp hack
if not input then input = { } end
if not input.suffixes then input.suffixes = { } end
if not input.formats then input.formats = { } end
if not input.aux then input.aux = { } end
if not input.suffixmap then input.suffixmap = { } end
if not input.locators then input.locators = { } end -- locate databases
if not input.hashers then input.hashers = { } end -- load databases
if not input.generators then input.generators = { } end -- generate databases
if not input.filters then input.filters = { } end -- conversion filters
local format = string.format
input.locators.notfound = { nil }
input.hashers.notfound = { nil }
input.generators.notfound = { nil }
input.cacheversion = '1.0.1'
input.banner = nil
input.verbose = false
input.debug = false
input.cnfname = 'texmf.cnf'
input.luaname = 'texmfcnf.lua'
input.lsrname = 'ls-R'
input.luasuffix = '.tma'
input.lucsuffix = '.tmc'
-- we use a cleaned up list / format=any is a wildcard, as is *name
input.formats['afm'] = 'AFMFONTS' input.suffixes['afm'] = { 'afm' }
input.formats['enc'] = 'ENCFONTS' input.suffixes['enc'] = { 'enc' }
input.formats['fmt'] = 'TEXFORMATS' input.suffixes['fmt'] = { 'fmt' }
input.formats['map'] = 'TEXFONTMAPS' input.suffixes['map'] = { 'map' }
input.formats['mp'] = 'MPINPUTS' input.suffixes['mp'] = { 'mp' }
input.formats['ocp'] = 'OCPINPUTS' input.suffixes['ocp'] = { 'ocp' }
input.formats['ofm'] = 'OFMFONTS' input.suffixes['ofm'] = { 'ofm', 'tfm' }
input.formats['otf'] = 'OPENTYPEFONTS' input.suffixes['otf'] = { 'otf' } -- 'ttf'
input.formats['opl'] = 'OPLFONTS' input.suffixes['opl'] = { 'opl' }
input.formats['otp'] = 'OTPINPUTS' input.suffixes['otp'] = { 'otp' }
input.formats['ovf'] = 'OVFFONTS' input.suffixes['ovf'] = { 'ovf', 'vf' }
input.formats['ovp'] = 'OVPFONTS' input.suffixes['ovp'] = { 'ovp' }
input.formats['tex'] = 'TEXINPUTS' input.suffixes['tex'] = { 'tex' }
input.formats['tfm'] = 'TFMFONTS' input.suffixes['tfm'] = { 'tfm' }
input.formats['ttf'] = 'TTFONTS' input.suffixes['ttf'] = { 'ttf', 'ttc' }
input.formats['pfb'] = 'T1FONTS' input.suffixes['pfb'] = { 'pfb', 'pfa' }
input.formats['vf'] = 'VFFONTS' input.suffixes['vf'] = { 'vf' }
input.formats['fea'] = 'FONTFEATURES' input.suffixes['fea'] = { 'fea' }
input.formats['cid'] = 'FONTCIDMAPS' input.suffixes['cid'] = { 'cid', 'cidmap' }
input.formats ['texmfscripts'] = 'TEXMFSCRIPTS' -- new
input.suffixes['texmfscripts'] = { 'rb', 'pl', 'py' } -- 'lua'
input.formats ['lua'] = 'LUAINPUTS' -- new
input.suffixes['lua'] = { 'lua', 'luc', 'tma', 'tmc' }
-- here we catch a few new thingies (todo: add these paths to context.tmf)
--
-- FONTFEATURES = .;$TEXMF/fonts/fea//
-- FONTCIDMAPS = .;$TEXMF/fonts/cid//
function input.checkconfigdata(instance) -- not yet ok, no time for debugging now
local function fix(varname,default)
local proname = varname .. "." .. instance.progname or "crap"
local p = instance.environment[proname]
local v = instance.environment[varname]
if not ((p and p ~= "") or (v and v ~= "")) then
instance.variables[varname] = default -- or environment?
end
end
fix("LUAINPUTS" , ".;$TEXINPUTS;$TEXMFSCRIPTS")
fix("FONTFEATURES", ".;$TEXMF/fonts/fea//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
fix("FONTCIDMAPS" , ".;$TEXMF/fonts/cid//;$OPENTYPEFONTS;$TTFONTS;$T1FONTS;$AFMFONTS")
end
-- backward compatible ones
input.alternatives = { }
input.alternatives['map files'] = 'map'
input.alternatives['enc files'] = 'enc'
input.alternatives['cid files'] = 'cid'
input.alternatives['fea files'] = 'fea'
input.alternatives['opentype fonts'] = 'otf'
input.alternatives['truetype fonts'] = 'ttf'
input.alternatives['truetype collections'] = 'ttc'
input.alternatives['type1 fonts'] = 'pfb'
-- obscure ones
input.formats ['misc fonts'] = ''
input.suffixes['misc fonts'] = { }
input.formats ['sfd'] = 'SFDFONTS'
input.suffixes ['sfd'] = { 'sfd' }
input.alternatives['subfont definition files'] = 'sfd'
function input.reset()
local instance = { }
instance.rootpath = ''
instance.treepath = ''
instance.progname = environment.progname or 'context'
instance.engine = environment.engine or 'luatex'
instance.format = ''
instance.environment = { }
instance.variables = { }
instance.expansions = { }
instance.files = { }
instance.remap = { }
instance.configuration = { }
instance.setup = { }
instance.order = { }
instance.found = { }
instance.foundintrees = { }
instance.kpsevars = { }
instance.hashes = { }
instance.cnffiles = { }
instance.luafiles = { }
instance.lists = { }
instance.remember = true
instance.diskcache = true
instance.renewcache = false
instance.scandisk = true
instance.cachepath = nil
instance.loaderror = false
instance.smallcache = false
instance.savelists = true
instance.cleanuppaths = true
instance.allresults = false
instance.pattern = nil -- lists
instance.kpseonly = false -- lists
instance.loadtime = 0
instance.starttime = 0
instance.stoptime = 0
instance.validfile = function(path,name) return true end
instance.data = { } -- only for loading
instance.force_suffixes = true
instance.dummy_path_expr = "^!*unset/*$"
instance.fakepaths = { }
instance.lsrmode = false
if os.env then
-- store once, freeze and faster
for k,v in pairs(os.env) do
instance.environment[k] = input.bare_variable(v)
end
else
-- we will access os.env frequently
for k,v in pairs({'HOME','TEXMF','TEXMFCNF'}) do
local e = os.getenv(v)
if e then
-- input.report("setting",v,"to",input.bare_variable(e))
instance.environment[v] = input.bare_variable(e)
end
end
end
-- cross referencing
for k, v in pairs(input.suffixes) do
for _, vv in pairs(v) do
if vv then
input.suffixmap[vv] = k
end
end
end
return instance
end
function input.reset_hashes(instance)
instance.lists = { }
instance.found = { }
end
function input.bare_variable(str) -- assumes str is a string
-- return string.gsub(string.gsub(string.gsub(str,"%s+$",""),'^"(.+)"$',"%1"),"^'(.+)'$","%1")
return (str:gsub("\s*([\"\']?)(.+)%1\s*", "%2"))
end
if texio then
input.log = texio.write_nl
else
input.log = print
end
function input.simple_logger(kind, name)
if name and name ~= "" then
if input.banner then
input.log(input.banner..kind..": "..name)
else
input.log("<<"..kind..": "..name..">>")
end
else
if input.banner then
input.log(input.banner..kind..": no name")
else
input.log("<<"..kind..": no name>>")
end
end
end
function input.dummy_logger()
end
function input.settrace(n)
input.trace = tonumber(n or 0)
if input.trace > 0 then
input.logger = input.simple_logger
input.verbose = true
else
input.logger = function() end
end
end
function input.report(...) -- inefficient
if input.verbose then
if input.banner then
input.log(input.banner .. table.concat({...},' '))
elseif input.logmode() == 'xml' then
input.log(""..table.concat({...},' ').."")
else
input.log("<<"..table.concat({...},' ')..">>")
end
end
end
function input.reportlines(str)
if type(str) == "string" then
str = str:split("\n")
end
for _,v in pairs(str) do input.report(v) end
end
input.settrace(tonumber(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0))
-- These functions can be used to test the performance, especially
-- loading the database files.
do
local clock = os.gettimeofday or os.clock
function input.starttiming(instance)
if instance then
instance.starttime = clock()
if not instance.loadtime then
instance.loadtime = 0
end
end
end
function input.stoptiming(instance, report)
if instance then
local starttime = instance.starttime
if starttime then
local stoptime = clock()
local loadtime = stoptime - starttime
instance.stoptime = stoptime
instance.loadtime = instance.loadtime + loadtime
if report then
input.report('load time', format("%0.3f",loadtime))
end
return loadtime
end
end
return 0
end
end
function input.elapsedtime(instance)
return format("%0.3f",(instance and instance.loadtime) or 0)
end
function input.report_loadtime(instance)
if instance then
input.report('total load time', input.elapsedtime(instance))
end
end
input.loadtime = input.elapsedtime
function input.env(instance,key)
return instance.environment[key] or input.osenv(instance,key)
end
function input.osenv(instance,key)
local ie = instance.environment
local value = ie[key]
if value == nil then
-- local e = os.getenv(key)
local e = os.env[key]
if e == nil then
-- value = "" -- false
else
value = input.bare_variable(e)
end
ie[key] = value
end
return value or ""
end
-- we follow a rather traditional approach:
--
-- (1) texmf.cnf given in TEXMFCNF
-- (2) texmf.cnf searched in TEXMF/web2c
--
-- for the moment we don't expect a configuration file in a zip
function input.identify_cnf(instance)
-- we no longer support treepath and rootpath (was handy for testing);
-- also we now follow the stupid route: if not set then just assume *one*
-- cnf file under texmf (i.e. distribution)
if #instance.cnffiles == 0 then
if input.env(instance,'TEXMFCNF') == "" then
local ownpath = environment.ownpath() or "."
if ownpath then
-- beware, this is tricky on my own system because at that location I do have
-- the raw tree that ends up in the zip; i.e. I cannot test this kind of mess
local function locate(filename,list)
local ownroot = input.normalize_name(file.join(ownpath,"../.."))
if not lfs.isdir(file.join(ownroot,"texmf")) then
ownroot = input.normalize_name(file.join(ownpath,".."))
if not lfs.isdir(file.join(ownroot,"texmf")) then
input.verbose = true
input.report("error", "unable to identify cnf file")
return
end
end
local texmfcnf = file.join(ownroot,"texmf-local/web2c",filename) -- for minimals and myself
if not lfs.isfile(texmfcnf) then
texmfcnf = file.join(ownroot,"texmf/web2c",filename)
if not lfs.isfile(texmfcnf) then
input.verbose = true
input.report("error", "unable to locate",filename)
return
end
end
table.insert(list,texmfcnf)
local ie = instance.environment
if not ie['SELFAUTOPARENT'] then ie['SELFAUTOPARENT'] = ownroot end
if not ie['TEXMFCNF'] then ie['TEXMFCNF'] = file.dirname(texmfcnf) end
end
locate(input.luaname,instance.luafiles)
locate(input.cnfname,instance.cnffiles)
if #instance.luafiles == 0 and instance.cnffiles == 0 then
input.verbose = true
input.report("error", "unable to locate",filename)
os.exit()
end
-- here we also assume then TEXMF is set in the distribution, if this trickery is
-- used in the minimals, then users who don't use setuptex are on their own with
-- regards to extra trees
else
input.verbose = true
input.report("error", "unable to identify own path")
os.exit()
end
else
local t = input.split_path(input.env(instance,'TEXMFCNF'))
t = input.aux.expanded_path(instance,t)
input.aux.expand_vars(instance,t)
local function locate(filename,list)
for _,v in ipairs(t) do
local texmfcnf = input.normalize_name(file.join(v,filename))
if lfs.isfile(texmfcnf) then
table.insert(list,texmfcnf)
end
end
end
locate(input.luaname,instance.luafiles)
locate(input.cnfname,instance.cnffiles)
end
end
end
function input.load_cnf(instance)
local function loadoldconfigdata()
for _, fname in ipairs(instance.cnffiles) do
input.aux.load_cnf(instance,fname)
end
end
-- instance.cnffiles contain complete names now !
if #instance.cnffiles == 0 then
input.report("no cnf files found (TEXMFCNF may not be set/known)")
else
instance.rootpath = instance.cnffiles[1]
for k,fname in ipairs(instance.cnffiles) do
instance.cnffiles[k] = input.normalize_name(fname:gsub("\\",'/'))
end
for i=1,3 do
instance.rootpath = file.dirname(instance.rootpath)
end
instance.rootpath = input.normalize_name(instance.rootpath)
instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
if instance.lsrmode then
loadoldconfigdata()
elseif instance.diskcache and not instance.renewcache then
input.loadoldconfig(instance,instance.cnffiles)
if instance.loaderror then
loadoldconfigdata()
input.saveoldconfig(instance)
end
else
loadoldconfigdata()
if instance.renewcache then
input.saveoldconfig(instance)
end
end
input.aux.collapse_cnf_data(instance)
end
input.checkconfigdata(instance)
end
function input.load_lua(instance)
if #instance.luafiles == 0 then
-- yet harmless
else
instance.rootpath = instance.luafiles[1]
for k,fname in ipairs(instance.luafiles) do
instance.luafiles[k] = input.normalize_name(fname:gsub("\\",'/'))
end
for i=1,3 do
instance.rootpath = file.dirname(instance.rootpath)
end
instance.rootpath = input.normalize_name(instance.rootpath)
instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
input.loadnewconfig(instance)
input.aux.collapse_cnf_data(instance)
end
input.checkconfigdata(instance)
end
function input.aux.collapse_cnf_data(instance) -- potential optmization: pass start index (setup and configuration are shared)
for _,c in ipairs(instance.order) do
for k,v in pairs(c) do
if not instance.variables[k] then
if instance.environment[k] then
instance.variables[k] = instance.environment[k]
else
instance.kpsevars[k] = true
instance.variables[k] = input.bare_variable(v)
end
end
end
end
end
function input.aux.load_cnf(instance,fname)
fname = input.clean_path(fname)
local lname = fname:gsub("%.%a+$",input.luasuffix)
local f = io.open(lname)
if f then -- this will go
f:close()
local dname = file.dirname(fname)
if not instance.configuration[dname] then
input.aux.load_configuration(instance,dname,lname)
instance.order[#instance.order+1] = instance.configuration[dname]
end
else
f = io.open(fname)
if f then
input.report("loading", fname)
local line, data, n, k, v
local dname = file.dirname(fname)
if not instance.configuration[dname] then
instance.configuration[dname] = { }
instance.order[#instance.order+1] = instance.configuration[dname]
end
local data = instance.configuration[dname]
while true do
local line, n = f:read(), 0
if line then
while true do -- join lines
line, n = line:gsub("\\%s*$", "")
if n > 0 then
line = line .. f:read()
else
break
end
end
if not line:find("^[%%#]") then
local k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
if k and v and not data[k] then
data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
instance.kpsevars[k] = true
end
end
else
break
end
end
f:close()
else
input.report("skipping", fname)
end
end
end
-- database loading
function input.load_hash(instance)
input.locatelists(instance)
if instance.lsrmode then
input.loadlists(instance)
elseif instance.diskcache and not instance.renewcache then
input.loadfiles(instance)
if instance.loaderror then
input.loadlists(instance)
input.savefiles(instance)
end
else
input.loadlists(instance)
if instance.renewcache then
input.savefiles(instance)
end
end
end
function input.aux.append_hash(instance,type,tag,name)
input.logger("= hash append",tag)
table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
end
function input.aux.prepend_hash(instance,type,tag,name)
input.logger("= hash prepend",tag)
table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
end
function input.aux.extend_texmf_var(instance,specification) -- crap
if instance.environment['TEXMF'] then
input.report("extending environment variable TEXMF with", specification)
instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
return "{" .. specification .. ","
end)
elseif instance.variables['TEXMF'] then
input.report("extending configuration variable TEXMF with", specification)
instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
return "{" .. specification .. ","
end)
else
input.report("setting configuration variable TEXMF to", specification)
instance.variables['TEXMF'] = "{" .. specification .. "}"
end
if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
input.report("adding {} to complex TEXMF variable, best do that yourself")
instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
end
input.expand_variables(instance)
input.reset_hashes(instance)
end
-- locators
function input.locatelists(instance)
for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
path = file.collapse_path(path)
input.report("locating list of",path)
input.locatedatabase(instance,input.normalize_name(path))
end
end
function input.locatedatabase(instance,specification)
return input.methodhandler('locators', instance, specification)
end
function input.locators.tex(instance,specification)
if specification and specification ~= '' and lfs.isdir(specification) then
input.logger('! tex locator', specification..' found')
input.aux.append_hash(instance,'file',specification,filename)
else
input.logger('? tex locator', specification..' not found')
end
end
-- hashers
function input.hashdatabase(instance,tag,name)
return input.methodhandler('hashers',instance,tag,name)
end
function input.loadfiles(instance)
instance.loaderror = false
instance.files = { }
if not instance.renewcache then
for _, hash in ipairs(instance.hashes) do
input.hashdatabase(instance,hash.tag,hash.name)
if instance.loaderror then break end
end
end
end
function input.hashers.tex(instance,tag,name)
input.aux.load_files(instance,tag)
end
-- generators:
function input.loadlists(instance)
for _, hash in ipairs(instance.hashes) do
input.generatedatabase(instance,hash.tag)
end
end
function input.generatedatabase(instance,specification)
return input.methodhandler('generators', instance, specification)
end
do
local weird = lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
function input.generators.tex(instance,specification)
local tag = specification
if not instance.lsrmode and lfs and lfs.dir then
input.report("scanning path",specification)
instance.files[tag] = { }
local files = instance.files[tag]
local n, m, r = 0, 0, 0
local spec = specification .. '/'
local attributes = lfs.attributes
local directory = lfs.dir
local small = instance.smallcache
local function action(path)
local mode, full
if path then
full = spec .. path .. '/'
else
full = spec
end
for name in directory(full) do
if name:find("^%.") then
-- skip
-- elseif name:find("[%~%`%!%#%$%%%^%&%*%(%)%=%{%}%[%]%:%;\"\'%|%<%>%,%?\n\r\t]") then -- too much escaped
elseif weird:match(name) then
-- texio.write_nl("skipping " .. name)
-- skip
else
mode = attributes(full..name,'mode')
if mode == "directory" then
m = m + 1
if path then
action(path..'/'..name)
else
action(name)
end
elseif path and mode == 'file' then
n = n + 1
local f = files[name]
if f then
if not small then
if type(f) == 'string' then
files[name] = { f, path }
else
f[#f+1] = path
end
end
else
files[name] = path
local lower = name:lower()
if name ~= lower then
files["remap:"..lower] = name
r = r + 1
end
end
end
end
end
end
action()
input.report(format("%s files found on %s directories with %s uppercase remappings",n,m,r))
else
local fullname = file.join(specification,input.lsrname)
local path = '.'
local f = io.open(fullname)
if f then
instance.files[tag] = { }
local files = instance.files[tag]
local small = instance.smallcache
input.report("loading lsr file",fullname)
-- for line in f:lines() do -- much slower then the next one
for line in (f:read("*a")):gmatch("(.-)\n") do
if line:find("^[%a%d]") then
local fl = files[line]
if fl then
if not small then
if type(fl) == 'string' then
files[line] = { fl, path } -- table
else
fl[#fl+1] = path
end
end
else
files[line] = path -- string
local lower = line:lower()
if line ~= lower then
files["remap:"..lower] = line
end
end
else
path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
end
end
f:close()
end
end
end
end
-- savers, todo
function input.savefiles(instance)
input.aux.save_data(instance, 'files', function(k,v)
return instance.validfile(k,v) -- path, name
end)
end
-- A config (optionally) has the paths split in tables. Internally
-- we join them and split them after the expansion has taken place. This
-- is more convenient.
function input.splitconfig(instance)
for i,c in ipairs(instance) do
for k,v in pairs(c) do
if type(v) == 'string' then
local t = file.split_path(v)
if #t > 1 then
c[k] = t
end
end
end
end
end
function input.joinconfig(instance)
for i,c in ipairs(instance.order) do
for k,v in pairs(c) do
if type(v) == 'table' then
c[k] = file.join_path(v)
end
end
end
end
function input.split_path(str)
if type(str) == 'table' then
return str
else
return file.split_path(str)
end
end
function input.join_path(str)
if type(str) == 'table' then
return file.join_path(str)
else
return str
end
end
function input.splitexpansions(instance)
for k,v in pairs(instance.expansions) do
local t, h = { }, { }
for _,vv in pairs(file.split_path(v)) do
if vv ~= "" and not h[vv] then
t[#t+1] = vv
h[vv] = true
end
end
if #t > 1 then
instance.expansions[k] = t
else
instance.expansions[k] = t[1]
end
end
end
-- end of split/join code
function input.saveoldconfig(instance)
input.splitconfig(instance)
input.aux.save_data(instance, 'configuration', nil)
input.joinconfig(instance)
end
input.configbanner = [[
-- This is a Luatex configuration file created by 'luatools.lua' or
-- 'luatex.exe' directly. For comment, suggestions and questions you can
-- contact the ConTeXt Development Team. This configuration file is
-- not copyrighted. [HH & TH]
]]
function input.serialize(files)
-- This version is somewhat optimized for the kind of
-- tables that we deal with, so it's much faster than
-- the generic serializer. This makes sense because
-- luatools and mtxtools are called frequently. Okay,
-- we pay a small price for properly tabbed tables.
local t = { }
local concat = table.concat
local sorted = table.sortedkeys
local function dump(k,v,m)
if type(v) == 'string' then
return m .. "['" .. k .. "']='" .. v .. "',"
elseif #v == 1 then
return m .. "['" .. k .. "']='" .. v[1] .. "',"
else
return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
end
end
t[#t+1] = "return {"
if instance.sortdata then
for _, k in pairs(sorted(files)) do
local fk = files[k]
if type(fk) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
for _, kk in pairs(sorted(fk)) do
t[#t+1] = dump(kk,fk[kk],"\t\t")
end
t[#t+1] = "\t},"
else
t[#t+1] = dump(k,fk,"\t")
end
end
else
for k, v in pairs(files) do
if type(v) == 'table' then
t[#t+1] = "\t['" .. k .. "']={"
for kk,vv in pairs(v) do
t[#t+1] = dump(kk,vv,"\t\t")
end
t[#t+1] = "\t},"
else
t[#t+1] = dump(k,v,"\t")
end
end
end
t[#t+1] = "}"
return concat(t,"\n")
end
if not texmf then texmf = {} end -- no longer needed, at least not here
function input.aux.save_data(instance, dataname, check, makename) -- untested without cache overload
for cachename, files in pairs(instance[dataname]) do
local name = (makename or file.join)(cachename,dataname)
local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
input.report("preparing " .. dataname .. " for", luaname)
for k, v in pairs(files) do
if not check or check(v,k) then -- path, name
if type(v) == "table" and #v == 1 then
files[k] = v[1]
end
else
files[k] = nil -- false
end
end
local data = {
type = dataname,
root = cachename,
version = input.cacheversion,
date = os.date("%Y-%m-%d"),
time = os.date("%H:%M:%S"),
content = files,
}
local f = io.open(luaname,'w')
if f then
input.report("saving " .. dataname .. " in", luaname)
f:write(input.serialize(data))
f:close()
input.report("compiling " .. dataname .. " to", lucname)
if not utils.lua.compile(luaname,lucname) then
input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
os.remove(lucname)
end
else
input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
end
end
end
function input.aux.load_data(instance,pathname,dataname,filename,makename) -- untested without cache overload
filename = ((not filename or (filename == "")) and dataname) or filename
filename = (makename and makename(dataname,filename)) or file.join(pathname,filename)
local blob = loadfile(filename .. input.lucsuffix) or loadfile(filename .. input.luasuffix)
if blob then
local data = blob()
if data and data.content and data.type == dataname and data.version == input.cacheversion then
input.report("loading",dataname,"for",pathname,"from",filename)
instance[dataname][pathname] = data.content
else
input.report("skipping",dataname,"for",pathname,"from",filename)
instance[dataname][pathname] = { }
instance.loaderror = true
end
else
input.report("skipping",dataname,"for",pathname,"from",filename)
end
end
-- some day i'll use the nested approach, but not yet (actually we even drop
-- engine/progname support since we have only luatex now)
--
-- first texmfcnf.lua files are located, next the cached texmf.cnf files
--
-- return {
-- TEXMFBOGUS = 'effe checken of dit werkt',
-- }
function input.aux.load_texmfcnf(instance,dataname,pathname)
local filename = file.join(pathname,input.luaname)
local blob = loadfile(filename)
if blob then
local data = blob()
if data then
input.report("loading","configuration file",filename)
if true then
-- flatten to variable.progname
local t = { }
for k, v in pairs(data) do -- v = progname
if type(v) == "string" then
t[k] = v
else
for kk, vv in pairs(v) do -- vv = variable
if type(vv) == "string" then
t[vv.."."..v] = kk
end
end
end
end
instance[dataname][pathname] = t
else
instance[dataname][pathname] = data
end
else
input.report("skipping","configuration file",filename)
instance[dataname][pathname] = { }
instance.loaderror = true
end
else
input.report("skipping","configuration file",filename)
end
end
function input.aux.load_configuration(instance,dname,lname)
input.aux.load_data(instance,dname,'configuration',lname and file.basename(lname))
end
function input.aux.load_files(instance,tag)
input.aux.load_data(instance,tag,'files')
end
function input.resetconfig(instance)
instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
end
function input.loadnewconfig(instance)
for _, cnf in ipairs(instance.luafiles) do
local dname = file.dirname(cnf)
input.aux.load_texmfcnf(instance,'setup',dname)
instance.order[#instance.order+1] = instance.setup[dname]
if instance.loaderror then break end
end
end
function input.loadoldconfig(instance)
if not instance.renewcache then
for _, cnf in ipairs(instance.cnffiles) do
local dname = file.dirname(cnf)
input.aux.load_configuration(instance,dname)
instance.order[#instance.order+1] = instance.configuration[dname]
if instance.loaderror then break end
end
end
input.joinconfig(instance)
end
function input.expand_variables(instance)
instance.expansions = { }
--~ instance.environment['SELFAUTOPARENT'] = instance.environment['SELFAUTOPARENT'] or instance.rootpath
if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
if instance.progname ~= "" then instance.environment['progname'] = instance.progname end
for k,v in pairs(instance.environment) do
local a, b = k:match("^(%a+)%_(.*)%s*$")
if a and b then
instance.expansions[a..'.'..b] = v
else
instance.expansions[k] = v
end
end
for k,v in pairs(instance.environment) do -- move environment to expansions
if not instance.expansions[k] then instance.expansions[k] = v end
end
for k,v in pairs(instance.variables) do -- move variables to expansions
if not instance.expansions[k] then instance.expansions[k] = v end
end
while true do
local busy = false
for k,v in pairs(instance.expansions) do
local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
busy = true
return instance.expansions[a] or input.env(instance,a)
end)
local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
busy = true
return instance.expansions[a] or input.env(instance,a)
end)
if n > 0 or m > 0 then
instance.expansions[k]= s
end
end
if not busy then break end
end
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("\\", '/')
end
end
function input.aux.expand_vars(instance,lst) -- simple vars
for k,v in pairs(lst) do
lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
return instance.variables[a] or input.env(instance,a)
end)
end
end
function input.aux.expanded_var(instance,var) -- simple vars
return var:gsub("%$([%a%d%_%-]+)", function(a)
return instance.variables[a] or input.env(instance,a)
end)
end
function input.aux.entry(instance,entries,name)
if name and (name ~= "") then
name = name:gsub('%$','')
local result = entries[name..'.'..instance.progname] or entries[name]
if result then
return result
else
result = input.env(instance,name)
if result then
instance.variables[name] = result
input.expand_variables(instance)
return instance.expansions[name] or ""
end
end
end
return ""
end
function input.variable(instance,name)
return input.aux.entry(instance,instance.variables,name)
end
function input.expansion(instance,name)
return input.aux.entry(instance,instance.expansions,name)
end
function input.aux.is_entry(instance,entries,name)
if name and name ~= "" then
name = name:gsub('%$','')
return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
else
return false
end
end
function input.is_variable(instance,name)
return input.aux.is_entry(instance,instance.variables,name)
end
function input.is_expansion(instance,name)
return input.aux.is_entry(instance,instance.expansions,name)
end
function input.simplified_list(str)
if type(str) == 'table' then
return str -- troubles ; ipv , in texmf
elseif str == '' then
return { }
else
local t = { }
for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
end
return t
end
end
function input.unexpanded_path_list(instance,str)
local pth = input.variable(instance,str)
local lst = input.split_path(pth)
return input.aux.expanded_path(instance,lst)
end
function input.unexpanded_path(instance,str)
return file.join_path(input.unexpanded_path_list(instance,str))
end
do
local done = { }
function input.reset_extra_path(instance)
local ep = instance.extra_paths
if not ep then
ep, done = { }, { }
instance.extra_paths = ep
elseif #ep > 0 then
instance.lists, done = { }, { }
end
end
function input.register_extra_path(instance,paths,subpaths)
local ep = instance.extra_paths or { }
local n = #ep
if paths and paths ~= "" then
if subpaths and subpaths ~= "" then
for p in paths:gmatch("[^,]+") do
-- we gmatch each step again, not that fast, but used seldom
for s in subpaths:gmatch("[^,]+") do
local ps = p .. "/" .. s
if not done[ps] then
ep[#ep+1] = input.clean_path(ps)
done[ps] = true
end
end
end
else
for p in paths:gmatch("[^,]+") do
if not done[p] then
ep[#ep+1] = input.clean_path(p)
done[p] = true
end
end
end
elseif subpaths and subpaths ~= "" then
for i=1,n do
-- we gmatch each step again, not that fast, but used seldom
for s in subpaths:gmatch("[^,]+") do
local ps = ep[i] .. "/" .. s
if not done[ps] then
ep[#ep+1] = input.clean_path(ps)
done[ps] = true
end
end
end
end
if #ep > 0 then
instance.extra_paths = ep -- register paths
end
if #ep > n then
instance.lists = { } -- erase the cache
end
end
end
function input.expanded_path_list(instance,str)
local function made_list(list)
local ep = instance.extra_paths
if not ep or #ep == 0 then
return list
else
local done, new = { }, { }
-- honour . .. ../.. but only when at the start
for k, v in ipairs(list) do
if not done[v] then
if v:find("^[%.%/]$") then
done[v] = true
new[#new+1] = v
else
break
end
end
end
-- first the extra paths
for k, v in ipairs(ep) do
if not done[v] then
done[v] = true
new[#new+1] = v
end
end
-- next the formal paths
for k, v in ipairs(list) do
if not done[v] then
done[v] = true
new[#new+1] = v
end
end
return new
end
end
if not str then
return ep or { }
elseif instance.savelists then
-- engine+progname hash
str = str:gsub("%$","")
if not instance.lists[str] then -- cached
local lst = made_list(input.split_path(input.expansion(instance,str)))
instance.lists[str] = input.aux.expanded_path(instance,lst)
end
return instance.lists[str]
else
local lst = input.split_path(input.expansion(instance,str))
return made_list(input.aux.expanded_path(instance,lst))
end
end
function input.expand_path(instance,str)
return file.join_path(input.expanded_path_list(instance,str))
end
--~ function input.first_writable_path(instance,name)
--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
--~ return v
--~ end
--~ end
--~ return "."
--~ end
function input.expanded_path_list_from_var(instance,str) -- brrr
local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
if tmp ~= "" then
return input.expanded_path_list(instance,str)
else
return input.expanded_path_list(instance,tmp)
end
end
function input.expand_path_from_var(instance,str)
return file.join_path(input.expanded_path_list_from_var(instance,str))
end
function input.format_of_var(str)
return input.formats[str] or input.formats[input.alternatives[str]] or ''
end
function input.format_of_suffix(str)
return input.suffixmap[file.extname(str)] or 'tex'
end
function input.variable_of_format(str)
return input.formats[str] or input.formats[input.alternatives[str]] or ''
end
function input.var_of_format_or_suffix(str)
local v = input.formats[str]
if v then
return v
end
v = input.formats[input.alternatives[str]]
if v then
return v
end
v = input.suffixmap[file.extname(str)]
if v then
return input.formats[isf]
end
return ''
end
function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
local ori = input.variable(instance,str)
local pth = input.aux.expanded_path(instance,input.split_path(ori))
return file.join_path(pth)
end
-- {a,b,c,d}
-- a,b,c/{p,q,r},d
-- a,b,c/{p,q,r}/d/{x,y,z}//
-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
-- a{b,c}{d,e}f
-- {a,b,c,d}
-- {a,b,c/{p,q,r},d}
-- {a,b,c/{p,q,r}/d/{x,y,z}//}
-- {a,b,c/{p,q/{x,y,z}},d/{p,q,r}}
-- {a,b,c/{p,q/{x,y,z},w}v,d/{p,q,r}}
-- this one is better and faster, but it took me a while to realize
-- that this kind of replacement is cleaner than messy parsing and
-- fuzzy concatenating we can probably gain a bit with selectively
-- applying lpeg, but experiments with lpeg parsing this proved not to
-- work that well; the parsing is ok, but dealing with the resulting
-- table is a pain because we need to work inside-out recursively
-- get rid of piecewise here, just a gmatch is ok
function input.aux.splitpathexpr(str, t, validate)
-- no need for optimization, only called a few times, we can use lpeg for the sub
t = t or { }
local concat = table.concat
while true do
local done = false
while true do
local ok = false
str = str:gsub("([^{},]+){([^{}]-)}", function(a,b)
local t = { }
b:piecewise(",", function(s) t[#t+1] = a .. s end)
ok, done = true, true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}([^{},]+)", function(a,b)
local t = { }
a:piecewise(",", function(s) t[#t+1] = s .. b end)
ok, done = true, true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("([,{]){([^{}]+)}([,}])", function(a,b,c)
ok, done = true, true
return a .. b .. c
end)
if not ok then break end
end
if not done then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}{([^{}]-)}", function(a,b)
local t = { }
a:piecewise(",", function(sa)
b:piecewise(",", function(sb)
t[#t+1] = sa .. sb
end)
end)
ok = true
return "{" .. concat(t,",") .. "}"
end)
if not ok then break end
end
while true do
local ok = false
str = str:gsub("{([^{}]-)}", function(a)
ok = true
return a
end)
if not ok then break end
end
if validate then
str:piecewise(",", function(s)
s = validate(s)
if s then t[#t+1] = s end
end)
else
str:piecewise(",", function(s)
t[#t+1] = s
end)
end
return t
end
function input.aux.expanded_path(instance,pathlist) -- maybe not a list, just a path
-- a previous version fed back into pathlist
local newlist, ok = { }, false
for _,v in ipairs(pathlist) do
if v:find("[{}]") then
ok = true
break
end
end
if ok then
for _, v in ipairs(pathlist) do
input.aux.splitpathexpr(v, newlist, function(s)
s = file.collapse_path(s)
return s ~= "" and not s:find(instance.dummy_path_expr) and s
end)
end
else
for _,v in ipairs(pathlist) do
for vv in string.gmatch(v..',',"(.-),") do
vv = file.collapse_path(v)
if vv ~= "" then newlist[#newlist+1] = vv end
end
end
end
return newlist
end
input.is_readable = { }
function input.aux.is_readable(readable, name)
if input.trace > 2 then
if readable then
input.logger("+ readable", name)
else
input.logger("- readable", name)
end
end
return readable
end
function input.is_readable.file(name)
-- return input.aux.is_readable(file.is_readable(name), name)
return input.aux.is_readable(input.aux.is_file(name), name)
end
input.is_readable.tex = input.is_readable.file
-- name
-- name/name
function input.aux.collect_files(instance,names)
local filelist = { }
for _, fname in pairs(names) do
if fname then
if input.trace > 2 then
input.logger("? blobpath asked",fname)
end
local bname = file.basename(fname)
local dname = file.dirname(fname)
if dname == "" or dname:find("^%.") then
dname = false
else
dname = "/" .. dname .. "$"
end
for _, hash in ipairs(instance.hashes) do
local blobpath = hash.tag
local files = blobpath and instance.files[blobpath]
if files then
if input.trace > 2 then
input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
end
local blobfile = files[bname]
if not blobfile then
local rname = "remap:"..bname
blobfile = files[rname]
if blobfile then
bname = files[rname]
blobfile = files[bname]
end
end
if blobfile then
if type(blobfile) == 'string' then
if not dname or blobfile:find(dname) then
filelist[#filelist+1] = {
hash.type,
file.join(blobpath,blobfile,bname), -- search
input.concatinators[hash.type](blobpath,blobfile,bname) -- result
}
end
else
for _, vv in pairs(blobfile) do
if not dname or vv:find(dname) then
filelist[#filelist+1] = {
hash.type,
file.join(blobpath,vv,bname), -- search
input.concatinators[hash.type](blobpath,vv,bname) -- result
}
end
end
end
end
elseif input.trace > 1 then
input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
end
end
end
end
if #filelist > 0 then
return filelist
else
return nil
end
end
function input.suffix_of_format(str)
if input.suffixes[str] then
return input.suffixes[str][1]
else
return ""
end
end
function input.suffixes_of_format(str)
if input.suffixes[str] then
return input.suffixes[str]
else
return {}
end
end
do
-- called about 700 times for an empty doc (font initializations etc)
-- i need to weed the font files for redundant calls
local letter = lpeg.R("az","AZ")
local separator = lpeg.P("://")
local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator
local rootbased = lpeg.P("/") + letter*lpeg.P(":")
-- ./name ../name /name c: ://
function input.aux.qualified_path(filename)
return qualified:match(filename)
end
function input.aux.rootbased_path(filename)
return rootbased:match(filename)
end
function input.normalize_name(original)
return original
end
input.normalize_name = file.collapse_path
end
function input.aux.register_in_trees(instance,name)
if not name:find("^%.") then
instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
end
end
-- split the next one up, better for jit
function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
local result = { }
local stamp = nil
filename = input.normalize_name(filename) -- elsewhere
filename = file.collapse_path(filename:gsub("\\","/")) -- elsewhere
-- speed up / beware: format problem
if instance.remember then
stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
if instance.found[stamp] then
input.logger('! remembered', filename)
return instance.found[stamp]
end
end
if filename:find('%*') then
input.logger('! wildcard', filename)
result = input.find_wildcard_files(instance,filename)
elseif input.aux.qualified_path(filename) then
if input.is_readable.file(filename) then
input.logger('! qualified', filename)
result = { filename }
else
local forcedname, ok = "", false
if file.extname(filename) == "" then
if instance.format == "" then
forcedname = filename .. ".tex"
if input.is_readable.file(forcedname) then
input.logger('! no suffix, forcing standard filetype tex')
result, ok = { forcedname }, true
end
else
for _, s in pairs(input.suffixes_of_format(instance.format)) do
forcedname = filename .. "." .. s
if input.is_readable.file(forcedname) then
input.logger('! no suffix, forcing format filetype', s)
result, ok = { forcedname }, true
break
end
end
end
end
if not ok then
input.logger('? qualified', filename)
end
end
else
-- search spec
local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
if ext == "" then
if not instance.force_suffixes then
wantedfiles[#wantedfiles+1] = filename
end
else
wantedfiles[#wantedfiles+1] = filename
end
if instance.format == "" then
if ext == "" then
local forcedname = filename .. '.tex'
wantedfiles[#wantedfiles+1] = forcedname
filetype = input.format_of_suffix(forcedname)
input.logger('! forcing filetype',filetype)
else
filetype = input.format_of_suffix(filename)
input.logger('! using suffix based filetype',filetype)
end
else
if ext == "" then
for _, s in pairs(input.suffixes_of_format(instance.format)) do
wantedfiles[#wantedfiles+1] = filename .. "." .. s
end
end
filetype = instance.format
input.logger('! using given filetype',filetype)
end
local typespec = input.variable_of_format(filetype)
local pathlist = input.expanded_path_list(instance,typespec)
if not pathlist or #pathlist == 0 then
-- no pathlist, access check only / todo == wildcard
if input.trace > 2 then
input.logger('? filename',filename)
input.logger('? filetype',filetype or '?')
input.logger('? wanted files',table.concat(wantedfiles," | "))
end
for _, fname in pairs(wantedfiles) do
if fname and input.is_readable.file(fname) then
filename, done = fname, true
result[#result+1] = file.join('.',fname)
break
end
end
-- this is actually 'other text files' or 'any' or 'whatever'
local filelist = input.aux.collect_files(instance,wantedfiles)
local fl = filelist and filelist[1]
if fl then
filename = fl[3]
result[#result+1] = filename
done = true
end
else
-- list search
local filelist = input.aux.collect_files(instance,wantedfiles)
local doscan, recurse
if input.trace > 2 then
input.logger('? filename',filename)
-- if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
-- if filelist then input.logger('? file list',table.concat(filelist," | ")) end
end
-- a bit messy ... esp the doscan setting here
for _, path in pairs(pathlist) do
if path:find("^!!") then doscan = false else doscan = true end
if path:find("//$") then recurse = true else recurse = false end
local pathname = path:gsub("^!+", '')
done = false
-- using file list
if filelist and not (done and not instance.allresults) and recurse then
-- compare list entries with permitted pattern
pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
pathname = pathname:gsub("//", '/.-/') -- not ok for /// but harmless
local expr = "^" .. pathname
-- input.debug('?',expr)
for _, fl in ipairs(filelist) do
local f = fl[2]
if f:find(expr) then
-- input.debug('T',' '..f)
if input.trace > 2 then
input.logger('= found in hash',f)
end
--- todo, test for readable
result[#result+1] = fl[3]
input.aux.register_in_trees(instance,f) -- for tracing used files
done = true
if not instance.allresults then break end
else
-- input.debug('F',' '..f)
end
end
end
if not done and doscan then
-- check if on disk / unchecked / does not work at all / also zips
if input.method_is_file(pathname) then -- ?
local pname = pathname:gsub("%.%*$",'')
if not pname:find("%*") then
local ppname = pname:gsub("/+$","")
if input.aux.can_be_dir(instance,ppname) then
for _, w in pairs(wantedfiles) do
local fname = file.join(ppname,w)
if input.is_readable.file(fname) then
if input.trace > 2 then
input.logger('= found by scanning',fname)
end
result[#result+1] = fname
done = true
if not instance.allresults then break end
end
end
else
-- no access needed for non existing path, speedup (esp in large tree with lots of fake)
end
end
end
end
if not done and doscan then
-- todo: slow path scanning
end
if done and not instance.allresults then break end
end
end
end
for k,v in pairs(result) do
result[k] = file.collapse_path(v)
end
if instance.remember then
instance.found[stamp] = result
end
return result
end
input.aux._find_file_ = input.aux.find_file
function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
local result = input.aux._find_file_(instance,filename)
if #result == 0 then
local lowered = filename:lower()
if filename ~= lowered then
return input.aux._find_file_(instance,lowered)
end
end
return result
end
if lfs and lfs.isfile then
input.aux.is_file = lfs.isfile -- to be done: use this
else
input.aux.is_file = file.is_readable
end
if lfs and lfs.isdir then
function input.aux.can_be_dir(instance,name)
if not instance.fakepaths[name] then
if lfs.isdir(name) then
instance.fakepaths[name] = 1 -- directory
else
instance.fakepaths[name] = 2 -- no directory
end
end
return (instance.fakepaths[name] == 1)
end
else
function input.aux.can_be_dir()
return true
end
end
if not input.concatinators then input.concatinators = { } end
input.concatinators.tex = file.join
input.concatinators.file = input.concatinators.tex
function input.find_files(instance,filename,filetype,mustexist)
if type(mustexist) == boolean then
-- all set
elseif type(filetype) == 'boolean' then
filetype, mustexist = nil, false
elseif type(filetype) ~= 'string' then
filetype, mustexist = nil, false
end
instance.format = filetype or ''
local t = input.aux.find_file(instance,filename,true)
instance.format = ''
return t
end
function input.find_file(instance,filename,filetype,mustexist)
return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
end
function input.find_given_files(instance,filename)
local bname, result = file.basename(filename), { }
for k, hash in ipairs(instance.hashes) do
local files = instance.files[hash.tag]
local blist = files[bname]
if not blist then
local rname = "remap:"..bname
blist = files[rname]
if blist then
bname = files[rname]
blist = files[bname]
end
end
if blist then
if type(blist) == 'string' then
result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
if not instance.allresults then break end
else
for kk,vv in pairs(blist) do
result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
if not instance.allresults then break end
end
end
end
end
return result
end
function input.find_given_file(instance,filename)
return (input.find_given_files(instance,filename)[1] or "")
end
function input.find_wildcard_files(instance,filename) -- todo: remap:
local result = { }
local bname, dname = file.basename(filename), file.dirname(filename)
local path = dname:gsub("^*/","")
path = path:gsub("*",".*")
path = path:gsub("-","%%-")
if dname == "" then
path = ".*"
end
local name = bname
name = name:gsub("*",".*")
name = name:gsub("-","%%-")
path = path:lower()
name = name:lower()
local function doit(blist,bname,hash,allresults)
local done = false
if blist then
if type(blist) == 'string' then
-- make function and share code
if (blist:lower()):find(path) then
result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
done = true
end
else
for kk,vv in pairs(blist) do
if (vv:lower()):find(path) then
result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
done = true
if not allresults then break end
end
end
end
end
return done
end
local files, allresults, done = instance.files, instance.allresults, false
if name:find("%*") then
for k, hash in ipairs(instance.hashes) do
for kk, hh in pairs(files[hash.tag]) do
if not kk:find("^remap:") then
if (kk:lower()):find(name) then
if doit(hh,kk,hash,allresults) then done = true end
if done and not allresults then break end
end
end
end
end
else
for k, hash in ipairs(instance.hashes) do
if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
if done and not allresults then break end
end
end
return result
end
function input.find_wildcard_file(instance,filename)
return (input.find_wildcard_files(instance,filename)[1] or "")
end
-- main user functions
function input.save_used_files_in_trees(instance, filename,jobname)
if not filename then filename = 'luatex.jlg' end
local f = io.open(filename,'w')
if f then
f:write("\n")
f:write("\n")
if jobname then
f:write("\t" .. jobname .. "\n")
end
f:write("\t\n")
for _,v in pairs(table.sortedkeys(instance.foundintrees)) do
f:write("\t\t" .. v .. "\n")
end
f:write("\t\n")
f:write("\n")
f:close()
end
end
function input.automount(instance)
-- implemented later
end
function input.load(instance)
input.starttiming(instance)
input.resetconfig(instance)
input.identify_cnf(instance)
input.load_lua(instance)
input.expand_variables(instance)
input.load_cnf(instance)
input.expand_variables(instance)
input.load_hash(instance)
input.automount(instance)
input.stoptiming(instance)
end
function input.for_files(instance, command, files, filetype, mustexist)
if files and #files > 0 then
local function report(str)
if input.verbose then
input.report(str) -- has already verbose
else
print(str)
end
end
if input.verbose then
report('')
end
for _, file in pairs(files) do
local result = command(instance,file,filetype,mustexist)
if type(result) == 'string' then
report(result)
else
for _,v in pairs(result) do
report(v)
end
end
end
end
end
-- strtab
function input.var_value(instance,str) -- output the value of variable $STRING.
return input.variable(instance,str)
end
function input.expand_var(instance,str) -- output variable expansion of STRING.
return input.expansion(instance,str)
end
function input.show_path(instance,str) -- output search path for file type NAME
return file.join_path(input.expanded_path_list(instance,input.format_of_var(str)))
end
-- input.find_file(filename)
-- input.find_file(filename, filetype, mustexist)
-- input.find_file(filename, mustexist)
-- input.find_file(filename, filetype)
function input.aux.register_file(files, name, path)
if files[name] then
if type(files[name]) == 'string' then
files[name] = { files[name], path }
else
files[name] = path
end
else
files[name] = path
end
end
if not input.finders then input.finders = { } end
if not input.openers then input.openers = { } end
if not input.loaders then input.loaders = { } end
input.finders.notfound = { nil }
input.openers.notfound = { nil }
input.loaders.notfound = { false, nil, 0 }
function input.splitmethod(filename)
if not filename then
return { } -- safeguard
elseif type(filename) == "table" then
return filename -- already split
elseif not filename:find("://") then
return { scheme="file", path = filename, original=filename } -- quick hack
else
return url.hashed(filename)
end
end
function input.method_is_file(filename)
return input.splitmethod(filename).scheme == 'file'
end
function table.sequenced(t,sep) -- temp here
local s = { }
for k, v in pairs(t) do
s[#s+1] = k .. "=" .. v
end
return table.concat(s, sep or " | ")
end
function input.methodhandler(what, instance, filename, filetype) -- ...
local specification = (type(filename) == "string" and input.splitmethod(filename)) or filename -- no or { }, let it bomb
local scheme = specification.scheme
if input[what][scheme] then
input.logger('= handler',specification.original .." -> " .. what .. " -> " .. table.sequenced(specification))
return input[what][scheme](instance,filename,filetype) -- todo: specification
else
return input[what].tex(instance,filename,filetype) -- todo: specification
end
end
-- also inside next test?
function input.findtexfile(instance, filename, filetype)
return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
end
function input.opentexfile(instance,filename)
return input.methodhandler('openers',instance, input.normalize_name(filename))
end
function input.findbinfile(instance, filename, filetype)
return input.methodhandler('finders',instance, input.normalize_name(filename), filetype)
end
function input.openbinfile(instance,filename)
return input.methodhandler('loaders',instance, input.normalize_name(filename))
end
function input.loadbinfile(instance, filename, filetype)
local fname = input.findbinfile(instance, input.normalize_name(filename), filetype)
if fname and fname ~= "" then
return input.openbinfile(instance,fname)
else
return unpack(input.loaders.notfound)
end
end
function input.texdatablob(instance, filename, filetype)
local ok, data, size = input.loadbinfile(instance, filename, filetype)
return data or ""
end
input.loadtexfile = input.texdatablob
function input.openfile(filename) -- brrr texmf.instance here / todo ! ! ! ! !
local fullname = input.findtexfile(texmf.instance, filename)
if fullname and (fullname ~= "") then
return input.opentexfile(texmf.instance, fullname)
else
return nil
end
end
function input.logmode()
return (os.getenv("MTX.LOG.MODE") or os.getenv("MTX_LOG_MODE") or "tex"):lower()
end
-- this is a prelude to engine/progname specific configuration files
-- in which case we can omit files meant for other programs and
-- packages
--- ctx
-- maybe texinputs + font paths
-- maybe positive selection tex/context fonts/tfm|afm|vf|opentype|type1|map|enc
input.validators = { }
input.validators.visibility = { }
function input.validators.visibility.default(path, name)
return true
end
function input.validators.visibility.context(path, name)
path = path[1] or path -- some day a loop
return not (
path:find("latex") or
-- path:find("doc") or
path:find("tex4ht") or
path:find("source") or
-- path:find("config") or
-- path:find("metafont") or
path:find("lists$") or
name:find("%.tpm$") or
name:find("%.bak$")
)
end
-- todo: describe which functions are public (maybe input.private. ... )
-- beware: i need to check where we still need a / on windows:
function input.clean_path(str)
--~ return (((str:gsub("\\","/")):gsub("^!+","")):gsub("//+","//"))
if str then
return ((str:gsub("\\","/")):gsub("^!+",""))
else
return nil
end
end
function input.do_with_path(name,func)
for _, v in pairs(input.expanded_path_list(instance,name)) do
func("^"..input.clean_path(v))
end
end
function input.do_with_var(name,func)
func(input.aux.expanded_var(name))
end
function input.with_files(instance,pattern,handle)
for _, hash in ipairs(instance.hashes) do
local blobpath = hash.tag
local blobtype = hash.type
if blobpath then
local files = instance.files[blobpath]
if files then
for k,v in pairs(files) do
if k:find("^remap:") then
k = files[k]
v = files[k] -- chained
end
if k:find(pattern) then
if type(v) == "string" then
handle(blobtype,blobpath,v,k)
else
for _,vv in pairs(v) do
handle(blobtype,blobpath,vv,k)
end
end
end
end
end
end
end
end
--~ function input.update_script(oldname,newname) -- oldname -> own.name, not per se a suffix
--~ newname = file.addsuffix(newname,"lua")
--~ local newscript = input.clean_path(input.find_file(instance, newname))
--~ local oldscript = input.clean_path(oldname)
--~ input.report("old script", oldscript)
--~ input.report("new script", newscript)
--~ if oldscript ~= newscript and (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
--~ local newdata = io.loaddata(newscript)
--~ if newdata then
--~ input.report("old script content replaced by new content")
--~ io.savedata(oldscript,newdata)
--~ end
--~ end
--~ end
function input.update_script(instance,oldname,newname) -- oldname -> own.name, not per se a suffix
local scriptpath = "scripts/context/lua"
newname = file.addsuffix(newname,"lua")
local oldscript = input.clean_path(oldname)
input.report("to be replaced old script", oldscript)
local newscripts = input.find_files(instance, newname) or { }
if #newscripts == 0 then
input.report("unable to locate new script")
else
for _, newscript in ipairs(newscripts) do
newscript = input.clean_path(newscript)
input.report("checking new script", newscript)
if oldscript == newscript then
input.report("old and new script are the same")
elseif not newscript:find(scriptpath) then
input.report("new script should come from",scriptpath)
elseif not (oldscript:find(file.removesuffix(newname).."$") or oldscript:find(newname.."$")) then
input.report("invalid new script name")
else
local newdata = io.loaddata(newscript)
if newdata then
input.report("old script content replaced by new content")
io.savedata(oldscript,newdata)
break
else
input.report("unable to load new script")
end
end
end
end
end
--~ print(table.serialize(input.aux.splitpathexpr("/usr/share/texmf-{texlive,tetex}", {})))
-- command line resolver:
--~ print(input.resolve("abc env:tmp file:cont-en.tex path:cont-en.tex full:cont-en.tex rel:zapf/one/p-chars.tex"))
do
local resolvers = { }
resolvers.environment = function(instance,str)
return input.clean_path(os.getenv(str) or os.getenv(str:upper()) or os.getenv(str:lower()) or "")
end
resolvers.relative = function(instance,str,n)
if io.exists(str) then
-- nothing
elseif io.exists("./" .. str) then
str = "./" .. str
else
local p = "../"
for i=1,n or 2 do
if io.exists(p .. str) then
str = p .. str
break
else
p = p .. "../"
end
end
end
return input.clean_path(str)
end
resolvers.locate = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path((fullname ~= "" and fullname) or str)
end
resolvers.filename = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path(file.basename((fullname ~= "" and fullname) or str))
end
resolvers.pathname = function(instance,str)
local fullname = input.find_given_file(instance,str) or ""
return input.clean_path(file.dirname((fullname ~= "" and fullname) or str))
end
resolvers.env = resolvers.environment
resolvers.rel = resolvers.relative
resolvers.loc = resolvers.locate
resolvers.kpse = resolvers.locate
resolvers.full = resolvers.locate
resolvers.file = resolvers.filename
resolvers.path = resolvers.pathname
local function resolve(instance,str)
if type(str) == "table" then
for k, v in pairs(str) do
str[k] = resolve(instance,v) or v
end
elseif str and str ~= "" then
str = str:gsub("([a-z]+):([^ ]+)", function(method,target)
if resolvers[method] then
return resolvers[method](instance,target)
else
return method .. ":" .. target
end
end)
end
return str
end
input.resolve = resolve
end