summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/luat-inp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/luat-inp.lua')
-rw-r--r--Master/texmf-dist/tex/context/base/luat-inp.lua2259
1 files changed, 2259 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/luat-inp.lua b/Master/texmf-dist/tex/context/base/luat-inp.lua
new file mode 100644
index 00000000000..63dc1d904fc
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/luat-inp.lua
@@ -0,0 +1,2259 @@
+-- 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("<t>"..table.concat({...},' ').."</t>")
+ else
+ input.log("<<"..table.concat({...},' ')..">>")
+ end
+ end
+end
+
+function input.reportlines(str)
+ if type(str) == "string" then
+ str = str:split("\n")
+ end
+ for _,v in pairs(str) do input.report(v) end
+end
+
+input.settrace(tonumber(os.getenv("MTX.INPUT.TRACE") or os.getenv("MTX_INPUT_TRACE") or input.trace or 0))
+
+-- These functions can be used to test the performance, especially
+-- loading the database files.
+
+do
+ local clock = os.gettimeofday or os.clock
+
+ function input.starttiming(instance)
+ if instance then
+ instance.starttime = clock()
+ if not instance.loadtime then
+ instance.loadtime = 0
+ end
+ end
+ end
+
+ function input.stoptiming(instance, report)
+ if instance then
+ local starttime = instance.starttime
+ if starttime then
+ local stoptime = clock()
+ local loadtime = stoptime - starttime
+ instance.stoptime = stoptime
+ instance.loadtime = instance.loadtime + loadtime
+ if report then
+ input.report('load time', format("%0.3f",loadtime))
+ end
+ return loadtime
+ end
+ end
+ return 0
+ end
+
+end
+
+function input.elapsedtime(instance)
+ return format("%0.3f",(instance and instance.loadtime) or 0)
+end
+
+function input.report_loadtime(instance)
+ if instance then
+ input.report('total load time', input.elapsedtime(instance))
+ end
+end
+
+input.loadtime = input.elapsedtime
+
+function input.env(instance,key)
+ return instance.environment[key] or input.osenv(instance,key)
+end
+
+function input.osenv(instance,key)
+ local ie = instance.environment
+ local value = ie[key]
+ if value == nil then
+ -- local e = os.getenv(key)
+ local e = os.env[key]
+ if e == nil then
+ -- value = "" -- false
+ else
+ value = input.bare_variable(e)
+ end
+ ie[key] = value
+ end
+ return value or ""
+end
+
+-- we follow a rather traditional approach:
+--
+-- (1) texmf.cnf given in TEXMFCNF
+-- (2) texmf.cnf searched in TEXMF/web2c
+--
+-- for the moment we don't expect a configuration file in a zip
+
+function input.identify_cnf(instance)
+ -- we no longer support treepath and rootpath (was handy for testing);
+ -- also we now follow the stupid route: if not set then just assume *one*
+ -- cnf file under texmf (i.e. distribution)
+ if #instance.cnffiles == 0 then
+ if input.env(instance,'TEXMFCNF') == "" then
+ local ownpath = environment.ownpath() or "."
+ if ownpath then
+ -- beware, this is tricky on my own system because at that location I do have
+ -- the raw tree that ends up in the zip; i.e. I cannot test this kind of mess
+ local function locate(filename,list)
+ local ownroot = input.normalize_name(file.join(ownpath,"../.."))
+ if not lfs.isdir(file.join(ownroot,"texmf")) then
+ ownroot = input.normalize_name(file.join(ownpath,".."))
+ if not lfs.isdir(file.join(ownroot,"texmf")) then
+ input.verbose = true
+ input.report("error", "unable to identify cnf file")
+ return
+ end
+ end
+ local texmfcnf = file.join(ownroot,"texmf-local/web2c",filename) -- for minimals and myself
+ if not lfs.isfile(texmfcnf) then
+ texmfcnf = file.join(ownroot,"texmf/web2c",filename)
+ if not lfs.isfile(texmfcnf) then
+ input.verbose = true
+ input.report("error", "unable to locate",filename)
+ return
+ end
+ end
+ table.insert(list,texmfcnf)
+ local ie = instance.environment
+ if not ie['SELFAUTOPARENT'] then ie['SELFAUTOPARENT'] = ownroot end
+ if not ie['TEXMFCNF'] then ie['TEXMFCNF'] = file.dirname(texmfcnf) end
+ end
+ locate(input.luaname,instance.luafiles)
+ locate(input.cnfname,instance.cnffiles)
+ if #instance.luafiles == 0 and instance.cnffiles == 0 then
+ input.verbose = true
+ input.report("error", "unable to locate",filename)
+ os.exit()
+ end
+ -- here we also assume then TEXMF is set in the distribution, if this trickery is
+ -- used in the minimals, then users who don't use setuptex are on their own with
+ -- regards to extra trees
+ else
+ input.verbose = true
+ input.report("error", "unable to identify own path")
+ os.exit()
+ end
+ else
+ local t = input.split_path(input.env(instance,'TEXMFCNF'))
+ t = input.aux.expanded_path(instance,t)
+ input.aux.expand_vars(instance,t)
+ local function locate(filename,list)
+ for _,v in ipairs(t) do
+ local texmfcnf = input.normalize_name(file.join(v,filename))
+ if lfs.isfile(texmfcnf) then
+ table.insert(list,texmfcnf)
+ end
+ end
+ end
+ locate(input.luaname,instance.luafiles)
+ locate(input.cnfname,instance.cnffiles)
+ end
+ end
+end
+
+function input.load_cnf(instance)
+ local function loadoldconfigdata()
+ for _, fname in ipairs(instance.cnffiles) do
+ input.aux.load_cnf(instance,fname)
+ end
+ end
+ -- instance.cnffiles contain complete names now !
+ if #instance.cnffiles == 0 then
+ input.report("no cnf files found (TEXMFCNF may not be set/known)")
+ else
+ instance.rootpath = instance.cnffiles[1]
+ for k,fname in ipairs(instance.cnffiles) do
+ instance.cnffiles[k] = input.normalize_name(fname:gsub("\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = input.normalize_name(instance.rootpath)
+ instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
+ if instance.lsrmode then
+ loadoldconfigdata()
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadoldconfig(instance,instance.cnffiles)
+ if instance.loaderror then
+ loadoldconfigdata()
+ input.saveoldconfig(instance)
+ end
+ else
+ loadoldconfigdata()
+ if instance.renewcache then
+ input.saveoldconfig(instance)
+ end
+ end
+ input.aux.collapse_cnf_data(instance)
+ end
+ input.checkconfigdata(instance)
+end
+
+function input.load_lua(instance)
+ if #instance.luafiles == 0 then
+ -- yet harmless
+ else
+ instance.rootpath = instance.luafiles[1]
+ for k,fname in ipairs(instance.luafiles) do
+ instance.luafiles[k] = input.normalize_name(fname:gsub("\\",'/'))
+ end
+ for i=1,3 do
+ instance.rootpath = file.dirname(instance.rootpath)
+ end
+ instance.rootpath = input.normalize_name(instance.rootpath)
+ instance.environment['SELFAUTOPARENT'] = instance.rootpath -- just to be sure
+ input.loadnewconfig(instance)
+ input.aux.collapse_cnf_data(instance)
+ end
+ input.checkconfigdata(instance)
+end
+
+function input.aux.collapse_cnf_data(instance) -- potential optmization: pass start index (setup and configuration are shared)
+ for _,c in ipairs(instance.order) do
+ for k,v in pairs(c) do
+ if not instance.variables[k] then
+ if instance.environment[k] then
+ instance.variables[k] = instance.environment[k]
+ else
+ instance.kpsevars[k] = true
+ instance.variables[k] = input.bare_variable(v)
+ end
+ end
+ end
+ end
+end
+
+function input.aux.load_cnf(instance,fname)
+ fname = input.clean_path(fname)
+ local lname = fname:gsub("%.%a+$",input.luasuffix)
+ local f = io.open(lname)
+ if f then -- this will go
+ f:close()
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ input.aux.load_configuration(instance,dname,lname)
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ else
+ f = io.open(fname)
+ if f then
+ input.report("loading", fname)
+ local line, data, n, k, v
+ local dname = file.dirname(fname)
+ if not instance.configuration[dname] then
+ instance.configuration[dname] = { }
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ end
+ local data = instance.configuration[dname]
+ while true do
+ local line, n = f:read(), 0
+ if line then
+ while true do -- join lines
+ line, n = line:gsub("\\%s*$", "")
+ if n > 0 then
+ line = line .. f:read()
+ else
+ break
+ end
+ end
+ if not line:find("^[%%#]") then
+ local k, v = (line:gsub("%s*%%.*$","")):match("%s*(.-)%s*=%s*(.-)%s*$")
+ if k and v and not data[k] then
+ data[k] = (v:gsub("[%%#].*",'')):gsub("~", "$HOME")
+ instance.kpsevars[k] = true
+ end
+ end
+ else
+ break
+ end
+ end
+ f:close()
+ else
+ input.report("skipping", fname)
+ end
+ end
+end
+
+-- database loading
+
+function input.load_hash(instance)
+ input.locatelists(instance)
+ if instance.lsrmode then
+ input.loadlists(instance)
+ elseif instance.diskcache and not instance.renewcache then
+ input.loadfiles(instance)
+ if instance.loaderror then
+ input.loadlists(instance)
+ input.savefiles(instance)
+ end
+ else
+ input.loadlists(instance)
+ if instance.renewcache then
+ input.savefiles(instance)
+ end
+ end
+end
+
+function input.aux.append_hash(instance,type,tag,name)
+ input.logger("= hash append",tag)
+ table.insert(instance.hashes, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.prepend_hash(instance,type,tag,name)
+ input.logger("= hash prepend",tag)
+ table.insert(instance.hashes, 1, { ['type']=type, ['tag']=tag, ['name']=name } )
+end
+
+function input.aux.extend_texmf_var(instance,specification) -- crap
+ if instance.environment['TEXMF'] then
+ input.report("extending environment variable TEXMF with", specification)
+ instance.environment['TEXMF'] = instance.environment['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ elseif instance.variables['TEXMF'] then
+ input.report("extending configuration variable TEXMF with", specification)
+ instance.variables['TEXMF'] = instance.variables['TEXMF']:gsub("^%{", function()
+ return "{" .. specification .. ","
+ end)
+ else
+ input.report("setting configuration variable TEXMF to", specification)
+ instance.variables['TEXMF'] = "{" .. specification .. "}"
+ end
+ if instance.variables['TEXMF']:find("%,") and not instance.variables['TEXMF']:find("^%{") then
+ input.report("adding {} to complex TEXMF variable, best do that yourself")
+ instance.variables['TEXMF'] = "{" .. instance.variables['TEXMF'] .. "}"
+ end
+ input.expand_variables(instance)
+ input.reset_hashes(instance)
+end
+
+-- locators
+
+function input.locatelists(instance)
+ for _, path in pairs(input.simplified_list(input.expansion(instance,'TEXMF'))) do
+ path = file.collapse_path(path)
+ input.report("locating list of",path)
+ input.locatedatabase(instance,input.normalize_name(path))
+ end
+end
+
+function input.locatedatabase(instance,specification)
+ return input.methodhandler('locators', instance, specification)
+end
+
+function input.locators.tex(instance,specification)
+ if specification and specification ~= '' and lfs.isdir(specification) then
+ input.logger('! tex locator', specification..' found')
+ input.aux.append_hash(instance,'file',specification,filename)
+ else
+ input.logger('? tex locator', specification..' not found')
+ end
+end
+
+-- hashers
+
+function input.hashdatabase(instance,tag,name)
+ return input.methodhandler('hashers',instance,tag,name)
+end
+
+function input.loadfiles(instance)
+ instance.loaderror = false
+ instance.files = { }
+ if not instance.renewcache then
+ for _, hash in ipairs(instance.hashes) do
+ input.hashdatabase(instance,hash.tag,hash.name)
+ if instance.loaderror then break end
+ end
+ end
+end
+
+function input.hashers.tex(instance,tag,name)
+ input.aux.load_files(instance,tag)
+end
+
+-- generators:
+
+function input.loadlists(instance)
+ for _, hash in ipairs(instance.hashes) do
+ input.generatedatabase(instance,hash.tag)
+ end
+end
+
+function input.generatedatabase(instance,specification)
+ return input.methodhandler('generators', instance, specification)
+end
+
+do
+
+ local weird = lpeg.anywhere(lpeg.S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t"))
+
+ function input.generators.tex(instance,specification)
+ local tag = specification
+ if not instance.lsrmode and lfs and lfs.dir then
+ input.report("scanning path",specification)
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local n, m, r = 0, 0, 0
+ local spec = specification .. '/'
+ local attributes = lfs.attributes
+ local directory = lfs.dir
+ local small = instance.smallcache
+ local function action(path)
+ local mode, full
+ if path then
+ full = spec .. path .. '/'
+ else
+ full = spec
+ end
+ for name in directory(full) do
+ if name:find("^%.") then
+ -- skip
+ -- elseif name:find("[%~%`%!%#%$%%%^%&%*%(%)%=%{%}%[%]%:%;\"\'%|%<%>%,%?\n\r\t]") then -- too much escaped
+ elseif weird:match(name) then
+ -- texio.write_nl("skipping " .. name)
+ -- skip
+ else
+ mode = attributes(full..name,'mode')
+ if mode == "directory" then
+ m = m + 1
+ if path then
+ action(path..'/'..name)
+ else
+ action(name)
+ end
+ elseif path and mode == 'file' then
+ n = n + 1
+ local f = files[name]
+ if f then
+ if not small then
+ if type(f) == 'string' then
+ files[name] = { f, path }
+ else
+ f[#f+1] = path
+ end
+ end
+ else
+ files[name] = path
+ local lower = name:lower()
+ if name ~= lower then
+ files["remap:"..lower] = name
+ r = r + 1
+ end
+ end
+ end
+ end
+ end
+ end
+ action()
+ input.report(format("%s files found on %s directories with %s uppercase remappings",n,m,r))
+ else
+ local fullname = file.join(specification,input.lsrname)
+ local path = '.'
+ local f = io.open(fullname)
+ if f then
+ instance.files[tag] = { }
+ local files = instance.files[tag]
+ local small = instance.smallcache
+ input.report("loading lsr file",fullname)
+ -- for line in f:lines() do -- much slower then the next one
+ for line in (f:read("*a")):gmatch("(.-)\n") do
+ if line:find("^[%a%d]") then
+ local fl = files[line]
+ if fl then
+ if not small then
+ if type(fl) == 'string' then
+ files[line] = { fl, path } -- table
+ else
+ fl[#fl+1] = path
+ end
+ end
+ else
+ files[line] = path -- string
+ local lower = line:lower()
+ if line ~= lower then
+ files["remap:"..lower] = line
+ end
+ end
+ else
+ path = line:match("%.%/(.-)%:$") or path -- match could be nil due to empty line
+ end
+ end
+ f:close()
+ end
+ end
+ end
+
+end
+
+-- savers, todo
+
+function input.savefiles(instance)
+ input.aux.save_data(instance, 'files', function(k,v)
+ return instance.validfile(k,v) -- path, name
+ end)
+end
+
+-- A config (optionally) has the paths split in tables. Internally
+-- we join them and split them after the expansion has taken place. This
+-- is more convenient.
+
+function input.splitconfig(instance)
+ for i,c in ipairs(instance) do
+ for k,v in pairs(c) do
+ if type(v) == 'string' then
+ local t = file.split_path(v)
+ if #t > 1 then
+ c[k] = t
+ end
+ end
+ end
+ end
+end
+function input.joinconfig(instance)
+ for i,c in ipairs(instance.order) do
+ for k,v in pairs(c) do
+ if type(v) == 'table' then
+ c[k] = file.join_path(v)
+ end
+ end
+ end
+end
+function input.split_path(str)
+ if type(str) == 'table' then
+ return str
+ else
+ return file.split_path(str)
+ end
+end
+function input.join_path(str)
+ if type(str) == 'table' then
+ return file.join_path(str)
+ else
+ return str
+ end
+end
+
+function input.splitexpansions(instance)
+ for k,v in pairs(instance.expansions) do
+ local t, h = { }, { }
+ for _,vv in pairs(file.split_path(v)) do
+ if vv ~= "" and not h[vv] then
+ t[#t+1] = vv
+ h[vv] = true
+ end
+ end
+ if #t > 1 then
+ instance.expansions[k] = t
+ else
+ instance.expansions[k] = t[1]
+ end
+ end
+end
+
+-- end of split/join code
+
+function input.saveoldconfig(instance)
+ input.splitconfig(instance)
+ input.aux.save_data(instance, 'configuration', nil)
+ input.joinconfig(instance)
+end
+
+input.configbanner = [[
+-- This is a Luatex configuration file created by 'luatools.lua' or
+-- 'luatex.exe' directly. For comment, suggestions and questions you can
+-- contact the ConTeXt Development Team. This configuration file is
+-- not copyrighted. [HH & TH]
+]]
+
+function input.serialize(files)
+ -- This version is somewhat optimized for the kind of
+ -- tables that we deal with, so it's much faster than
+ -- the generic serializer. This makes sense because
+ -- luatools and mtxtools are called frequently. Okay,
+ -- we pay a small price for properly tabbed tables.
+ local t = { }
+ local concat = table.concat
+ local sorted = table.sortedkeys
+ local function dump(k,v,m)
+ if type(v) == 'string' then
+ return m .. "['" .. k .. "']='" .. v .. "',"
+ elseif #v == 1 then
+ return m .. "['" .. k .. "']='" .. v[1] .. "',"
+ else
+ return m .. "['" .. k .. "']={'" .. concat(v,"','").. "'},"
+ end
+ end
+ t[#t+1] = "return {"
+ if instance.sortdata then
+ for _, k in pairs(sorted(files)) do
+ local fk = files[k]
+ if type(fk) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for _, kk in pairs(sorted(fk)) do
+ t[#t+1] = dump(kk,fk[kk],"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,fk,"\t")
+ end
+ end
+ else
+ for k, v in pairs(files) do
+ if type(v) == 'table' then
+ t[#t+1] = "\t['" .. k .. "']={"
+ for kk,vv in pairs(v) do
+ t[#t+1] = dump(kk,vv,"\t\t")
+ end
+ t[#t+1] = "\t},"
+ else
+ t[#t+1] = dump(k,v,"\t")
+ end
+ end
+ end
+ t[#t+1] = "}"
+ return concat(t,"\n")
+end
+
+if not texmf then texmf = {} end -- no longer needed, at least not here
+
+function input.aux.save_data(instance, dataname, check, makename) -- untested without cache overload
+ for cachename, files in pairs(instance[dataname]) do
+ local name = (makename or file.join)(cachename,dataname)
+ local luaname, lucname = name .. input.luasuffix, name .. input.lucsuffix
+ input.report("preparing " .. dataname .. " for", luaname)
+ for k, v in pairs(files) do
+ if not check or check(v,k) then -- path, name
+ if type(v) == "table" and #v == 1 then
+ files[k] = v[1]
+ end
+ else
+ files[k] = nil -- false
+ end
+ end
+ local data = {
+ type = dataname,
+ root = cachename,
+ version = input.cacheversion,
+ date = os.date("%Y-%m-%d"),
+ time = os.date("%H:%M:%S"),
+ content = files,
+ }
+ local f = io.open(luaname,'w')
+ if f then
+ input.report("saving " .. dataname .. " in", luaname)
+ f:write(input.serialize(data))
+ f:close()
+ input.report("compiling " .. dataname .. " to", lucname)
+ if not utils.lua.compile(luaname,lucname) then
+ input.report("compiling failed for " .. dataname .. ", deleting file " .. lucname)
+ os.remove(lucname)
+ end
+ else
+ input.report("unable to save " .. dataname .. " in " .. name..input.luasuffix)
+ end
+ end
+end
+
+function input.aux.load_data(instance,pathname,dataname,filename,makename) -- untested without cache overload
+ filename = ((not filename or (filename == "")) and dataname) or filename
+ filename = (makename and makename(dataname,filename)) or file.join(pathname,filename)
+ local blob = loadfile(filename .. input.lucsuffix) or loadfile(filename .. input.luasuffix)
+ if blob then
+ local data = blob()
+ if data and data.content and data.type == dataname and data.version == input.cacheversion then
+ input.report("loading",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = data.content
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ else
+ input.report("skipping",dataname,"for",pathname,"from",filename)
+ end
+end
+
+-- some day i'll use the nested approach, but not yet (actually we even drop
+-- engine/progname support since we have only luatex now)
+--
+-- first texmfcnf.lua files are located, next the cached texmf.cnf files
+--
+-- return {
+-- TEXMFBOGUS = 'effe checken of dit werkt',
+-- }
+
+function input.aux.load_texmfcnf(instance,dataname,pathname)
+ local filename = file.join(pathname,input.luaname)
+ local blob = loadfile(filename)
+ if blob then
+ local data = blob()
+ if data then
+ input.report("loading","configuration file",filename)
+ if true then
+ -- flatten to variable.progname
+ local t = { }
+ for k, v in pairs(data) do -- v = progname
+ if type(v) == "string" then
+ t[k] = v
+ else
+ for kk, vv in pairs(v) do -- vv = variable
+ if type(vv) == "string" then
+ t[vv.."."..v] = kk
+ end
+ end
+ end
+ end
+ instance[dataname][pathname] = t
+ else
+ instance[dataname][pathname] = data
+ end
+ else
+ input.report("skipping","configuration file",filename)
+ instance[dataname][pathname] = { }
+ instance.loaderror = true
+ end
+ else
+ input.report("skipping","configuration file",filename)
+ end
+end
+
+function input.aux.load_configuration(instance,dname,lname)
+ input.aux.load_data(instance,dname,'configuration',lname and file.basename(lname))
+end
+function input.aux.load_files(instance,tag)
+ input.aux.load_data(instance,tag,'files')
+end
+
+function input.resetconfig(instance)
+ instance.configuration, instance.setup, instance.order, instance.loaderror = { }, { }, { }, false
+end
+
+function input.loadnewconfig(instance)
+ for _, cnf in ipairs(instance.luafiles) do
+ local dname = file.dirname(cnf)
+ input.aux.load_texmfcnf(instance,'setup',dname)
+ instance.order[#instance.order+1] = instance.setup[dname]
+ if instance.loaderror then break end
+ end
+end
+
+function input.loadoldconfig(instance)
+ if not instance.renewcache then
+ for _, cnf in ipairs(instance.cnffiles) do
+ local dname = file.dirname(cnf)
+ input.aux.load_configuration(instance,dname)
+ instance.order[#instance.order+1] = instance.configuration[dname]
+ if instance.loaderror then break end
+ end
+ end
+ input.joinconfig(instance)
+end
+
+function input.expand_variables(instance)
+ instance.expansions = { }
+--~ instance.environment['SELFAUTOPARENT'] = instance.environment['SELFAUTOPARENT'] or instance.rootpath
+ if instance.engine ~= "" then instance.environment['engine'] = instance.engine end
+ if instance.progname ~= "" then instance.environment['progname'] = instance.progname end
+ for k,v in pairs(instance.environment) do
+ local a, b = k:match("^(%a+)%_(.*)%s*$")
+ if a and b then
+ instance.expansions[a..'.'..b] = v
+ else
+ instance.expansions[k] = v
+ end
+ end
+ for k,v in pairs(instance.environment) do -- move environment to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ for k,v in pairs(instance.variables) do -- move variables to expansions
+ if not instance.expansions[k] then instance.expansions[k] = v end
+ end
+ while true do
+ local busy = false
+ for k,v in pairs(instance.expansions) do
+ local s, n = v:gsub("%$([%a%d%_%-]+)", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ local s, m = s:gsub("%$%{([%a%d%_%-]+)%}", function(a)
+ busy = true
+ return instance.expansions[a] or input.env(instance,a)
+ end)
+ if n > 0 or m > 0 then
+ instance.expansions[k]= s
+ end
+ end
+ if not busy then break end
+ end
+ for k,v in pairs(instance.expansions) do
+ instance.expansions[k] = v:gsub("\\", '/')
+ end
+end
+
+function input.aux.expand_vars(instance,lst) -- simple vars
+ for k,v in pairs(lst) do
+ lst[k] = v:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+ end
+end
+
+function input.aux.expanded_var(instance,var) -- simple vars
+ return var:gsub("%$([%a%d%_%-]+)", function(a)
+ return instance.variables[a] or input.env(instance,a)
+ end)
+end
+
+function input.aux.entry(instance,entries,name)
+ if name and (name ~= "") then
+ name = name:gsub('%$','')
+ local result = entries[name..'.'..instance.progname] or entries[name]
+ if result then
+ return result
+ else
+ result = input.env(instance,name)
+ if result then
+ instance.variables[name] = result
+ input.expand_variables(instance)
+ return instance.expansions[name] or ""
+ end
+ end
+ end
+ return ""
+end
+function input.variable(instance,name)
+ return input.aux.entry(instance,instance.variables,name)
+end
+function input.expansion(instance,name)
+ return input.aux.entry(instance,instance.expansions,name)
+end
+
+function input.aux.is_entry(instance,entries,name)
+ if name and name ~= "" then
+ name = name:gsub('%$','')
+ return (entries[name..'.'..instance.progname] or entries[name]) ~= nil
+ else
+ return false
+ end
+end
+
+function input.is_variable(instance,name)
+ return input.aux.is_entry(instance,instance.variables,name)
+end
+function input.is_expansion(instance,name)
+ return input.aux.is_entry(instance,instance.expansions,name)
+end
+
+function input.simplified_list(str)
+ if type(str) == 'table' then
+ return str -- troubles ; ipv , in texmf
+ elseif str == '' then
+ return { }
+ else
+ local t = { }
+ for _,v in ipairs(string.splitchr(str:gsub("^\{(.+)\}$","%1"),",")) do
+ t[#t+1] = (v:gsub("^[%!]*(.+)[%/\\]*$","%1"))
+ end
+ return t
+ end
+end
+
+function input.unexpanded_path_list(instance,str)
+ local pth = input.variable(instance,str)
+ local lst = input.split_path(pth)
+ return input.aux.expanded_path(instance,lst)
+end
+function input.unexpanded_path(instance,str)
+ return file.join_path(input.unexpanded_path_list(instance,str))
+end
+
+do
+ local done = { }
+
+ function input.reset_extra_path(instance)
+ local ep = instance.extra_paths
+ if not ep then
+ ep, done = { }, { }
+ instance.extra_paths = ep
+ elseif #ep > 0 then
+ instance.lists, done = { }, { }
+ end
+ end
+
+ function input.register_extra_path(instance,paths,subpaths)
+ local ep = instance.extra_paths or { }
+ local n = #ep
+ if paths and paths ~= "" then
+ if subpaths and subpaths ~= "" then
+ for p in paths:gmatch("[^,]+") do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in subpaths:gmatch("[^,]+") do
+ local ps = p .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = input.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ else
+ for p in paths:gmatch("[^,]+") do
+ if not done[p] then
+ ep[#ep+1] = input.clean_path(p)
+ done[p] = true
+ end
+ end
+ end
+ elseif subpaths and subpaths ~= "" then
+ for i=1,n do
+ -- we gmatch each step again, not that fast, but used seldom
+ for s in subpaths:gmatch("[^,]+") do
+ local ps = ep[i] .. "/" .. s
+ if not done[ps] then
+ ep[#ep+1] = input.clean_path(ps)
+ done[ps] = true
+ end
+ end
+ end
+ end
+ if #ep > 0 then
+ instance.extra_paths = ep -- register paths
+ end
+ if #ep > n then
+ instance.lists = { } -- erase the cache
+ end
+ end
+
+end
+
+function input.expanded_path_list(instance,str)
+ local function made_list(list)
+ local ep = instance.extra_paths
+ if not ep or #ep == 0 then
+ return list
+ else
+ local done, new = { }, { }
+ -- honour . .. ../.. but only when at the start
+ for k, v in ipairs(list) do
+ if not done[v] then
+ if v:find("^[%.%/]$") then
+ done[v] = true
+ new[#new+1] = v
+ else
+ break
+ end
+ end
+ end
+ -- first the extra paths
+ for k, v in ipairs(ep) do
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ -- next the formal paths
+ for k, v in ipairs(list) do
+ if not done[v] then
+ done[v] = true
+ new[#new+1] = v
+ end
+ end
+ return new
+ end
+ end
+ if not str then
+ return ep or { }
+ elseif instance.savelists then
+ -- engine+progname hash
+ str = str:gsub("%$","")
+ if not instance.lists[str] then -- cached
+ local lst = made_list(input.split_path(input.expansion(instance,str)))
+ instance.lists[str] = input.aux.expanded_path(instance,lst)
+ end
+ return instance.lists[str]
+ else
+ local lst = input.split_path(input.expansion(instance,str))
+ return made_list(input.aux.expanded_path(instance,lst))
+ end
+end
+
+function input.expand_path(instance,str)
+ return file.join_path(input.expanded_path_list(instance,str))
+end
+
+--~ function input.first_writable_path(instance,name)
+--~ for _,v in pairs(input.expanded_path_list(instance,name)) do
+--~ if file.is_writable(file.join(v,'luatex-cache.tmp')) then
+--~ return v
+--~ end
+--~ end
+--~ return "."
+--~ end
+
+function input.expanded_path_list_from_var(instance,str) -- brrr
+ local tmp = input.var_of_format_or_suffix(str:gsub("%$",""))
+ if tmp ~= "" then
+ return input.expanded_path_list(instance,str)
+ else
+ return input.expanded_path_list(instance,tmp)
+ end
+end
+function input.expand_path_from_var(instance,str)
+ return file.join_path(input.expanded_path_list_from_var(instance,str))
+end
+
+function input.format_of_var(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+function input.format_of_suffix(str)
+ return input.suffixmap[file.extname(str)] or 'tex'
+end
+
+function input.variable_of_format(str)
+ return input.formats[str] or input.formats[input.alternatives[str]] or ''
+end
+
+function input.var_of_format_or_suffix(str)
+ local v = input.formats[str]
+ if v then
+ return v
+ end
+ v = input.formats[input.alternatives[str]]
+ if v then
+ return v
+ end
+ v = input.suffixmap[file.extname(str)]
+ if v then
+ return input.formats[isf]
+ end
+ return ''
+end
+
+function input.expand_braces(instance,str) -- output variable and brace expansion of STRING
+ local ori = input.variable(instance,str)
+ local pth = input.aux.expanded_path(instance,input.split_path(ori))
+ return file.join_path(pth)
+end
+
+-- {a,b,c,d}
+-- a,b,c/{p,q,r},d
+-- a,b,c/{p,q,r}/d/{x,y,z}//
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+-- a,b,c/{p,q/{x,y,z},r},d/{p,q,r}
+-- a{b,c}{d,e}f
+-- {a,b,c,d}
+-- {a,b,c/{p,q,r},d}
+-- {a,b,c/{p,q,r}/d/{x,y,z}//}
+-- {a,b,c/{p,q/{x,y,z}},d/{p,q,r}}
+-- {a,b,c/{p,q/{x,y,z},w}v,d/{p,q,r}}
+
+-- this one is better and faster, but it took me a while to realize
+-- that this kind of replacement is cleaner than messy parsing and
+-- fuzzy concatenating we can probably gain a bit with selectively
+-- applying lpeg, but experiments with lpeg parsing this proved not to
+-- work that well; the parsing is ok, but dealing with the resulting
+-- table is a pain because we need to work inside-out recursively
+
+-- get rid of piecewise here, just a gmatch is ok
+
+function input.aux.splitpathexpr(str, t, validate)
+ -- no need for optimization, only called a few times, we can use lpeg for the sub
+ t = t or { }
+ local concat = table.concat
+ while true do
+ local done = false
+ while true do
+ local ok = false
+ str = str:gsub("([^{},]+){([^{}]-)}", function(a,b)
+ local t = { }
+ b:piecewise(",", function(s) t[#t+1] = a .. s end)
+ ok, done = true, true
+ return "{" .. concat(t,",") .. "}"
+ end)
+ if not ok then break end
+ end
+ while true do
+ local ok = false
+ str = str:gsub("{([^{}]-)}([^{},]+)", function(a,b)
+ local t = { }
+ a:piecewise(",", function(s) t[#t+1] = s .. b end)
+ ok, done = true, true
+ return "{" .. concat(t,",") .. "}"
+ end)
+ if not ok then break end
+ end
+ while true do
+ local ok = false
+ str = str:gsub("([,{]){([^{}]+)}([,}])", function(a,b,c)
+ ok, done = true, true
+ return a .. b .. c
+ end)
+ if not ok then break end
+ end
+ if not done then break end
+ end
+ while true do
+ local ok = false
+ str = str:gsub("{([^{}]-)}{([^{}]-)}", function(a,b)
+ local t = { }
+ a:piecewise(",", function(sa)
+ b:piecewise(",", function(sb)
+ t[#t+1] = sa .. sb
+ end)
+ end)
+ ok = true
+ return "{" .. concat(t,",") .. "}"
+ end)
+ if not ok then break end
+ end
+ while true do
+ local ok = false
+ str = str:gsub("{([^{}]-)}", function(a)
+ ok = true
+ return a
+ end)
+ if not ok then break end
+ end
+ if validate then
+ str:piecewise(",", function(s)
+ s = validate(s)
+ if s then t[#t+1] = s end
+ end)
+ else
+ str:piecewise(",", function(s)
+ t[#t+1] = s
+ end)
+ end
+ return t
+end
+
+function input.aux.expanded_path(instance,pathlist) -- maybe not a list, just a path
+ -- a previous version fed back into pathlist
+ local newlist, ok = { }, false
+ for _,v in ipairs(pathlist) do
+ if v:find("[{}]") then
+ ok = true
+ break
+ end
+ end
+ if ok then
+ for _, v in ipairs(pathlist) do
+ input.aux.splitpathexpr(v, newlist, function(s)
+ s = file.collapse_path(s)
+ return s ~= "" and not s:find(instance.dummy_path_expr) and s
+ end)
+ end
+ else
+ for _,v in ipairs(pathlist) do
+ for vv in string.gmatch(v..',',"(.-),") do
+ vv = file.collapse_path(v)
+ if vv ~= "" then newlist[#newlist+1] = vv end
+ end
+ end
+ end
+ return newlist
+end
+
+input.is_readable = { }
+
+function input.aux.is_readable(readable, name)
+ if input.trace > 2 then
+ if readable then
+ input.logger("+ readable", name)
+ else
+ input.logger("- readable", name)
+ end
+ end
+ return readable
+end
+
+function input.is_readable.file(name)
+ -- return input.aux.is_readable(file.is_readable(name), name)
+ return input.aux.is_readable(input.aux.is_file(name), name)
+end
+
+input.is_readable.tex = input.is_readable.file
+
+-- name
+-- name/name
+
+function input.aux.collect_files(instance,names)
+ local filelist = { }
+ for _, fname in pairs(names) do
+ if fname then
+ if input.trace > 2 then
+ input.logger("? blobpath asked",fname)
+ end
+ local bname = file.basename(fname)
+ local dname = file.dirname(fname)
+ if dname == "" or dname:find("^%.") then
+ dname = false
+ else
+ dname = "/" .. dname .. "$"
+ end
+ for _, hash in ipairs(instance.hashes) do
+ local blobpath = hash.tag
+ local files = blobpath and instance.files[blobpath]
+ if files then
+ if input.trace > 2 then
+ input.logger('? blobpath do',blobpath .. " (" .. bname ..")")
+ end
+ local blobfile = files[bname]
+ if not blobfile then
+ local rname = "remap:"..bname
+ blobfile = files[rname]
+ if blobfile then
+ bname = files[rname]
+ blobfile = files[bname]
+ end
+ end
+ if blobfile then
+ if type(blobfile) == 'string' then
+ if not dname or blobfile:find(dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,blobfile,bname), -- search
+ input.concatinators[hash.type](blobpath,blobfile,bname) -- result
+ }
+ end
+ else
+ for _, vv in pairs(blobfile) do
+ if not dname or vv:find(dname) then
+ filelist[#filelist+1] = {
+ hash.type,
+ file.join(blobpath,vv,bname), -- search
+ input.concatinators[hash.type](blobpath,vv,bname) -- result
+ }
+ end
+ end
+ end
+ end
+ elseif input.trace > 1 then
+ input.logger('! blobpath no',blobpath .. " (" .. bname ..")" )
+ end
+ end
+ end
+ end
+ if #filelist > 0 then
+ return filelist
+ else
+ return nil
+ end
+end
+
+function input.suffix_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str][1]
+ else
+ return ""
+ end
+end
+
+function input.suffixes_of_format(str)
+ if input.suffixes[str] then
+ return input.suffixes[str]
+ else
+ return {}
+ end
+end
+
+do
+
+ -- called about 700 times for an empty doc (font initializations etc)
+ -- i need to weed the font files for redundant calls
+
+ local letter = lpeg.R("az","AZ")
+ local separator = lpeg.P("://")
+
+ local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator
+ local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+
+ -- ./name ../name /name c: ://
+ function input.aux.qualified_path(filename)
+ return qualified:match(filename)
+ end
+ function input.aux.rootbased_path(filename)
+ return rootbased:match(filename)
+ end
+
+ function input.normalize_name(original)
+ return original
+ end
+
+ input.normalize_name = file.collapse_path
+
+end
+
+function input.aux.register_in_trees(instance,name)
+ if not name:find("^%.") then
+ instance.foundintrees[name] = (instance.foundintrees[name] or 0) + 1 -- maybe only one
+ end
+end
+
+-- split the next one up, better for jit
+
+function input.aux.find_file(instance,filename) -- todo : plugin (scanners, checkers etc)
+ local result = { }
+ local stamp = nil
+ filename = input.normalize_name(filename) -- elsewhere
+ filename = file.collapse_path(filename:gsub("\\","/")) -- elsewhere
+ -- speed up / beware: format problem
+ if instance.remember then
+ stamp = filename .. "--" .. instance.engine .. "--" .. instance.progname .. "--" .. instance.format
+ if instance.found[stamp] then
+ input.logger('! remembered', filename)
+ return instance.found[stamp]
+ end
+ end
+ if filename:find('%*') then
+ input.logger('! wildcard', filename)
+ result = input.find_wildcard_files(instance,filename)
+ elseif input.aux.qualified_path(filename) then
+ if input.is_readable.file(filename) then
+ input.logger('! qualified', filename)
+ result = { filename }
+ else
+ local forcedname, ok = "", false
+ if file.extname(filename) == "" then
+ if instance.format == "" then
+ forcedname = filename .. ".tex"
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing standard filetype tex')
+ result, ok = { forcedname }, true
+ end
+ else
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ forcedname = filename .. "." .. s
+ if input.is_readable.file(forcedname) then
+ input.logger('! no suffix, forcing format filetype', s)
+ result, ok = { forcedname }, true
+ break
+ end
+ end
+ end
+ end
+ if not ok then
+ input.logger('? qualified', filename)
+ end
+ end
+ else
+ -- search spec
+ local filetype, extra, done, wantedfiles, ext = '', nil, false, { }, file.extname(filename)
+ if ext == "" then
+ if not instance.force_suffixes then
+ wantedfiles[#wantedfiles+1] = filename
+ end
+ else
+ wantedfiles[#wantedfiles+1] = filename
+ end
+ if instance.format == "" then
+ if ext == "" then
+ local forcedname = filename .. '.tex'
+ wantedfiles[#wantedfiles+1] = forcedname
+ filetype = input.format_of_suffix(forcedname)
+ input.logger('! forcing filetype',filetype)
+ else
+ filetype = input.format_of_suffix(filename)
+ input.logger('! using suffix based filetype',filetype)
+ end
+ else
+ if ext == "" then
+ for _, s in pairs(input.suffixes_of_format(instance.format)) do
+ wantedfiles[#wantedfiles+1] = filename .. "." .. s
+ end
+ end
+ filetype = instance.format
+ input.logger('! using given filetype',filetype)
+ end
+ local typespec = input.variable_of_format(filetype)
+ local pathlist = input.expanded_path_list(instance,typespec)
+ if not pathlist or #pathlist == 0 then
+ -- no pathlist, access check only / todo == wildcard
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ input.logger('? filetype',filetype or '?')
+ input.logger('? wanted files',table.concat(wantedfiles," | "))
+ end
+ for _, fname in pairs(wantedfiles) do
+ if fname and input.is_readable.file(fname) then
+ filename, done = fname, true
+ result[#result+1] = file.join('.',fname)
+ break
+ end
+ end
+ -- this is actually 'other text files' or 'any' or 'whatever'
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ local fl = filelist and filelist[1]
+ if fl then
+ filename = fl[3]
+ result[#result+1] = filename
+ done = true
+ end
+ else
+ -- list search
+ local filelist = input.aux.collect_files(instance,wantedfiles)
+ local doscan, recurse
+ if input.trace > 2 then
+ input.logger('? filename',filename)
+ -- if pathlist then input.logger('? path list',table.concat(pathlist," | ")) end
+ -- if filelist then input.logger('? file list',table.concat(filelist," | ")) end
+ end
+ -- a bit messy ... esp the doscan setting here
+ for _, path in pairs(pathlist) do
+ if path:find("^!!") then doscan = false else doscan = true end
+ if path:find("//$") then recurse = true else recurse = false end
+ local pathname = path:gsub("^!+", '')
+ done = false
+ -- using file list
+ if filelist and not (done and not instance.allresults) and recurse then
+ -- compare list entries with permitted pattern
+ pathname = pathname:gsub("([%-%.])","%%%1") -- this also influences
+ pathname = pathname:gsub("/+$", '/.*') -- later usage of pathname
+ pathname = pathname:gsub("//", '/.-/') -- not ok for /// but harmless
+ local expr = "^" .. pathname
+ -- input.debug('?',expr)
+ for _, fl in ipairs(filelist) do
+ local f = fl[2]
+ if f:find(expr) then
+ -- input.debug('T',' '..f)
+ if input.trace > 2 then
+ input.logger('= found in hash',f)
+ end
+ --- todo, test for readable
+ result[#result+1] = fl[3]
+ input.aux.register_in_trees(instance,f) -- for tracing used files
+ done = true
+ if not instance.allresults then break end
+ else
+ -- input.debug('F',' '..f)
+ end
+ end
+ end
+ if not done and doscan then
+ -- check if on disk / unchecked / does not work at all / also zips
+ if input.method_is_file(pathname) then -- ?
+ local pname = pathname:gsub("%.%*$",'')
+ if not pname:find("%*") then
+ local ppname = pname:gsub("/+$","")
+ if input.aux.can_be_dir(instance,ppname) then
+ for _, w in pairs(wantedfiles) do
+ local fname = file.join(ppname,w)
+ if input.is_readable.file(fname) then
+ if input.trace > 2 then
+ input.logger('= found by scanning',fname)
+ end
+ result[#result+1] = fname
+ done = true
+ if not instance.allresults then break end
+ end
+ end
+ else
+ -- no access needed for non existing path, speedup (esp in large tree with lots of fake)
+ end
+ end
+ end
+ end
+ if not done and doscan then
+ -- todo: slow path scanning
+ end
+ if done and not instance.allresults then break end
+ end
+ end
+ end
+ for k,v in pairs(result) do
+ result[k] = file.collapse_path(v)
+ end
+ if instance.remember then
+ instance.found[stamp] = result
+ end
+ return result
+end
+
+input.aux._find_file_ = input.aux.find_file
+
+function input.aux.find_file(instance,filename) -- maybe make a lowres cache too
+ local result = input.aux._find_file_(instance,filename)
+ if #result == 0 then
+ local lowered = filename:lower()
+ if filename ~= lowered then
+ return input.aux._find_file_(instance,lowered)
+ end
+ end
+ return result
+end
+
+if lfs and lfs.isfile then
+ input.aux.is_file = lfs.isfile -- to be done: use this
+else
+ input.aux.is_file = file.is_readable
+end
+
+if lfs and lfs.isdir then
+ function input.aux.can_be_dir(instance,name)
+ if not instance.fakepaths[name] then
+ if lfs.isdir(name) then
+ instance.fakepaths[name] = 1 -- directory
+ else
+ instance.fakepaths[name] = 2 -- no directory
+ end
+ end
+ return (instance.fakepaths[name] == 1)
+ end
+else
+ function input.aux.can_be_dir()
+ return true
+ end
+end
+
+if not input.concatinators then input.concatinators = { } end
+
+input.concatinators.tex = file.join
+input.concatinators.file = input.concatinators.tex
+
+function input.find_files(instance,filename,filetype,mustexist)
+ if type(mustexist) == boolean then
+ -- all set
+ elseif type(filetype) == 'boolean' then
+ filetype, mustexist = nil, false
+ elseif type(filetype) ~= 'string' then
+ filetype, mustexist = nil, false
+ end
+ instance.format = filetype or ''
+ local t = input.aux.find_file(instance,filename,true)
+ instance.format = ''
+ return t
+end
+
+function input.find_file(instance,filename,filetype,mustexist)
+ return (input.find_files(instance,filename,filetype,mustexist)[1] or "")
+end
+
+function input.find_given_files(instance,filename)
+ local bname, result = file.basename(filename), { }
+ for k, hash in ipairs(instance.hashes) do
+ local files = instance.files[hash.tag]
+ local blist = files[bname]
+ if not blist then
+ local rname = "remap:"..bname
+ blist = files[rname]
+ if blist then
+ bname = files[rname]
+ blist = files[bname]
+ end
+ end
+ if blist then
+ if type(blist) == 'string' then
+ result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
+ if not instance.allresults then break end
+ else
+ for kk,vv in pairs(blist) do
+ result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
+ if not instance.allresults then break end
+ end
+ end
+ end
+ end
+ return result
+end
+
+function input.find_given_file(instance,filename)
+ return (input.find_given_files(instance,filename)[1] or "")
+end
+
+function input.find_wildcard_files(instance,filename) -- todo: remap:
+ local result = { }
+ local bname, dname = file.basename(filename), file.dirname(filename)
+ local path = dname:gsub("^*/","")
+ path = path:gsub("*",".*")
+ path = path:gsub("-","%%-")
+ if dname == "" then
+ path = ".*"
+ end
+ local name = bname
+ name = name:gsub("*",".*")
+ name = name:gsub("-","%%-")
+ path = path:lower()
+ name = name:lower()
+ local function doit(blist,bname,hash,allresults)
+ local done = false
+ if blist then
+ if type(blist) == 'string' then
+ -- make function and share code
+ if (blist:lower()):find(path) then
+ result[#result+1] = input.concatinators[hash.type](hash.tag,blist,bname) or ""
+ done = true
+ end
+ else
+ for kk,vv in pairs(blist) do
+ if (vv:lower()):find(path) then
+ result[#result+1] = input.concatinators[hash.type](hash.tag,vv,bname) or ""
+ done = true
+ if not allresults then break end
+ end
+ end
+ end
+ end
+ return done
+ end
+ local files, allresults, done = instance.files, instance.allresults, false
+ if name:find("%*") then
+ for k, hash in ipairs(instance.hashes) do
+ for kk, hh in pairs(files[hash.tag]) do
+ if not kk:find("^remap:") then
+ if (kk:lower()):find(name) then
+ if doit(hh,kk,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ end
+ end
+ else
+ for k, hash in ipairs(instance.hashes) do
+ if doit(files[hash.tag][bname],bname,hash,allresults) then done = true end
+ if done and not allresults then break end
+ end
+ end
+ return result
+end
+
+function input.find_wildcard_file(instance,filename)
+ return (input.find_wildcard_files(instance,filename)[1] or "")
+end
+
+-- main user functions
+
+function input.save_used_files_in_trees(instance, filename,jobname)
+ if not filename then filename = 'luatex.jlg' end
+ local f = io.open(filename,'w')
+ if f then
+ f:write("<?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)
+ -- 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