diff options
author | Karl Berry <karl@freefriends.org> | 2015-04-18 22:52:45 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2015-04-18 22:52:45 +0000 |
commit | 16aa5a7c87f18a2483d0d61795899f886781b51c (patch) | |
tree | 1d72f00b2a4185425393598402fe055c61d1de58 /Master/texmf-dist/scripts | |
parent | e68dc4d5506d46bf72823234f902bc76d1f70352 (diff) |
context, from www.pragma-ade.com/context/beta/cont-tmf.zip (18apr15)
git-svn-id: svn://tug.org/texlive/trunk@36923 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts')
17 files changed, 10205 insertions, 5374 deletions
diff --git a/Master/texmf-dist/scripts/context/lua/mtx-bibtex.lua b/Master/texmf-dist/scripts/context/lua/mtx-bibtex.lua index c81fd596f10..92036e3a5ed 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-bibtex.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-bibtex.lua @@ -19,6 +19,7 @@ local helpinfo = [[ <subcategory> <flag name="toxml"><short>convert bibtex database(s) to xml</short></flag> <flag name="tolua"><short>convert bibtex database(s) to lua</short></flag> + <flag name="search"><short>seatch bibtex database(s)</short></flag> </subcategory> </category> </flags> @@ -29,6 +30,7 @@ local helpinfo = [[ <example><command>mtxrun --script bibtex --tolua bibl-001.bib</command></example> <example><command>mtxrun --script bibtex --tolua --simple bibl-001.bib</command></example> <example><command>mtxrun --script bibtex --toxml bibl-001.bib bibl-002.bib bibl-003.bib biblio.xml</command></example> + <example><command>mtxrun --script bibtex --search --list --pattern=match(author:foo) bar.bib</command></example> </subcategory> </category> </examples> @@ -43,7 +45,9 @@ local application = logs.application { local report = application.report +require("util-seq") require("publ-dat") +require("publ-fnd") scripts = scripts or { } scripts.bibtex = scripts.bibtex or { } @@ -57,7 +61,7 @@ function scripts.bibtex.toxml(files) if filetype == "xml" then target = filename elseif filetype == "bib" then - bibtex.load(instance,filename) + bibtex.load { dataset = instance, filename = filename } else -- not supported end @@ -77,7 +81,8 @@ function scripts.bibtex.tolua(files) if filetype == "lua" then target = filename elseif filetype == "bib" then - bibtex.load(instance,filename) + bibtex.load { dataset = instance, filename = filename } + else -- not supported end @@ -92,7 +97,48 @@ function scripts.bibtex.tolua(files) end end -if environment.arguments.toxml then +function scripts.bibtex.search(files,pattern,list) + if pattern then + local dataset = publications.datasets["whatever"] + for i=1,#files do + local filename = resolvers.findfile(files[i]) + if filename and filename ~= "" then + publications.load { dataset = "whatever", filename = filename } + end + end + local found = publications.search(dataset,pattern) + local tags = table.sortedkeys(found) + if #tags == 0 then + report("no match") + elseif list then + report("%s matches:",#tags) + local result = { } + local luadata = dataset.luadata + for i=1,#tags do + local tag = tags[i] + local entry = luadata[tag] + result[i] = { + tag, + entry.year, + entry.author, + entry.title, + } + end + utilities.formatters.formatcolumns(result) + logs.newline() + for i=1,#result do + texio.write_nl(result[i]) + end + logs.newline() + else + report("%s matches: % t",#tags,tags) + end + end +end + +if environment.arguments.search then + scripts.bibtex.search(environment.files,environment.arguments.pattern,environment.arguments.list) +elseif environment.arguments.toxml then scripts.bibtex.toxml(environment.files) elseif environment.arguments.tolua then scripts.bibtex.tolua(environment.files) diff --git a/Master/texmf-dist/scripts/context/lua/mtx-check.lua b/Master/texmf-dist/scripts/context/lua/mtx-check.lua index 9f52509ec7e..c456b44143e 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-check.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-check.lua @@ -21,7 +21,7 @@ local helpinfo = [[ <flags> <category name="basic"> <subcategory> - <flag name="convert"><short>check tex file for errors</short></flag> + <flag name="check"><short>check tex file for errors</short></flag> </subcategory> </category> </flags> @@ -34,17 +34,17 @@ local application = logs.application { helpinfo = helpinfo, } -local report = application.report +local report = application.report -scripts = scripts or { } -scripts.checker = scripts.checker or { } +scripts = scripts or { } +scripts.checker = scripts.checker or { } -local validator = { } +local validator = { } -validator.n = 1 -validator.errors = { } -validator.trace = false -validator.direct = false +validator.n = 1 +validator.errors = { } +validator.trace = false +validator.direct = false validator.printer = print validator.tracer = print @@ -68,27 +68,24 @@ local progress = function(position, data, kind) end end -local i_m, d_m = P("$"), P("$$") -local l_s, r_s = P("["), P("]") -local l_g, r_g = P("{"), P("}") +local i_m, d_m = P("$"), P("$$") +local l_s, r_s = P("["), P("]") +local l_g, r_g = P("{"), P("}") -local okay = lpeg.P("{[}") + lpeg.P("{]}") +local okay = lpeg.P("{[}") + lpeg.P("{]}") -local esc = P("\\") -local cr = P("\r") -local lf = P("\n") -local crlf = P("\r\n") -local space = S(" \t\f\v") -local newline = crlf + cr + lf +local esc = P("\\") +local space = S(" \t\f\v") +local newline = lpeg.patterns.newline -local line = newline / function() validator.n = validator.n + 1 end +local line = newline / function() validator.n = validator.n + 1 end local startluacode = P("\\startluacode") local stopluacode = P("\\stopluacode") -local somecode = startluacode * (1-stopluacode)^1 * stopluacode +local somecode = startluacode * (1-stopluacode)^1 * stopluacode -local stack = { } +local stack = { } local function push(p,s) -- print("start",p,s) @@ -117,7 +114,7 @@ local contextgrammar = P { "tokens", ["start"] = start, ["stop"] = stop, ["whatever"] = line + esc * 1 + C(P("%") * (1-line)^0), - ["grouped"] = l_g * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_g - r_g))^0 * r_g, + ["grouped"] = l_g * (V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + line + (1 - l_g - r_g))^0 * r_g, ["setup"] = l_s * (okay + V("whatever") + V("grouped") + V("setup") + V("display") + V("inline") + (1 - l_s - r_s))^0 * r_s, ["display"] = d_m * (V("whatever") + V("grouped") + (1 - d_m))^0 * d_m, ["inline"] = i_m * (V("whatever") + V("grouped") + (1 - i_m))^0 * i_m, diff --git a/Master/texmf-dist/scripts/context/lua/mtx-context.lua b/Master/texmf-dist/scripts/context/lua/mtx-context.lua index 37172327140..919dbbb8cd4 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-context.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-context.lua @@ -28,10 +28,13 @@ local fileaddsuffix = file.addsuffix local filenewsuffix = file.replacesuffix local removesuffix = file.removesuffix local validfile = lfs.isfile +local removefile = os.remove +local renamefile = os.rename +local formatters = string.formatters local application = logs.application { name = "mtx-context", - banner = "ConTeXt Process Management 0.60", + banner = "ConTeXt Process Management 0.61", -- helpinfo = helpinfo, -- table with { category_a = text_1, category_b = text_2 } or helpstring or xml_blob helpinfo = "mtx-context.xml", } @@ -279,11 +282,47 @@ local function multipass_changed(oldhash, newhash) return false end -local function multipass_copyluafile(jobname) +local f_tempfile = formatters["%s-%s-%02d.tmp"] + +local function backup(run,kind,filename) + if run == 1 then + for i=1,10 do + local tmpname = f_tempfile(jobname,kind,i) + if validfile(tmpname) then + removefile(tmpname) + report("removing %a",tmpname) + end + end + end + if validfile(filename) then + local tmpname = f_tempfile(jobname,kind,run or 1) + report("copying %a into %a",filename,tmpname) + file.copy(filename,tmpname) + else + report("no file %a, nothing kept",filename) + end +end + +local function multipass_copyluafile(jobname,run) local tuaname, tucname = jobname..".tua", jobname..".tuc" if validfile(tuaname) then - os.remove(tucname) - os.rename(tuaname,tucname) + if run then + backup(run,"tuc",tucname) + report("copying %a into %a",tuaname,tucname) + report() + end + removefile(tucname) + renamefile(tuaname,tucname) + end +end + +local function multipass_copylogfile(jobname,run) + local logname = jobname..".log" + if validfile(logname) then + if run then + backup(run,"log",logname) + report() + end end end @@ -348,8 +387,8 @@ local function result_push_purge(oldbase,newbase) for _, suffix in next, usedsuffixes.after do local oldname = fileaddsuffix(oldbase,suffix) local newname = fileaddsuffix(newbase,suffix) - os.remove(newname) - os.remove(oldname) + removefile(newname) + removefile(oldname) end end @@ -358,10 +397,10 @@ local function result_push_keep(oldbase,newbase) local oldname = fileaddsuffix(oldbase,suffix) local newname = fileaddsuffix(newbase,suffix) local tmpname = "keep-"..oldname - os.remove(tmpname) - os.rename(oldname,tmpname) - os.remove(oldname) - os.rename(newname,oldname) + removefile(tmpname) + renamefile(oldname,tmpname) + removefile(oldname) + renamefile(newname,oldname) end end @@ -369,8 +408,8 @@ local function result_save_error(oldbase,newbase) for _, suffix in next, usedsuffixes.keep do local oldname = fileaddsuffix(oldbase,suffix) local newname = fileaddsuffix(newbase,suffix) - os.remove(newname) -- to be sure - os.rename(oldname,newname) + removefile(newname) -- to be sure + renamefile(oldname,newname) end end @@ -378,8 +417,8 @@ local function result_save_purge(oldbase,newbase) for _, suffix in next, usedsuffixes.after do local oldname = fileaddsuffix(oldbase,suffix) local newname = fileaddsuffix(newbase,suffix) - os.remove(newname) -- to be sure - os.rename(oldname,newname) + removefile(newname) -- to be sure + renamefile(oldname,newname) end end @@ -388,9 +427,9 @@ local function result_save_keep(oldbase,newbase) local oldname = fileaddsuffix(oldbase,suffix) local newname = fileaddsuffix(newbase,suffix) local tmpname = "keep-"..oldname - os.remove(newname) - os.rename(oldname,newname) - os.rename(tmpname,oldname) + removefile(newname) + renamefile(oldname,newname) + renamefile(tmpname,oldname) end end @@ -549,6 +588,19 @@ function scripts.context.run(ctxdata,filename) local a_jiton = getargument("jiton") local a_jithash = getargument("jithash") local a_texformat = getargument("texformat") + local a_keeptuc = getargument("keeptuc") + local a_keeplog = getargument("keeplog") + + -- the following flag is not officially supported because i cannot forsee + -- side effects (so no bug reports please) .. we provide --sandbox that + -- does similar things but tries to ensure that context works as expected + + local a_safer = getargument("safer") + + if a_safer then + report("warning: using the luatex safer options, processing is not guaranteed") + end + -- a_batchmode = (a_batchmode and "batchmode") or (a_nonstopmode and "nonstopmode") or (a_scrollmode and "scrollmode") or nil a_synctex = check_synctex(a_synctex) @@ -592,6 +644,22 @@ function scripts.context.run(ctxdata,filename) scripts.context.make(formatname,a_engine) formatfile, scriptfile = resolvers.locateformat(formatname) end + -- + local function combine(key) + local flag = validstring(environment[key]) + local plus = analysis[key] + if flag and plus then + return plus .. "," .. flag -- flag wins + else + return flag or plus -- flag wins + end + end + local a_trackers = analysis.trackers + local a_experiments = analysis.experiments + local directives = combine("directives") + local trackers = combine("trackers") + local experiments = combine("experiments") + -- if formatfile and scriptfile then local suffix = validstring(getargument("suffix")) local resultname = validstring(getargument("result")) @@ -639,9 +707,9 @@ function scripts.context.run(ctxdata,filename) local maxnofruns = once and 1 or multipass_nofruns -- local c_flags = { - directives = validstring(environment.directives), -- gets passed via mtxrun - trackers = validstring(environment.trackers), -- gets passed via mtxrun - experiments = validstring(environment.experiments), -- gets passed via mtxrun + directives = directives, -- gets passed via mtxrun + trackers = trackers, -- gets passed via mtxrun + experiments = experiments, -- gets passed via mtxrun -- result = validstring(resultname), input = validstring(getargument("input") or filename), -- alternative input @@ -662,6 +730,7 @@ function scripts.context.run(ctxdata,filename) ["interaction"] = a_batchmode, ["synctex"] = a_synctex, ["no-parse-first-line"] = true, + ["safer"] = a_safer, -- ["no-mktex"] = true, -- ["file-line-error-style"] = true, ["fmt"] = formatfile, @@ -671,10 +740,6 @@ function scripts.context.run(ctxdata,filename) ["jithash"] = a_jithash, } -- - if a_synctex then - report("warning: synctex is enabled") -- can add upto 5% runtime - end - -- if not a_timing then -- okay elseif c_flags.usemodule then @@ -691,6 +756,15 @@ function scripts.context.run(ctxdata,filename) c_flags.directives = "system.profile" end -- + if a_synctex then + report("warning: synctex is enabled") -- can add upto 5% runtime + if c_flags.directives then + c_flags.directives = format("system.synctex=%s,%s",a_synctex,c_flags.directives) + else + c_flags.directives = format("system.synctex=%s",a_synctex) + end + end + -- -- kindofrun: 1:first run, 2:successive run, 3:once, 4:last of maxruns -- for currentrun=1,maxnofruns do @@ -704,9 +778,10 @@ function scripts.context.run(ctxdata,filename) -- local command = luatex_command(l_flags,c_flags,mainfile,a_engine) -- - report("run %s: %s",i,command) + report("run %s: %s",currentrun,command) print("") -- cleaner, else continuation on same line local returncode, errorstring = os.spawn(command) + -- todo: remake format when no proper format is found if not returncode then report("fatal error: no return code, message: %s",errorstring or "?") if resultname then @@ -715,7 +790,8 @@ function scripts.context.run(ctxdata,filename) os.exit(1) break elseif returncode == 0 then - multipass_copyluafile(jobname) + multipass_copyluafile(jobname,a_keeptuc and currentrun) + multipass_copylogfile(jobname,a_keeplog and currentrun) if not multipass_forcedruns then newhash = multipass_hashfiles(jobname) if multipass_changed(oldhash,newhash) then @@ -852,7 +928,7 @@ function scripts.context.pipe() -- still used? scripts.context.purge_job(filename) elseif getargument("purgeall") then scripts.context.purge_job(filename,true) - os.remove(filename) + removefile(filename) end else if formatname then @@ -1046,11 +1122,11 @@ local special_runfiles = { local function purge_file(dfile,cfile) if cfile and validfile(cfile) then - if os.remove(dfile) then + if removefile(dfile) then return filebasename(dfile) end elseif dfile then - if os.remove(dfile) then + if removefile(dfile) then return filebasename(dfile) end end @@ -1144,8 +1220,8 @@ local function touch(path,name,versionpattern,kind,kindpattern) end if newdata ~= "" and (oldversion ~= newversion or oldkind ~= newkind or newdata ~= olddata) then local backup = filenewsuffix(name,"tmp") - os.remove(backup) - os.rename(name,backup) + removefile(backup) + renamefile(name,backup) io.savedata(name,newdata) return name, oldversion, newversion, oldkind, newkind end diff --git a/Master/texmf-dist/scripts/context/lua/mtx-context.xml b/Master/texmf-dist/scripts/context/lua/mtx-context.xml index af8d5cc20e5..c4109328900 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-context.xml +++ b/Master/texmf-dist/scripts/context/lua/mtx-context.xml @@ -142,7 +142,7 @@ <short>assume given file present elsewhere</short> </flag> <flag name="nofile"> - <short>use dummy file as jobname</short> + <short>use dummy file as jobname</short> </flag> </subcategory> </category> @@ -152,16 +152,22 @@ <short>update context version number (also provide <ref name="expert"/>, optionally provide <ref name="basepath"/>)</short> </flag> <flag name="nostatistics"> - <short>omit runtime statistics at the end of the run</short> + <short>omit runtime statistics at the end of the run</short> </flag> <flag name="update"> - <short>update context from website (not to be confused with contextgarden)</short> + <short>update context from website (not to be confused with contextgarden)</short> </flag> - <flag name="profile"> - <short>profile job (use: mtxrun <ref name="script"/> profile <ref name="analyze"/>)</short> + <flag name="profile"> + <short>profile job (use: mtxrun <ref name="script"/> profile <ref name="analyze"/>)</short> </flag> - <flag name="timing"> - <short>generate timing and statistics overview</short> + <flag name="timing"> + <short>generate timing and statistics overview</short> + </flag> + <flag name="keeptuc"> + <short>keep previous tuc files (jobname-tuc-[run].tmp)</short> + </flag> + <flag name="keeplog"> + <short>keep previous log files (jobname-log-[run].tmp)</short> </flag> </subcategory> <subcategory> @@ -190,6 +196,11 @@ <short>do not check for file and enter scroll mode (<ref name="dummyfile"/>=whatever.tmp)</short> </flag> </subcategory> + <subcategory> + <flag name="sandbox"> + <short>process file in a limited environment</short> + </flag> + </subcategory> </category> </flags> </application> diff --git a/Master/texmf-dist/scripts/context/lua/mtx-convert.lua b/Master/texmf-dist/scripts/context/lua/mtx-convert.lua index b76b3baaf62..d5dba075acf 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-convert.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-convert.lua @@ -48,7 +48,7 @@ local convert = scripts.convert convert.converters = convert.converters or { } local converters = convert.converters -local gsprogram = os.type == "windows" and "gswin32c" or "gs" +local gsprogram = (os.type == "windows" and (os.which("gswin64c.exe") or os.which("gswin32c.exe"))) or "gs" local gstemplate_eps = "%s -q -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -dEPSCrop -dNOPAUSE -dSAFER -dNOCACHE -dBATCH -dAutoRotatePages=/None -dProcessColorModel=/DeviceCMYK -sOutputFile=%s %s -c quit" local gstemplate_ps = "%s -q -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -dNOPAUSE -dSAFER -dNOCACHE -dBATCH -dAutoRotatePages=/None -dProcessColorModel=/DeviceCMYK -sOutputFile=%s %s -c quit" diff --git a/Master/texmf-dist/scripts/context/lua/mtx-epub.lua b/Master/texmf-dist/scripts/context/lua/mtx-epub.lua index 11f0a202453..956ce4931a9 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-epub.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-epub.lua @@ -18,8 +18,89 @@ if not modules then modules = { } end modules ['mtx-epub'] = { -- first we need a decent strategy to export them. More information will be -- available on the wiki. -local format, gsub = string.format, string.gsub -local concat = table.concat +-- META-INF +-- container.xml +-- OEBPS +-- content.opf +-- toc.ncx +-- images +-- styles +-- mimetype + +-- todo: +-- +-- remove m_k_v_i prefixes +-- remap fonts %mono% in css so that we can replace +-- coverpage tests +-- split up + +-- todo: automated cover page: +-- +-- \startMPpage +-- StartPage ; +-- fill Page withcolor .5red ; +-- numeric n ; +-- for i=10 downto 1 : +-- n := i * PaperWidth/40 ; +-- draw +-- lrcorner Page shifted (0,n) +-- % -- lrcorner Page +-- -- lrcorner Page shifted (-n,0) +-- % -- cycle +-- withpen pencircle scaled 1mm +-- withcolor white ; +-- endfor ; +-- picture p ; p := image ( +-- draw +-- anchored.top( +-- textext.bot("\tttf Some Title") +-- xsized .8PaperWidth +-- ,center topboundary Page +-- ) +-- withcolor white ; +-- ) ; +-- picture q ; q := image ( +-- draw +-- anchored.top( +-- textext.bot("\tttf An Author") +-- xsized .4PaperWidth +-- shifted (0,-PaperHeight/40) +-- ,center bottomboundary p +-- ) +-- withcolor white ; +-- ) ; +-- draw p ; +-- draw q ; +-- StopPage ; +-- \stopMPpage + +local format, gsub, find = string.format, string.gsub, string.find +local concat, sortedhash = table.concat, table.sortedhash + +local formatters = string.formatters +local replacetemplate = utilities.templates.replace + +local addsuffix = file.addsuffix +local nameonly = file.nameonly +local basename = file.basename +local pathpart = file.pathpart +local joinfile = file.join +local suffix = file.suffix +local addsuffix = file.addsuffix +local removesuffix = file.removesuffix +local replacesuffix = file.replacesuffix + +local copyfile = file.copy +local removefile = os.remove + +local needsupdating = file.needsupdating + +local isdir = lfs.isdir +local isfile = lfs.isfile +local mkdir = lfs.mkdir + +local pushdir = dir.push +local popdir = dir.pop local helpinfo = [[ <?xml version="1.0"?> @@ -27,12 +108,17 @@ local helpinfo = [[ <metadata> <entry name="name">mtx-epub</entry> <entry name="detail">ConTeXt EPUB Helpers</entry> - <entry name="version">0.12</entry> + <entry name="version">1.10</entry> </metadata> <flags> <category name="basic"> <subcategory> <flag name="make"><short>create epub zip file</short></flag> + <flag name="purge"><short>remove obsolete files</short></flag> + <flag name="rename"><short>rename images to sane names</short></flag> + <flag name="svgmath"><short>convert mathml to svg</short></flag> + <flag name="svgstyle"><short>use given tex style for svg generation (overloads style in specification)</short></flag> + <flag name="all"><short>assume: --purge --rename --svgmath (for fast testing)</short></flag> </subcategory> </category> </flags> @@ -49,10 +135,12 @@ local helpinfo = [[ local application = logs.application { name = "mtx-epub", - banner = "ConTeXt EPUB Helpers 0.12", + banner = "ConTeXt EPUB Helpers 1.10", helpinfo = helpinfo, } +local report = application.report + -- script code scripts = scripts or { } @@ -60,91 +148,142 @@ scripts.epub = scripts.epub or { } local mimetype = "application/epub+zip" -local container = [[ +local t_container = [[ <?xml version="1.0" encoding="UTF-8"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> - <rootfile full-path="OEBPS/%s" media-type="application/oebps-package+xml"/> + <rootfile full-path="OEBPS/%rootfile%" media-type="application/oebps-package+xml"/> </rootfiles> </container> ]] -local package = [[ +-- urn:uuid: + +-- <dc:identifier id="%identifier%" opf:scheme="UUID">%uuid%</dc:identifier> + +local t_package = [[ <?xml version="1.0" encoding="UTF-8"?> -<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="%s"> +<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="%identifier%" version="3.0"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> - <dc:title>%s</dc:title> - <dc:language>%s</dc:language> - <dc:identifier id="%s" opf:scheme="UUID">urn:uuid:%s</dc:identifier> - <dc:creator>%s</dc:creator> - <dc:date>%s</dc:date> - <meta name="cover" content="%s" /> + <dc:title>%title%</dc:title> + <dc:language>%language%</dc:language> + <dc:identifier id="%identifier%">%uuid%</dc:identifier> + <dc:creator>%creator%</dc:creator> + <dc:date>%date%</dc:date> + <!-- + <dc:subject>%subject%</dc:subject> + <dc:description>%description%</dc:description> + <dc:publisher>%publisher%</dc:publisher> + <dc:source>%source%</dc:source> + <dc:relation>%relation%</dc:relation> + <dc:coverage>%coverage%</dc:coverage> + <dc:rights>%rights%</dc:rights> + --> + <meta name="cover" content="%coverpage%" /> + <meta name="generator" content="ConTeXt MkIV" /> + <meta property="dcterms:modified">%date%</meta> </metadata> <manifest> -%s +%manifest% </manifest> <spine toc="ncx"> <itemref idref="cover-xhtml" /> - <itemref idref="%s" /> + <itemref idref="%rootfile%" /> </spine> </package> ]] -local item = [[ <item id="%s" href="%s" media-type="%s"/>]] -local toc = [[ -<?xml version="1.0"?> +local t_item = [[ <item id="%id%" href="%filename%" media-type="%mime%" />]] +local t_prop = [[ <item id="%id%" href="%filename%" media-type="%mime%" properties="%properties%" />]] + +-- <!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"> + +local t_toc = [[ +<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"> +<!-- this is no longer needed in epub 3.0+ --> <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1"> <head> - <meta name="dtb:uid" content="%s" /> + <meta name="generator" content="ConTeXt MkIV" /> + <meta name="dtb:uid" content="%identifier%" /> <meta name="dtb:depth" content="2" /> <meta name="dtb:totalPgeCount" content="0" /> <meta name="dtb:maxPageNumber" content="0" /> </head> <docTitle> - <text>%s</text> + <text>%title%</text> </docTitle> + <docAuthor> + <text>%author%</text> + </docAuthor> + <navMap> <navPoint id="np-1" playOrder="1"> <navLabel> <text>start</text> </navLabel> - <content src="%s"/> + <content src="%root%"/> </navPoint> </navMap> </ncx> ]] -local coverxhtml = [[ +local t_navtoc = [[ <?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"> + <head> + <meta charset="utf-8" /> + <title>navtoc</title> + </head> + <body> + <div class="navtoc"> + <!-- <nav epub:type="lot"> --> + <nav epub:type="toc" id="navtoc"> + <ol> + <li><a href="%root%">document</a></li> + </ol> + </nav> + </div> + </body> +</html> +]] + +-- <html xmlns="http://www.w3.org/1999/xhtml"> +-- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> + +local t_coverxhtml = [[ +<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> - <title>cover.xhtml</title> + <meta charset="utf-8" /> + <title>cover page</title> </head> <body> - <div> - <img src="%s" alt="The cover image" style="max-width: 100%%;" /> + <div class="coverpage"> + %content% </div> </body> </html> ]] +local t_coverimg = [[ + <img src="%image%" alt="The cover image" style="max-width: 100%%;" /> +]] + -- We need to figure out what is permitted. Numbers only seem to give -- problems is some applications as do names with dashes. Also the -- optional toc is supposed to be there and although id's are by @@ -154,12 +293,13 @@ local coverxhtml = [[ local function dumbid(filename) -- return (string.gsub(os.uuid(),"%-%","")) -- to be tested - return file.nameonly(filename) .. "-" .. file.suffix(filename) + return nameonly(filename) .. "-" .. suffix(filename) end local mimetypes = { xhtml = "application/xhtml+xml", xml = "application/xhtml+xml", + html = "application/html", css = "text/css", svg = "image/svg+xml", png = "image/png", @@ -175,204 +315,601 @@ local idmakers = { default = function(filename) return dumbid(filename) end, } --- specification = { --- name = "document", --- identifier = "123", --- root = "a.xhtml", --- files = { --- "a.xhtml", --- "b.css", --- "c.png", --- } --- } - -local function locateimages(oldname,newname,subpath) +local function relocateimages(imagedata,oldname,newname,subpath,rename) local data = io.loaddata(oldname) - local images = { } - local done = gsub(data,"(background%-image *: * url%()(.-)(%))", function(before,name,after) - if subpath then - name = file.join(subpath,name) + if data then + subpath = joinfile("..",subpath) + report("relocating images") + local n = 0 + local done = gsub(data,[[(id=")(.-)(".-background%-image *: *url%()(.-)(%))]], function(s1,id,s2,name,s3) + local data = imagedata[id] + if data then + local newname = data[id].newname + if newname then + if subpath then + name = joinfile(subpath,basename(newname)) + else + name = basename(newname) + end + -- name = url.addscheme(name) + end + if newname then + n = n + 1 + if rename then + name = joinfile(subpath,addsuffix(id,suffix(name))) + end + return s1 .. id .. s2 .. name .. s3 + end + end + end) + report("%s images relocated in %a",n,newname) + if newname then + io.savedata(newname,done) end - images[#images+1] = name - return before .. name .. after - end) - if newname then - io.savedata(done,newname) end return images end +function reportobsolete(oldfiles,newfiles,purge) + + for i=1,#oldfiles do oldfiles[i] = gsub(oldfiles[i],"^[%./]+","") end + for i=1,#newfiles do newfiles[i] = gsub(newfiles[i],"^[%./]+","") end + + local old = table.tohash(oldfiles) + local new = table.tohash(newfiles) + local done = false + + for name in sortedhash(old) do + if not new[name] then + if not done then + report() + if purge then + report("removing obsolete files:") + else + report("obsolete files:") + end + report() + done = true + end + report(" %s",name) + if purge then + removefile(name) + end + end + end + + if done then + report() + end + + return done + +end + + local zippers = { { name = "zip", + binary = "zip", uncompressed = "zip %s -X -0 %s", compressed = "zip %s -X -9 -r %s", }, { - name = "7zip (7z)", + name = "7z (7zip)", + binary = "7z", uncompressed = "7z a -tzip -mx0 %s %s", compressed = "7z a -tzip %s %s", }, } -function scripts.epub.make() +function scripts.epub.make(purge,rename,svgmath,svgstyle) + + -- one can enter a jobname or jobname-export but the simple jobname is + -- preferred local filename = environment.files[1] - if filename and filename ~= "" and type(filename) == "string" then + if not filename or filename == "" or type(filename) ~= "string" then + report("provide filename") + return + end + + local specpath, specname, specfull - filename = file.basename(filename) - local specfile = file.replacesuffix(filename,"specification") - local specification = lfs.isfile(specfile) and dofile(specfile) or { } + if isdir(filename) then + specpath = filename + specname = addsuffix(specpath,"lua") + specfull = joinfile(specpath,specname) + end + + if not specfull or not isfile(specfull) then + specpath = filename .. "-export" + specname = addsuffix(filename .. "-pub","lua") + specfull = joinfile(specpath,specname) + end + + if not specfull or not isfile(specfull) then + report("unknown specificaton file %a for %a",specfull or "?",filename) + return + end --- inspect(specification) + local specification = dofile(specfull) + + if not specification or not next(specification) then + report("invalid specificaton file %a",specfile) + return + end + + report("using specification file %a",specfull) + + -- images: { ... url = location ... } + + local defaultcoverpage = "cover.xhtml" + + local name = specification.name or nameonly(filename) + local identifier = specification.identifier or "" + local htmlfiles = specification.htmlfiles or { } + local styles = specification.styles or { } + local images = specification.images or { } + local htmlroot = specification.htmlroot or htmlfiles[1] or "" + local language = specification.language or "en" + local creator = specification.creator or "context mkiv" + local author = specification.author or "anonymous" + local title = specification.title or name + local subtitle = specification.subtitle or "" + local imagefile = specification.imagefile or "" + local imagepath = specification.imagepath or "images" + local stylepath = specification.stylepath or "styles" + local coverpage = specification.firstpage or defaultcoverpage + + if type(svgstyle) == "string" and not svgstyle then + svgstyle = specification.svgstyle or "" + end - local name = specification.name or file.removesuffix(filename) - local identifier = specification.identifier or os.uuid(true) - local files = specification.files or { file.addsuffix(filename,"xhtml") } - local images = specification.images or { } - local root = specification.root or files[1] - local language = specification.language or "en" - local creator = specification.author or "My Self" - local title = specification.title or "My Title" - local firstpage = specification.firstpage or "" - local lastpage = specification.lastpage or "" + local obsolete = false - -- identifier = gsub(identifier,"[^a-zA-z0-9]","") + if #htmlfiles == 0 then + report("no html files specified") + return + end + if htmlroot == "" then + report("no html root file specified") + return + end + + if subtitle ~= "" then + title = format("%s, %s",title,subtitle) + end + + local htmlsource = specpath + local imagesource = joinfile(specpath,imagepath) + local stylesource = joinfile(specpath,stylepath) + + -- once we're here we can start moving files to the right spot; first we deal + -- with images + + -- ["image-1"]={ + -- height = "7.056cm", + -- name = "file:///t:/sources/cow.svg", + -- page = "1", + -- width = "9.701cm", + -- } + + -- end of todo + + local pdftosvg = os.which("mudraw") and formatters[ [[mudraw -o "%s" "%s" %s]] ] + + local f_svgpage = formatters["%s-page-%s.svg"] + local f_svgname = formatters["%s.svg"] + + local notupdated = 0 + local updated = 0 + local skipped = 0 + local oldfiles = dir.glob(file.join(imagesource,"*")) + local newfiles = { } + + if not pdftosvg then + report("the %a binary is not present","mudraw") + end + + -- a coverpage file has to be in the root of the export tree + + if not coverpage then + report("no cover page (image) defined") + elseif suffix(coverpage) ~= "xhtml" then + report("using cover page %a",coverpage) + local source = coverpage + local target = joinfile(htmlsource,coverpage) + htmlfiles[#htmlfiles+1 ] = coverpage + report("copying coverpage %a to %a",source,target) + copyfile(source,target) + elseif isfile(coverpage) then + report("using cover page image %a",coverpage) + images.cover = { + height = "100%", + width = "100%", + page = "1", + name = url.filename(coverpage), + used = coverpage, + } + local data = replacetemplate(t_coverxhtml, { + content = replacetemplate(t_coverimg, { + image = coverpage, + }) + }) + coverpage = defaultcoverpage + local target = joinfile(htmlsource,coverpage) + report("saving coverpage to %a",target) + io.savedata(target,data) + htmlfiles[#htmlfiles+1 ] = coverpage + else + report("cover page image %a is not present",coverpage) + coverpage = false + end + + if not coverpage then + local data = replacetemplate(t_coverxhtml, { + content = "no cover page" + }) + coverpage = defaultcoverpage + local target = joinfile(htmlsource,coverpage) + report("saving dummy coverpage to %a",target) + io.savedata(target,data) + htmlfiles[#htmlfiles+1 ] = coverpage + end - if firstpage ~= "" then - images[firstpage] = firstpage + for id, data in sortedhash(images) do + local name = url.filename(data.name) + local used = url.filename(data.used) + local base = basename(used) + local page = tonumber(data.page) or 1 + -- todo : check timestamp and prefix, rename to image-* + if suffix(used) == "pdf" then + -- todo: pass svg name + if page > 1 then + name = f_svgpage(nameonly(name),page) + else + name = f_svgname(nameonly(name)) + end + local source = used + local target = joinfile(imagesource,name) + if needsupdating(source,target) then + if pdftosvg then + local command = pdftosvg(target,source,page) + report("running command %a",command) + os.execute(command) + updated = updated + 1 + else + skipped = skipped + 1 + end + else + notupdated = notupdated + 1 + end + newfiles[#newfiles+1] = target + else + name = basename(used) + local source = used + local target = joinfile(imagesource,name) + if needsupdating(source,target) then + report("copying %a to %a",source,target) + copyfile(source,target) + updated = updated + 1 + else + notupdated = notupdated + 1 + -- no message + end + newfiles[#newfiles+1] = target end - if lastpage ~= "" then - images[lastpage] = lastpage + local target = newfiles[#newfiles] + if suffix(target) == "svg" and isfile(target) then + local data = io.loaddata(target) + if data then + local done = gsub(data,"<!(DOCTYPE.-)>","<!-- %1 -->",1) + if data ~= done then + report("doctype fixed in %a",target) + io.savedata(target,data) + end + end end + data.newname = name -- without path + end + + report("%s images checked, %s updated, %s kept, %s skipped",updated + notupdated + skipped,updated,notupdated,skipped) - identifier = "BookId" -- weird requirement - - local epubname = name - local epubpath = file.replacesuffix(name,"tree") - local epubfile = file.replacesuffix(name,"epub") - local epubroot = file.replacesuffix(name,"opf") - local epubtoc = "toc.ncx" - local epubcover = "cover.xhtml" - - application.report("creating paths in tree %s",epubpath) - lfs.mkdir(epubpath) - lfs.mkdir(file.join(epubpath,"META-INF")) - lfs.mkdir(file.join(epubpath,"OEBPS")) - - local used = { } - - local function copyone(filename) - local suffix = file.suffix(filename) - local mime = mimetypes[suffix] - if mime then - local idmaker = idmakers[suffix] or idmakers.default - local target = file.join(epubpath,"OEBPS",filename) - file.copy(filename,target) - application.report("copying %s to %s",filename,target) - used[#used+1] = format(item,idmaker(filename),filename,mime) + if reportobsolete(oldfiles,newfiles,purge) then + obsolete = true + end + + -- here we can decide not to make an epub + + local uuid = format("urn:uuid:%s",os.uuid(true)) -- os.uuid() + local identifier = "bookid" -- for now + + local epubname = removesuffix(name) + local epubpath = name .. "-epub" + local epubfile = replacesuffix(name,"epub") + local epubroot = replacesuffix(name,"opf") + local epubtoc = "toc.ncx" + local epubmimetypes = "mimetype" + local epubcontainer = "container.xml" + local epubnavigator = "nav.xhtml" + + local metapath = "META-INF" + local datapath = "OEBPS" + + local oldfiles = dir.glob(file.join(epubpath,"**/*")) + local newfiles = { } + + report("creating paths in tree %a",epubpath) + + if not isdir(epubpath) then + mkdir(epubpath) + end + if not isdir(epubpath) then + report("unable to create path %a",epubpath) + return + end + + local metatarget = joinfile(epubpath,metapath) + local htmltarget = joinfile(epubpath,datapath) + local styletarget = joinfile(epubpath,datapath,stylepath) + local imagetarget = joinfile(epubpath,datapath,imagepath) + + mkdir(metatarget) + mkdir(htmltarget) + mkdir(styletarget) + mkdir(imagetarget) + + local used = { } + local notupdated = 0 + local updated = 0 + + local oldimagespecification = joinfile(htmlsource,imagefile) + local newimagespecification = joinfile(htmltarget,imagefile) + + report("removing %a",newimagespecification) + -- removefile(newimagespecification) -- because we update that one + + local function registerone(path,filename,mathml) + local suffix = suffix(filename) + local mime = mimetypes[suffix] + if mime then + local idmaker = idmakers[suffix] or idmakers.default + local fullname = path and joinfile(path,filename) or filename + if mathml then + used[#used+1] = replacetemplate(t_prop, { + id = idmaker(filename), + filename = fullname, + mime = mime, + properties = "mathml", + } ) + else + used[#used+1] = replacetemplate(t_item, { + id = idmaker(filename), + filename = fullname, + mime = mime, + } ) end + return true + end + end + + local function registerandcopyfile(check,path,name,sourcepath,targetpath,newname,image) + + if name == "" then + report("ignoring unknown image") + return + end + + if newname then + newname = replacesuffix(newname,suffix(name)) + else + newname = name end - copyone("cover.xhtml") - copyone("toc.ncx") + local source = joinfile(sourcepath,name) + local target = joinfile(targetpath,newname) + local mathml = false - local function copythem(files) - for i=1,#files do - local filename = files[i] - if type(filename) == "string" then - copyone(filename) + if suffix(source) == "xhtml" then + if find(io.loaddata(source),"MathML") then + mathml = true -- inbelievable: the property is only valid when there is mathml + end + else + report("checking image %a -> %a",source,target) + end + if registerone(path,newname,mathml) then + if not check or needsupdating(source,target) or mathml and svgmath then + report("copying %a to %a",source,target) + copyfile(source,target) + updated = updated + 1 + else + notupdated = notupdated + 1 + end + newfiles[#newfiles+1] = target + if mathml and svgmath then + report() + report("converting mathml into svg in %a",target) + report() + local status, total, unique = moduledata.svgmath.convert(target,svgstyle) + report() + if status then + report("%s formulas converted, %s are unique",total,unique) + else + report("warning: %a in %a",total,target) end + report() end end + end - copythem(files) + -- local nofdummies = 0 + -- local dummyname = formatters["dummy-figure-%03i"] + -- local makedummy = formatters["context --extra=dummies --noconsole --once --result=%s"] + -- + -- local function registerandcopydummy(targetpath,name) + -- nofdummies = nofdummies + 1 + -- local newname = dummyname(nofdummies) + -- local target = joinfile(targetpath,newname) + -- if not isfile(target) then + -- pushdir(targetpath) + -- report("generating dummy %a for %a",newname,name or "unknown") + -- os.execute(makedummy(newname)) + -- popdir() + -- end + -- return newname + -- end + + for image, data in sortedhash(images) do + -- if data.used == "" then + -- data.newname = registerandcopydummy(imagetarget,data.name) + -- end + registerandcopyfile(true,imagepath,data.newname,imagesource,imagetarget,rename and image,true) + end + for i=1,#styles do + registerandcopyfile(false,stylepath,styles[i],stylesource,styletarget) + end + for i=1,#htmlfiles do + registerandcopyfile(false,false,htmlfiles[i],htmlsource,htmltarget) + end - local theimages = { } + relocateimages(images,oldimagespecification,oldimagespecification,imagepath,rename) + relocateimages(images,oldimagespecification,newimagespecification,imagepath,rename) - for k, v in table.sortedpairs(images) do - theimages[#theimages+1] = k - if not lfs.isfile(k) and file.suffix(k) == "svg" and file.suffix(v) == "pdf" then - local command = format("inkscape --export-plain-svg=%s %s",k,v) - application.report("running command '%s'\n\n",command) - os.execute(command) - end + report("%s files registered, %s updated, %s kept",updated + notupdated,updated,notupdated) + + local function saveinfile(what,name,data) + report("saving %s in %a",what,name) + io.savedata(name,data) + newfiles[#newfiles+1] = name + end + + used[#used+1] = replacetemplate(t_prop, { + id = "nav", + filename = epubnavigator, + properties = "nav", + mime = "application/xhtml+xml", + }) + + registerone(false,epubtoc) + + saveinfile("navigation data",joinfile(htmltarget,epubnavigator),replacetemplate(t_navtoc, { -- version 3.0 + root = htmlroot, + } ) ) + + saveinfile("used mimetypes",joinfile(epubpath,epubmimetypes),mimetype) + + saveinfile("version 2.0 container",joinfile(metatarget,epubcontainer),replacetemplate(t_container, { + rootfile = epubroot + } ) ) + + local idmaker = idmakers[suffix(htmlroot)] or idmakers.default + + saveinfile("package specification",joinfile(htmltarget,epubroot),replacetemplate(t_package, { + identifier = identifier, + title = title, + language = language, + uuid = uuid, + creator = creator, + date = os.date("!%Y-%m-%dT%H:%M:%SZ"), + coverpage = idmaker(coverpage), + manifest = concat(used,"\n"), + rootfile = idmaker(htmlroot) + } ) ) + + -- t_toc is replaced by t_navtoc in >= 3 + + saveinfile("table of contents",joinfile(htmltarget,epubtoc), replacetemplate(t_toc, { + identifier = uuid, -- identifier, + title = title, + author = author, + root = htmlroot, + } ) ) + + report("creating archive\n\n") + + pushdir(epubpath) + + removefile(epubfile) + + local usedzipper = false + + local function zipped(zipper) + local ok = os.execute(format(zipper.uncompressed,epubfile,epubmimetypes)) + if ok == 0 then + os.execute(format(zipper.compressed,epubfile,metapath)) + os.execute(format(zipper.compressed,epubfile,datapath)) + usedzipper = zipper.name + return true end + end + + -- nice way + + for i=1,#zippers do + if os.which(zippers[i].binary) and zipped(zippers[i]) then + break + end + end - copythem(theimages) - - local idmaker = idmakers[file.suffix(root)] or idmakers.default - - container = format(container, - epubroot - ) - package = format(package, - identifier, - title, - language, - identifier, - os.uuid(), - creator, - os.date("!%Y-%m-%dT%H:%M:%SZ"), - idmaker(firstpage), - concat(used,"\n"), - idmaker(root) - ) - toc = format(toc, - identifier, - title, - root - ) - coverxhtml = format(coverxhtml, - firstpage - ) - - io.savedata(file.join(epubpath,"mimetype"),mimetype) - io.savedata(file.join(epubpath,"META-INF","container.xml"),container) - io.savedata(file.join(epubpath,"OEBPS",epubroot),package) - io.savedata(file.join(epubpath,"OEBPS",epubtoc),toc) - io.savedata(file.join(epubpath,"OEBPS",epubcover),coverxhtml) - - application.report("creating archive\n\n") - - lfs.chdir(epubpath) - os.remove(epubfile) - - local done = false + -- trial and error + if not usedzipper then for i=1,#zippers do - local zipper = zippers[i] - if os.execute(format(zipper.uncompressed,epubfile,"mimetype")) then - os.execute(format(zipper.compressed,epubfile,"META-INF")) - os.execute(format(zipper.compressed,epubfile,"OEBPS")) - done = zipper.name + if zipped(zippers[i]) then break end end + end - lfs.chdir("..") + popdir() - if done then - application.report("epub archive made using %s: %s",done,file.join(epubpath,epubfile)) - else - local list = { } - for i=1,#zippers do - list[#list+1] = zipper.name - end - application.report("no epub archive made, install one of: %s",concat(list," ")) + if usedzipper then + local treefile = joinfile(epubpath,epubfile) + removefile(epubfile) + copyfile(treefile,epubfile) + if isfile(epubfile) then + removefile(treefile) end + report("epub archive made using %s: %s",usedzipper,epubfile) + else + local list = { } + for i=1,#zippers do + list[#list+1] = zippers[i].name + end + report("no epub archive made, install one of: % | t",list) + end + + if reportobsolete(oldfiles,newfiles,purge) then + obsolete = true + end + if obsolete and not purge then + report("use --purge to remove obsolete files") end end -- -if environment.argument("make") then - scripts.epub.make() -elseif environment.argument("exporthelp") then - application.export(environment.argument("exporthelp"),environment.files[1]) +local a_exporthelp = environment.argument("exporthelp") +local a_make = environment.argument("make") +local a_all = environment.argument("all") +local a_purge = a_all or environment.argument("purge") +local a_rename = a_all or environment.argument("rename") +local a_svgmath = a_all or environment.argument("svgmath") +local a_svgstyle = environment.argument("svgstyle") + +if a_make and a_svgmath then + require("x-math-svg") +end + +if a_make then + scripts.epub.make(a_purge,a_rename,a_svgmath,a_svgstyle) +elseif a_exporthelp then + application.export(a_exporthelp,environment.files[1]) else application.help() end + +-- java -jar d:\epubcheck\epubcheck-3.0.1.jar -v 3.0 -mode xhtml mkiv-publications.tree\mkiv-publications.epub diff --git a/Master/texmf-dist/scripts/context/lua/mtx-fcd.lua b/Master/texmf-dist/scripts/context/lua/mtx-fcd.lua index 2fcb9a2c7e9..76087cc37ea 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-fcd.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-fcd.lua @@ -247,7 +247,7 @@ end local function fcd_find() found = { } - pattern = environment.files[1] or "" + pattern = lower(environment.files[1] or "") if pattern ~= "" then pattern = string.escapedpattern(pattern) local paths = hash.paths @@ -255,7 +255,7 @@ local function fcd_find() local paths = paths[i][2] for i=1,#paths do local path = paths[i] - if find(path,pattern) then + if find(lower(path),pattern) then found[#found+1] = path end end diff --git a/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua b/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua index 675d9fb129e..694e6a6496f 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-fonts.lua @@ -38,7 +38,7 @@ local helpinfo = [[ <flag name="filter" value="list"><short>key-value pairs</short></flag> <flag name="all"><short>show all found instances (combined with other flags)</short></flag> <flag name="info"><short>give more details</short></flag> - <flag name="track" value="list"><short>enable trackers</short></flag> + <flag name="trackers" value="list"><short>enable trackers</short></flag> <flag name="statistics"><short>some info about the database</short></flag> </subcategory> </category> diff --git a/Master/texmf-dist/scripts/context/lua/mtx-interface.lua b/Master/texmf-dist/scripts/context/lua/mtx-interface.lua index 82cefd63817..1640f0891fd 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-interface.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-interface.lua @@ -248,7 +248,7 @@ function scripts.interface.editor(editor,split,forcedinterfaces) local mappings = { } local environments = { } local x = xml.load(keyfile) - for e, d, k in xml.elements(x,"cd:command") do + for e, d, k in xml.elements(x,"/cd:interface/cd:commands/cd:command") do -- somehow this was variable local at = d[k].at local name, value = at.name, at.value if name and value then @@ -256,7 +256,7 @@ function scripts.interface.editor(editor,split,forcedinterfaces) end end local x = xml.load(xmlfile) - for e, d, k in xml.elements(x,"cd:command") do + for e, d, k in xml.elements(x,"/cd:interface/cd:command") do local at = d[k].at local name, type = at.name, at["type"] if name and name ~= "" then @@ -322,7 +322,7 @@ function scripts.interface.check() if f then f:write("\\starttext\n") local x = xml.load(xmlfile) - for e, d, k in xml.elements(x,"cd:command") do + for e, d, k in xml.elements(x,"/cd:interface/cd:command") do local dk = d[k] local at = dk.at if at then @@ -384,6 +384,7 @@ function scripts.interface.interfaces() return a .. b .. c .. b end) end + -- we could just replace attributes for language, _ in next, commands.setuplayout do local texresult, xmlresult = { }, { } texresult[#texresult+1] = format("%% this file is auto-generated, don't edit this file\n%%") @@ -403,6 +404,7 @@ function scripts.interface.interfaces() report("saving interface translations '%s'",xmlfilename) if language ~= "en" and xmldata ~= "" then local newdata = xmldata:gsub("(<cd:interface.*language=.)en(.)","%1"..language.."%2",1) +-- newdata = replace(newdata, 'cd:command', 'name', interface.commands, interface.elements, language) newdata = replace(newdata, 'cd:string', 'value', interface.commands, interface.elements, language) newdata = replace(newdata, 'cd:variable' , 'value', interface.variables, nil, language) newdata = replace(newdata, 'cd:parameter', 'name', interface.constants, nil, language) diff --git a/Master/texmf-dist/scripts/context/lua/mtx-metapost.lua b/Master/texmf-dist/scripts/context/lua/mtx-metapost.lua index 08daec978dc..6306125d405 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-metapost.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-metapost.lua @@ -6,6 +6,8 @@ if not modules then modules = { } end modules ['mtx-metapost'] = { -- this was m license = "see context related readme files" } +-- todo: load map files + local helpinfo = [[ <?xml version="1.0"?> <application> @@ -60,24 +62,42 @@ local function assumes_latex(filename) return find(d,"\\documentstyle") or find(d,"\\documentclass") or find(d,"\\begin{document}") end +local basemaps = "original-base.map,original-ams-base.map,original-ams-euler.map,original-public-lm.map" + +local wrapper = "\\starttext\n%s\n%s\\stoptext" +local loadmap = "\\loadmapfile[%s]\n" local template = "\\startTEXpage\n\\convertMPtoPDF{%s}{1}{1}\n\\stopTEXpage" local texified = "\\starttext\n%s\n\\stoptext" local splitter = "\\startTEXpage\\externalfigure[%s][page=%s]\\stopTEXpage" local tempname = "mptopdf-temp.tex" -local function do_convert(filename) +local function do_mapfiles(mapfiles) + local maps = { } + for i=1,#mapfiles do + local mapfile = mapfiles[i] + application.report("using map file %a",mapfile) + maps[i] = format(loadmap,mapfile) + end + return table.concat(maps) +end + +local function do_convert(filename,mapfiles) if find(filename,".%d+$") or find(filename,"%.mps$") then - io.savedata(tempname,format(template,filename)) + local body = format(template,filename) + local maps = do_mapfiles(mapfiles) + io.savedata(tempname,format(wrapper,maps,body)) local resultname = format("%s-%s.pdf",file.nameonly(filename),file.suffix(filename)) local result = os.execute(format([[context --once --batch --purge --result=%s "%s"]],resultname,tempname)) return lfs.isfile(resultname) and resultname end end -local function do_split(filename,numbers) +local function do_split(filename,numbers,mapfiles) local name = file.nameonly(filename) + local maps = do_mapfiles(mapfiles) for i=1,#numbers do - io.savedata(tempname,format(splitter,file.addsuffix(name,"pdf"),i)) + local body = format(splitter,file.addsuffix(name,"pdf"),i) + io.savedata(tempname,format(wrapper,maps,body)) local resultname = format("%s-%s.pdf",name,numbers[i]) local result = os.execute(format([[context --once --batch --purge --result=%s "%s"]],resultname,tempname)) end @@ -99,12 +119,12 @@ local function do_texify(str) return format(texified,str), numbers end -local function do_convert_all(filename) +local function do_convert_all(filename,mapfiles) local results = dir.glob(file.nameonly(filename) .. ".*") -- reset local report = { } for i=1,#results do local filename = results[i] - local resultname = do_convert(filename) + local resultname = do_convert(filename,mapfiles) if resultname then report[#report+1] = { filename, resultname } end @@ -121,8 +141,8 @@ local function do_convert_all(filename) end end -local function do_convert_one(filename) - local resultname = do_convert(filename) +local function do_convert_one(filename,mapfiles) + local resultname = do_convert(filename,mapfiles) if resultname then report("%s => %s", filename,resultname) else @@ -131,17 +151,13 @@ local function do_convert_one(filename) end function scripts.mptopdf.convertall() - local rawmp = environment.arguments.rawmp or false - local metafun = environment.arguments.metafun or false - local latex = environment.arguments.latex or false - local pattern = environment.arguments.pattern or false - local split = environment.arguments.split or false - local files - if pattern then - files = dir.glob(file.nameonly(filename)) - else - files = environment.files - end + local rawmp = environment.arguments.rawmp or false + local metafun = environment.arguments.metafun or false + local latex = environment.arguments.latex or false + local pattern = environment.arguments.pattern or false + local split = environment.arguments.split or false + local files = pattern and dir.glob(file.nameonly(filename)) or environment.files + local mapfiles = utilities.parsers.settings_to_array(environment.arguments.mapfiles or basemaps) if #files > 0 then for i=1,#files do local filename = files[i] @@ -168,16 +184,16 @@ function scripts.mptopdf.convertall() local done = os.execute(command) if done then if convert then - do_convert_all(filename) + do_convert_all(filename,mapfiles) elseif split then - do_split(filename,numbers) + do_split(filename,numbers,mapfiles) -- already pdf, maybe optionally split end else report("error while processing mp file '%s'", filename) end else - do_convert_one(filename) + do_convert_one(filename,mapfiles) end end else diff --git a/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua b/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua index 5e2b2d902ca..efd2e9faf47 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-patterns.lua @@ -29,6 +29,7 @@ local helpinfo = [[ <flag name="specification"><short>additional patterns: e.g.: =cy,hyph-cy,welsh</short></flag> <flag name="compress"><short>compress data</short></flag> <flag name="words"><short>update words in given file</short></flag> + <flag name="hyphenate"><short>show hypephenated words</short></flag> </subcategory> </category> </flags> @@ -40,6 +41,7 @@ local helpinfo = [[ <example><command>mtxrun --script pattern --check --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns</command></example> <example><command>mtxrun --script pattern --convert --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns/tex --destination=e:/tmp/patterns</command></example> <example><command>mtxrun --script pattern --convert --path=c:/data/develop/svn-hyphen/trunk/hyph-utf8/tex/generic/hyph-utf8/patterns/txt --destination=e:/tmp/patterns</command></example> + <example><command>mtxrun --script pattern --hyphenate --language=nl --left=3 nogalwiedes inderdaad</command></example> </subcategory> </category> </examples> @@ -625,6 +627,49 @@ function scripts.patterns.words() end end +-- mtxrun --script patterns --hyphenate --language=nl nogalwiedes --left=3 +-- +-- hyphenator | +-- hyphenator | . n o g a l w i e d e s . . n o g a l w i e d e s . +-- hyphenator | .0n4 0 4 0 0 0 0 0 0 0 0 0 0 +-- hyphenator | 0o0g0a4l0 0 4 0 0 4 0 0 0 0 0 0 0 +-- hyphenator | 1g0a0 0 4 1 0 4 0 0 0 0 0 0 0 +-- hyphenator | 0l1w0 0 4 1 0 4 1 0 0 0 0 0 0 +-- hyphenator | 4i0e0 0 4 1 0 4 1 4 0 0 0 0 0 +-- hyphenator | 0i0e3d0e0 0 4 1 0 4 1 4 0 3 0 0 0 +-- hyphenator | 0e1d0 0 4 1 0 4 1 4 0 3 0 0 0 +-- hyphenator | 1d0e0 0 4 1 0 4 1 4 0 3 0 0 0 +-- hyphenator | 0d0e2s0 0 4 1 0 4 1 4 0 3 0 2 0 +-- hyphenator | 4s0. 0 4 1 0 4 1 4 0 3 0 4 0 +-- hyphenator | .0n4o1g0a4l1w4i0e3d0e4s0. . n o-g a l-w i e-d e s . +-- hyphenator | +-- mtx-patterns | nl 3 3 : nogalwiedes : nogal-wie-des + +function scripts.patterns.hyphenate() + require("lang-hyp") + local traditional = languages.hyphenators.traditional + local left = tonumber(environment.arguments.left) or 3 + local right = tonumber(environment.arguments.right) or 3 + local language = environment.arguments.language or "us" + local dictionary = traditional.loadpatterns(language) + local words = environment.files + local specification = { + leftcharmin = left, + rightcharmin = right, + leftchar = false, + rightchar = false, + } + trackers.enable("hyphenator.steps") + for i=1,#words do + local word = words[i] + report("%s %s %s : %s : %s", + language, left, right, + word, + traditional.injecthyphens(dictionary,word,specification) + ) + end +end + if environment.argument("check") then scripts.patterns.prepare() scripts.patterns.check() @@ -633,6 +678,8 @@ elseif environment.argument("convert") then scripts.patterns.convert() elseif environment.argument("words") then scripts.patterns.words() -- for the moment here +elseif environment.argument("hyphenate") then + scripts.patterns.hyphenate() -- for the moment here elseif environment.argument("exporthelp") then application.export(environment.argument("exporthelp"),environment.files[1]) else diff --git a/Master/texmf-dist/scripts/context/lua/mtx-server.lua b/Master/texmf-dist/scripts/context/lua/mtx-server.lua index 5466bfe80a2..dba07f1d584 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-server.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-server.lua @@ -278,6 +278,20 @@ handlers.html = handlers.htm local indices = { "index.htm", "index.html" } local portnumber = 31415 -- pi suits tex +local newline = lpeg.patterns.newline +local spacer = lpeg.patterns.spacer +local whitespace = lpeg.patterns.whitespace +local method = lpeg.P("GET") + + lpeg.P("POST") +local identify = (1-method)^0 + * lpeg.C(method) + * spacer^1 + * lpeg.C((1-spacer)^1) + * spacer^1 + * lpeg.P("HTTP/") + * (1-whitespace)^0 + * lpeg.C(lpeg.P(1)^0) + function scripts.webserver.run(configuration) -- check configuration configuration.port = tonumber(configuration.port or os.getenv("MTX_SERVER_PORT") or portnumber) or portnumber @@ -329,17 +343,24 @@ function scripts.webserver.run(configuration) local from = client:getpeername() report("request from: %s",tostring(from)) report("request data: %s",tostring(request)) - local fullurl = string.match(request,"GET (.+) HTTP/.*$") or "" -- todo: more clever / post - if fullurl == "" then + -- local fullurl = string.match(request,"(GET) (.+) HTTP/.*$") or "" -- todo: more clever / post + -- if fullurl == "" then +-- print("!!!!",request) + local method, fullurl, body = lpeg.match(identify,request) + if method == "" or fullurl == "" then report("no url") errormessage(client,configuration,404) else + + -- todo: method: POST + fullurl = url.unescapeget(fullurl) report("requested url: %s",fullurl) -- fullurl = socket.url.unescape(fullurl) -- happens later local hashed = url.hashed(fullurl) local query = url.query(hashed.query) local filename = hashed.path -- hm, not query? + hashed.body = body if script then filename = script report("forced script: %s",filename) diff --git a/Master/texmf-dist/scripts/context/lua/mtx-update.lua b/Master/texmf-dist/scripts/context/lua/mtx-update.lua index aedc48041e3..daf4f5b16ef 100644 --- a/Master/texmf-dist/scripts/context/lua/mtx-update.lua +++ b/Master/texmf-dist/scripts/context/lua/mtx-update.lua @@ -183,6 +183,8 @@ scripts.update.platforms = { ["linux-64"] = "linux-64", ["linux64"] = "linux-64", -- + ["linux-armhf"] = "linux-armhf", + -- ["freebsd"] = "freebsd", -- ["freebsd-amd64"] = "freebsd-amd64", diff --git a/Master/texmf-dist/scripts/context/lua/mtxrun.lua b/Master/texmf-dist/scripts/context/lua/mtxrun.lua index 5cb5c8d2416..48a9a27a250 100755 --- a/Master/texmf-dist/scripts/context/lua/mtxrun.lua +++ b/Master/texmf-dist/scripts/context/lua/mtxrun.lua @@ -56,7 +56,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lua"] = package.loaded["l-lua"] or true --- original size: 3409, stripped down to: 1763 +-- original size: 3888, stripped down to: 2197 if not modules then modules={} end modules ['l-lua']={ version=1.001, @@ -139,6 +139,13 @@ end if lua then lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" end +local flush=io.flush +if flush then + local execute=os.execute if execute then function os.execute(...) flush() return execute(...) end end + local exec=os.exec if exec then function os.exec (...) flush() return exec (...) end end + local spawn=os.spawn if spawn then function os.spawn (...) flush() return spawn (...) end end + local popen=io.popen if popen then function io.popen (...) flush() return popen (...) end end +end end -- of closure @@ -437,7 +444,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lpeg"] = package.loaded["l-lpeg"] or true --- original size: 29983, stripped down to: 16202 +-- original size: 36225, stripped down to: 19891 if not modules then modules={} end modules ['l-lpeg']={ version=1.001, @@ -498,6 +505,8 @@ patterns.utfbom_16_le=utfbom_16_le patterns.utfbom_8=utfbom_8 patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") +patterns.utf_32_be_nl=P("\000\000\000\r\000\000\000\n")+P("\000\000\000\r")+P("\000\000\000\n") +patterns.utf_32_le_nl=P("\r\000\000\000\n\000\000\000")+P("\r\000\000\000")+P("\n\000\000\000") patterns.utf8one=R("\000\127") patterns.utf8two=R("\194\223")*utf8next patterns.utf8three=R("\224\239")*utf8next*utf8next @@ -527,9 +536,21 @@ patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) +local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) +local e_collapser=Cs((whitespace^1*P(-1)/""+nonwhitespace^1+whitespace^1/" ")^0) +local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) +local b_stripper=Cs(spacer^0/""*(nonspacer^1+spacer^1/" ")^0) +local e_stripper=Cs((spacer^1*P(-1)/""+nonspacer^1+spacer^1/" ")^0) +local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser +patterns.b_collapser=b_collapser +patterns.m_collapser=m_collapser +patterns.e_collapser=e_collapser +patterns.b_stripper=b_stripper +patterns.m_stripper=m_stripper +patterns.e_stripper=e_stripper patterns.lowercase=lowercase patterns.uppercase=uppercase patterns.letter=patterns.lowercase+patterns.uppercase @@ -566,9 +587,12 @@ patterns.integer=sign^-1*digit^1 patterns.unsigned=digit^0*period*digit^1 patterns.float=sign^-1*patterns.unsigned patterns.cunsigned=digit^0*comma*digit^1 +patterns.cpunsigned=digit^0*(period+comma)*digit^1 patterns.cfloat=sign^-1*patterns.cunsigned +patterns.cpfloat=sign^-1*patterns.cpunsigned patterns.number=patterns.float+patterns.integer patterns.cnumber=patterns.cfloat+patterns.integer +patterns.cpnumber=patterns.cpfloat+patterns.integer patterns.oct=zero*octdigit^1 patterns.octal=patterns.oct patterns.HEX=zero*P("X")*(digit+uppercase)^1 @@ -985,23 +1009,40 @@ function lpeg.append(list,pp,delayed,checked) end return p end +local p_false=P(false) +local p_true=P(true) local function make(t) - local p + local function making(t) + local p=p_false + local keys=sortedkeys(t) + for i=1,#keys do + local k=keys[i] + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then + else + p=p+P(k)*making(v) + end + end + end + if t[""] then + p=p+p_true + end + return p + end + local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] - local v=t[k] - if not p then - if next(v) then - p=P(k)*make(v) - else - p=P(k) - end - else - if next(v) then - p=p+P(k)*make(v) + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then else - p=p+P(k) + p=p+P(k)*making(v) end end end @@ -1009,13 +1050,73 @@ local function make(t) end function lpeg.utfchartabletopattern(list) local tree={} - for i=1,#list do - local t=tree - for c in gmatch(list[i],".") do - if not t[c] then - t[c]={} + local n=#list + if n==0 then + for s in next,list do + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true + end + end + else + for i=1,n do + local s=list[i] + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true end - t=t[c] end end return make(tree) @@ -1055,6 +1156,65 @@ local case_2=period*(digit-trailingzeros)^1*(trailingzeros/"") local number=digit^1*(case_1+case_2) local stripper=Cs((number+1)^0) lpeg.patterns.stripzeros=stripper +local byte_to_HEX={} +local byte_to_hex={} +local byte_to_dec={} +local hex_to_byte={} +for i=0,255 do + local H=format("%02X",i) + local h=format("%02x",i) + local d=format("%03i",i) + local c=char(i) + byte_to_HEX[c]=H + byte_to_hex[c]=h + byte_to_dec[c]=d + hex_to_byte[h]=c + hex_to_byte[H]=c +end +local hextobyte=P(2)/hex_to_byte +local bytetoHEX=P(1)/byte_to_HEX +local bytetohex=P(1)/byte_to_hex +local bytetodec=P(1)/byte_to_dec +local hextobytes=Cs(hextobyte^0) +local bytestoHEX=Cs(bytetoHEX^0) +local bytestohex=Cs(bytetohex^0) +local bytestodec=Cs(bytetodec^0) +patterns.hextobyte=hextobyte +patterns.bytetoHEX=bytetoHEX +patterns.bytetohex=bytetohex +patterns.bytetodec=bytetodec +patterns.hextobytes=hextobytes +patterns.bytestoHEX=bytestoHEX +patterns.bytestohex=bytestohex +patterns.bytestodec=bytestodec +function string.toHEX(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestoHEX,s) + end +end +function string.tohex(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestohex,s) + end +end +function string.todec(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestodec,s) + end +end +function string.tobytes(s) + if not s or s=="" then + return s + else + return lpegmatch(hextobytes,s) + end +end end -- of closure @@ -1082,7 +1242,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-string"] = package.loaded["l-string"] or true --- original size: 5671, stripped down to: 2827 +-- original size: 5694, stripped down to: 2827 if not modules then modules={} end modules ['l-string']={ version=1.001, @@ -1187,7 +1347,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-table"] = package.loaded["l-table"] or true --- original size: 31860, stripped down to: 20846 +-- original size: 33830, stripped down to: 21894 if not modules then modules={} end modules ['l-table']={ version=1.001, @@ -1230,11 +1390,12 @@ function table.keys(t) end end local function compare(a,b) - local ta,tb=type(a),type(b) - if ta==tb then + local ta=type(a) + local tb=type(b) + if ta==tb and ta=="number" then return a<b else - return tostring(a)<tostring(b) + return tostring(a)<tostring(b) end end local function sortedkeys(tab) @@ -1334,14 +1495,14 @@ local function sortedhash(t,cmp) end local n=0 local m=#s - local function kv(s) + local function kv() if n<m then n=n+1 local k=s[n] return k,t[k] end end - return kv,s + return kv else return nothing end @@ -1506,7 +1667,7 @@ local function simple_table(t) if tv=="number" then nt=nt+1 if hexify then - tt[nt]=format("0x%04X",v) + tt[nt]=format("0x%X",v) else tt[nt]=tostring(v) end @@ -1537,7 +1698,7 @@ local function do_serialize(root,name,depth,level,indexed) local tn=type(name) if tn=="number" then if hexify then - handle(format("%s[0x%04X]={",depth,name)) + handle(format("%s[0x%X]={",depth,name)) else handle(format("%s[%s]={",depth,name)) end @@ -1554,7 +1715,7 @@ local function do_serialize(root,name,depth,level,indexed) end end end - if root and next(root) then + if root and next(root)~=nil then local first,last=nil,0 if compact then last=#root @@ -1576,7 +1737,7 @@ local function do_serialize(root,name,depth,level,indexed) if compact and first and tk=="number" and k>=first and k<=last then if tv=="number" then if hexify then - handle(format("%s 0x%04X,",depth,v)) + handle(format("%s 0x%X,",depth,v)) else handle(format("%s %s,",depth,v)) end @@ -1587,7 +1748,7 @@ local function do_serialize(root,name,depth,level,indexed) handle(format("%s %q,",depth,v)) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then handle(format("%s {},",depth)) elseif inline then local st=simple_table(v) @@ -1617,25 +1778,25 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="number" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=0x%04X,",depth,k,v)) + handle(format("%s [0x%X]=0x%X,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end elseif tk=="boolean" then if hexify then - handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v)) + handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v)) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then - handle(format("%s %s=0x%04X,",depth,k,v)) + handle(format("%s %s=0x%X,",depth,k,v)) else handle(format("%s %s=%s,",depth,k,v)) end else if hexify then - handle(format("%s [%q]=0x%04X,",depth,k,v)) + handle(format("%s [%q]=0x%X,",depth,k,v)) else handle(format("%s [%q]=%s,",depth,k,v)) end @@ -1644,7 +1805,7 @@ local function do_serialize(root,name,depth,level,indexed) if reduce and tonumber(v) then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v)) + handle(format("%s [0x%X]=%s,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end @@ -1658,7 +1819,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,v)) + handle(format("%s [0x%X]=%q,",depth,k,v)) else handle(format("%s [%s]=%q,",depth,k,v)) end @@ -1671,10 +1832,10 @@ local function do_serialize(root,name,depth,level,indexed) end end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={},",depth,k)) + handle(format("%s [0x%X]={},",depth,k)) else handle(format("%s [%s]={},",depth,k)) end @@ -1690,7 +1851,7 @@ local function do_serialize(root,name,depth,level,indexed) if st then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", "))) + handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%s]={ %s },",depth,k,concat(st,", "))) end @@ -1710,7 +1871,7 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="boolean" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false")) + handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%s]=%s,",depth,k,v and "true" or "false")) end @@ -1726,7 +1887,7 @@ local function do_serialize(root,name,depth,level,indexed) local f=getinfo(v).what=="C" and dump(dummy) or dump(v) if tk=="number" then if hexify then - handle(format("%s [0x%04X]=load(%q),",depth,k,f)) + handle(format("%s [0x%X]=load(%q),",depth,k,f)) else handle(format("%s [%s]=load(%q),",depth,k,f)) end @@ -1741,7 +1902,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,tostring(v))) + handle(format("%s [0x%X]=%q,",depth,k,tostring(v))) else handle(format("%s [%s]=%q,",depth,k,tostring(v))) end @@ -1795,7 +1956,7 @@ local function serialize(_handle,root,name,specification) end elseif tname=="number" then if hexify then - handle(format("[0x%04X]={",name)) + handle(format("[0x%X]={",name)) else handle("["..name.."]={") end @@ -1813,7 +1974,7 @@ local function serialize(_handle,root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,"",0) end end @@ -1942,14 +2103,25 @@ local function identical(a,b) end table.identical=identical table.are_equal=are_equal -function table.compact(t) - if t then - for k,v in next,t do - if not next(v) then - t[k]=nil +local function sparse(old,nest,keeptables) + local new={} + for k,v in next,old do + if not (v=="" or v==false) then + if nest and type(v)=="table" then + v=sparse(v,nest) + if keeptables or next(v)~=nil then + new[k]=v + end + else + new[k]=v end end end + return new +end +table.sparse=sparse +function table.compact(t) + return sparse(t,true,true) end function table.contains(t,v) if t then @@ -2054,10 +2226,10 @@ function table.sub(t,i,j) return { unpack(t,i,j) } end function table.is_empty(t) - return not t or not next(t) + return not t or next(t)==nil end function table.has_one_entry(t) - return t and not next(t,next(t)) + return t and next(t,next(t))==nil end function table.loweredkeys(t) local l={} @@ -2102,6 +2274,44 @@ function table.values(t,s) return {} end end +function table.filtered(t,pattern,sort,cmp) + if t and type(pattern)=="string" then + if sort then + local s + if cmp then + s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) + else + s=sortedkeys(t) + end + local n=0 + local m=#s + local function kv(s) + while n<m do + n=n+1 + local k=s[n] + if find(k,pattern) then + return k,t[k] + end + end + end + return kv,s + else + local n=next(t) + local function iterator() + while n~=nil do + local k=n + n=next(t,k) + if find(k,pattern) then + return k,t[k] + end + end + end + return iterator,t + end + else + return nothing + end +end end -- of closure @@ -2110,7 +2320,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-io"] = package.loaded["l-io"] or true --- original size: 8824, stripped down to: 6347 +-- original size: 8643, stripped down to: 6232 if not modules then modules={} end modules ['l-io']={ version=1.001, @@ -2417,8 +2627,6 @@ function io.readstring(f,n,m) local str=gsub(f:read(n),"\000","") return str end -if not io.i_limiter then function io.i_limiter() end end -if not io.o_limiter then function io.o_limiter() end end end -- of closure @@ -2645,7 +2853,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-os"] = package.loaded["l-os"] or true --- original size: 16093, stripped down to: 9704 +-- original size: 15761, stripped down to: 9403 if not modules then modules={} end modules ['l-os']={ version=1.001, @@ -2719,13 +2927,10 @@ if not os.__getenv__ then setmetatable(os.env,{ __index=__index,__newindex=__newindex } ) end end -local execute,spawn,exec,iopopen,ioflush=os.execute,os.spawn or os.execute,os.exec or os.execute,io.popen,io.flush -function os.execute(...) ioflush() return execute(...) end -function os.spawn (...) ioflush() return spawn (...) end -function os.exec (...) ioflush() return exec (...) end -function io.popen (...) ioflush() return iopopen(...) end +local execute=os.execute +local iopopen=io.popen function os.resultof(command) - local handle=io.popen(command,"r") + local handle=iopopen(command,"r") if handle then local result=handle:read("*all") or "" handle:close() @@ -2754,7 +2959,7 @@ local launchers={ unix="$BROWSER %s &> /dev/null &", } function os.launch(str) - os.execute(format(launchers[os.name] or launchers.unix,str)) + execute(format(launchers[os.name] or launchers.unix,str)) end if not os.times then function os.times() @@ -3029,7 +3234,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-file"] = package.loaded["l-file"] or true --- original size: 18308, stripped down to: 9948 +-- original size: 20949, stripped down to: 9945 if not modules then modules={} end modules ['l-file']={ version=1.001, @@ -3043,41 +3248,28 @@ local file=file if not lfs then lfs=optionalrequire("lfs") end -if not lfs then - lfs={ - getcurrentdir=function() - return "." - end, - attributes=function() - return nil - end, - isfile=function(name) - local f=io.open(name,'rb') - if f then - f:close() - return true - end - end, - isdir=function(name) - print("you need to load lfs") - return false - end - } -elseif not lfs.isfile then - local attributes=lfs.attributes - function lfs.isdir(name) - return attributes(name,"mode")=="directory" - end - function lfs.isfile(name) - return attributes(name,"mode")=="file" - end -end local insert,concat=table.insert,table.concat local match,find,gmatch=string.match,string.find,string.gmatch local lpegmatch=lpeg.match local getcurrentdir,attributes=lfs.currentdir,lfs.attributes local checkedsplit=string.checkedsplit local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct +local tricky=S("/\\")*P(-1) +local attributes=lfs.attributes +if sandbox then + sandbox.redefine(lfs.isfile,"lfs.isfile") + sandbox.redefine(lfs.isdir,"lfs.isdir") +end +function lfs.isdir(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end +end +function lfs.isfile(name) + return attributes(name,"mode")=="file" +end local colon=P(":") local period=P(".") local periods=P("..") @@ -3279,28 +3471,30 @@ local isroot=fwslash^1*-1 local hasroot=fwslash^1 local reslasher=lpeg.replacer(S("\\/"),"/") local deslasher=lpeg.replacer(S("\\/")^1,"/") -function file.join(...) - local lst={... } - local one=lst[1] +function file.join(one,two,three,...) + if not two then + return one=="" and one or lpegmatch(stripper,one) + end + if one=="" then + return lpegmatch(stripper,three and concat({ two,three,... },"/") or two) + end if lpegmatch(isnetwork,one) then local one=lpegmatch(reslasher,one) - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return one..two else return one.."/"..two end elseif lpegmatch(isroot,one) then - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return two else return "/"..two end - elseif one=="" then - return lpegmatch(stripper,concat(lst,"/",2)) else - return lpegmatch(deslasher,concat(lst,"/")) + return lpegmatch(deslasher,concat({ one,two,three,... },"/")) end end local drivespec=R("az","AZ")^1*colon @@ -3474,7 +3668,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-md5"] = package.loaded["l-md5"] or true --- original size: 3760, stripped down to: 2088 +-- original size: 3248, stripped down to: 2266 if not modules then modules={} end modules ['l-md5']={ version=1.001, @@ -3492,14 +3686,20 @@ if not md5 then } end local md5,file=md5,file -local gsub,format,byte=string.gsub,string.format,string.byte -local md5sum=md5.sum -local function convert(str,fmt) - return (gsub(md5sum(str),".",function(chr) return format(fmt,byte(chr)) end)) -end -if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end -if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end -if not md5.dec then function md5.dec(str) return convert(str,"%03i") end end +local gsub=string.gsub +do + local patterns=lpeg and lpeg.patterns + if patterns then + local bytestoHEX=patterns.bytestoHEX + local bytestohex=patterns.bytestohex + local bytestodec=patterns.bytestodec + local lpegmatch=lpeg.match + local md5sum=md5.sum + if not md5.HEX then function md5.HEX(str) if str then return lpegmatch(bytestoHEX,md5sum(str)) end end end + if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end + if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end + end +end function file.needsupdating(oldname,newname,threshold) local oldtime=lfs.attributes(oldname,"modification") if oldtime then @@ -3556,7 +3756,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-url"] = package.loaded["l-url"] or true --- original size: 12292, stripped down to: 5585 +-- original size: 12531, stripped down to: 5721 if not modules then modules={} end modules ['l-url']={ version=1.001, @@ -3642,19 +3842,25 @@ local splitquery=Cf (Ct("")*P { "sequence", pair=Cg(key*equal*value), },rawset) local function hashed(str) - if str=="" then + if not str or str=="" then return { scheme="invalid", original=str, } end - local s=split(str) - local rawscheme=s[1] - local rawquery=s[4] - local somescheme=rawscheme~="" - local somequery=rawquery~="" + local detailed=split(str) + local rawscheme="" + local rawquery="" + local somescheme=false + local somequery=false + if detailed then + rawscheme=detailed[1] + rawquery=detailed[4] + somescheme=rawscheme~="" + somequery=rawquery~="" + end if not somescheme and not somequery then - s={ + return { scheme="file", authority="", path=str, @@ -3664,28 +3870,28 @@ local function hashed(str) noscheme=true, filename=str, } - else - local authority,path,filename=s[2],s[3] - if authority=="" then - filename=path - elseif path=="" then - filename="" - else - filename=authority.."/"..path - end - s={ - scheme=rawscheme, - authority=authority, - path=path, - query=lpegmatch(unescaper,rawquery), - queries=lpegmatch(splitquery,rawquery), - fragment=s[5], - original=str, - noscheme=false, - filename=filename, - } end - return s + local authority=detailed[2] + local path=detailed[3] + local filename=nil + if authority=="" then + filename=path + elseif path=="" then + filename="" + else + filename=authority.."/"..path + end + return { + scheme=rawscheme, + authority=authority, + path=path, + query=lpegmatch(unescaper,rawquery), + queries=lpegmatch(splitquery,rawquery), + fragment=detailed[5], + original=str, + noscheme=false, + filename=filename, + } end url.split=split url.hasscheme=hasscheme @@ -3719,7 +3925,7 @@ function url.construct(hash) end return lpegmatch(escaper,concat(fullurl)) end -local pattern=Cs(noslash*R("az","AZ")*(S(":|")/":")*noslash*P(1)^0) +local pattern=Cs(slash^-1/""*R("az","AZ")*((S(":|")/":")+P(":"))*slash*P(1)^0) function url.filename(filename) local spec=hashed(filename) local path=spec.path @@ -3767,7 +3973,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-dir"] = package.loaded["l-dir"] or true --- original size: 14788, stripped down to: 9096 +-- original size: 16765, stripped down to: 11003 if not modules then modules={} end modules ['l-dir']={ version=1.001, @@ -3777,7 +3983,7 @@ if not modules then modules={} end modules ['l-dir']={ license="see context related readme files" } local type,select=type,select -local find,gmatch,match,gsub=string.find,string.gmatch,string.match,string.gsub +local find,gmatch,match,gsub,sub=string.find,string.gmatch,string.match,string.gsub,string.sub local concat,insert,remove,unpack=table.concat,table.insert,table.remove,table.unpack local lpegmatch=lpeg.match local P,S,R,C,Cc,Cs,Ct,Cv,V=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cv,lpeg.V @@ -3786,53 +3992,126 @@ local dir=dir local lfs=lfs local attributes=lfs.attributes local walkdir=lfs.dir -local isdir=lfs.isdir -local isfile=lfs.isfile +local isdir=lfs.isdir +local isfile=lfs.isfile local currentdir=lfs.currentdir local chdir=lfs.chdir local mkdir=lfs.mkdir local onwindows=os.type=="windows" or find(os.getenv("PATH"),";",1,true) -if not isdir then - function isdir(name) - local a=attributes(name) - return a and a.mode=="directory" +if onwindows then + local tricky=S("/\\")*P(-1) + isdir=function(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end + end + isfile=function(name) + return attributes(name,"mode")=="file" end lfs.isdir=isdir -end -if not isfile then - function isfile(name) - local a=attributes(name) - return a and a.mode=="file" + lfs.isfile=isfile +else + isdir=function(name) + return attributes(name,"mode")=="directory" end + isfile=function(name) + return attributes(name,"mode")=="file" + end + lfs.isdir=isdir lfs.isfile=isfile end function dir.current() return (gsub(currentdir(),"\\","/")) end -local lfsisdir=isdir -local function isdir(path) - path=gsub(path,"[/\\]+$","") - return lfsisdir(path) +local function glob_pattern_function(path,patt,recurse,action) + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + action(full) + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end + end + end + end + if dirs then + for i=1,#dirs do + glob_pattern_function(dirs[i],patt,recurse,action) + end + end + end end -lfs.isdir=isdir -local function globpattern(path,patt,recurse,action) - if path=="/" then - path=path.."." - elseif not find(path,"/$") then - path=path..'/' - end - if isdir(path) then - for name in walkdir(path) do - local full=path..name - local mode=attributes(full,'mode') - if mode=='file' then - if find(full,patt) then - action(full) +local function glob_pattern_table(path,patt,recurse,result) + if not result then + result={} + end + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + result[#result+1]=full + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - globpattern(full,patt,recurse,action) end end + if dirs then + for i=1,#dirs do + glob_pattern_table(dirs[i],patt,recurse,result) + end + end + end + return result +end +local function globpattern(path,patt,recurse,method) + local kind=type(method) + if patt and sub(patt,1,-3)==path then + patt=false + end + if kind=="function" then + return glob_pattern_function(path,patt,recurse,method) + elseif kind=="table" then + return glob_pattern_table(path,patt,recurse,method) + else + return glob_pattern_table(path,patt,recurse,{}) end end dir.globpattern=globpattern @@ -3845,34 +4124,40 @@ local function collectpattern(path,patt,recurse,result) ok,scanner,first=xpcall(function() return walkdir(path) end,function() end) end if ok and type(scanner)=="function" then - if not find(path,"/$") then path=path..'/' end + if not find(path,"/$") then + path=path..'/' + end for name in scanner,first do - local full=path..name - local attr=attributes(full) - local mode=attr.mode - if mode=='file' then - if find(full,patt) then + if name=="." then + elseif name==".." then + else + local full=path..name + local attr=attributes(full) + local mode=attr.mode + if mode=='file' then + if find(full,patt) then + result[name]=attr + end + elseif recurse and mode=="directory" then + attr.list=collectpattern(full,patt,recurse) result[name]=attr end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - attr.list=collectpattern(full,patt,recurse) - result[name]=attr end end end return result end dir.collectpattern=collectpattern -local separator -if onwindows then +local separator,pattern +if onwindows then local slash=S("/\\")/"/" - pattern=Ct { + pattern={ [1]=(Cs(P(".")+slash^1)+Cs(R("az","AZ")*P(":")*slash^0)+Cc("./"))*V(2)*V(3), [2]=Cs(((1-S("*?/\\"))^0*slash)^0), [3]=Cs(P(1)^0) } -else - pattern=Ct { +else + pattern={ [1]=(C(P(".")+P("/")^1)+Cc("./"))*V(2)*V(3), [2]=C(((1-S("*?/"))^0*P("/"))^0), [3]=C(P(1)^0) @@ -3890,9 +4175,8 @@ local function glob(str,t) elseif isfile(str) then t(str) else - local split=lpegmatch(pattern,str) - if split then - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) @@ -3914,16 +4198,12 @@ local function glob(str,t) return { str } end else - local split=lpegmatch(pattern,str) - if split then - local t=t or {} - local action=action or function(name) t[#t+1]=name end - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) - globpattern(start,result,recurse,action) - return t + return globpattern(start,result,recurse,t) else return {} end @@ -4071,47 +4351,51 @@ else end end dir.makedirs=dir.mkdirs -if onwindows then - function dir.expandname(str) - local first,nothing,last=match(str,"^(//)(//*)(.*)$") - if first then - first=dir.current().."/" - end - if not first then - first,last=match(str,"^(//)/*(.*)$") - end - if not first then - first,last=match(str,"^([a-zA-Z]:)(.*)$") - if first and not find(last,"^/") then - local d=currentdir() - if chdir(first) then - first=dir.current() +do + local chdir=sandbox and sandbox.original(chdir) or chdir + if onwindows then + local xcurrentdir=dir.current + function dir.expandname(str) + local first,nothing,last=match(str,"^(//)(//*)(.*)$") + if first then + first=xcurrentdir().."/" + end + if not first then + first,last=match(str,"^(//)/*(.*)$") + end + if not first then + first,last=match(str,"^([a-zA-Z]:)(.*)$") + if first and not find(last,"^/") then + local d=currentdir() + if chdir(first) then + first=xcurrentdir() + end + chdir(d) end - chdir(d) + end + if not first then + first,last=xcurrentdir(),str + end + last=gsub(last,"//","/") + last=gsub(last,"/%./","/") + last=gsub(last,"^/*","") + first=gsub(first,"/*$","") + if last=="" or last=="." then + return first + else + return first.."/"..last end end - if not first then - first,last=dir.current(),str - end - last=gsub(last,"//","/") - last=gsub(last,"/%./","/") - last=gsub(last,"^/*","") - first=gsub(first,"/*$","") - if last=="" or last=="." then - return first - else - return first.."/"..last - end - end -else - function dir.expandname(str) - if not find(str,"^/") then - str=currentdir().."/"..str + else + function dir.expandname(str) + if not find(str,"^/") then + str=currentdir().."/"..str + end + str=gsub(str,"//","/") + str=gsub(str,"/%./","/") + str=gsub(str,"(.)/%.$","%1") + return str end - str=gsub(str,"//","/") - str=gsub(str,"/%./","/") - str=gsub(str,"(.)/%.$","%1") - return str end end file.expandname=dir.expandname @@ -4154,7 +4438,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-boolean"] = package.loaded["l-boolean"] or true --- original size: 1809, stripped down to: 1527 +-- original size: 1850, stripped down to: 1568 if not modules then modules={} end modules ['l-boolean']={ version=1.001, @@ -4208,11 +4492,11 @@ function string.booleanstring(str) return str=="yes" or str=="on" or str=="t" end end -function string.is_boolean(str,default) +function string.is_boolean(str,default,strict) if type(str)=="string" then - if str=="true" or str=="yes" or str=="on" or str=="t" or str=="1" then + if str=="true" or str=="yes" or str=="on" or str=="t" or (not strict and str=="1") then return true - elseif str=="false" or str=="no" or str=="off" or str=="f" or str=="0" then + elseif str=="false" or str=="no" or str=="off" or str=="f" or (not strict and str=="0") then return false end end @@ -4226,7 +4510,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-unicode"] = package.loaded["l-unicode"] or true --- original size: 33706, stripped down to: 14938 +-- original size: 37233, stripped down to: 15684 if not modules then modules={} end modules ['l-unicode']={ version=1.001, @@ -4242,7 +4526,9 @@ local type=type local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch local concat=table.concat local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp -local lpegmatch,patterns=lpeg.match,lpeg.patterns +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern local bytepairs=string.bytepairs local finder=lpeg.finder local replacer=lpeg.replacer @@ -4251,7 +4537,7 @@ local utfgmatch=utf.gmatch local p_utftype=patterns.utftype local p_utfstricttype=patterns.utfstricttype local p_utfoffset=patterns.utfoffset -local p_utf8char=patterns.utf8char +local p_utf8char=patterns.utf8character local p_utf8byte=patterns.utf8byte local p_utfbom=patterns.utfbom local p_newline=patterns.newline @@ -4450,15 +4736,52 @@ if not utf.sub then end end end -function utf.remapper(mapping) - local pattern=Cs((p_utf8char/mapping)^0) - return function(str) - if not str or str=="" then - return "" +function utf.remapper(mapping,option) + local variant=type(mapping) + if variant=="table" then + if option=="dynamic" then + local pattern=false + table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) + return function(str) + if not str or str=="" then + return "" + else + if not pattern then + pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + end + return lpegmatch(pattern,str) + end + end + elseif option=="pattern" then + return Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) else - return lpegmatch(pattern,str) + local pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + elseif variant=="function" then + if option=="pattern" then + return Cs((p_utf8char/mapping+p_utf8char)^0) + else + local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + else + return function(str) + return str or "" end - end,pattern + end end function utf.replacer(t) local r=replacer(t,false,false,true) @@ -4508,190 +4831,157 @@ function utf.magic(f) end local utf16_to_utf8_be,utf16_to_utf8_le local utf32_to_utf8_be,utf32_to_utf8_le -local utf_16_be_linesplitter=patterns.utfbom_16_be^-1*lpeg.tsplitat(patterns.utf_16_be_nl) -local utf_16_le_linesplitter=patterns.utfbom_16_le^-1*lpeg.tsplitat(patterns.utf_16_le_nl) -if bytepairs then - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*left+right - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_be,s) + else + return s end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*right+left - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +end +local utf16_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_be_linesplitter,t) end - utf32_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*256*256*a+256*256*b - else - r=r+1 - result[t]=utfchar(more+256*a+b) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_be,s) end - return t end - utf32_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*b+a - else - r=r+1 - result[t]=utfchar(more+256*256*256*b+256*256*a) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) - end - return t + return t +end +utf16_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_le,s) + else + return s end -else - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if left=="\000" then - r=r+1 - result[r]=utfchar(byte(right)) - elseif right then - local now=256*byte(left)+byte(right) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) +end +local utf16_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_le,s) end - return t end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) + return t +end +utf32_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_be,s) + else + return s + end +end +local utf32_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_be,s) end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if right=="\000" then - r=r+1 - result[r]=utfchar(byte(left)) - elseif right then - local now=256*byte(right)+byte(left) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) + end + return t +end +utf32_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_le,s) + else + return s + end +end +local utf32_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_le,s) end - return t end - utf32_to_utf8_le=function() return {} end - utf32_to_utf8_be=function() return {} end + return t end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t utf.utf16_to_utf8_le=utf16_to_utf8_le utf.utf16_to_utf8_be=utf16_to_utf8_be utf.utf32_to_utf8_le=utf32_to_utf8_le utf.utf32_to_utf8_be=utf32_to_utf8_be -function utf.utf8_to_utf8(t) +function utf.utf8_to_utf8_t(t) return type(t)=="string" and lpegmatch(utflinesplitter,t) or t end -function utf.utf16_to_utf8(t,endian) - return endian and utf16_to_utf8_be(t) or utf16_to_utf8_le(t) or t +function utf.utf16_to_utf8_t(t,endian) + return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t end -function utf.utf32_to_utf8(t,endian) - return endian and utf32_to_utf8_be(t) or utf32_to_utf8_le(t) or t +function utf.utf32_to_utf8_t(t,endian) + return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t end -local function little(c) - local b=byte(c) +local function little(b) if b<0x10000 then return char(b%256,b/256) else @@ -4700,8 +4990,7 @@ local function little(c) return char(b1%256,b1/256,b2%256,b2/256) end end -local function big(c) - local b=byte(c) +local function big(b) if b<0x10000 then return char(b/256,b%256) else @@ -4710,27 +4999,29 @@ local function big(c) return char(b1/256,b1%256,b2/256,b2%256) end end -local _,l_remap=utf.remapper(little) -local _,b_remap=utf.remapper(big) -function utf.utf8_to_utf16_be(str,nobom) +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) if nobom then return lpegmatch(b_remap,str) else return char(254,255)..lpegmatch(b_remap,str) end end -function utf.utf8_to_utf16_le(str,nobom) +local function utf8_to_utf16_le(str,nobom) if nobom then return lpegmatch(l_remap,str) else return char(255,254)..lpegmatch(l_remap,str) end end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le function utf.utf8_to_utf16(str,littleendian,nobom) if littleendian then - return utf.utf8_to_utf16_le(str,nobom) + return utf8_to_utf16_le(str,nobom) else - return utf.utf8_to_utf16_be(str,nobom) + return utf8_to_utf16_be(str,nobom) end end local pattern=Cs ( @@ -4746,16 +5037,16 @@ function utf.xstring(s) return format("0x%05X",type(s)=="number" and s or utfbyte(s)) end function utf.toeight(str) - if not str then + if not str or str=="" then return nil end local utftype=lpegmatch(p_utfstricttype,str) if utftype=="utf-8" then - return sub(str,4) - elseif utftype=="utf-16-le" then - return utf16_to_utf8_le(str) + return sub(str,4) elseif utftype=="utf-16-be" then - return utf16_to_utf8_ne(str) + return utf16_to_utf8_be(str) + elseif utftype=="utf-16-le" then + return utf16_to_utf8_le(str) else return str end @@ -4834,7 +5125,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-math"] = package.loaded["l-math"] or true --- original size: 915, stripped down to: 836 +-- original size: 974, stripped down to: 890 if not modules then modules={} end modules ['l-math']={ version=1.001, @@ -4844,6 +5135,9 @@ if not modules then modules={} end modules ['l-math']={ license="see context related readme files" } local floor,sin,cos,tan=math.floor,math.sin,math.cos,math.tan +if not math.ceiling then + math.ceiling=math.ceil +end if not math.round then function math.round(x) return floor(x+0.5) end end @@ -4871,7 +5165,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-str"] = package.loaded["util-str"] or true --- original size: 33063, stripped down to: 18397 +-- original size: 34407, stripped down to: 18852 if not modules then modules={} end modules ['util-str']={ version=1.001, @@ -5001,10 +5295,12 @@ local striplinepatterns={ ["retain"]=p_retain_normal, ["retain and collapse"]=p_retain_collapse, ["retain and no empty"]=p_retain_noempty, + ["collapse"]=patterns.collapser, } +setmetatable(striplinepatterns,{ __index=function(t,k) return p_prune_collapse end }) strings.striplinepatterns=striplinepatterns function strings.striplines(str,how) - return str and lpegmatch(how and striplinepatterns[how] or p_prune_collapse,str) or str + return str and lpegmatch(striplinepatterns[how],str) or str end strings.striplong=strings.striplines function strings.nice(str) @@ -5044,10 +5340,10 @@ string.tracedchars=tracedchars strings.tracers=tracedchars function string.tracedchar(b) if type(b)=="number" then - return tracedchars[b] or (utfchar(b).." (U+"..format('%05X',b)..")") + return tracedchars[b] or (utfchar(b).." (U+"..format("%05X",b)..")") else local c=utfbyte(b) - return tracedchars[c] or (b.." (U+"..format('%05X',c)..")") + return tracedchars[c] or (b.." (U+"..(c and format("%05X",c) or "?????")..")") end end function number.signed(i) @@ -5184,12 +5480,12 @@ local format_f=function(f) n=n+1 return format("format('%%%sf',a%s)",f,n) end -local format_F=function(f) +local format_F=function(f) n=n+1 if not f or f=="" then - return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or (a%s == 1 and '1') or format('%%.9f',a%s))",n,n,n,n) + return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n) else - return format("((a%s == 0 and '0') or (a%s == 1 and '1') or format('%%%sf',a%s))",n,n,f,n) + return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n) end end local format_g=function(f) @@ -5417,7 +5713,6 @@ local builder=Cs { "start", +V("j")+V("J") +V("m")+V("M") +V("z") -+V("*") )+V("*") )*(P(-1)+Carg(1)) )^0, @@ -5461,6 +5756,7 @@ local builder=Cs { "start", ["a"]=(prefix_any*P("a"))/format_a, ["A"]=(prefix_any*P("A"))/format_A, ["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest, + ["?"]=Cs(((1-P("%"))^1 )^1)/format_rest, ["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension, } local direct=Cs ( @@ -5535,6 +5831,15 @@ else add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=lpeg.patterns.texescape }) add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=lpeg.patterns.luaescape }) end +local dquote=patterns.dquote +local equote=patterns.escaped+dquote/'\\"'+1 +local space=patterns.space +local cquote=Cc('"') +local pattern=Cs(dquote*(equote-P(-2))^0*dquote) ++Cs(cquote*(equote-space)^0*space*equote^0*cquote) +function string.optionalquoted(str) + return lpegmatch(pattern,str) or str +end end -- of closure @@ -5543,7 +5848,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 23985, stripped down to: 16069 +-- original size: 24267, stripped down to: 16260 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -5633,6 +5938,15 @@ function tables.removevalue(t,value) end end end +function tables.replacevalue(t,oldvalue,newvalue) + if oldvalue and newvalue then + for i=1,#t do + if t[i]==oldvalue then + t[i]=newvalue + end + end + end +end function tables.insertbeforevalue(t,value,extra) for i=1,#t do if t[i]==extra then @@ -6026,7 +6340,7 @@ function table.serialize(root,name,specification) end depth=depth+1 end - if root and next(root) then + if root and next(root)~=nil then local first=nil local last=0 last=#root @@ -6051,7 +6365,7 @@ function table.serialize(root,name,specification) elseif tv=="string" then n=n+1 t[n]=f_val_str(depth,v) elseif tv=="table" then - if not next(v) then + if next(v)==nil then n=n+1 t[n]=f_val_not(depth) else local st=simple_table(v) @@ -6081,7 +6395,7 @@ function table.serialize(root,name,specification) n=n+1 t[n]=f_key_boo_value_str(depth,k,v) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then n=n+1 t[n]=f_key_num_value_not(depth,k,v) elseif tk=="string" then @@ -6139,7 +6453,7 @@ function table.serialize(root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,1,0) end end @@ -6302,7 +6616,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-prs"] = package.loaded["util-prs"] or true --- original size: 19618, stripped down to: 14012 +-- original size: 21751, stripped down to: 15108 if not modules then modules={} end modules ['util-prs']={ version=1.001, @@ -6458,11 +6772,30 @@ function parsers.settings_to_array(str,strict) return { str } end end -local separator=space^0*comma*space^0 -local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(comma+P(-1)))))^0) -local withvalue=Carg(1)*value/function(f,s) return f(s) end -local pattern_a=spaces*Ct(value*(separator*value)^0) -local pattern_b=spaces*withvalue*(separator*withvalue)^0 +local cache_a={} +local cache_b={} +function parsers.groupedsplitat(symbol,withaction) + if not symbol then + symbol="," + end + local pattern=(withaction and cache_b or cache_a)[symbol] + if not pattern then + local symbols=S(symbol) + local separator=space^0*symbols*space^0 + local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(symbols+P(-1)))))^0) + if withaction then + local withvalue=Carg(1)*value/function(f,s) return f(s) end + pattern=spaces*withvalue*(separator*withvalue)^0 + cache_b[symbol]=pattern + else + pattern=spaces*Ct(value*(separator*value)^0) + cache_a[symbol]=pattern + end + end + return pattern +end +local pattern_a=parsers.groupedsplitat(",",false) +local pattern_b=parsers.groupedsplitat(",",true) function parsers.stripped_settings_to_array(str) if not str or str=="" then return {} @@ -6524,12 +6857,9 @@ function parsers.array_to_string(a,separator) return "" end end -function parsers.settings_to_set(str,t) - t=t or {} - for s in gmatch(str,"[^, ]+") do - t[s]=true - end - return t +local pattern=Cf(Ct("")*Cg(C((1-S(", "))^1)*S(", ")^0*Cc(true))^1,rawset) +function utilities.parsers.settings_to_set(str,t) + return str and lpegmatch(pattern,str) or {} end function parsers.simple_hash_to_string(h,separator) local t,tn={},0 @@ -6541,12 +6871,16 @@ function parsers.simple_hash_to_string(h,separator) end return concat(t,separator or ",") end -local str=C((1-whitespace-equal)^1) +local str=Cs(lpegpatterns.unquoted)+C((1-whitespace-equal)^1) local setting=Cf(Carg(1)*(whitespace^0*Cg(str*whitespace^0*(equal*whitespace^0*str+Cc(""))))^1,rawset) local splitter=setting^1 function utilities.parsers.options_to_hash(str,target) return str and lpegmatch(splitter,str,1,target or {}) or {} end +local splitter=lpeg.tsplitat(" ") +function utilities.parsers.options_to_array(str) + return str and lpegmatch(splitter,str) or {} +end local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C(digit^1*lparent*(noparent+nestedparents)^1*rparent)+C((nestedbraces+(1-comma))^1) local pattern_a=spaces*Ct(value*(separator*value)^0) local function repeater(n,str) @@ -6668,7 +7002,7 @@ function parsers.rfc4180splitter(specification) local field=escaped+non_escaped+Cc("") local record=Ct(field*(separator*field)^1) local headerline=record*Cp() - local wholeblob=Ct((newline^-1*record)^0) + local wholeblob=Ct((newline^(specification.strict and -1 or 1)*record)^0) return function(data,getheader) if getheader then local header,position=lpegmatch(headerline,data) @@ -6705,13 +7039,13 @@ function parsers.stepper(str,n,action) lpegmatch(stepper,str,1,n,action or print) end end -local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) -local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) +local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) +local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) patterns.unittotex=pattern function parsers.unittotex(str,textmode) return lpegmatch(textmode and pattern_text or pattern_math,str) end -local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+P(1))^0) +local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+anything)^0) function parsers.unittoxml(str) return lpegmatch(pattern,str) end @@ -6783,6 +7117,18 @@ function utilities.parsers.runtime(time) local seconds=mod(time,60) return days,hours,minutes,seconds end +local spacing=whitespace^0 +local apply=P("->") +local method=C((1-apply)^1) +local token=lbrace*C((1-rbrace)^1)*rbrace+C(anything^1) +local pattern=spacing*(method*spacing*apply+Carg(1))*spacing*token +function utilities.parsers.splitmethod(str,default) + if str then + return lpegmatch(pattern,str,1,default or false) + else + return default or false,"" + end +end end -- of closure @@ -6872,7 +7218,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-set"] = package.loaded["trac-set"] or true --- original size: 12365, stripped down to: 8799 +-- original size: 12482, stripped down to: 8864 if not modules then modules={} end modules ['trac-set']={ version=1.001, @@ -6900,7 +7246,7 @@ function setters.initialize(filename,name,values) local data=setter.data if data then for key,newvalue in next,values do - local newvalue=is_boolean(newvalue,newvalue) + local newvalue=is_boolean(newvalue,newvalue,true) local functions=data[key] if functions then local oldvalue=functions.value @@ -6954,7 +7300,7 @@ local function set(t,what,newvalue) elseif not value then value=false else - value=is_boolean(value,value) + value=is_boolean(value,value,true) end w=topattern(w,true,true) for name,functions in next,data do @@ -7093,6 +7439,7 @@ function setters.new(name) report=function(...) setters.report (setter,...) end, enable=function(...) enable (setter,...) end, disable=function(...) disable (setter,...) end, + reset=function(...) reset (setter,...) end, register=function(...) register(setter,...) end, list=function(...) list (setter,...) end, show=function(...) show (setter,...) end, @@ -7184,7 +7531,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-log"] = package.loaded["trac-log"] or true --- original size: 25613, stripped down to: 16617 +-- original size: 29359, stripped down to: 20483 if not modules then modules={} end modules ['trac-log']={ version=1.001, @@ -7193,15 +7540,18 @@ if not modules then modules={} end modules ['trac-log']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } +local next,type,select,print=next,type,select,print local write_nl,write=texio and texio.write_nl or print,texio and texio.write or io.write local format,gmatch,find=string.format,string.gmatch,string.find local concat,insert,remove=table.concat,table.insert,table.remove local topattern=string.topattern -local next,type,select=next,type,select local utfchar=utf.char +local datetime=os.date +local openfile=io.open local setmetatableindex=table.setmetatableindex local formatters=string.formatters local texgetcount=tex and tex.getcount +local variant="default" logs=logs or {} local logs=logs local moreinfo=[[ @@ -7211,32 +7561,122 @@ maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context webpage : http://www.pragma-ade.nl / http://tex.aanhet.net wiki : http://contextgarden.net ]] -utilities.strings.formatters.add ( +formatters.add ( formatters,"unichr", [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]] ) -utilities.strings.formatters.add ( +formatters.add ( formatters,"chruni", [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]] ) local function ignore() end setmetatableindex(logs,function(t,k) t[k]=ignore;return ignore end) local report,subreport,status,settarget,setformats,settranslations -local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters +local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters,newline if tex and (tex.jobname or tex.formatname) then - local valueiskey={ __index=function(t,k) t[k]=k return k end } - local target="term and log" + local function useluawrites() + local texio_write_nl=texio.write_nl + local texio_write=texio.write + local io_write=io.write + write_nl=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write_nl("log",...) + texio_write_nl("term","") + io_write(...) + elseif target=="log" then + texio_write_nl("log",...) + elseif target=="term" then + texio_write_nl("term","") + io_write(...) + elseif target~="none" then + texio_write_nl("log",target,...) + texio_write_nl("term","") + io_write(target,...) + end + end + write=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write("log",...) + io_write(...) + elseif target=="log" then + texio_write("log",...) + elseif target=="term" then + io_write(...) + elseif target~="none" then + texio_write("log",target,...) + io_write(target,...) + end + end + texio.write=write + texio.write_nl=write_nl + useluawrites=ignore + end + local whereto="both" + local target=nil + local targets=nil + local formats=table.setmetatableindex("self") + local translations=table.setmetatableindex("self") + local report_yes,subreport_yes,direct_yes,subdirect_yes,status_yes + local report_nop,subreport_nop,direct_nop,subdirect_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s > %s\n"], + report_nop=formatters["%-15s >\n"], + direct_yes=formatters["%-15s > %s"], + direct_nop=formatters["%-15s >"], + subreport_yes=formatters["%-15s > %s > %s\n"], + subreport_nop=formatters["%-15s > %s >\n"], + subdirect_yes=formatters["%-15s > %s > %s"], + subdirect_nop=formatters["%-15s > %s >"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + targets={ + logfile="log", + log="log", + file="log", + console="term", + terminal="term", + both="term and log", + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;33m%-15s [0;1m>[0m %s\n"], + report_nop=formatters["[0;33m%-15s [0;1m>[0m\n"], + direct_yes=formatters["[0;33m%-15s [0;1m>[0m %s"], + direct_nop=formatters["[0;33m%-15s [0;1m>[0m"], + subreport_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"], + subreport_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"], + subdirect_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"], + subdirect_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"], + status_yes=formatters["[0;33m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;33m%-15s [0;1m:[0m\n"], + }, + targets={ + logfile="none", + log="none", + file="none", + console="term", + terminal="term", + both="term", + }, + } + } logs.flush=io.flush - local formats={} setmetatable(formats,valueiskey) - local translations={} setmetatable(translations,valueiskey) writer=function(...) write_nl(target,...) end newline=function() write_nl(target,"\n") end - local report_yes=formatters["%-15s > %s\n"] - local report_nop=formatters["%-15s >\n"] report=function(a,b,c,...) if c then write_nl(target,report_yes(translations[a],formatters[formats[b]](c,...))) @@ -7248,8 +7688,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local direct_yes=formatters["%-15s > %s"] - local direct_nop=formatters["%-15s >"] direct=function(a,b,c,...) if c then return direct_yes(translations[a],formatters[formats[b]](c,...)) @@ -7261,8 +7699,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local subreport_yes=formatters["%-15s > %s > %s\n"] - local subreport_nop=formatters["%-15s > %s >\n"] subreport=function(a,s,b,c,...) if c then write_nl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...))) @@ -7274,8 +7710,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local subdirect_yes=formatters["%-15s > %s > %s"] - local subdirect_nop=formatters["%-15s > %s >"] subdirect=function(a,s,b,c,...) if c then return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...)) @@ -7287,8 +7721,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(target,status_yes(translations[a],formatters[formats[b]](c,...))) @@ -7300,16 +7732,13 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local targets={ - logfile="log", - log="log", - file="log", - console="term", - terminal="term", - both="term and log", - } - settarget=function(whereto) - target=targets[whereto or "both"] or targets.both + settarget=function(askedwhereto) + whereto=askedwhereto or whereto or "both" + target=targets[whereto] + if not target then + whereto="both" + target=targets[whereto] + end if target=="term" or target=="term and log" then logs.flush=io.flush else @@ -7338,21 +7767,74 @@ if tex and (tex.jobname or tex.formatname) then writeline(target,f(...)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - direct_yes=f.direct_yes or direct_yes - direct_nop=f.direct_nop or direct_nop - subdirect_yes=f.subdirect_yes or subdirect_yes - subdirect_nop=f.subdirect_nop or subdirect_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local t=nil + local f=nil + local d=variants.default + if not specification then + elseif type(specification)=="table" then + t=specification.targets + f=specification.formats or specification + else + local v=variants[specification] + if v then + t=v.targets + f=v.formats + variant=specification + end + end + targets=t or d.targets + target=targets[whereto] or target + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + direct_yes=f.direct_yes + direct_nop=f.direct_nop + subdirect_yes=f.subdirect_yes + subdirect_nop=f.subdirect_nop + status_yes=f.status_yes + status_nop=f.status_nop + if variant=="ansi" then + useluawrites() + end + settarget(whereto) + end + setformatters(variant) setlogfile=ignore settimedlog=ignore else + local report_yes,subreport_yes,status_yes + local report_nop,subreport_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s | %s"], + report_nop=formatters["%-15s |"], + subreport_yes=formatters["%-15s | %s | %s"], + subreport_nop=formatters["%-15s | %s |"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;32m%-15s [0;1m|[0m %s"], + report_nop=formatters["[0;32m%-15s [0;1m|[0m"], + subreport_yes=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m %s"], + subreport_nop=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m"], + status_yes=formatters["[0;32m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;32m%-15s [0;1m:[0m\n"], + }, + }, + } logs.flush=ignore writer=function(s) write_nl(s) @@ -7360,8 +7842,6 @@ else newline=function() write_nl("\n") end - local report_yes=formatters["%-15s | %s"] - local report_nop=formatters["%-15s |"] report=function(a,b,c,...) if c then write_nl(report_yes(a,formatters[b](c,...))) @@ -7373,8 +7853,6 @@ else write_nl("") end end - local subreport_yes=formatters["%-15s | %s | %s"] - local subreport_nop=formatters["%-15s | %s |"] subreport=function(a,sub,b,c,...) if c then write_nl(subreport_yes(a,sub,formatters[b](c,...))) @@ -7386,8 +7864,6 @@ else write_nl("") end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(status_yes(a,formatters[b](c,...))) @@ -7412,14 +7888,34 @@ else writeline(f(s)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local f=nil + local d=variants.default + if specification then + if type(specification)=="table" then + f=specification.formats or specification + else + local v=variants[specification] + if v then + f=v.formats + end + end + end + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + status_yes=f.status_yes + status_nop=f.status_nop + end + setformatters(variant) setlogfile=function(name,keepopen) if name and name~="" then local localtime=os.localtime @@ -7672,13 +8168,13 @@ end local simple=logs.reporter("comment") logs.simple=simple logs.simpleline=simple -function logs.setprogram () end -function logs.extendbanner() end -function logs.reportlines () end -function logs.reportbanner() end -function logs.reportline () end -function logs.simplelines () end -function logs.help () end +logs.setprogram=ignore +logs.extendbanner=ignore +logs.reportlines=ignore +logs.reportbanner=ignore +logs.reportline=ignore +logs.simplelines=ignore +logs.help=ignore local Carg,C,lpegmatch=lpeg.Carg,lpeg.C,lpeg.match local p_newline=lpeg.patterns.newline local linewise=( @@ -7755,10 +8251,11 @@ function logs.application(t) end return t end -function logs.system(whereto,process,jobname,category,...) - local message=formatters["%s %s => %s => %s => %s\r"](os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)) +local f_syslog=formatters["%s %s => %s => %s => %s\r"] +function logs.system(whereto,process,jobname,category,fmt,arg,...) + local message=f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg==nil and fmt or format(fmt,arg,...)) for i=1,10 do - local f=io.open(whereto,"a") + local f=openfile(whereto,"a") if f then f:write(message) f:close() @@ -7820,7 +8317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-inf"] = package.loaded["trac-inf"] or true --- original size: 7011, stripped down to: 5590 +-- original size: 6704, stripped down to: 5343 if not modules then modules={} end modules ['trac-inf']={ version=1.001, @@ -7993,15 +8490,6 @@ function statistics.tracefunction(base,tag,...) statistics.register(formatters["%s.%s"](tag,name),function() return serialize(stat,"calls") end) end end -commands=commands or {} -function commands.resettimer(name) - resettiming(name or "whatever") - starttiming(name or "whatever") -end -function commands.elapsedtime(name) - stoptiming(name or "whatever") - context(elapsedtime(name or "whatever")) -end end -- of closure @@ -8568,7 +9056,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tpl"] = package.loaded["util-tpl"] or true --- original size: 6251, stripped down to: 3488 +-- original size: 7100, stripped down to: 3978 if not modules then modules={} end modules ['util-tpl']={ version=1.001, @@ -8610,7 +9098,7 @@ local sqlescape=lpeg.replacer { { "\r\n","\\n" }, { "\r","\\n" }, } -local sqlquoted=lpeg.Cs(lpeg.Cc("'")*sqlescape*lpeg.Cc("'")) +local sqlquoted=Cs(Cc("'")*sqlescape*Cc("'")) lpegpatterns.sqlescape=sqlescape lpegpatterns.sqlquoted=sqlquoted local luaescape=lpegpatterns.luaescape @@ -8633,12 +9121,24 @@ local quotedescapers={ local luaescaper=escapers.lua local quotedluaescaper=quotedescapers.lua local function replacekeyunquoted(s,t,how,recurse) - local escaper=how and escapers[how] or luaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and escapers[how] or luaescaper + return escaper(replacekey(s,t,how,recurse)) + end end local function replacekeyquoted(s,t,how,recurse) - local escaper=how and quotedescapers[how] or quotedluaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and quotedescapers[how] or quotedluaescaper + return escaper(replacekey(s,t,how,recurse)) + end +end +local function replaceoptional(l,m,r,t,how,recurse) + local v=t[l] + return v and v~="" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or "" end local single=P("%") local double=P("%%") @@ -8653,11 +9153,16 @@ local nolquoted=lquoted/'' local norquoted=rquoted/'' local nolquotedq=lquotedq/'' local norquotedq=rquotedq/'' -local key=nosingle*((C((1-nosingle )^1)*Carg(1)*Carg(2)*Carg(3))/replacekey )*nosingle -local quoted=nolquotedq*((C((1-norquotedq)^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyquoted )*norquotedq -local unquoted=nolquoted*((C((1-norquoted )^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyunquoted)*norquoted +local noloptional=P("%?")/'' +local noroptional=P("?%")/'' +local nomoptional=P(":")/'' +local args=Carg(1)*Carg(2)*Carg(3) +local key=nosingle*((C((1-nosingle )^1)*args)/replacekey )*nosingle +local quoted=nolquotedq*((C((1-norquotedq )^1)*args)/replacekeyquoted )*norquotedq +local unquoted=nolquoted*((C((1-norquoted )^1)*args)/replacekeyunquoted)*norquoted +local optional=noloptional*((C((1-nomoptional)^1)*nomoptional*C((1-noroptional)^1)*args)/replaceoptional)*noroptional local any=P(1) - replacer=Cs((unquoted+quoted+escape+key+any)^0) + replacer=Cs((unquoted+quoted+escape+optional+key+any)^0) local function replace(str,mapping,how,recurse) if mapping and str then return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str @@ -8696,7 +9201,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-env"] = package.loaded["util-env"] or true --- original size: 8814, stripped down to: 5092 +-- original size: 8022, stripped down to: 5038 if not modules then modules={} end modules ['util-env']={ version=1.001, @@ -8707,7 +9212,7 @@ if not modules then modules={} end modules ['util-env']={ } local allocate,mark=utilities.storage.allocate,utilities.storage.mark local format,sub,match,gsub,find=string.format,string.sub,string.match,string.gsub,string.find -local unquoted,quoted=string.unquoted,string.quoted +local unquoted,quoted,optionalquoted=string.unquoted,string.quoted,string.optionalquoted local concat,insert,remove=table.concat,table.insert,table.remove environment=environment or {} local environment=environment @@ -8820,24 +9325,14 @@ function environment.splitarguments(separator) return before,after end function environment.reconstructcommandline(arg,noquote) + local resolveprefix=resolvers.resolve arg=arg or environment.originalarguments if noquote and #arg==1 then - local a=arg[1] - a=resolvers.resolve(a) - a=unquoted(a) - return a + return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1]) elseif #arg>0 then local result={} for i=1,#arg do - local a=arg[i] - a=resolvers.resolve(a) - a=unquoted(a) - a=gsub(a,'"','\\"') - if find(a," ",1,true) then - result[#result+1]=quoted(a) - else - result[#result+1]=a - end + result[i]=optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix) end return concat(result," ") else @@ -9046,7 +9541,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-tab"] = package.loaded["lxml-tab"] or true --- original size: 42614, stripped down to: 26694 +-- original size: 43882, stripped down to: 26870 if not modules then modules={} end modules ['lxml-tab']={ version=1.001, @@ -9061,10 +9556,10 @@ if lpeg.setmaxstack then lpeg.setmaxstack(1000) end xml=xml or {} local xml=xml local concat,remove,insert=table.concat,table.remove,table.insert -local type,next,setmetatable,getmetatable,tonumber=type,next,setmetatable,getmetatable,tonumber +local type,next,setmetatable,getmetatable,tonumber,rawset=type,next,setmetatable,getmetatable,tonumber,rawset local lower,find,match,gsub=string.lower,string.find,string.match,string.gsub local utfchar=utf.char -local lpegmatch=lpeg.match +local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local P,S,R,C,V,C,Cs=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.V,lpeg.C,lpeg.Cs local formatters=string.formatters xml.xmlns=xml.xmlns or {} @@ -9226,9 +9721,10 @@ local function fromdec(s) return formatters["d:%s"](s),true end end -local rest=(1-P(";"))^0 -local many=P(1)^0 -local parsedentity=P("&")*(P("#x")*(rest/fromhex)+P("#")*(rest/fromdec))*P(";")*P(-1)+(P("#x")*(many/fromhex)+P("#")*(many/fromdec)) +local p_rest=(1-P(";"))^0 +local p_many=P(1)^0 +local p_char=lpegpatterns.utf8character +local parsedentity=P("&")*(P("#x")*(p_rest/fromhex)+P("#")*(p_rest/fromdec))*P(";")*P(-1)+(P("#x")*(p_many/fromhex)+P("#")*(p_many/fromdec)) local predefined_unified={ [38]="&", [42]=""", @@ -9254,7 +9750,9 @@ local privates_u={ local privates_p={} local privates_n={ } -local escaped=utf.remapper(privates_u) +local escaped=utf.remapper(privates_u,"dynamic") +local unprivatized=utf.remapper(privates_p,"dynamic") +xml.unprivatized=unprivatized local function unescaped(s) local p=privates_n[s] if not p then @@ -9267,9 +9765,7 @@ local function unescaped(s) end return p end -local unprivatized=utf.remapper(privates_p) xml.privatetoken=unescaped -xml.unprivatized=unprivatized xml.privatecodes=privates_n local function handle_hex_entity(str) local h=hcache[str] @@ -9422,7 +9918,7 @@ local valid=R('az','AZ','09')+S('_-.') local name_yes=C(valid^1)*colon*C(valid^1) local name_nop=C(P(true))*C(valid^1) local name=name_yes+name_nop -local utfbom=lpeg.patterns.utfbom +local utfbom=lpegpatterns.utfbom local spacing=C(space^0) local anyentitycontent=(1-open-semicolon-space-close)^0 local hexentitycontent=R("AF","af","09")^0 @@ -9590,7 +10086,7 @@ local function _xmlconvert_(data,settings) reported_attribute_errors,mt,errorhandler=nil,nil,nil return result end -function xmlconvert(data,settings) +local function xmlconvert(data,settings) local ok,result=pcall(function() return _xmlconvert_(data,settings) end) if ok then return result @@ -9682,14 +10178,17 @@ function xml.checkbom(root) insert(dt,2,"\n" ) end end -local function verbose_element(e,handlers) +local f_attribute=formatters['%s=%q'] +local function verbose_element(e,handlers,escape) local handle=handlers.handle local serialize=handlers.serialize local ens,etg,eat,edt,ern=e.ns,e.tg,e.at,e.dt,e.rn local ats=eat and next(eat) and {} if ats then + local n=0 for k,v in next,eat do - ats[#ats+1]=formatters['%s=%q'](k,escaped(v)) + n=n+1 + ats[n]=f_attribute(k,escaped(v)) end end if ern and trace_entities and ern~=ens then @@ -10033,7 +10532,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48030, stripped down to: 30595 +-- original size: 48229, stripped down to: 30684 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -10418,8 +10917,8 @@ local lp_builtin=P ( P("ns")/"ll.ns" )*((spaces*P("(")*spaces*P(")"))/"") local lp_attribute=(P("@")+P("attribute::"))/""*Cc("(ll.at and ll.at['")*((R("az","AZ")+S("-_:"))^1)*Cc("'])") -lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" -lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" +local lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" +local lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" local lp_fastpos=lp_fastpos_n+lp_fastpos_p local lp_reserved=C("and")+C("or")+C("not")+C("div")+C("mod")+C("true")+C("false") local lp_lua_function=Cs((R("az","AZ","__")^1*(P(".")*R("az","AZ","__")^1)^1)*("("))/"%0" @@ -10598,7 +11097,7 @@ local function nodesettostring(set,nodetest) if not ns or ns=="" then ns="*" end if not tg or tg=="" then tg="*" end tg=(tg=="@rt@" and "[root]") or format("%s:%s",ns,tg) - t[i]=(directive and tg) or format("not(%s)",tg) + t[#t+1]=(directive and tg) or format("not(%s)",tg) end if nodetest==false then return format("not(%s)",concat(t,"|")) @@ -10864,7 +11363,6 @@ expressions.print=function(...) print(...) return true end -expressions.contains=find expressions.find=find expressions.upper=upper expressions.lower=lower @@ -10886,6 +11384,9 @@ function expressions.contains(str,pattern) end return false end +function xml.expressions.idstring(str) + return type(str)=="string" and gsub(str,"^#","") or "" +end local function traverse(root,pattern,handle) local collected=applylpath(root,pattern) if collected then @@ -11173,7 +11674,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-aux"] = package.loaded["lxml-aux"] or true --- original size: 23804, stripped down to: 16817 +-- original size: 28225, stripped down to: 20125 if not modules then modules={} end modules ['lxml-aux']={ version=1.001, @@ -11183,16 +11684,19 @@ if not modules then modules={} end modules ['lxml-aux']={ license="see context related readme files" } local trace_manipulations=false trackers.register("lxml.manipulations",function(v) trace_manipulations=v end) +local trace_inclusions=false trackers.register("lxml.inclusions",function(v) trace_inclusions=v end) local report_xml=logs.reporter("xml") local xml=xml -local xmlconvert,xmlcopy,xmlname=xml.convert,xml.copy,xml.name +local xmlcopy,xmlname=xml.copy,xml.name local xmlinheritedconvert=xml.inheritedconvert local xmlapplylpath=xml.applylpath local xmlfilter=xml.filter -local type,setmetatable,getmetatable=type,setmetatable,getmetatable +local type,next,setmetatable,getmetatable=type,next,setmetatable,getmetatable local insert,remove,fastcopy,concat=table.insert,table.remove,table.fastcopy,table.concat local gmatch,gsub,format,find,strip=string.gmatch,string.gsub,string.format,string.find,string.strip local utfbyte=utf.byte +local lpegmatch=lpeg.match +local striplinepatterns=utilities.strings.striplinepatterns local function report(what,pattern,c,e) report_xml("%s element %a, root %a, position %a, index %a, pattern %a",what,xmlname(e),xmlname(e.__p__),c,e.ni,pattern) end @@ -11247,13 +11751,15 @@ end function xml.each(root,pattern,handle,reverse) local collected=xmlapplylpath(root,pattern) if collected then - if reverse then - for c=#collected,1,-1 do - handle(collected[c]) - end - else - for c=1,#collected do - handle(collected[c]) + if handle then + if reverse then + for c=#collected,1,-1 do + handle(collected[c]) + end + else + for c=1,#collected do + handle(collected[c]) + end end end return collected @@ -11309,6 +11815,7 @@ local function redo_ni(d) end end end +xml.reindex=redo_ni local function xmltoelement(whatever,root) if not whatever then return nil @@ -11360,8 +11867,16 @@ function xml.delete(root,pattern) report('deleting',pattern,c,e) end local d=p.dt - remove(d,e.ni) - redo_ni(d) + local ni=e.ni + if ni<=#d then + if false then + p.dt[ni]="" + else + remove(d,ni) + redo_ni(d) + end + else + end end end end @@ -11481,28 +11996,40 @@ xml.insertafter=insert_element xml.insertbefore=function(r,p,e) insert_element(r,p,e,true) end xml.injectafter=inject_element xml.injectbefore=function(r,p,e) inject_element(r,p,e,true) end -local function include(xmldata,pattern,attribute,recursive,loaddata) +local function include(xmldata,pattern,attribute,recursive,loaddata,level) pattern=pattern or 'include' loaddata=loaddata or io.loaddata local collected=xmlapplylpath(xmldata,pattern) if collected then + if not level then + level=1 + end for c=1,#collected do local ek=collected[c] local name=nil local ekdt=ek.dt local ekat=ek.at - local epdt=ek.__p__.dt + local ekrt=ek.__p__ + local epdt=ekrt.dt if not attribute or attribute=="" then name=(type(ekdt)=="table" and ekdt[1]) or ekdt end if not name then for a in gmatch(attribute or "href","([^|]+)") do name=ekat[a] - if name then break end + if name then + break + end + end + end + local data=nil + if name and name~="" then + data=loaddata(name) or "" + if trace_inclusions then + report_xml("including %s bytes from %a at level %s by pattern %a and attribute %a (%srecursing)",#data,name,level,pattern,attribute or "",recursive and "" or "not ") end end - local data=(name and name~="" and loaddata(name)) or "" - if data=="" then + if not data or data=="" then epdt[ek.ni]="" elseif ekat["parse"]=="text" then epdt[ek.ni]=xml.escaped(data) @@ -11512,70 +12039,113 @@ local function include(xmldata,pattern,attribute,recursive,loaddata) epdt[ek.ni]="" else if recursive then - include(xi,pattern,attribute,recursive,loaddata) + include(xi,pattern,attribute,recursive,loaddata,level+1) + end + local child=xml.body(xi) + child.__p__=ekrt + child.__f__=name + epdt[ek.ni]=child + local inclusions=xmldata.settings.inclusions + if inclusions then + inclusions[#inclusions+1]=name + else + xmldata.settings.inclusions={ name } end - epdt[ek.ni]=xml.body(xi) end end end end end xml.include=include +function xml.inclusion(e,default) + while e do + local f=e.__f__ + if f then + return f + else + e=e.__p__ + end + end + return default +end +function xml.inclusions(e,sorted) + while e do + local settings=e.settings + if settings then + local inclusions=settings.inclusions + if inclusions then + inclusions=table.unique(inclusions) + if sorted then + table.sort(inclusions) + end + return inclusions + else + e=e.__p__ + end + else + e=e.__p__ + end + end +end +local b_collapser=lpeg.patterns.b_collapser +local m_collapser=lpeg.patterns.m_collapser +local e_collapser=lpeg.patterns.e_collapser +local b_stripper=lpeg.patterns.b_stripper +local m_stripper=lpeg.patterns.m_stripper +local e_stripper=lpeg.patterns.e_stripper +local lpegmatch=lpeg.match local function stripelement(e,nolines,anywhere) local edt=e.dt if edt then - if anywhere then - local t,n={},0 - for e=1,#edt do + local n=#edt + if n==0 then + return e + elseif anywhere then + local t={} + local m=0 + for e=1,n do local str=edt[e] if type(str)~="string" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str elseif str~="" then if nolines then - str=gsub(str,"%s+"," ") + str=lpegmatch((n==1 and b_collapser) or (n==m and e_collapser) or m_collapser,str) + else + str=lpegmatch((n==1 and b_stripper) or (n==m and e_stripper) or m_stripper,str) end - str=gsub(str,"^%s*(.-)%s*$","%1") if str~="" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str end end end e.dt=t else - if #edt>0 then - local str=edt[1] - if type(str)~="string" then - elseif str=="" then + local str=edt[1] + if type(str)=="string" then + if str~="" then + str=lpegmatch(nolines and b_collapser or b_stripper,str) + end + if str=="" then remove(edt,1) + n=n-1 else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"^%s+","") - if str=="" then - remove(edt,1) - else - edt[1]=str - end + edt[1]=str end end - local nedt=#edt - if nedt>0 then - local str=edt[nedt] - if type(str)~="string" then - elseif str=="" then - remove(edt) - else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"%s+$","") + if n>0 then + str=edt[n] + if type(str)=="string" then if str=="" then remove(edt) else - edt[nedt]=str + str=lpegmatch(nolines and e_collapser or e_stripper,str) + if str=="" then + remove(edt) + else + edt[n]=str + end end end end @@ -11761,8 +12331,8 @@ function xml.finalizers.xml.cdata(collected) end return "" end -function xml.insertcomment(e,str,n) - table.insert(e.dt,n or 1,{ +function xml.insertcomment(e,str,n) + insert(e.dt,n or 1,{ tg="@cm@", ns="", special=true, @@ -11770,7 +12340,25 @@ function xml.insertcomment(e,str,n) dt={ str }, }) end -function xml.setcdata(e,str) +function xml.insertcdata(e,str,n) + insert(e.dt,n or 1,{ + tg="@cd@", + ns="", + special=true, + at={}, + dt={ str }, + }) +end +function xml.setcomment(e,str,n) + e.dt={ { + tg="@cm@", + ns="", + special=true, + at={}, + dt={ str }, + } } +end +function xml.setcdata(e,str) e.dt={ { tg="@cd@", ns="", @@ -11840,7 +12428,7 @@ local function recurse(e,action) for i=1,#edt do local str=edt[i] if type(str)~="string" then - recurse(str,action,recursive) + recurse(str,action) elseif str~="" then edt[i]=action(str) end @@ -11858,6 +12446,65 @@ function helpers.recursetext(collected,action,recursive) end end end +local specials={ + ["@rt@"]="root", + ["@pi@"]="instruction", + ["@cm@"]="comment", + ["@dt@"]="declaration", + ["@cd@"]="cdata", +} +local function convert(x,strip,flat) + local ns=x.ns + local tg=x.tg + local at=x.at + local dt=x.dt + local node=flat and { + [0]=(not x.special and (ns~="" and ns..":"..tg or tg)) or nil, + } or { + _namespace=ns~="" and ns or nil, + _tag=not x.special and tg or nil, + _type=specials[tg] or "_element", + } + if at then + for k,v in next,at do + node[k]=v + end + end + local n=0 + for i=1,#dt do + local di=dt[i] + if type(di)=="table" then + if flat and di.special then + else + di=convert(di,strip,flat) + if di then + n=n+1 + node[n]=di + end + end + elseif strip then + di=lpegmatch(strip,di) + if di~="" then + n=n+1 + node[n]=di + end + else + n=n+1 + node[n]=di + end + end + if next(node) then + return node + end +end +function xml.totable(x,strip,flat) + if type(x)=="table" then + if strip then + strip=striplinepatterns[strip] + end + return convert(x,strip,flat) + end +end end -- of closure @@ -12414,7 +13061,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-ini"] = package.loaded["data-ini"] or true --- original size: 7927, stripped down to: 5528 +-- original size: 11085, stripped down to: 7662 if not modules then modules={} end modules ['data-ini']={ version=1.001, @@ -12423,14 +13070,15 @@ if not modules then modules={} end modules ['data-ini']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files", } +local next,type,getmetatable,rawset=next,type,getmetatable,rawset local gsub,find,gmatch,char=string.gsub,string.find,string.gmatch,string.char -local next,type=next,type local filedirname,filebasename,filejoin=file.dirname,file.basename,file.join +local ostype,osname,osuname,ossetenv,osgetenv=os.type,os.name,os.uname,os.setenv,os.getenv +local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers.register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) local report_initialization=logs.reporter("resolvers","initialization") -local ostype,osname,ossetenv,osgetenv=os.type,os.name,os.setenv,os.getenv resolvers=resolvers or {} local resolvers=resolvers texconfig.kpse_init=false @@ -12563,10 +13211,103 @@ if type(profiler)=="table" and not jit then profiler.start("luatex-profile.log") end) end -if not resolvers.resolve then - function resolvers.resolve (s) return s end - function resolvers.unresolve(s) return s end - function resolvers.repath (s) return s end +local prefixes=utilities.storage.allocate() +resolvers.prefixes=prefixes +local resolved={} +local abstract={} +local dynamic={} +function resolvers.resetresolve(str) + resolved,abstract={},{} +end +function resolvers.allprefixes(separator) + local all=table.sortedkeys(prefixes) + if separator then + for i=1,#all do + all[i]=all[i]..":" + end + end + return all +end +local function _resolve_(method,target) + local action=prefixes[method] + if action then + return action(target) + else + return method..":"..target + end +end +function resolvers.unresolve(str) + return abstract[str] or str +end +function resolvers.setdynamic(str) + dynamic[str]=true +end +local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) +local prefix=C(R("az")^2)*P(":") +local target=C((1-S(" \"\';,"))^1) +local notarget=(#S(";,")+P(-1))*Cc("") +local p_resolve=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) +local p_simple=prefix*P(-1) +local function resolve(str) + if type(str)=="table" then + local res={} + for i=1,#str do + res[i]=resolve(str[i]) + end + return res + end + local res=resolved[str] + if res then + return res + end + local simple=lpegmatch(p_simple,str) + local action=prefixes[simple] + if action then + local res=action(res) + if not dynamic[simple] then + resolved[simple]=res + abstract[res]=simple + end + return res + end + res=lpegmatch(p_resolve,str) + resolved[str]=res + abstract[res]=str + return res +end +resolvers.resolve=resolve +if type(osuname)=="function" then + for k,v in next,osuname() do + if not prefixes[k] then + prefixes[k]=function() return v end + end + end +end +if ostype=="unix" then + local pattern + local function makepattern(t,k,v) + if t then + rawset(t,k,v) + end + local colon=P(":") + for k,v in table.sortedpairs(prefixes) do + if p then + p=P(k)+p + else + p=P(k) + end + end + pattern=Cs((p*colon+colon/";"+P(1))^0) + end + makepattern() + table.setmetatablenewindex(prefixes,makepattern) + function resolvers.repath(str) + return lpegmatch(pattern,str) + end +else + function resolvers.repath(str) + return str + end end @@ -12576,7 +13317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-exp"] = package.loaded["data-exp"] or true --- original size: 15317, stripped down to: 9723 +-- original size: 17216, stripped down to: 10657 if not modules then modules={} end modules ['data-exp']={ version=1.001, @@ -12590,12 +13331,16 @@ local concat,sort=table.concat,table.sort local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local Ct,Cs,Cc,Carg,P,C,S=lpeg.Ct,lpeg.Cs,lpeg.Cc,lpeg.Carg,lpeg.P,lpeg.C,lpeg.S local type,next=type,next +local isdir=lfs.isdir local ostype=os.type -local collapsepath=file.collapsepath +local collapsepath,joinpath,basename=file.collapsepath,file.join,file.basename local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_globbing=true trackers.register("resolvers.globbing",function(v) trace_globbing=v end) local report_expansions=logs.reporter("resolvers","expansions") +local report_globbing=logs.reporter("resolvers","globbing") local resolvers=resolvers +local resolveprefix=resolvers.resolve local function f_both(a,b) local t,n={},0 for sb in gmatch(b,"[^,]+") do @@ -12685,35 +13430,27 @@ function resolvers.expandedpathfromlist(pathlist) end return newlist end -local cleanup=lpeg.replacer { - { "!","" }, - { "\\","/" }, -} -function resolvers.cleanpath(str) - local doslashes=(P("\\")/"/"+1)^0 - local donegation=(P("!")/"" )^0 - local homedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") - if homedir=="~" or homedir=="" or not lfs.isdir(homedir) then - if trace_expansions then - report_expansions("no home dir set, ignoring dependent paths") - end - function resolvers.cleanpath(str) - if not str or find(str,"~",1,true) then - return "" - else - return lpegmatch(cleanup,str) +local usedhomedir=nil +local donegation=(P("!")/"" )^0 +local doslashes=(P("\\")/"/"+1)^0 +local function expandedhome() + if not usedhomedir then + usedhomedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") + if usedhomedir=="~" or usedhomedir=="" or not isdir(usedhomedir) then + if trace_expansions then + report_expansions("no home dir set, ignoring dependent path using current path") end - end - else - local dohome=((P("~")+P("$HOME"))/homedir)^0 - local cleanup=Cs(donegation*dohome*doslashes) - function resolvers.cleanpath(str) - return str and lpegmatch(cleanup,str) or "" + usedhomedir="." end end - return resolvers.cleanpath(str) + return usedhomedir +end +local dohome=((P("~")+P("$HOME")+P("%HOME%"))/expandedhome)^0 +local cleanup=Cs(donegation*dohome*doslashes) +resolvers.cleanpath=function(str) + return str and lpegmatch(cleanup,str) or "" end -local expandhome=P("~")/"$HOME" +local expandhome=P("~")/"$HOME" local dodouble=P('"')/""*(expandhome+(1-P('"')))^0*P('"')/"" local dosingle=P("'")/""*(expandhome+(1-P("'")))^0*P("'")/"" local dostring=(expandhome+1 )^0 @@ -12765,46 +13502,67 @@ function resolvers.splitpath(str) end function resolvers.joinpath(str) if type(str)=='table' then - return file.joinpath(str) + return joinpath(str) else return str end end local attributes,directory=lfs.attributes,lfs.dir local weird=P(".")^1+lpeg.anywhere(S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t")) +local lessweird=P(".")^1+lpeg.anywhere(S("~`#$%^&*:;\"\'||<>,?\n\r\t")) local timer={} local scanned={} local nofscans=0 local scancache={} -local function scan(files,spec,path,n,m,r) - local full=(path=="" and spec) or (spec..path..'/') +local fullcache={} +local nofsharedscans=0 +local function scan(files,remap,spec,path,n,m,r,onlyone,tolerant) + local full=path=="" and spec or (spec..path..'/') local dirs={} local nofdirs=0 + local pattern=tolerant and lessweird or weird for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then + if not lpegmatch(pattern,name) then + local mode=attributes(full..name,"mode") + if mode=="file" then n=n+1 - local f=files[name] - if f then - if type(f)=='string' then - files[name]={ f,path } + local lower=lower(name) + local paths=files[lower] + if paths then + if onlyone then else - f[#f+1]=path + if type(paths)=="string" then + files[lower]={ paths,path } + else + paths[#paths+1]=path + end + if name~=lower then + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end + end end else - files[name]=path - local lower=lower(name) + files[lower]=path if name~=lower then - files["remap:"..lower]=name - r=r+1 + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end end end - elseif mode=='directory' then + elseif mode=="directory" then m=m+1 nofdirs=nofdirs+1 if path~="" then - dirs[nofdirs]=path..'/'..name + dirs[nofdirs]=path.."/"..name else dirs[nofdirs]=name end @@ -12814,107 +13572,69 @@ local function scan(files,spec,path,n,m,r) if nofdirs>0 then sort(dirs) for i=1,nofdirs do - files,n,m,r=scan(files,spec,dirs[i],n,m,r) + files,remap,n,m,r=scan(files,remap,spec,dirs[i],n,m,r,onlyonce,tolerant) end end scancache[sub(full,1,-2)]=files - return files,n,m,r + return files,remap,n,m,r end -local fullcache={} -function resolvers.scanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) +function resolvers.scanfiles(path,branch,usecache,onlyonce,tolerant) + local realpath=resolveprefix(path) if usecache then - local files=fullcache[realpath] - if files then + local content=fullcache[realpath] + if content then if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) + report_expansions("using cached scan of path %a, branch %a",path,branch or path) end - return files + nofsharedscans=nofsharedscans+1 + return content end end + statistics.starttiming(timer) if trace_locating then report_expansions("scanning path %a, branch %a",path,branch or path) end - local files,n,m,r=scan({},realpath..'/',"",0,0,0) - files.__path__=path - files.__files__=n - files.__directories__=m - files.__remappings__=r - if trace_locating then - report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) - end - if usecache then - scanned[#scanned+1]=realpath - fullcache[realpath]=files - end - nofscans=nofscans+1 - statistics.stoptiming(timer) - return files -end -local function simplescan(files,spec,path) - local full=(path=="" and spec) or (spec..path..'/') - local dirs={} - local nofdirs=0 - for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then - if not files[name] then - files[name]=path - end - elseif mode=='directory' then - nofdirs=nofdirs+1 - if path~="" then - dirs[nofdirs]=path..'/'..name - else - dirs[nofdirs]=name - end - end - end - end - if nofdirs>0 then - sort(dirs) - for i=1,nofdirs do - files=simplescan(files,spec,dirs[i]) - end - end - return files -end -local simplecache={} -local nofsharedscans=0 -function resolvers.simplescanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) - if usecache then - local files=simplecache[realpath] - if not files then - files=scancache[realpath] - if files then - nofsharedscans=nofsharedscans+1 - end + local content + if isdir(realpath) then + local files,remap,n,m,r=scan({},{},realpath..'/',"",0,0,0,onlyonce,tolerant) + content={ + metadata={ + path=path, + files=n, + directories=m, + remappings=r, + }, + files=files, + remap=remap, + } + if trace_locating then + report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) end - if files then - if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) - end - return files + else + content={ + metadata={ + path=path, + files=0, + directories=0, + remappings=0, + }, + files={}, + remap={}, + } + if trace_locating then + report_expansions("invalid path %a",realpath) end end - if trace_locating then - report_expansions("scanning path %a, branch %a",path,branch or path) - end - local files=simplescan({},realpath..'/',"") - if trace_locating then - report_expansions("%s files found",table.count(files)) - end if usecache then scanned[#scanned+1]=realpath - simplecache[realpath]=files + fullcache[realpath]=content end nofscans=nofscans+1 statistics.stoptiming(timer) - return files + return content +end +function resolvers.simplescanfiles(path,branch,usecache) + return resolvers.scanfiles(path,branch,usecache,true,true) end function resolvers.scandata() table.sort(scanned) @@ -12925,6 +13645,52 @@ function resolvers.scandata() paths=scanned, } end +function resolvers.get_from_content(content,path,name) + if not content then + return + end + local files=content.files + if not files then + return + end + local remap=content.remap + if not remap then + return + end + if name then + local used=lower(name) + return path,remap[used] or used + else + local name=path + local used=lower(name) + local path=files[used] + if path then + return path,remap[used] or used + end + end +end +local nothing=function() end +function resolvers.filtered_from_content(content,pattern) + if content and type(pattern)=="string" then + local pattern=lower(pattern) + local files=content.files + local remap=content.remap + if files and remap then + local n=next(files) + local function iterator() + while n do + local k=n + n=next(files,k) + if find(k,pattern) then + return files[k],remap and remap[k] or k + end + end + end + return iterator + end + end + return nothing +end end -- of closure @@ -12933,7 +13699,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-env"] = package.loaded["data-env"] or true --- original size: 8769, stripped down to: 6490 +-- original size: 9216, stripped down to: 6798 if not modules then modules={} end modules ['data-env']={ version=1.001, @@ -12951,10 +13717,12 @@ local formats=allocate() local suffixes=allocate() local dangerous=allocate() local suffixmap=allocate() +local usertypes=allocate() resolvers.formats=formats resolvers.suffixes=suffixes resolvers.dangerous=dangerous resolvers.suffixmap=suffixmap +resolvers.usertypes=usertypes local luasuffixes=utilities.lua.suffixes local relations=allocate { core={ @@ -13022,11 +13790,13 @@ local relations=allocate { names={ "mp" }, variable='MPINPUTS', suffixes={ 'mp','mpvi','mpiv','mpii' }, + usertype=true, }, tex={ names={ "tex" }, variable='TEXINPUTS', - suffixes={ 'tex',"mkvi","mkiv","mkii" }, + suffixes={ "tex","mkvi","mkiv","mkii","cld","lfg","xml" }, + usertype=true, }, icc={ names={ "icc","icc profile","icc profiles" }, @@ -13042,6 +13812,7 @@ local relations=allocate { names={ "lua" }, variable='LUAINPUTS', suffixes={ luasuffixes.lua,luasuffixes.luc,luasuffixes.tma,luasuffixes.tmc }, + usertype=true, }, lib={ names={ "lib" }, @@ -13050,11 +13821,15 @@ local relations=allocate { }, bib={ names={ 'bib' }, + variable='BIBINPUTS', suffixes={ 'bib' }, + usertype=true, }, bst={ names={ 'bst' }, + variable='BSTINPUTS', suffixes={ 'bst' }, + usertype=true, }, fontconfig={ names={ 'fontconfig','fontconfig file','fontconfig files' }, @@ -13136,8 +13911,9 @@ function resolvers.updaterelations() for name,relation in next,categories do local rn=relation.names local rv=relation.variable - local rs=relation.suffixes if rn and rv then + local rs=relation.suffixes + local ru=relation.usertype for i=1,#rn do local rni=lower(gsub(rn[i]," ","")) formats[rni]=rv @@ -13149,8 +13925,9 @@ function resolvers.updaterelations() end end end - end - if rs then + if ru then + usertypes[name]=true + end end end end @@ -13201,7 +13978,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmp"] = package.loaded["data-tmp"] or true --- original size: 15532, stripped down to: 11648 +-- original size: 15681, stripped down to: 11761 if not modules then modules={} end modules ['data-tmp']={ version=1.100, @@ -13220,6 +13997,7 @@ local trace_cache=false trackers.register("resolvers.cache",function(v) trace_ca local report_caches=logs.reporter("resolvers","caches") local report_resolvers=logs.reporter("resolvers","caching") local resolvers=resolvers +local cleanpath=resolvers.cleanpath local directive_cleanup=false directives.register("system.compile.cleanup",function(v) directive_cleanup=v end) local directive_strip=false directives.register("system.compile.strip",function(v) directive_strip=v end) local compile=utilities.lua.compile @@ -13241,7 +14019,7 @@ caches.relocate=false caches.defaults={ "TMPDIR","TEMPDIR","TMP","TEMP","HOME","HOMEPATH" } local writable,readables,usedreadables=nil,{},{} local function identify() - local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") + local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") if texmfcaches then for k=1,#texmfcaches do local cachepath=texmfcaches[k] @@ -13495,10 +14273,12 @@ local content_state={} function caches.contentstate() return content_state or {} end -function caches.loadcontent(cachename,dataname) - local name=caches.hashed(cachename) - local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.loadcontent(cachename,dataname,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local blob=loadfile(addsuffix(filename,luasuffixes.luc)) or loadfile(addsuffix(filename,luasuffixes.lua)) if blob then local data=blob() @@ -13530,10 +14310,12 @@ function caches.collapsecontent(content) end end end -function caches.savecontent(cachename,dataname,content) - local name=caches.hashed(cachename) - local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.savecontent(cachename,dataname,content,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local luaname=addsuffix(filename,luasuffixes.lua) local lucname=addsuffix(filename,luasuffixes.luc) if trace_locating then @@ -13576,7 +14358,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-met"] = package.loaded["data-met"] or true --- original size: 5460, stripped down to: 4014 +-- original size: 5347, stripped down to: 4015 if not modules then modules={} end modules ['data-met']={ version=1.100, @@ -13604,7 +14386,7 @@ local function splitmethod(filename) if type(filename)=="table" then return filename end - filename=file.collapsepath(filename,".") + filename=file.collapsepath(filename,".") if not find(filename,"://",1,true) then return { scheme="file",path=filename,original=filename,filename=filename } end @@ -13695,7 +14477,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-res"] = package.loaded["data-res"] or true --- original size: 62045, stripped down to: 43116 +-- original size: 67003, stripped down to: 46291 if not modules then modules={} end modules ['data-res']={ version=1.001, @@ -13705,7 +14487,7 @@ if not modules then modules={} end modules ['data-res']={ license="see context related readme files", } local gsub,find,lower,upper,match,gmatch=string.gsub,string.find,string.lower,string.upper,string.match,string.gmatch -local concat,insert,sortedkeys=table.concat,table.insert,table.sortedkeys +local concat,insert,remove,sortedkeys,sortedhash=table.concat,table.insert,table.remove,table.sortedkeys,table.sortedhash local next,type,rawget=next,type,rawget local os=os local P,S,R,C,Cc,Cs,Ct,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Carg @@ -13714,17 +14496,23 @@ local formatters=string.formatters local filedirname=file.dirname local filebasename=file.basename local suffixonly=file.suffixonly +local addsuffix=file.addsuffix +local removesuffix=file.removesuffix local filejoin=file.join local collapsepath=file.collapsepath local joinpath=file.joinpath +local is_qualified_path=file.is_qualified_path local allocate=utilities.storage.allocate local settings_to_array=utilities.parsers.settings_to_array +local getcurrentdir=lfs.currentdir +local isfile=lfs.isfile +local isdir=lfs.isdir local setmetatableindex=table.setmetatableindex local luasuffixes=utilities.lua.suffixes -local getcurrentdir=lfs.currentdir local trace_locating=false trackers .register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers .register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers .register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_paths=false trackers .register("resolvers.paths",function(v) trace_paths=v end) local resolve_otherwise=true directives.register("resolvers.otherwise",function(v) resolve_otherwise=v end) local report_resolving=logs.reporter("resolvers","resolving") local resolvers=resolvers @@ -13732,10 +14520,14 @@ local expandedpathfromlist=resolvers.expandedpathfromlist local checkedvariable=resolvers.checkedvariable local splitconfigurationpath=resolvers.splitconfigurationpath local methodhandler=resolvers.methodhandler +local filtered=resolvers.filtered_from_content +local lookup=resolvers.get_from_content +local cleanpath=resolvers.cleanpath +local resolveprefix=resolvers.resolve local initializesetter=utilities.setters.initialize local ostype,osname,osenv,ossetenv,osgetenv=os.type,os.name,os.env,os.setenv,os.getenv -resolvers.cacheversion='1.0.1' -resolvers.configbanner='' +resolvers.cacheversion="1.100" +resolvers.configbanner="" resolvers.homedir=environment.homedir resolvers.criticalvars=allocate { "SELFAUTOLOC","SELFAUTODIR","SELFAUTOPARENT","TEXMFCNF","TEXMF","TEXOS" } resolvers.luacnfname="texmfcnf.lua" @@ -13754,6 +14546,7 @@ end local unset_variable="unset" local formats=resolvers.formats local suffixes=resolvers.suffixes +local usertypes=resolvers.usertypes local dangerous=resolvers.dangerous local suffixmap=resolvers.suffixmap resolvers.defaultsuffixes={ "tex" } @@ -13762,7 +14555,7 @@ local instance=resolvers.instance or nil function resolvers.setenv(key,value,raw) if instance then instance.environment[key]=value - ossetenv(key,raw and value or resolvers.resolve(value)) + ossetenv(key,raw and value or resolveprefix(value)) end end local function getenv(key) @@ -13776,7 +14569,7 @@ local function getenv(key) end resolvers.getenv=getenv resolvers.env=getenv -local function resolve(k) +local function resolvevariable(k) return instance.expansions[k] end local dollarstripper=lpeg.stripper("$") @@ -13785,19 +14578,19 @@ local backslashswapper=lpeg.replacer("\\","/") local somevariable=P("$")/"" local somekey=C(R("az","AZ","09","__","--")^1) local somethingelse=P(";")*((1-S("!{}/\\"))^1*P(";")/"")+P(";")*(P(";")/"")+P(1) -local variableexpander=Cs((somevariable*(somekey/resolve)+somethingelse)^1 ) +local variableexpander=Cs((somevariable*(somekey/resolvevariable)+somethingelse)^1 ) local cleaner=P("\\")/"/"+P(";")*S("!{}/\\")^0*P(";")^1/";" local variablecleaner=Cs((cleaner+P(1))^0) -local somevariable=R("az","AZ","09","__","--")^1/resolve +local somevariable=R("az","AZ","09","__","--")^1/resolvevariable local variable=(P("$")/"")*(somevariable+(P("{")/"")*somevariable*(P("}")/"")) local variableresolver=Cs((variable+P(1))^0) local function expandedvariable(var) return lpegmatch(variableexpander,var) or var end -function resolvers.newinstance() - if trace_locating then +function resolvers.newinstance() + if trace_locating then report_resolving("creating instance") - end + end local environment,variables,expansions,order=allocate(),allocate(),allocate(),allocate() local newinstance={ environment=environment, @@ -13810,6 +14603,7 @@ function resolvers.newinstance() foundintrees=allocate(), hashes=allocate(), hashed=allocate(), + pathlists=false, specification=allocate(), lists=allocate(), data=allocate(), @@ -13822,6 +14616,7 @@ function resolvers.newinstance() savelists=true, pattern=nil, force_suffixes=true, + pathstack={}, } setmetatableindex(variables,function(t,k) local v @@ -13871,8 +14666,13 @@ function resolvers.reset() end local function reset_hashes() instance.lists={} + instance.pathlists=false instance.found={} end +local function reset_caches() + instance.lists={} + instance.pathlists=false +end local slash=P("/") local pathexpressionpattern=Cs ( Cc("^")*( @@ -13924,13 +14724,13 @@ local function identify_configuration_files() for i=1,#cnfpaths do local filepath=cnfpaths[i] local filename=collapsepath(filejoin(filepath,luacnfname)) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) if trace_locating then - local fullpath=gsub(resolvers.resolve(collapsepath(filepath)),"//","/") + local fullpath=gsub(resolveprefix(collapsepath(filepath)),"//","/") local weirdpath=find(fullpath,"/texmf.+/texmf") or not find(fullpath,"/web2c",1,true) report_resolving("looking for %a on %s path %a from specification %a",luacnfname,weirdpath and "weird" or "given",fullpath,filepath) end - if lfs.isfile(realname) then + if isfile(realname) then specification[#specification+1]=filename if trace_locating then report_resolving("found configuration file %a",realname) @@ -13952,7 +14752,7 @@ local function load_configuration_files() local filename=specification[i] local pathname=filedirname(filename) local filename=filejoin(pathname,luacnfname) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local setups=instance.setups @@ -13960,7 +14760,7 @@ local function load_configuration_files() local parent=data and data.parent if parent then local filename=filejoin(pathname,parent) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local parentdata=blob() @@ -13985,7 +14785,7 @@ local function load_configuration_files() elseif variables[k]==nil then if trace_locating and not warning then report_resolving("variables like %a in configuration file %a should move to the 'variables' subtable", - k,resolvers.resolve(filename)) + k,resolveprefix(filename)) warning=true end variables[k]=v @@ -14045,7 +14845,7 @@ local function locate_file_databases() local stripped=lpegmatch(inhibitstripper,path) if stripped~="" then local runtime=stripped==path - path=resolvers.cleanpath(path) + path=cleanpath(path) local spec=resolvers.splitmethod(stripped) if runtime and (spec.noscheme or spec.scheme=="file") then stripped="tree:///"..stripped @@ -14108,8 +14908,8 @@ function resolvers.renew(hashname) report_resolving("identifying tree %a",hashname) end end - local realpath=resolvers.resolve(hashname) - if lfs.isdir(realpath) then + local realpath=resolveprefix(hashname) + if isdir(realpath) then if trace_locating then report_resolving("using path %a",realpath) end @@ -14210,19 +15010,53 @@ end function resolvers.unexpandedpath(str) return joinpath(resolvers.unexpandedpathlist(str)) end +function resolvers.pushpath(name) + local pathstack=instance.pathstack + local lastpath=pathstack[#pathstack] + local pluspath=filedirname(name) + if lastpath then + lastpath=collapsepath(filejoin(lastpath,pluspath)) + else + lastpath=collapsepath(pluspath) + end + insert(pathstack,lastpath) + if trace_paths then + report_resolving("pushing path %a",lastpath) + end +end +function resolvers.poppath() + local pathstack=instance.pathstack + if trace_paths and #pathstack>0 then + report_resolving("popping path %a",pathstack[#pathstack]) + end + remove(pathstack) +end +function resolvers.stackpath() + local pathstack=instance.pathstack + local currentpath=pathstack[#pathstack] + return currentpath~="" and currentpath or nil +end local done={} function resolvers.resetextrapath() local ep=instance.extra_paths if not ep then - ep,done={},{} - instance.extra_paths=ep + done={} + instance.extra_paths={} elseif #ep>0 then - instance.lists,done={},{} + done={} + reset_caches() end end function resolvers.registerextrapath(paths,subpaths) - paths=settings_to_array(paths) - subpaths=settings_to_array(subpaths) + if not subpaths or subpaths=="" then + if not paths or path=="" then + return + elseif done[paths] then + return + end + end + local paths=settings_to_array(paths) + local subpaths=settings_to_array(subpaths) local ep=instance.extra_paths or {} local oldn=#ep local newn=oldn @@ -14237,7 +15071,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=p.."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14247,7 +15081,7 @@ function resolvers.registerextrapath(paths,subpaths) local p=paths[i] if not done[p] then newn=newn+1 - ep[newn]=resolvers.cleanpath(p) + ep[newn]=cleanpath(p) done[p]=true end end @@ -14259,7 +15093,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=ep[i].."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14268,52 +15102,70 @@ function resolvers.registerextrapath(paths,subpaths) if newn>0 then instance.extra_paths=ep end - if newn>oldn then - instance.lists={} + if newn~=oldn then + reset_caches() end end -local function made_list(instance,list) - local ep=instance.extra_paths - if not ep or #ep==0 then - return list +function resolvers.pushextrapath(path) + local paths=settings_to_array(path) + if instance.extra_stack then + insert(instance.extra_stack,1,paths) else - local done,new,newn={},{},0 - for k=1,#list do - local v=list[k] - if not done[v] then - if find(v,"^[%.%/]$") then - done[v]=true - newn=newn+1 - new[newn]=v - else - break - end - end - end - for k=1,#ep do - local v=ep[k] + instance.extra_stack={ paths } + end + reset_caches() +end +function resolvers.popextrapath() + if instance.extra_stack then + reset_caches() + return remove(instance.extra_stack,1) + end +end +local function made_list(instance,list,extra_too) + local done={} + local new={} + local newn=0 + local function add(p) + for k=1,#p do + local v=p[k] if not done[v] then done[v]=true newn=newn+1 new[newn]=v end end - for k=1,#list do - local v=list[k] - if not done[v] then - done[v]=true - newn=newn+1 - new[newn]=v + end + for k=1,#list do + local v=list[k] + if done[v] then + elseif find(v,"^[%.%/]$") then + done[v]=true + newn=newn+1 + new[newn]=v + else + break + end + end + if extra_too then + local es=instance.extra_stack + if es and #es>0 then + for k=1,#es do + add(es[k]) end end - return new + local ep=instance.extra_paths + if ep and #ep>0 then + add(ep) + end end + add(list) + return new end function resolvers.cleanpathlist(str) local t=resolvers.expandedpathlist(str) if t then for i=1,#t do - t[i]=collapsepath(resolvers.cleanpath(t[i])) + t[i]=collapsepath(cleanpath(t[i])) end end return t @@ -14321,22 +15173,22 @@ end function resolvers.expandpath(str) return joinpath(resolvers.expandedpathlist(str)) end -function resolvers.expandedpathlist(str) +function resolvers.expandedpathlist(str,extra_too) if not str then return {} - elseif instance.savelists then + elseif instance.savelists then str=lpegmatch(dollarstripper,str) local lists=instance.lists local lst=lists[str] if not lst then - local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str))) + local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str)),extra_too) lst=expandedpathfromlist(l) lists[str]=lst end return lst else local lst=resolvers.splitpath(resolvers.expansion(str)) - return made_list(instance,expandedpathfromlist(lst)) + return made_list(instance,expandedpathfromlist(lst),extra_too) end end function resolvers.expandedpathlistfromvariable(str) @@ -14347,6 +15199,13 @@ end function resolvers.expandpathfromvariable(str) return joinpath(resolvers.expandedpathlistfromvariable(str)) end +function resolvers.cleanedpathlist(v) + local t=resolvers.expandedpathlist(v) + for i=1,#t do + t[i]=resolvers.resolve(resolvers.cleanpath(t[i])) + end + return t +end function resolvers.expandbraces(str) local ori=str local pth=expandedpathfromlist(resolvers.splitpath(ori)) @@ -14363,7 +15222,7 @@ function resolvers.registerfilehash(name,content,someerror) end end local function isreadable(name) - local readable=lfs.isfile(name) + local readable=isfile(name) if trace_detail then if readable then report_resolving("file %a is readable",name) @@ -14373,70 +15232,57 @@ local function isreadable(name) end return readable end -local function collect_files(names) - local filelist,noffiles={},0 +local function collect_files(names) + local filelist={} + local noffiles=0 + local function check(hash,root,pathname,path,name) + if not pathname or find(path,pathname) then + local variant=hash.type + local search=filejoin(root,path,name) + local result=methodhandler('concatinators',variant,root,path,name) + if trace_detail then + report_resolving("match: variant %a, search %a, result %a",variant,search,result) + end + noffiles=noffiles+1 + filelist[noffiles]={ variant,search,result } + end + end for k=1,#names do - local fname=names[k] + local filename=names[k] if trace_detail then - report_resolving("checking name %a",fname) + report_resolving("checking name %a",filename) end - local bname=filebasename(fname) - local dname=filedirname(fname) - if dname=="" or find(dname,"^%.") then - dname=false + local basename=filebasename(filename) + local pathname=filedirname(filename) + if pathname=="" or find(pathname,"^%.") then + pathname=false else - dname=gsub(dname,"%*",".*") - dname="/"..dname.."$" + pathname=gsub(pathname,"%*",".*") + pathname="/"..pathname.."$" end local hashes=instance.hashes for h=1,#hashes do local hash=hashes[h] - local blobpath=hash.name - local files=blobpath and instance.files[blobpath] - if files then + local hashname=hash.name + local content=hashname and instance.files[hashname] + if content then if trace_detail then - report_resolving("deep checking %a, base %a, pattern %a",blobpath,bname,dname) - 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 + report_resolving("deep checking %a, base %a, pattern %a",hashname,basename,pathname) end - if blobfile then - local blobroot=files.__path__ or blobpath - if type(blobfile)=='string' then - if not dname or find(blobfile,dname) then - local variant=hash.type - local search=filejoin(blobroot,blobfile,bname) - local result=methodhandler('concatinators',hash.type,blobroot,blobfile,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + local path,name=lookup(content,basename) + if path then + local metadata=content.metadata + local realroot=metadata and metadata.path or hashname + if type(path)=="string" then + check(hash,realroot,pathname,path,name) else - for kk=1,#blobfile do - local vv=blobfile[kk] - if not dname or find(vv,dname) then - local variant=hash.type - local search=filejoin(blobroot,vv,bname) - local result=methodhandler('concatinators',hash.type,blobroot,vv,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + for i=1,#path do + check(hash,realroot,pathname,path[i],name) end end end elseif trace_locating then - report_resolving("no match in %a (%s)",blobpath,bname) + report_resolving("no match in %a (%s)",hashname,basename) end end end @@ -14461,7 +15307,7 @@ end local function can_be_dir(name) local fakepaths=instance.fakepaths if not fakepaths[name] then - if lfs.isdir(name) then + if isdir(name) then fakepaths[name]=1 else fakepaths[name]=2 @@ -14477,10 +15323,11 @@ local function find_analyze(filename,askedformat,allresults) if askedformat=="" then if ext=="" or not suffixmap[ext] then local defaultsuffixes=resolvers.defaultsuffixes + local formatofsuffix=resolvers.formatofsuffix for i=1,#defaultsuffixes do local forcedname=filename..'.'..defaultsuffixes[i] wantedfiles[#wantedfiles+1]=forcedname - filetype=resolvers.formatofsuffix(forcedname) + filetype=formatofsuffix(forcedname) if trace_locating then report_resolving("forcing filetype %a",filetype) end @@ -14520,14 +15367,14 @@ local function find_wildcard(filename,allresults) if trace_locating then report_resolving("checking wildcard %a",filename) end - local method,result=resolvers.findwildcardfiles(filename) + local result=resolvers.findwildcardfiles(filename) if result then return "wildcard",result end end end local function find_qualified(filename,allresults,askedformat,alsostripped) - if not file.is_qualified_path(filename) then + if not is_qualified_path(filename) then return end if trace_locating then @@ -14601,33 +15448,66 @@ local function check_subpath(fname) return fname end end -local function find_intree(filename,filetype,wantedfiles,allresults) +local function makepathlist(list,filetype) local typespec=resolvers.variableofformat(filetype) - local pathlist=resolvers.expandedpathlist(typespec) - local method="intree" + local pathlist=resolvers.expandedpathlist(typespec,filetype and usertypes[filetype]) + local entry={} if pathlist and #pathlist>0 then - local filelist=collect_files(wantedfiles) + for k=1,#pathlist do + local path=pathlist[k] + local prescanned=find(path,'^!!') + local resursive=find(path,'//$') + local pathname=lpegmatch(inhibitstripper,path) + local expression=makepathexpression(pathname) + local barename=gsub(pathname,"/+$","") + barename=resolveprefix(barename) + local scheme=url.hasscheme(barename) + local schemename=gsub(barename,"%.%*$",'') + entry[k]={ + path=path, + pathname=pathname, + prescanned=prescanned, + recursive=recursive, + expression=expression, + barename=barename, + scheme=scheme, + schemename=schemename, + } + end + entry.typespec=typespec + list[filetype]=entry + else + list[filetype]=false + end + return entry +end +local function find_intree(filename,filetype,wantedfiles,allresults) + local pathlists=instance.pathlists + if not pathlists then + pathlists=setmetatableindex(allocate(),makepathlist) + instance.pathlists=pathlists + end + local pathlist=pathlists[filetype] + if pathlist then + local method="intree" + local filelist=collect_files(wantedfiles) local dirlist={} + local result={} if filelist then for i=1,#filelist do dirlist[i]=filedirname(filelist[i][3]).."/" end end if trace_detail then - report_resolving("checking filename %a",filename) + report_resolving("checking filename %a in tree",filename) end - local resolve=resolvers.resolve - local result={} for k=1,#pathlist do - local path=pathlist[k] - local pathname=lpegmatch(inhibitstripper,path) - local doscan=path==pathname - if not find (pathname,'//$') then - doscan=false - end + local entry=pathlist[k] + local path=entry.path + local pathname=entry.pathname local done=false if filelist then - local expression=makepathexpression(pathname) + local expression=entry.expression if trace_detail then report_resolving("using pattern %a for path %a",expression,pathname) end @@ -14635,8 +15515,8 @@ local function find_intree(filename,filetype,wantedfiles,allresults) local fl=filelist[k] local f=fl[2] local d=dirlist[k] - if find(d,expression) or find(resolve(d),expression) then - result[#result+1]=resolve(fl[3]) + if find(d,expression) or find(resolveprefix(d),expression) then + result[#result+1]=resolveprefix(fl[3]) done=true if allresults then if trace_detail then @@ -14657,56 +15537,62 @@ local function find_intree(filename,filetype,wantedfiles,allresults) method="database" else method="filesystem" - pathname=gsub(pathname,"/+$","") - pathname=resolve(pathname) - local scheme=url.hasscheme(pathname) + local scheme=entry.scheme if not scheme or scheme=="file" then - local pname=gsub(pathname,"%.%*$",'') + local pname=entry.schemename if not find(pname,"*",1,true) then if can_be_dir(pname) then - for k=1,#wantedfiles do - local w=wantedfiles[k] - local fname=check_subpath(filejoin(pname,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + if not done and not entry.prescanned then + if trace_detail then + report_resolving("quick root scan for %a",pname) end - end - if not done and doscan then - local files=resolvers.simplescanfiles(pname,false,true) for k=1,#wantedfiles do local w=wantedfiles[k] - local subpath=files[w] - if not subpath or subpath=="" then - elseif type(subpath)=="string" then - local fname=check_subpath(filejoin(pname,subpath,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + local fname=check_subpath(filejoin(pname,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break end - else - for i=1,#subpath do - local sp=subpath[i] - if sp=="" then - else - local fname=check_subpath(filejoin(pname,sp,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break + end + end + if not done and entry.recursive then + if trace_detail then + report_resolving("scanning filesystem for %a",pname) + end + local files=resolvers.simplescanfiles(pname,false,true) + for k=1,#wantedfiles do + local w=wantedfiles[k] + local subpath=files[w] + if not subpath or subpath=="" then + elseif type(subpath)=="string" then + local fname=check_subpath(filejoin(pname,subpath,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + else + for i=1,#subpath do + local sp=subpath[i] + if sp=="" then + else + local fname=check_subpath(filejoin(pname,sp,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end end end end - end - if done and not allresults then - break + if done and not allresults then + break + end end end end @@ -14714,6 +15600,18 @@ local function find_intree(filename,filetype,wantedfiles,allresults) end else end + else + for k=1,#wantedfiles do + local pname=entry.barename + local fname=methodhandler('finders',pname.."/"..wantedfiles[k]) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + end end end if done and not allresults then @@ -14748,10 +15646,13 @@ local function find_otherwise(filename,filetype,wantedfiles,allresults) local filelist=collect_files(wantedfiles) local fl=filelist and filelist[1] if fl then - return "otherwise",{ resolvers.resolve(fl[3]) } + return "otherwise",{ resolveprefix(fl[3]) } end end collect_instance_files=function(filename,askedformat,allresults) + if not filename or filename=="" then + return {} + end askedformat=askedformat or "" filename=collapsepath(filename,".") filename=gsub(filename,"^%./",getcurrentdir().."/") @@ -14786,7 +15687,11 @@ collect_instance_files=function(filename,askedformat,allresults) else local method,result,stamp,filetype,wantedfiles if instance.remember then - stamp=formatters["%s--%s"](filename,askedformat) + if askedformat=="" then + stamp=formatters["%s::%s"](suffixonly(filename),filename) + else + stamp=formatters["%s::%s"](askedformat,filename) + end result=stamp and instance.found[stamp] if result then if trace_locating then @@ -14821,7 +15726,7 @@ collect_instance_files=function(filename,askedformat,allresults) end if stamp then if trace_locating then - report_resolving("remembering file %a",filename) + report_resolving("remembering file %a using hash %a",filename,stamp) end instance.found[stamp]=result end @@ -14829,6 +15734,9 @@ collect_instance_files=function(filename,askedformat,allresults) end end local function findfiles(filename,filetype,allresults) + if not filename or filename=="" then + return {} + end local result,status=collect_instance_files(filename,filetype or "",allresults) if not result or #result==0 then local lowered=lower(filename) @@ -14848,39 +15756,30 @@ function resolvers.findpath(filename,filetype) return filedirname(findfiles(filename,filetype,false)[1] or "") end local function findgivenfiles(filename,allresults) - local bname,result=filebasename(filename),{} + local base=filebasename(filename) + local result={} local hashes=instance.hashes - local noffound=0 + local function okay(hash,path,name) + local found=methodhandler('concatinators',hash.type,hash.name,path,name) + if found and found~="" then + result[#result+1]=resolveprefix(found) + return not allresults + end + end for k=1,#hashes do local hash=hashes[k] - local files=instance.files[hash.name] or {} - 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 - local found=methodhandler('concatinators',hash.type,hash.name,blist,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then - break - end + local content=instance.files[hash.name] + if content then + local path,name=lookup(content,base) + if not path then + elseif type(path)=="string" then + if okay(hash,path,name) then + return result end else - for kk=1,#blist do - local vv=blist[kk] - local found=methodhandler('concatinators',hash.type,hash.name,vv,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then break end + for i=1,#path do + if okay(hash,path[i],name) then + return result end end end @@ -14894,64 +15793,80 @@ end function resolvers.findgivenfile(filename) return findgivenfiles(filename,false)[1] or "" end -local function doit(path,blist,bname,tag,variant,result,allresults) - local done=false - if blist and variant then - local resolve=resolvers.resolve - if type(blist)=='string' then - if find(lower(blist),path) then - local full=methodhandler('concatinators',variant,tag,blist,bname) or "" - result[#result+1]=resolve(full) - done=true - end - else - for kk=1,#blist do - local vv=blist[kk] - if find(lower(vv),path) then - local full=methodhandler('concatinators',variant,tag,vv,bname) or "" - result[#result+1]=resolve(full) - done=true - if not allresults then break end - end - end - end - end - return done -end local makewildcard=Cs( (P("^")^0*P("/")*P(-1)+P(-1))/".*"+(P("^")^0*P("/")/"")^0*(P("*")/".*"+P("-")/"%%-"+P(".")/"%%."+P("?")/"."+P("\\")/"/"+P(1))^0 ) function resolvers.wildcardpattern(pattern) return lpegmatch(makewildcard,pattern) or pattern end -local function findwildcardfiles(filename,allresults,result) - result=result or {} +local function findwildcardfiles(filename,allresults,result) + local result=result or {} local base=filebasename(filename) local dirn=filedirname(filename) local path=lower(lpegmatch(makewildcard,dirn) or dirn) local name=lower(lpegmatch(makewildcard,base) or base) - local files,done=instance.files,false + local files=instance.files if find(name,"*",1,true) then local hashes=instance.hashes + local function okay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - for kk,hh in next,files[hashname] do - if not find(kk,"^remap:") then - if find(lower(kk),name) then - if doit(path,hh,kk,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + for found,base in filtered(files[hashname],name) do + if type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end end end end end else + local function okayokay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end local hashes=instance.hashes for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - if doit(path,files[hashname][base],base,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + local found,base=lookup(content,base) + if not found then + elseif type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end + end + end end end return result @@ -15024,7 +15939,7 @@ end function resolvers.dowithpath(name,func) local pathlist=resolvers.expandedpathlist(name) for i=1,#pathlist do - func("^"..resolvers.cleanpath(pathlist[i])) + func("^"..cleanpath(pathlist[i])) end end function resolvers.dowithvariable(name,func) @@ -15032,23 +15947,23 @@ function resolvers.dowithvariable(name,func) end function resolvers.locateformat(name) local engine=environment.ownmain or "luatex" - local barename=file.removesuffix(name) - local fullname=file.addsuffix(barename,"fmt") + local barename=removesuffix(name) + local fullname=addsuffix(barename,"fmt") local fmtname=caches.getfirstreadablefile(fullname,"formats",engine) or "" if fmtname=="" then fmtname=resolvers.findfile(fullname) - fmtname=resolvers.cleanpath(fmtname) + fmtname=cleanpath(fmtname) end if fmtname~="" then - local barename=file.removesuffix(fmtname) - local luaname=file.addsuffix(barename,luasuffixes.lua) - local lucname=file.addsuffix(barename,luasuffixes.luc) - local luiname=file.addsuffix(barename,luasuffixes.lui) - if lfs.isfile(luiname) then + local barename=removesuffix(fmtname) + local luaname=addsuffix(barename,luasuffixes.lua) + local lucname=addsuffix(barename,luasuffixes.luc) + local luiname=addsuffix(barename,luasuffixes.lui) + if isfile(luiname) then return barename,luiname - elseif lfs.isfile(lucname) then + elseif isfile(lucname) then return barename,lucname - elseif lfs.isfile(luaname) then + elseif isfile(luaname) then return barename,luaname end end @@ -15070,29 +15985,24 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) local hash=hashes[i] local blobtype=hash.type local blobpath=hash.name - if blobpath then + if blobtype and blobpath then + local total=0 + local checked=0 + local done=0 if before then before(blobtype,blobpath,pattern) end - local files=instance.files[blobpath] - local total,checked,done=0,0,0 - if files then - for k,v in table.sortedhash(files) do - total=total+1 - if find(k,"^remap:") then - elseif find(k,pattern) then - if type(v)=="string" then - checked=checked+1 - if handle(blobtype,blobpath,v,k) then - done=done+1 - end - else - checked=checked+#v - for i=1,#v do - if handle(blobtype,blobpath,v[i],k) then - done=done+1 - end - end + for path,name in filtered(instance.files[blobpath],pattern) do + if type(path)=="string" then + checked=checked+1 + if handle(blobtype,blobpath,path,name) then + done=done+1 + end + else + checked=checked+#path + for i=1,#path do + if handle(blobtype,blobpath,path[i],name) then + done=done+1 end end end @@ -15103,8 +16013,8 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) end end end -resolvers.obsolete=resolvers.obsolete or {} -local obsolete=resolvers.obsolete +local obsolete=resolvers.obsolete or {} +resolvers.obsolete=obsolete resolvers.find_file=resolvers.findfile obsolete.find_file=resolvers.findfile resolvers.find_files=resolvers.findfiles obsolete.find_files=resolvers.findfiles @@ -15115,7 +16025,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-pre"] = package.loaded["data-pre"] or true --- original size: 6643, stripped down to: 4401 +-- original size: 3950, stripped down to: 2935 if not modules then modules={} end modules ['data-pre']={ version=1.001, @@ -15125,44 +16035,51 @@ if not modules then modules={} end modules ['data-pre']={ license="see context related readme files" } local resolvers=resolvers -local prefixes=utilities.storage.allocate() -resolvers.prefixes=prefixes -local cleanpath,findgivenfile,expansion=resolvers.cleanpath,resolvers.findgivenfile,resolvers.expansion +local prefixes=resolvers.prefixes +local cleanpath=resolvers.cleanpath +local findgivenfile=resolvers.findgivenfile +local expansion=resolvers.expansion local getenv=resolvers.getenv -local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match -local joinpath,basename,dirname=file.join,file.basename,file.dirname -local getmetatable,rawset,type=getmetatable,rawset,type +local basename=file.basename +local dirname=file.dirname +local joinpath=file.join +local isfile=lfs.isfile prefixes.environment=function(str) return cleanpath(expansion(str)) end -prefixes.relative=function(str,n) - if io.exists(str) then - 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.."../" +local function relative(str,n) + if not isfile(str) then + local pstr="./"..str + if isfile(pstr) then + str=pstr + else + local p="../" + for i=1,n or 2 do + local pstr=p..str + if isfile(pstr) then + str=pstr + break + else + p=p.."../" + end end end end return cleanpath(str) end +local function locate(str) + local fullname=findgivenfile(str) or "" + return cleanpath(fullname~="" and fullname or str) +end +prefixes.relative=relative +prefixes.locate=locate prefixes.auto=function(str) - local fullname=prefixes.relative(str) - if not lfs.isfile(fullname) then - fullname=prefixes.locate(str) + local fullname=relative(str) + if not isfile(fullname) then + fullname=locate(str) end return fullname end -prefixes.locate=function(str) - local fullname=findgivenfile(str) or "" - return cleanpath((fullname~="" and fullname) or str) -end prefixes.filename=function(str) local fullname=findgivenfile(str) or "" return cleanpath(basename((fullname~="" and fullname) or str)) @@ -15183,6 +16100,13 @@ end prefixes.home=function(str) return cleanpath(joinpath(getenv('HOME'),str)) end +prefixes.env=prefixes.environment +prefixes.rel=prefixes.relative +prefixes.loc=prefixes.locate +prefixes.kpse=prefixes.locate +prefixes.full=prefixes.locate +prefixes.file=prefixes.filename +prefixes.path=prefixes.pathname local function toppath() local inputstack=resolvers.inputstack if not inputstack then @@ -15195,98 +16119,22 @@ local function toppath() return pathname end end -resolvers.toppath=toppath -prefixes.toppath=function(str) - return cleanpath(joinpath(toppath(),str)) -end -prefixes.env=prefixes.environment -prefixes.rel=prefixes.relative -prefixes.loc=prefixes.locate -prefixes.kpse=prefixes.locate -prefixes.full=prefixes.locate -prefixes.file=prefixes.filename -prefixes.path=prefixes.pathname -function resolvers.allprefixes(separator) - local all=table.sortedkeys(prefixes) - if separator then - for i=1,#all do - all[i]=all[i]..":" - end - end - return all -end -local function _resolve_(method,target) - local action=prefixes[method] - if action then - return action(target) - else - return method..":"..target - end -end -local resolved,abstract={},{} -function resolvers.resetresolve(str) - resolved,abstract={},{} -end -local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) -local prefix=C(R("az")^2)*P(":") -local target=C((1-S(" \"\';,"))^1) -local notarget=(#S(";,")+P(-1))*Cc("") -local pattern=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) -local function resolve(str) - if type(str)=="table" then - local t={} - for i=1,#str do - t[i]=resolve(str[i]) - end - return t +local function jobpath() + local path=resolvers.stackpath() + if not path or path=="" then + return "." else - local res=resolved[str] - if not res then - res=lpegmatch(pattern,str) - resolved[str]=res - abstract[res]=str - end - return res - end -end -local function unresolve(str) - return abstract[str] or str -end -resolvers.resolve=resolve -resolvers.unresolve=unresolve -if type(os.uname)=="function" then - for k,v in next,os.uname() do - if not prefixes[k] then - prefixes[k]=function() return v end - end - end -end -if os.type=="unix" then - local pattern - local function makepattern(t,k,v) - if t then - rawset(t,k,v) - end - local colon=P(":") - for k,v in table.sortedpairs(prefixes) do - if p then - p=P(k)+p - else - p=P(k) - end - end - pattern=Cs((p*colon+colon/";"+P(1))^0) - end - makepattern() - getmetatable(prefixes).__newindex=makepattern - function resolvers.repath(str) - return lpegmatch(pattern,str) - end -else - function resolvers.repath(str) - return str + return path end end +resolvers.toppath=toppath +resolvers.jobpath=jobpath +prefixes.toppath=function(str) return cleanpath(joinpath(toppath(),str)) end +prefixes.jobpath=function(str) return cleanpath(joinpath(jobpath(),str)) end +resolvers.setdynamic("toppath") +resolvers.setdynamic("jobpath") +prefixes.jobfile=prefixes.jobpath +resolvers.setdynamic("jobfile") end -- of closure @@ -15348,7 +16196,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-fil"] = package.loaded["data-fil"] or true --- original size: 3801, stripped down to: 3231 +-- original size: 3863, stripped down to: 3310 if not modules then modules={} end modules ['data-fil']={ version=1.001, @@ -15360,30 +16208,31 @@ if not modules then modules={} end modules ['data-fil']={ local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_files=logs.reporter("resolvers","files") local resolvers=resolvers +local resolveprefix=resolvers.resolve local finders,openers,loaders,savers=resolvers.finders,resolvers.openers,resolvers.loaders,resolvers.savers local locators,hashers,generators,concatinators=resolvers.locators,resolvers.hashers,resolvers.generators,resolvers.concatinators local checkgarbage=utilities.garbagecollector and utilities.garbagecollector.check function locators.file(specification) - local name=specification.filename - local realname=resolvers.resolve(name) + local filename=specification.filename + local realname=resolveprefix(filename) if realname and realname~='' and lfs.isdir(realname) then if trace_locating then - report_files("file locator %a found as %a",name,realname) + report_files("file locator %a found as %a",filename,realname) end - resolvers.appendhash('file',name,true) + resolvers.appendhash('file',filename,true) elseif trace_locating then - report_files("file locator %a not found",name) + report_files("file locator %a not found",filename) end end function hashers.file(specification) - local name=specification.filename - local content=caches.loadcontent(name,'files') - resolvers.registerfilehash(name,content,content==nil) + local pathname=specification.filename + local content=caches.loadcontent(pathname,'files') + resolvers.registerfilehash(pathname,content,content==nil) end function generators.file(specification) - local path=specification.filename - local content=resolvers.scanfiles(path,false,true) - resolvers.registerfilehash(path,content,true) + local pathname=specification.filename + local content=resolvers.scanfiles(pathname,false,true) + resolvers.registerfilehash(pathname,content,true) end concatinators.file=file.join function finders.file(specification,filetype) @@ -15665,7 +16514,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-zip"] = package.loaded["data-zip"] or true --- original size: 8489, stripped down to: 6757 +-- original size: 8772, stripped down to: 6841 if not modules then modules={} end modules ['data-zip']={ version=1.001, @@ -15684,16 +16533,6 @@ zip.archives=zip.archives or {} local archives=zip.archives zip.registeredfiles=zip.registeredfiles or {} local registeredfiles=zip.registeredfiles -local limited=false -directives.register("system.inputmode",function(v) - if not limited then - local i_limiter=io.i_limiter(v) - if i_limiter then - zip.open=i_limiter.protect(zip.open) - limited=true - end - end -end) local function validzip(str) if not find(str,"^zip://") then return "zip:///"..str @@ -15708,7 +16547,7 @@ function zip.openarchive(name) local arch=archives[name] if not arch then local full=resolvers.findfile(name) or "" - arch=(full~="" and zip.open(full)) or false + arch=full~="" and zip.open(full) or false archives[name]=arch end return arch @@ -15867,31 +16706,42 @@ function resolvers.usezipfile(archive) end end function resolvers.registerzipfile(z,tree) - local files,filter={},"" - if tree=="" then - filter="^(.+)/(.-)$" - else - filter=format("^%s/(.+)/(.-)$",tree) - end + local names={} + local files={} + local remap={} + local n=0 + local filter=tree=="" and "^(.+)/(.-)$" or format("^%s/(.+)/(.-)$",tree) + local register=resolvers.registerfile if trace_locating then report_zip("registering: using filter %a",filter) end - local register,n=resolvers.registerfile,0 for i in z:files() do - local path,name=match(i.filename,filter) - if path then - if name and name~='' then - register(files,name,path) - n=n+1 - else + local filename=i.filename + local path,name=match(filename,filter) + if not path then + n=n+1 + register(names,filename,"") + local usedname=lower(filename) + files[usedname]="" + if usedname~=filename then + remap[usedname]=filename end - else - register(files,i.filename,'') + elseif name and name~="" then n=n+1 + register(names,name,path) + local usedname=lower(name) + files[usedname]=path + if usedname~=name then + remap[usedname]=name + end + else end end report_zip("registering: %s files registered",n) - return files + return { + files=files, + remap=remap, + } end @@ -15901,7 +16751,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tre"] = package.loaded["data-tre"] or true --- original size: 2508, stripped down to: 2074 +-- original size: 8479, stripped down to: 5580 if not modules then modules={} end modules ['data-tre']={ version=1.001, @@ -15910,42 +16760,64 @@ if not modules then modules={} end modules ['data-tre']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,gsub,format=string.find,string.gsub,string.format +local find,gsub,lower=string.find,string.gsub,string.lower +local basename,dirname,joinname=file.basename,file.dirname,file .join +local globdir,isdir,isfile=dir.glob,lfs.isdir,lfs.isfile +local P,lpegmatch=lpeg.P,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_trees=logs.reporter("resolvers","trees") local resolvers=resolvers -local done,found,notfound={},{},resolvers.finders.notfound -function resolvers.finders.tree(specification) +local resolveprefix=resolvers.resolve +local notfound=resolvers.finders.notfound +local lookup=resolvers.get_from_content +local collectors={} +local found={} +function resolvers.finders.tree(specification) local spec=specification.filename - local fnd=found[spec] - if fnd==nil then + local okay=found[spec] + if okay==nil then if spec~="" then - local path,name=file.dirname(spec),file.basename(spec) - if path=="" then path="." end - local hash=done[path] - if not hash then - local pattern=path.."/*" - hash=dir.glob(pattern) - done[path]=hash + local path=dirname(spec) + local name=basename(spec) + if path=="" then + path="." + end + local names=collectors[path] + if not names then + local pattern=find(path,"/%*+$") and path or (path.."/*") + names=globdir(pattern) + collectors[path]=names end local pattern="/"..gsub(name,"([%.%-%+])","%%%1").."$" - for k=1,#hash do - local v=hash[k] - if find(v,pattern) then - found[spec]=v - return v + for i=1,#names do + local fullname=names[i] + if find(fullname,pattern) then + found[spec]=fullname + return fullname + end + end + local pattern=lower(pattern) + for i=1,#names do + local fullname=lower(names[i]) + if find(fullname,pattern) then + if isfile(fullname) then + found[spec]=fullname + return fullname + else + break + end end end end - fnd=notfound() - found[spec]=fnd + okay=notfound() + found[spec]=okay end - return fnd + return okay end function resolvers.locators.tree(specification) local name=specification.filename - local realname=resolvers.resolve(name) - if realname and realname~='' and lfs.isdir(realname) then + local realname=resolveprefix(name) + if realname and realname~='' and isdir(realname) then if trace_locating then report_trees("locator %a found",realname) end @@ -15956,16 +16828,110 @@ function resolvers.locators.tree(specification) end function resolvers.hashers.tree(specification) local name=specification.filename - if trace_locating then - report_trees("analysing %a",name) - end + report_trees("analyzing %a",name) resolvers.methodhandler("hashers",name) resolvers.generators.file(specification) end -resolvers.concatinators.tree=resolvers.concatinators.file -resolvers.generators.tree=resolvers.generators.file -resolvers.openers.tree=resolvers.openers.file -resolvers.loaders.tree=resolvers.loaders.file +local collectors={} +local splitter=lpeg.splitat("/**/") +local stripper=lpeg.replacer { [P("/")*P("*")^1*P(-1)]="" } +table.setmetatableindex(collectors,function(t,k) + local rootname=lpegmatch(stripper,k) + local dataname=joinname(rootname,"dirlist") + local content=caches.loadcontent(dataname,"files",dataname) + if not content then + content=resolvers.scanfiles(rootname,nil,nil,false,true) + caches.savecontent(dataname,"files",content,dataname) + end + t[k]=content + return content +end) +local function checked(root,p,n) + if p then + if type(p)=="table" then + for i=1,#p do + local fullname=joinname(root,p[i],n) + if isfile(fullname) then + return fullname + end + end + else + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + end + return notfound() +end +local function resolve(specification) + local filename=specification.filename + if filename~="" then + local root,rest=lpegmatch(splitter,filename) + if root and rest then + local path,name=dirname(rest),basename(rest) + if name~=rest then + local content=collectors[root] + local p,n=lookup(content,name) + if not p then + return notfound() + end + local pattern=".*/"..path.."$" + local istable=type(p)=="table" + if istable then + for i=1,#p do + local pi=p[i] + if pi==path or find(pi,pattern) then + local fullname=joinname(root,pi,n) + if isfile(fullname) then + return fullname + end + end + end + elseif p==path or find(p,pattern) then + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + local queries=specification.queries + if queries and queries.option=="fileonly" then + return checked(root,p,n) + else + return notfound() + end + end + end + local path,name=dirname(filename),basename(filename) + local root=lpegmatch(stripper,path) + local content=collectors[path] + local p,n=lookup(content,name) + if p then + return checked(root,p,n) + end + end + return notfound() +end +resolvers.finders .dirlist=resolve +resolvers.locators .dirlist=resolvers.locators .tree +resolvers.hashers .dirlist=resolvers.hashers .tree +resolvers.generators.dirlist=resolvers.generators.file +resolvers.openers .dirlist=resolvers.openers .file +resolvers.loaders .dirlist=resolvers.loaders .file +function resolvers.finders.dirfile(specification) + local queries=specification.queries + if queries then + queries.option="fileonly" + else + specification.queries={ option="fileonly" } + end + return resolve(specification) +end +resolvers.locators .dirfile=resolvers.locators .dirlist +resolvers.hashers .dirfile=resolvers.hashers .dirlist +resolvers.generators.dirfile=resolvers.generators.dirlist +resolvers.openers .dirfile=resolvers.openers .dirlist +resolvers.loaders .dirfile=resolvers.loaders .dirlist end -- of closure @@ -15974,7 +16940,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-sch"] = package.loaded["data-sch"] or true --- original size: 6213, stripped down to: 5160 +-- original size: 6569, stripped down to: 5304 if not modules then modules={} end modules ['data-sch']={ version=1.001, @@ -16000,8 +16966,13 @@ directives.register("schemes.threshold",function(v) threshold=tonumber(v) or thr function cleaners.none(specification) return specification.original end -function cleaners.strip(specification) - return (gsub(specification.original,"[^%a%d%.]+","-")) +function cleaners.strip(specification) + local path,name=file.splitbase(specification.original) + if path=="" then + return (gsub(name,"[^%a%d%.]+","-")) + else + return (gsub((gsub(path,"%.","-").."-"..name),"[^%a%d%.]+","-")) + end end function cleaners.md5(specification) return file.addsuffix(md5.hex(specification.original),file.suffix(specification.path)) @@ -16018,7 +16989,7 @@ end local cached,loaded,reused,thresholds,handlers={},{},{},{},{} local function runcurl(name,cachename) local command="curl --silent --insecure --create-dirs --output "..cachename.." "..name - os.spawn(command) + os.execute(command) end local function fetch(specification) local original=specification.original @@ -16150,7 +17121,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lua"] = package.loaded["data-lua"] or true --- original size: 4237, stripped down to: 3177 +-- original size: 4313, stripped down to: 3227 if not modules then modules={} end modules ['data-lua']={ version=1.001, @@ -16159,7 +17130,7 @@ if not modules then modules={} end modules ['data-lua']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local resolvers,package=resolvers,package +local package,lpeg=package,lpeg local gsub=string.gsub local concat=table.concat local addsuffix=file.addsuffix @@ -16170,9 +17141,11 @@ local luaformats={ 'TEXINPUTS','LUAINPUTS' } local libformats={ 'CLUAINPUTS' } local helpers=package.helpers or {} local methods=helpers.methods or {} +local resolvers=resolvers +local resolveprefix=resolvers.resolve +helpers.report=logs.reporter("resolvers","libraries") trackers.register("resolvers.libraries",function(v) helpers.trace=v end) trackers.register("resolvers.locating",function(v) helpers.trace=v end) -helpers.report=logs.reporter("resolvers","libraries") helpers.sequence={ "already loaded", "preload table", @@ -16187,7 +17160,7 @@ helpers.sequence={ } local pattern=Cs(P("!")^0/""*(P("/")*P(-1)/"/"+P("/")^1/"/"+1)^0) function helpers.cleanpath(path) - return resolvers.resolve(lpegmatch(pattern,path)) + return resolveprefix(lpegmatch(pattern,path)) end local loadedaslib=helpers.loadedaslib local getextraluapaths=package.extraluapaths @@ -16324,7 +17297,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmf"] = package.loaded["data-tmf"] or true --- original size: 2600, stripped down to: 1627 +-- original size: 2601, stripped down to: 1627 if not modules then modules={} end modules ['data-tmf']={ version=1.001, @@ -16380,7 +17353,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lst"] = package.loaded["data-lst"] or true --- original size: 2654, stripped down to: 2301 +-- original size: 2734, stripped down to: 2354 if not modules then modules={} end modules ['data-lst']={ version=1.001, @@ -16389,10 +17362,13 @@ if not modules then modules={} end modules ['data-lst']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,concat,upper,format=string.find,table.concat,string.upper,string.format +local rawget,type,next=rawget,type,next +local find,concat,upper=string.find,table.concat,string.upper local fastcopy,sortedpairs=table.fastcopy,table.sortedpairs -resolvers.listers=resolvers.listers or {} local resolvers=resolvers +local listers=resolvers.listers or {} +resolvers.listers=listers +local resolveprefix=resolvers.resolve local report_lists=logs.reporter("resolvers","lists") local function tabstr(str) if type(str)=='table' then @@ -16401,7 +17377,7 @@ local function tabstr(str) return str end end -function resolvers.listers.variables(pattern) +function listers.variables(pattern) local instance=resolvers.instance local environment=instance.environment local variables=instance.variables @@ -16422,10 +17398,10 @@ function resolvers.listers.variables(pattern) for key,value in sortedpairs(configured) do if key~="" and (pattern=="" or find(upper(key),pattern)) then report_lists(key) - report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") - report_lists(" var: %s",tabstr(configured[key]) or "unset") - report_lists(" exp: %s",tabstr(expansions[key]) or "unset") - report_lists(" res: %s",tabstr(resolvers.resolve(expansions[key])) or "unset") + report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") + report_lists(" var: %s",tabstr(configured[key]) or "unset") + report_lists(" exp: %s",tabstr(expansions[key]) or "unset") + report_lists(" res: %s",tabstr(resolveprefix(expansions[key])) or "unset") end end instance.environment=fastcopy(env) @@ -16433,15 +17409,15 @@ function resolvers.listers.variables(pattern) instance.expansions=fastcopy(exp) end local report_resolved=logs.reporter("system","resolved") -function resolvers.listers.configurations() +function listers.configurations() local configurations=resolvers.instance.specification for i=1,#configurations do - report_resolved("file : %s",resolvers.resolve(configurations[i])) + report_resolved("file : %s",resolveprefix(configurations[i])) end report_resolved("") local list=resolvers.expandedpathfromlist(resolvers.splitpath(resolvers.luacnfspec)) for i=1,#list do - local li=resolvers.resolve(list[i]) + local li=resolveprefix(list[i]) if lfs.isdir(li) then report_resolved("path - %s",li) else @@ -16746,7 +17722,7 @@ do -- create closure to overcome 200 locals limit package.loaded["luat-fmt"] = package.loaded["luat-fmt"] or true --- original size: 5951, stripped down to: 4922 +-- original size: 5955, stripped down to: 4926 if not modules then modules={} end modules ['luat-fmt']={ version=1.001, @@ -16834,7 +17810,7 @@ function environment.make_format(name) end local command=format("%s --ini %s --lua=%s %s %sdump",engine,primaryflags(),quoted(usedluastub),quoted(fulltexsourcename),os.platform=="unix" and "\\\\" or "\\") report_format("running command: %s\n",command) - os.spawn(command) + os.execute(command) local pattern=file.removesuffix(file.basename(usedluastub)).."-*.mem" local mp=dir.glob(pattern) if mp then @@ -16869,7 +17845,7 @@ function environment.run_format(name,data,more) else local command=format("%s %s --fmt=%s --lua=%s %s %s",engine,primaryflags(),quoted(barename),quoted(luaname),quoted(data),more~="" and quoted(more) or "") report_format("running command: %s",command) - os.spawn(command) + os.execute(command) end end end @@ -16880,8 +17856,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 695180 --- stripped bytes : 246794 +-- original bytes : 739251 +-- stripped bytes : 264497 -- end library merge @@ -17107,6 +18083,7 @@ local helpinfo = [[ <category name="basic"> <subcategory> <flag name="script"><short>run an mtx script (lua prefered method) (<ref name="noquotes"/>), no script gives list</short></flag> + <flag name="evaluate"><short>run code passed on the commandline (between quotes)</short></flag> <flag name="execute"><short>run a script or program (texmfstart method) (<ref name="noquotes"/>)</short></flag> <flag name="resolve"><short>resolve prefixed arguments</short></flag> <flag name="ctxlua"><short>run internally (using preloaded libs)</short></flag> @@ -17132,6 +18109,7 @@ local helpinfo = [[ <flag name="verbose"><short>give a bit more info</short></flag> <flag name="trackers" value="list"><short>enable given trackers</short></flag> <flag name="progname" value="str"><short>format or backend</short></flag> + <flag name="systeminfo" value="str"><short>show current operating system, processor, etc</short></flag> </subcategory> <subcategory> <flag name="edit"><short>launch editor with found file</short></flag> @@ -17761,6 +18739,39 @@ function runners.associate(filename) os.launch(filename) end +function runners.evaluate(code,filename) -- for Luigi + if code == "loop" then + while true do + io.write("> ") + local code = io.read() + if code ~= "" then + local temp = string.match(code,"^= (.*)$") + if temp then + code = "print("..temp..")" + end + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("! " .. (message or code).."\n") + else + io.write(compiled()) + end + end + end + else + if type(code) ~= "string" or code == "" then + code = filename + end + if code ~= "" then + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("invalid lua code: " .. (message or code)) + return + end + io.write(compiled()) + end + end +end + function runners.gethelp(filename) local url = environment.argument("url") if url and url ~= "" then @@ -17772,6 +18783,15 @@ function runners.gethelp(filename) end end +function runners.systeminfo() + report("architecture : %s",os.platform or "<unset>") + report("operating system : %s",os.name or "<unset>") + report("file architecture : %s",os.type or "<unset>") + report("binary path : %s",os.selfdir or "<unset>") + report("binary suffix : %s",os.binsuffix or "<unset>") + report("library suffix : %s",os.libsuffix or "<unset>") +end + -- this is a bit dirty ... first we store the first filename and next we -- split the arguments so that we only see the ones meant for this script -- ... later we will use the second half @@ -17887,16 +18907,7 @@ end if e_argument("ansi") then - local formatters = string.formatters - - logs.setformatters { - report_yes = formatters["[1;32m%-15s [0;1m|[0m %s"], - report_nop = formatters["[1;32m%-15s [0;1m|[0m"], - subreport_yes = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m %s"], - subreport_nop = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m"], - status_yes = formatters["[1;32m%-15s [0;1m:[0m %s\n"], - status_nop = formatters["[1;32m%-15s [0;1m:[0m\n"], - } + logs.setformatters("ansi") local script = e_argument("script") or e_argument("scripts") @@ -17921,6 +18932,10 @@ if e_argument("script") or e_argument("scripts") then ok = runners.execute_ctx_script(filename) end +elseif e_argument("evaluate") then + + runners.evaluate(e_argument("evaluate"),filename) + elseif e_argument("selfmerge") then -- embed used libraries @@ -18213,6 +19228,10 @@ elseif e_argument("exporthelp") then runners.loadbase() application.export(e_argument("exporthelp"),filename) +elseif e_argument("systeminfo") then + + runners.systeminfo() + elseif e_argument("help") or filename=='help' or filename == "" then application.help() diff --git a/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua b/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua index 5cb5c8d2416..48a9a27a250 100644 --- a/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua +++ b/Master/texmf-dist/scripts/context/stubs/mswin/mtxrun.lua @@ -56,7 +56,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lua"] = package.loaded["l-lua"] or true --- original size: 3409, stripped down to: 1763 +-- original size: 3888, stripped down to: 2197 if not modules then modules={} end modules ['l-lua']={ version=1.001, @@ -139,6 +139,13 @@ end if lua then lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" end +local flush=io.flush +if flush then + local execute=os.execute if execute then function os.execute(...) flush() return execute(...) end end + local exec=os.exec if exec then function os.exec (...) flush() return exec (...) end end + local spawn=os.spawn if spawn then function os.spawn (...) flush() return spawn (...) end end + local popen=io.popen if popen then function io.popen (...) flush() return popen (...) end end +end end -- of closure @@ -437,7 +444,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lpeg"] = package.loaded["l-lpeg"] or true --- original size: 29983, stripped down to: 16202 +-- original size: 36225, stripped down to: 19891 if not modules then modules={} end modules ['l-lpeg']={ version=1.001, @@ -498,6 +505,8 @@ patterns.utfbom_16_le=utfbom_16_le patterns.utfbom_8=utfbom_8 patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") +patterns.utf_32_be_nl=P("\000\000\000\r\000\000\000\n")+P("\000\000\000\r")+P("\000\000\000\n") +patterns.utf_32_le_nl=P("\r\000\000\000\n\000\000\000")+P("\r\000\000\000")+P("\n\000\000\000") patterns.utf8one=R("\000\127") patterns.utf8two=R("\194\223")*utf8next patterns.utf8three=R("\224\239")*utf8next*utf8next @@ -527,9 +536,21 @@ patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) +local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) +local e_collapser=Cs((whitespace^1*P(-1)/""+nonwhitespace^1+whitespace^1/" ")^0) +local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) +local b_stripper=Cs(spacer^0/""*(nonspacer^1+spacer^1/" ")^0) +local e_stripper=Cs((spacer^1*P(-1)/""+nonspacer^1+spacer^1/" ")^0) +local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser +patterns.b_collapser=b_collapser +patterns.m_collapser=m_collapser +patterns.e_collapser=e_collapser +patterns.b_stripper=b_stripper +patterns.m_stripper=m_stripper +patterns.e_stripper=e_stripper patterns.lowercase=lowercase patterns.uppercase=uppercase patterns.letter=patterns.lowercase+patterns.uppercase @@ -566,9 +587,12 @@ patterns.integer=sign^-1*digit^1 patterns.unsigned=digit^0*period*digit^1 patterns.float=sign^-1*patterns.unsigned patterns.cunsigned=digit^0*comma*digit^1 +patterns.cpunsigned=digit^0*(period+comma)*digit^1 patterns.cfloat=sign^-1*patterns.cunsigned +patterns.cpfloat=sign^-1*patterns.cpunsigned patterns.number=patterns.float+patterns.integer patterns.cnumber=patterns.cfloat+patterns.integer +patterns.cpnumber=patterns.cpfloat+patterns.integer patterns.oct=zero*octdigit^1 patterns.octal=patterns.oct patterns.HEX=zero*P("X")*(digit+uppercase)^1 @@ -985,23 +1009,40 @@ function lpeg.append(list,pp,delayed,checked) end return p end +local p_false=P(false) +local p_true=P(true) local function make(t) - local p + local function making(t) + local p=p_false + local keys=sortedkeys(t) + for i=1,#keys do + local k=keys[i] + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then + else + p=p+P(k)*making(v) + end + end + end + if t[""] then + p=p+p_true + end + return p + end + local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] - local v=t[k] - if not p then - if next(v) then - p=P(k)*make(v) - else - p=P(k) - end - else - if next(v) then - p=p+P(k)*make(v) + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then else - p=p+P(k) + p=p+P(k)*making(v) end end end @@ -1009,13 +1050,73 @@ local function make(t) end function lpeg.utfchartabletopattern(list) local tree={} - for i=1,#list do - local t=tree - for c in gmatch(list[i],".") do - if not t[c] then - t[c]={} + local n=#list + if n==0 then + for s in next,list do + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true + end + end + else + for i=1,n do + local s=list[i] + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true end - t=t[c] end end return make(tree) @@ -1055,6 +1156,65 @@ local case_2=period*(digit-trailingzeros)^1*(trailingzeros/"") local number=digit^1*(case_1+case_2) local stripper=Cs((number+1)^0) lpeg.patterns.stripzeros=stripper +local byte_to_HEX={} +local byte_to_hex={} +local byte_to_dec={} +local hex_to_byte={} +for i=0,255 do + local H=format("%02X",i) + local h=format("%02x",i) + local d=format("%03i",i) + local c=char(i) + byte_to_HEX[c]=H + byte_to_hex[c]=h + byte_to_dec[c]=d + hex_to_byte[h]=c + hex_to_byte[H]=c +end +local hextobyte=P(2)/hex_to_byte +local bytetoHEX=P(1)/byte_to_HEX +local bytetohex=P(1)/byte_to_hex +local bytetodec=P(1)/byte_to_dec +local hextobytes=Cs(hextobyte^0) +local bytestoHEX=Cs(bytetoHEX^0) +local bytestohex=Cs(bytetohex^0) +local bytestodec=Cs(bytetodec^0) +patterns.hextobyte=hextobyte +patterns.bytetoHEX=bytetoHEX +patterns.bytetohex=bytetohex +patterns.bytetodec=bytetodec +patterns.hextobytes=hextobytes +patterns.bytestoHEX=bytestoHEX +patterns.bytestohex=bytestohex +patterns.bytestodec=bytestodec +function string.toHEX(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestoHEX,s) + end +end +function string.tohex(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestohex,s) + end +end +function string.todec(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestodec,s) + end +end +function string.tobytes(s) + if not s or s=="" then + return s + else + return lpegmatch(hextobytes,s) + end +end end -- of closure @@ -1082,7 +1242,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-string"] = package.loaded["l-string"] or true --- original size: 5671, stripped down to: 2827 +-- original size: 5694, stripped down to: 2827 if not modules then modules={} end modules ['l-string']={ version=1.001, @@ -1187,7 +1347,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-table"] = package.loaded["l-table"] or true --- original size: 31860, stripped down to: 20846 +-- original size: 33830, stripped down to: 21894 if not modules then modules={} end modules ['l-table']={ version=1.001, @@ -1230,11 +1390,12 @@ function table.keys(t) end end local function compare(a,b) - local ta,tb=type(a),type(b) - if ta==tb then + local ta=type(a) + local tb=type(b) + if ta==tb and ta=="number" then return a<b else - return tostring(a)<tostring(b) + return tostring(a)<tostring(b) end end local function sortedkeys(tab) @@ -1334,14 +1495,14 @@ local function sortedhash(t,cmp) end local n=0 local m=#s - local function kv(s) + local function kv() if n<m then n=n+1 local k=s[n] return k,t[k] end end - return kv,s + return kv else return nothing end @@ -1506,7 +1667,7 @@ local function simple_table(t) if tv=="number" then nt=nt+1 if hexify then - tt[nt]=format("0x%04X",v) + tt[nt]=format("0x%X",v) else tt[nt]=tostring(v) end @@ -1537,7 +1698,7 @@ local function do_serialize(root,name,depth,level,indexed) local tn=type(name) if tn=="number" then if hexify then - handle(format("%s[0x%04X]={",depth,name)) + handle(format("%s[0x%X]={",depth,name)) else handle(format("%s[%s]={",depth,name)) end @@ -1554,7 +1715,7 @@ local function do_serialize(root,name,depth,level,indexed) end end end - if root and next(root) then + if root and next(root)~=nil then local first,last=nil,0 if compact then last=#root @@ -1576,7 +1737,7 @@ local function do_serialize(root,name,depth,level,indexed) if compact and first and tk=="number" and k>=first and k<=last then if tv=="number" then if hexify then - handle(format("%s 0x%04X,",depth,v)) + handle(format("%s 0x%X,",depth,v)) else handle(format("%s %s,",depth,v)) end @@ -1587,7 +1748,7 @@ local function do_serialize(root,name,depth,level,indexed) handle(format("%s %q,",depth,v)) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then handle(format("%s {},",depth)) elseif inline then local st=simple_table(v) @@ -1617,25 +1778,25 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="number" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=0x%04X,",depth,k,v)) + handle(format("%s [0x%X]=0x%X,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end elseif tk=="boolean" then if hexify then - handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v)) + handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v)) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then - handle(format("%s %s=0x%04X,",depth,k,v)) + handle(format("%s %s=0x%X,",depth,k,v)) else handle(format("%s %s=%s,",depth,k,v)) end else if hexify then - handle(format("%s [%q]=0x%04X,",depth,k,v)) + handle(format("%s [%q]=0x%X,",depth,k,v)) else handle(format("%s [%q]=%s,",depth,k,v)) end @@ -1644,7 +1805,7 @@ local function do_serialize(root,name,depth,level,indexed) if reduce and tonumber(v) then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v)) + handle(format("%s [0x%X]=%s,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end @@ -1658,7 +1819,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,v)) + handle(format("%s [0x%X]=%q,",depth,k,v)) else handle(format("%s [%s]=%q,",depth,k,v)) end @@ -1671,10 +1832,10 @@ local function do_serialize(root,name,depth,level,indexed) end end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={},",depth,k)) + handle(format("%s [0x%X]={},",depth,k)) else handle(format("%s [%s]={},",depth,k)) end @@ -1690,7 +1851,7 @@ local function do_serialize(root,name,depth,level,indexed) if st then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", "))) + handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%s]={ %s },",depth,k,concat(st,", "))) end @@ -1710,7 +1871,7 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="boolean" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false")) + handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%s]=%s,",depth,k,v and "true" or "false")) end @@ -1726,7 +1887,7 @@ local function do_serialize(root,name,depth,level,indexed) local f=getinfo(v).what=="C" and dump(dummy) or dump(v) if tk=="number" then if hexify then - handle(format("%s [0x%04X]=load(%q),",depth,k,f)) + handle(format("%s [0x%X]=load(%q),",depth,k,f)) else handle(format("%s [%s]=load(%q),",depth,k,f)) end @@ -1741,7 +1902,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,tostring(v))) + handle(format("%s [0x%X]=%q,",depth,k,tostring(v))) else handle(format("%s [%s]=%q,",depth,k,tostring(v))) end @@ -1795,7 +1956,7 @@ local function serialize(_handle,root,name,specification) end elseif tname=="number" then if hexify then - handle(format("[0x%04X]={",name)) + handle(format("[0x%X]={",name)) else handle("["..name.."]={") end @@ -1813,7 +1974,7 @@ local function serialize(_handle,root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,"",0) end end @@ -1942,14 +2103,25 @@ local function identical(a,b) end table.identical=identical table.are_equal=are_equal -function table.compact(t) - if t then - for k,v in next,t do - if not next(v) then - t[k]=nil +local function sparse(old,nest,keeptables) + local new={} + for k,v in next,old do + if not (v=="" or v==false) then + if nest and type(v)=="table" then + v=sparse(v,nest) + if keeptables or next(v)~=nil then + new[k]=v + end + else + new[k]=v end end end + return new +end +table.sparse=sparse +function table.compact(t) + return sparse(t,true,true) end function table.contains(t,v) if t then @@ -2054,10 +2226,10 @@ function table.sub(t,i,j) return { unpack(t,i,j) } end function table.is_empty(t) - return not t or not next(t) + return not t or next(t)==nil end function table.has_one_entry(t) - return t and not next(t,next(t)) + return t and next(t,next(t))==nil end function table.loweredkeys(t) local l={} @@ -2102,6 +2274,44 @@ function table.values(t,s) return {} end end +function table.filtered(t,pattern,sort,cmp) + if t and type(pattern)=="string" then + if sort then + local s + if cmp then + s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) + else + s=sortedkeys(t) + end + local n=0 + local m=#s + local function kv(s) + while n<m do + n=n+1 + local k=s[n] + if find(k,pattern) then + return k,t[k] + end + end + end + return kv,s + else + local n=next(t) + local function iterator() + while n~=nil do + local k=n + n=next(t,k) + if find(k,pattern) then + return k,t[k] + end + end + end + return iterator,t + end + else + return nothing + end +end end -- of closure @@ -2110,7 +2320,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-io"] = package.loaded["l-io"] or true --- original size: 8824, stripped down to: 6347 +-- original size: 8643, stripped down to: 6232 if not modules then modules={} end modules ['l-io']={ version=1.001, @@ -2417,8 +2627,6 @@ function io.readstring(f,n,m) local str=gsub(f:read(n),"\000","") return str end -if not io.i_limiter then function io.i_limiter() end end -if not io.o_limiter then function io.o_limiter() end end end -- of closure @@ -2645,7 +2853,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-os"] = package.loaded["l-os"] or true --- original size: 16093, stripped down to: 9704 +-- original size: 15761, stripped down to: 9403 if not modules then modules={} end modules ['l-os']={ version=1.001, @@ -2719,13 +2927,10 @@ if not os.__getenv__ then setmetatable(os.env,{ __index=__index,__newindex=__newindex } ) end end -local execute,spawn,exec,iopopen,ioflush=os.execute,os.spawn or os.execute,os.exec or os.execute,io.popen,io.flush -function os.execute(...) ioflush() return execute(...) end -function os.spawn (...) ioflush() return spawn (...) end -function os.exec (...) ioflush() return exec (...) end -function io.popen (...) ioflush() return iopopen(...) end +local execute=os.execute +local iopopen=io.popen function os.resultof(command) - local handle=io.popen(command,"r") + local handle=iopopen(command,"r") if handle then local result=handle:read("*all") or "" handle:close() @@ -2754,7 +2959,7 @@ local launchers={ unix="$BROWSER %s &> /dev/null &", } function os.launch(str) - os.execute(format(launchers[os.name] or launchers.unix,str)) + execute(format(launchers[os.name] or launchers.unix,str)) end if not os.times then function os.times() @@ -3029,7 +3234,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-file"] = package.loaded["l-file"] or true --- original size: 18308, stripped down to: 9948 +-- original size: 20949, stripped down to: 9945 if not modules then modules={} end modules ['l-file']={ version=1.001, @@ -3043,41 +3248,28 @@ local file=file if not lfs then lfs=optionalrequire("lfs") end -if not lfs then - lfs={ - getcurrentdir=function() - return "." - end, - attributes=function() - return nil - end, - isfile=function(name) - local f=io.open(name,'rb') - if f then - f:close() - return true - end - end, - isdir=function(name) - print("you need to load lfs") - return false - end - } -elseif not lfs.isfile then - local attributes=lfs.attributes - function lfs.isdir(name) - return attributes(name,"mode")=="directory" - end - function lfs.isfile(name) - return attributes(name,"mode")=="file" - end -end local insert,concat=table.insert,table.concat local match,find,gmatch=string.match,string.find,string.gmatch local lpegmatch=lpeg.match local getcurrentdir,attributes=lfs.currentdir,lfs.attributes local checkedsplit=string.checkedsplit local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct +local tricky=S("/\\")*P(-1) +local attributes=lfs.attributes +if sandbox then + sandbox.redefine(lfs.isfile,"lfs.isfile") + sandbox.redefine(lfs.isdir,"lfs.isdir") +end +function lfs.isdir(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end +end +function lfs.isfile(name) + return attributes(name,"mode")=="file" +end local colon=P(":") local period=P(".") local periods=P("..") @@ -3279,28 +3471,30 @@ local isroot=fwslash^1*-1 local hasroot=fwslash^1 local reslasher=lpeg.replacer(S("\\/"),"/") local deslasher=lpeg.replacer(S("\\/")^1,"/") -function file.join(...) - local lst={... } - local one=lst[1] +function file.join(one,two,three,...) + if not two then + return one=="" and one or lpegmatch(stripper,one) + end + if one=="" then + return lpegmatch(stripper,three and concat({ two,three,... },"/") or two) + end if lpegmatch(isnetwork,one) then local one=lpegmatch(reslasher,one) - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return one..two else return one.."/"..two end elseif lpegmatch(isroot,one) then - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return two else return "/"..two end - elseif one=="" then - return lpegmatch(stripper,concat(lst,"/",2)) else - return lpegmatch(deslasher,concat(lst,"/")) + return lpegmatch(deslasher,concat({ one,two,three,... },"/")) end end local drivespec=R("az","AZ")^1*colon @@ -3474,7 +3668,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-md5"] = package.loaded["l-md5"] or true --- original size: 3760, stripped down to: 2088 +-- original size: 3248, stripped down to: 2266 if not modules then modules={} end modules ['l-md5']={ version=1.001, @@ -3492,14 +3686,20 @@ if not md5 then } end local md5,file=md5,file -local gsub,format,byte=string.gsub,string.format,string.byte -local md5sum=md5.sum -local function convert(str,fmt) - return (gsub(md5sum(str),".",function(chr) return format(fmt,byte(chr)) end)) -end -if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end -if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end -if not md5.dec then function md5.dec(str) return convert(str,"%03i") end end +local gsub=string.gsub +do + local patterns=lpeg and lpeg.patterns + if patterns then + local bytestoHEX=patterns.bytestoHEX + local bytestohex=patterns.bytestohex + local bytestodec=patterns.bytestodec + local lpegmatch=lpeg.match + local md5sum=md5.sum + if not md5.HEX then function md5.HEX(str) if str then return lpegmatch(bytestoHEX,md5sum(str)) end end end + if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end + if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end + end +end function file.needsupdating(oldname,newname,threshold) local oldtime=lfs.attributes(oldname,"modification") if oldtime then @@ -3556,7 +3756,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-url"] = package.loaded["l-url"] or true --- original size: 12292, stripped down to: 5585 +-- original size: 12531, stripped down to: 5721 if not modules then modules={} end modules ['l-url']={ version=1.001, @@ -3642,19 +3842,25 @@ local splitquery=Cf (Ct("")*P { "sequence", pair=Cg(key*equal*value), },rawset) local function hashed(str) - if str=="" then + if not str or str=="" then return { scheme="invalid", original=str, } end - local s=split(str) - local rawscheme=s[1] - local rawquery=s[4] - local somescheme=rawscheme~="" - local somequery=rawquery~="" + local detailed=split(str) + local rawscheme="" + local rawquery="" + local somescheme=false + local somequery=false + if detailed then + rawscheme=detailed[1] + rawquery=detailed[4] + somescheme=rawscheme~="" + somequery=rawquery~="" + end if not somescheme and not somequery then - s={ + return { scheme="file", authority="", path=str, @@ -3664,28 +3870,28 @@ local function hashed(str) noscheme=true, filename=str, } - else - local authority,path,filename=s[2],s[3] - if authority=="" then - filename=path - elseif path=="" then - filename="" - else - filename=authority.."/"..path - end - s={ - scheme=rawscheme, - authority=authority, - path=path, - query=lpegmatch(unescaper,rawquery), - queries=lpegmatch(splitquery,rawquery), - fragment=s[5], - original=str, - noscheme=false, - filename=filename, - } end - return s + local authority=detailed[2] + local path=detailed[3] + local filename=nil + if authority=="" then + filename=path + elseif path=="" then + filename="" + else + filename=authority.."/"..path + end + return { + scheme=rawscheme, + authority=authority, + path=path, + query=lpegmatch(unescaper,rawquery), + queries=lpegmatch(splitquery,rawquery), + fragment=detailed[5], + original=str, + noscheme=false, + filename=filename, + } end url.split=split url.hasscheme=hasscheme @@ -3719,7 +3925,7 @@ function url.construct(hash) end return lpegmatch(escaper,concat(fullurl)) end -local pattern=Cs(noslash*R("az","AZ")*(S(":|")/":")*noslash*P(1)^0) +local pattern=Cs(slash^-1/""*R("az","AZ")*((S(":|")/":")+P(":"))*slash*P(1)^0) function url.filename(filename) local spec=hashed(filename) local path=spec.path @@ -3767,7 +3973,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-dir"] = package.loaded["l-dir"] or true --- original size: 14788, stripped down to: 9096 +-- original size: 16765, stripped down to: 11003 if not modules then modules={} end modules ['l-dir']={ version=1.001, @@ -3777,7 +3983,7 @@ if not modules then modules={} end modules ['l-dir']={ license="see context related readme files" } local type,select=type,select -local find,gmatch,match,gsub=string.find,string.gmatch,string.match,string.gsub +local find,gmatch,match,gsub,sub=string.find,string.gmatch,string.match,string.gsub,string.sub local concat,insert,remove,unpack=table.concat,table.insert,table.remove,table.unpack local lpegmatch=lpeg.match local P,S,R,C,Cc,Cs,Ct,Cv,V=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cv,lpeg.V @@ -3786,53 +3992,126 @@ local dir=dir local lfs=lfs local attributes=lfs.attributes local walkdir=lfs.dir -local isdir=lfs.isdir -local isfile=lfs.isfile +local isdir=lfs.isdir +local isfile=lfs.isfile local currentdir=lfs.currentdir local chdir=lfs.chdir local mkdir=lfs.mkdir local onwindows=os.type=="windows" or find(os.getenv("PATH"),";",1,true) -if not isdir then - function isdir(name) - local a=attributes(name) - return a and a.mode=="directory" +if onwindows then + local tricky=S("/\\")*P(-1) + isdir=function(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end + end + isfile=function(name) + return attributes(name,"mode")=="file" end lfs.isdir=isdir -end -if not isfile then - function isfile(name) - local a=attributes(name) - return a and a.mode=="file" + lfs.isfile=isfile +else + isdir=function(name) + return attributes(name,"mode")=="directory" end + isfile=function(name) + return attributes(name,"mode")=="file" + end + lfs.isdir=isdir lfs.isfile=isfile end function dir.current() return (gsub(currentdir(),"\\","/")) end -local lfsisdir=isdir -local function isdir(path) - path=gsub(path,"[/\\]+$","") - return lfsisdir(path) +local function glob_pattern_function(path,patt,recurse,action) + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + action(full) + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end + end + end + end + if dirs then + for i=1,#dirs do + glob_pattern_function(dirs[i],patt,recurse,action) + end + end + end end -lfs.isdir=isdir -local function globpattern(path,patt,recurse,action) - if path=="/" then - path=path.."." - elseif not find(path,"/$") then - path=path..'/' - end - if isdir(path) then - for name in walkdir(path) do - local full=path..name - local mode=attributes(full,'mode') - if mode=='file' then - if find(full,patt) then - action(full) +local function glob_pattern_table(path,patt,recurse,result) + if not result then + result={} + end + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + result[#result+1]=full + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - globpattern(full,patt,recurse,action) end end + if dirs then + for i=1,#dirs do + glob_pattern_table(dirs[i],patt,recurse,result) + end + end + end + return result +end +local function globpattern(path,patt,recurse,method) + local kind=type(method) + if patt and sub(patt,1,-3)==path then + patt=false + end + if kind=="function" then + return glob_pattern_function(path,patt,recurse,method) + elseif kind=="table" then + return glob_pattern_table(path,patt,recurse,method) + else + return glob_pattern_table(path,patt,recurse,{}) end end dir.globpattern=globpattern @@ -3845,34 +4124,40 @@ local function collectpattern(path,patt,recurse,result) ok,scanner,first=xpcall(function() return walkdir(path) end,function() end) end if ok and type(scanner)=="function" then - if not find(path,"/$") then path=path..'/' end + if not find(path,"/$") then + path=path..'/' + end for name in scanner,first do - local full=path..name - local attr=attributes(full) - local mode=attr.mode - if mode=='file' then - if find(full,patt) then + if name=="." then + elseif name==".." then + else + local full=path..name + local attr=attributes(full) + local mode=attr.mode + if mode=='file' then + if find(full,patt) then + result[name]=attr + end + elseif recurse and mode=="directory" then + attr.list=collectpattern(full,patt,recurse) result[name]=attr end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - attr.list=collectpattern(full,patt,recurse) - result[name]=attr end end end return result end dir.collectpattern=collectpattern -local separator -if onwindows then +local separator,pattern +if onwindows then local slash=S("/\\")/"/" - pattern=Ct { + pattern={ [1]=(Cs(P(".")+slash^1)+Cs(R("az","AZ")*P(":")*slash^0)+Cc("./"))*V(2)*V(3), [2]=Cs(((1-S("*?/\\"))^0*slash)^0), [3]=Cs(P(1)^0) } -else - pattern=Ct { +else + pattern={ [1]=(C(P(".")+P("/")^1)+Cc("./"))*V(2)*V(3), [2]=C(((1-S("*?/"))^0*P("/"))^0), [3]=C(P(1)^0) @@ -3890,9 +4175,8 @@ local function glob(str,t) elseif isfile(str) then t(str) else - local split=lpegmatch(pattern,str) - if split then - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) @@ -3914,16 +4198,12 @@ local function glob(str,t) return { str } end else - local split=lpegmatch(pattern,str) - if split then - local t=t or {} - local action=action or function(name) t[#t+1]=name end - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) - globpattern(start,result,recurse,action) - return t + return globpattern(start,result,recurse,t) else return {} end @@ -4071,47 +4351,51 @@ else end end dir.makedirs=dir.mkdirs -if onwindows then - function dir.expandname(str) - local first,nothing,last=match(str,"^(//)(//*)(.*)$") - if first then - first=dir.current().."/" - end - if not first then - first,last=match(str,"^(//)/*(.*)$") - end - if not first then - first,last=match(str,"^([a-zA-Z]:)(.*)$") - if first and not find(last,"^/") then - local d=currentdir() - if chdir(first) then - first=dir.current() +do + local chdir=sandbox and sandbox.original(chdir) or chdir + if onwindows then + local xcurrentdir=dir.current + function dir.expandname(str) + local first,nothing,last=match(str,"^(//)(//*)(.*)$") + if first then + first=xcurrentdir().."/" + end + if not first then + first,last=match(str,"^(//)/*(.*)$") + end + if not first then + first,last=match(str,"^([a-zA-Z]:)(.*)$") + if first and not find(last,"^/") then + local d=currentdir() + if chdir(first) then + first=xcurrentdir() + end + chdir(d) end - chdir(d) + end + if not first then + first,last=xcurrentdir(),str + end + last=gsub(last,"//","/") + last=gsub(last,"/%./","/") + last=gsub(last,"^/*","") + first=gsub(first,"/*$","") + if last=="" or last=="." then + return first + else + return first.."/"..last end end - if not first then - first,last=dir.current(),str - end - last=gsub(last,"//","/") - last=gsub(last,"/%./","/") - last=gsub(last,"^/*","") - first=gsub(first,"/*$","") - if last=="" or last=="." then - return first - else - return first.."/"..last - end - end -else - function dir.expandname(str) - if not find(str,"^/") then - str=currentdir().."/"..str + else + function dir.expandname(str) + if not find(str,"^/") then + str=currentdir().."/"..str + end + str=gsub(str,"//","/") + str=gsub(str,"/%./","/") + str=gsub(str,"(.)/%.$","%1") + return str end - str=gsub(str,"//","/") - str=gsub(str,"/%./","/") - str=gsub(str,"(.)/%.$","%1") - return str end end file.expandname=dir.expandname @@ -4154,7 +4438,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-boolean"] = package.loaded["l-boolean"] or true --- original size: 1809, stripped down to: 1527 +-- original size: 1850, stripped down to: 1568 if not modules then modules={} end modules ['l-boolean']={ version=1.001, @@ -4208,11 +4492,11 @@ function string.booleanstring(str) return str=="yes" or str=="on" or str=="t" end end -function string.is_boolean(str,default) +function string.is_boolean(str,default,strict) if type(str)=="string" then - if str=="true" or str=="yes" or str=="on" or str=="t" or str=="1" then + if str=="true" or str=="yes" or str=="on" or str=="t" or (not strict and str=="1") then return true - elseif str=="false" or str=="no" or str=="off" or str=="f" or str=="0" then + elseif str=="false" or str=="no" or str=="off" or str=="f" or (not strict and str=="0") then return false end end @@ -4226,7 +4510,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-unicode"] = package.loaded["l-unicode"] or true --- original size: 33706, stripped down to: 14938 +-- original size: 37233, stripped down to: 15684 if not modules then modules={} end modules ['l-unicode']={ version=1.001, @@ -4242,7 +4526,9 @@ local type=type local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch local concat=table.concat local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp -local lpegmatch,patterns=lpeg.match,lpeg.patterns +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern local bytepairs=string.bytepairs local finder=lpeg.finder local replacer=lpeg.replacer @@ -4251,7 +4537,7 @@ local utfgmatch=utf.gmatch local p_utftype=patterns.utftype local p_utfstricttype=patterns.utfstricttype local p_utfoffset=patterns.utfoffset -local p_utf8char=patterns.utf8char +local p_utf8char=patterns.utf8character local p_utf8byte=patterns.utf8byte local p_utfbom=patterns.utfbom local p_newline=patterns.newline @@ -4450,15 +4736,52 @@ if not utf.sub then end end end -function utf.remapper(mapping) - local pattern=Cs((p_utf8char/mapping)^0) - return function(str) - if not str or str=="" then - return "" +function utf.remapper(mapping,option) + local variant=type(mapping) + if variant=="table" then + if option=="dynamic" then + local pattern=false + table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) + return function(str) + if not str or str=="" then + return "" + else + if not pattern then + pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + end + return lpegmatch(pattern,str) + end + end + elseif option=="pattern" then + return Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) else - return lpegmatch(pattern,str) + local pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + elseif variant=="function" then + if option=="pattern" then + return Cs((p_utf8char/mapping+p_utf8char)^0) + else + local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + else + return function(str) + return str or "" end - end,pattern + end end function utf.replacer(t) local r=replacer(t,false,false,true) @@ -4508,190 +4831,157 @@ function utf.magic(f) end local utf16_to_utf8_be,utf16_to_utf8_le local utf32_to_utf8_be,utf32_to_utf8_le -local utf_16_be_linesplitter=patterns.utfbom_16_be^-1*lpeg.tsplitat(patterns.utf_16_be_nl) -local utf_16_le_linesplitter=patterns.utfbom_16_le^-1*lpeg.tsplitat(patterns.utf_16_le_nl) -if bytepairs then - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*left+right - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_be,s) + else + return s end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*right+left - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +end +local utf16_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_be_linesplitter,t) end - utf32_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*256*256*a+256*256*b - else - r=r+1 - result[t]=utfchar(more+256*a+b) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_be,s) end - return t end - utf32_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*b+a - else - r=r+1 - result[t]=utfchar(more+256*256*256*b+256*256*a) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) - end - return t + return t +end +utf16_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_le,s) + else + return s end -else - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if left=="\000" then - r=r+1 - result[r]=utfchar(byte(right)) - elseif right then - local now=256*byte(left)+byte(right) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) +end +local utf16_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_le,s) end - return t end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) + return t +end +utf32_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_be,s) + else + return s + end +end +local utf32_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_be,s) end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if right=="\000" then - r=r+1 - result[r]=utfchar(byte(left)) - elseif right then - local now=256*byte(right)+byte(left) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) + end + return t +end +utf32_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_le,s) + else + return s + end +end +local utf32_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_le,s) end - return t end - utf32_to_utf8_le=function() return {} end - utf32_to_utf8_be=function() return {} end + return t end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t utf.utf16_to_utf8_le=utf16_to_utf8_le utf.utf16_to_utf8_be=utf16_to_utf8_be utf.utf32_to_utf8_le=utf32_to_utf8_le utf.utf32_to_utf8_be=utf32_to_utf8_be -function utf.utf8_to_utf8(t) +function utf.utf8_to_utf8_t(t) return type(t)=="string" and lpegmatch(utflinesplitter,t) or t end -function utf.utf16_to_utf8(t,endian) - return endian and utf16_to_utf8_be(t) or utf16_to_utf8_le(t) or t +function utf.utf16_to_utf8_t(t,endian) + return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t end -function utf.utf32_to_utf8(t,endian) - return endian and utf32_to_utf8_be(t) or utf32_to_utf8_le(t) or t +function utf.utf32_to_utf8_t(t,endian) + return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t end -local function little(c) - local b=byte(c) +local function little(b) if b<0x10000 then return char(b%256,b/256) else @@ -4700,8 +4990,7 @@ local function little(c) return char(b1%256,b1/256,b2%256,b2/256) end end -local function big(c) - local b=byte(c) +local function big(b) if b<0x10000 then return char(b/256,b%256) else @@ -4710,27 +4999,29 @@ local function big(c) return char(b1/256,b1%256,b2/256,b2%256) end end -local _,l_remap=utf.remapper(little) -local _,b_remap=utf.remapper(big) -function utf.utf8_to_utf16_be(str,nobom) +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) if nobom then return lpegmatch(b_remap,str) else return char(254,255)..lpegmatch(b_remap,str) end end -function utf.utf8_to_utf16_le(str,nobom) +local function utf8_to_utf16_le(str,nobom) if nobom then return lpegmatch(l_remap,str) else return char(255,254)..lpegmatch(l_remap,str) end end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le function utf.utf8_to_utf16(str,littleendian,nobom) if littleendian then - return utf.utf8_to_utf16_le(str,nobom) + return utf8_to_utf16_le(str,nobom) else - return utf.utf8_to_utf16_be(str,nobom) + return utf8_to_utf16_be(str,nobom) end end local pattern=Cs ( @@ -4746,16 +5037,16 @@ function utf.xstring(s) return format("0x%05X",type(s)=="number" and s or utfbyte(s)) end function utf.toeight(str) - if not str then + if not str or str=="" then return nil end local utftype=lpegmatch(p_utfstricttype,str) if utftype=="utf-8" then - return sub(str,4) - elseif utftype=="utf-16-le" then - return utf16_to_utf8_le(str) + return sub(str,4) elseif utftype=="utf-16-be" then - return utf16_to_utf8_ne(str) + return utf16_to_utf8_be(str) + elseif utftype=="utf-16-le" then + return utf16_to_utf8_le(str) else return str end @@ -4834,7 +5125,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-math"] = package.loaded["l-math"] or true --- original size: 915, stripped down to: 836 +-- original size: 974, stripped down to: 890 if not modules then modules={} end modules ['l-math']={ version=1.001, @@ -4844,6 +5135,9 @@ if not modules then modules={} end modules ['l-math']={ license="see context related readme files" } local floor,sin,cos,tan=math.floor,math.sin,math.cos,math.tan +if not math.ceiling then + math.ceiling=math.ceil +end if not math.round then function math.round(x) return floor(x+0.5) end end @@ -4871,7 +5165,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-str"] = package.loaded["util-str"] or true --- original size: 33063, stripped down to: 18397 +-- original size: 34407, stripped down to: 18852 if not modules then modules={} end modules ['util-str']={ version=1.001, @@ -5001,10 +5295,12 @@ local striplinepatterns={ ["retain"]=p_retain_normal, ["retain and collapse"]=p_retain_collapse, ["retain and no empty"]=p_retain_noempty, + ["collapse"]=patterns.collapser, } +setmetatable(striplinepatterns,{ __index=function(t,k) return p_prune_collapse end }) strings.striplinepatterns=striplinepatterns function strings.striplines(str,how) - return str and lpegmatch(how and striplinepatterns[how] or p_prune_collapse,str) or str + return str and lpegmatch(striplinepatterns[how],str) or str end strings.striplong=strings.striplines function strings.nice(str) @@ -5044,10 +5340,10 @@ string.tracedchars=tracedchars strings.tracers=tracedchars function string.tracedchar(b) if type(b)=="number" then - return tracedchars[b] or (utfchar(b).." (U+"..format('%05X',b)..")") + return tracedchars[b] or (utfchar(b).." (U+"..format("%05X",b)..")") else local c=utfbyte(b) - return tracedchars[c] or (b.." (U+"..format('%05X',c)..")") + return tracedchars[c] or (b.." (U+"..(c and format("%05X",c) or "?????")..")") end end function number.signed(i) @@ -5184,12 +5480,12 @@ local format_f=function(f) n=n+1 return format("format('%%%sf',a%s)",f,n) end -local format_F=function(f) +local format_F=function(f) n=n+1 if not f or f=="" then - return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or (a%s == 1 and '1') or format('%%.9f',a%s))",n,n,n,n) + return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n) else - return format("((a%s == 0 and '0') or (a%s == 1 and '1') or format('%%%sf',a%s))",n,n,f,n) + return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n) end end local format_g=function(f) @@ -5417,7 +5713,6 @@ local builder=Cs { "start", +V("j")+V("J") +V("m")+V("M") +V("z") -+V("*") )+V("*") )*(P(-1)+Carg(1)) )^0, @@ -5461,6 +5756,7 @@ local builder=Cs { "start", ["a"]=(prefix_any*P("a"))/format_a, ["A"]=(prefix_any*P("A"))/format_A, ["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest, + ["?"]=Cs(((1-P("%"))^1 )^1)/format_rest, ["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension, } local direct=Cs ( @@ -5535,6 +5831,15 @@ else add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=lpeg.patterns.texescape }) add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=lpeg.patterns.luaescape }) end +local dquote=patterns.dquote +local equote=patterns.escaped+dquote/'\\"'+1 +local space=patterns.space +local cquote=Cc('"') +local pattern=Cs(dquote*(equote-P(-2))^0*dquote) ++Cs(cquote*(equote-space)^0*space*equote^0*cquote) +function string.optionalquoted(str) + return lpegmatch(pattern,str) or str +end end -- of closure @@ -5543,7 +5848,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 23985, stripped down to: 16069 +-- original size: 24267, stripped down to: 16260 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -5633,6 +5938,15 @@ function tables.removevalue(t,value) end end end +function tables.replacevalue(t,oldvalue,newvalue) + if oldvalue and newvalue then + for i=1,#t do + if t[i]==oldvalue then + t[i]=newvalue + end + end + end +end function tables.insertbeforevalue(t,value,extra) for i=1,#t do if t[i]==extra then @@ -6026,7 +6340,7 @@ function table.serialize(root,name,specification) end depth=depth+1 end - if root and next(root) then + if root and next(root)~=nil then local first=nil local last=0 last=#root @@ -6051,7 +6365,7 @@ function table.serialize(root,name,specification) elseif tv=="string" then n=n+1 t[n]=f_val_str(depth,v) elseif tv=="table" then - if not next(v) then + if next(v)==nil then n=n+1 t[n]=f_val_not(depth) else local st=simple_table(v) @@ -6081,7 +6395,7 @@ function table.serialize(root,name,specification) n=n+1 t[n]=f_key_boo_value_str(depth,k,v) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then n=n+1 t[n]=f_key_num_value_not(depth,k,v) elseif tk=="string" then @@ -6139,7 +6453,7 @@ function table.serialize(root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,1,0) end end @@ -6302,7 +6616,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-prs"] = package.loaded["util-prs"] or true --- original size: 19618, stripped down to: 14012 +-- original size: 21751, stripped down to: 15108 if not modules then modules={} end modules ['util-prs']={ version=1.001, @@ -6458,11 +6772,30 @@ function parsers.settings_to_array(str,strict) return { str } end end -local separator=space^0*comma*space^0 -local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(comma+P(-1)))))^0) -local withvalue=Carg(1)*value/function(f,s) return f(s) end -local pattern_a=spaces*Ct(value*(separator*value)^0) -local pattern_b=spaces*withvalue*(separator*withvalue)^0 +local cache_a={} +local cache_b={} +function parsers.groupedsplitat(symbol,withaction) + if not symbol then + symbol="," + end + local pattern=(withaction and cache_b or cache_a)[symbol] + if not pattern then + local symbols=S(symbol) + local separator=space^0*symbols*space^0 + local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(symbols+P(-1)))))^0) + if withaction then + local withvalue=Carg(1)*value/function(f,s) return f(s) end + pattern=spaces*withvalue*(separator*withvalue)^0 + cache_b[symbol]=pattern + else + pattern=spaces*Ct(value*(separator*value)^0) + cache_a[symbol]=pattern + end + end + return pattern +end +local pattern_a=parsers.groupedsplitat(",",false) +local pattern_b=parsers.groupedsplitat(",",true) function parsers.stripped_settings_to_array(str) if not str or str=="" then return {} @@ -6524,12 +6857,9 @@ function parsers.array_to_string(a,separator) return "" end end -function parsers.settings_to_set(str,t) - t=t or {} - for s in gmatch(str,"[^, ]+") do - t[s]=true - end - return t +local pattern=Cf(Ct("")*Cg(C((1-S(", "))^1)*S(", ")^0*Cc(true))^1,rawset) +function utilities.parsers.settings_to_set(str,t) + return str and lpegmatch(pattern,str) or {} end function parsers.simple_hash_to_string(h,separator) local t,tn={},0 @@ -6541,12 +6871,16 @@ function parsers.simple_hash_to_string(h,separator) end return concat(t,separator or ",") end -local str=C((1-whitespace-equal)^1) +local str=Cs(lpegpatterns.unquoted)+C((1-whitespace-equal)^1) local setting=Cf(Carg(1)*(whitespace^0*Cg(str*whitespace^0*(equal*whitespace^0*str+Cc(""))))^1,rawset) local splitter=setting^1 function utilities.parsers.options_to_hash(str,target) return str and lpegmatch(splitter,str,1,target or {}) or {} end +local splitter=lpeg.tsplitat(" ") +function utilities.parsers.options_to_array(str) + return str and lpegmatch(splitter,str) or {} +end local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C(digit^1*lparent*(noparent+nestedparents)^1*rparent)+C((nestedbraces+(1-comma))^1) local pattern_a=spaces*Ct(value*(separator*value)^0) local function repeater(n,str) @@ -6668,7 +7002,7 @@ function parsers.rfc4180splitter(specification) local field=escaped+non_escaped+Cc("") local record=Ct(field*(separator*field)^1) local headerline=record*Cp() - local wholeblob=Ct((newline^-1*record)^0) + local wholeblob=Ct((newline^(specification.strict and -1 or 1)*record)^0) return function(data,getheader) if getheader then local header,position=lpegmatch(headerline,data) @@ -6705,13 +7039,13 @@ function parsers.stepper(str,n,action) lpegmatch(stepper,str,1,n,action or print) end end -local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) -local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) +local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) +local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) patterns.unittotex=pattern function parsers.unittotex(str,textmode) return lpegmatch(textmode and pattern_text or pattern_math,str) end -local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+P(1))^0) +local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+anything)^0) function parsers.unittoxml(str) return lpegmatch(pattern,str) end @@ -6783,6 +7117,18 @@ function utilities.parsers.runtime(time) local seconds=mod(time,60) return days,hours,minutes,seconds end +local spacing=whitespace^0 +local apply=P("->") +local method=C((1-apply)^1) +local token=lbrace*C((1-rbrace)^1)*rbrace+C(anything^1) +local pattern=spacing*(method*spacing*apply+Carg(1))*spacing*token +function utilities.parsers.splitmethod(str,default) + if str then + return lpegmatch(pattern,str,1,default or false) + else + return default or false,"" + end +end end -- of closure @@ -6872,7 +7218,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-set"] = package.loaded["trac-set"] or true --- original size: 12365, stripped down to: 8799 +-- original size: 12482, stripped down to: 8864 if not modules then modules={} end modules ['trac-set']={ version=1.001, @@ -6900,7 +7246,7 @@ function setters.initialize(filename,name,values) local data=setter.data if data then for key,newvalue in next,values do - local newvalue=is_boolean(newvalue,newvalue) + local newvalue=is_boolean(newvalue,newvalue,true) local functions=data[key] if functions then local oldvalue=functions.value @@ -6954,7 +7300,7 @@ local function set(t,what,newvalue) elseif not value then value=false else - value=is_boolean(value,value) + value=is_boolean(value,value,true) end w=topattern(w,true,true) for name,functions in next,data do @@ -7093,6 +7439,7 @@ function setters.new(name) report=function(...) setters.report (setter,...) end, enable=function(...) enable (setter,...) end, disable=function(...) disable (setter,...) end, + reset=function(...) reset (setter,...) end, register=function(...) register(setter,...) end, list=function(...) list (setter,...) end, show=function(...) show (setter,...) end, @@ -7184,7 +7531,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-log"] = package.loaded["trac-log"] or true --- original size: 25613, stripped down to: 16617 +-- original size: 29359, stripped down to: 20483 if not modules then modules={} end modules ['trac-log']={ version=1.001, @@ -7193,15 +7540,18 @@ if not modules then modules={} end modules ['trac-log']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } +local next,type,select,print=next,type,select,print local write_nl,write=texio and texio.write_nl or print,texio and texio.write or io.write local format,gmatch,find=string.format,string.gmatch,string.find local concat,insert,remove=table.concat,table.insert,table.remove local topattern=string.topattern -local next,type,select=next,type,select local utfchar=utf.char +local datetime=os.date +local openfile=io.open local setmetatableindex=table.setmetatableindex local formatters=string.formatters local texgetcount=tex and tex.getcount +local variant="default" logs=logs or {} local logs=logs local moreinfo=[[ @@ -7211,32 +7561,122 @@ maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context webpage : http://www.pragma-ade.nl / http://tex.aanhet.net wiki : http://contextgarden.net ]] -utilities.strings.formatters.add ( +formatters.add ( formatters,"unichr", [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]] ) -utilities.strings.formatters.add ( +formatters.add ( formatters,"chruni", [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]] ) local function ignore() end setmetatableindex(logs,function(t,k) t[k]=ignore;return ignore end) local report,subreport,status,settarget,setformats,settranslations -local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters +local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters,newline if tex and (tex.jobname or tex.formatname) then - local valueiskey={ __index=function(t,k) t[k]=k return k end } - local target="term and log" + local function useluawrites() + local texio_write_nl=texio.write_nl + local texio_write=texio.write + local io_write=io.write + write_nl=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write_nl("log",...) + texio_write_nl("term","") + io_write(...) + elseif target=="log" then + texio_write_nl("log",...) + elseif target=="term" then + texio_write_nl("term","") + io_write(...) + elseif target~="none" then + texio_write_nl("log",target,...) + texio_write_nl("term","") + io_write(target,...) + end + end + write=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write("log",...) + io_write(...) + elseif target=="log" then + texio_write("log",...) + elseif target=="term" then + io_write(...) + elseif target~="none" then + texio_write("log",target,...) + io_write(target,...) + end + end + texio.write=write + texio.write_nl=write_nl + useluawrites=ignore + end + local whereto="both" + local target=nil + local targets=nil + local formats=table.setmetatableindex("self") + local translations=table.setmetatableindex("self") + local report_yes,subreport_yes,direct_yes,subdirect_yes,status_yes + local report_nop,subreport_nop,direct_nop,subdirect_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s > %s\n"], + report_nop=formatters["%-15s >\n"], + direct_yes=formatters["%-15s > %s"], + direct_nop=formatters["%-15s >"], + subreport_yes=formatters["%-15s > %s > %s\n"], + subreport_nop=formatters["%-15s > %s >\n"], + subdirect_yes=formatters["%-15s > %s > %s"], + subdirect_nop=formatters["%-15s > %s >"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + targets={ + logfile="log", + log="log", + file="log", + console="term", + terminal="term", + both="term and log", + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;33m%-15s [0;1m>[0m %s\n"], + report_nop=formatters["[0;33m%-15s [0;1m>[0m\n"], + direct_yes=formatters["[0;33m%-15s [0;1m>[0m %s"], + direct_nop=formatters["[0;33m%-15s [0;1m>[0m"], + subreport_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"], + subreport_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"], + subdirect_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"], + subdirect_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"], + status_yes=formatters["[0;33m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;33m%-15s [0;1m:[0m\n"], + }, + targets={ + logfile="none", + log="none", + file="none", + console="term", + terminal="term", + both="term", + }, + } + } logs.flush=io.flush - local formats={} setmetatable(formats,valueiskey) - local translations={} setmetatable(translations,valueiskey) writer=function(...) write_nl(target,...) end newline=function() write_nl(target,"\n") end - local report_yes=formatters["%-15s > %s\n"] - local report_nop=formatters["%-15s >\n"] report=function(a,b,c,...) if c then write_nl(target,report_yes(translations[a],formatters[formats[b]](c,...))) @@ -7248,8 +7688,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local direct_yes=formatters["%-15s > %s"] - local direct_nop=formatters["%-15s >"] direct=function(a,b,c,...) if c then return direct_yes(translations[a],formatters[formats[b]](c,...)) @@ -7261,8 +7699,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local subreport_yes=formatters["%-15s > %s > %s\n"] - local subreport_nop=formatters["%-15s > %s >\n"] subreport=function(a,s,b,c,...) if c then write_nl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...))) @@ -7274,8 +7710,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local subdirect_yes=formatters["%-15s > %s > %s"] - local subdirect_nop=formatters["%-15s > %s >"] subdirect=function(a,s,b,c,...) if c then return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...)) @@ -7287,8 +7721,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(target,status_yes(translations[a],formatters[formats[b]](c,...))) @@ -7300,16 +7732,13 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local targets={ - logfile="log", - log="log", - file="log", - console="term", - terminal="term", - both="term and log", - } - settarget=function(whereto) - target=targets[whereto or "both"] or targets.both + settarget=function(askedwhereto) + whereto=askedwhereto or whereto or "both" + target=targets[whereto] + if not target then + whereto="both" + target=targets[whereto] + end if target=="term" or target=="term and log" then logs.flush=io.flush else @@ -7338,21 +7767,74 @@ if tex and (tex.jobname or tex.formatname) then writeline(target,f(...)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - direct_yes=f.direct_yes or direct_yes - direct_nop=f.direct_nop or direct_nop - subdirect_yes=f.subdirect_yes or subdirect_yes - subdirect_nop=f.subdirect_nop or subdirect_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local t=nil + local f=nil + local d=variants.default + if not specification then + elseif type(specification)=="table" then + t=specification.targets + f=specification.formats or specification + else + local v=variants[specification] + if v then + t=v.targets + f=v.formats + variant=specification + end + end + targets=t or d.targets + target=targets[whereto] or target + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + direct_yes=f.direct_yes + direct_nop=f.direct_nop + subdirect_yes=f.subdirect_yes + subdirect_nop=f.subdirect_nop + status_yes=f.status_yes + status_nop=f.status_nop + if variant=="ansi" then + useluawrites() + end + settarget(whereto) + end + setformatters(variant) setlogfile=ignore settimedlog=ignore else + local report_yes,subreport_yes,status_yes + local report_nop,subreport_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s | %s"], + report_nop=formatters["%-15s |"], + subreport_yes=formatters["%-15s | %s | %s"], + subreport_nop=formatters["%-15s | %s |"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;32m%-15s [0;1m|[0m %s"], + report_nop=formatters["[0;32m%-15s [0;1m|[0m"], + subreport_yes=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m %s"], + subreport_nop=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m"], + status_yes=formatters["[0;32m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;32m%-15s [0;1m:[0m\n"], + }, + }, + } logs.flush=ignore writer=function(s) write_nl(s) @@ -7360,8 +7842,6 @@ else newline=function() write_nl("\n") end - local report_yes=formatters["%-15s | %s"] - local report_nop=formatters["%-15s |"] report=function(a,b,c,...) if c then write_nl(report_yes(a,formatters[b](c,...))) @@ -7373,8 +7853,6 @@ else write_nl("") end end - local subreport_yes=formatters["%-15s | %s | %s"] - local subreport_nop=formatters["%-15s | %s |"] subreport=function(a,sub,b,c,...) if c then write_nl(subreport_yes(a,sub,formatters[b](c,...))) @@ -7386,8 +7864,6 @@ else write_nl("") end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(status_yes(a,formatters[b](c,...))) @@ -7412,14 +7888,34 @@ else writeline(f(s)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local f=nil + local d=variants.default + if specification then + if type(specification)=="table" then + f=specification.formats or specification + else + local v=variants[specification] + if v then + f=v.formats + end + end + end + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + status_yes=f.status_yes + status_nop=f.status_nop + end + setformatters(variant) setlogfile=function(name,keepopen) if name and name~="" then local localtime=os.localtime @@ -7672,13 +8168,13 @@ end local simple=logs.reporter("comment") logs.simple=simple logs.simpleline=simple -function logs.setprogram () end -function logs.extendbanner() end -function logs.reportlines () end -function logs.reportbanner() end -function logs.reportline () end -function logs.simplelines () end -function logs.help () end +logs.setprogram=ignore +logs.extendbanner=ignore +logs.reportlines=ignore +logs.reportbanner=ignore +logs.reportline=ignore +logs.simplelines=ignore +logs.help=ignore local Carg,C,lpegmatch=lpeg.Carg,lpeg.C,lpeg.match local p_newline=lpeg.patterns.newline local linewise=( @@ -7755,10 +8251,11 @@ function logs.application(t) end return t end -function logs.system(whereto,process,jobname,category,...) - local message=formatters["%s %s => %s => %s => %s\r"](os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)) +local f_syslog=formatters["%s %s => %s => %s => %s\r"] +function logs.system(whereto,process,jobname,category,fmt,arg,...) + local message=f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg==nil and fmt or format(fmt,arg,...)) for i=1,10 do - local f=io.open(whereto,"a") + local f=openfile(whereto,"a") if f then f:write(message) f:close() @@ -7820,7 +8317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-inf"] = package.loaded["trac-inf"] or true --- original size: 7011, stripped down to: 5590 +-- original size: 6704, stripped down to: 5343 if not modules then modules={} end modules ['trac-inf']={ version=1.001, @@ -7993,15 +8490,6 @@ function statistics.tracefunction(base,tag,...) statistics.register(formatters["%s.%s"](tag,name),function() return serialize(stat,"calls") end) end end -commands=commands or {} -function commands.resettimer(name) - resettiming(name or "whatever") - starttiming(name or "whatever") -end -function commands.elapsedtime(name) - stoptiming(name or "whatever") - context(elapsedtime(name or "whatever")) -end end -- of closure @@ -8568,7 +9056,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tpl"] = package.loaded["util-tpl"] or true --- original size: 6251, stripped down to: 3488 +-- original size: 7100, stripped down to: 3978 if not modules then modules={} end modules ['util-tpl']={ version=1.001, @@ -8610,7 +9098,7 @@ local sqlescape=lpeg.replacer { { "\r\n","\\n" }, { "\r","\\n" }, } -local sqlquoted=lpeg.Cs(lpeg.Cc("'")*sqlescape*lpeg.Cc("'")) +local sqlquoted=Cs(Cc("'")*sqlescape*Cc("'")) lpegpatterns.sqlescape=sqlescape lpegpatterns.sqlquoted=sqlquoted local luaescape=lpegpatterns.luaescape @@ -8633,12 +9121,24 @@ local quotedescapers={ local luaescaper=escapers.lua local quotedluaescaper=quotedescapers.lua local function replacekeyunquoted(s,t,how,recurse) - local escaper=how and escapers[how] or luaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and escapers[how] or luaescaper + return escaper(replacekey(s,t,how,recurse)) + end end local function replacekeyquoted(s,t,how,recurse) - local escaper=how and quotedescapers[how] or quotedluaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and quotedescapers[how] or quotedluaescaper + return escaper(replacekey(s,t,how,recurse)) + end +end +local function replaceoptional(l,m,r,t,how,recurse) + local v=t[l] + return v and v~="" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or "" end local single=P("%") local double=P("%%") @@ -8653,11 +9153,16 @@ local nolquoted=lquoted/'' local norquoted=rquoted/'' local nolquotedq=lquotedq/'' local norquotedq=rquotedq/'' -local key=nosingle*((C((1-nosingle )^1)*Carg(1)*Carg(2)*Carg(3))/replacekey )*nosingle -local quoted=nolquotedq*((C((1-norquotedq)^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyquoted )*norquotedq -local unquoted=nolquoted*((C((1-norquoted )^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyunquoted)*norquoted +local noloptional=P("%?")/'' +local noroptional=P("?%")/'' +local nomoptional=P(":")/'' +local args=Carg(1)*Carg(2)*Carg(3) +local key=nosingle*((C((1-nosingle )^1)*args)/replacekey )*nosingle +local quoted=nolquotedq*((C((1-norquotedq )^1)*args)/replacekeyquoted )*norquotedq +local unquoted=nolquoted*((C((1-norquoted )^1)*args)/replacekeyunquoted)*norquoted +local optional=noloptional*((C((1-nomoptional)^1)*nomoptional*C((1-noroptional)^1)*args)/replaceoptional)*noroptional local any=P(1) - replacer=Cs((unquoted+quoted+escape+key+any)^0) + replacer=Cs((unquoted+quoted+escape+optional+key+any)^0) local function replace(str,mapping,how,recurse) if mapping and str then return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str @@ -8696,7 +9201,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-env"] = package.loaded["util-env"] or true --- original size: 8814, stripped down to: 5092 +-- original size: 8022, stripped down to: 5038 if not modules then modules={} end modules ['util-env']={ version=1.001, @@ -8707,7 +9212,7 @@ if not modules then modules={} end modules ['util-env']={ } local allocate,mark=utilities.storage.allocate,utilities.storage.mark local format,sub,match,gsub,find=string.format,string.sub,string.match,string.gsub,string.find -local unquoted,quoted=string.unquoted,string.quoted +local unquoted,quoted,optionalquoted=string.unquoted,string.quoted,string.optionalquoted local concat,insert,remove=table.concat,table.insert,table.remove environment=environment or {} local environment=environment @@ -8820,24 +9325,14 @@ function environment.splitarguments(separator) return before,after end function environment.reconstructcommandline(arg,noquote) + local resolveprefix=resolvers.resolve arg=arg or environment.originalarguments if noquote and #arg==1 then - local a=arg[1] - a=resolvers.resolve(a) - a=unquoted(a) - return a + return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1]) elseif #arg>0 then local result={} for i=1,#arg do - local a=arg[i] - a=resolvers.resolve(a) - a=unquoted(a) - a=gsub(a,'"','\\"') - if find(a," ",1,true) then - result[#result+1]=quoted(a) - else - result[#result+1]=a - end + result[i]=optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix) end return concat(result," ") else @@ -9046,7 +9541,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-tab"] = package.loaded["lxml-tab"] or true --- original size: 42614, stripped down to: 26694 +-- original size: 43882, stripped down to: 26870 if not modules then modules={} end modules ['lxml-tab']={ version=1.001, @@ -9061,10 +9556,10 @@ if lpeg.setmaxstack then lpeg.setmaxstack(1000) end xml=xml or {} local xml=xml local concat,remove,insert=table.concat,table.remove,table.insert -local type,next,setmetatable,getmetatable,tonumber=type,next,setmetatable,getmetatable,tonumber +local type,next,setmetatable,getmetatable,tonumber,rawset=type,next,setmetatable,getmetatable,tonumber,rawset local lower,find,match,gsub=string.lower,string.find,string.match,string.gsub local utfchar=utf.char -local lpegmatch=lpeg.match +local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local P,S,R,C,V,C,Cs=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.V,lpeg.C,lpeg.Cs local formatters=string.formatters xml.xmlns=xml.xmlns or {} @@ -9226,9 +9721,10 @@ local function fromdec(s) return formatters["d:%s"](s),true end end -local rest=(1-P(";"))^0 -local many=P(1)^0 -local parsedentity=P("&")*(P("#x")*(rest/fromhex)+P("#")*(rest/fromdec))*P(";")*P(-1)+(P("#x")*(many/fromhex)+P("#")*(many/fromdec)) +local p_rest=(1-P(";"))^0 +local p_many=P(1)^0 +local p_char=lpegpatterns.utf8character +local parsedentity=P("&")*(P("#x")*(p_rest/fromhex)+P("#")*(p_rest/fromdec))*P(";")*P(-1)+(P("#x")*(p_many/fromhex)+P("#")*(p_many/fromdec)) local predefined_unified={ [38]="&", [42]=""", @@ -9254,7 +9750,9 @@ local privates_u={ local privates_p={} local privates_n={ } -local escaped=utf.remapper(privates_u) +local escaped=utf.remapper(privates_u,"dynamic") +local unprivatized=utf.remapper(privates_p,"dynamic") +xml.unprivatized=unprivatized local function unescaped(s) local p=privates_n[s] if not p then @@ -9267,9 +9765,7 @@ local function unescaped(s) end return p end -local unprivatized=utf.remapper(privates_p) xml.privatetoken=unescaped -xml.unprivatized=unprivatized xml.privatecodes=privates_n local function handle_hex_entity(str) local h=hcache[str] @@ -9422,7 +9918,7 @@ local valid=R('az','AZ','09')+S('_-.') local name_yes=C(valid^1)*colon*C(valid^1) local name_nop=C(P(true))*C(valid^1) local name=name_yes+name_nop -local utfbom=lpeg.patterns.utfbom +local utfbom=lpegpatterns.utfbom local spacing=C(space^0) local anyentitycontent=(1-open-semicolon-space-close)^0 local hexentitycontent=R("AF","af","09")^0 @@ -9590,7 +10086,7 @@ local function _xmlconvert_(data,settings) reported_attribute_errors,mt,errorhandler=nil,nil,nil return result end -function xmlconvert(data,settings) +local function xmlconvert(data,settings) local ok,result=pcall(function() return _xmlconvert_(data,settings) end) if ok then return result @@ -9682,14 +10178,17 @@ function xml.checkbom(root) insert(dt,2,"\n" ) end end -local function verbose_element(e,handlers) +local f_attribute=formatters['%s=%q'] +local function verbose_element(e,handlers,escape) local handle=handlers.handle local serialize=handlers.serialize local ens,etg,eat,edt,ern=e.ns,e.tg,e.at,e.dt,e.rn local ats=eat and next(eat) and {} if ats then + local n=0 for k,v in next,eat do - ats[#ats+1]=formatters['%s=%q'](k,escaped(v)) + n=n+1 + ats[n]=f_attribute(k,escaped(v)) end end if ern and trace_entities and ern~=ens then @@ -10033,7 +10532,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48030, stripped down to: 30595 +-- original size: 48229, stripped down to: 30684 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -10418,8 +10917,8 @@ local lp_builtin=P ( P("ns")/"ll.ns" )*((spaces*P("(")*spaces*P(")"))/"") local lp_attribute=(P("@")+P("attribute::"))/""*Cc("(ll.at and ll.at['")*((R("az","AZ")+S("-_:"))^1)*Cc("'])") -lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" -lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" +local lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" +local lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" local lp_fastpos=lp_fastpos_n+lp_fastpos_p local lp_reserved=C("and")+C("or")+C("not")+C("div")+C("mod")+C("true")+C("false") local lp_lua_function=Cs((R("az","AZ","__")^1*(P(".")*R("az","AZ","__")^1)^1)*("("))/"%0" @@ -10598,7 +11097,7 @@ local function nodesettostring(set,nodetest) if not ns or ns=="" then ns="*" end if not tg or tg=="" then tg="*" end tg=(tg=="@rt@" and "[root]") or format("%s:%s",ns,tg) - t[i]=(directive and tg) or format("not(%s)",tg) + t[#t+1]=(directive and tg) or format("not(%s)",tg) end if nodetest==false then return format("not(%s)",concat(t,"|")) @@ -10864,7 +11363,6 @@ expressions.print=function(...) print(...) return true end -expressions.contains=find expressions.find=find expressions.upper=upper expressions.lower=lower @@ -10886,6 +11384,9 @@ function expressions.contains(str,pattern) end return false end +function xml.expressions.idstring(str) + return type(str)=="string" and gsub(str,"^#","") or "" +end local function traverse(root,pattern,handle) local collected=applylpath(root,pattern) if collected then @@ -11173,7 +11674,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-aux"] = package.loaded["lxml-aux"] or true --- original size: 23804, stripped down to: 16817 +-- original size: 28225, stripped down to: 20125 if not modules then modules={} end modules ['lxml-aux']={ version=1.001, @@ -11183,16 +11684,19 @@ if not modules then modules={} end modules ['lxml-aux']={ license="see context related readme files" } local trace_manipulations=false trackers.register("lxml.manipulations",function(v) trace_manipulations=v end) +local trace_inclusions=false trackers.register("lxml.inclusions",function(v) trace_inclusions=v end) local report_xml=logs.reporter("xml") local xml=xml -local xmlconvert,xmlcopy,xmlname=xml.convert,xml.copy,xml.name +local xmlcopy,xmlname=xml.copy,xml.name local xmlinheritedconvert=xml.inheritedconvert local xmlapplylpath=xml.applylpath local xmlfilter=xml.filter -local type,setmetatable,getmetatable=type,setmetatable,getmetatable +local type,next,setmetatable,getmetatable=type,next,setmetatable,getmetatable local insert,remove,fastcopy,concat=table.insert,table.remove,table.fastcopy,table.concat local gmatch,gsub,format,find,strip=string.gmatch,string.gsub,string.format,string.find,string.strip local utfbyte=utf.byte +local lpegmatch=lpeg.match +local striplinepatterns=utilities.strings.striplinepatterns local function report(what,pattern,c,e) report_xml("%s element %a, root %a, position %a, index %a, pattern %a",what,xmlname(e),xmlname(e.__p__),c,e.ni,pattern) end @@ -11247,13 +11751,15 @@ end function xml.each(root,pattern,handle,reverse) local collected=xmlapplylpath(root,pattern) if collected then - if reverse then - for c=#collected,1,-1 do - handle(collected[c]) - end - else - for c=1,#collected do - handle(collected[c]) + if handle then + if reverse then + for c=#collected,1,-1 do + handle(collected[c]) + end + else + for c=1,#collected do + handle(collected[c]) + end end end return collected @@ -11309,6 +11815,7 @@ local function redo_ni(d) end end end +xml.reindex=redo_ni local function xmltoelement(whatever,root) if not whatever then return nil @@ -11360,8 +11867,16 @@ function xml.delete(root,pattern) report('deleting',pattern,c,e) end local d=p.dt - remove(d,e.ni) - redo_ni(d) + local ni=e.ni + if ni<=#d then + if false then + p.dt[ni]="" + else + remove(d,ni) + redo_ni(d) + end + else + end end end end @@ -11481,28 +11996,40 @@ xml.insertafter=insert_element xml.insertbefore=function(r,p,e) insert_element(r,p,e,true) end xml.injectafter=inject_element xml.injectbefore=function(r,p,e) inject_element(r,p,e,true) end -local function include(xmldata,pattern,attribute,recursive,loaddata) +local function include(xmldata,pattern,attribute,recursive,loaddata,level) pattern=pattern or 'include' loaddata=loaddata or io.loaddata local collected=xmlapplylpath(xmldata,pattern) if collected then + if not level then + level=1 + end for c=1,#collected do local ek=collected[c] local name=nil local ekdt=ek.dt local ekat=ek.at - local epdt=ek.__p__.dt + local ekrt=ek.__p__ + local epdt=ekrt.dt if not attribute or attribute=="" then name=(type(ekdt)=="table" and ekdt[1]) or ekdt end if not name then for a in gmatch(attribute or "href","([^|]+)") do name=ekat[a] - if name then break end + if name then + break + end + end + end + local data=nil + if name and name~="" then + data=loaddata(name) or "" + if trace_inclusions then + report_xml("including %s bytes from %a at level %s by pattern %a and attribute %a (%srecursing)",#data,name,level,pattern,attribute or "",recursive and "" or "not ") end end - local data=(name and name~="" and loaddata(name)) or "" - if data=="" then + if not data or data=="" then epdt[ek.ni]="" elseif ekat["parse"]=="text" then epdt[ek.ni]=xml.escaped(data) @@ -11512,70 +12039,113 @@ local function include(xmldata,pattern,attribute,recursive,loaddata) epdt[ek.ni]="" else if recursive then - include(xi,pattern,attribute,recursive,loaddata) + include(xi,pattern,attribute,recursive,loaddata,level+1) + end + local child=xml.body(xi) + child.__p__=ekrt + child.__f__=name + epdt[ek.ni]=child + local inclusions=xmldata.settings.inclusions + if inclusions then + inclusions[#inclusions+1]=name + else + xmldata.settings.inclusions={ name } end - epdt[ek.ni]=xml.body(xi) end end end end end xml.include=include +function xml.inclusion(e,default) + while e do + local f=e.__f__ + if f then + return f + else + e=e.__p__ + end + end + return default +end +function xml.inclusions(e,sorted) + while e do + local settings=e.settings + if settings then + local inclusions=settings.inclusions + if inclusions then + inclusions=table.unique(inclusions) + if sorted then + table.sort(inclusions) + end + return inclusions + else + e=e.__p__ + end + else + e=e.__p__ + end + end +end +local b_collapser=lpeg.patterns.b_collapser +local m_collapser=lpeg.patterns.m_collapser +local e_collapser=lpeg.patterns.e_collapser +local b_stripper=lpeg.patterns.b_stripper +local m_stripper=lpeg.patterns.m_stripper +local e_stripper=lpeg.patterns.e_stripper +local lpegmatch=lpeg.match local function stripelement(e,nolines,anywhere) local edt=e.dt if edt then - if anywhere then - local t,n={},0 - for e=1,#edt do + local n=#edt + if n==0 then + return e + elseif anywhere then + local t={} + local m=0 + for e=1,n do local str=edt[e] if type(str)~="string" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str elseif str~="" then if nolines then - str=gsub(str,"%s+"," ") + str=lpegmatch((n==1 and b_collapser) or (n==m and e_collapser) or m_collapser,str) + else + str=lpegmatch((n==1 and b_stripper) or (n==m and e_stripper) or m_stripper,str) end - str=gsub(str,"^%s*(.-)%s*$","%1") if str~="" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str end end end e.dt=t else - if #edt>0 then - local str=edt[1] - if type(str)~="string" then - elseif str=="" then + local str=edt[1] + if type(str)=="string" then + if str~="" then + str=lpegmatch(nolines and b_collapser or b_stripper,str) + end + if str=="" then remove(edt,1) + n=n-1 else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"^%s+","") - if str=="" then - remove(edt,1) - else - edt[1]=str - end + edt[1]=str end end - local nedt=#edt - if nedt>0 then - local str=edt[nedt] - if type(str)~="string" then - elseif str=="" then - remove(edt) - else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"%s+$","") + if n>0 then + str=edt[n] + if type(str)=="string" then if str=="" then remove(edt) else - edt[nedt]=str + str=lpegmatch(nolines and e_collapser or e_stripper,str) + if str=="" then + remove(edt) + else + edt[n]=str + end end end end @@ -11761,8 +12331,8 @@ function xml.finalizers.xml.cdata(collected) end return "" end -function xml.insertcomment(e,str,n) - table.insert(e.dt,n or 1,{ +function xml.insertcomment(e,str,n) + insert(e.dt,n or 1,{ tg="@cm@", ns="", special=true, @@ -11770,7 +12340,25 @@ function xml.insertcomment(e,str,n) dt={ str }, }) end -function xml.setcdata(e,str) +function xml.insertcdata(e,str,n) + insert(e.dt,n or 1,{ + tg="@cd@", + ns="", + special=true, + at={}, + dt={ str }, + }) +end +function xml.setcomment(e,str,n) + e.dt={ { + tg="@cm@", + ns="", + special=true, + at={}, + dt={ str }, + } } +end +function xml.setcdata(e,str) e.dt={ { tg="@cd@", ns="", @@ -11840,7 +12428,7 @@ local function recurse(e,action) for i=1,#edt do local str=edt[i] if type(str)~="string" then - recurse(str,action,recursive) + recurse(str,action) elseif str~="" then edt[i]=action(str) end @@ -11858,6 +12446,65 @@ function helpers.recursetext(collected,action,recursive) end end end +local specials={ + ["@rt@"]="root", + ["@pi@"]="instruction", + ["@cm@"]="comment", + ["@dt@"]="declaration", + ["@cd@"]="cdata", +} +local function convert(x,strip,flat) + local ns=x.ns + local tg=x.tg + local at=x.at + local dt=x.dt + local node=flat and { + [0]=(not x.special and (ns~="" and ns..":"..tg or tg)) or nil, + } or { + _namespace=ns~="" and ns or nil, + _tag=not x.special and tg or nil, + _type=specials[tg] or "_element", + } + if at then + for k,v in next,at do + node[k]=v + end + end + local n=0 + for i=1,#dt do + local di=dt[i] + if type(di)=="table" then + if flat and di.special then + else + di=convert(di,strip,flat) + if di then + n=n+1 + node[n]=di + end + end + elseif strip then + di=lpegmatch(strip,di) + if di~="" then + n=n+1 + node[n]=di + end + else + n=n+1 + node[n]=di + end + end + if next(node) then + return node + end +end +function xml.totable(x,strip,flat) + if type(x)=="table" then + if strip then + strip=striplinepatterns[strip] + end + return convert(x,strip,flat) + end +end end -- of closure @@ -12414,7 +13061,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-ini"] = package.loaded["data-ini"] or true --- original size: 7927, stripped down to: 5528 +-- original size: 11085, stripped down to: 7662 if not modules then modules={} end modules ['data-ini']={ version=1.001, @@ -12423,14 +13070,15 @@ if not modules then modules={} end modules ['data-ini']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files", } +local next,type,getmetatable,rawset=next,type,getmetatable,rawset local gsub,find,gmatch,char=string.gsub,string.find,string.gmatch,string.char -local next,type=next,type local filedirname,filebasename,filejoin=file.dirname,file.basename,file.join +local ostype,osname,osuname,ossetenv,osgetenv=os.type,os.name,os.uname,os.setenv,os.getenv +local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers.register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) local report_initialization=logs.reporter("resolvers","initialization") -local ostype,osname,ossetenv,osgetenv=os.type,os.name,os.setenv,os.getenv resolvers=resolvers or {} local resolvers=resolvers texconfig.kpse_init=false @@ -12563,10 +13211,103 @@ if type(profiler)=="table" and not jit then profiler.start("luatex-profile.log") end) end -if not resolvers.resolve then - function resolvers.resolve (s) return s end - function resolvers.unresolve(s) return s end - function resolvers.repath (s) return s end +local prefixes=utilities.storage.allocate() +resolvers.prefixes=prefixes +local resolved={} +local abstract={} +local dynamic={} +function resolvers.resetresolve(str) + resolved,abstract={},{} +end +function resolvers.allprefixes(separator) + local all=table.sortedkeys(prefixes) + if separator then + for i=1,#all do + all[i]=all[i]..":" + end + end + return all +end +local function _resolve_(method,target) + local action=prefixes[method] + if action then + return action(target) + else + return method..":"..target + end +end +function resolvers.unresolve(str) + return abstract[str] or str +end +function resolvers.setdynamic(str) + dynamic[str]=true +end +local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) +local prefix=C(R("az")^2)*P(":") +local target=C((1-S(" \"\';,"))^1) +local notarget=(#S(";,")+P(-1))*Cc("") +local p_resolve=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) +local p_simple=prefix*P(-1) +local function resolve(str) + if type(str)=="table" then + local res={} + for i=1,#str do + res[i]=resolve(str[i]) + end + return res + end + local res=resolved[str] + if res then + return res + end + local simple=lpegmatch(p_simple,str) + local action=prefixes[simple] + if action then + local res=action(res) + if not dynamic[simple] then + resolved[simple]=res + abstract[res]=simple + end + return res + end + res=lpegmatch(p_resolve,str) + resolved[str]=res + abstract[res]=str + return res +end +resolvers.resolve=resolve +if type(osuname)=="function" then + for k,v in next,osuname() do + if not prefixes[k] then + prefixes[k]=function() return v end + end + end +end +if ostype=="unix" then + local pattern + local function makepattern(t,k,v) + if t then + rawset(t,k,v) + end + local colon=P(":") + for k,v in table.sortedpairs(prefixes) do + if p then + p=P(k)+p + else + p=P(k) + end + end + pattern=Cs((p*colon+colon/";"+P(1))^0) + end + makepattern() + table.setmetatablenewindex(prefixes,makepattern) + function resolvers.repath(str) + return lpegmatch(pattern,str) + end +else + function resolvers.repath(str) + return str + end end @@ -12576,7 +13317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-exp"] = package.loaded["data-exp"] or true --- original size: 15317, stripped down to: 9723 +-- original size: 17216, stripped down to: 10657 if not modules then modules={} end modules ['data-exp']={ version=1.001, @@ -12590,12 +13331,16 @@ local concat,sort=table.concat,table.sort local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local Ct,Cs,Cc,Carg,P,C,S=lpeg.Ct,lpeg.Cs,lpeg.Cc,lpeg.Carg,lpeg.P,lpeg.C,lpeg.S local type,next=type,next +local isdir=lfs.isdir local ostype=os.type -local collapsepath=file.collapsepath +local collapsepath,joinpath,basename=file.collapsepath,file.join,file.basename local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_globbing=true trackers.register("resolvers.globbing",function(v) trace_globbing=v end) local report_expansions=logs.reporter("resolvers","expansions") +local report_globbing=logs.reporter("resolvers","globbing") local resolvers=resolvers +local resolveprefix=resolvers.resolve local function f_both(a,b) local t,n={},0 for sb in gmatch(b,"[^,]+") do @@ -12685,35 +13430,27 @@ function resolvers.expandedpathfromlist(pathlist) end return newlist end -local cleanup=lpeg.replacer { - { "!","" }, - { "\\","/" }, -} -function resolvers.cleanpath(str) - local doslashes=(P("\\")/"/"+1)^0 - local donegation=(P("!")/"" )^0 - local homedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") - if homedir=="~" or homedir=="" or not lfs.isdir(homedir) then - if trace_expansions then - report_expansions("no home dir set, ignoring dependent paths") - end - function resolvers.cleanpath(str) - if not str or find(str,"~",1,true) then - return "" - else - return lpegmatch(cleanup,str) +local usedhomedir=nil +local donegation=(P("!")/"" )^0 +local doslashes=(P("\\")/"/"+1)^0 +local function expandedhome() + if not usedhomedir then + usedhomedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") + if usedhomedir=="~" or usedhomedir=="" or not isdir(usedhomedir) then + if trace_expansions then + report_expansions("no home dir set, ignoring dependent path using current path") end - end - else - local dohome=((P("~")+P("$HOME"))/homedir)^0 - local cleanup=Cs(donegation*dohome*doslashes) - function resolvers.cleanpath(str) - return str and lpegmatch(cleanup,str) or "" + usedhomedir="." end end - return resolvers.cleanpath(str) + return usedhomedir +end +local dohome=((P("~")+P("$HOME")+P("%HOME%"))/expandedhome)^0 +local cleanup=Cs(donegation*dohome*doslashes) +resolvers.cleanpath=function(str) + return str and lpegmatch(cleanup,str) or "" end -local expandhome=P("~")/"$HOME" +local expandhome=P("~")/"$HOME" local dodouble=P('"')/""*(expandhome+(1-P('"')))^0*P('"')/"" local dosingle=P("'")/""*(expandhome+(1-P("'")))^0*P("'")/"" local dostring=(expandhome+1 )^0 @@ -12765,46 +13502,67 @@ function resolvers.splitpath(str) end function resolvers.joinpath(str) if type(str)=='table' then - return file.joinpath(str) + return joinpath(str) else return str end end local attributes,directory=lfs.attributes,lfs.dir local weird=P(".")^1+lpeg.anywhere(S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t")) +local lessweird=P(".")^1+lpeg.anywhere(S("~`#$%^&*:;\"\'||<>,?\n\r\t")) local timer={} local scanned={} local nofscans=0 local scancache={} -local function scan(files,spec,path,n,m,r) - local full=(path=="" and spec) or (spec..path..'/') +local fullcache={} +local nofsharedscans=0 +local function scan(files,remap,spec,path,n,m,r,onlyone,tolerant) + local full=path=="" and spec or (spec..path..'/') local dirs={} local nofdirs=0 + local pattern=tolerant and lessweird or weird for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then + if not lpegmatch(pattern,name) then + local mode=attributes(full..name,"mode") + if mode=="file" then n=n+1 - local f=files[name] - if f then - if type(f)=='string' then - files[name]={ f,path } + local lower=lower(name) + local paths=files[lower] + if paths then + if onlyone then else - f[#f+1]=path + if type(paths)=="string" then + files[lower]={ paths,path } + else + paths[#paths+1]=path + end + if name~=lower then + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end + end end else - files[name]=path - local lower=lower(name) + files[lower]=path if name~=lower then - files["remap:"..lower]=name - r=r+1 + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end end end - elseif mode=='directory' then + elseif mode=="directory" then m=m+1 nofdirs=nofdirs+1 if path~="" then - dirs[nofdirs]=path..'/'..name + dirs[nofdirs]=path.."/"..name else dirs[nofdirs]=name end @@ -12814,107 +13572,69 @@ local function scan(files,spec,path,n,m,r) if nofdirs>0 then sort(dirs) for i=1,nofdirs do - files,n,m,r=scan(files,spec,dirs[i],n,m,r) + files,remap,n,m,r=scan(files,remap,spec,dirs[i],n,m,r,onlyonce,tolerant) end end scancache[sub(full,1,-2)]=files - return files,n,m,r + return files,remap,n,m,r end -local fullcache={} -function resolvers.scanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) +function resolvers.scanfiles(path,branch,usecache,onlyonce,tolerant) + local realpath=resolveprefix(path) if usecache then - local files=fullcache[realpath] - if files then + local content=fullcache[realpath] + if content then if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) + report_expansions("using cached scan of path %a, branch %a",path,branch or path) end - return files + nofsharedscans=nofsharedscans+1 + return content end end + statistics.starttiming(timer) if trace_locating then report_expansions("scanning path %a, branch %a",path,branch or path) end - local files,n,m,r=scan({},realpath..'/',"",0,0,0) - files.__path__=path - files.__files__=n - files.__directories__=m - files.__remappings__=r - if trace_locating then - report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) - end - if usecache then - scanned[#scanned+1]=realpath - fullcache[realpath]=files - end - nofscans=nofscans+1 - statistics.stoptiming(timer) - return files -end -local function simplescan(files,spec,path) - local full=(path=="" and spec) or (spec..path..'/') - local dirs={} - local nofdirs=0 - for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then - if not files[name] then - files[name]=path - end - elseif mode=='directory' then - nofdirs=nofdirs+1 - if path~="" then - dirs[nofdirs]=path..'/'..name - else - dirs[nofdirs]=name - end - end - end - end - if nofdirs>0 then - sort(dirs) - for i=1,nofdirs do - files=simplescan(files,spec,dirs[i]) - end - end - return files -end -local simplecache={} -local nofsharedscans=0 -function resolvers.simplescanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) - if usecache then - local files=simplecache[realpath] - if not files then - files=scancache[realpath] - if files then - nofsharedscans=nofsharedscans+1 - end + local content + if isdir(realpath) then + local files,remap,n,m,r=scan({},{},realpath..'/',"",0,0,0,onlyonce,tolerant) + content={ + metadata={ + path=path, + files=n, + directories=m, + remappings=r, + }, + files=files, + remap=remap, + } + if trace_locating then + report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) end - if files then - if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) - end - return files + else + content={ + metadata={ + path=path, + files=0, + directories=0, + remappings=0, + }, + files={}, + remap={}, + } + if trace_locating then + report_expansions("invalid path %a",realpath) end end - if trace_locating then - report_expansions("scanning path %a, branch %a",path,branch or path) - end - local files=simplescan({},realpath..'/',"") - if trace_locating then - report_expansions("%s files found",table.count(files)) - end if usecache then scanned[#scanned+1]=realpath - simplecache[realpath]=files + fullcache[realpath]=content end nofscans=nofscans+1 statistics.stoptiming(timer) - return files + return content +end +function resolvers.simplescanfiles(path,branch,usecache) + return resolvers.scanfiles(path,branch,usecache,true,true) end function resolvers.scandata() table.sort(scanned) @@ -12925,6 +13645,52 @@ function resolvers.scandata() paths=scanned, } end +function resolvers.get_from_content(content,path,name) + if not content then + return + end + local files=content.files + if not files then + return + end + local remap=content.remap + if not remap then + return + end + if name then + local used=lower(name) + return path,remap[used] or used + else + local name=path + local used=lower(name) + local path=files[used] + if path then + return path,remap[used] or used + end + end +end +local nothing=function() end +function resolvers.filtered_from_content(content,pattern) + if content and type(pattern)=="string" then + local pattern=lower(pattern) + local files=content.files + local remap=content.remap + if files and remap then + local n=next(files) + local function iterator() + while n do + local k=n + n=next(files,k) + if find(k,pattern) then + return files[k],remap and remap[k] or k + end + end + end + return iterator + end + end + return nothing +end end -- of closure @@ -12933,7 +13699,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-env"] = package.loaded["data-env"] or true --- original size: 8769, stripped down to: 6490 +-- original size: 9216, stripped down to: 6798 if not modules then modules={} end modules ['data-env']={ version=1.001, @@ -12951,10 +13717,12 @@ local formats=allocate() local suffixes=allocate() local dangerous=allocate() local suffixmap=allocate() +local usertypes=allocate() resolvers.formats=formats resolvers.suffixes=suffixes resolvers.dangerous=dangerous resolvers.suffixmap=suffixmap +resolvers.usertypes=usertypes local luasuffixes=utilities.lua.suffixes local relations=allocate { core={ @@ -13022,11 +13790,13 @@ local relations=allocate { names={ "mp" }, variable='MPINPUTS', suffixes={ 'mp','mpvi','mpiv','mpii' }, + usertype=true, }, tex={ names={ "tex" }, variable='TEXINPUTS', - suffixes={ 'tex',"mkvi","mkiv","mkii" }, + suffixes={ "tex","mkvi","mkiv","mkii","cld","lfg","xml" }, + usertype=true, }, icc={ names={ "icc","icc profile","icc profiles" }, @@ -13042,6 +13812,7 @@ local relations=allocate { names={ "lua" }, variable='LUAINPUTS', suffixes={ luasuffixes.lua,luasuffixes.luc,luasuffixes.tma,luasuffixes.tmc }, + usertype=true, }, lib={ names={ "lib" }, @@ -13050,11 +13821,15 @@ local relations=allocate { }, bib={ names={ 'bib' }, + variable='BIBINPUTS', suffixes={ 'bib' }, + usertype=true, }, bst={ names={ 'bst' }, + variable='BSTINPUTS', suffixes={ 'bst' }, + usertype=true, }, fontconfig={ names={ 'fontconfig','fontconfig file','fontconfig files' }, @@ -13136,8 +13911,9 @@ function resolvers.updaterelations() for name,relation in next,categories do local rn=relation.names local rv=relation.variable - local rs=relation.suffixes if rn and rv then + local rs=relation.suffixes + local ru=relation.usertype for i=1,#rn do local rni=lower(gsub(rn[i]," ","")) formats[rni]=rv @@ -13149,8 +13925,9 @@ function resolvers.updaterelations() end end end - end - if rs then + if ru then + usertypes[name]=true + end end end end @@ -13201,7 +13978,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmp"] = package.loaded["data-tmp"] or true --- original size: 15532, stripped down to: 11648 +-- original size: 15681, stripped down to: 11761 if not modules then modules={} end modules ['data-tmp']={ version=1.100, @@ -13220,6 +13997,7 @@ local trace_cache=false trackers.register("resolvers.cache",function(v) trace_ca local report_caches=logs.reporter("resolvers","caches") local report_resolvers=logs.reporter("resolvers","caching") local resolvers=resolvers +local cleanpath=resolvers.cleanpath local directive_cleanup=false directives.register("system.compile.cleanup",function(v) directive_cleanup=v end) local directive_strip=false directives.register("system.compile.strip",function(v) directive_strip=v end) local compile=utilities.lua.compile @@ -13241,7 +14019,7 @@ caches.relocate=false caches.defaults={ "TMPDIR","TEMPDIR","TMP","TEMP","HOME","HOMEPATH" } local writable,readables,usedreadables=nil,{},{} local function identify() - local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") + local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") if texmfcaches then for k=1,#texmfcaches do local cachepath=texmfcaches[k] @@ -13495,10 +14273,12 @@ local content_state={} function caches.contentstate() return content_state or {} end -function caches.loadcontent(cachename,dataname) - local name=caches.hashed(cachename) - local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.loadcontent(cachename,dataname,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local blob=loadfile(addsuffix(filename,luasuffixes.luc)) or loadfile(addsuffix(filename,luasuffixes.lua)) if blob then local data=blob() @@ -13530,10 +14310,12 @@ function caches.collapsecontent(content) end end end -function caches.savecontent(cachename,dataname,content) - local name=caches.hashed(cachename) - local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.savecontent(cachename,dataname,content,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local luaname=addsuffix(filename,luasuffixes.lua) local lucname=addsuffix(filename,luasuffixes.luc) if trace_locating then @@ -13576,7 +14358,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-met"] = package.loaded["data-met"] or true --- original size: 5460, stripped down to: 4014 +-- original size: 5347, stripped down to: 4015 if not modules then modules={} end modules ['data-met']={ version=1.100, @@ -13604,7 +14386,7 @@ local function splitmethod(filename) if type(filename)=="table" then return filename end - filename=file.collapsepath(filename,".") + filename=file.collapsepath(filename,".") if not find(filename,"://",1,true) then return { scheme="file",path=filename,original=filename,filename=filename } end @@ -13695,7 +14477,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-res"] = package.loaded["data-res"] or true --- original size: 62045, stripped down to: 43116 +-- original size: 67003, stripped down to: 46291 if not modules then modules={} end modules ['data-res']={ version=1.001, @@ -13705,7 +14487,7 @@ if not modules then modules={} end modules ['data-res']={ license="see context related readme files", } local gsub,find,lower,upper,match,gmatch=string.gsub,string.find,string.lower,string.upper,string.match,string.gmatch -local concat,insert,sortedkeys=table.concat,table.insert,table.sortedkeys +local concat,insert,remove,sortedkeys,sortedhash=table.concat,table.insert,table.remove,table.sortedkeys,table.sortedhash local next,type,rawget=next,type,rawget local os=os local P,S,R,C,Cc,Cs,Ct,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Carg @@ -13714,17 +14496,23 @@ local formatters=string.formatters local filedirname=file.dirname local filebasename=file.basename local suffixonly=file.suffixonly +local addsuffix=file.addsuffix +local removesuffix=file.removesuffix local filejoin=file.join local collapsepath=file.collapsepath local joinpath=file.joinpath +local is_qualified_path=file.is_qualified_path local allocate=utilities.storage.allocate local settings_to_array=utilities.parsers.settings_to_array +local getcurrentdir=lfs.currentdir +local isfile=lfs.isfile +local isdir=lfs.isdir local setmetatableindex=table.setmetatableindex local luasuffixes=utilities.lua.suffixes -local getcurrentdir=lfs.currentdir local trace_locating=false trackers .register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers .register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers .register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_paths=false trackers .register("resolvers.paths",function(v) trace_paths=v end) local resolve_otherwise=true directives.register("resolvers.otherwise",function(v) resolve_otherwise=v end) local report_resolving=logs.reporter("resolvers","resolving") local resolvers=resolvers @@ -13732,10 +14520,14 @@ local expandedpathfromlist=resolvers.expandedpathfromlist local checkedvariable=resolvers.checkedvariable local splitconfigurationpath=resolvers.splitconfigurationpath local methodhandler=resolvers.methodhandler +local filtered=resolvers.filtered_from_content +local lookup=resolvers.get_from_content +local cleanpath=resolvers.cleanpath +local resolveprefix=resolvers.resolve local initializesetter=utilities.setters.initialize local ostype,osname,osenv,ossetenv,osgetenv=os.type,os.name,os.env,os.setenv,os.getenv -resolvers.cacheversion='1.0.1' -resolvers.configbanner='' +resolvers.cacheversion="1.100" +resolvers.configbanner="" resolvers.homedir=environment.homedir resolvers.criticalvars=allocate { "SELFAUTOLOC","SELFAUTODIR","SELFAUTOPARENT","TEXMFCNF","TEXMF","TEXOS" } resolvers.luacnfname="texmfcnf.lua" @@ -13754,6 +14546,7 @@ end local unset_variable="unset" local formats=resolvers.formats local suffixes=resolvers.suffixes +local usertypes=resolvers.usertypes local dangerous=resolvers.dangerous local suffixmap=resolvers.suffixmap resolvers.defaultsuffixes={ "tex" } @@ -13762,7 +14555,7 @@ local instance=resolvers.instance or nil function resolvers.setenv(key,value,raw) if instance then instance.environment[key]=value - ossetenv(key,raw and value or resolvers.resolve(value)) + ossetenv(key,raw and value or resolveprefix(value)) end end local function getenv(key) @@ -13776,7 +14569,7 @@ local function getenv(key) end resolvers.getenv=getenv resolvers.env=getenv -local function resolve(k) +local function resolvevariable(k) return instance.expansions[k] end local dollarstripper=lpeg.stripper("$") @@ -13785,19 +14578,19 @@ local backslashswapper=lpeg.replacer("\\","/") local somevariable=P("$")/"" local somekey=C(R("az","AZ","09","__","--")^1) local somethingelse=P(";")*((1-S("!{}/\\"))^1*P(";")/"")+P(";")*(P(";")/"")+P(1) -local variableexpander=Cs((somevariable*(somekey/resolve)+somethingelse)^1 ) +local variableexpander=Cs((somevariable*(somekey/resolvevariable)+somethingelse)^1 ) local cleaner=P("\\")/"/"+P(";")*S("!{}/\\")^0*P(";")^1/";" local variablecleaner=Cs((cleaner+P(1))^0) -local somevariable=R("az","AZ","09","__","--")^1/resolve +local somevariable=R("az","AZ","09","__","--")^1/resolvevariable local variable=(P("$")/"")*(somevariable+(P("{")/"")*somevariable*(P("}")/"")) local variableresolver=Cs((variable+P(1))^0) local function expandedvariable(var) return lpegmatch(variableexpander,var) or var end -function resolvers.newinstance() - if trace_locating then +function resolvers.newinstance() + if trace_locating then report_resolving("creating instance") - end + end local environment,variables,expansions,order=allocate(),allocate(),allocate(),allocate() local newinstance={ environment=environment, @@ -13810,6 +14603,7 @@ function resolvers.newinstance() foundintrees=allocate(), hashes=allocate(), hashed=allocate(), + pathlists=false, specification=allocate(), lists=allocate(), data=allocate(), @@ -13822,6 +14616,7 @@ function resolvers.newinstance() savelists=true, pattern=nil, force_suffixes=true, + pathstack={}, } setmetatableindex(variables,function(t,k) local v @@ -13871,8 +14666,13 @@ function resolvers.reset() end local function reset_hashes() instance.lists={} + instance.pathlists=false instance.found={} end +local function reset_caches() + instance.lists={} + instance.pathlists=false +end local slash=P("/") local pathexpressionpattern=Cs ( Cc("^")*( @@ -13924,13 +14724,13 @@ local function identify_configuration_files() for i=1,#cnfpaths do local filepath=cnfpaths[i] local filename=collapsepath(filejoin(filepath,luacnfname)) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) if trace_locating then - local fullpath=gsub(resolvers.resolve(collapsepath(filepath)),"//","/") + local fullpath=gsub(resolveprefix(collapsepath(filepath)),"//","/") local weirdpath=find(fullpath,"/texmf.+/texmf") or not find(fullpath,"/web2c",1,true) report_resolving("looking for %a on %s path %a from specification %a",luacnfname,weirdpath and "weird" or "given",fullpath,filepath) end - if lfs.isfile(realname) then + if isfile(realname) then specification[#specification+1]=filename if trace_locating then report_resolving("found configuration file %a",realname) @@ -13952,7 +14752,7 @@ local function load_configuration_files() local filename=specification[i] local pathname=filedirname(filename) local filename=filejoin(pathname,luacnfname) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local setups=instance.setups @@ -13960,7 +14760,7 @@ local function load_configuration_files() local parent=data and data.parent if parent then local filename=filejoin(pathname,parent) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local parentdata=blob() @@ -13985,7 +14785,7 @@ local function load_configuration_files() elseif variables[k]==nil then if trace_locating and not warning then report_resolving("variables like %a in configuration file %a should move to the 'variables' subtable", - k,resolvers.resolve(filename)) + k,resolveprefix(filename)) warning=true end variables[k]=v @@ -14045,7 +14845,7 @@ local function locate_file_databases() local stripped=lpegmatch(inhibitstripper,path) if stripped~="" then local runtime=stripped==path - path=resolvers.cleanpath(path) + path=cleanpath(path) local spec=resolvers.splitmethod(stripped) if runtime and (spec.noscheme or spec.scheme=="file") then stripped="tree:///"..stripped @@ -14108,8 +14908,8 @@ function resolvers.renew(hashname) report_resolving("identifying tree %a",hashname) end end - local realpath=resolvers.resolve(hashname) - if lfs.isdir(realpath) then + local realpath=resolveprefix(hashname) + if isdir(realpath) then if trace_locating then report_resolving("using path %a",realpath) end @@ -14210,19 +15010,53 @@ end function resolvers.unexpandedpath(str) return joinpath(resolvers.unexpandedpathlist(str)) end +function resolvers.pushpath(name) + local pathstack=instance.pathstack + local lastpath=pathstack[#pathstack] + local pluspath=filedirname(name) + if lastpath then + lastpath=collapsepath(filejoin(lastpath,pluspath)) + else + lastpath=collapsepath(pluspath) + end + insert(pathstack,lastpath) + if trace_paths then + report_resolving("pushing path %a",lastpath) + end +end +function resolvers.poppath() + local pathstack=instance.pathstack + if trace_paths and #pathstack>0 then + report_resolving("popping path %a",pathstack[#pathstack]) + end + remove(pathstack) +end +function resolvers.stackpath() + local pathstack=instance.pathstack + local currentpath=pathstack[#pathstack] + return currentpath~="" and currentpath or nil +end local done={} function resolvers.resetextrapath() local ep=instance.extra_paths if not ep then - ep,done={},{} - instance.extra_paths=ep + done={} + instance.extra_paths={} elseif #ep>0 then - instance.lists,done={},{} + done={} + reset_caches() end end function resolvers.registerextrapath(paths,subpaths) - paths=settings_to_array(paths) - subpaths=settings_to_array(subpaths) + if not subpaths or subpaths=="" then + if not paths or path=="" then + return + elseif done[paths] then + return + end + end + local paths=settings_to_array(paths) + local subpaths=settings_to_array(subpaths) local ep=instance.extra_paths or {} local oldn=#ep local newn=oldn @@ -14237,7 +15071,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=p.."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14247,7 +15081,7 @@ function resolvers.registerextrapath(paths,subpaths) local p=paths[i] if not done[p] then newn=newn+1 - ep[newn]=resolvers.cleanpath(p) + ep[newn]=cleanpath(p) done[p]=true end end @@ -14259,7 +15093,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=ep[i].."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14268,52 +15102,70 @@ function resolvers.registerextrapath(paths,subpaths) if newn>0 then instance.extra_paths=ep end - if newn>oldn then - instance.lists={} + if newn~=oldn then + reset_caches() end end -local function made_list(instance,list) - local ep=instance.extra_paths - if not ep or #ep==0 then - return list +function resolvers.pushextrapath(path) + local paths=settings_to_array(path) + if instance.extra_stack then + insert(instance.extra_stack,1,paths) else - local done,new,newn={},{},0 - for k=1,#list do - local v=list[k] - if not done[v] then - if find(v,"^[%.%/]$") then - done[v]=true - newn=newn+1 - new[newn]=v - else - break - end - end - end - for k=1,#ep do - local v=ep[k] + instance.extra_stack={ paths } + end + reset_caches() +end +function resolvers.popextrapath() + if instance.extra_stack then + reset_caches() + return remove(instance.extra_stack,1) + end +end +local function made_list(instance,list,extra_too) + local done={} + local new={} + local newn=0 + local function add(p) + for k=1,#p do + local v=p[k] if not done[v] then done[v]=true newn=newn+1 new[newn]=v end end - for k=1,#list do - local v=list[k] - if not done[v] then - done[v]=true - newn=newn+1 - new[newn]=v + end + for k=1,#list do + local v=list[k] + if done[v] then + elseif find(v,"^[%.%/]$") then + done[v]=true + newn=newn+1 + new[newn]=v + else + break + end + end + if extra_too then + local es=instance.extra_stack + if es and #es>0 then + for k=1,#es do + add(es[k]) end end - return new + local ep=instance.extra_paths + if ep and #ep>0 then + add(ep) + end end + add(list) + return new end function resolvers.cleanpathlist(str) local t=resolvers.expandedpathlist(str) if t then for i=1,#t do - t[i]=collapsepath(resolvers.cleanpath(t[i])) + t[i]=collapsepath(cleanpath(t[i])) end end return t @@ -14321,22 +15173,22 @@ end function resolvers.expandpath(str) return joinpath(resolvers.expandedpathlist(str)) end -function resolvers.expandedpathlist(str) +function resolvers.expandedpathlist(str,extra_too) if not str then return {} - elseif instance.savelists then + elseif instance.savelists then str=lpegmatch(dollarstripper,str) local lists=instance.lists local lst=lists[str] if not lst then - local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str))) + local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str)),extra_too) lst=expandedpathfromlist(l) lists[str]=lst end return lst else local lst=resolvers.splitpath(resolvers.expansion(str)) - return made_list(instance,expandedpathfromlist(lst)) + return made_list(instance,expandedpathfromlist(lst),extra_too) end end function resolvers.expandedpathlistfromvariable(str) @@ -14347,6 +15199,13 @@ end function resolvers.expandpathfromvariable(str) return joinpath(resolvers.expandedpathlistfromvariable(str)) end +function resolvers.cleanedpathlist(v) + local t=resolvers.expandedpathlist(v) + for i=1,#t do + t[i]=resolvers.resolve(resolvers.cleanpath(t[i])) + end + return t +end function resolvers.expandbraces(str) local ori=str local pth=expandedpathfromlist(resolvers.splitpath(ori)) @@ -14363,7 +15222,7 @@ function resolvers.registerfilehash(name,content,someerror) end end local function isreadable(name) - local readable=lfs.isfile(name) + local readable=isfile(name) if trace_detail then if readable then report_resolving("file %a is readable",name) @@ -14373,70 +15232,57 @@ local function isreadable(name) end return readable end -local function collect_files(names) - local filelist,noffiles={},0 +local function collect_files(names) + local filelist={} + local noffiles=0 + local function check(hash,root,pathname,path,name) + if not pathname or find(path,pathname) then + local variant=hash.type + local search=filejoin(root,path,name) + local result=methodhandler('concatinators',variant,root,path,name) + if trace_detail then + report_resolving("match: variant %a, search %a, result %a",variant,search,result) + end + noffiles=noffiles+1 + filelist[noffiles]={ variant,search,result } + end + end for k=1,#names do - local fname=names[k] + local filename=names[k] if trace_detail then - report_resolving("checking name %a",fname) + report_resolving("checking name %a",filename) end - local bname=filebasename(fname) - local dname=filedirname(fname) - if dname=="" or find(dname,"^%.") then - dname=false + local basename=filebasename(filename) + local pathname=filedirname(filename) + if pathname=="" or find(pathname,"^%.") then + pathname=false else - dname=gsub(dname,"%*",".*") - dname="/"..dname.."$" + pathname=gsub(pathname,"%*",".*") + pathname="/"..pathname.."$" end local hashes=instance.hashes for h=1,#hashes do local hash=hashes[h] - local blobpath=hash.name - local files=blobpath and instance.files[blobpath] - if files then + local hashname=hash.name + local content=hashname and instance.files[hashname] + if content then if trace_detail then - report_resolving("deep checking %a, base %a, pattern %a",blobpath,bname,dname) - 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 + report_resolving("deep checking %a, base %a, pattern %a",hashname,basename,pathname) end - if blobfile then - local blobroot=files.__path__ or blobpath - if type(blobfile)=='string' then - if not dname or find(blobfile,dname) then - local variant=hash.type - local search=filejoin(blobroot,blobfile,bname) - local result=methodhandler('concatinators',hash.type,blobroot,blobfile,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + local path,name=lookup(content,basename) + if path then + local metadata=content.metadata + local realroot=metadata and metadata.path or hashname + if type(path)=="string" then + check(hash,realroot,pathname,path,name) else - for kk=1,#blobfile do - local vv=blobfile[kk] - if not dname or find(vv,dname) then - local variant=hash.type - local search=filejoin(blobroot,vv,bname) - local result=methodhandler('concatinators',hash.type,blobroot,vv,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + for i=1,#path do + check(hash,realroot,pathname,path[i],name) end end end elseif trace_locating then - report_resolving("no match in %a (%s)",blobpath,bname) + report_resolving("no match in %a (%s)",hashname,basename) end end end @@ -14461,7 +15307,7 @@ end local function can_be_dir(name) local fakepaths=instance.fakepaths if not fakepaths[name] then - if lfs.isdir(name) then + if isdir(name) then fakepaths[name]=1 else fakepaths[name]=2 @@ -14477,10 +15323,11 @@ local function find_analyze(filename,askedformat,allresults) if askedformat=="" then if ext=="" or not suffixmap[ext] then local defaultsuffixes=resolvers.defaultsuffixes + local formatofsuffix=resolvers.formatofsuffix for i=1,#defaultsuffixes do local forcedname=filename..'.'..defaultsuffixes[i] wantedfiles[#wantedfiles+1]=forcedname - filetype=resolvers.formatofsuffix(forcedname) + filetype=formatofsuffix(forcedname) if trace_locating then report_resolving("forcing filetype %a",filetype) end @@ -14520,14 +15367,14 @@ local function find_wildcard(filename,allresults) if trace_locating then report_resolving("checking wildcard %a",filename) end - local method,result=resolvers.findwildcardfiles(filename) + local result=resolvers.findwildcardfiles(filename) if result then return "wildcard",result end end end local function find_qualified(filename,allresults,askedformat,alsostripped) - if not file.is_qualified_path(filename) then + if not is_qualified_path(filename) then return end if trace_locating then @@ -14601,33 +15448,66 @@ local function check_subpath(fname) return fname end end -local function find_intree(filename,filetype,wantedfiles,allresults) +local function makepathlist(list,filetype) local typespec=resolvers.variableofformat(filetype) - local pathlist=resolvers.expandedpathlist(typespec) - local method="intree" + local pathlist=resolvers.expandedpathlist(typespec,filetype and usertypes[filetype]) + local entry={} if pathlist and #pathlist>0 then - local filelist=collect_files(wantedfiles) + for k=1,#pathlist do + local path=pathlist[k] + local prescanned=find(path,'^!!') + local resursive=find(path,'//$') + local pathname=lpegmatch(inhibitstripper,path) + local expression=makepathexpression(pathname) + local barename=gsub(pathname,"/+$","") + barename=resolveprefix(barename) + local scheme=url.hasscheme(barename) + local schemename=gsub(barename,"%.%*$",'') + entry[k]={ + path=path, + pathname=pathname, + prescanned=prescanned, + recursive=recursive, + expression=expression, + barename=barename, + scheme=scheme, + schemename=schemename, + } + end + entry.typespec=typespec + list[filetype]=entry + else + list[filetype]=false + end + return entry +end +local function find_intree(filename,filetype,wantedfiles,allresults) + local pathlists=instance.pathlists + if not pathlists then + pathlists=setmetatableindex(allocate(),makepathlist) + instance.pathlists=pathlists + end + local pathlist=pathlists[filetype] + if pathlist then + local method="intree" + local filelist=collect_files(wantedfiles) local dirlist={} + local result={} if filelist then for i=1,#filelist do dirlist[i]=filedirname(filelist[i][3]).."/" end end if trace_detail then - report_resolving("checking filename %a",filename) + report_resolving("checking filename %a in tree",filename) end - local resolve=resolvers.resolve - local result={} for k=1,#pathlist do - local path=pathlist[k] - local pathname=lpegmatch(inhibitstripper,path) - local doscan=path==pathname - if not find (pathname,'//$') then - doscan=false - end + local entry=pathlist[k] + local path=entry.path + local pathname=entry.pathname local done=false if filelist then - local expression=makepathexpression(pathname) + local expression=entry.expression if trace_detail then report_resolving("using pattern %a for path %a",expression,pathname) end @@ -14635,8 +15515,8 @@ local function find_intree(filename,filetype,wantedfiles,allresults) local fl=filelist[k] local f=fl[2] local d=dirlist[k] - if find(d,expression) or find(resolve(d),expression) then - result[#result+1]=resolve(fl[3]) + if find(d,expression) or find(resolveprefix(d),expression) then + result[#result+1]=resolveprefix(fl[3]) done=true if allresults then if trace_detail then @@ -14657,56 +15537,62 @@ local function find_intree(filename,filetype,wantedfiles,allresults) method="database" else method="filesystem" - pathname=gsub(pathname,"/+$","") - pathname=resolve(pathname) - local scheme=url.hasscheme(pathname) + local scheme=entry.scheme if not scheme or scheme=="file" then - local pname=gsub(pathname,"%.%*$",'') + local pname=entry.schemename if not find(pname,"*",1,true) then if can_be_dir(pname) then - for k=1,#wantedfiles do - local w=wantedfiles[k] - local fname=check_subpath(filejoin(pname,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + if not done and not entry.prescanned then + if trace_detail then + report_resolving("quick root scan for %a",pname) end - end - if not done and doscan then - local files=resolvers.simplescanfiles(pname,false,true) for k=1,#wantedfiles do local w=wantedfiles[k] - local subpath=files[w] - if not subpath or subpath=="" then - elseif type(subpath)=="string" then - local fname=check_subpath(filejoin(pname,subpath,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + local fname=check_subpath(filejoin(pname,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break end - else - for i=1,#subpath do - local sp=subpath[i] - if sp=="" then - else - local fname=check_subpath(filejoin(pname,sp,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break + end + end + if not done and entry.recursive then + if trace_detail then + report_resolving("scanning filesystem for %a",pname) + end + local files=resolvers.simplescanfiles(pname,false,true) + for k=1,#wantedfiles do + local w=wantedfiles[k] + local subpath=files[w] + if not subpath or subpath=="" then + elseif type(subpath)=="string" then + local fname=check_subpath(filejoin(pname,subpath,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + else + for i=1,#subpath do + local sp=subpath[i] + if sp=="" then + else + local fname=check_subpath(filejoin(pname,sp,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end end end end - end - if done and not allresults then - break + if done and not allresults then + break + end end end end @@ -14714,6 +15600,18 @@ local function find_intree(filename,filetype,wantedfiles,allresults) end else end + else + for k=1,#wantedfiles do + local pname=entry.barename + local fname=methodhandler('finders',pname.."/"..wantedfiles[k]) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + end end end if done and not allresults then @@ -14748,10 +15646,13 @@ local function find_otherwise(filename,filetype,wantedfiles,allresults) local filelist=collect_files(wantedfiles) local fl=filelist and filelist[1] if fl then - return "otherwise",{ resolvers.resolve(fl[3]) } + return "otherwise",{ resolveprefix(fl[3]) } end end collect_instance_files=function(filename,askedformat,allresults) + if not filename or filename=="" then + return {} + end askedformat=askedformat or "" filename=collapsepath(filename,".") filename=gsub(filename,"^%./",getcurrentdir().."/") @@ -14786,7 +15687,11 @@ collect_instance_files=function(filename,askedformat,allresults) else local method,result,stamp,filetype,wantedfiles if instance.remember then - stamp=formatters["%s--%s"](filename,askedformat) + if askedformat=="" then + stamp=formatters["%s::%s"](suffixonly(filename),filename) + else + stamp=formatters["%s::%s"](askedformat,filename) + end result=stamp and instance.found[stamp] if result then if trace_locating then @@ -14821,7 +15726,7 @@ collect_instance_files=function(filename,askedformat,allresults) end if stamp then if trace_locating then - report_resolving("remembering file %a",filename) + report_resolving("remembering file %a using hash %a",filename,stamp) end instance.found[stamp]=result end @@ -14829,6 +15734,9 @@ collect_instance_files=function(filename,askedformat,allresults) end end local function findfiles(filename,filetype,allresults) + if not filename or filename=="" then + return {} + end local result,status=collect_instance_files(filename,filetype or "",allresults) if not result or #result==0 then local lowered=lower(filename) @@ -14848,39 +15756,30 @@ function resolvers.findpath(filename,filetype) return filedirname(findfiles(filename,filetype,false)[1] or "") end local function findgivenfiles(filename,allresults) - local bname,result=filebasename(filename),{} + local base=filebasename(filename) + local result={} local hashes=instance.hashes - local noffound=0 + local function okay(hash,path,name) + local found=methodhandler('concatinators',hash.type,hash.name,path,name) + if found and found~="" then + result[#result+1]=resolveprefix(found) + return not allresults + end + end for k=1,#hashes do local hash=hashes[k] - local files=instance.files[hash.name] or {} - 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 - local found=methodhandler('concatinators',hash.type,hash.name,blist,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then - break - end + local content=instance.files[hash.name] + if content then + local path,name=lookup(content,base) + if not path then + elseif type(path)=="string" then + if okay(hash,path,name) then + return result end else - for kk=1,#blist do - local vv=blist[kk] - local found=methodhandler('concatinators',hash.type,hash.name,vv,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then break end + for i=1,#path do + if okay(hash,path[i],name) then + return result end end end @@ -14894,64 +15793,80 @@ end function resolvers.findgivenfile(filename) return findgivenfiles(filename,false)[1] or "" end -local function doit(path,blist,bname,tag,variant,result,allresults) - local done=false - if blist and variant then - local resolve=resolvers.resolve - if type(blist)=='string' then - if find(lower(blist),path) then - local full=methodhandler('concatinators',variant,tag,blist,bname) or "" - result[#result+1]=resolve(full) - done=true - end - else - for kk=1,#blist do - local vv=blist[kk] - if find(lower(vv),path) then - local full=methodhandler('concatinators',variant,tag,vv,bname) or "" - result[#result+1]=resolve(full) - done=true - if not allresults then break end - end - end - end - end - return done -end local makewildcard=Cs( (P("^")^0*P("/")*P(-1)+P(-1))/".*"+(P("^")^0*P("/")/"")^0*(P("*")/".*"+P("-")/"%%-"+P(".")/"%%."+P("?")/"."+P("\\")/"/"+P(1))^0 ) function resolvers.wildcardpattern(pattern) return lpegmatch(makewildcard,pattern) or pattern end -local function findwildcardfiles(filename,allresults,result) - result=result or {} +local function findwildcardfiles(filename,allresults,result) + local result=result or {} local base=filebasename(filename) local dirn=filedirname(filename) local path=lower(lpegmatch(makewildcard,dirn) or dirn) local name=lower(lpegmatch(makewildcard,base) or base) - local files,done=instance.files,false + local files=instance.files if find(name,"*",1,true) then local hashes=instance.hashes + local function okay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - for kk,hh in next,files[hashname] do - if not find(kk,"^remap:") then - if find(lower(kk),name) then - if doit(path,hh,kk,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + for found,base in filtered(files[hashname],name) do + if type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end end end end end else + local function okayokay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end local hashes=instance.hashes for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - if doit(path,files[hashname][base],base,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + local found,base=lookup(content,base) + if not found then + elseif type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end + end + end end end return result @@ -15024,7 +15939,7 @@ end function resolvers.dowithpath(name,func) local pathlist=resolvers.expandedpathlist(name) for i=1,#pathlist do - func("^"..resolvers.cleanpath(pathlist[i])) + func("^"..cleanpath(pathlist[i])) end end function resolvers.dowithvariable(name,func) @@ -15032,23 +15947,23 @@ function resolvers.dowithvariable(name,func) end function resolvers.locateformat(name) local engine=environment.ownmain or "luatex" - local barename=file.removesuffix(name) - local fullname=file.addsuffix(barename,"fmt") + local barename=removesuffix(name) + local fullname=addsuffix(barename,"fmt") local fmtname=caches.getfirstreadablefile(fullname,"formats",engine) or "" if fmtname=="" then fmtname=resolvers.findfile(fullname) - fmtname=resolvers.cleanpath(fmtname) + fmtname=cleanpath(fmtname) end if fmtname~="" then - local barename=file.removesuffix(fmtname) - local luaname=file.addsuffix(barename,luasuffixes.lua) - local lucname=file.addsuffix(barename,luasuffixes.luc) - local luiname=file.addsuffix(barename,luasuffixes.lui) - if lfs.isfile(luiname) then + local barename=removesuffix(fmtname) + local luaname=addsuffix(barename,luasuffixes.lua) + local lucname=addsuffix(barename,luasuffixes.luc) + local luiname=addsuffix(barename,luasuffixes.lui) + if isfile(luiname) then return barename,luiname - elseif lfs.isfile(lucname) then + elseif isfile(lucname) then return barename,lucname - elseif lfs.isfile(luaname) then + elseif isfile(luaname) then return barename,luaname end end @@ -15070,29 +15985,24 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) local hash=hashes[i] local blobtype=hash.type local blobpath=hash.name - if blobpath then + if blobtype and blobpath then + local total=0 + local checked=0 + local done=0 if before then before(blobtype,blobpath,pattern) end - local files=instance.files[blobpath] - local total,checked,done=0,0,0 - if files then - for k,v in table.sortedhash(files) do - total=total+1 - if find(k,"^remap:") then - elseif find(k,pattern) then - if type(v)=="string" then - checked=checked+1 - if handle(blobtype,blobpath,v,k) then - done=done+1 - end - else - checked=checked+#v - for i=1,#v do - if handle(blobtype,blobpath,v[i],k) then - done=done+1 - end - end + for path,name in filtered(instance.files[blobpath],pattern) do + if type(path)=="string" then + checked=checked+1 + if handle(blobtype,blobpath,path,name) then + done=done+1 + end + else + checked=checked+#path + for i=1,#path do + if handle(blobtype,blobpath,path[i],name) then + done=done+1 end end end @@ -15103,8 +16013,8 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) end end end -resolvers.obsolete=resolvers.obsolete or {} -local obsolete=resolvers.obsolete +local obsolete=resolvers.obsolete or {} +resolvers.obsolete=obsolete resolvers.find_file=resolvers.findfile obsolete.find_file=resolvers.findfile resolvers.find_files=resolvers.findfiles obsolete.find_files=resolvers.findfiles @@ -15115,7 +16025,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-pre"] = package.loaded["data-pre"] or true --- original size: 6643, stripped down to: 4401 +-- original size: 3950, stripped down to: 2935 if not modules then modules={} end modules ['data-pre']={ version=1.001, @@ -15125,44 +16035,51 @@ if not modules then modules={} end modules ['data-pre']={ license="see context related readme files" } local resolvers=resolvers -local prefixes=utilities.storage.allocate() -resolvers.prefixes=prefixes -local cleanpath,findgivenfile,expansion=resolvers.cleanpath,resolvers.findgivenfile,resolvers.expansion +local prefixes=resolvers.prefixes +local cleanpath=resolvers.cleanpath +local findgivenfile=resolvers.findgivenfile +local expansion=resolvers.expansion local getenv=resolvers.getenv -local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match -local joinpath,basename,dirname=file.join,file.basename,file.dirname -local getmetatable,rawset,type=getmetatable,rawset,type +local basename=file.basename +local dirname=file.dirname +local joinpath=file.join +local isfile=lfs.isfile prefixes.environment=function(str) return cleanpath(expansion(str)) end -prefixes.relative=function(str,n) - if io.exists(str) then - 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.."../" +local function relative(str,n) + if not isfile(str) then + local pstr="./"..str + if isfile(pstr) then + str=pstr + else + local p="../" + for i=1,n or 2 do + local pstr=p..str + if isfile(pstr) then + str=pstr + break + else + p=p.."../" + end end end end return cleanpath(str) end +local function locate(str) + local fullname=findgivenfile(str) or "" + return cleanpath(fullname~="" and fullname or str) +end +prefixes.relative=relative +prefixes.locate=locate prefixes.auto=function(str) - local fullname=prefixes.relative(str) - if not lfs.isfile(fullname) then - fullname=prefixes.locate(str) + local fullname=relative(str) + if not isfile(fullname) then + fullname=locate(str) end return fullname end -prefixes.locate=function(str) - local fullname=findgivenfile(str) or "" - return cleanpath((fullname~="" and fullname) or str) -end prefixes.filename=function(str) local fullname=findgivenfile(str) or "" return cleanpath(basename((fullname~="" and fullname) or str)) @@ -15183,6 +16100,13 @@ end prefixes.home=function(str) return cleanpath(joinpath(getenv('HOME'),str)) end +prefixes.env=prefixes.environment +prefixes.rel=prefixes.relative +prefixes.loc=prefixes.locate +prefixes.kpse=prefixes.locate +prefixes.full=prefixes.locate +prefixes.file=prefixes.filename +prefixes.path=prefixes.pathname local function toppath() local inputstack=resolvers.inputstack if not inputstack then @@ -15195,98 +16119,22 @@ local function toppath() return pathname end end -resolvers.toppath=toppath -prefixes.toppath=function(str) - return cleanpath(joinpath(toppath(),str)) -end -prefixes.env=prefixes.environment -prefixes.rel=prefixes.relative -prefixes.loc=prefixes.locate -prefixes.kpse=prefixes.locate -prefixes.full=prefixes.locate -prefixes.file=prefixes.filename -prefixes.path=prefixes.pathname -function resolvers.allprefixes(separator) - local all=table.sortedkeys(prefixes) - if separator then - for i=1,#all do - all[i]=all[i]..":" - end - end - return all -end -local function _resolve_(method,target) - local action=prefixes[method] - if action then - return action(target) - else - return method..":"..target - end -end -local resolved,abstract={},{} -function resolvers.resetresolve(str) - resolved,abstract={},{} -end -local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) -local prefix=C(R("az")^2)*P(":") -local target=C((1-S(" \"\';,"))^1) -local notarget=(#S(";,")+P(-1))*Cc("") -local pattern=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) -local function resolve(str) - if type(str)=="table" then - local t={} - for i=1,#str do - t[i]=resolve(str[i]) - end - return t +local function jobpath() + local path=resolvers.stackpath() + if not path or path=="" then + return "." else - local res=resolved[str] - if not res then - res=lpegmatch(pattern,str) - resolved[str]=res - abstract[res]=str - end - return res - end -end -local function unresolve(str) - return abstract[str] or str -end -resolvers.resolve=resolve -resolvers.unresolve=unresolve -if type(os.uname)=="function" then - for k,v in next,os.uname() do - if not prefixes[k] then - prefixes[k]=function() return v end - end - end -end -if os.type=="unix" then - local pattern - local function makepattern(t,k,v) - if t then - rawset(t,k,v) - end - local colon=P(":") - for k,v in table.sortedpairs(prefixes) do - if p then - p=P(k)+p - else - p=P(k) - end - end - pattern=Cs((p*colon+colon/";"+P(1))^0) - end - makepattern() - getmetatable(prefixes).__newindex=makepattern - function resolvers.repath(str) - return lpegmatch(pattern,str) - end -else - function resolvers.repath(str) - return str + return path end end +resolvers.toppath=toppath +resolvers.jobpath=jobpath +prefixes.toppath=function(str) return cleanpath(joinpath(toppath(),str)) end +prefixes.jobpath=function(str) return cleanpath(joinpath(jobpath(),str)) end +resolvers.setdynamic("toppath") +resolvers.setdynamic("jobpath") +prefixes.jobfile=prefixes.jobpath +resolvers.setdynamic("jobfile") end -- of closure @@ -15348,7 +16196,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-fil"] = package.loaded["data-fil"] or true --- original size: 3801, stripped down to: 3231 +-- original size: 3863, stripped down to: 3310 if not modules then modules={} end modules ['data-fil']={ version=1.001, @@ -15360,30 +16208,31 @@ if not modules then modules={} end modules ['data-fil']={ local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_files=logs.reporter("resolvers","files") local resolvers=resolvers +local resolveprefix=resolvers.resolve local finders,openers,loaders,savers=resolvers.finders,resolvers.openers,resolvers.loaders,resolvers.savers local locators,hashers,generators,concatinators=resolvers.locators,resolvers.hashers,resolvers.generators,resolvers.concatinators local checkgarbage=utilities.garbagecollector and utilities.garbagecollector.check function locators.file(specification) - local name=specification.filename - local realname=resolvers.resolve(name) + local filename=specification.filename + local realname=resolveprefix(filename) if realname and realname~='' and lfs.isdir(realname) then if trace_locating then - report_files("file locator %a found as %a",name,realname) + report_files("file locator %a found as %a",filename,realname) end - resolvers.appendhash('file',name,true) + resolvers.appendhash('file',filename,true) elseif trace_locating then - report_files("file locator %a not found",name) + report_files("file locator %a not found",filename) end end function hashers.file(specification) - local name=specification.filename - local content=caches.loadcontent(name,'files') - resolvers.registerfilehash(name,content,content==nil) + local pathname=specification.filename + local content=caches.loadcontent(pathname,'files') + resolvers.registerfilehash(pathname,content,content==nil) end function generators.file(specification) - local path=specification.filename - local content=resolvers.scanfiles(path,false,true) - resolvers.registerfilehash(path,content,true) + local pathname=specification.filename + local content=resolvers.scanfiles(pathname,false,true) + resolvers.registerfilehash(pathname,content,true) end concatinators.file=file.join function finders.file(specification,filetype) @@ -15665,7 +16514,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-zip"] = package.loaded["data-zip"] or true --- original size: 8489, stripped down to: 6757 +-- original size: 8772, stripped down to: 6841 if not modules then modules={} end modules ['data-zip']={ version=1.001, @@ -15684,16 +16533,6 @@ zip.archives=zip.archives or {} local archives=zip.archives zip.registeredfiles=zip.registeredfiles or {} local registeredfiles=zip.registeredfiles -local limited=false -directives.register("system.inputmode",function(v) - if not limited then - local i_limiter=io.i_limiter(v) - if i_limiter then - zip.open=i_limiter.protect(zip.open) - limited=true - end - end -end) local function validzip(str) if not find(str,"^zip://") then return "zip:///"..str @@ -15708,7 +16547,7 @@ function zip.openarchive(name) local arch=archives[name] if not arch then local full=resolvers.findfile(name) or "" - arch=(full~="" and zip.open(full)) or false + arch=full~="" and zip.open(full) or false archives[name]=arch end return arch @@ -15867,31 +16706,42 @@ function resolvers.usezipfile(archive) end end function resolvers.registerzipfile(z,tree) - local files,filter={},"" - if tree=="" then - filter="^(.+)/(.-)$" - else - filter=format("^%s/(.+)/(.-)$",tree) - end + local names={} + local files={} + local remap={} + local n=0 + local filter=tree=="" and "^(.+)/(.-)$" or format("^%s/(.+)/(.-)$",tree) + local register=resolvers.registerfile if trace_locating then report_zip("registering: using filter %a",filter) end - local register,n=resolvers.registerfile,0 for i in z:files() do - local path,name=match(i.filename,filter) - if path then - if name and name~='' then - register(files,name,path) - n=n+1 - else + local filename=i.filename + local path,name=match(filename,filter) + if not path then + n=n+1 + register(names,filename,"") + local usedname=lower(filename) + files[usedname]="" + if usedname~=filename then + remap[usedname]=filename end - else - register(files,i.filename,'') + elseif name and name~="" then n=n+1 + register(names,name,path) + local usedname=lower(name) + files[usedname]=path + if usedname~=name then + remap[usedname]=name + end + else end end report_zip("registering: %s files registered",n) - return files + return { + files=files, + remap=remap, + } end @@ -15901,7 +16751,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tre"] = package.loaded["data-tre"] or true --- original size: 2508, stripped down to: 2074 +-- original size: 8479, stripped down to: 5580 if not modules then modules={} end modules ['data-tre']={ version=1.001, @@ -15910,42 +16760,64 @@ if not modules then modules={} end modules ['data-tre']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,gsub,format=string.find,string.gsub,string.format +local find,gsub,lower=string.find,string.gsub,string.lower +local basename,dirname,joinname=file.basename,file.dirname,file .join +local globdir,isdir,isfile=dir.glob,lfs.isdir,lfs.isfile +local P,lpegmatch=lpeg.P,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_trees=logs.reporter("resolvers","trees") local resolvers=resolvers -local done,found,notfound={},{},resolvers.finders.notfound -function resolvers.finders.tree(specification) +local resolveprefix=resolvers.resolve +local notfound=resolvers.finders.notfound +local lookup=resolvers.get_from_content +local collectors={} +local found={} +function resolvers.finders.tree(specification) local spec=specification.filename - local fnd=found[spec] - if fnd==nil then + local okay=found[spec] + if okay==nil then if spec~="" then - local path,name=file.dirname(spec),file.basename(spec) - if path=="" then path="." end - local hash=done[path] - if not hash then - local pattern=path.."/*" - hash=dir.glob(pattern) - done[path]=hash + local path=dirname(spec) + local name=basename(spec) + if path=="" then + path="." + end + local names=collectors[path] + if not names then + local pattern=find(path,"/%*+$") and path or (path.."/*") + names=globdir(pattern) + collectors[path]=names end local pattern="/"..gsub(name,"([%.%-%+])","%%%1").."$" - for k=1,#hash do - local v=hash[k] - if find(v,pattern) then - found[spec]=v - return v + for i=1,#names do + local fullname=names[i] + if find(fullname,pattern) then + found[spec]=fullname + return fullname + end + end + local pattern=lower(pattern) + for i=1,#names do + local fullname=lower(names[i]) + if find(fullname,pattern) then + if isfile(fullname) then + found[spec]=fullname + return fullname + else + break + end end end end - fnd=notfound() - found[spec]=fnd + okay=notfound() + found[spec]=okay end - return fnd + return okay end function resolvers.locators.tree(specification) local name=specification.filename - local realname=resolvers.resolve(name) - if realname and realname~='' and lfs.isdir(realname) then + local realname=resolveprefix(name) + if realname and realname~='' and isdir(realname) then if trace_locating then report_trees("locator %a found",realname) end @@ -15956,16 +16828,110 @@ function resolvers.locators.tree(specification) end function resolvers.hashers.tree(specification) local name=specification.filename - if trace_locating then - report_trees("analysing %a",name) - end + report_trees("analyzing %a",name) resolvers.methodhandler("hashers",name) resolvers.generators.file(specification) end -resolvers.concatinators.tree=resolvers.concatinators.file -resolvers.generators.tree=resolvers.generators.file -resolvers.openers.tree=resolvers.openers.file -resolvers.loaders.tree=resolvers.loaders.file +local collectors={} +local splitter=lpeg.splitat("/**/") +local stripper=lpeg.replacer { [P("/")*P("*")^1*P(-1)]="" } +table.setmetatableindex(collectors,function(t,k) + local rootname=lpegmatch(stripper,k) + local dataname=joinname(rootname,"dirlist") + local content=caches.loadcontent(dataname,"files",dataname) + if not content then + content=resolvers.scanfiles(rootname,nil,nil,false,true) + caches.savecontent(dataname,"files",content,dataname) + end + t[k]=content + return content +end) +local function checked(root,p,n) + if p then + if type(p)=="table" then + for i=1,#p do + local fullname=joinname(root,p[i],n) + if isfile(fullname) then + return fullname + end + end + else + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + end + return notfound() +end +local function resolve(specification) + local filename=specification.filename + if filename~="" then + local root,rest=lpegmatch(splitter,filename) + if root and rest then + local path,name=dirname(rest),basename(rest) + if name~=rest then + local content=collectors[root] + local p,n=lookup(content,name) + if not p then + return notfound() + end + local pattern=".*/"..path.."$" + local istable=type(p)=="table" + if istable then + for i=1,#p do + local pi=p[i] + if pi==path or find(pi,pattern) then + local fullname=joinname(root,pi,n) + if isfile(fullname) then + return fullname + end + end + end + elseif p==path or find(p,pattern) then + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + local queries=specification.queries + if queries and queries.option=="fileonly" then + return checked(root,p,n) + else + return notfound() + end + end + end + local path,name=dirname(filename),basename(filename) + local root=lpegmatch(stripper,path) + local content=collectors[path] + local p,n=lookup(content,name) + if p then + return checked(root,p,n) + end + end + return notfound() +end +resolvers.finders .dirlist=resolve +resolvers.locators .dirlist=resolvers.locators .tree +resolvers.hashers .dirlist=resolvers.hashers .tree +resolvers.generators.dirlist=resolvers.generators.file +resolvers.openers .dirlist=resolvers.openers .file +resolvers.loaders .dirlist=resolvers.loaders .file +function resolvers.finders.dirfile(specification) + local queries=specification.queries + if queries then + queries.option="fileonly" + else + specification.queries={ option="fileonly" } + end + return resolve(specification) +end +resolvers.locators .dirfile=resolvers.locators .dirlist +resolvers.hashers .dirfile=resolvers.hashers .dirlist +resolvers.generators.dirfile=resolvers.generators.dirlist +resolvers.openers .dirfile=resolvers.openers .dirlist +resolvers.loaders .dirfile=resolvers.loaders .dirlist end -- of closure @@ -15974,7 +16940,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-sch"] = package.loaded["data-sch"] or true --- original size: 6213, stripped down to: 5160 +-- original size: 6569, stripped down to: 5304 if not modules then modules={} end modules ['data-sch']={ version=1.001, @@ -16000,8 +16966,13 @@ directives.register("schemes.threshold",function(v) threshold=tonumber(v) or thr function cleaners.none(specification) return specification.original end -function cleaners.strip(specification) - return (gsub(specification.original,"[^%a%d%.]+","-")) +function cleaners.strip(specification) + local path,name=file.splitbase(specification.original) + if path=="" then + return (gsub(name,"[^%a%d%.]+","-")) + else + return (gsub((gsub(path,"%.","-").."-"..name),"[^%a%d%.]+","-")) + end end function cleaners.md5(specification) return file.addsuffix(md5.hex(specification.original),file.suffix(specification.path)) @@ -16018,7 +16989,7 @@ end local cached,loaded,reused,thresholds,handlers={},{},{},{},{} local function runcurl(name,cachename) local command="curl --silent --insecure --create-dirs --output "..cachename.." "..name - os.spawn(command) + os.execute(command) end local function fetch(specification) local original=specification.original @@ -16150,7 +17121,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lua"] = package.loaded["data-lua"] or true --- original size: 4237, stripped down to: 3177 +-- original size: 4313, stripped down to: 3227 if not modules then modules={} end modules ['data-lua']={ version=1.001, @@ -16159,7 +17130,7 @@ if not modules then modules={} end modules ['data-lua']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local resolvers,package=resolvers,package +local package,lpeg=package,lpeg local gsub=string.gsub local concat=table.concat local addsuffix=file.addsuffix @@ -16170,9 +17141,11 @@ local luaformats={ 'TEXINPUTS','LUAINPUTS' } local libformats={ 'CLUAINPUTS' } local helpers=package.helpers or {} local methods=helpers.methods or {} +local resolvers=resolvers +local resolveprefix=resolvers.resolve +helpers.report=logs.reporter("resolvers","libraries") trackers.register("resolvers.libraries",function(v) helpers.trace=v end) trackers.register("resolvers.locating",function(v) helpers.trace=v end) -helpers.report=logs.reporter("resolvers","libraries") helpers.sequence={ "already loaded", "preload table", @@ -16187,7 +17160,7 @@ helpers.sequence={ } local pattern=Cs(P("!")^0/""*(P("/")*P(-1)/"/"+P("/")^1/"/"+1)^0) function helpers.cleanpath(path) - return resolvers.resolve(lpegmatch(pattern,path)) + return resolveprefix(lpegmatch(pattern,path)) end local loadedaslib=helpers.loadedaslib local getextraluapaths=package.extraluapaths @@ -16324,7 +17297,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmf"] = package.loaded["data-tmf"] or true --- original size: 2600, stripped down to: 1627 +-- original size: 2601, stripped down to: 1627 if not modules then modules={} end modules ['data-tmf']={ version=1.001, @@ -16380,7 +17353,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lst"] = package.loaded["data-lst"] or true --- original size: 2654, stripped down to: 2301 +-- original size: 2734, stripped down to: 2354 if not modules then modules={} end modules ['data-lst']={ version=1.001, @@ -16389,10 +17362,13 @@ if not modules then modules={} end modules ['data-lst']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,concat,upper,format=string.find,table.concat,string.upper,string.format +local rawget,type,next=rawget,type,next +local find,concat,upper=string.find,table.concat,string.upper local fastcopy,sortedpairs=table.fastcopy,table.sortedpairs -resolvers.listers=resolvers.listers or {} local resolvers=resolvers +local listers=resolvers.listers or {} +resolvers.listers=listers +local resolveprefix=resolvers.resolve local report_lists=logs.reporter("resolvers","lists") local function tabstr(str) if type(str)=='table' then @@ -16401,7 +17377,7 @@ local function tabstr(str) return str end end -function resolvers.listers.variables(pattern) +function listers.variables(pattern) local instance=resolvers.instance local environment=instance.environment local variables=instance.variables @@ -16422,10 +17398,10 @@ function resolvers.listers.variables(pattern) for key,value in sortedpairs(configured) do if key~="" and (pattern=="" or find(upper(key),pattern)) then report_lists(key) - report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") - report_lists(" var: %s",tabstr(configured[key]) or "unset") - report_lists(" exp: %s",tabstr(expansions[key]) or "unset") - report_lists(" res: %s",tabstr(resolvers.resolve(expansions[key])) or "unset") + report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") + report_lists(" var: %s",tabstr(configured[key]) or "unset") + report_lists(" exp: %s",tabstr(expansions[key]) or "unset") + report_lists(" res: %s",tabstr(resolveprefix(expansions[key])) or "unset") end end instance.environment=fastcopy(env) @@ -16433,15 +17409,15 @@ function resolvers.listers.variables(pattern) instance.expansions=fastcopy(exp) end local report_resolved=logs.reporter("system","resolved") -function resolvers.listers.configurations() +function listers.configurations() local configurations=resolvers.instance.specification for i=1,#configurations do - report_resolved("file : %s",resolvers.resolve(configurations[i])) + report_resolved("file : %s",resolveprefix(configurations[i])) end report_resolved("") local list=resolvers.expandedpathfromlist(resolvers.splitpath(resolvers.luacnfspec)) for i=1,#list do - local li=resolvers.resolve(list[i]) + local li=resolveprefix(list[i]) if lfs.isdir(li) then report_resolved("path - %s",li) else @@ -16746,7 +17722,7 @@ do -- create closure to overcome 200 locals limit package.loaded["luat-fmt"] = package.loaded["luat-fmt"] or true --- original size: 5951, stripped down to: 4922 +-- original size: 5955, stripped down to: 4926 if not modules then modules={} end modules ['luat-fmt']={ version=1.001, @@ -16834,7 +17810,7 @@ function environment.make_format(name) end local command=format("%s --ini %s --lua=%s %s %sdump",engine,primaryflags(),quoted(usedluastub),quoted(fulltexsourcename),os.platform=="unix" and "\\\\" or "\\") report_format("running command: %s\n",command) - os.spawn(command) + os.execute(command) local pattern=file.removesuffix(file.basename(usedluastub)).."-*.mem" local mp=dir.glob(pattern) if mp then @@ -16869,7 +17845,7 @@ function environment.run_format(name,data,more) else local command=format("%s %s --fmt=%s --lua=%s %s %s",engine,primaryflags(),quoted(barename),quoted(luaname),quoted(data),more~="" and quoted(more) or "") report_format("running command: %s",command) - os.spawn(command) + os.execute(command) end end end @@ -16880,8 +17856,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 695180 --- stripped bytes : 246794 +-- original bytes : 739251 +-- stripped bytes : 264497 -- end library merge @@ -17107,6 +18083,7 @@ local helpinfo = [[ <category name="basic"> <subcategory> <flag name="script"><short>run an mtx script (lua prefered method) (<ref name="noquotes"/>), no script gives list</short></flag> + <flag name="evaluate"><short>run code passed on the commandline (between quotes)</short></flag> <flag name="execute"><short>run a script or program (texmfstart method) (<ref name="noquotes"/>)</short></flag> <flag name="resolve"><short>resolve prefixed arguments</short></flag> <flag name="ctxlua"><short>run internally (using preloaded libs)</short></flag> @@ -17132,6 +18109,7 @@ local helpinfo = [[ <flag name="verbose"><short>give a bit more info</short></flag> <flag name="trackers" value="list"><short>enable given trackers</short></flag> <flag name="progname" value="str"><short>format or backend</short></flag> + <flag name="systeminfo" value="str"><short>show current operating system, processor, etc</short></flag> </subcategory> <subcategory> <flag name="edit"><short>launch editor with found file</short></flag> @@ -17761,6 +18739,39 @@ function runners.associate(filename) os.launch(filename) end +function runners.evaluate(code,filename) -- for Luigi + if code == "loop" then + while true do + io.write("> ") + local code = io.read() + if code ~= "" then + local temp = string.match(code,"^= (.*)$") + if temp then + code = "print("..temp..")" + end + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("! " .. (message or code).."\n") + else + io.write(compiled()) + end + end + end + else + if type(code) ~= "string" or code == "" then + code = filename + end + if code ~= "" then + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("invalid lua code: " .. (message or code)) + return + end + io.write(compiled()) + end + end +end + function runners.gethelp(filename) local url = environment.argument("url") if url and url ~= "" then @@ -17772,6 +18783,15 @@ function runners.gethelp(filename) end end +function runners.systeminfo() + report("architecture : %s",os.platform or "<unset>") + report("operating system : %s",os.name or "<unset>") + report("file architecture : %s",os.type or "<unset>") + report("binary path : %s",os.selfdir or "<unset>") + report("binary suffix : %s",os.binsuffix or "<unset>") + report("library suffix : %s",os.libsuffix or "<unset>") +end + -- this is a bit dirty ... first we store the first filename and next we -- split the arguments so that we only see the ones meant for this script -- ... later we will use the second half @@ -17887,16 +18907,7 @@ end if e_argument("ansi") then - local formatters = string.formatters - - logs.setformatters { - report_yes = formatters["[1;32m%-15s [0;1m|[0m %s"], - report_nop = formatters["[1;32m%-15s [0;1m|[0m"], - subreport_yes = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m %s"], - subreport_nop = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m"], - status_yes = formatters["[1;32m%-15s [0;1m:[0m %s\n"], - status_nop = formatters["[1;32m%-15s [0;1m:[0m\n"], - } + logs.setformatters("ansi") local script = e_argument("script") or e_argument("scripts") @@ -17921,6 +18932,10 @@ if e_argument("script") or e_argument("scripts") then ok = runners.execute_ctx_script(filename) end +elseif e_argument("evaluate") then + + runners.evaluate(e_argument("evaluate"),filename) + elseif e_argument("selfmerge") then -- embed used libraries @@ -18213,6 +19228,10 @@ elseif e_argument("exporthelp") then runners.loadbase() application.export(e_argument("exporthelp"),filename) +elseif e_argument("systeminfo") then + + runners.systeminfo() + elseif e_argument("help") or filename=='help' or filename == "" then application.help() diff --git a/Master/texmf-dist/scripts/context/stubs/unix/mtxrun b/Master/texmf-dist/scripts/context/stubs/unix/mtxrun index 5cb5c8d2416..48a9a27a250 100755 --- a/Master/texmf-dist/scripts/context/stubs/unix/mtxrun +++ b/Master/texmf-dist/scripts/context/stubs/unix/mtxrun @@ -56,7 +56,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lua"] = package.loaded["l-lua"] or true --- original size: 3409, stripped down to: 1763 +-- original size: 3888, stripped down to: 2197 if not modules then modules={} end modules ['l-lua']={ version=1.001, @@ -139,6 +139,13 @@ end if lua then lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" end +local flush=io.flush +if flush then + local execute=os.execute if execute then function os.execute(...) flush() return execute(...) end end + local exec=os.exec if exec then function os.exec (...) flush() return exec (...) end end + local spawn=os.spawn if spawn then function os.spawn (...) flush() return spawn (...) end end + local popen=io.popen if popen then function io.popen (...) flush() return popen (...) end end +end end -- of closure @@ -437,7 +444,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lpeg"] = package.loaded["l-lpeg"] or true --- original size: 29983, stripped down to: 16202 +-- original size: 36225, stripped down to: 19891 if not modules then modules={} end modules ['l-lpeg']={ version=1.001, @@ -498,6 +505,8 @@ patterns.utfbom_16_le=utfbom_16_le patterns.utfbom_8=utfbom_8 patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") +patterns.utf_32_be_nl=P("\000\000\000\r\000\000\000\n")+P("\000\000\000\r")+P("\000\000\000\n") +patterns.utf_32_le_nl=P("\r\000\000\000\n\000\000\000")+P("\r\000\000\000")+P("\n\000\000\000") patterns.utf8one=R("\000\127") patterns.utf8two=R("\194\223")*utf8next patterns.utf8three=R("\224\239")*utf8next*utf8next @@ -527,9 +536,21 @@ patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) +local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) +local e_collapser=Cs((whitespace^1*P(-1)/""+nonwhitespace^1+whitespace^1/" ")^0) +local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) +local b_stripper=Cs(spacer^0/""*(nonspacer^1+spacer^1/" ")^0) +local e_stripper=Cs((spacer^1*P(-1)/""+nonspacer^1+spacer^1/" ")^0) +local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser +patterns.b_collapser=b_collapser +patterns.m_collapser=m_collapser +patterns.e_collapser=e_collapser +patterns.b_stripper=b_stripper +patterns.m_stripper=m_stripper +patterns.e_stripper=e_stripper patterns.lowercase=lowercase patterns.uppercase=uppercase patterns.letter=patterns.lowercase+patterns.uppercase @@ -566,9 +587,12 @@ patterns.integer=sign^-1*digit^1 patterns.unsigned=digit^0*period*digit^1 patterns.float=sign^-1*patterns.unsigned patterns.cunsigned=digit^0*comma*digit^1 +patterns.cpunsigned=digit^0*(period+comma)*digit^1 patterns.cfloat=sign^-1*patterns.cunsigned +patterns.cpfloat=sign^-1*patterns.cpunsigned patterns.number=patterns.float+patterns.integer patterns.cnumber=patterns.cfloat+patterns.integer +patterns.cpnumber=patterns.cpfloat+patterns.integer patterns.oct=zero*octdigit^1 patterns.octal=patterns.oct patterns.HEX=zero*P("X")*(digit+uppercase)^1 @@ -985,23 +1009,40 @@ function lpeg.append(list,pp,delayed,checked) end return p end +local p_false=P(false) +local p_true=P(true) local function make(t) - local p + local function making(t) + local p=p_false + local keys=sortedkeys(t) + for i=1,#keys do + local k=keys[i] + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then + else + p=p+P(k)*making(v) + end + end + end + if t[""] then + p=p+p_true + end + return p + end + local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] - local v=t[k] - if not p then - if next(v) then - p=P(k)*make(v) - else - p=P(k) - end - else - if next(v) then - p=p+P(k)*make(v) + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then else - p=p+P(k) + p=p+P(k)*making(v) end end end @@ -1009,13 +1050,73 @@ local function make(t) end function lpeg.utfchartabletopattern(list) local tree={} - for i=1,#list do - local t=tree - for c in gmatch(list[i],".") do - if not t[c] then - t[c]={} + local n=#list + if n==0 then + for s in next,list do + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true + end + end + else + for i=1,n do + local s=list[i] + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true end - t=t[c] end end return make(tree) @@ -1055,6 +1156,65 @@ local case_2=period*(digit-trailingzeros)^1*(trailingzeros/"") local number=digit^1*(case_1+case_2) local stripper=Cs((number+1)^0) lpeg.patterns.stripzeros=stripper +local byte_to_HEX={} +local byte_to_hex={} +local byte_to_dec={} +local hex_to_byte={} +for i=0,255 do + local H=format("%02X",i) + local h=format("%02x",i) + local d=format("%03i",i) + local c=char(i) + byte_to_HEX[c]=H + byte_to_hex[c]=h + byte_to_dec[c]=d + hex_to_byte[h]=c + hex_to_byte[H]=c +end +local hextobyte=P(2)/hex_to_byte +local bytetoHEX=P(1)/byte_to_HEX +local bytetohex=P(1)/byte_to_hex +local bytetodec=P(1)/byte_to_dec +local hextobytes=Cs(hextobyte^0) +local bytestoHEX=Cs(bytetoHEX^0) +local bytestohex=Cs(bytetohex^0) +local bytestodec=Cs(bytetodec^0) +patterns.hextobyte=hextobyte +patterns.bytetoHEX=bytetoHEX +patterns.bytetohex=bytetohex +patterns.bytetodec=bytetodec +patterns.hextobytes=hextobytes +patterns.bytestoHEX=bytestoHEX +patterns.bytestohex=bytestohex +patterns.bytestodec=bytestodec +function string.toHEX(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestoHEX,s) + end +end +function string.tohex(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestohex,s) + end +end +function string.todec(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestodec,s) + end +end +function string.tobytes(s) + if not s or s=="" then + return s + else + return lpegmatch(hextobytes,s) + end +end end -- of closure @@ -1082,7 +1242,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-string"] = package.loaded["l-string"] or true --- original size: 5671, stripped down to: 2827 +-- original size: 5694, stripped down to: 2827 if not modules then modules={} end modules ['l-string']={ version=1.001, @@ -1187,7 +1347,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-table"] = package.loaded["l-table"] or true --- original size: 31860, stripped down to: 20846 +-- original size: 33830, stripped down to: 21894 if not modules then modules={} end modules ['l-table']={ version=1.001, @@ -1230,11 +1390,12 @@ function table.keys(t) end end local function compare(a,b) - local ta,tb=type(a),type(b) - if ta==tb then + local ta=type(a) + local tb=type(b) + if ta==tb and ta=="number" then return a<b else - return tostring(a)<tostring(b) + return tostring(a)<tostring(b) end end local function sortedkeys(tab) @@ -1334,14 +1495,14 @@ local function sortedhash(t,cmp) end local n=0 local m=#s - local function kv(s) + local function kv() if n<m then n=n+1 local k=s[n] return k,t[k] end end - return kv,s + return kv else return nothing end @@ -1506,7 +1667,7 @@ local function simple_table(t) if tv=="number" then nt=nt+1 if hexify then - tt[nt]=format("0x%04X",v) + tt[nt]=format("0x%X",v) else tt[nt]=tostring(v) end @@ -1537,7 +1698,7 @@ local function do_serialize(root,name,depth,level,indexed) local tn=type(name) if tn=="number" then if hexify then - handle(format("%s[0x%04X]={",depth,name)) + handle(format("%s[0x%X]={",depth,name)) else handle(format("%s[%s]={",depth,name)) end @@ -1554,7 +1715,7 @@ local function do_serialize(root,name,depth,level,indexed) end end end - if root and next(root) then + if root and next(root)~=nil then local first,last=nil,0 if compact then last=#root @@ -1576,7 +1737,7 @@ local function do_serialize(root,name,depth,level,indexed) if compact and first and tk=="number" and k>=first and k<=last then if tv=="number" then if hexify then - handle(format("%s 0x%04X,",depth,v)) + handle(format("%s 0x%X,",depth,v)) else handle(format("%s %s,",depth,v)) end @@ -1587,7 +1748,7 @@ local function do_serialize(root,name,depth,level,indexed) handle(format("%s %q,",depth,v)) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then handle(format("%s {},",depth)) elseif inline then local st=simple_table(v) @@ -1617,25 +1778,25 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="number" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=0x%04X,",depth,k,v)) + handle(format("%s [0x%X]=0x%X,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end elseif tk=="boolean" then if hexify then - handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v)) + handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v)) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then - handle(format("%s %s=0x%04X,",depth,k,v)) + handle(format("%s %s=0x%X,",depth,k,v)) else handle(format("%s %s=%s,",depth,k,v)) end else if hexify then - handle(format("%s [%q]=0x%04X,",depth,k,v)) + handle(format("%s [%q]=0x%X,",depth,k,v)) else handle(format("%s [%q]=%s,",depth,k,v)) end @@ -1644,7 +1805,7 @@ local function do_serialize(root,name,depth,level,indexed) if reduce and tonumber(v) then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v)) + handle(format("%s [0x%X]=%s,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end @@ -1658,7 +1819,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,v)) + handle(format("%s [0x%X]=%q,",depth,k,v)) else handle(format("%s [%s]=%q,",depth,k,v)) end @@ -1671,10 +1832,10 @@ local function do_serialize(root,name,depth,level,indexed) end end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={},",depth,k)) + handle(format("%s [0x%X]={},",depth,k)) else handle(format("%s [%s]={},",depth,k)) end @@ -1690,7 +1851,7 @@ local function do_serialize(root,name,depth,level,indexed) if st then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", "))) + handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%s]={ %s },",depth,k,concat(st,", "))) end @@ -1710,7 +1871,7 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="boolean" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false")) + handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%s]=%s,",depth,k,v and "true" or "false")) end @@ -1726,7 +1887,7 @@ local function do_serialize(root,name,depth,level,indexed) local f=getinfo(v).what=="C" and dump(dummy) or dump(v) if tk=="number" then if hexify then - handle(format("%s [0x%04X]=load(%q),",depth,k,f)) + handle(format("%s [0x%X]=load(%q),",depth,k,f)) else handle(format("%s [%s]=load(%q),",depth,k,f)) end @@ -1741,7 +1902,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,tostring(v))) + handle(format("%s [0x%X]=%q,",depth,k,tostring(v))) else handle(format("%s [%s]=%q,",depth,k,tostring(v))) end @@ -1795,7 +1956,7 @@ local function serialize(_handle,root,name,specification) end elseif tname=="number" then if hexify then - handle(format("[0x%04X]={",name)) + handle(format("[0x%X]={",name)) else handle("["..name.."]={") end @@ -1813,7 +1974,7 @@ local function serialize(_handle,root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,"",0) end end @@ -1942,14 +2103,25 @@ local function identical(a,b) end table.identical=identical table.are_equal=are_equal -function table.compact(t) - if t then - for k,v in next,t do - if not next(v) then - t[k]=nil +local function sparse(old,nest,keeptables) + local new={} + for k,v in next,old do + if not (v=="" or v==false) then + if nest and type(v)=="table" then + v=sparse(v,nest) + if keeptables or next(v)~=nil then + new[k]=v + end + else + new[k]=v end end end + return new +end +table.sparse=sparse +function table.compact(t) + return sparse(t,true,true) end function table.contains(t,v) if t then @@ -2054,10 +2226,10 @@ function table.sub(t,i,j) return { unpack(t,i,j) } end function table.is_empty(t) - return not t or not next(t) + return not t or next(t)==nil end function table.has_one_entry(t) - return t and not next(t,next(t)) + return t and next(t,next(t))==nil end function table.loweredkeys(t) local l={} @@ -2102,6 +2274,44 @@ function table.values(t,s) return {} end end +function table.filtered(t,pattern,sort,cmp) + if t and type(pattern)=="string" then + if sort then + local s + if cmp then + s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) + else + s=sortedkeys(t) + end + local n=0 + local m=#s + local function kv(s) + while n<m do + n=n+1 + local k=s[n] + if find(k,pattern) then + return k,t[k] + end + end + end + return kv,s + else + local n=next(t) + local function iterator() + while n~=nil do + local k=n + n=next(t,k) + if find(k,pattern) then + return k,t[k] + end + end + end + return iterator,t + end + else + return nothing + end +end end -- of closure @@ -2110,7 +2320,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-io"] = package.loaded["l-io"] or true --- original size: 8824, stripped down to: 6347 +-- original size: 8643, stripped down to: 6232 if not modules then modules={} end modules ['l-io']={ version=1.001, @@ -2417,8 +2627,6 @@ function io.readstring(f,n,m) local str=gsub(f:read(n),"\000","") return str end -if not io.i_limiter then function io.i_limiter() end end -if not io.o_limiter then function io.o_limiter() end end end -- of closure @@ -2645,7 +2853,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-os"] = package.loaded["l-os"] or true --- original size: 16093, stripped down to: 9704 +-- original size: 15761, stripped down to: 9403 if not modules then modules={} end modules ['l-os']={ version=1.001, @@ -2719,13 +2927,10 @@ if not os.__getenv__ then setmetatable(os.env,{ __index=__index,__newindex=__newindex } ) end end -local execute,spawn,exec,iopopen,ioflush=os.execute,os.spawn or os.execute,os.exec or os.execute,io.popen,io.flush -function os.execute(...) ioflush() return execute(...) end -function os.spawn (...) ioflush() return spawn (...) end -function os.exec (...) ioflush() return exec (...) end -function io.popen (...) ioflush() return iopopen(...) end +local execute=os.execute +local iopopen=io.popen function os.resultof(command) - local handle=io.popen(command,"r") + local handle=iopopen(command,"r") if handle then local result=handle:read("*all") or "" handle:close() @@ -2754,7 +2959,7 @@ local launchers={ unix="$BROWSER %s &> /dev/null &", } function os.launch(str) - os.execute(format(launchers[os.name] or launchers.unix,str)) + execute(format(launchers[os.name] or launchers.unix,str)) end if not os.times then function os.times() @@ -3029,7 +3234,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-file"] = package.loaded["l-file"] or true --- original size: 18308, stripped down to: 9948 +-- original size: 20949, stripped down to: 9945 if not modules then modules={} end modules ['l-file']={ version=1.001, @@ -3043,41 +3248,28 @@ local file=file if not lfs then lfs=optionalrequire("lfs") end -if not lfs then - lfs={ - getcurrentdir=function() - return "." - end, - attributes=function() - return nil - end, - isfile=function(name) - local f=io.open(name,'rb') - if f then - f:close() - return true - end - end, - isdir=function(name) - print("you need to load lfs") - return false - end - } -elseif not lfs.isfile then - local attributes=lfs.attributes - function lfs.isdir(name) - return attributes(name,"mode")=="directory" - end - function lfs.isfile(name) - return attributes(name,"mode")=="file" - end -end local insert,concat=table.insert,table.concat local match,find,gmatch=string.match,string.find,string.gmatch local lpegmatch=lpeg.match local getcurrentdir,attributes=lfs.currentdir,lfs.attributes local checkedsplit=string.checkedsplit local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct +local tricky=S("/\\")*P(-1) +local attributes=lfs.attributes +if sandbox then + sandbox.redefine(lfs.isfile,"lfs.isfile") + sandbox.redefine(lfs.isdir,"lfs.isdir") +end +function lfs.isdir(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end +end +function lfs.isfile(name) + return attributes(name,"mode")=="file" +end local colon=P(":") local period=P(".") local periods=P("..") @@ -3279,28 +3471,30 @@ local isroot=fwslash^1*-1 local hasroot=fwslash^1 local reslasher=lpeg.replacer(S("\\/"),"/") local deslasher=lpeg.replacer(S("\\/")^1,"/") -function file.join(...) - local lst={... } - local one=lst[1] +function file.join(one,two,three,...) + if not two then + return one=="" and one or lpegmatch(stripper,one) + end + if one=="" then + return lpegmatch(stripper,three and concat({ two,three,... },"/") or two) + end if lpegmatch(isnetwork,one) then local one=lpegmatch(reslasher,one) - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return one..two else return one.."/"..two end elseif lpegmatch(isroot,one) then - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return two else return "/"..two end - elseif one=="" then - return lpegmatch(stripper,concat(lst,"/",2)) else - return lpegmatch(deslasher,concat(lst,"/")) + return lpegmatch(deslasher,concat({ one,two,three,... },"/")) end end local drivespec=R("az","AZ")^1*colon @@ -3474,7 +3668,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-md5"] = package.loaded["l-md5"] or true --- original size: 3760, stripped down to: 2088 +-- original size: 3248, stripped down to: 2266 if not modules then modules={} end modules ['l-md5']={ version=1.001, @@ -3492,14 +3686,20 @@ if not md5 then } end local md5,file=md5,file -local gsub,format,byte=string.gsub,string.format,string.byte -local md5sum=md5.sum -local function convert(str,fmt) - return (gsub(md5sum(str),".",function(chr) return format(fmt,byte(chr)) end)) -end -if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end -if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end -if not md5.dec then function md5.dec(str) return convert(str,"%03i") end end +local gsub=string.gsub +do + local patterns=lpeg and lpeg.patterns + if patterns then + local bytestoHEX=patterns.bytestoHEX + local bytestohex=patterns.bytestohex + local bytestodec=patterns.bytestodec + local lpegmatch=lpeg.match + local md5sum=md5.sum + if not md5.HEX then function md5.HEX(str) if str then return lpegmatch(bytestoHEX,md5sum(str)) end end end + if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end + if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end + end +end function file.needsupdating(oldname,newname,threshold) local oldtime=lfs.attributes(oldname,"modification") if oldtime then @@ -3556,7 +3756,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-url"] = package.loaded["l-url"] or true --- original size: 12292, stripped down to: 5585 +-- original size: 12531, stripped down to: 5721 if not modules then modules={} end modules ['l-url']={ version=1.001, @@ -3642,19 +3842,25 @@ local splitquery=Cf (Ct("")*P { "sequence", pair=Cg(key*equal*value), },rawset) local function hashed(str) - if str=="" then + if not str or str=="" then return { scheme="invalid", original=str, } end - local s=split(str) - local rawscheme=s[1] - local rawquery=s[4] - local somescheme=rawscheme~="" - local somequery=rawquery~="" + local detailed=split(str) + local rawscheme="" + local rawquery="" + local somescheme=false + local somequery=false + if detailed then + rawscheme=detailed[1] + rawquery=detailed[4] + somescheme=rawscheme~="" + somequery=rawquery~="" + end if not somescheme and not somequery then - s={ + return { scheme="file", authority="", path=str, @@ -3664,28 +3870,28 @@ local function hashed(str) noscheme=true, filename=str, } - else - local authority,path,filename=s[2],s[3] - if authority=="" then - filename=path - elseif path=="" then - filename="" - else - filename=authority.."/"..path - end - s={ - scheme=rawscheme, - authority=authority, - path=path, - query=lpegmatch(unescaper,rawquery), - queries=lpegmatch(splitquery,rawquery), - fragment=s[5], - original=str, - noscheme=false, - filename=filename, - } end - return s + local authority=detailed[2] + local path=detailed[3] + local filename=nil + if authority=="" then + filename=path + elseif path=="" then + filename="" + else + filename=authority.."/"..path + end + return { + scheme=rawscheme, + authority=authority, + path=path, + query=lpegmatch(unescaper,rawquery), + queries=lpegmatch(splitquery,rawquery), + fragment=detailed[5], + original=str, + noscheme=false, + filename=filename, + } end url.split=split url.hasscheme=hasscheme @@ -3719,7 +3925,7 @@ function url.construct(hash) end return lpegmatch(escaper,concat(fullurl)) end -local pattern=Cs(noslash*R("az","AZ")*(S(":|")/":")*noslash*P(1)^0) +local pattern=Cs(slash^-1/""*R("az","AZ")*((S(":|")/":")+P(":"))*slash*P(1)^0) function url.filename(filename) local spec=hashed(filename) local path=spec.path @@ -3767,7 +3973,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-dir"] = package.loaded["l-dir"] or true --- original size: 14788, stripped down to: 9096 +-- original size: 16765, stripped down to: 11003 if not modules then modules={} end modules ['l-dir']={ version=1.001, @@ -3777,7 +3983,7 @@ if not modules then modules={} end modules ['l-dir']={ license="see context related readme files" } local type,select=type,select -local find,gmatch,match,gsub=string.find,string.gmatch,string.match,string.gsub +local find,gmatch,match,gsub,sub=string.find,string.gmatch,string.match,string.gsub,string.sub local concat,insert,remove,unpack=table.concat,table.insert,table.remove,table.unpack local lpegmatch=lpeg.match local P,S,R,C,Cc,Cs,Ct,Cv,V=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cv,lpeg.V @@ -3786,53 +3992,126 @@ local dir=dir local lfs=lfs local attributes=lfs.attributes local walkdir=lfs.dir -local isdir=lfs.isdir -local isfile=lfs.isfile +local isdir=lfs.isdir +local isfile=lfs.isfile local currentdir=lfs.currentdir local chdir=lfs.chdir local mkdir=lfs.mkdir local onwindows=os.type=="windows" or find(os.getenv("PATH"),";",1,true) -if not isdir then - function isdir(name) - local a=attributes(name) - return a and a.mode=="directory" +if onwindows then + local tricky=S("/\\")*P(-1) + isdir=function(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end + end + isfile=function(name) + return attributes(name,"mode")=="file" end lfs.isdir=isdir -end -if not isfile then - function isfile(name) - local a=attributes(name) - return a and a.mode=="file" + lfs.isfile=isfile +else + isdir=function(name) + return attributes(name,"mode")=="directory" end + isfile=function(name) + return attributes(name,"mode")=="file" + end + lfs.isdir=isdir lfs.isfile=isfile end function dir.current() return (gsub(currentdir(),"\\","/")) end -local lfsisdir=isdir -local function isdir(path) - path=gsub(path,"[/\\]+$","") - return lfsisdir(path) +local function glob_pattern_function(path,patt,recurse,action) + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + action(full) + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end + end + end + end + if dirs then + for i=1,#dirs do + glob_pattern_function(dirs[i],patt,recurse,action) + end + end + end end -lfs.isdir=isdir -local function globpattern(path,patt,recurse,action) - if path=="/" then - path=path.."." - elseif not find(path,"/$") then - path=path..'/' - end - if isdir(path) then - for name in walkdir(path) do - local full=path..name - local mode=attributes(full,'mode') - if mode=='file' then - if find(full,patt) then - action(full) +local function glob_pattern_table(path,patt,recurse,result) + if not result then + result={} + end + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + result[#result+1]=full + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - globpattern(full,patt,recurse,action) end end + if dirs then + for i=1,#dirs do + glob_pattern_table(dirs[i],patt,recurse,result) + end + end + end + return result +end +local function globpattern(path,patt,recurse,method) + local kind=type(method) + if patt and sub(patt,1,-3)==path then + patt=false + end + if kind=="function" then + return glob_pattern_function(path,patt,recurse,method) + elseif kind=="table" then + return glob_pattern_table(path,patt,recurse,method) + else + return glob_pattern_table(path,patt,recurse,{}) end end dir.globpattern=globpattern @@ -3845,34 +4124,40 @@ local function collectpattern(path,patt,recurse,result) ok,scanner,first=xpcall(function() return walkdir(path) end,function() end) end if ok and type(scanner)=="function" then - if not find(path,"/$") then path=path..'/' end + if not find(path,"/$") then + path=path..'/' + end for name in scanner,first do - local full=path..name - local attr=attributes(full) - local mode=attr.mode - if mode=='file' then - if find(full,patt) then + if name=="." then + elseif name==".." then + else + local full=path..name + local attr=attributes(full) + local mode=attr.mode + if mode=='file' then + if find(full,patt) then + result[name]=attr + end + elseif recurse and mode=="directory" then + attr.list=collectpattern(full,patt,recurse) result[name]=attr end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - attr.list=collectpattern(full,patt,recurse) - result[name]=attr end end end return result end dir.collectpattern=collectpattern -local separator -if onwindows then +local separator,pattern +if onwindows then local slash=S("/\\")/"/" - pattern=Ct { + pattern={ [1]=(Cs(P(".")+slash^1)+Cs(R("az","AZ")*P(":")*slash^0)+Cc("./"))*V(2)*V(3), [2]=Cs(((1-S("*?/\\"))^0*slash)^0), [3]=Cs(P(1)^0) } -else - pattern=Ct { +else + pattern={ [1]=(C(P(".")+P("/")^1)+Cc("./"))*V(2)*V(3), [2]=C(((1-S("*?/"))^0*P("/"))^0), [3]=C(P(1)^0) @@ -3890,9 +4175,8 @@ local function glob(str,t) elseif isfile(str) then t(str) else - local split=lpegmatch(pattern,str) - if split then - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) @@ -3914,16 +4198,12 @@ local function glob(str,t) return { str } end else - local split=lpegmatch(pattern,str) - if split then - local t=t or {} - local action=action or function(name) t[#t+1]=name end - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) - globpattern(start,result,recurse,action) - return t + return globpattern(start,result,recurse,t) else return {} end @@ -4071,47 +4351,51 @@ else end end dir.makedirs=dir.mkdirs -if onwindows then - function dir.expandname(str) - local first,nothing,last=match(str,"^(//)(//*)(.*)$") - if first then - first=dir.current().."/" - end - if not first then - first,last=match(str,"^(//)/*(.*)$") - end - if not first then - first,last=match(str,"^([a-zA-Z]:)(.*)$") - if first and not find(last,"^/") then - local d=currentdir() - if chdir(first) then - first=dir.current() +do + local chdir=sandbox and sandbox.original(chdir) or chdir + if onwindows then + local xcurrentdir=dir.current + function dir.expandname(str) + local first,nothing,last=match(str,"^(//)(//*)(.*)$") + if first then + first=xcurrentdir().."/" + end + if not first then + first,last=match(str,"^(//)/*(.*)$") + end + if not first then + first,last=match(str,"^([a-zA-Z]:)(.*)$") + if first and not find(last,"^/") then + local d=currentdir() + if chdir(first) then + first=xcurrentdir() + end + chdir(d) end - chdir(d) + end + if not first then + first,last=xcurrentdir(),str + end + last=gsub(last,"//","/") + last=gsub(last,"/%./","/") + last=gsub(last,"^/*","") + first=gsub(first,"/*$","") + if last=="" or last=="." then + return first + else + return first.."/"..last end end - if not first then - first,last=dir.current(),str - end - last=gsub(last,"//","/") - last=gsub(last,"/%./","/") - last=gsub(last,"^/*","") - first=gsub(first,"/*$","") - if last=="" or last=="." then - return first - else - return first.."/"..last - end - end -else - function dir.expandname(str) - if not find(str,"^/") then - str=currentdir().."/"..str + else + function dir.expandname(str) + if not find(str,"^/") then + str=currentdir().."/"..str + end + str=gsub(str,"//","/") + str=gsub(str,"/%./","/") + str=gsub(str,"(.)/%.$","%1") + return str end - str=gsub(str,"//","/") - str=gsub(str,"/%./","/") - str=gsub(str,"(.)/%.$","%1") - return str end end file.expandname=dir.expandname @@ -4154,7 +4438,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-boolean"] = package.loaded["l-boolean"] or true --- original size: 1809, stripped down to: 1527 +-- original size: 1850, stripped down to: 1568 if not modules then modules={} end modules ['l-boolean']={ version=1.001, @@ -4208,11 +4492,11 @@ function string.booleanstring(str) return str=="yes" or str=="on" or str=="t" end end -function string.is_boolean(str,default) +function string.is_boolean(str,default,strict) if type(str)=="string" then - if str=="true" or str=="yes" or str=="on" or str=="t" or str=="1" then + if str=="true" or str=="yes" or str=="on" or str=="t" or (not strict and str=="1") then return true - elseif str=="false" or str=="no" or str=="off" or str=="f" or str=="0" then + elseif str=="false" or str=="no" or str=="off" or str=="f" or (not strict and str=="0") then return false end end @@ -4226,7 +4510,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-unicode"] = package.loaded["l-unicode"] or true --- original size: 33706, stripped down to: 14938 +-- original size: 37233, stripped down to: 15684 if not modules then modules={} end modules ['l-unicode']={ version=1.001, @@ -4242,7 +4526,9 @@ local type=type local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch local concat=table.concat local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp -local lpegmatch,patterns=lpeg.match,lpeg.patterns +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern local bytepairs=string.bytepairs local finder=lpeg.finder local replacer=lpeg.replacer @@ -4251,7 +4537,7 @@ local utfgmatch=utf.gmatch local p_utftype=patterns.utftype local p_utfstricttype=patterns.utfstricttype local p_utfoffset=patterns.utfoffset -local p_utf8char=patterns.utf8char +local p_utf8char=patterns.utf8character local p_utf8byte=patterns.utf8byte local p_utfbom=patterns.utfbom local p_newline=patterns.newline @@ -4450,15 +4736,52 @@ if not utf.sub then end end end -function utf.remapper(mapping) - local pattern=Cs((p_utf8char/mapping)^0) - return function(str) - if not str or str=="" then - return "" +function utf.remapper(mapping,option) + local variant=type(mapping) + if variant=="table" then + if option=="dynamic" then + local pattern=false + table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) + return function(str) + if not str or str=="" then + return "" + else + if not pattern then + pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + end + return lpegmatch(pattern,str) + end + end + elseif option=="pattern" then + return Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) else - return lpegmatch(pattern,str) + local pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + elseif variant=="function" then + if option=="pattern" then + return Cs((p_utf8char/mapping+p_utf8char)^0) + else + local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + else + return function(str) + return str or "" end - end,pattern + end end function utf.replacer(t) local r=replacer(t,false,false,true) @@ -4508,190 +4831,157 @@ function utf.magic(f) end local utf16_to_utf8_be,utf16_to_utf8_le local utf32_to_utf8_be,utf32_to_utf8_le -local utf_16_be_linesplitter=patterns.utfbom_16_be^-1*lpeg.tsplitat(patterns.utf_16_be_nl) -local utf_16_le_linesplitter=patterns.utfbom_16_le^-1*lpeg.tsplitat(patterns.utf_16_le_nl) -if bytepairs then - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*left+right - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_be,s) + else + return s end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*right+left - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +end +local utf16_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_be_linesplitter,t) end - utf32_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*256*256*a+256*256*b - else - r=r+1 - result[t]=utfchar(more+256*a+b) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_be,s) end - return t end - utf32_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*b+a - else - r=r+1 - result[t]=utfchar(more+256*256*256*b+256*256*a) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) - end - return t + return t +end +utf16_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_le,s) + else + return s end -else - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if left=="\000" then - r=r+1 - result[r]=utfchar(byte(right)) - elseif right then - local now=256*byte(left)+byte(right) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) +end +local utf16_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_le,s) end - return t end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) + return t +end +utf32_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_be,s) + else + return s + end +end +local utf32_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_be,s) end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if right=="\000" then - r=r+1 - result[r]=utfchar(byte(left)) - elseif right then - local now=256*byte(right)+byte(left) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) + end + return t +end +utf32_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_le,s) + else + return s + end +end +local utf32_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_le,s) end - return t end - utf32_to_utf8_le=function() return {} end - utf32_to_utf8_be=function() return {} end + return t end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t utf.utf16_to_utf8_le=utf16_to_utf8_le utf.utf16_to_utf8_be=utf16_to_utf8_be utf.utf32_to_utf8_le=utf32_to_utf8_le utf.utf32_to_utf8_be=utf32_to_utf8_be -function utf.utf8_to_utf8(t) +function utf.utf8_to_utf8_t(t) return type(t)=="string" and lpegmatch(utflinesplitter,t) or t end -function utf.utf16_to_utf8(t,endian) - return endian and utf16_to_utf8_be(t) or utf16_to_utf8_le(t) or t +function utf.utf16_to_utf8_t(t,endian) + return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t end -function utf.utf32_to_utf8(t,endian) - return endian and utf32_to_utf8_be(t) or utf32_to_utf8_le(t) or t +function utf.utf32_to_utf8_t(t,endian) + return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t end -local function little(c) - local b=byte(c) +local function little(b) if b<0x10000 then return char(b%256,b/256) else @@ -4700,8 +4990,7 @@ local function little(c) return char(b1%256,b1/256,b2%256,b2/256) end end -local function big(c) - local b=byte(c) +local function big(b) if b<0x10000 then return char(b/256,b%256) else @@ -4710,27 +4999,29 @@ local function big(c) return char(b1/256,b1%256,b2/256,b2%256) end end -local _,l_remap=utf.remapper(little) -local _,b_remap=utf.remapper(big) -function utf.utf8_to_utf16_be(str,nobom) +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) if nobom then return lpegmatch(b_remap,str) else return char(254,255)..lpegmatch(b_remap,str) end end -function utf.utf8_to_utf16_le(str,nobom) +local function utf8_to_utf16_le(str,nobom) if nobom then return lpegmatch(l_remap,str) else return char(255,254)..lpegmatch(l_remap,str) end end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le function utf.utf8_to_utf16(str,littleendian,nobom) if littleendian then - return utf.utf8_to_utf16_le(str,nobom) + return utf8_to_utf16_le(str,nobom) else - return utf.utf8_to_utf16_be(str,nobom) + return utf8_to_utf16_be(str,nobom) end end local pattern=Cs ( @@ -4746,16 +5037,16 @@ function utf.xstring(s) return format("0x%05X",type(s)=="number" and s or utfbyte(s)) end function utf.toeight(str) - if not str then + if not str or str=="" then return nil end local utftype=lpegmatch(p_utfstricttype,str) if utftype=="utf-8" then - return sub(str,4) - elseif utftype=="utf-16-le" then - return utf16_to_utf8_le(str) + return sub(str,4) elseif utftype=="utf-16-be" then - return utf16_to_utf8_ne(str) + return utf16_to_utf8_be(str) + elseif utftype=="utf-16-le" then + return utf16_to_utf8_le(str) else return str end @@ -4834,7 +5125,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-math"] = package.loaded["l-math"] or true --- original size: 915, stripped down to: 836 +-- original size: 974, stripped down to: 890 if not modules then modules={} end modules ['l-math']={ version=1.001, @@ -4844,6 +5135,9 @@ if not modules then modules={} end modules ['l-math']={ license="see context related readme files" } local floor,sin,cos,tan=math.floor,math.sin,math.cos,math.tan +if not math.ceiling then + math.ceiling=math.ceil +end if not math.round then function math.round(x) return floor(x+0.5) end end @@ -4871,7 +5165,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-str"] = package.loaded["util-str"] or true --- original size: 33063, stripped down to: 18397 +-- original size: 34407, stripped down to: 18852 if not modules then modules={} end modules ['util-str']={ version=1.001, @@ -5001,10 +5295,12 @@ local striplinepatterns={ ["retain"]=p_retain_normal, ["retain and collapse"]=p_retain_collapse, ["retain and no empty"]=p_retain_noempty, + ["collapse"]=patterns.collapser, } +setmetatable(striplinepatterns,{ __index=function(t,k) return p_prune_collapse end }) strings.striplinepatterns=striplinepatterns function strings.striplines(str,how) - return str and lpegmatch(how and striplinepatterns[how] or p_prune_collapse,str) or str + return str and lpegmatch(striplinepatterns[how],str) or str end strings.striplong=strings.striplines function strings.nice(str) @@ -5044,10 +5340,10 @@ string.tracedchars=tracedchars strings.tracers=tracedchars function string.tracedchar(b) if type(b)=="number" then - return tracedchars[b] or (utfchar(b).." (U+"..format('%05X',b)..")") + return tracedchars[b] or (utfchar(b).." (U+"..format("%05X",b)..")") else local c=utfbyte(b) - return tracedchars[c] or (b.." (U+"..format('%05X',c)..")") + return tracedchars[c] or (b.." (U+"..(c and format("%05X",c) or "?????")..")") end end function number.signed(i) @@ -5184,12 +5480,12 @@ local format_f=function(f) n=n+1 return format("format('%%%sf',a%s)",f,n) end -local format_F=function(f) +local format_F=function(f) n=n+1 if not f or f=="" then - return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or (a%s == 1 and '1') or format('%%.9f',a%s))",n,n,n,n) + return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n) else - return format("((a%s == 0 and '0') or (a%s == 1 and '1') or format('%%%sf',a%s))",n,n,f,n) + return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n) end end local format_g=function(f) @@ -5417,7 +5713,6 @@ local builder=Cs { "start", +V("j")+V("J") +V("m")+V("M") +V("z") -+V("*") )+V("*") )*(P(-1)+Carg(1)) )^0, @@ -5461,6 +5756,7 @@ local builder=Cs { "start", ["a"]=(prefix_any*P("a"))/format_a, ["A"]=(prefix_any*P("A"))/format_A, ["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest, + ["?"]=Cs(((1-P("%"))^1 )^1)/format_rest, ["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension, } local direct=Cs ( @@ -5535,6 +5831,15 @@ else add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=lpeg.patterns.texescape }) add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=lpeg.patterns.luaescape }) end +local dquote=patterns.dquote +local equote=patterns.escaped+dquote/'\\"'+1 +local space=patterns.space +local cquote=Cc('"') +local pattern=Cs(dquote*(equote-P(-2))^0*dquote) ++Cs(cquote*(equote-space)^0*space*equote^0*cquote) +function string.optionalquoted(str) + return lpegmatch(pattern,str) or str +end end -- of closure @@ -5543,7 +5848,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 23985, stripped down to: 16069 +-- original size: 24267, stripped down to: 16260 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -5633,6 +5938,15 @@ function tables.removevalue(t,value) end end end +function tables.replacevalue(t,oldvalue,newvalue) + if oldvalue and newvalue then + for i=1,#t do + if t[i]==oldvalue then + t[i]=newvalue + end + end + end +end function tables.insertbeforevalue(t,value,extra) for i=1,#t do if t[i]==extra then @@ -6026,7 +6340,7 @@ function table.serialize(root,name,specification) end depth=depth+1 end - if root and next(root) then + if root and next(root)~=nil then local first=nil local last=0 last=#root @@ -6051,7 +6365,7 @@ function table.serialize(root,name,specification) elseif tv=="string" then n=n+1 t[n]=f_val_str(depth,v) elseif tv=="table" then - if not next(v) then + if next(v)==nil then n=n+1 t[n]=f_val_not(depth) else local st=simple_table(v) @@ -6081,7 +6395,7 @@ function table.serialize(root,name,specification) n=n+1 t[n]=f_key_boo_value_str(depth,k,v) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then n=n+1 t[n]=f_key_num_value_not(depth,k,v) elseif tk=="string" then @@ -6139,7 +6453,7 @@ function table.serialize(root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,1,0) end end @@ -6302,7 +6616,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-prs"] = package.loaded["util-prs"] or true --- original size: 19618, stripped down to: 14012 +-- original size: 21751, stripped down to: 15108 if not modules then modules={} end modules ['util-prs']={ version=1.001, @@ -6458,11 +6772,30 @@ function parsers.settings_to_array(str,strict) return { str } end end -local separator=space^0*comma*space^0 -local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(comma+P(-1)))))^0) -local withvalue=Carg(1)*value/function(f,s) return f(s) end -local pattern_a=spaces*Ct(value*(separator*value)^0) -local pattern_b=spaces*withvalue*(separator*withvalue)^0 +local cache_a={} +local cache_b={} +function parsers.groupedsplitat(symbol,withaction) + if not symbol then + symbol="," + end + local pattern=(withaction and cache_b or cache_a)[symbol] + if not pattern then + local symbols=S(symbol) + local separator=space^0*symbols*space^0 + local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(symbols+P(-1)))))^0) + if withaction then + local withvalue=Carg(1)*value/function(f,s) return f(s) end + pattern=spaces*withvalue*(separator*withvalue)^0 + cache_b[symbol]=pattern + else + pattern=spaces*Ct(value*(separator*value)^0) + cache_a[symbol]=pattern + end + end + return pattern +end +local pattern_a=parsers.groupedsplitat(",",false) +local pattern_b=parsers.groupedsplitat(",",true) function parsers.stripped_settings_to_array(str) if not str or str=="" then return {} @@ -6524,12 +6857,9 @@ function parsers.array_to_string(a,separator) return "" end end -function parsers.settings_to_set(str,t) - t=t or {} - for s in gmatch(str,"[^, ]+") do - t[s]=true - end - return t +local pattern=Cf(Ct("")*Cg(C((1-S(", "))^1)*S(", ")^0*Cc(true))^1,rawset) +function utilities.parsers.settings_to_set(str,t) + return str and lpegmatch(pattern,str) or {} end function parsers.simple_hash_to_string(h,separator) local t,tn={},0 @@ -6541,12 +6871,16 @@ function parsers.simple_hash_to_string(h,separator) end return concat(t,separator or ",") end -local str=C((1-whitespace-equal)^1) +local str=Cs(lpegpatterns.unquoted)+C((1-whitespace-equal)^1) local setting=Cf(Carg(1)*(whitespace^0*Cg(str*whitespace^0*(equal*whitespace^0*str+Cc(""))))^1,rawset) local splitter=setting^1 function utilities.parsers.options_to_hash(str,target) return str and lpegmatch(splitter,str,1,target or {}) or {} end +local splitter=lpeg.tsplitat(" ") +function utilities.parsers.options_to_array(str) + return str and lpegmatch(splitter,str) or {} +end local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C(digit^1*lparent*(noparent+nestedparents)^1*rparent)+C((nestedbraces+(1-comma))^1) local pattern_a=spaces*Ct(value*(separator*value)^0) local function repeater(n,str) @@ -6668,7 +7002,7 @@ function parsers.rfc4180splitter(specification) local field=escaped+non_escaped+Cc("") local record=Ct(field*(separator*field)^1) local headerline=record*Cp() - local wholeblob=Ct((newline^-1*record)^0) + local wholeblob=Ct((newline^(specification.strict and -1 or 1)*record)^0) return function(data,getheader) if getheader then local header,position=lpegmatch(headerline,data) @@ -6705,13 +7039,13 @@ function parsers.stepper(str,n,action) lpegmatch(stepper,str,1,n,action or print) end end -local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) -local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) +local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) +local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) patterns.unittotex=pattern function parsers.unittotex(str,textmode) return lpegmatch(textmode and pattern_text or pattern_math,str) end -local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+P(1))^0) +local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+anything)^0) function parsers.unittoxml(str) return lpegmatch(pattern,str) end @@ -6783,6 +7117,18 @@ function utilities.parsers.runtime(time) local seconds=mod(time,60) return days,hours,minutes,seconds end +local spacing=whitespace^0 +local apply=P("->") +local method=C((1-apply)^1) +local token=lbrace*C((1-rbrace)^1)*rbrace+C(anything^1) +local pattern=spacing*(method*spacing*apply+Carg(1))*spacing*token +function utilities.parsers.splitmethod(str,default) + if str then + return lpegmatch(pattern,str,1,default or false) + else + return default or false,"" + end +end end -- of closure @@ -6872,7 +7218,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-set"] = package.loaded["trac-set"] or true --- original size: 12365, stripped down to: 8799 +-- original size: 12482, stripped down to: 8864 if not modules then modules={} end modules ['trac-set']={ version=1.001, @@ -6900,7 +7246,7 @@ function setters.initialize(filename,name,values) local data=setter.data if data then for key,newvalue in next,values do - local newvalue=is_boolean(newvalue,newvalue) + local newvalue=is_boolean(newvalue,newvalue,true) local functions=data[key] if functions then local oldvalue=functions.value @@ -6954,7 +7300,7 @@ local function set(t,what,newvalue) elseif not value then value=false else - value=is_boolean(value,value) + value=is_boolean(value,value,true) end w=topattern(w,true,true) for name,functions in next,data do @@ -7093,6 +7439,7 @@ function setters.new(name) report=function(...) setters.report (setter,...) end, enable=function(...) enable (setter,...) end, disable=function(...) disable (setter,...) end, + reset=function(...) reset (setter,...) end, register=function(...) register(setter,...) end, list=function(...) list (setter,...) end, show=function(...) show (setter,...) end, @@ -7184,7 +7531,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-log"] = package.loaded["trac-log"] or true --- original size: 25613, stripped down to: 16617 +-- original size: 29359, stripped down to: 20483 if not modules then modules={} end modules ['trac-log']={ version=1.001, @@ -7193,15 +7540,18 @@ if not modules then modules={} end modules ['trac-log']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } +local next,type,select,print=next,type,select,print local write_nl,write=texio and texio.write_nl or print,texio and texio.write or io.write local format,gmatch,find=string.format,string.gmatch,string.find local concat,insert,remove=table.concat,table.insert,table.remove local topattern=string.topattern -local next,type,select=next,type,select local utfchar=utf.char +local datetime=os.date +local openfile=io.open local setmetatableindex=table.setmetatableindex local formatters=string.formatters local texgetcount=tex and tex.getcount +local variant="default" logs=logs or {} local logs=logs local moreinfo=[[ @@ -7211,32 +7561,122 @@ maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context webpage : http://www.pragma-ade.nl / http://tex.aanhet.net wiki : http://contextgarden.net ]] -utilities.strings.formatters.add ( +formatters.add ( formatters,"unichr", [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]] ) -utilities.strings.formatters.add ( +formatters.add ( formatters,"chruni", [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]] ) local function ignore() end setmetatableindex(logs,function(t,k) t[k]=ignore;return ignore end) local report,subreport,status,settarget,setformats,settranslations -local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters +local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters,newline if tex and (tex.jobname or tex.formatname) then - local valueiskey={ __index=function(t,k) t[k]=k return k end } - local target="term and log" + local function useluawrites() + local texio_write_nl=texio.write_nl + local texio_write=texio.write + local io_write=io.write + write_nl=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write_nl("log",...) + texio_write_nl("term","") + io_write(...) + elseif target=="log" then + texio_write_nl("log",...) + elseif target=="term" then + texio_write_nl("term","") + io_write(...) + elseif target~="none" then + texio_write_nl("log",target,...) + texio_write_nl("term","") + io_write(target,...) + end + end + write=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write("log",...) + io_write(...) + elseif target=="log" then + texio_write("log",...) + elseif target=="term" then + io_write(...) + elseif target~="none" then + texio_write("log",target,...) + io_write(target,...) + end + end + texio.write=write + texio.write_nl=write_nl + useluawrites=ignore + end + local whereto="both" + local target=nil + local targets=nil + local formats=table.setmetatableindex("self") + local translations=table.setmetatableindex("self") + local report_yes,subreport_yes,direct_yes,subdirect_yes,status_yes + local report_nop,subreport_nop,direct_nop,subdirect_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s > %s\n"], + report_nop=formatters["%-15s >\n"], + direct_yes=formatters["%-15s > %s"], + direct_nop=formatters["%-15s >"], + subreport_yes=formatters["%-15s > %s > %s\n"], + subreport_nop=formatters["%-15s > %s >\n"], + subdirect_yes=formatters["%-15s > %s > %s"], + subdirect_nop=formatters["%-15s > %s >"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + targets={ + logfile="log", + log="log", + file="log", + console="term", + terminal="term", + both="term and log", + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;33m%-15s [0;1m>[0m %s\n"], + report_nop=formatters["[0;33m%-15s [0;1m>[0m\n"], + direct_yes=formatters["[0;33m%-15s [0;1m>[0m %s"], + direct_nop=formatters["[0;33m%-15s [0;1m>[0m"], + subreport_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"], + subreport_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"], + subdirect_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"], + subdirect_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"], + status_yes=formatters["[0;33m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;33m%-15s [0;1m:[0m\n"], + }, + targets={ + logfile="none", + log="none", + file="none", + console="term", + terminal="term", + both="term", + }, + } + } logs.flush=io.flush - local formats={} setmetatable(formats,valueiskey) - local translations={} setmetatable(translations,valueiskey) writer=function(...) write_nl(target,...) end newline=function() write_nl(target,"\n") end - local report_yes=formatters["%-15s > %s\n"] - local report_nop=formatters["%-15s >\n"] report=function(a,b,c,...) if c then write_nl(target,report_yes(translations[a],formatters[formats[b]](c,...))) @@ -7248,8 +7688,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local direct_yes=formatters["%-15s > %s"] - local direct_nop=formatters["%-15s >"] direct=function(a,b,c,...) if c then return direct_yes(translations[a],formatters[formats[b]](c,...)) @@ -7261,8 +7699,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local subreport_yes=formatters["%-15s > %s > %s\n"] - local subreport_nop=formatters["%-15s > %s >\n"] subreport=function(a,s,b,c,...) if c then write_nl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...))) @@ -7274,8 +7710,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local subdirect_yes=formatters["%-15s > %s > %s"] - local subdirect_nop=formatters["%-15s > %s >"] subdirect=function(a,s,b,c,...) if c then return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...)) @@ -7287,8 +7721,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(target,status_yes(translations[a],formatters[formats[b]](c,...))) @@ -7300,16 +7732,13 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local targets={ - logfile="log", - log="log", - file="log", - console="term", - terminal="term", - both="term and log", - } - settarget=function(whereto) - target=targets[whereto or "both"] or targets.both + settarget=function(askedwhereto) + whereto=askedwhereto or whereto or "both" + target=targets[whereto] + if not target then + whereto="both" + target=targets[whereto] + end if target=="term" or target=="term and log" then logs.flush=io.flush else @@ -7338,21 +7767,74 @@ if tex and (tex.jobname or tex.formatname) then writeline(target,f(...)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - direct_yes=f.direct_yes or direct_yes - direct_nop=f.direct_nop or direct_nop - subdirect_yes=f.subdirect_yes or subdirect_yes - subdirect_nop=f.subdirect_nop or subdirect_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local t=nil + local f=nil + local d=variants.default + if not specification then + elseif type(specification)=="table" then + t=specification.targets + f=specification.formats or specification + else + local v=variants[specification] + if v then + t=v.targets + f=v.formats + variant=specification + end + end + targets=t or d.targets + target=targets[whereto] or target + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + direct_yes=f.direct_yes + direct_nop=f.direct_nop + subdirect_yes=f.subdirect_yes + subdirect_nop=f.subdirect_nop + status_yes=f.status_yes + status_nop=f.status_nop + if variant=="ansi" then + useluawrites() + end + settarget(whereto) + end + setformatters(variant) setlogfile=ignore settimedlog=ignore else + local report_yes,subreport_yes,status_yes + local report_nop,subreport_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s | %s"], + report_nop=formatters["%-15s |"], + subreport_yes=formatters["%-15s | %s | %s"], + subreport_nop=formatters["%-15s | %s |"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;32m%-15s [0;1m|[0m %s"], + report_nop=formatters["[0;32m%-15s [0;1m|[0m"], + subreport_yes=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m %s"], + subreport_nop=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m"], + status_yes=formatters["[0;32m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;32m%-15s [0;1m:[0m\n"], + }, + }, + } logs.flush=ignore writer=function(s) write_nl(s) @@ -7360,8 +7842,6 @@ else newline=function() write_nl("\n") end - local report_yes=formatters["%-15s | %s"] - local report_nop=formatters["%-15s |"] report=function(a,b,c,...) if c then write_nl(report_yes(a,formatters[b](c,...))) @@ -7373,8 +7853,6 @@ else write_nl("") end end - local subreport_yes=formatters["%-15s | %s | %s"] - local subreport_nop=formatters["%-15s | %s |"] subreport=function(a,sub,b,c,...) if c then write_nl(subreport_yes(a,sub,formatters[b](c,...))) @@ -7386,8 +7864,6 @@ else write_nl("") end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(status_yes(a,formatters[b](c,...))) @@ -7412,14 +7888,34 @@ else writeline(f(s)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local f=nil + local d=variants.default + if specification then + if type(specification)=="table" then + f=specification.formats or specification + else + local v=variants[specification] + if v then + f=v.formats + end + end + end + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + status_yes=f.status_yes + status_nop=f.status_nop + end + setformatters(variant) setlogfile=function(name,keepopen) if name and name~="" then local localtime=os.localtime @@ -7672,13 +8168,13 @@ end local simple=logs.reporter("comment") logs.simple=simple logs.simpleline=simple -function logs.setprogram () end -function logs.extendbanner() end -function logs.reportlines () end -function logs.reportbanner() end -function logs.reportline () end -function logs.simplelines () end -function logs.help () end +logs.setprogram=ignore +logs.extendbanner=ignore +logs.reportlines=ignore +logs.reportbanner=ignore +logs.reportline=ignore +logs.simplelines=ignore +logs.help=ignore local Carg,C,lpegmatch=lpeg.Carg,lpeg.C,lpeg.match local p_newline=lpeg.patterns.newline local linewise=( @@ -7755,10 +8251,11 @@ function logs.application(t) end return t end -function logs.system(whereto,process,jobname,category,...) - local message=formatters["%s %s => %s => %s => %s\r"](os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)) +local f_syslog=formatters["%s %s => %s => %s => %s\r"] +function logs.system(whereto,process,jobname,category,fmt,arg,...) + local message=f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg==nil and fmt or format(fmt,arg,...)) for i=1,10 do - local f=io.open(whereto,"a") + local f=openfile(whereto,"a") if f then f:write(message) f:close() @@ -7820,7 +8317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-inf"] = package.loaded["trac-inf"] or true --- original size: 7011, stripped down to: 5590 +-- original size: 6704, stripped down to: 5343 if not modules then modules={} end modules ['trac-inf']={ version=1.001, @@ -7993,15 +8490,6 @@ function statistics.tracefunction(base,tag,...) statistics.register(formatters["%s.%s"](tag,name),function() return serialize(stat,"calls") end) end end -commands=commands or {} -function commands.resettimer(name) - resettiming(name or "whatever") - starttiming(name or "whatever") -end -function commands.elapsedtime(name) - stoptiming(name or "whatever") - context(elapsedtime(name or "whatever")) -end end -- of closure @@ -8568,7 +9056,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tpl"] = package.loaded["util-tpl"] or true --- original size: 6251, stripped down to: 3488 +-- original size: 7100, stripped down to: 3978 if not modules then modules={} end modules ['util-tpl']={ version=1.001, @@ -8610,7 +9098,7 @@ local sqlescape=lpeg.replacer { { "\r\n","\\n" }, { "\r","\\n" }, } -local sqlquoted=lpeg.Cs(lpeg.Cc("'")*sqlescape*lpeg.Cc("'")) +local sqlquoted=Cs(Cc("'")*sqlescape*Cc("'")) lpegpatterns.sqlescape=sqlescape lpegpatterns.sqlquoted=sqlquoted local luaescape=lpegpatterns.luaescape @@ -8633,12 +9121,24 @@ local quotedescapers={ local luaescaper=escapers.lua local quotedluaescaper=quotedescapers.lua local function replacekeyunquoted(s,t,how,recurse) - local escaper=how and escapers[how] or luaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and escapers[how] or luaescaper + return escaper(replacekey(s,t,how,recurse)) + end end local function replacekeyquoted(s,t,how,recurse) - local escaper=how and quotedescapers[how] or quotedluaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and quotedescapers[how] or quotedluaescaper + return escaper(replacekey(s,t,how,recurse)) + end +end +local function replaceoptional(l,m,r,t,how,recurse) + local v=t[l] + return v and v~="" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or "" end local single=P("%") local double=P("%%") @@ -8653,11 +9153,16 @@ local nolquoted=lquoted/'' local norquoted=rquoted/'' local nolquotedq=lquotedq/'' local norquotedq=rquotedq/'' -local key=nosingle*((C((1-nosingle )^1)*Carg(1)*Carg(2)*Carg(3))/replacekey )*nosingle -local quoted=nolquotedq*((C((1-norquotedq)^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyquoted )*norquotedq -local unquoted=nolquoted*((C((1-norquoted )^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyunquoted)*norquoted +local noloptional=P("%?")/'' +local noroptional=P("?%")/'' +local nomoptional=P(":")/'' +local args=Carg(1)*Carg(2)*Carg(3) +local key=nosingle*((C((1-nosingle )^1)*args)/replacekey )*nosingle +local quoted=nolquotedq*((C((1-norquotedq )^1)*args)/replacekeyquoted )*norquotedq +local unquoted=nolquoted*((C((1-norquoted )^1)*args)/replacekeyunquoted)*norquoted +local optional=noloptional*((C((1-nomoptional)^1)*nomoptional*C((1-noroptional)^1)*args)/replaceoptional)*noroptional local any=P(1) - replacer=Cs((unquoted+quoted+escape+key+any)^0) + replacer=Cs((unquoted+quoted+escape+optional+key+any)^0) local function replace(str,mapping,how,recurse) if mapping and str then return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str @@ -8696,7 +9201,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-env"] = package.loaded["util-env"] or true --- original size: 8814, stripped down to: 5092 +-- original size: 8022, stripped down to: 5038 if not modules then modules={} end modules ['util-env']={ version=1.001, @@ -8707,7 +9212,7 @@ if not modules then modules={} end modules ['util-env']={ } local allocate,mark=utilities.storage.allocate,utilities.storage.mark local format,sub,match,gsub,find=string.format,string.sub,string.match,string.gsub,string.find -local unquoted,quoted=string.unquoted,string.quoted +local unquoted,quoted,optionalquoted=string.unquoted,string.quoted,string.optionalquoted local concat,insert,remove=table.concat,table.insert,table.remove environment=environment or {} local environment=environment @@ -8820,24 +9325,14 @@ function environment.splitarguments(separator) return before,after end function environment.reconstructcommandline(arg,noquote) + local resolveprefix=resolvers.resolve arg=arg or environment.originalarguments if noquote and #arg==1 then - local a=arg[1] - a=resolvers.resolve(a) - a=unquoted(a) - return a + return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1]) elseif #arg>0 then local result={} for i=1,#arg do - local a=arg[i] - a=resolvers.resolve(a) - a=unquoted(a) - a=gsub(a,'"','\\"') - if find(a," ",1,true) then - result[#result+1]=quoted(a) - else - result[#result+1]=a - end + result[i]=optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix) end return concat(result," ") else @@ -9046,7 +9541,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-tab"] = package.loaded["lxml-tab"] or true --- original size: 42614, stripped down to: 26694 +-- original size: 43882, stripped down to: 26870 if not modules then modules={} end modules ['lxml-tab']={ version=1.001, @@ -9061,10 +9556,10 @@ if lpeg.setmaxstack then lpeg.setmaxstack(1000) end xml=xml or {} local xml=xml local concat,remove,insert=table.concat,table.remove,table.insert -local type,next,setmetatable,getmetatable,tonumber=type,next,setmetatable,getmetatable,tonumber +local type,next,setmetatable,getmetatable,tonumber,rawset=type,next,setmetatable,getmetatable,tonumber,rawset local lower,find,match,gsub=string.lower,string.find,string.match,string.gsub local utfchar=utf.char -local lpegmatch=lpeg.match +local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local P,S,R,C,V,C,Cs=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.V,lpeg.C,lpeg.Cs local formatters=string.formatters xml.xmlns=xml.xmlns or {} @@ -9226,9 +9721,10 @@ local function fromdec(s) return formatters["d:%s"](s),true end end -local rest=(1-P(";"))^0 -local many=P(1)^0 -local parsedentity=P("&")*(P("#x")*(rest/fromhex)+P("#")*(rest/fromdec))*P(";")*P(-1)+(P("#x")*(many/fromhex)+P("#")*(many/fromdec)) +local p_rest=(1-P(";"))^0 +local p_many=P(1)^0 +local p_char=lpegpatterns.utf8character +local parsedentity=P("&")*(P("#x")*(p_rest/fromhex)+P("#")*(p_rest/fromdec))*P(";")*P(-1)+(P("#x")*(p_many/fromhex)+P("#")*(p_many/fromdec)) local predefined_unified={ [38]="&", [42]=""", @@ -9254,7 +9750,9 @@ local privates_u={ local privates_p={} local privates_n={ } -local escaped=utf.remapper(privates_u) +local escaped=utf.remapper(privates_u,"dynamic") +local unprivatized=utf.remapper(privates_p,"dynamic") +xml.unprivatized=unprivatized local function unescaped(s) local p=privates_n[s] if not p then @@ -9267,9 +9765,7 @@ local function unescaped(s) end return p end -local unprivatized=utf.remapper(privates_p) xml.privatetoken=unescaped -xml.unprivatized=unprivatized xml.privatecodes=privates_n local function handle_hex_entity(str) local h=hcache[str] @@ -9422,7 +9918,7 @@ local valid=R('az','AZ','09')+S('_-.') local name_yes=C(valid^1)*colon*C(valid^1) local name_nop=C(P(true))*C(valid^1) local name=name_yes+name_nop -local utfbom=lpeg.patterns.utfbom +local utfbom=lpegpatterns.utfbom local spacing=C(space^0) local anyentitycontent=(1-open-semicolon-space-close)^0 local hexentitycontent=R("AF","af","09")^0 @@ -9590,7 +10086,7 @@ local function _xmlconvert_(data,settings) reported_attribute_errors,mt,errorhandler=nil,nil,nil return result end -function xmlconvert(data,settings) +local function xmlconvert(data,settings) local ok,result=pcall(function() return _xmlconvert_(data,settings) end) if ok then return result @@ -9682,14 +10178,17 @@ function xml.checkbom(root) insert(dt,2,"\n" ) end end -local function verbose_element(e,handlers) +local f_attribute=formatters['%s=%q'] +local function verbose_element(e,handlers,escape) local handle=handlers.handle local serialize=handlers.serialize local ens,etg,eat,edt,ern=e.ns,e.tg,e.at,e.dt,e.rn local ats=eat and next(eat) and {} if ats then + local n=0 for k,v in next,eat do - ats[#ats+1]=formatters['%s=%q'](k,escaped(v)) + n=n+1 + ats[n]=f_attribute(k,escaped(v)) end end if ern and trace_entities and ern~=ens then @@ -10033,7 +10532,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48030, stripped down to: 30595 +-- original size: 48229, stripped down to: 30684 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -10418,8 +10917,8 @@ local lp_builtin=P ( P("ns")/"ll.ns" )*((spaces*P("(")*spaces*P(")"))/"") local lp_attribute=(P("@")+P("attribute::"))/""*Cc("(ll.at and ll.at['")*((R("az","AZ")+S("-_:"))^1)*Cc("'])") -lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" -lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" +local lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" +local lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" local lp_fastpos=lp_fastpos_n+lp_fastpos_p local lp_reserved=C("and")+C("or")+C("not")+C("div")+C("mod")+C("true")+C("false") local lp_lua_function=Cs((R("az","AZ","__")^1*(P(".")*R("az","AZ","__")^1)^1)*("("))/"%0" @@ -10598,7 +11097,7 @@ local function nodesettostring(set,nodetest) if not ns or ns=="" then ns="*" end if not tg or tg=="" then tg="*" end tg=(tg=="@rt@" and "[root]") or format("%s:%s",ns,tg) - t[i]=(directive and tg) or format("not(%s)",tg) + t[#t+1]=(directive and tg) or format("not(%s)",tg) end if nodetest==false then return format("not(%s)",concat(t,"|")) @@ -10864,7 +11363,6 @@ expressions.print=function(...) print(...) return true end -expressions.contains=find expressions.find=find expressions.upper=upper expressions.lower=lower @@ -10886,6 +11384,9 @@ function expressions.contains(str,pattern) end return false end +function xml.expressions.idstring(str) + return type(str)=="string" and gsub(str,"^#","") or "" +end local function traverse(root,pattern,handle) local collected=applylpath(root,pattern) if collected then @@ -11173,7 +11674,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-aux"] = package.loaded["lxml-aux"] or true --- original size: 23804, stripped down to: 16817 +-- original size: 28225, stripped down to: 20125 if not modules then modules={} end modules ['lxml-aux']={ version=1.001, @@ -11183,16 +11684,19 @@ if not modules then modules={} end modules ['lxml-aux']={ license="see context related readme files" } local trace_manipulations=false trackers.register("lxml.manipulations",function(v) trace_manipulations=v end) +local trace_inclusions=false trackers.register("lxml.inclusions",function(v) trace_inclusions=v end) local report_xml=logs.reporter("xml") local xml=xml -local xmlconvert,xmlcopy,xmlname=xml.convert,xml.copy,xml.name +local xmlcopy,xmlname=xml.copy,xml.name local xmlinheritedconvert=xml.inheritedconvert local xmlapplylpath=xml.applylpath local xmlfilter=xml.filter -local type,setmetatable,getmetatable=type,setmetatable,getmetatable +local type,next,setmetatable,getmetatable=type,next,setmetatable,getmetatable local insert,remove,fastcopy,concat=table.insert,table.remove,table.fastcopy,table.concat local gmatch,gsub,format,find,strip=string.gmatch,string.gsub,string.format,string.find,string.strip local utfbyte=utf.byte +local lpegmatch=lpeg.match +local striplinepatterns=utilities.strings.striplinepatterns local function report(what,pattern,c,e) report_xml("%s element %a, root %a, position %a, index %a, pattern %a",what,xmlname(e),xmlname(e.__p__),c,e.ni,pattern) end @@ -11247,13 +11751,15 @@ end function xml.each(root,pattern,handle,reverse) local collected=xmlapplylpath(root,pattern) if collected then - if reverse then - for c=#collected,1,-1 do - handle(collected[c]) - end - else - for c=1,#collected do - handle(collected[c]) + if handle then + if reverse then + for c=#collected,1,-1 do + handle(collected[c]) + end + else + for c=1,#collected do + handle(collected[c]) + end end end return collected @@ -11309,6 +11815,7 @@ local function redo_ni(d) end end end +xml.reindex=redo_ni local function xmltoelement(whatever,root) if not whatever then return nil @@ -11360,8 +11867,16 @@ function xml.delete(root,pattern) report('deleting',pattern,c,e) end local d=p.dt - remove(d,e.ni) - redo_ni(d) + local ni=e.ni + if ni<=#d then + if false then + p.dt[ni]="" + else + remove(d,ni) + redo_ni(d) + end + else + end end end end @@ -11481,28 +11996,40 @@ xml.insertafter=insert_element xml.insertbefore=function(r,p,e) insert_element(r,p,e,true) end xml.injectafter=inject_element xml.injectbefore=function(r,p,e) inject_element(r,p,e,true) end -local function include(xmldata,pattern,attribute,recursive,loaddata) +local function include(xmldata,pattern,attribute,recursive,loaddata,level) pattern=pattern or 'include' loaddata=loaddata or io.loaddata local collected=xmlapplylpath(xmldata,pattern) if collected then + if not level then + level=1 + end for c=1,#collected do local ek=collected[c] local name=nil local ekdt=ek.dt local ekat=ek.at - local epdt=ek.__p__.dt + local ekrt=ek.__p__ + local epdt=ekrt.dt if not attribute or attribute=="" then name=(type(ekdt)=="table" and ekdt[1]) or ekdt end if not name then for a in gmatch(attribute or "href","([^|]+)") do name=ekat[a] - if name then break end + if name then + break + end + end + end + local data=nil + if name and name~="" then + data=loaddata(name) or "" + if trace_inclusions then + report_xml("including %s bytes from %a at level %s by pattern %a and attribute %a (%srecursing)",#data,name,level,pattern,attribute or "",recursive and "" or "not ") end end - local data=(name and name~="" and loaddata(name)) or "" - if data=="" then + if not data or data=="" then epdt[ek.ni]="" elseif ekat["parse"]=="text" then epdt[ek.ni]=xml.escaped(data) @@ -11512,70 +12039,113 @@ local function include(xmldata,pattern,attribute,recursive,loaddata) epdt[ek.ni]="" else if recursive then - include(xi,pattern,attribute,recursive,loaddata) + include(xi,pattern,attribute,recursive,loaddata,level+1) + end + local child=xml.body(xi) + child.__p__=ekrt + child.__f__=name + epdt[ek.ni]=child + local inclusions=xmldata.settings.inclusions + if inclusions then + inclusions[#inclusions+1]=name + else + xmldata.settings.inclusions={ name } end - epdt[ek.ni]=xml.body(xi) end end end end end xml.include=include +function xml.inclusion(e,default) + while e do + local f=e.__f__ + if f then + return f + else + e=e.__p__ + end + end + return default +end +function xml.inclusions(e,sorted) + while e do + local settings=e.settings + if settings then + local inclusions=settings.inclusions + if inclusions then + inclusions=table.unique(inclusions) + if sorted then + table.sort(inclusions) + end + return inclusions + else + e=e.__p__ + end + else + e=e.__p__ + end + end +end +local b_collapser=lpeg.patterns.b_collapser +local m_collapser=lpeg.patterns.m_collapser +local e_collapser=lpeg.patterns.e_collapser +local b_stripper=lpeg.patterns.b_stripper +local m_stripper=lpeg.patterns.m_stripper +local e_stripper=lpeg.patterns.e_stripper +local lpegmatch=lpeg.match local function stripelement(e,nolines,anywhere) local edt=e.dt if edt then - if anywhere then - local t,n={},0 - for e=1,#edt do + local n=#edt + if n==0 then + return e + elseif anywhere then + local t={} + local m=0 + for e=1,n do local str=edt[e] if type(str)~="string" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str elseif str~="" then if nolines then - str=gsub(str,"%s+"," ") + str=lpegmatch((n==1 and b_collapser) or (n==m and e_collapser) or m_collapser,str) + else + str=lpegmatch((n==1 and b_stripper) or (n==m and e_stripper) or m_stripper,str) end - str=gsub(str,"^%s*(.-)%s*$","%1") if str~="" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str end end end e.dt=t else - if #edt>0 then - local str=edt[1] - if type(str)~="string" then - elseif str=="" then + local str=edt[1] + if type(str)=="string" then + if str~="" then + str=lpegmatch(nolines and b_collapser or b_stripper,str) + end + if str=="" then remove(edt,1) + n=n-1 else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"^%s+","") - if str=="" then - remove(edt,1) - else - edt[1]=str - end + edt[1]=str end end - local nedt=#edt - if nedt>0 then - local str=edt[nedt] - if type(str)~="string" then - elseif str=="" then - remove(edt) - else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"%s+$","") + if n>0 then + str=edt[n] + if type(str)=="string" then if str=="" then remove(edt) else - edt[nedt]=str + str=lpegmatch(nolines and e_collapser or e_stripper,str) + if str=="" then + remove(edt) + else + edt[n]=str + end end end end @@ -11761,8 +12331,8 @@ function xml.finalizers.xml.cdata(collected) end return "" end -function xml.insertcomment(e,str,n) - table.insert(e.dt,n or 1,{ +function xml.insertcomment(e,str,n) + insert(e.dt,n or 1,{ tg="@cm@", ns="", special=true, @@ -11770,7 +12340,25 @@ function xml.insertcomment(e,str,n) dt={ str }, }) end -function xml.setcdata(e,str) +function xml.insertcdata(e,str,n) + insert(e.dt,n or 1,{ + tg="@cd@", + ns="", + special=true, + at={}, + dt={ str }, + }) +end +function xml.setcomment(e,str,n) + e.dt={ { + tg="@cm@", + ns="", + special=true, + at={}, + dt={ str }, + } } +end +function xml.setcdata(e,str) e.dt={ { tg="@cd@", ns="", @@ -11840,7 +12428,7 @@ local function recurse(e,action) for i=1,#edt do local str=edt[i] if type(str)~="string" then - recurse(str,action,recursive) + recurse(str,action) elseif str~="" then edt[i]=action(str) end @@ -11858,6 +12446,65 @@ function helpers.recursetext(collected,action,recursive) end end end +local specials={ + ["@rt@"]="root", + ["@pi@"]="instruction", + ["@cm@"]="comment", + ["@dt@"]="declaration", + ["@cd@"]="cdata", +} +local function convert(x,strip,flat) + local ns=x.ns + local tg=x.tg + local at=x.at + local dt=x.dt + local node=flat and { + [0]=(not x.special and (ns~="" and ns..":"..tg or tg)) or nil, + } or { + _namespace=ns~="" and ns or nil, + _tag=not x.special and tg or nil, + _type=specials[tg] or "_element", + } + if at then + for k,v in next,at do + node[k]=v + end + end + local n=0 + for i=1,#dt do + local di=dt[i] + if type(di)=="table" then + if flat and di.special then + else + di=convert(di,strip,flat) + if di then + n=n+1 + node[n]=di + end + end + elseif strip then + di=lpegmatch(strip,di) + if di~="" then + n=n+1 + node[n]=di + end + else + n=n+1 + node[n]=di + end + end + if next(node) then + return node + end +end +function xml.totable(x,strip,flat) + if type(x)=="table" then + if strip then + strip=striplinepatterns[strip] + end + return convert(x,strip,flat) + end +end end -- of closure @@ -12414,7 +13061,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-ini"] = package.loaded["data-ini"] or true --- original size: 7927, stripped down to: 5528 +-- original size: 11085, stripped down to: 7662 if not modules then modules={} end modules ['data-ini']={ version=1.001, @@ -12423,14 +13070,15 @@ if not modules then modules={} end modules ['data-ini']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files", } +local next,type,getmetatable,rawset=next,type,getmetatable,rawset local gsub,find,gmatch,char=string.gsub,string.find,string.gmatch,string.char -local next,type=next,type local filedirname,filebasename,filejoin=file.dirname,file.basename,file.join +local ostype,osname,osuname,ossetenv,osgetenv=os.type,os.name,os.uname,os.setenv,os.getenv +local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers.register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) local report_initialization=logs.reporter("resolvers","initialization") -local ostype,osname,ossetenv,osgetenv=os.type,os.name,os.setenv,os.getenv resolvers=resolvers or {} local resolvers=resolvers texconfig.kpse_init=false @@ -12563,10 +13211,103 @@ if type(profiler)=="table" and not jit then profiler.start("luatex-profile.log") end) end -if not resolvers.resolve then - function resolvers.resolve (s) return s end - function resolvers.unresolve(s) return s end - function resolvers.repath (s) return s end +local prefixes=utilities.storage.allocate() +resolvers.prefixes=prefixes +local resolved={} +local abstract={} +local dynamic={} +function resolvers.resetresolve(str) + resolved,abstract={},{} +end +function resolvers.allprefixes(separator) + local all=table.sortedkeys(prefixes) + if separator then + for i=1,#all do + all[i]=all[i]..":" + end + end + return all +end +local function _resolve_(method,target) + local action=prefixes[method] + if action then + return action(target) + else + return method..":"..target + end +end +function resolvers.unresolve(str) + return abstract[str] or str +end +function resolvers.setdynamic(str) + dynamic[str]=true +end +local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) +local prefix=C(R("az")^2)*P(":") +local target=C((1-S(" \"\';,"))^1) +local notarget=(#S(";,")+P(-1))*Cc("") +local p_resolve=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) +local p_simple=prefix*P(-1) +local function resolve(str) + if type(str)=="table" then + local res={} + for i=1,#str do + res[i]=resolve(str[i]) + end + return res + end + local res=resolved[str] + if res then + return res + end + local simple=lpegmatch(p_simple,str) + local action=prefixes[simple] + if action then + local res=action(res) + if not dynamic[simple] then + resolved[simple]=res + abstract[res]=simple + end + return res + end + res=lpegmatch(p_resolve,str) + resolved[str]=res + abstract[res]=str + return res +end +resolvers.resolve=resolve +if type(osuname)=="function" then + for k,v in next,osuname() do + if not prefixes[k] then + prefixes[k]=function() return v end + end + end +end +if ostype=="unix" then + local pattern + local function makepattern(t,k,v) + if t then + rawset(t,k,v) + end + local colon=P(":") + for k,v in table.sortedpairs(prefixes) do + if p then + p=P(k)+p + else + p=P(k) + end + end + pattern=Cs((p*colon+colon/";"+P(1))^0) + end + makepattern() + table.setmetatablenewindex(prefixes,makepattern) + function resolvers.repath(str) + return lpegmatch(pattern,str) + end +else + function resolvers.repath(str) + return str + end end @@ -12576,7 +13317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-exp"] = package.loaded["data-exp"] or true --- original size: 15317, stripped down to: 9723 +-- original size: 17216, stripped down to: 10657 if not modules then modules={} end modules ['data-exp']={ version=1.001, @@ -12590,12 +13331,16 @@ local concat,sort=table.concat,table.sort local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local Ct,Cs,Cc,Carg,P,C,S=lpeg.Ct,lpeg.Cs,lpeg.Cc,lpeg.Carg,lpeg.P,lpeg.C,lpeg.S local type,next=type,next +local isdir=lfs.isdir local ostype=os.type -local collapsepath=file.collapsepath +local collapsepath,joinpath,basename=file.collapsepath,file.join,file.basename local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_globbing=true trackers.register("resolvers.globbing",function(v) trace_globbing=v end) local report_expansions=logs.reporter("resolvers","expansions") +local report_globbing=logs.reporter("resolvers","globbing") local resolvers=resolvers +local resolveprefix=resolvers.resolve local function f_both(a,b) local t,n={},0 for sb in gmatch(b,"[^,]+") do @@ -12685,35 +13430,27 @@ function resolvers.expandedpathfromlist(pathlist) end return newlist end -local cleanup=lpeg.replacer { - { "!","" }, - { "\\","/" }, -} -function resolvers.cleanpath(str) - local doslashes=(P("\\")/"/"+1)^0 - local donegation=(P("!")/"" )^0 - local homedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") - if homedir=="~" or homedir=="" or not lfs.isdir(homedir) then - if trace_expansions then - report_expansions("no home dir set, ignoring dependent paths") - end - function resolvers.cleanpath(str) - if not str or find(str,"~",1,true) then - return "" - else - return lpegmatch(cleanup,str) +local usedhomedir=nil +local donegation=(P("!")/"" )^0 +local doslashes=(P("\\")/"/"+1)^0 +local function expandedhome() + if not usedhomedir then + usedhomedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") + if usedhomedir=="~" or usedhomedir=="" or not isdir(usedhomedir) then + if trace_expansions then + report_expansions("no home dir set, ignoring dependent path using current path") end - end - else - local dohome=((P("~")+P("$HOME"))/homedir)^0 - local cleanup=Cs(donegation*dohome*doslashes) - function resolvers.cleanpath(str) - return str and lpegmatch(cleanup,str) or "" + usedhomedir="." end end - return resolvers.cleanpath(str) + return usedhomedir +end +local dohome=((P("~")+P("$HOME")+P("%HOME%"))/expandedhome)^0 +local cleanup=Cs(donegation*dohome*doslashes) +resolvers.cleanpath=function(str) + return str and lpegmatch(cleanup,str) or "" end -local expandhome=P("~")/"$HOME" +local expandhome=P("~")/"$HOME" local dodouble=P('"')/""*(expandhome+(1-P('"')))^0*P('"')/"" local dosingle=P("'")/""*(expandhome+(1-P("'")))^0*P("'")/"" local dostring=(expandhome+1 )^0 @@ -12765,46 +13502,67 @@ function resolvers.splitpath(str) end function resolvers.joinpath(str) if type(str)=='table' then - return file.joinpath(str) + return joinpath(str) else return str end end local attributes,directory=lfs.attributes,lfs.dir local weird=P(".")^1+lpeg.anywhere(S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t")) +local lessweird=P(".")^1+lpeg.anywhere(S("~`#$%^&*:;\"\'||<>,?\n\r\t")) local timer={} local scanned={} local nofscans=0 local scancache={} -local function scan(files,spec,path,n,m,r) - local full=(path=="" and spec) or (spec..path..'/') +local fullcache={} +local nofsharedscans=0 +local function scan(files,remap,spec,path,n,m,r,onlyone,tolerant) + local full=path=="" and spec or (spec..path..'/') local dirs={} local nofdirs=0 + local pattern=tolerant and lessweird or weird for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then + if not lpegmatch(pattern,name) then + local mode=attributes(full..name,"mode") + if mode=="file" then n=n+1 - local f=files[name] - if f then - if type(f)=='string' then - files[name]={ f,path } + local lower=lower(name) + local paths=files[lower] + if paths then + if onlyone then else - f[#f+1]=path + if type(paths)=="string" then + files[lower]={ paths,path } + else + paths[#paths+1]=path + end + if name~=lower then + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end + end end else - files[name]=path - local lower=lower(name) + files[lower]=path if name~=lower then - files["remap:"..lower]=name - r=r+1 + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end end end - elseif mode=='directory' then + elseif mode=="directory" then m=m+1 nofdirs=nofdirs+1 if path~="" then - dirs[nofdirs]=path..'/'..name + dirs[nofdirs]=path.."/"..name else dirs[nofdirs]=name end @@ -12814,107 +13572,69 @@ local function scan(files,spec,path,n,m,r) if nofdirs>0 then sort(dirs) for i=1,nofdirs do - files,n,m,r=scan(files,spec,dirs[i],n,m,r) + files,remap,n,m,r=scan(files,remap,spec,dirs[i],n,m,r,onlyonce,tolerant) end end scancache[sub(full,1,-2)]=files - return files,n,m,r + return files,remap,n,m,r end -local fullcache={} -function resolvers.scanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) +function resolvers.scanfiles(path,branch,usecache,onlyonce,tolerant) + local realpath=resolveprefix(path) if usecache then - local files=fullcache[realpath] - if files then + local content=fullcache[realpath] + if content then if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) + report_expansions("using cached scan of path %a, branch %a",path,branch or path) end - return files + nofsharedscans=nofsharedscans+1 + return content end end + statistics.starttiming(timer) if trace_locating then report_expansions("scanning path %a, branch %a",path,branch or path) end - local files,n,m,r=scan({},realpath..'/',"",0,0,0) - files.__path__=path - files.__files__=n - files.__directories__=m - files.__remappings__=r - if trace_locating then - report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) - end - if usecache then - scanned[#scanned+1]=realpath - fullcache[realpath]=files - end - nofscans=nofscans+1 - statistics.stoptiming(timer) - return files -end -local function simplescan(files,spec,path) - local full=(path=="" and spec) or (spec..path..'/') - local dirs={} - local nofdirs=0 - for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then - if not files[name] then - files[name]=path - end - elseif mode=='directory' then - nofdirs=nofdirs+1 - if path~="" then - dirs[nofdirs]=path..'/'..name - else - dirs[nofdirs]=name - end - end - end - end - if nofdirs>0 then - sort(dirs) - for i=1,nofdirs do - files=simplescan(files,spec,dirs[i]) - end - end - return files -end -local simplecache={} -local nofsharedscans=0 -function resolvers.simplescanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) - if usecache then - local files=simplecache[realpath] - if not files then - files=scancache[realpath] - if files then - nofsharedscans=nofsharedscans+1 - end + local content + if isdir(realpath) then + local files,remap,n,m,r=scan({},{},realpath..'/',"",0,0,0,onlyonce,tolerant) + content={ + metadata={ + path=path, + files=n, + directories=m, + remappings=r, + }, + files=files, + remap=remap, + } + if trace_locating then + report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) end - if files then - if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) - end - return files + else + content={ + metadata={ + path=path, + files=0, + directories=0, + remappings=0, + }, + files={}, + remap={}, + } + if trace_locating then + report_expansions("invalid path %a",realpath) end end - if trace_locating then - report_expansions("scanning path %a, branch %a",path,branch or path) - end - local files=simplescan({},realpath..'/',"") - if trace_locating then - report_expansions("%s files found",table.count(files)) - end if usecache then scanned[#scanned+1]=realpath - simplecache[realpath]=files + fullcache[realpath]=content end nofscans=nofscans+1 statistics.stoptiming(timer) - return files + return content +end +function resolvers.simplescanfiles(path,branch,usecache) + return resolvers.scanfiles(path,branch,usecache,true,true) end function resolvers.scandata() table.sort(scanned) @@ -12925,6 +13645,52 @@ function resolvers.scandata() paths=scanned, } end +function resolvers.get_from_content(content,path,name) + if not content then + return + end + local files=content.files + if not files then + return + end + local remap=content.remap + if not remap then + return + end + if name then + local used=lower(name) + return path,remap[used] or used + else + local name=path + local used=lower(name) + local path=files[used] + if path then + return path,remap[used] or used + end + end +end +local nothing=function() end +function resolvers.filtered_from_content(content,pattern) + if content and type(pattern)=="string" then + local pattern=lower(pattern) + local files=content.files + local remap=content.remap + if files and remap then + local n=next(files) + local function iterator() + while n do + local k=n + n=next(files,k) + if find(k,pattern) then + return files[k],remap and remap[k] or k + end + end + end + return iterator + end + end + return nothing +end end -- of closure @@ -12933,7 +13699,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-env"] = package.loaded["data-env"] or true --- original size: 8769, stripped down to: 6490 +-- original size: 9216, stripped down to: 6798 if not modules then modules={} end modules ['data-env']={ version=1.001, @@ -12951,10 +13717,12 @@ local formats=allocate() local suffixes=allocate() local dangerous=allocate() local suffixmap=allocate() +local usertypes=allocate() resolvers.formats=formats resolvers.suffixes=suffixes resolvers.dangerous=dangerous resolvers.suffixmap=suffixmap +resolvers.usertypes=usertypes local luasuffixes=utilities.lua.suffixes local relations=allocate { core={ @@ -13022,11 +13790,13 @@ local relations=allocate { names={ "mp" }, variable='MPINPUTS', suffixes={ 'mp','mpvi','mpiv','mpii' }, + usertype=true, }, tex={ names={ "tex" }, variable='TEXINPUTS', - suffixes={ 'tex',"mkvi","mkiv","mkii" }, + suffixes={ "tex","mkvi","mkiv","mkii","cld","lfg","xml" }, + usertype=true, }, icc={ names={ "icc","icc profile","icc profiles" }, @@ -13042,6 +13812,7 @@ local relations=allocate { names={ "lua" }, variable='LUAINPUTS', suffixes={ luasuffixes.lua,luasuffixes.luc,luasuffixes.tma,luasuffixes.tmc }, + usertype=true, }, lib={ names={ "lib" }, @@ -13050,11 +13821,15 @@ local relations=allocate { }, bib={ names={ 'bib' }, + variable='BIBINPUTS', suffixes={ 'bib' }, + usertype=true, }, bst={ names={ 'bst' }, + variable='BSTINPUTS', suffixes={ 'bst' }, + usertype=true, }, fontconfig={ names={ 'fontconfig','fontconfig file','fontconfig files' }, @@ -13136,8 +13911,9 @@ function resolvers.updaterelations() for name,relation in next,categories do local rn=relation.names local rv=relation.variable - local rs=relation.suffixes if rn and rv then + local rs=relation.suffixes + local ru=relation.usertype for i=1,#rn do local rni=lower(gsub(rn[i]," ","")) formats[rni]=rv @@ -13149,8 +13925,9 @@ function resolvers.updaterelations() end end end - end - if rs then + if ru then + usertypes[name]=true + end end end end @@ -13201,7 +13978,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmp"] = package.loaded["data-tmp"] or true --- original size: 15532, stripped down to: 11648 +-- original size: 15681, stripped down to: 11761 if not modules then modules={} end modules ['data-tmp']={ version=1.100, @@ -13220,6 +13997,7 @@ local trace_cache=false trackers.register("resolvers.cache",function(v) trace_ca local report_caches=logs.reporter("resolvers","caches") local report_resolvers=logs.reporter("resolvers","caching") local resolvers=resolvers +local cleanpath=resolvers.cleanpath local directive_cleanup=false directives.register("system.compile.cleanup",function(v) directive_cleanup=v end) local directive_strip=false directives.register("system.compile.strip",function(v) directive_strip=v end) local compile=utilities.lua.compile @@ -13241,7 +14019,7 @@ caches.relocate=false caches.defaults={ "TMPDIR","TEMPDIR","TMP","TEMP","HOME","HOMEPATH" } local writable,readables,usedreadables=nil,{},{} local function identify() - local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") + local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") if texmfcaches then for k=1,#texmfcaches do local cachepath=texmfcaches[k] @@ -13495,10 +14273,12 @@ local content_state={} function caches.contentstate() return content_state or {} end -function caches.loadcontent(cachename,dataname) - local name=caches.hashed(cachename) - local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.loadcontent(cachename,dataname,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local blob=loadfile(addsuffix(filename,luasuffixes.luc)) or loadfile(addsuffix(filename,luasuffixes.lua)) if blob then local data=blob() @@ -13530,10 +14310,12 @@ function caches.collapsecontent(content) end end end -function caches.savecontent(cachename,dataname,content) - local name=caches.hashed(cachename) - local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.savecontent(cachename,dataname,content,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local luaname=addsuffix(filename,luasuffixes.lua) local lucname=addsuffix(filename,luasuffixes.luc) if trace_locating then @@ -13576,7 +14358,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-met"] = package.loaded["data-met"] or true --- original size: 5460, stripped down to: 4014 +-- original size: 5347, stripped down to: 4015 if not modules then modules={} end modules ['data-met']={ version=1.100, @@ -13604,7 +14386,7 @@ local function splitmethod(filename) if type(filename)=="table" then return filename end - filename=file.collapsepath(filename,".") + filename=file.collapsepath(filename,".") if not find(filename,"://",1,true) then return { scheme="file",path=filename,original=filename,filename=filename } end @@ -13695,7 +14477,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-res"] = package.loaded["data-res"] or true --- original size: 62045, stripped down to: 43116 +-- original size: 67003, stripped down to: 46291 if not modules then modules={} end modules ['data-res']={ version=1.001, @@ -13705,7 +14487,7 @@ if not modules then modules={} end modules ['data-res']={ license="see context related readme files", } local gsub,find,lower,upper,match,gmatch=string.gsub,string.find,string.lower,string.upper,string.match,string.gmatch -local concat,insert,sortedkeys=table.concat,table.insert,table.sortedkeys +local concat,insert,remove,sortedkeys,sortedhash=table.concat,table.insert,table.remove,table.sortedkeys,table.sortedhash local next,type,rawget=next,type,rawget local os=os local P,S,R,C,Cc,Cs,Ct,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Carg @@ -13714,17 +14496,23 @@ local formatters=string.formatters local filedirname=file.dirname local filebasename=file.basename local suffixonly=file.suffixonly +local addsuffix=file.addsuffix +local removesuffix=file.removesuffix local filejoin=file.join local collapsepath=file.collapsepath local joinpath=file.joinpath +local is_qualified_path=file.is_qualified_path local allocate=utilities.storage.allocate local settings_to_array=utilities.parsers.settings_to_array +local getcurrentdir=lfs.currentdir +local isfile=lfs.isfile +local isdir=lfs.isdir local setmetatableindex=table.setmetatableindex local luasuffixes=utilities.lua.suffixes -local getcurrentdir=lfs.currentdir local trace_locating=false trackers .register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers .register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers .register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_paths=false trackers .register("resolvers.paths",function(v) trace_paths=v end) local resolve_otherwise=true directives.register("resolvers.otherwise",function(v) resolve_otherwise=v end) local report_resolving=logs.reporter("resolvers","resolving") local resolvers=resolvers @@ -13732,10 +14520,14 @@ local expandedpathfromlist=resolvers.expandedpathfromlist local checkedvariable=resolvers.checkedvariable local splitconfigurationpath=resolvers.splitconfigurationpath local methodhandler=resolvers.methodhandler +local filtered=resolvers.filtered_from_content +local lookup=resolvers.get_from_content +local cleanpath=resolvers.cleanpath +local resolveprefix=resolvers.resolve local initializesetter=utilities.setters.initialize local ostype,osname,osenv,ossetenv,osgetenv=os.type,os.name,os.env,os.setenv,os.getenv -resolvers.cacheversion='1.0.1' -resolvers.configbanner='' +resolvers.cacheversion="1.100" +resolvers.configbanner="" resolvers.homedir=environment.homedir resolvers.criticalvars=allocate { "SELFAUTOLOC","SELFAUTODIR","SELFAUTOPARENT","TEXMFCNF","TEXMF","TEXOS" } resolvers.luacnfname="texmfcnf.lua" @@ -13754,6 +14546,7 @@ end local unset_variable="unset" local formats=resolvers.formats local suffixes=resolvers.suffixes +local usertypes=resolvers.usertypes local dangerous=resolvers.dangerous local suffixmap=resolvers.suffixmap resolvers.defaultsuffixes={ "tex" } @@ -13762,7 +14555,7 @@ local instance=resolvers.instance or nil function resolvers.setenv(key,value,raw) if instance then instance.environment[key]=value - ossetenv(key,raw and value or resolvers.resolve(value)) + ossetenv(key,raw and value or resolveprefix(value)) end end local function getenv(key) @@ -13776,7 +14569,7 @@ local function getenv(key) end resolvers.getenv=getenv resolvers.env=getenv -local function resolve(k) +local function resolvevariable(k) return instance.expansions[k] end local dollarstripper=lpeg.stripper("$") @@ -13785,19 +14578,19 @@ local backslashswapper=lpeg.replacer("\\","/") local somevariable=P("$")/"" local somekey=C(R("az","AZ","09","__","--")^1) local somethingelse=P(";")*((1-S("!{}/\\"))^1*P(";")/"")+P(";")*(P(";")/"")+P(1) -local variableexpander=Cs((somevariable*(somekey/resolve)+somethingelse)^1 ) +local variableexpander=Cs((somevariable*(somekey/resolvevariable)+somethingelse)^1 ) local cleaner=P("\\")/"/"+P(";")*S("!{}/\\")^0*P(";")^1/";" local variablecleaner=Cs((cleaner+P(1))^0) -local somevariable=R("az","AZ","09","__","--")^1/resolve +local somevariable=R("az","AZ","09","__","--")^1/resolvevariable local variable=(P("$")/"")*(somevariable+(P("{")/"")*somevariable*(P("}")/"")) local variableresolver=Cs((variable+P(1))^0) local function expandedvariable(var) return lpegmatch(variableexpander,var) or var end -function resolvers.newinstance() - if trace_locating then +function resolvers.newinstance() + if trace_locating then report_resolving("creating instance") - end + end local environment,variables,expansions,order=allocate(),allocate(),allocate(),allocate() local newinstance={ environment=environment, @@ -13810,6 +14603,7 @@ function resolvers.newinstance() foundintrees=allocate(), hashes=allocate(), hashed=allocate(), + pathlists=false, specification=allocate(), lists=allocate(), data=allocate(), @@ -13822,6 +14616,7 @@ function resolvers.newinstance() savelists=true, pattern=nil, force_suffixes=true, + pathstack={}, } setmetatableindex(variables,function(t,k) local v @@ -13871,8 +14666,13 @@ function resolvers.reset() end local function reset_hashes() instance.lists={} + instance.pathlists=false instance.found={} end +local function reset_caches() + instance.lists={} + instance.pathlists=false +end local slash=P("/") local pathexpressionpattern=Cs ( Cc("^")*( @@ -13924,13 +14724,13 @@ local function identify_configuration_files() for i=1,#cnfpaths do local filepath=cnfpaths[i] local filename=collapsepath(filejoin(filepath,luacnfname)) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) if trace_locating then - local fullpath=gsub(resolvers.resolve(collapsepath(filepath)),"//","/") + local fullpath=gsub(resolveprefix(collapsepath(filepath)),"//","/") local weirdpath=find(fullpath,"/texmf.+/texmf") or not find(fullpath,"/web2c",1,true) report_resolving("looking for %a on %s path %a from specification %a",luacnfname,weirdpath and "weird" or "given",fullpath,filepath) end - if lfs.isfile(realname) then + if isfile(realname) then specification[#specification+1]=filename if trace_locating then report_resolving("found configuration file %a",realname) @@ -13952,7 +14752,7 @@ local function load_configuration_files() local filename=specification[i] local pathname=filedirname(filename) local filename=filejoin(pathname,luacnfname) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local setups=instance.setups @@ -13960,7 +14760,7 @@ local function load_configuration_files() local parent=data and data.parent if parent then local filename=filejoin(pathname,parent) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local parentdata=blob() @@ -13985,7 +14785,7 @@ local function load_configuration_files() elseif variables[k]==nil then if trace_locating and not warning then report_resolving("variables like %a in configuration file %a should move to the 'variables' subtable", - k,resolvers.resolve(filename)) + k,resolveprefix(filename)) warning=true end variables[k]=v @@ -14045,7 +14845,7 @@ local function locate_file_databases() local stripped=lpegmatch(inhibitstripper,path) if stripped~="" then local runtime=stripped==path - path=resolvers.cleanpath(path) + path=cleanpath(path) local spec=resolvers.splitmethod(stripped) if runtime and (spec.noscheme or spec.scheme=="file") then stripped="tree:///"..stripped @@ -14108,8 +14908,8 @@ function resolvers.renew(hashname) report_resolving("identifying tree %a",hashname) end end - local realpath=resolvers.resolve(hashname) - if lfs.isdir(realpath) then + local realpath=resolveprefix(hashname) + if isdir(realpath) then if trace_locating then report_resolving("using path %a",realpath) end @@ -14210,19 +15010,53 @@ end function resolvers.unexpandedpath(str) return joinpath(resolvers.unexpandedpathlist(str)) end +function resolvers.pushpath(name) + local pathstack=instance.pathstack + local lastpath=pathstack[#pathstack] + local pluspath=filedirname(name) + if lastpath then + lastpath=collapsepath(filejoin(lastpath,pluspath)) + else + lastpath=collapsepath(pluspath) + end + insert(pathstack,lastpath) + if trace_paths then + report_resolving("pushing path %a",lastpath) + end +end +function resolvers.poppath() + local pathstack=instance.pathstack + if trace_paths and #pathstack>0 then + report_resolving("popping path %a",pathstack[#pathstack]) + end + remove(pathstack) +end +function resolvers.stackpath() + local pathstack=instance.pathstack + local currentpath=pathstack[#pathstack] + return currentpath~="" and currentpath or nil +end local done={} function resolvers.resetextrapath() local ep=instance.extra_paths if not ep then - ep,done={},{} - instance.extra_paths=ep + done={} + instance.extra_paths={} elseif #ep>0 then - instance.lists,done={},{} + done={} + reset_caches() end end function resolvers.registerextrapath(paths,subpaths) - paths=settings_to_array(paths) - subpaths=settings_to_array(subpaths) + if not subpaths or subpaths=="" then + if not paths or path=="" then + return + elseif done[paths] then + return + end + end + local paths=settings_to_array(paths) + local subpaths=settings_to_array(subpaths) local ep=instance.extra_paths or {} local oldn=#ep local newn=oldn @@ -14237,7 +15071,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=p.."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14247,7 +15081,7 @@ function resolvers.registerextrapath(paths,subpaths) local p=paths[i] if not done[p] then newn=newn+1 - ep[newn]=resolvers.cleanpath(p) + ep[newn]=cleanpath(p) done[p]=true end end @@ -14259,7 +15093,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=ep[i].."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14268,52 +15102,70 @@ function resolvers.registerextrapath(paths,subpaths) if newn>0 then instance.extra_paths=ep end - if newn>oldn then - instance.lists={} + if newn~=oldn then + reset_caches() end end -local function made_list(instance,list) - local ep=instance.extra_paths - if not ep or #ep==0 then - return list +function resolvers.pushextrapath(path) + local paths=settings_to_array(path) + if instance.extra_stack then + insert(instance.extra_stack,1,paths) else - local done,new,newn={},{},0 - for k=1,#list do - local v=list[k] - if not done[v] then - if find(v,"^[%.%/]$") then - done[v]=true - newn=newn+1 - new[newn]=v - else - break - end - end - end - for k=1,#ep do - local v=ep[k] + instance.extra_stack={ paths } + end + reset_caches() +end +function resolvers.popextrapath() + if instance.extra_stack then + reset_caches() + return remove(instance.extra_stack,1) + end +end +local function made_list(instance,list,extra_too) + local done={} + local new={} + local newn=0 + local function add(p) + for k=1,#p do + local v=p[k] if not done[v] then done[v]=true newn=newn+1 new[newn]=v end end - for k=1,#list do - local v=list[k] - if not done[v] then - done[v]=true - newn=newn+1 - new[newn]=v + end + for k=1,#list do + local v=list[k] + if done[v] then + elseif find(v,"^[%.%/]$") then + done[v]=true + newn=newn+1 + new[newn]=v + else + break + end + end + if extra_too then + local es=instance.extra_stack + if es and #es>0 then + for k=1,#es do + add(es[k]) end end - return new + local ep=instance.extra_paths + if ep and #ep>0 then + add(ep) + end end + add(list) + return new end function resolvers.cleanpathlist(str) local t=resolvers.expandedpathlist(str) if t then for i=1,#t do - t[i]=collapsepath(resolvers.cleanpath(t[i])) + t[i]=collapsepath(cleanpath(t[i])) end end return t @@ -14321,22 +15173,22 @@ end function resolvers.expandpath(str) return joinpath(resolvers.expandedpathlist(str)) end -function resolvers.expandedpathlist(str) +function resolvers.expandedpathlist(str,extra_too) if not str then return {} - elseif instance.savelists then + elseif instance.savelists then str=lpegmatch(dollarstripper,str) local lists=instance.lists local lst=lists[str] if not lst then - local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str))) + local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str)),extra_too) lst=expandedpathfromlist(l) lists[str]=lst end return lst else local lst=resolvers.splitpath(resolvers.expansion(str)) - return made_list(instance,expandedpathfromlist(lst)) + return made_list(instance,expandedpathfromlist(lst),extra_too) end end function resolvers.expandedpathlistfromvariable(str) @@ -14347,6 +15199,13 @@ end function resolvers.expandpathfromvariable(str) return joinpath(resolvers.expandedpathlistfromvariable(str)) end +function resolvers.cleanedpathlist(v) + local t=resolvers.expandedpathlist(v) + for i=1,#t do + t[i]=resolvers.resolve(resolvers.cleanpath(t[i])) + end + return t +end function resolvers.expandbraces(str) local ori=str local pth=expandedpathfromlist(resolvers.splitpath(ori)) @@ -14363,7 +15222,7 @@ function resolvers.registerfilehash(name,content,someerror) end end local function isreadable(name) - local readable=lfs.isfile(name) + local readable=isfile(name) if trace_detail then if readable then report_resolving("file %a is readable",name) @@ -14373,70 +15232,57 @@ local function isreadable(name) end return readable end -local function collect_files(names) - local filelist,noffiles={},0 +local function collect_files(names) + local filelist={} + local noffiles=0 + local function check(hash,root,pathname,path,name) + if not pathname or find(path,pathname) then + local variant=hash.type + local search=filejoin(root,path,name) + local result=methodhandler('concatinators',variant,root,path,name) + if trace_detail then + report_resolving("match: variant %a, search %a, result %a",variant,search,result) + end + noffiles=noffiles+1 + filelist[noffiles]={ variant,search,result } + end + end for k=1,#names do - local fname=names[k] + local filename=names[k] if trace_detail then - report_resolving("checking name %a",fname) + report_resolving("checking name %a",filename) end - local bname=filebasename(fname) - local dname=filedirname(fname) - if dname=="" or find(dname,"^%.") then - dname=false + local basename=filebasename(filename) + local pathname=filedirname(filename) + if pathname=="" or find(pathname,"^%.") then + pathname=false else - dname=gsub(dname,"%*",".*") - dname="/"..dname.."$" + pathname=gsub(pathname,"%*",".*") + pathname="/"..pathname.."$" end local hashes=instance.hashes for h=1,#hashes do local hash=hashes[h] - local blobpath=hash.name - local files=blobpath and instance.files[blobpath] - if files then + local hashname=hash.name + local content=hashname and instance.files[hashname] + if content then if trace_detail then - report_resolving("deep checking %a, base %a, pattern %a",blobpath,bname,dname) - 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 + report_resolving("deep checking %a, base %a, pattern %a",hashname,basename,pathname) end - if blobfile then - local blobroot=files.__path__ or blobpath - if type(blobfile)=='string' then - if not dname or find(blobfile,dname) then - local variant=hash.type - local search=filejoin(blobroot,blobfile,bname) - local result=methodhandler('concatinators',hash.type,blobroot,blobfile,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + local path,name=lookup(content,basename) + if path then + local metadata=content.metadata + local realroot=metadata and metadata.path or hashname + if type(path)=="string" then + check(hash,realroot,pathname,path,name) else - for kk=1,#blobfile do - local vv=blobfile[kk] - if not dname or find(vv,dname) then - local variant=hash.type - local search=filejoin(blobroot,vv,bname) - local result=methodhandler('concatinators',hash.type,blobroot,vv,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + for i=1,#path do + check(hash,realroot,pathname,path[i],name) end end end elseif trace_locating then - report_resolving("no match in %a (%s)",blobpath,bname) + report_resolving("no match in %a (%s)",hashname,basename) end end end @@ -14461,7 +15307,7 @@ end local function can_be_dir(name) local fakepaths=instance.fakepaths if not fakepaths[name] then - if lfs.isdir(name) then + if isdir(name) then fakepaths[name]=1 else fakepaths[name]=2 @@ -14477,10 +15323,11 @@ local function find_analyze(filename,askedformat,allresults) if askedformat=="" then if ext=="" or not suffixmap[ext] then local defaultsuffixes=resolvers.defaultsuffixes + local formatofsuffix=resolvers.formatofsuffix for i=1,#defaultsuffixes do local forcedname=filename..'.'..defaultsuffixes[i] wantedfiles[#wantedfiles+1]=forcedname - filetype=resolvers.formatofsuffix(forcedname) + filetype=formatofsuffix(forcedname) if trace_locating then report_resolving("forcing filetype %a",filetype) end @@ -14520,14 +15367,14 @@ local function find_wildcard(filename,allresults) if trace_locating then report_resolving("checking wildcard %a",filename) end - local method,result=resolvers.findwildcardfiles(filename) + local result=resolvers.findwildcardfiles(filename) if result then return "wildcard",result end end end local function find_qualified(filename,allresults,askedformat,alsostripped) - if not file.is_qualified_path(filename) then + if not is_qualified_path(filename) then return end if trace_locating then @@ -14601,33 +15448,66 @@ local function check_subpath(fname) return fname end end -local function find_intree(filename,filetype,wantedfiles,allresults) +local function makepathlist(list,filetype) local typespec=resolvers.variableofformat(filetype) - local pathlist=resolvers.expandedpathlist(typespec) - local method="intree" + local pathlist=resolvers.expandedpathlist(typespec,filetype and usertypes[filetype]) + local entry={} if pathlist and #pathlist>0 then - local filelist=collect_files(wantedfiles) + for k=1,#pathlist do + local path=pathlist[k] + local prescanned=find(path,'^!!') + local resursive=find(path,'//$') + local pathname=lpegmatch(inhibitstripper,path) + local expression=makepathexpression(pathname) + local barename=gsub(pathname,"/+$","") + barename=resolveprefix(barename) + local scheme=url.hasscheme(barename) + local schemename=gsub(barename,"%.%*$",'') + entry[k]={ + path=path, + pathname=pathname, + prescanned=prescanned, + recursive=recursive, + expression=expression, + barename=barename, + scheme=scheme, + schemename=schemename, + } + end + entry.typespec=typespec + list[filetype]=entry + else + list[filetype]=false + end + return entry +end +local function find_intree(filename,filetype,wantedfiles,allresults) + local pathlists=instance.pathlists + if not pathlists then + pathlists=setmetatableindex(allocate(),makepathlist) + instance.pathlists=pathlists + end + local pathlist=pathlists[filetype] + if pathlist then + local method="intree" + local filelist=collect_files(wantedfiles) local dirlist={} + local result={} if filelist then for i=1,#filelist do dirlist[i]=filedirname(filelist[i][3]).."/" end end if trace_detail then - report_resolving("checking filename %a",filename) + report_resolving("checking filename %a in tree",filename) end - local resolve=resolvers.resolve - local result={} for k=1,#pathlist do - local path=pathlist[k] - local pathname=lpegmatch(inhibitstripper,path) - local doscan=path==pathname - if not find (pathname,'//$') then - doscan=false - end + local entry=pathlist[k] + local path=entry.path + local pathname=entry.pathname local done=false if filelist then - local expression=makepathexpression(pathname) + local expression=entry.expression if trace_detail then report_resolving("using pattern %a for path %a",expression,pathname) end @@ -14635,8 +15515,8 @@ local function find_intree(filename,filetype,wantedfiles,allresults) local fl=filelist[k] local f=fl[2] local d=dirlist[k] - if find(d,expression) or find(resolve(d),expression) then - result[#result+1]=resolve(fl[3]) + if find(d,expression) or find(resolveprefix(d),expression) then + result[#result+1]=resolveprefix(fl[3]) done=true if allresults then if trace_detail then @@ -14657,56 +15537,62 @@ local function find_intree(filename,filetype,wantedfiles,allresults) method="database" else method="filesystem" - pathname=gsub(pathname,"/+$","") - pathname=resolve(pathname) - local scheme=url.hasscheme(pathname) + local scheme=entry.scheme if not scheme or scheme=="file" then - local pname=gsub(pathname,"%.%*$",'') + local pname=entry.schemename if not find(pname,"*",1,true) then if can_be_dir(pname) then - for k=1,#wantedfiles do - local w=wantedfiles[k] - local fname=check_subpath(filejoin(pname,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + if not done and not entry.prescanned then + if trace_detail then + report_resolving("quick root scan for %a",pname) end - end - if not done and doscan then - local files=resolvers.simplescanfiles(pname,false,true) for k=1,#wantedfiles do local w=wantedfiles[k] - local subpath=files[w] - if not subpath or subpath=="" then - elseif type(subpath)=="string" then - local fname=check_subpath(filejoin(pname,subpath,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + local fname=check_subpath(filejoin(pname,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break end - else - for i=1,#subpath do - local sp=subpath[i] - if sp=="" then - else - local fname=check_subpath(filejoin(pname,sp,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break + end + end + if not done and entry.recursive then + if trace_detail then + report_resolving("scanning filesystem for %a",pname) + end + local files=resolvers.simplescanfiles(pname,false,true) + for k=1,#wantedfiles do + local w=wantedfiles[k] + local subpath=files[w] + if not subpath or subpath=="" then + elseif type(subpath)=="string" then + local fname=check_subpath(filejoin(pname,subpath,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + else + for i=1,#subpath do + local sp=subpath[i] + if sp=="" then + else + local fname=check_subpath(filejoin(pname,sp,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end end end end - end - if done and not allresults then - break + if done and not allresults then + break + end end end end @@ -14714,6 +15600,18 @@ local function find_intree(filename,filetype,wantedfiles,allresults) end else end + else + for k=1,#wantedfiles do + local pname=entry.barename + local fname=methodhandler('finders',pname.."/"..wantedfiles[k]) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + end end end if done and not allresults then @@ -14748,10 +15646,13 @@ local function find_otherwise(filename,filetype,wantedfiles,allresults) local filelist=collect_files(wantedfiles) local fl=filelist and filelist[1] if fl then - return "otherwise",{ resolvers.resolve(fl[3]) } + return "otherwise",{ resolveprefix(fl[3]) } end end collect_instance_files=function(filename,askedformat,allresults) + if not filename or filename=="" then + return {} + end askedformat=askedformat or "" filename=collapsepath(filename,".") filename=gsub(filename,"^%./",getcurrentdir().."/") @@ -14786,7 +15687,11 @@ collect_instance_files=function(filename,askedformat,allresults) else local method,result,stamp,filetype,wantedfiles if instance.remember then - stamp=formatters["%s--%s"](filename,askedformat) + if askedformat=="" then + stamp=formatters["%s::%s"](suffixonly(filename),filename) + else + stamp=formatters["%s::%s"](askedformat,filename) + end result=stamp and instance.found[stamp] if result then if trace_locating then @@ -14821,7 +15726,7 @@ collect_instance_files=function(filename,askedformat,allresults) end if stamp then if trace_locating then - report_resolving("remembering file %a",filename) + report_resolving("remembering file %a using hash %a",filename,stamp) end instance.found[stamp]=result end @@ -14829,6 +15734,9 @@ collect_instance_files=function(filename,askedformat,allresults) end end local function findfiles(filename,filetype,allresults) + if not filename or filename=="" then + return {} + end local result,status=collect_instance_files(filename,filetype or "",allresults) if not result or #result==0 then local lowered=lower(filename) @@ -14848,39 +15756,30 @@ function resolvers.findpath(filename,filetype) return filedirname(findfiles(filename,filetype,false)[1] or "") end local function findgivenfiles(filename,allresults) - local bname,result=filebasename(filename),{} + local base=filebasename(filename) + local result={} local hashes=instance.hashes - local noffound=0 + local function okay(hash,path,name) + local found=methodhandler('concatinators',hash.type,hash.name,path,name) + if found and found~="" then + result[#result+1]=resolveprefix(found) + return not allresults + end + end for k=1,#hashes do local hash=hashes[k] - local files=instance.files[hash.name] or {} - 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 - local found=methodhandler('concatinators',hash.type,hash.name,blist,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then - break - end + local content=instance.files[hash.name] + if content then + local path,name=lookup(content,base) + if not path then + elseif type(path)=="string" then + if okay(hash,path,name) then + return result end else - for kk=1,#blist do - local vv=blist[kk] - local found=methodhandler('concatinators',hash.type,hash.name,vv,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then break end + for i=1,#path do + if okay(hash,path[i],name) then + return result end end end @@ -14894,64 +15793,80 @@ end function resolvers.findgivenfile(filename) return findgivenfiles(filename,false)[1] or "" end -local function doit(path,blist,bname,tag,variant,result,allresults) - local done=false - if blist and variant then - local resolve=resolvers.resolve - if type(blist)=='string' then - if find(lower(blist),path) then - local full=methodhandler('concatinators',variant,tag,blist,bname) or "" - result[#result+1]=resolve(full) - done=true - end - else - for kk=1,#blist do - local vv=blist[kk] - if find(lower(vv),path) then - local full=methodhandler('concatinators',variant,tag,vv,bname) or "" - result[#result+1]=resolve(full) - done=true - if not allresults then break end - end - end - end - end - return done -end local makewildcard=Cs( (P("^")^0*P("/")*P(-1)+P(-1))/".*"+(P("^")^0*P("/")/"")^0*(P("*")/".*"+P("-")/"%%-"+P(".")/"%%."+P("?")/"."+P("\\")/"/"+P(1))^0 ) function resolvers.wildcardpattern(pattern) return lpegmatch(makewildcard,pattern) or pattern end -local function findwildcardfiles(filename,allresults,result) - result=result or {} +local function findwildcardfiles(filename,allresults,result) + local result=result or {} local base=filebasename(filename) local dirn=filedirname(filename) local path=lower(lpegmatch(makewildcard,dirn) or dirn) local name=lower(lpegmatch(makewildcard,base) or base) - local files,done=instance.files,false + local files=instance.files if find(name,"*",1,true) then local hashes=instance.hashes + local function okay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - for kk,hh in next,files[hashname] do - if not find(kk,"^remap:") then - if find(lower(kk),name) then - if doit(path,hh,kk,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + for found,base in filtered(files[hashname],name) do + if type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end end end end end else + local function okayokay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end local hashes=instance.hashes for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - if doit(path,files[hashname][base],base,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + local found,base=lookup(content,base) + if not found then + elseif type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end + end + end end end return result @@ -15024,7 +15939,7 @@ end function resolvers.dowithpath(name,func) local pathlist=resolvers.expandedpathlist(name) for i=1,#pathlist do - func("^"..resolvers.cleanpath(pathlist[i])) + func("^"..cleanpath(pathlist[i])) end end function resolvers.dowithvariable(name,func) @@ -15032,23 +15947,23 @@ function resolvers.dowithvariable(name,func) end function resolvers.locateformat(name) local engine=environment.ownmain or "luatex" - local barename=file.removesuffix(name) - local fullname=file.addsuffix(barename,"fmt") + local barename=removesuffix(name) + local fullname=addsuffix(barename,"fmt") local fmtname=caches.getfirstreadablefile(fullname,"formats",engine) or "" if fmtname=="" then fmtname=resolvers.findfile(fullname) - fmtname=resolvers.cleanpath(fmtname) + fmtname=cleanpath(fmtname) end if fmtname~="" then - local barename=file.removesuffix(fmtname) - local luaname=file.addsuffix(barename,luasuffixes.lua) - local lucname=file.addsuffix(barename,luasuffixes.luc) - local luiname=file.addsuffix(barename,luasuffixes.lui) - if lfs.isfile(luiname) then + local barename=removesuffix(fmtname) + local luaname=addsuffix(barename,luasuffixes.lua) + local lucname=addsuffix(barename,luasuffixes.luc) + local luiname=addsuffix(barename,luasuffixes.lui) + if isfile(luiname) then return barename,luiname - elseif lfs.isfile(lucname) then + elseif isfile(lucname) then return barename,lucname - elseif lfs.isfile(luaname) then + elseif isfile(luaname) then return barename,luaname end end @@ -15070,29 +15985,24 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) local hash=hashes[i] local blobtype=hash.type local blobpath=hash.name - if blobpath then + if blobtype and blobpath then + local total=0 + local checked=0 + local done=0 if before then before(blobtype,blobpath,pattern) end - local files=instance.files[blobpath] - local total,checked,done=0,0,0 - if files then - for k,v in table.sortedhash(files) do - total=total+1 - if find(k,"^remap:") then - elseif find(k,pattern) then - if type(v)=="string" then - checked=checked+1 - if handle(blobtype,blobpath,v,k) then - done=done+1 - end - else - checked=checked+#v - for i=1,#v do - if handle(blobtype,blobpath,v[i],k) then - done=done+1 - end - end + for path,name in filtered(instance.files[blobpath],pattern) do + if type(path)=="string" then + checked=checked+1 + if handle(blobtype,blobpath,path,name) then + done=done+1 + end + else + checked=checked+#path + for i=1,#path do + if handle(blobtype,blobpath,path[i],name) then + done=done+1 end end end @@ -15103,8 +16013,8 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) end end end -resolvers.obsolete=resolvers.obsolete or {} -local obsolete=resolvers.obsolete +local obsolete=resolvers.obsolete or {} +resolvers.obsolete=obsolete resolvers.find_file=resolvers.findfile obsolete.find_file=resolvers.findfile resolvers.find_files=resolvers.findfiles obsolete.find_files=resolvers.findfiles @@ -15115,7 +16025,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-pre"] = package.loaded["data-pre"] or true --- original size: 6643, stripped down to: 4401 +-- original size: 3950, stripped down to: 2935 if not modules then modules={} end modules ['data-pre']={ version=1.001, @@ -15125,44 +16035,51 @@ if not modules then modules={} end modules ['data-pre']={ license="see context related readme files" } local resolvers=resolvers -local prefixes=utilities.storage.allocate() -resolvers.prefixes=prefixes -local cleanpath,findgivenfile,expansion=resolvers.cleanpath,resolvers.findgivenfile,resolvers.expansion +local prefixes=resolvers.prefixes +local cleanpath=resolvers.cleanpath +local findgivenfile=resolvers.findgivenfile +local expansion=resolvers.expansion local getenv=resolvers.getenv -local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match -local joinpath,basename,dirname=file.join,file.basename,file.dirname -local getmetatable,rawset,type=getmetatable,rawset,type +local basename=file.basename +local dirname=file.dirname +local joinpath=file.join +local isfile=lfs.isfile prefixes.environment=function(str) return cleanpath(expansion(str)) end -prefixes.relative=function(str,n) - if io.exists(str) then - 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.."../" +local function relative(str,n) + if not isfile(str) then + local pstr="./"..str + if isfile(pstr) then + str=pstr + else + local p="../" + for i=1,n or 2 do + local pstr=p..str + if isfile(pstr) then + str=pstr + break + else + p=p.."../" + end end end end return cleanpath(str) end +local function locate(str) + local fullname=findgivenfile(str) or "" + return cleanpath(fullname~="" and fullname or str) +end +prefixes.relative=relative +prefixes.locate=locate prefixes.auto=function(str) - local fullname=prefixes.relative(str) - if not lfs.isfile(fullname) then - fullname=prefixes.locate(str) + local fullname=relative(str) + if not isfile(fullname) then + fullname=locate(str) end return fullname end -prefixes.locate=function(str) - local fullname=findgivenfile(str) or "" - return cleanpath((fullname~="" and fullname) or str) -end prefixes.filename=function(str) local fullname=findgivenfile(str) or "" return cleanpath(basename((fullname~="" and fullname) or str)) @@ -15183,6 +16100,13 @@ end prefixes.home=function(str) return cleanpath(joinpath(getenv('HOME'),str)) end +prefixes.env=prefixes.environment +prefixes.rel=prefixes.relative +prefixes.loc=prefixes.locate +prefixes.kpse=prefixes.locate +prefixes.full=prefixes.locate +prefixes.file=prefixes.filename +prefixes.path=prefixes.pathname local function toppath() local inputstack=resolvers.inputstack if not inputstack then @@ -15195,98 +16119,22 @@ local function toppath() return pathname end end -resolvers.toppath=toppath -prefixes.toppath=function(str) - return cleanpath(joinpath(toppath(),str)) -end -prefixes.env=prefixes.environment -prefixes.rel=prefixes.relative -prefixes.loc=prefixes.locate -prefixes.kpse=prefixes.locate -prefixes.full=prefixes.locate -prefixes.file=prefixes.filename -prefixes.path=prefixes.pathname -function resolvers.allprefixes(separator) - local all=table.sortedkeys(prefixes) - if separator then - for i=1,#all do - all[i]=all[i]..":" - end - end - return all -end -local function _resolve_(method,target) - local action=prefixes[method] - if action then - return action(target) - else - return method..":"..target - end -end -local resolved,abstract={},{} -function resolvers.resetresolve(str) - resolved,abstract={},{} -end -local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) -local prefix=C(R("az")^2)*P(":") -local target=C((1-S(" \"\';,"))^1) -local notarget=(#S(";,")+P(-1))*Cc("") -local pattern=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) -local function resolve(str) - if type(str)=="table" then - local t={} - for i=1,#str do - t[i]=resolve(str[i]) - end - return t +local function jobpath() + local path=resolvers.stackpath() + if not path or path=="" then + return "." else - local res=resolved[str] - if not res then - res=lpegmatch(pattern,str) - resolved[str]=res - abstract[res]=str - end - return res - end -end -local function unresolve(str) - return abstract[str] or str -end -resolvers.resolve=resolve -resolvers.unresolve=unresolve -if type(os.uname)=="function" then - for k,v in next,os.uname() do - if not prefixes[k] then - prefixes[k]=function() return v end - end - end -end -if os.type=="unix" then - local pattern - local function makepattern(t,k,v) - if t then - rawset(t,k,v) - end - local colon=P(":") - for k,v in table.sortedpairs(prefixes) do - if p then - p=P(k)+p - else - p=P(k) - end - end - pattern=Cs((p*colon+colon/";"+P(1))^0) - end - makepattern() - getmetatable(prefixes).__newindex=makepattern - function resolvers.repath(str) - return lpegmatch(pattern,str) - end -else - function resolvers.repath(str) - return str + return path end end +resolvers.toppath=toppath +resolvers.jobpath=jobpath +prefixes.toppath=function(str) return cleanpath(joinpath(toppath(),str)) end +prefixes.jobpath=function(str) return cleanpath(joinpath(jobpath(),str)) end +resolvers.setdynamic("toppath") +resolvers.setdynamic("jobpath") +prefixes.jobfile=prefixes.jobpath +resolvers.setdynamic("jobfile") end -- of closure @@ -15348,7 +16196,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-fil"] = package.loaded["data-fil"] or true --- original size: 3801, stripped down to: 3231 +-- original size: 3863, stripped down to: 3310 if not modules then modules={} end modules ['data-fil']={ version=1.001, @@ -15360,30 +16208,31 @@ if not modules then modules={} end modules ['data-fil']={ local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_files=logs.reporter("resolvers","files") local resolvers=resolvers +local resolveprefix=resolvers.resolve local finders,openers,loaders,savers=resolvers.finders,resolvers.openers,resolvers.loaders,resolvers.savers local locators,hashers,generators,concatinators=resolvers.locators,resolvers.hashers,resolvers.generators,resolvers.concatinators local checkgarbage=utilities.garbagecollector and utilities.garbagecollector.check function locators.file(specification) - local name=specification.filename - local realname=resolvers.resolve(name) + local filename=specification.filename + local realname=resolveprefix(filename) if realname and realname~='' and lfs.isdir(realname) then if trace_locating then - report_files("file locator %a found as %a",name,realname) + report_files("file locator %a found as %a",filename,realname) end - resolvers.appendhash('file',name,true) + resolvers.appendhash('file',filename,true) elseif trace_locating then - report_files("file locator %a not found",name) + report_files("file locator %a not found",filename) end end function hashers.file(specification) - local name=specification.filename - local content=caches.loadcontent(name,'files') - resolvers.registerfilehash(name,content,content==nil) + local pathname=specification.filename + local content=caches.loadcontent(pathname,'files') + resolvers.registerfilehash(pathname,content,content==nil) end function generators.file(specification) - local path=specification.filename - local content=resolvers.scanfiles(path,false,true) - resolvers.registerfilehash(path,content,true) + local pathname=specification.filename + local content=resolvers.scanfiles(pathname,false,true) + resolvers.registerfilehash(pathname,content,true) end concatinators.file=file.join function finders.file(specification,filetype) @@ -15665,7 +16514,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-zip"] = package.loaded["data-zip"] or true --- original size: 8489, stripped down to: 6757 +-- original size: 8772, stripped down to: 6841 if not modules then modules={} end modules ['data-zip']={ version=1.001, @@ -15684,16 +16533,6 @@ zip.archives=zip.archives or {} local archives=zip.archives zip.registeredfiles=zip.registeredfiles or {} local registeredfiles=zip.registeredfiles -local limited=false -directives.register("system.inputmode",function(v) - if not limited then - local i_limiter=io.i_limiter(v) - if i_limiter then - zip.open=i_limiter.protect(zip.open) - limited=true - end - end -end) local function validzip(str) if not find(str,"^zip://") then return "zip:///"..str @@ -15708,7 +16547,7 @@ function zip.openarchive(name) local arch=archives[name] if not arch then local full=resolvers.findfile(name) or "" - arch=(full~="" and zip.open(full)) or false + arch=full~="" and zip.open(full) or false archives[name]=arch end return arch @@ -15867,31 +16706,42 @@ function resolvers.usezipfile(archive) end end function resolvers.registerzipfile(z,tree) - local files,filter={},"" - if tree=="" then - filter="^(.+)/(.-)$" - else - filter=format("^%s/(.+)/(.-)$",tree) - end + local names={} + local files={} + local remap={} + local n=0 + local filter=tree=="" and "^(.+)/(.-)$" or format("^%s/(.+)/(.-)$",tree) + local register=resolvers.registerfile if trace_locating then report_zip("registering: using filter %a",filter) end - local register,n=resolvers.registerfile,0 for i in z:files() do - local path,name=match(i.filename,filter) - if path then - if name and name~='' then - register(files,name,path) - n=n+1 - else + local filename=i.filename + local path,name=match(filename,filter) + if not path then + n=n+1 + register(names,filename,"") + local usedname=lower(filename) + files[usedname]="" + if usedname~=filename then + remap[usedname]=filename end - else - register(files,i.filename,'') + elseif name and name~="" then n=n+1 + register(names,name,path) + local usedname=lower(name) + files[usedname]=path + if usedname~=name then + remap[usedname]=name + end + else end end report_zip("registering: %s files registered",n) - return files + return { + files=files, + remap=remap, + } end @@ -15901,7 +16751,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tre"] = package.loaded["data-tre"] or true --- original size: 2508, stripped down to: 2074 +-- original size: 8479, stripped down to: 5580 if not modules then modules={} end modules ['data-tre']={ version=1.001, @@ -15910,42 +16760,64 @@ if not modules then modules={} end modules ['data-tre']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,gsub,format=string.find,string.gsub,string.format +local find,gsub,lower=string.find,string.gsub,string.lower +local basename,dirname,joinname=file.basename,file.dirname,file .join +local globdir,isdir,isfile=dir.glob,lfs.isdir,lfs.isfile +local P,lpegmatch=lpeg.P,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_trees=logs.reporter("resolvers","trees") local resolvers=resolvers -local done,found,notfound={},{},resolvers.finders.notfound -function resolvers.finders.tree(specification) +local resolveprefix=resolvers.resolve +local notfound=resolvers.finders.notfound +local lookup=resolvers.get_from_content +local collectors={} +local found={} +function resolvers.finders.tree(specification) local spec=specification.filename - local fnd=found[spec] - if fnd==nil then + local okay=found[spec] + if okay==nil then if spec~="" then - local path,name=file.dirname(spec),file.basename(spec) - if path=="" then path="." end - local hash=done[path] - if not hash then - local pattern=path.."/*" - hash=dir.glob(pattern) - done[path]=hash + local path=dirname(spec) + local name=basename(spec) + if path=="" then + path="." + end + local names=collectors[path] + if not names then + local pattern=find(path,"/%*+$") and path or (path.."/*") + names=globdir(pattern) + collectors[path]=names end local pattern="/"..gsub(name,"([%.%-%+])","%%%1").."$" - for k=1,#hash do - local v=hash[k] - if find(v,pattern) then - found[spec]=v - return v + for i=1,#names do + local fullname=names[i] + if find(fullname,pattern) then + found[spec]=fullname + return fullname + end + end + local pattern=lower(pattern) + for i=1,#names do + local fullname=lower(names[i]) + if find(fullname,pattern) then + if isfile(fullname) then + found[spec]=fullname + return fullname + else + break + end end end end - fnd=notfound() - found[spec]=fnd + okay=notfound() + found[spec]=okay end - return fnd + return okay end function resolvers.locators.tree(specification) local name=specification.filename - local realname=resolvers.resolve(name) - if realname and realname~='' and lfs.isdir(realname) then + local realname=resolveprefix(name) + if realname and realname~='' and isdir(realname) then if trace_locating then report_trees("locator %a found",realname) end @@ -15956,16 +16828,110 @@ function resolvers.locators.tree(specification) end function resolvers.hashers.tree(specification) local name=specification.filename - if trace_locating then - report_trees("analysing %a",name) - end + report_trees("analyzing %a",name) resolvers.methodhandler("hashers",name) resolvers.generators.file(specification) end -resolvers.concatinators.tree=resolvers.concatinators.file -resolvers.generators.tree=resolvers.generators.file -resolvers.openers.tree=resolvers.openers.file -resolvers.loaders.tree=resolvers.loaders.file +local collectors={} +local splitter=lpeg.splitat("/**/") +local stripper=lpeg.replacer { [P("/")*P("*")^1*P(-1)]="" } +table.setmetatableindex(collectors,function(t,k) + local rootname=lpegmatch(stripper,k) + local dataname=joinname(rootname,"dirlist") + local content=caches.loadcontent(dataname,"files",dataname) + if not content then + content=resolvers.scanfiles(rootname,nil,nil,false,true) + caches.savecontent(dataname,"files",content,dataname) + end + t[k]=content + return content +end) +local function checked(root,p,n) + if p then + if type(p)=="table" then + for i=1,#p do + local fullname=joinname(root,p[i],n) + if isfile(fullname) then + return fullname + end + end + else + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + end + return notfound() +end +local function resolve(specification) + local filename=specification.filename + if filename~="" then + local root,rest=lpegmatch(splitter,filename) + if root and rest then + local path,name=dirname(rest),basename(rest) + if name~=rest then + local content=collectors[root] + local p,n=lookup(content,name) + if not p then + return notfound() + end + local pattern=".*/"..path.."$" + local istable=type(p)=="table" + if istable then + for i=1,#p do + local pi=p[i] + if pi==path or find(pi,pattern) then + local fullname=joinname(root,pi,n) + if isfile(fullname) then + return fullname + end + end + end + elseif p==path or find(p,pattern) then + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + local queries=specification.queries + if queries and queries.option=="fileonly" then + return checked(root,p,n) + else + return notfound() + end + end + end + local path,name=dirname(filename),basename(filename) + local root=lpegmatch(stripper,path) + local content=collectors[path] + local p,n=lookup(content,name) + if p then + return checked(root,p,n) + end + end + return notfound() +end +resolvers.finders .dirlist=resolve +resolvers.locators .dirlist=resolvers.locators .tree +resolvers.hashers .dirlist=resolvers.hashers .tree +resolvers.generators.dirlist=resolvers.generators.file +resolvers.openers .dirlist=resolvers.openers .file +resolvers.loaders .dirlist=resolvers.loaders .file +function resolvers.finders.dirfile(specification) + local queries=specification.queries + if queries then + queries.option="fileonly" + else + specification.queries={ option="fileonly" } + end + return resolve(specification) +end +resolvers.locators .dirfile=resolvers.locators .dirlist +resolvers.hashers .dirfile=resolvers.hashers .dirlist +resolvers.generators.dirfile=resolvers.generators.dirlist +resolvers.openers .dirfile=resolvers.openers .dirlist +resolvers.loaders .dirfile=resolvers.loaders .dirlist end -- of closure @@ -15974,7 +16940,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-sch"] = package.loaded["data-sch"] or true --- original size: 6213, stripped down to: 5160 +-- original size: 6569, stripped down to: 5304 if not modules then modules={} end modules ['data-sch']={ version=1.001, @@ -16000,8 +16966,13 @@ directives.register("schemes.threshold",function(v) threshold=tonumber(v) or thr function cleaners.none(specification) return specification.original end -function cleaners.strip(specification) - return (gsub(specification.original,"[^%a%d%.]+","-")) +function cleaners.strip(specification) + local path,name=file.splitbase(specification.original) + if path=="" then + return (gsub(name,"[^%a%d%.]+","-")) + else + return (gsub((gsub(path,"%.","-").."-"..name),"[^%a%d%.]+","-")) + end end function cleaners.md5(specification) return file.addsuffix(md5.hex(specification.original),file.suffix(specification.path)) @@ -16018,7 +16989,7 @@ end local cached,loaded,reused,thresholds,handlers={},{},{},{},{} local function runcurl(name,cachename) local command="curl --silent --insecure --create-dirs --output "..cachename.." "..name - os.spawn(command) + os.execute(command) end local function fetch(specification) local original=specification.original @@ -16150,7 +17121,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lua"] = package.loaded["data-lua"] or true --- original size: 4237, stripped down to: 3177 +-- original size: 4313, stripped down to: 3227 if not modules then modules={} end modules ['data-lua']={ version=1.001, @@ -16159,7 +17130,7 @@ if not modules then modules={} end modules ['data-lua']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local resolvers,package=resolvers,package +local package,lpeg=package,lpeg local gsub=string.gsub local concat=table.concat local addsuffix=file.addsuffix @@ -16170,9 +17141,11 @@ local luaformats={ 'TEXINPUTS','LUAINPUTS' } local libformats={ 'CLUAINPUTS' } local helpers=package.helpers or {} local methods=helpers.methods or {} +local resolvers=resolvers +local resolveprefix=resolvers.resolve +helpers.report=logs.reporter("resolvers","libraries") trackers.register("resolvers.libraries",function(v) helpers.trace=v end) trackers.register("resolvers.locating",function(v) helpers.trace=v end) -helpers.report=logs.reporter("resolvers","libraries") helpers.sequence={ "already loaded", "preload table", @@ -16187,7 +17160,7 @@ helpers.sequence={ } local pattern=Cs(P("!")^0/""*(P("/")*P(-1)/"/"+P("/")^1/"/"+1)^0) function helpers.cleanpath(path) - return resolvers.resolve(lpegmatch(pattern,path)) + return resolveprefix(lpegmatch(pattern,path)) end local loadedaslib=helpers.loadedaslib local getextraluapaths=package.extraluapaths @@ -16324,7 +17297,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmf"] = package.loaded["data-tmf"] or true --- original size: 2600, stripped down to: 1627 +-- original size: 2601, stripped down to: 1627 if not modules then modules={} end modules ['data-tmf']={ version=1.001, @@ -16380,7 +17353,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lst"] = package.loaded["data-lst"] or true --- original size: 2654, stripped down to: 2301 +-- original size: 2734, stripped down to: 2354 if not modules then modules={} end modules ['data-lst']={ version=1.001, @@ -16389,10 +17362,13 @@ if not modules then modules={} end modules ['data-lst']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,concat,upper,format=string.find,table.concat,string.upper,string.format +local rawget,type,next=rawget,type,next +local find,concat,upper=string.find,table.concat,string.upper local fastcopy,sortedpairs=table.fastcopy,table.sortedpairs -resolvers.listers=resolvers.listers or {} local resolvers=resolvers +local listers=resolvers.listers or {} +resolvers.listers=listers +local resolveprefix=resolvers.resolve local report_lists=logs.reporter("resolvers","lists") local function tabstr(str) if type(str)=='table' then @@ -16401,7 +17377,7 @@ local function tabstr(str) return str end end -function resolvers.listers.variables(pattern) +function listers.variables(pattern) local instance=resolvers.instance local environment=instance.environment local variables=instance.variables @@ -16422,10 +17398,10 @@ function resolvers.listers.variables(pattern) for key,value in sortedpairs(configured) do if key~="" and (pattern=="" or find(upper(key),pattern)) then report_lists(key) - report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") - report_lists(" var: %s",tabstr(configured[key]) or "unset") - report_lists(" exp: %s",tabstr(expansions[key]) or "unset") - report_lists(" res: %s",tabstr(resolvers.resolve(expansions[key])) or "unset") + report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") + report_lists(" var: %s",tabstr(configured[key]) or "unset") + report_lists(" exp: %s",tabstr(expansions[key]) or "unset") + report_lists(" res: %s",tabstr(resolveprefix(expansions[key])) or "unset") end end instance.environment=fastcopy(env) @@ -16433,15 +17409,15 @@ function resolvers.listers.variables(pattern) instance.expansions=fastcopy(exp) end local report_resolved=logs.reporter("system","resolved") -function resolvers.listers.configurations() +function listers.configurations() local configurations=resolvers.instance.specification for i=1,#configurations do - report_resolved("file : %s",resolvers.resolve(configurations[i])) + report_resolved("file : %s",resolveprefix(configurations[i])) end report_resolved("") local list=resolvers.expandedpathfromlist(resolvers.splitpath(resolvers.luacnfspec)) for i=1,#list do - local li=resolvers.resolve(list[i]) + local li=resolveprefix(list[i]) if lfs.isdir(li) then report_resolved("path - %s",li) else @@ -16746,7 +17722,7 @@ do -- create closure to overcome 200 locals limit package.loaded["luat-fmt"] = package.loaded["luat-fmt"] or true --- original size: 5951, stripped down to: 4922 +-- original size: 5955, stripped down to: 4926 if not modules then modules={} end modules ['luat-fmt']={ version=1.001, @@ -16834,7 +17810,7 @@ function environment.make_format(name) end local command=format("%s --ini %s --lua=%s %s %sdump",engine,primaryflags(),quoted(usedluastub),quoted(fulltexsourcename),os.platform=="unix" and "\\\\" or "\\") report_format("running command: %s\n",command) - os.spawn(command) + os.execute(command) local pattern=file.removesuffix(file.basename(usedluastub)).."-*.mem" local mp=dir.glob(pattern) if mp then @@ -16869,7 +17845,7 @@ function environment.run_format(name,data,more) else local command=format("%s %s --fmt=%s --lua=%s %s %s",engine,primaryflags(),quoted(barename),quoted(luaname),quoted(data),more~="" and quoted(more) or "") report_format("running command: %s",command) - os.spawn(command) + os.execute(command) end end end @@ -16880,8 +17856,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 695180 --- stripped bytes : 246794 +-- original bytes : 739251 +-- stripped bytes : 264497 -- end library merge @@ -17107,6 +18083,7 @@ local helpinfo = [[ <category name="basic"> <subcategory> <flag name="script"><short>run an mtx script (lua prefered method) (<ref name="noquotes"/>), no script gives list</short></flag> + <flag name="evaluate"><short>run code passed on the commandline (between quotes)</short></flag> <flag name="execute"><short>run a script or program (texmfstart method) (<ref name="noquotes"/>)</short></flag> <flag name="resolve"><short>resolve prefixed arguments</short></flag> <flag name="ctxlua"><short>run internally (using preloaded libs)</short></flag> @@ -17132,6 +18109,7 @@ local helpinfo = [[ <flag name="verbose"><short>give a bit more info</short></flag> <flag name="trackers" value="list"><short>enable given trackers</short></flag> <flag name="progname" value="str"><short>format or backend</short></flag> + <flag name="systeminfo" value="str"><short>show current operating system, processor, etc</short></flag> </subcategory> <subcategory> <flag name="edit"><short>launch editor with found file</short></flag> @@ -17761,6 +18739,39 @@ function runners.associate(filename) os.launch(filename) end +function runners.evaluate(code,filename) -- for Luigi + if code == "loop" then + while true do + io.write("> ") + local code = io.read() + if code ~= "" then + local temp = string.match(code,"^= (.*)$") + if temp then + code = "print("..temp..")" + end + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("! " .. (message or code).."\n") + else + io.write(compiled()) + end + end + end + else + if type(code) ~= "string" or code == "" then + code = filename + end + if code ~= "" then + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("invalid lua code: " .. (message or code)) + return + end + io.write(compiled()) + end + end +end + function runners.gethelp(filename) local url = environment.argument("url") if url and url ~= "" then @@ -17772,6 +18783,15 @@ function runners.gethelp(filename) end end +function runners.systeminfo() + report("architecture : %s",os.platform or "<unset>") + report("operating system : %s",os.name or "<unset>") + report("file architecture : %s",os.type or "<unset>") + report("binary path : %s",os.selfdir or "<unset>") + report("binary suffix : %s",os.binsuffix or "<unset>") + report("library suffix : %s",os.libsuffix or "<unset>") +end + -- this is a bit dirty ... first we store the first filename and next we -- split the arguments so that we only see the ones meant for this script -- ... later we will use the second half @@ -17887,16 +18907,7 @@ end if e_argument("ansi") then - local formatters = string.formatters - - logs.setformatters { - report_yes = formatters["[1;32m%-15s [0;1m|[0m %s"], - report_nop = formatters["[1;32m%-15s [0;1m|[0m"], - subreport_yes = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m %s"], - subreport_nop = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m"], - status_yes = formatters["[1;32m%-15s [0;1m:[0m %s\n"], - status_nop = formatters["[1;32m%-15s [0;1m:[0m\n"], - } + logs.setformatters("ansi") local script = e_argument("script") or e_argument("scripts") @@ -17921,6 +18932,10 @@ if e_argument("script") or e_argument("scripts") then ok = runners.execute_ctx_script(filename) end +elseif e_argument("evaluate") then + + runners.evaluate(e_argument("evaluate"),filename) + elseif e_argument("selfmerge") then -- embed used libraries @@ -18213,6 +19228,10 @@ elseif e_argument("exporthelp") then runners.loadbase() application.export(e_argument("exporthelp"),filename) +elseif e_argument("systeminfo") then + + runners.systeminfo() + elseif e_argument("help") or filename=='help' or filename == "" then application.help() diff --git a/Master/texmf-dist/scripts/context/stubs/win64/mtxrun.lua b/Master/texmf-dist/scripts/context/stubs/win64/mtxrun.lua index 5cb5c8d2416..48a9a27a250 100644 --- a/Master/texmf-dist/scripts/context/stubs/win64/mtxrun.lua +++ b/Master/texmf-dist/scripts/context/stubs/win64/mtxrun.lua @@ -56,7 +56,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lua"] = package.loaded["l-lua"] or true --- original size: 3409, stripped down to: 1763 +-- original size: 3888, stripped down to: 2197 if not modules then modules={} end modules ['l-lua']={ version=1.001, @@ -139,6 +139,13 @@ end if lua then lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" end +local flush=io.flush +if flush then + local execute=os.execute if execute then function os.execute(...) flush() return execute(...) end end + local exec=os.exec if exec then function os.exec (...) flush() return exec (...) end end + local spawn=os.spawn if spawn then function os.spawn (...) flush() return spawn (...) end end + local popen=io.popen if popen then function io.popen (...) flush() return popen (...) end end +end end -- of closure @@ -437,7 +444,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-lpeg"] = package.loaded["l-lpeg"] or true --- original size: 29983, stripped down to: 16202 +-- original size: 36225, stripped down to: 19891 if not modules then modules={} end modules ['l-lpeg']={ version=1.001, @@ -498,6 +505,8 @@ patterns.utfbom_16_le=utfbom_16_le patterns.utfbom_8=utfbom_8 patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") +patterns.utf_32_be_nl=P("\000\000\000\r\000\000\000\n")+P("\000\000\000\r")+P("\000\000\000\n") +patterns.utf_32_le_nl=P("\r\000\000\000\n\000\000\000")+P("\r\000\000\000")+P("\n\000\000\000") patterns.utf8one=R("\000\127") patterns.utf8two=R("\194\223")*utf8next patterns.utf8three=R("\224\239")*utf8next*utf8next @@ -527,9 +536,21 @@ patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) +local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) +local e_collapser=Cs((whitespace^1*P(-1)/""+nonwhitespace^1+whitespace^1/" ")^0) +local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) +local b_stripper=Cs(spacer^0/""*(nonspacer^1+spacer^1/" ")^0) +local e_stripper=Cs((spacer^1*P(-1)/""+nonspacer^1+spacer^1/" ")^0) +local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser +patterns.b_collapser=b_collapser +patterns.m_collapser=m_collapser +patterns.e_collapser=e_collapser +patterns.b_stripper=b_stripper +patterns.m_stripper=m_stripper +patterns.e_stripper=e_stripper patterns.lowercase=lowercase patterns.uppercase=uppercase patterns.letter=patterns.lowercase+patterns.uppercase @@ -566,9 +587,12 @@ patterns.integer=sign^-1*digit^1 patterns.unsigned=digit^0*period*digit^1 patterns.float=sign^-1*patterns.unsigned patterns.cunsigned=digit^0*comma*digit^1 +patterns.cpunsigned=digit^0*(period+comma)*digit^1 patterns.cfloat=sign^-1*patterns.cunsigned +patterns.cpfloat=sign^-1*patterns.cpunsigned patterns.number=patterns.float+patterns.integer patterns.cnumber=patterns.cfloat+patterns.integer +patterns.cpnumber=patterns.cpfloat+patterns.integer patterns.oct=zero*octdigit^1 patterns.octal=patterns.oct patterns.HEX=zero*P("X")*(digit+uppercase)^1 @@ -985,23 +1009,40 @@ function lpeg.append(list,pp,delayed,checked) end return p end +local p_false=P(false) +local p_true=P(true) local function make(t) - local p + local function making(t) + local p=p_false + local keys=sortedkeys(t) + for i=1,#keys do + local k=keys[i] + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then + else + p=p+P(k)*making(v) + end + end + end + if t[""] then + p=p+p_true + end + return p + end + local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] - local v=t[k] - if not p then - if next(v) then - p=P(k)*make(v) - else - p=P(k) - end - else - if next(v) then - p=p+P(k)*make(v) + if k~="" then + local v=t[k] + if v==true then + p=p+P(k)*p_true + elseif v==false then else - p=p+P(k) + p=p+P(k)*making(v) end end end @@ -1009,13 +1050,73 @@ local function make(t) end function lpeg.utfchartabletopattern(list) local tree={} - for i=1,#list do - local t=tree - for c in gmatch(list[i],".") do - if not t[c] then - t[c]={} + local n=#list + if n==0 then + for s in next,list do + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true + end + end + else + for i=1,n do + local s=list[i] + local t=tree + local p,pk + for c in gmatch(s,".") do + if t==true then + t={ [c]=true,[""]=true } + p[pk]=t + p=t + t=false + elseif t==false then + t={ [c]=false } + p[pk]=t + p=t + t=false + else + local tc=t[c] + if not tc then + tc=false + t[c]=false + end + p=t + t=tc + end + pk=c + end + if t==false then + p[pk]=true + elseif t==true then + else + t[""]=true end - t=t[c] end end return make(tree) @@ -1055,6 +1156,65 @@ local case_2=period*(digit-trailingzeros)^1*(trailingzeros/"") local number=digit^1*(case_1+case_2) local stripper=Cs((number+1)^0) lpeg.patterns.stripzeros=stripper +local byte_to_HEX={} +local byte_to_hex={} +local byte_to_dec={} +local hex_to_byte={} +for i=0,255 do + local H=format("%02X",i) + local h=format("%02x",i) + local d=format("%03i",i) + local c=char(i) + byte_to_HEX[c]=H + byte_to_hex[c]=h + byte_to_dec[c]=d + hex_to_byte[h]=c + hex_to_byte[H]=c +end +local hextobyte=P(2)/hex_to_byte +local bytetoHEX=P(1)/byte_to_HEX +local bytetohex=P(1)/byte_to_hex +local bytetodec=P(1)/byte_to_dec +local hextobytes=Cs(hextobyte^0) +local bytestoHEX=Cs(bytetoHEX^0) +local bytestohex=Cs(bytetohex^0) +local bytestodec=Cs(bytetodec^0) +patterns.hextobyte=hextobyte +patterns.bytetoHEX=bytetoHEX +patterns.bytetohex=bytetohex +patterns.bytetodec=bytetodec +patterns.hextobytes=hextobytes +patterns.bytestoHEX=bytestoHEX +patterns.bytestohex=bytestohex +patterns.bytestodec=bytestodec +function string.toHEX(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestoHEX,s) + end +end +function string.tohex(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestohex,s) + end +end +function string.todec(s) + if not s or s=="" then + return s + else + return lpegmatch(bytestodec,s) + end +end +function string.tobytes(s) + if not s or s=="" then + return s + else + return lpegmatch(hextobytes,s) + end +end end -- of closure @@ -1082,7 +1242,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-string"] = package.loaded["l-string"] or true --- original size: 5671, stripped down to: 2827 +-- original size: 5694, stripped down to: 2827 if not modules then modules={} end modules ['l-string']={ version=1.001, @@ -1187,7 +1347,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-table"] = package.loaded["l-table"] or true --- original size: 31860, stripped down to: 20846 +-- original size: 33830, stripped down to: 21894 if not modules then modules={} end modules ['l-table']={ version=1.001, @@ -1230,11 +1390,12 @@ function table.keys(t) end end local function compare(a,b) - local ta,tb=type(a),type(b) - if ta==tb then + local ta=type(a) + local tb=type(b) + if ta==tb and ta=="number" then return a<b else - return tostring(a)<tostring(b) + return tostring(a)<tostring(b) end end local function sortedkeys(tab) @@ -1334,14 +1495,14 @@ local function sortedhash(t,cmp) end local n=0 local m=#s - local function kv(s) + local function kv() if n<m then n=n+1 local k=s[n] return k,t[k] end end - return kv,s + return kv else return nothing end @@ -1506,7 +1667,7 @@ local function simple_table(t) if tv=="number" then nt=nt+1 if hexify then - tt[nt]=format("0x%04X",v) + tt[nt]=format("0x%X",v) else tt[nt]=tostring(v) end @@ -1537,7 +1698,7 @@ local function do_serialize(root,name,depth,level,indexed) local tn=type(name) if tn=="number" then if hexify then - handle(format("%s[0x%04X]={",depth,name)) + handle(format("%s[0x%X]={",depth,name)) else handle(format("%s[%s]={",depth,name)) end @@ -1554,7 +1715,7 @@ local function do_serialize(root,name,depth,level,indexed) end end end - if root and next(root) then + if root and next(root)~=nil then local first,last=nil,0 if compact then last=#root @@ -1576,7 +1737,7 @@ local function do_serialize(root,name,depth,level,indexed) if compact and first and tk=="number" and k>=first and k<=last then if tv=="number" then if hexify then - handle(format("%s 0x%04X,",depth,v)) + handle(format("%s 0x%X,",depth,v)) else handle(format("%s %s,",depth,v)) end @@ -1587,7 +1748,7 @@ local function do_serialize(root,name,depth,level,indexed) handle(format("%s %q,",depth,v)) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then handle(format("%s {},",depth)) elseif inline then local st=simple_table(v) @@ -1617,25 +1778,25 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="number" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=0x%04X,",depth,k,v)) + handle(format("%s [0x%X]=0x%X,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end elseif tk=="boolean" then if hexify then - handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v)) + handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v)) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then - handle(format("%s %s=0x%04X,",depth,k,v)) + handle(format("%s %s=0x%X,",depth,k,v)) else handle(format("%s %s=%s,",depth,k,v)) end else if hexify then - handle(format("%s [%q]=0x%04X,",depth,k,v)) + handle(format("%s [%q]=0x%X,",depth,k,v)) else handle(format("%s [%q]=%s,",depth,k,v)) end @@ -1644,7 +1805,7 @@ local function do_serialize(root,name,depth,level,indexed) if reduce and tonumber(v) then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v)) + handle(format("%s [0x%X]=%s,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end @@ -1658,7 +1819,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,v)) + handle(format("%s [0x%X]=%q,",depth,k,v)) else handle(format("%s [%s]=%q,",depth,k,v)) end @@ -1671,10 +1832,10 @@ local function do_serialize(root,name,depth,level,indexed) end end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={},",depth,k)) + handle(format("%s [0x%X]={},",depth,k)) else handle(format("%s [%s]={},",depth,k)) end @@ -1690,7 +1851,7 @@ local function do_serialize(root,name,depth,level,indexed) if st then if tk=="number" then if hexify then - handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", "))) + handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%s]={ %s },",depth,k,concat(st,", "))) end @@ -1710,7 +1871,7 @@ local function do_serialize(root,name,depth,level,indexed) elseif tv=="boolean" then if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false")) + handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%s]=%s,",depth,k,v and "true" or "false")) end @@ -1726,7 +1887,7 @@ local function do_serialize(root,name,depth,level,indexed) local f=getinfo(v).what=="C" and dump(dummy) or dump(v) if tk=="number" then if hexify then - handle(format("%s [0x%04X]=load(%q),",depth,k,f)) + handle(format("%s [0x%X]=load(%q),",depth,k,f)) else handle(format("%s [%s]=load(%q),",depth,k,f)) end @@ -1741,7 +1902,7 @@ local function do_serialize(root,name,depth,level,indexed) else if tk=="number" then if hexify then - handle(format("%s [0x%04X]=%q,",depth,k,tostring(v))) + handle(format("%s [0x%X]=%q,",depth,k,tostring(v))) else handle(format("%s [%s]=%q,",depth,k,tostring(v))) end @@ -1795,7 +1956,7 @@ local function serialize(_handle,root,name,specification) end elseif tname=="number" then if hexify then - handle(format("[0x%04X]={",name)) + handle(format("[0x%X]={",name)) else handle("["..name.."]={") end @@ -1813,7 +1974,7 @@ local function serialize(_handle,root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,"",0) end end @@ -1942,14 +2103,25 @@ local function identical(a,b) end table.identical=identical table.are_equal=are_equal -function table.compact(t) - if t then - for k,v in next,t do - if not next(v) then - t[k]=nil +local function sparse(old,nest,keeptables) + local new={} + for k,v in next,old do + if not (v=="" or v==false) then + if nest and type(v)=="table" then + v=sparse(v,nest) + if keeptables or next(v)~=nil then + new[k]=v + end + else + new[k]=v end end end + return new +end +table.sparse=sparse +function table.compact(t) + return sparse(t,true,true) end function table.contains(t,v) if t then @@ -2054,10 +2226,10 @@ function table.sub(t,i,j) return { unpack(t,i,j) } end function table.is_empty(t) - return not t or not next(t) + return not t or next(t)==nil end function table.has_one_entry(t) - return t and not next(t,next(t)) + return t and next(t,next(t))==nil end function table.loweredkeys(t) local l={} @@ -2102,6 +2274,44 @@ function table.values(t,s) return {} end end +function table.filtered(t,pattern,sort,cmp) + if t and type(pattern)=="string" then + if sort then + local s + if cmp then + s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) + else + s=sortedkeys(t) + end + local n=0 + local m=#s + local function kv(s) + while n<m do + n=n+1 + local k=s[n] + if find(k,pattern) then + return k,t[k] + end + end + end + return kv,s + else + local n=next(t) + local function iterator() + while n~=nil do + local k=n + n=next(t,k) + if find(k,pattern) then + return k,t[k] + end + end + end + return iterator,t + end + else + return nothing + end +end end -- of closure @@ -2110,7 +2320,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-io"] = package.loaded["l-io"] or true --- original size: 8824, stripped down to: 6347 +-- original size: 8643, stripped down to: 6232 if not modules then modules={} end modules ['l-io']={ version=1.001, @@ -2417,8 +2627,6 @@ function io.readstring(f,n,m) local str=gsub(f:read(n),"\000","") return str end -if not io.i_limiter then function io.i_limiter() end end -if not io.o_limiter then function io.o_limiter() end end end -- of closure @@ -2645,7 +2853,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-os"] = package.loaded["l-os"] or true --- original size: 16093, stripped down to: 9704 +-- original size: 15761, stripped down to: 9403 if not modules then modules={} end modules ['l-os']={ version=1.001, @@ -2719,13 +2927,10 @@ if not os.__getenv__ then setmetatable(os.env,{ __index=__index,__newindex=__newindex } ) end end -local execute,spawn,exec,iopopen,ioflush=os.execute,os.spawn or os.execute,os.exec or os.execute,io.popen,io.flush -function os.execute(...) ioflush() return execute(...) end -function os.spawn (...) ioflush() return spawn (...) end -function os.exec (...) ioflush() return exec (...) end -function io.popen (...) ioflush() return iopopen(...) end +local execute=os.execute +local iopopen=io.popen function os.resultof(command) - local handle=io.popen(command,"r") + local handle=iopopen(command,"r") if handle then local result=handle:read("*all") or "" handle:close() @@ -2754,7 +2959,7 @@ local launchers={ unix="$BROWSER %s &> /dev/null &", } function os.launch(str) - os.execute(format(launchers[os.name] or launchers.unix,str)) + execute(format(launchers[os.name] or launchers.unix,str)) end if not os.times then function os.times() @@ -3029,7 +3234,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-file"] = package.loaded["l-file"] or true --- original size: 18308, stripped down to: 9948 +-- original size: 20949, stripped down to: 9945 if not modules then modules={} end modules ['l-file']={ version=1.001, @@ -3043,41 +3248,28 @@ local file=file if not lfs then lfs=optionalrequire("lfs") end -if not lfs then - lfs={ - getcurrentdir=function() - return "." - end, - attributes=function() - return nil - end, - isfile=function(name) - local f=io.open(name,'rb') - if f then - f:close() - return true - end - end, - isdir=function(name) - print("you need to load lfs") - return false - end - } -elseif not lfs.isfile then - local attributes=lfs.attributes - function lfs.isdir(name) - return attributes(name,"mode")=="directory" - end - function lfs.isfile(name) - return attributes(name,"mode")=="file" - end -end local insert,concat=table.insert,table.concat local match,find,gmatch=string.match,string.find,string.gmatch local lpegmatch=lpeg.match local getcurrentdir,attributes=lfs.currentdir,lfs.attributes local checkedsplit=string.checkedsplit local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct +local tricky=S("/\\")*P(-1) +local attributes=lfs.attributes +if sandbox then + sandbox.redefine(lfs.isfile,"lfs.isfile") + sandbox.redefine(lfs.isdir,"lfs.isdir") +end +function lfs.isdir(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end +end +function lfs.isfile(name) + return attributes(name,"mode")=="file" +end local colon=P(":") local period=P(".") local periods=P("..") @@ -3279,28 +3471,30 @@ local isroot=fwslash^1*-1 local hasroot=fwslash^1 local reslasher=lpeg.replacer(S("\\/"),"/") local deslasher=lpeg.replacer(S("\\/")^1,"/") -function file.join(...) - local lst={... } - local one=lst[1] +function file.join(one,two,three,...) + if not two then + return one=="" and one or lpegmatch(stripper,one) + end + if one=="" then + return lpegmatch(stripper,three and concat({ two,three,... },"/") or two) + end if lpegmatch(isnetwork,one) then local one=lpegmatch(reslasher,one) - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return one..two else return one.."/"..two end elseif lpegmatch(isroot,one) then - local two=lpegmatch(deslasher,concat(lst,"/",2)) + local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return two else return "/"..two end - elseif one=="" then - return lpegmatch(stripper,concat(lst,"/",2)) else - return lpegmatch(deslasher,concat(lst,"/")) + return lpegmatch(deslasher,concat({ one,two,three,... },"/")) end end local drivespec=R("az","AZ")^1*colon @@ -3474,7 +3668,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-md5"] = package.loaded["l-md5"] or true --- original size: 3760, stripped down to: 2088 +-- original size: 3248, stripped down to: 2266 if not modules then modules={} end modules ['l-md5']={ version=1.001, @@ -3492,14 +3686,20 @@ if not md5 then } end local md5,file=md5,file -local gsub,format,byte=string.gsub,string.format,string.byte -local md5sum=md5.sum -local function convert(str,fmt) - return (gsub(md5sum(str),".",function(chr) return format(fmt,byte(chr)) end)) -end -if not md5.HEX then function md5.HEX(str) return convert(str,"%02X") end end -if not md5.hex then function md5.hex(str) return convert(str,"%02x") end end -if not md5.dec then function md5.dec(str) return convert(str,"%03i") end end +local gsub=string.gsub +do + local patterns=lpeg and lpeg.patterns + if patterns then + local bytestoHEX=patterns.bytestoHEX + local bytestohex=patterns.bytestohex + local bytestodec=patterns.bytestodec + local lpegmatch=lpeg.match + local md5sum=md5.sum + if not md5.HEX then function md5.HEX(str) if str then return lpegmatch(bytestoHEX,md5sum(str)) end end end + if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end + if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end + end +end function file.needsupdating(oldname,newname,threshold) local oldtime=lfs.attributes(oldname,"modification") if oldtime then @@ -3556,7 +3756,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-url"] = package.loaded["l-url"] or true --- original size: 12292, stripped down to: 5585 +-- original size: 12531, stripped down to: 5721 if not modules then modules={} end modules ['l-url']={ version=1.001, @@ -3642,19 +3842,25 @@ local splitquery=Cf (Ct("")*P { "sequence", pair=Cg(key*equal*value), },rawset) local function hashed(str) - if str=="" then + if not str or str=="" then return { scheme="invalid", original=str, } end - local s=split(str) - local rawscheme=s[1] - local rawquery=s[4] - local somescheme=rawscheme~="" - local somequery=rawquery~="" + local detailed=split(str) + local rawscheme="" + local rawquery="" + local somescheme=false + local somequery=false + if detailed then + rawscheme=detailed[1] + rawquery=detailed[4] + somescheme=rawscheme~="" + somequery=rawquery~="" + end if not somescheme and not somequery then - s={ + return { scheme="file", authority="", path=str, @@ -3664,28 +3870,28 @@ local function hashed(str) noscheme=true, filename=str, } - else - local authority,path,filename=s[2],s[3] - if authority=="" then - filename=path - elseif path=="" then - filename="" - else - filename=authority.."/"..path - end - s={ - scheme=rawscheme, - authority=authority, - path=path, - query=lpegmatch(unescaper,rawquery), - queries=lpegmatch(splitquery,rawquery), - fragment=s[5], - original=str, - noscheme=false, - filename=filename, - } end - return s + local authority=detailed[2] + local path=detailed[3] + local filename=nil + if authority=="" then + filename=path + elseif path=="" then + filename="" + else + filename=authority.."/"..path + end + return { + scheme=rawscheme, + authority=authority, + path=path, + query=lpegmatch(unescaper,rawquery), + queries=lpegmatch(splitquery,rawquery), + fragment=detailed[5], + original=str, + noscheme=false, + filename=filename, + } end url.split=split url.hasscheme=hasscheme @@ -3719,7 +3925,7 @@ function url.construct(hash) end return lpegmatch(escaper,concat(fullurl)) end -local pattern=Cs(noslash*R("az","AZ")*(S(":|")/":")*noslash*P(1)^0) +local pattern=Cs(slash^-1/""*R("az","AZ")*((S(":|")/":")+P(":"))*slash*P(1)^0) function url.filename(filename) local spec=hashed(filename) local path=spec.path @@ -3767,7 +3973,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-dir"] = package.loaded["l-dir"] or true --- original size: 14788, stripped down to: 9096 +-- original size: 16765, stripped down to: 11003 if not modules then modules={} end modules ['l-dir']={ version=1.001, @@ -3777,7 +3983,7 @@ if not modules then modules={} end modules ['l-dir']={ license="see context related readme files" } local type,select=type,select -local find,gmatch,match,gsub=string.find,string.gmatch,string.match,string.gsub +local find,gmatch,match,gsub,sub=string.find,string.gmatch,string.match,string.gsub,string.sub local concat,insert,remove,unpack=table.concat,table.insert,table.remove,table.unpack local lpegmatch=lpeg.match local P,S,R,C,Cc,Cs,Ct,Cv,V=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cv,lpeg.V @@ -3786,53 +3992,126 @@ local dir=dir local lfs=lfs local attributes=lfs.attributes local walkdir=lfs.dir -local isdir=lfs.isdir -local isfile=lfs.isfile +local isdir=lfs.isdir +local isfile=lfs.isfile local currentdir=lfs.currentdir local chdir=lfs.chdir local mkdir=lfs.mkdir local onwindows=os.type=="windows" or find(os.getenv("PATH"),";",1,true) -if not isdir then - function isdir(name) - local a=attributes(name) - return a and a.mode=="directory" +if onwindows then + local tricky=S("/\\")*P(-1) + isdir=function(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode")=="directory" + else + return attributes(name.."/.","mode")=="directory" + end + end + isfile=function(name) + return attributes(name,"mode")=="file" end lfs.isdir=isdir -end -if not isfile then - function isfile(name) - local a=attributes(name) - return a and a.mode=="file" + lfs.isfile=isfile +else + isdir=function(name) + return attributes(name,"mode")=="directory" end + isfile=function(name) + return attributes(name,"mode")=="file" + end + lfs.isdir=isdir lfs.isfile=isfile end function dir.current() return (gsub(currentdir(),"\\","/")) end -local lfsisdir=isdir -local function isdir(path) - path=gsub(path,"[/\\]+$","") - return lfsisdir(path) +local function glob_pattern_function(path,patt,recurse,action) + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + action(full) + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end + end + end + end + if dirs then + for i=1,#dirs do + glob_pattern_function(dirs[i],patt,recurse,action) + end + end + end end -lfs.isdir=isdir -local function globpattern(path,patt,recurse,action) - if path=="/" then - path=path.."." - elseif not find(path,"/$") then - path=path..'/' - end - if isdir(path) then - for name in walkdir(path) do - local full=path..name - local mode=attributes(full,'mode') - if mode=='file' then - if find(full,patt) then - action(full) +local function glob_pattern_table(path,patt,recurse,result) + if not result then + result={} + end + if isdir(path) then + local usedpath + if path=="/" then + usedpath="/." + elseif not find(path,"/$") then + usedpath=path.."/." + path=path.."/" + else + usedpath=path + end + local dirs + for name in walkdir(usedpath) do + if name~="." and name~=".." then + local full=path..name + local mode=attributes(full,'mode') + if mode=='file' then + if not patt or find(full,patt) then + result[#result+1]=full + end + elseif recurse and mode=="directory" then + if not dirs then + dirs={ full } + else + dirs[#dirs+1]=full + end end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - globpattern(full,patt,recurse,action) end end + if dirs then + for i=1,#dirs do + glob_pattern_table(dirs[i],patt,recurse,result) + end + end + end + return result +end +local function globpattern(path,patt,recurse,method) + local kind=type(method) + if patt and sub(patt,1,-3)==path then + patt=false + end + if kind=="function" then + return glob_pattern_function(path,patt,recurse,method) + elseif kind=="table" then + return glob_pattern_table(path,patt,recurse,method) + else + return glob_pattern_table(path,patt,recurse,{}) end end dir.globpattern=globpattern @@ -3845,34 +4124,40 @@ local function collectpattern(path,patt,recurse,result) ok,scanner,first=xpcall(function() return walkdir(path) end,function() end) end if ok and type(scanner)=="function" then - if not find(path,"/$") then path=path..'/' end + if not find(path,"/$") then + path=path..'/' + end for name in scanner,first do - local full=path..name - local attr=attributes(full) - local mode=attr.mode - if mode=='file' then - if find(full,patt) then + if name=="." then + elseif name==".." then + else + local full=path..name + local attr=attributes(full) + local mode=attr.mode + if mode=='file' then + if find(full,patt) then + result[name]=attr + end + elseif recurse and mode=="directory" then + attr.list=collectpattern(full,patt,recurse) result[name]=attr end - elseif recurse and (mode=="directory") and (name~='.') and (name~="..") then - attr.list=collectpattern(full,patt,recurse) - result[name]=attr end end end return result end dir.collectpattern=collectpattern -local separator -if onwindows then +local separator,pattern +if onwindows then local slash=S("/\\")/"/" - pattern=Ct { + pattern={ [1]=(Cs(P(".")+slash^1)+Cs(R("az","AZ")*P(":")*slash^0)+Cc("./"))*V(2)*V(3), [2]=Cs(((1-S("*?/\\"))^0*slash)^0), [3]=Cs(P(1)^0) } -else - pattern=Ct { +else + pattern={ [1]=(C(P(".")+P("/")^1)+Cc("./"))*V(2)*V(3), [2]=C(((1-S("*?/"))^0*P("/"))^0), [3]=C(P(1)^0) @@ -3890,9 +4175,8 @@ local function glob(str,t) elseif isfile(str) then t(str) else - local split=lpegmatch(pattern,str) - if split then - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) @@ -3914,16 +4198,12 @@ local function glob(str,t) return { str } end else - local split=lpegmatch(pattern,str) - if split then - local t=t or {} - local action=action or function(name) t[#t+1]=name end - local root,path,base=split[1],split[2],split[3] + local root,path,base=lpegmatch(pattern,str) + if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) - globpattern(start,result,recurse,action) - return t + return globpattern(start,result,recurse,t) else return {} end @@ -4071,47 +4351,51 @@ else end end dir.makedirs=dir.mkdirs -if onwindows then - function dir.expandname(str) - local first,nothing,last=match(str,"^(//)(//*)(.*)$") - if first then - first=dir.current().."/" - end - if not first then - first,last=match(str,"^(//)/*(.*)$") - end - if not first then - first,last=match(str,"^([a-zA-Z]:)(.*)$") - if first and not find(last,"^/") then - local d=currentdir() - if chdir(first) then - first=dir.current() +do + local chdir=sandbox and sandbox.original(chdir) or chdir + if onwindows then + local xcurrentdir=dir.current + function dir.expandname(str) + local first,nothing,last=match(str,"^(//)(//*)(.*)$") + if first then + first=xcurrentdir().."/" + end + if not first then + first,last=match(str,"^(//)/*(.*)$") + end + if not first then + first,last=match(str,"^([a-zA-Z]:)(.*)$") + if first and not find(last,"^/") then + local d=currentdir() + if chdir(first) then + first=xcurrentdir() + end + chdir(d) end - chdir(d) + end + if not first then + first,last=xcurrentdir(),str + end + last=gsub(last,"//","/") + last=gsub(last,"/%./","/") + last=gsub(last,"^/*","") + first=gsub(first,"/*$","") + if last=="" or last=="." then + return first + else + return first.."/"..last end end - if not first then - first,last=dir.current(),str - end - last=gsub(last,"//","/") - last=gsub(last,"/%./","/") - last=gsub(last,"^/*","") - first=gsub(first,"/*$","") - if last=="" or last=="." then - return first - else - return first.."/"..last - end - end -else - function dir.expandname(str) - if not find(str,"^/") then - str=currentdir().."/"..str + else + function dir.expandname(str) + if not find(str,"^/") then + str=currentdir().."/"..str + end + str=gsub(str,"//","/") + str=gsub(str,"/%./","/") + str=gsub(str,"(.)/%.$","%1") + return str end - str=gsub(str,"//","/") - str=gsub(str,"/%./","/") - str=gsub(str,"(.)/%.$","%1") - return str end end file.expandname=dir.expandname @@ -4154,7 +4438,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-boolean"] = package.loaded["l-boolean"] or true --- original size: 1809, stripped down to: 1527 +-- original size: 1850, stripped down to: 1568 if not modules then modules={} end modules ['l-boolean']={ version=1.001, @@ -4208,11 +4492,11 @@ function string.booleanstring(str) return str=="yes" or str=="on" or str=="t" end end -function string.is_boolean(str,default) +function string.is_boolean(str,default,strict) if type(str)=="string" then - if str=="true" or str=="yes" or str=="on" or str=="t" or str=="1" then + if str=="true" or str=="yes" or str=="on" or str=="t" or (not strict and str=="1") then return true - elseif str=="false" or str=="no" or str=="off" or str=="f" or str=="0" then + elseif str=="false" or str=="no" or str=="off" or str=="f" or (not strict and str=="0") then return false end end @@ -4226,7 +4510,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-unicode"] = package.loaded["l-unicode"] or true --- original size: 33706, stripped down to: 14938 +-- original size: 37233, stripped down to: 15684 if not modules then modules={} end modules ['l-unicode']={ version=1.001, @@ -4242,7 +4526,9 @@ local type=type local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch local concat=table.concat local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp -local lpegmatch,patterns=lpeg.match,lpeg.patterns +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern local bytepairs=string.bytepairs local finder=lpeg.finder local replacer=lpeg.replacer @@ -4251,7 +4537,7 @@ local utfgmatch=utf.gmatch local p_utftype=patterns.utftype local p_utfstricttype=patterns.utfstricttype local p_utfoffset=patterns.utfoffset -local p_utf8char=patterns.utf8char +local p_utf8char=patterns.utf8character local p_utf8byte=patterns.utf8byte local p_utfbom=patterns.utfbom local p_newline=patterns.newline @@ -4450,15 +4736,52 @@ if not utf.sub then end end end -function utf.remapper(mapping) - local pattern=Cs((p_utf8char/mapping)^0) - return function(str) - if not str or str=="" then - return "" +function utf.remapper(mapping,option) + local variant=type(mapping) + if variant=="table" then + if option=="dynamic" then + local pattern=false + table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) + return function(str) + if not str or str=="" then + return "" + else + if not pattern then + pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + end + return lpegmatch(pattern,str) + end + end + elseif option=="pattern" then + return Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) else - return lpegmatch(pattern,str) + local pattern=Cs((tabletopattern(mapping)/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + elseif variant=="function" then + if option=="pattern" then + return Cs((p_utf8char/mapping+p_utf8char)^0) + else + local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + else + return function(str) + return str or "" end - end,pattern + end end function utf.replacer(t) local r=replacer(t,false,false,true) @@ -4508,190 +4831,157 @@ function utf.magic(f) end local utf16_to_utf8_be,utf16_to_utf8_le local utf32_to_utf8_be,utf32_to_utf8_le -local utf_16_be_linesplitter=patterns.utfbom_16_be^-1*lpeg.tsplitat(patterns.utf_16_be_nl) -local utf_16_le_linesplitter=patterns.utfbom_16_le^-1*lpeg.tsplitat(patterns.utf_16_le_nl) -if bytepairs then - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*left+right - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_be,s) + else + return s end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in bytepairs(t[i]) do - if right then - local now=256*right+left - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) - end - return t +end +local utf16_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_be_linesplitter,t) end - utf32_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*256*256*a+256*256*b - else - r=r+1 - result[t]=utfchar(more+256*a+b) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_be,s) end - return t end - utf32_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utflinesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,-1 - for a,b in bytepairs(t[i]) do - if a and b then - if more<0 then - more=256*b+a - else - r=r+1 - result[t]=utfchar(more+256*256*256*b+256*256*a) - more=-1 - end - else - break - end - end - t[i]=concat(result,"",1,r) - end - return t + return t +end +utf16_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_le,s) + else + return s end -else - utf16_to_utf8_be=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_be_linesplitter,t) - end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if left=="\000" then - r=r+1 - result[r]=utfchar(byte(right)) - elseif right then - local now=256*byte(left)+byte(right) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) +end +local utf16_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_le,s) end - return t end - utf16_to_utf8_le=function(t) - if type(t)=="string" then - t=lpegmatch(utf_16_le_linesplitter,t) + return t +end +utf32_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_be,s) + else + return s + end +end +local utf32_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_be,s) end - local result={} - for i=1,#t do - local r,more=0,0 - for left,right in gmatch(t[i],"(.)(.)") do - if right=="\000" then - r=r+1 - result[r]=utfchar(byte(left)) - elseif right then - local now=256*byte(right)+byte(left) - if more>0 then - now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 - more=0 - r=r+1 - result[r]=utfchar(now) - elseif now>=0xD800 and now<=0xDBFF then - more=now - else - r=r+1 - result[r]=utfchar(now) - end - end - end - t[i]=concat(result,"",1,r) + end + return t +end +utf32_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_le,s) + else + return s + end +end +local utf32_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_le,s) end - return t end - utf32_to_utf8_le=function() return {} end - utf32_to_utf8_be=function() return {} end + return t end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t utf.utf16_to_utf8_le=utf16_to_utf8_le utf.utf16_to_utf8_be=utf16_to_utf8_be utf.utf32_to_utf8_le=utf32_to_utf8_le utf.utf32_to_utf8_be=utf32_to_utf8_be -function utf.utf8_to_utf8(t) +function utf.utf8_to_utf8_t(t) return type(t)=="string" and lpegmatch(utflinesplitter,t) or t end -function utf.utf16_to_utf8(t,endian) - return endian and utf16_to_utf8_be(t) or utf16_to_utf8_le(t) or t +function utf.utf16_to_utf8_t(t,endian) + return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t end -function utf.utf32_to_utf8(t,endian) - return endian and utf32_to_utf8_be(t) or utf32_to_utf8_le(t) or t +function utf.utf32_to_utf8_t(t,endian) + return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t end -local function little(c) - local b=byte(c) +local function little(b) if b<0x10000 then return char(b%256,b/256) else @@ -4700,8 +4990,7 @@ local function little(c) return char(b1%256,b1/256,b2%256,b2/256) end end -local function big(c) - local b=byte(c) +local function big(b) if b<0x10000 then return char(b/256,b%256) else @@ -4710,27 +4999,29 @@ local function big(c) return char(b1/256,b1%256,b2/256,b2%256) end end -local _,l_remap=utf.remapper(little) -local _,b_remap=utf.remapper(big) -function utf.utf8_to_utf16_be(str,nobom) +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) if nobom then return lpegmatch(b_remap,str) else return char(254,255)..lpegmatch(b_remap,str) end end -function utf.utf8_to_utf16_le(str,nobom) +local function utf8_to_utf16_le(str,nobom) if nobom then return lpegmatch(l_remap,str) else return char(255,254)..lpegmatch(l_remap,str) end end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le function utf.utf8_to_utf16(str,littleendian,nobom) if littleendian then - return utf.utf8_to_utf16_le(str,nobom) + return utf8_to_utf16_le(str,nobom) else - return utf.utf8_to_utf16_be(str,nobom) + return utf8_to_utf16_be(str,nobom) end end local pattern=Cs ( @@ -4746,16 +5037,16 @@ function utf.xstring(s) return format("0x%05X",type(s)=="number" and s or utfbyte(s)) end function utf.toeight(str) - if not str then + if not str or str=="" then return nil end local utftype=lpegmatch(p_utfstricttype,str) if utftype=="utf-8" then - return sub(str,4) - elseif utftype=="utf-16-le" then - return utf16_to_utf8_le(str) + return sub(str,4) elseif utftype=="utf-16-be" then - return utf16_to_utf8_ne(str) + return utf16_to_utf8_be(str) + elseif utftype=="utf-16-le" then + return utf16_to_utf8_le(str) else return str end @@ -4834,7 +5125,7 @@ do -- create closure to overcome 200 locals limit package.loaded["l-math"] = package.loaded["l-math"] or true --- original size: 915, stripped down to: 836 +-- original size: 974, stripped down to: 890 if not modules then modules={} end modules ['l-math']={ version=1.001, @@ -4844,6 +5135,9 @@ if not modules then modules={} end modules ['l-math']={ license="see context related readme files" } local floor,sin,cos,tan=math.floor,math.sin,math.cos,math.tan +if not math.ceiling then + math.ceiling=math.ceil +end if not math.round then function math.round(x) return floor(x+0.5) end end @@ -4871,7 +5165,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-str"] = package.loaded["util-str"] or true --- original size: 33063, stripped down to: 18397 +-- original size: 34407, stripped down to: 18852 if not modules then modules={} end modules ['util-str']={ version=1.001, @@ -5001,10 +5295,12 @@ local striplinepatterns={ ["retain"]=p_retain_normal, ["retain and collapse"]=p_retain_collapse, ["retain and no empty"]=p_retain_noempty, + ["collapse"]=patterns.collapser, } +setmetatable(striplinepatterns,{ __index=function(t,k) return p_prune_collapse end }) strings.striplinepatterns=striplinepatterns function strings.striplines(str,how) - return str and lpegmatch(how and striplinepatterns[how] or p_prune_collapse,str) or str + return str and lpegmatch(striplinepatterns[how],str) or str end strings.striplong=strings.striplines function strings.nice(str) @@ -5044,10 +5340,10 @@ string.tracedchars=tracedchars strings.tracers=tracedchars function string.tracedchar(b) if type(b)=="number" then - return tracedchars[b] or (utfchar(b).." (U+"..format('%05X',b)..")") + return tracedchars[b] or (utfchar(b).." (U+"..format("%05X",b)..")") else local c=utfbyte(b) - return tracedchars[c] or (b.." (U+"..format('%05X',c)..")") + return tracedchars[c] or (b.." (U+"..(c and format("%05X",c) or "?????")..")") end end function number.signed(i) @@ -5184,12 +5480,12 @@ local format_f=function(f) n=n+1 return format("format('%%%sf',a%s)",f,n) end -local format_F=function(f) +local format_F=function(f) n=n+1 if not f or f=="" then - return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or (a%s == 1 and '1') or format('%%.9f',a%s))",n,n,n,n) + return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n) else - return format("((a%s == 0 and '0') or (a%s == 1 and '1') or format('%%%sf',a%s))",n,n,f,n) + return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n) end end local format_g=function(f) @@ -5417,7 +5713,6 @@ local builder=Cs { "start", +V("j")+V("J") +V("m")+V("M") +V("z") -+V("*") )+V("*") )*(P(-1)+Carg(1)) )^0, @@ -5461,6 +5756,7 @@ local builder=Cs { "start", ["a"]=(prefix_any*P("a"))/format_a, ["A"]=(prefix_any*P("A"))/format_A, ["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest, + ["?"]=Cs(((1-P("%"))^1 )^1)/format_rest, ["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension, } local direct=Cs ( @@ -5535,6 +5831,15 @@ else add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=lpeg.patterns.texescape }) add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=lpeg.patterns.luaescape }) end +local dquote=patterns.dquote +local equote=patterns.escaped+dquote/'\\"'+1 +local space=patterns.space +local cquote=Cc('"') +local pattern=Cs(dquote*(equote-P(-2))^0*dquote) ++Cs(cquote*(equote-space)^0*space*equote^0*cquote) +function string.optionalquoted(str) + return lpegmatch(pattern,str) or str +end end -- of closure @@ -5543,7 +5848,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 23985, stripped down to: 16069 +-- original size: 24267, stripped down to: 16260 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -5633,6 +5938,15 @@ function tables.removevalue(t,value) end end end +function tables.replacevalue(t,oldvalue,newvalue) + if oldvalue and newvalue then + for i=1,#t do + if t[i]==oldvalue then + t[i]=newvalue + end + end + end +end function tables.insertbeforevalue(t,value,extra) for i=1,#t do if t[i]==extra then @@ -6026,7 +6340,7 @@ function table.serialize(root,name,specification) end depth=depth+1 end - if root and next(root) then + if root and next(root)~=nil then local first=nil local last=0 last=#root @@ -6051,7 +6365,7 @@ function table.serialize(root,name,specification) elseif tv=="string" then n=n+1 t[n]=f_val_str(depth,v) elseif tv=="table" then - if not next(v) then + if next(v)==nil then n=n+1 t[n]=f_val_not(depth) else local st=simple_table(v) @@ -6081,7 +6395,7 @@ function table.serialize(root,name,specification) n=n+1 t[n]=f_key_boo_value_str(depth,k,v) end elseif tv=="table" then - if not next(v) then + if next(v)==nil then if tk=="number" then n=n+1 t[n]=f_key_num_value_not(depth,k,v) elseif tk=="string" then @@ -6139,7 +6453,7 @@ function table.serialize(root,name,specification) local dummy=root._w_h_a_t_e_v_e_r_ root._w_h_a_t_e_v_e_r_=nil end - if next(root) then + if next(root)~=nil then do_serialize(root,name,1,0) end end @@ -6302,7 +6616,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-prs"] = package.loaded["util-prs"] or true --- original size: 19618, stripped down to: 14012 +-- original size: 21751, stripped down to: 15108 if not modules then modules={} end modules ['util-prs']={ version=1.001, @@ -6458,11 +6772,30 @@ function parsers.settings_to_array(str,strict) return { str } end end -local separator=space^0*comma*space^0 -local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(comma+P(-1)))))^0) -local withvalue=Carg(1)*value/function(f,s) return f(s) end -local pattern_a=spaces*Ct(value*(separator*value)^0) -local pattern_b=spaces*withvalue*(separator*withvalue)^0 +local cache_a={} +local cache_b={} +function parsers.groupedsplitat(symbol,withaction) + if not symbol then + symbol="," + end + local pattern=(withaction and cache_b or cache_a)[symbol] + if not pattern then + local symbols=S(symbol) + local separator=space^0*symbols*space^0 + local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C((nestedbraces+(1-(space^0*(symbols+P(-1)))))^0) + if withaction then + local withvalue=Carg(1)*value/function(f,s) return f(s) end + pattern=spaces*withvalue*(separator*withvalue)^0 + cache_b[symbol]=pattern + else + pattern=spaces*Ct(value*(separator*value)^0) + cache_a[symbol]=pattern + end + end + return pattern +end +local pattern_a=parsers.groupedsplitat(",",false) +local pattern_b=parsers.groupedsplitat(",",true) function parsers.stripped_settings_to_array(str) if not str or str=="" then return {} @@ -6524,12 +6857,9 @@ function parsers.array_to_string(a,separator) return "" end end -function parsers.settings_to_set(str,t) - t=t or {} - for s in gmatch(str,"[^, ]+") do - t[s]=true - end - return t +local pattern=Cf(Ct("")*Cg(C((1-S(", "))^1)*S(", ")^0*Cc(true))^1,rawset) +function utilities.parsers.settings_to_set(str,t) + return str and lpegmatch(pattern,str) or {} end function parsers.simple_hash_to_string(h,separator) local t,tn={},0 @@ -6541,12 +6871,16 @@ function parsers.simple_hash_to_string(h,separator) end return concat(t,separator or ",") end -local str=C((1-whitespace-equal)^1) +local str=Cs(lpegpatterns.unquoted)+C((1-whitespace-equal)^1) local setting=Cf(Carg(1)*(whitespace^0*Cg(str*whitespace^0*(equal*whitespace^0*str+Cc(""))))^1,rawset) local splitter=setting^1 function utilities.parsers.options_to_hash(str,target) return str and lpegmatch(splitter,str,1,target or {}) or {} end +local splitter=lpeg.tsplitat(" ") +function utilities.parsers.options_to_array(str) + return str and lpegmatch(splitter,str) or {} +end local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C(digit^1*lparent*(noparent+nestedparents)^1*rparent)+C((nestedbraces+(1-comma))^1) local pattern_a=spaces*Ct(value*(separator*value)^0) local function repeater(n,str) @@ -6668,7 +7002,7 @@ function parsers.rfc4180splitter(specification) local field=escaped+non_escaped+Cc("") local record=Ct(field*(separator*field)^1) local headerline=record*Cp() - local wholeblob=Ct((newline^-1*record)^0) + local wholeblob=Ct((newline^(specification.strict and -1 or 1)*record)^0) return function(data,getheader) if getheader then local header,position=lpegmatch(headerline,data) @@ -6705,13 +7039,13 @@ function parsers.stepper(str,n,action) lpegmatch(stepper,str,1,n,action or print) end end -local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) -local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+P(1))^0) +local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) +local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0) patterns.unittotex=pattern function parsers.unittotex(str,textmode) return lpegmatch(textmode and pattern_text or pattern_math,str) end -local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+P(1))^0) +local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+anything)^0) function parsers.unittoxml(str) return lpegmatch(pattern,str) end @@ -6783,6 +7117,18 @@ function utilities.parsers.runtime(time) local seconds=mod(time,60) return days,hours,minutes,seconds end +local spacing=whitespace^0 +local apply=P("->") +local method=C((1-apply)^1) +local token=lbrace*C((1-rbrace)^1)*rbrace+C(anything^1) +local pattern=spacing*(method*spacing*apply+Carg(1))*spacing*token +function utilities.parsers.splitmethod(str,default) + if str then + return lpegmatch(pattern,str,1,default or false) + else + return default or false,"" + end +end end -- of closure @@ -6872,7 +7218,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-set"] = package.loaded["trac-set"] or true --- original size: 12365, stripped down to: 8799 +-- original size: 12482, stripped down to: 8864 if not modules then modules={} end modules ['trac-set']={ version=1.001, @@ -6900,7 +7246,7 @@ function setters.initialize(filename,name,values) local data=setter.data if data then for key,newvalue in next,values do - local newvalue=is_boolean(newvalue,newvalue) + local newvalue=is_boolean(newvalue,newvalue,true) local functions=data[key] if functions then local oldvalue=functions.value @@ -6954,7 +7300,7 @@ local function set(t,what,newvalue) elseif not value then value=false else - value=is_boolean(value,value) + value=is_boolean(value,value,true) end w=topattern(w,true,true) for name,functions in next,data do @@ -7093,6 +7439,7 @@ function setters.new(name) report=function(...) setters.report (setter,...) end, enable=function(...) enable (setter,...) end, disable=function(...) disable (setter,...) end, + reset=function(...) reset (setter,...) end, register=function(...) register(setter,...) end, list=function(...) list (setter,...) end, show=function(...) show (setter,...) end, @@ -7184,7 +7531,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-log"] = package.loaded["trac-log"] or true --- original size: 25613, stripped down to: 16617 +-- original size: 29359, stripped down to: 20483 if not modules then modules={} end modules ['trac-log']={ version=1.001, @@ -7193,15 +7540,18 @@ if not modules then modules={} end modules ['trac-log']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } +local next,type,select,print=next,type,select,print local write_nl,write=texio and texio.write_nl or print,texio and texio.write or io.write local format,gmatch,find=string.format,string.gmatch,string.find local concat,insert,remove=table.concat,table.insert,table.remove local topattern=string.topattern -local next,type,select=next,type,select local utfchar=utf.char +local datetime=os.date +local openfile=io.open local setmetatableindex=table.setmetatableindex local formatters=string.formatters local texgetcount=tex and tex.getcount +local variant="default" logs=logs or {} local logs=logs local moreinfo=[[ @@ -7211,32 +7561,122 @@ maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context webpage : http://www.pragma-ade.nl / http://tex.aanhet.net wiki : http://contextgarden.net ]] -utilities.strings.formatters.add ( +formatters.add ( formatters,"unichr", [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]] ) -utilities.strings.formatters.add ( +formatters.add ( formatters,"chruni", [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]] ) local function ignore() end setmetatableindex(logs,function(t,k) t[k]=ignore;return ignore end) local report,subreport,status,settarget,setformats,settranslations -local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters +local direct,subdirect,writer,pushtarget,poptarget,setlogfile,settimedlog,setprocessor,setformatters,newline if tex and (tex.jobname or tex.formatname) then - local valueiskey={ __index=function(t,k) t[k]=k return k end } - local target="term and log" + local function useluawrites() + local texio_write_nl=texio.write_nl + local texio_write=texio.write + local io_write=io.write + write_nl=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write_nl("log",...) + texio_write_nl("term","") + io_write(...) + elseif target=="log" then + texio_write_nl("log",...) + elseif target=="term" then + texio_write_nl("term","") + io_write(...) + elseif target~="none" then + texio_write_nl("log",target,...) + texio_write_nl("term","") + io_write(target,...) + end + end + write=function(target,...) + if not io_write then + io_write=io.write + end + if target=="term and log" then + texio_write("log",...) + io_write(...) + elseif target=="log" then + texio_write("log",...) + elseif target=="term" then + io_write(...) + elseif target~="none" then + texio_write("log",target,...) + io_write(target,...) + end + end + texio.write=write + texio.write_nl=write_nl + useluawrites=ignore + end + local whereto="both" + local target=nil + local targets=nil + local formats=table.setmetatableindex("self") + local translations=table.setmetatableindex("self") + local report_yes,subreport_yes,direct_yes,subdirect_yes,status_yes + local report_nop,subreport_nop,direct_nop,subdirect_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s > %s\n"], + report_nop=formatters["%-15s >\n"], + direct_yes=formatters["%-15s > %s"], + direct_nop=formatters["%-15s >"], + subreport_yes=formatters["%-15s > %s > %s\n"], + subreport_nop=formatters["%-15s > %s >\n"], + subdirect_yes=formatters["%-15s > %s > %s"], + subdirect_nop=formatters["%-15s > %s >"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + targets={ + logfile="log", + log="log", + file="log", + console="term", + terminal="term", + both="term and log", + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;33m%-15s [0;1m>[0m %s\n"], + report_nop=formatters["[0;33m%-15s [0;1m>[0m\n"], + direct_yes=formatters["[0;33m%-15s [0;1m>[0m %s"], + direct_nop=formatters["[0;33m%-15s [0;1m>[0m"], + subreport_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"], + subreport_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"], + subdirect_yes=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"], + subdirect_nop=formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"], + status_yes=formatters["[0;33m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;33m%-15s [0;1m:[0m\n"], + }, + targets={ + logfile="none", + log="none", + file="none", + console="term", + terminal="term", + both="term", + }, + } + } logs.flush=io.flush - local formats={} setmetatable(formats,valueiskey) - local translations={} setmetatable(translations,valueiskey) writer=function(...) write_nl(target,...) end newline=function() write_nl(target,"\n") end - local report_yes=formatters["%-15s > %s\n"] - local report_nop=formatters["%-15s >\n"] report=function(a,b,c,...) if c then write_nl(target,report_yes(translations[a],formatters[formats[b]](c,...))) @@ -7248,8 +7688,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local direct_yes=formatters["%-15s > %s"] - local direct_nop=formatters["%-15s >"] direct=function(a,b,c,...) if c then return direct_yes(translations[a],formatters[formats[b]](c,...)) @@ -7261,8 +7699,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local subreport_yes=formatters["%-15s > %s > %s\n"] - local subreport_nop=formatters["%-15s > %s >\n"] subreport=function(a,s,b,c,...) if c then write_nl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...))) @@ -7274,8 +7710,6 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local subdirect_yes=formatters["%-15s > %s > %s"] - local subdirect_nop=formatters["%-15s > %s >"] subdirect=function(a,s,b,c,...) if c then return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...)) @@ -7287,8 +7721,6 @@ if tex and (tex.jobname or tex.formatname) then return "" end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(target,status_yes(translations[a],formatters[formats[b]](c,...))) @@ -7300,16 +7732,13 @@ if tex and (tex.jobname or tex.formatname) then write_nl(target,"\n") end end - local targets={ - logfile="log", - log="log", - file="log", - console="term", - terminal="term", - both="term and log", - } - settarget=function(whereto) - target=targets[whereto or "both"] or targets.both + settarget=function(askedwhereto) + whereto=askedwhereto or whereto or "both" + target=targets[whereto] + if not target then + whereto="both" + target=targets[whereto] + end if target=="term" or target=="term and log" then logs.flush=io.flush else @@ -7338,21 +7767,74 @@ if tex and (tex.jobname or tex.formatname) then writeline(target,f(...)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - direct_yes=f.direct_yes or direct_yes - direct_nop=f.direct_nop or direct_nop - subdirect_yes=f.subdirect_yes or subdirect_yes - subdirect_nop=f.subdirect_nop or subdirect_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local t=nil + local f=nil + local d=variants.default + if not specification then + elseif type(specification)=="table" then + t=specification.targets + f=specification.formats or specification + else + local v=variants[specification] + if v then + t=v.targets + f=v.formats + variant=specification + end + end + targets=t or d.targets + target=targets[whereto] or target + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + direct_yes=f.direct_yes + direct_nop=f.direct_nop + subdirect_yes=f.subdirect_yes + subdirect_nop=f.subdirect_nop + status_yes=f.status_yes + status_nop=f.status_nop + if variant=="ansi" then + useluawrites() + end + settarget(whereto) + end + setformatters(variant) setlogfile=ignore settimedlog=ignore else + local report_yes,subreport_yes,status_yes + local report_nop,subreport_nop,status_nop + local variants={ + default={ + formats={ + report_yes=formatters["%-15s | %s"], + report_nop=formatters["%-15s |"], + subreport_yes=formatters["%-15s | %s | %s"], + subreport_nop=formatters["%-15s | %s |"], + status_yes=formatters["%-15s : %s\n"], + status_nop=formatters["%-15s :\n"], + }, + }, + ansi={ + formats={ + report_yes=formatters["[0;32m%-15s [0;1m|[0m %s"], + report_nop=formatters["[0;32m%-15s [0;1m|[0m"], + subreport_yes=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m %s"], + subreport_nop=formatters["[0;32m%-15s [0;1m|[0;31m %s [0;1m|[0m"], + status_yes=formatters["[0;32m%-15s [0;1m:[0m %s\n"], + status_nop=formatters["[0;32m%-15s [0;1m:[0m\n"], + }, + }, + } logs.flush=ignore writer=function(s) write_nl(s) @@ -7360,8 +7842,6 @@ else newline=function() write_nl("\n") end - local report_yes=formatters["%-15s | %s"] - local report_nop=formatters["%-15s |"] report=function(a,b,c,...) if c then write_nl(report_yes(a,formatters[b](c,...))) @@ -7373,8 +7853,6 @@ else write_nl("") end end - local subreport_yes=formatters["%-15s | %s | %s"] - local subreport_nop=formatters["%-15s | %s |"] subreport=function(a,sub,b,c,...) if c then write_nl(subreport_yes(a,sub,formatters[b](c,...))) @@ -7386,8 +7864,6 @@ else write_nl("") end end - local status_yes=formatters["%-15s : %s\n"] - local status_nop=formatters["%-15s :\n"] status=function(a,b,c,...) if c then write_nl(status_yes(a,formatters[b](c,...))) @@ -7412,14 +7888,34 @@ else writeline(f(s)) end end - setformatters=function(f) - report_yes=f.report_yes or report_yes - report_nop=f.report_nop or report_nop - subreport_yes=f.subreport_yes or subreport_yes - subreport_nop=f.subreport_nop or subreport_nop - status_yes=f.status_yes or status_yes - status_nop=f.status_nop or status_nop - end + setformatters=function(specification) + local f=nil + local d=variants.default + if specification then + if type(specification)=="table" then + f=specification.formats or specification + else + local v=variants[specification] + if v then + f=v.formats + end + end + end + if f then + d=d.formats + else + f=d.formats + d=f + end + setmetatableindex(f,d) + report_yes=f.report_yes + report_nop=f.report_nop + subreport_yes=f.subreport_yes + subreport_nop=f.subreport_nop + status_yes=f.status_yes + status_nop=f.status_nop + end + setformatters(variant) setlogfile=function(name,keepopen) if name and name~="" then local localtime=os.localtime @@ -7672,13 +8168,13 @@ end local simple=logs.reporter("comment") logs.simple=simple logs.simpleline=simple -function logs.setprogram () end -function logs.extendbanner() end -function logs.reportlines () end -function logs.reportbanner() end -function logs.reportline () end -function logs.simplelines () end -function logs.help () end +logs.setprogram=ignore +logs.extendbanner=ignore +logs.reportlines=ignore +logs.reportbanner=ignore +logs.reportline=ignore +logs.simplelines=ignore +logs.help=ignore local Carg,C,lpegmatch=lpeg.Carg,lpeg.C,lpeg.match local p_newline=lpeg.patterns.newline local linewise=( @@ -7755,10 +8251,11 @@ function logs.application(t) end return t end -function logs.system(whereto,process,jobname,category,...) - local message=formatters["%s %s => %s => %s => %s\r"](os.date("%d/%m/%y %H:%m:%S"),process,jobname,category,format(...)) +local f_syslog=formatters["%s %s => %s => %s => %s\r"] +function logs.system(whereto,process,jobname,category,fmt,arg,...) + local message=f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg==nil and fmt or format(fmt,arg,...)) for i=1,10 do - local f=io.open(whereto,"a") + local f=openfile(whereto,"a") if f then f:write(message) f:close() @@ -7820,7 +8317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["trac-inf"] = package.loaded["trac-inf"] or true --- original size: 7011, stripped down to: 5590 +-- original size: 6704, stripped down to: 5343 if not modules then modules={} end modules ['trac-inf']={ version=1.001, @@ -7993,15 +8490,6 @@ function statistics.tracefunction(base,tag,...) statistics.register(formatters["%s.%s"](tag,name),function() return serialize(stat,"calls") end) end end -commands=commands or {} -function commands.resettimer(name) - resettiming(name or "whatever") - starttiming(name or "whatever") -end -function commands.elapsedtime(name) - stoptiming(name or "whatever") - context(elapsedtime(name or "whatever")) -end end -- of closure @@ -8568,7 +9056,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tpl"] = package.loaded["util-tpl"] or true --- original size: 6251, stripped down to: 3488 +-- original size: 7100, stripped down to: 3978 if not modules then modules={} end modules ['util-tpl']={ version=1.001, @@ -8610,7 +9098,7 @@ local sqlescape=lpeg.replacer { { "\r\n","\\n" }, { "\r","\\n" }, } -local sqlquoted=lpeg.Cs(lpeg.Cc("'")*sqlescape*lpeg.Cc("'")) +local sqlquoted=Cs(Cc("'")*sqlescape*Cc("'")) lpegpatterns.sqlescape=sqlescape lpegpatterns.sqlquoted=sqlquoted local luaescape=lpegpatterns.luaescape @@ -8633,12 +9121,24 @@ local quotedescapers={ local luaescaper=escapers.lua local quotedluaescaper=quotedescapers.lua local function replacekeyunquoted(s,t,how,recurse) - local escaper=how and escapers[how] or luaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and escapers[how] or luaescaper + return escaper(replacekey(s,t,how,recurse)) + end end local function replacekeyquoted(s,t,how,recurse) - local escaper=how and quotedescapers[how] or quotedluaescaper - return escaper(replacekey(s,t,how,recurse)) + if how==false then + return replacekey(s,t,how,recurse) + else + local escaper=how and quotedescapers[how] or quotedluaescaper + return escaper(replacekey(s,t,how,recurse)) + end +end +local function replaceoptional(l,m,r,t,how,recurse) + local v=t[l] + return v and v~="" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or "" end local single=P("%") local double=P("%%") @@ -8653,11 +9153,16 @@ local nolquoted=lquoted/'' local norquoted=rquoted/'' local nolquotedq=lquotedq/'' local norquotedq=rquotedq/'' -local key=nosingle*((C((1-nosingle )^1)*Carg(1)*Carg(2)*Carg(3))/replacekey )*nosingle -local quoted=nolquotedq*((C((1-norquotedq)^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyquoted )*norquotedq -local unquoted=nolquoted*((C((1-norquoted )^1)*Carg(1)*Carg(2)*Carg(3))/replacekeyunquoted)*norquoted +local noloptional=P("%?")/'' +local noroptional=P("?%")/'' +local nomoptional=P(":")/'' +local args=Carg(1)*Carg(2)*Carg(3) +local key=nosingle*((C((1-nosingle )^1)*args)/replacekey )*nosingle +local quoted=nolquotedq*((C((1-norquotedq )^1)*args)/replacekeyquoted )*norquotedq +local unquoted=nolquoted*((C((1-norquoted )^1)*args)/replacekeyunquoted)*norquoted +local optional=noloptional*((C((1-nomoptional)^1)*nomoptional*C((1-noroptional)^1)*args)/replaceoptional)*noroptional local any=P(1) - replacer=Cs((unquoted+quoted+escape+key+any)^0) + replacer=Cs((unquoted+quoted+escape+optional+key+any)^0) local function replace(str,mapping,how,recurse) if mapping and str then return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str @@ -8696,7 +9201,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-env"] = package.loaded["util-env"] or true --- original size: 8814, stripped down to: 5092 +-- original size: 8022, stripped down to: 5038 if not modules then modules={} end modules ['util-env']={ version=1.001, @@ -8707,7 +9212,7 @@ if not modules then modules={} end modules ['util-env']={ } local allocate,mark=utilities.storage.allocate,utilities.storage.mark local format,sub,match,gsub,find=string.format,string.sub,string.match,string.gsub,string.find -local unquoted,quoted=string.unquoted,string.quoted +local unquoted,quoted,optionalquoted=string.unquoted,string.quoted,string.optionalquoted local concat,insert,remove=table.concat,table.insert,table.remove environment=environment or {} local environment=environment @@ -8820,24 +9325,14 @@ function environment.splitarguments(separator) return before,after end function environment.reconstructcommandline(arg,noquote) + local resolveprefix=resolvers.resolve arg=arg or environment.originalarguments if noquote and #arg==1 then - local a=arg[1] - a=resolvers.resolve(a) - a=unquoted(a) - return a + return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1]) elseif #arg>0 then local result={} for i=1,#arg do - local a=arg[i] - a=resolvers.resolve(a) - a=unquoted(a) - a=gsub(a,'"','\\"') - if find(a," ",1,true) then - result[#result+1]=quoted(a) - else - result[#result+1]=a - end + result[i]=optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix) end return concat(result," ") else @@ -9046,7 +9541,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-tab"] = package.loaded["lxml-tab"] or true --- original size: 42614, stripped down to: 26694 +-- original size: 43882, stripped down to: 26870 if not modules then modules={} end modules ['lxml-tab']={ version=1.001, @@ -9061,10 +9556,10 @@ if lpeg.setmaxstack then lpeg.setmaxstack(1000) end xml=xml or {} local xml=xml local concat,remove,insert=table.concat,table.remove,table.insert -local type,next,setmetatable,getmetatable,tonumber=type,next,setmetatable,getmetatable,tonumber +local type,next,setmetatable,getmetatable,tonumber,rawset=type,next,setmetatable,getmetatable,tonumber,rawset local lower,find,match,gsub=string.lower,string.find,string.match,string.gsub local utfchar=utf.char -local lpegmatch=lpeg.match +local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local P,S,R,C,V,C,Cs=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.V,lpeg.C,lpeg.Cs local formatters=string.formatters xml.xmlns=xml.xmlns or {} @@ -9226,9 +9721,10 @@ local function fromdec(s) return formatters["d:%s"](s),true end end -local rest=(1-P(";"))^0 -local many=P(1)^0 -local parsedentity=P("&")*(P("#x")*(rest/fromhex)+P("#")*(rest/fromdec))*P(";")*P(-1)+(P("#x")*(many/fromhex)+P("#")*(many/fromdec)) +local p_rest=(1-P(";"))^0 +local p_many=P(1)^0 +local p_char=lpegpatterns.utf8character +local parsedentity=P("&")*(P("#x")*(p_rest/fromhex)+P("#")*(p_rest/fromdec))*P(";")*P(-1)+(P("#x")*(p_many/fromhex)+P("#")*(p_many/fromdec)) local predefined_unified={ [38]="&", [42]=""", @@ -9254,7 +9750,9 @@ local privates_u={ local privates_p={} local privates_n={ } -local escaped=utf.remapper(privates_u) +local escaped=utf.remapper(privates_u,"dynamic") +local unprivatized=utf.remapper(privates_p,"dynamic") +xml.unprivatized=unprivatized local function unescaped(s) local p=privates_n[s] if not p then @@ -9267,9 +9765,7 @@ local function unescaped(s) end return p end -local unprivatized=utf.remapper(privates_p) xml.privatetoken=unescaped -xml.unprivatized=unprivatized xml.privatecodes=privates_n local function handle_hex_entity(str) local h=hcache[str] @@ -9422,7 +9918,7 @@ local valid=R('az','AZ','09')+S('_-.') local name_yes=C(valid^1)*colon*C(valid^1) local name_nop=C(P(true))*C(valid^1) local name=name_yes+name_nop -local utfbom=lpeg.patterns.utfbom +local utfbom=lpegpatterns.utfbom local spacing=C(space^0) local anyentitycontent=(1-open-semicolon-space-close)^0 local hexentitycontent=R("AF","af","09")^0 @@ -9590,7 +10086,7 @@ local function _xmlconvert_(data,settings) reported_attribute_errors,mt,errorhandler=nil,nil,nil return result end -function xmlconvert(data,settings) +local function xmlconvert(data,settings) local ok,result=pcall(function() return _xmlconvert_(data,settings) end) if ok then return result @@ -9682,14 +10178,17 @@ function xml.checkbom(root) insert(dt,2,"\n" ) end end -local function verbose_element(e,handlers) +local f_attribute=formatters['%s=%q'] +local function verbose_element(e,handlers,escape) local handle=handlers.handle local serialize=handlers.serialize local ens,etg,eat,edt,ern=e.ns,e.tg,e.at,e.dt,e.rn local ats=eat and next(eat) and {} if ats then + local n=0 for k,v in next,eat do - ats[#ats+1]=formatters['%s=%q'](k,escaped(v)) + n=n+1 + ats[n]=f_attribute(k,escaped(v)) end end if ern and trace_entities and ern~=ens then @@ -10033,7 +10532,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48030, stripped down to: 30595 +-- original size: 48229, stripped down to: 30684 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -10418,8 +10917,8 @@ local lp_builtin=P ( P("ns")/"ll.ns" )*((spaces*P("(")*spaces*P(")"))/"") local lp_attribute=(P("@")+P("attribute::"))/""*Cc("(ll.at and ll.at['")*((R("az","AZ")+S("-_:"))^1)*Cc("'])") -lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" -lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" +local lp_fastpos_p=P("+")^0*R("09")^1*P(-1)/"l==%0" +local lp_fastpos_n=P("-")*R("09")^1*P(-1)/"(%0<0 and (#list+%0==l))" local lp_fastpos=lp_fastpos_n+lp_fastpos_p local lp_reserved=C("and")+C("or")+C("not")+C("div")+C("mod")+C("true")+C("false") local lp_lua_function=Cs((R("az","AZ","__")^1*(P(".")*R("az","AZ","__")^1)^1)*("("))/"%0" @@ -10598,7 +11097,7 @@ local function nodesettostring(set,nodetest) if not ns or ns=="" then ns="*" end if not tg or tg=="" then tg="*" end tg=(tg=="@rt@" and "[root]") or format("%s:%s",ns,tg) - t[i]=(directive and tg) or format("not(%s)",tg) + t[#t+1]=(directive and tg) or format("not(%s)",tg) end if nodetest==false then return format("not(%s)",concat(t,"|")) @@ -10864,7 +11363,6 @@ expressions.print=function(...) print(...) return true end -expressions.contains=find expressions.find=find expressions.upper=upper expressions.lower=lower @@ -10886,6 +11384,9 @@ function expressions.contains(str,pattern) end return false end +function xml.expressions.idstring(str) + return type(str)=="string" and gsub(str,"^#","") or "" +end local function traverse(root,pattern,handle) local collected=applylpath(root,pattern) if collected then @@ -11173,7 +11674,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-aux"] = package.loaded["lxml-aux"] or true --- original size: 23804, stripped down to: 16817 +-- original size: 28225, stripped down to: 20125 if not modules then modules={} end modules ['lxml-aux']={ version=1.001, @@ -11183,16 +11684,19 @@ if not modules then modules={} end modules ['lxml-aux']={ license="see context related readme files" } local trace_manipulations=false trackers.register("lxml.manipulations",function(v) trace_manipulations=v end) +local trace_inclusions=false trackers.register("lxml.inclusions",function(v) trace_inclusions=v end) local report_xml=logs.reporter("xml") local xml=xml -local xmlconvert,xmlcopy,xmlname=xml.convert,xml.copy,xml.name +local xmlcopy,xmlname=xml.copy,xml.name local xmlinheritedconvert=xml.inheritedconvert local xmlapplylpath=xml.applylpath local xmlfilter=xml.filter -local type,setmetatable,getmetatable=type,setmetatable,getmetatable +local type,next,setmetatable,getmetatable=type,next,setmetatable,getmetatable local insert,remove,fastcopy,concat=table.insert,table.remove,table.fastcopy,table.concat local gmatch,gsub,format,find,strip=string.gmatch,string.gsub,string.format,string.find,string.strip local utfbyte=utf.byte +local lpegmatch=lpeg.match +local striplinepatterns=utilities.strings.striplinepatterns local function report(what,pattern,c,e) report_xml("%s element %a, root %a, position %a, index %a, pattern %a",what,xmlname(e),xmlname(e.__p__),c,e.ni,pattern) end @@ -11247,13 +11751,15 @@ end function xml.each(root,pattern,handle,reverse) local collected=xmlapplylpath(root,pattern) if collected then - if reverse then - for c=#collected,1,-1 do - handle(collected[c]) - end - else - for c=1,#collected do - handle(collected[c]) + if handle then + if reverse then + for c=#collected,1,-1 do + handle(collected[c]) + end + else + for c=1,#collected do + handle(collected[c]) + end end end return collected @@ -11309,6 +11815,7 @@ local function redo_ni(d) end end end +xml.reindex=redo_ni local function xmltoelement(whatever,root) if not whatever then return nil @@ -11360,8 +11867,16 @@ function xml.delete(root,pattern) report('deleting',pattern,c,e) end local d=p.dt - remove(d,e.ni) - redo_ni(d) + local ni=e.ni + if ni<=#d then + if false then + p.dt[ni]="" + else + remove(d,ni) + redo_ni(d) + end + else + end end end end @@ -11481,28 +11996,40 @@ xml.insertafter=insert_element xml.insertbefore=function(r,p,e) insert_element(r,p,e,true) end xml.injectafter=inject_element xml.injectbefore=function(r,p,e) inject_element(r,p,e,true) end -local function include(xmldata,pattern,attribute,recursive,loaddata) +local function include(xmldata,pattern,attribute,recursive,loaddata,level) pattern=pattern or 'include' loaddata=loaddata or io.loaddata local collected=xmlapplylpath(xmldata,pattern) if collected then + if not level then + level=1 + end for c=1,#collected do local ek=collected[c] local name=nil local ekdt=ek.dt local ekat=ek.at - local epdt=ek.__p__.dt + local ekrt=ek.__p__ + local epdt=ekrt.dt if not attribute or attribute=="" then name=(type(ekdt)=="table" and ekdt[1]) or ekdt end if not name then for a in gmatch(attribute or "href","([^|]+)") do name=ekat[a] - if name then break end + if name then + break + end + end + end + local data=nil + if name and name~="" then + data=loaddata(name) or "" + if trace_inclusions then + report_xml("including %s bytes from %a at level %s by pattern %a and attribute %a (%srecursing)",#data,name,level,pattern,attribute or "",recursive and "" or "not ") end end - local data=(name and name~="" and loaddata(name)) or "" - if data=="" then + if not data or data=="" then epdt[ek.ni]="" elseif ekat["parse"]=="text" then epdt[ek.ni]=xml.escaped(data) @@ -11512,70 +12039,113 @@ local function include(xmldata,pattern,attribute,recursive,loaddata) epdt[ek.ni]="" else if recursive then - include(xi,pattern,attribute,recursive,loaddata) + include(xi,pattern,attribute,recursive,loaddata,level+1) + end + local child=xml.body(xi) + child.__p__=ekrt + child.__f__=name + epdt[ek.ni]=child + local inclusions=xmldata.settings.inclusions + if inclusions then + inclusions[#inclusions+1]=name + else + xmldata.settings.inclusions={ name } end - epdt[ek.ni]=xml.body(xi) end end end end end xml.include=include +function xml.inclusion(e,default) + while e do + local f=e.__f__ + if f then + return f + else + e=e.__p__ + end + end + return default +end +function xml.inclusions(e,sorted) + while e do + local settings=e.settings + if settings then + local inclusions=settings.inclusions + if inclusions then + inclusions=table.unique(inclusions) + if sorted then + table.sort(inclusions) + end + return inclusions + else + e=e.__p__ + end + else + e=e.__p__ + end + end +end +local b_collapser=lpeg.patterns.b_collapser +local m_collapser=lpeg.patterns.m_collapser +local e_collapser=lpeg.patterns.e_collapser +local b_stripper=lpeg.patterns.b_stripper +local m_stripper=lpeg.patterns.m_stripper +local e_stripper=lpeg.patterns.e_stripper +local lpegmatch=lpeg.match local function stripelement(e,nolines,anywhere) local edt=e.dt if edt then - if anywhere then - local t,n={},0 - for e=1,#edt do + local n=#edt + if n==0 then + return e + elseif anywhere then + local t={} + local m=0 + for e=1,n do local str=edt[e] if type(str)~="string" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str elseif str~="" then if nolines then - str=gsub(str,"%s+"," ") + str=lpegmatch((n==1 and b_collapser) or (n==m and e_collapser) or m_collapser,str) + else + str=lpegmatch((n==1 and b_stripper) or (n==m and e_stripper) or m_stripper,str) end - str=gsub(str,"^%s*(.-)%s*$","%1") if str~="" then - n=n+1 - t[n]=str + m=m+1 + t[m]=str end end end e.dt=t else - if #edt>0 then - local str=edt[1] - if type(str)~="string" then - elseif str=="" then + local str=edt[1] + if type(str)=="string" then + if str~="" then + str=lpegmatch(nolines and b_collapser or b_stripper,str) + end + if str=="" then remove(edt,1) + n=n-1 else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"^%s+","") - if str=="" then - remove(edt,1) - else - edt[1]=str - end + edt[1]=str end end - local nedt=#edt - if nedt>0 then - local str=edt[nedt] - if type(str)~="string" then - elseif str=="" then - remove(edt) - else - if nolines then - str=gsub(str,"%s+"," ") - end - str=gsub(str,"%s+$","") + if n>0 then + str=edt[n] + if type(str)=="string" then if str=="" then remove(edt) else - edt[nedt]=str + str=lpegmatch(nolines and e_collapser or e_stripper,str) + if str=="" then + remove(edt) + else + edt[n]=str + end end end end @@ -11761,8 +12331,8 @@ function xml.finalizers.xml.cdata(collected) end return "" end -function xml.insertcomment(e,str,n) - table.insert(e.dt,n or 1,{ +function xml.insertcomment(e,str,n) + insert(e.dt,n or 1,{ tg="@cm@", ns="", special=true, @@ -11770,7 +12340,25 @@ function xml.insertcomment(e,str,n) dt={ str }, }) end -function xml.setcdata(e,str) +function xml.insertcdata(e,str,n) + insert(e.dt,n or 1,{ + tg="@cd@", + ns="", + special=true, + at={}, + dt={ str }, + }) +end +function xml.setcomment(e,str,n) + e.dt={ { + tg="@cm@", + ns="", + special=true, + at={}, + dt={ str }, + } } +end +function xml.setcdata(e,str) e.dt={ { tg="@cd@", ns="", @@ -11840,7 +12428,7 @@ local function recurse(e,action) for i=1,#edt do local str=edt[i] if type(str)~="string" then - recurse(str,action,recursive) + recurse(str,action) elseif str~="" then edt[i]=action(str) end @@ -11858,6 +12446,65 @@ function helpers.recursetext(collected,action,recursive) end end end +local specials={ + ["@rt@"]="root", + ["@pi@"]="instruction", + ["@cm@"]="comment", + ["@dt@"]="declaration", + ["@cd@"]="cdata", +} +local function convert(x,strip,flat) + local ns=x.ns + local tg=x.tg + local at=x.at + local dt=x.dt + local node=flat and { + [0]=(not x.special and (ns~="" and ns..":"..tg or tg)) or nil, + } or { + _namespace=ns~="" and ns or nil, + _tag=not x.special and tg or nil, + _type=specials[tg] or "_element", + } + if at then + for k,v in next,at do + node[k]=v + end + end + local n=0 + for i=1,#dt do + local di=dt[i] + if type(di)=="table" then + if flat and di.special then + else + di=convert(di,strip,flat) + if di then + n=n+1 + node[n]=di + end + end + elseif strip then + di=lpegmatch(strip,di) + if di~="" then + n=n+1 + node[n]=di + end + else + n=n+1 + node[n]=di + end + end + if next(node) then + return node + end +end +function xml.totable(x,strip,flat) + if type(x)=="table" then + if strip then + strip=striplinepatterns[strip] + end + return convert(x,strip,flat) + end +end end -- of closure @@ -12414,7 +13061,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-ini"] = package.loaded["data-ini"] or true --- original size: 7927, stripped down to: 5528 +-- original size: 11085, stripped down to: 7662 if not modules then modules={} end modules ['data-ini']={ version=1.001, @@ -12423,14 +13070,15 @@ if not modules then modules={} end modules ['data-ini']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files", } +local next,type,getmetatable,rawset=next,type,getmetatable,rawset local gsub,find,gmatch,char=string.gsub,string.find,string.gmatch,string.char -local next,type=next,type local filedirname,filebasename,filejoin=file.dirname,file.basename,file.join +local ostype,osname,osuname,ossetenv,osgetenv=os.type,os.name,os.uname,os.setenv,os.getenv +local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers.register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) local report_initialization=logs.reporter("resolvers","initialization") -local ostype,osname,ossetenv,osgetenv=os.type,os.name,os.setenv,os.getenv resolvers=resolvers or {} local resolvers=resolvers texconfig.kpse_init=false @@ -12563,10 +13211,103 @@ if type(profiler)=="table" and not jit then profiler.start("luatex-profile.log") end) end -if not resolvers.resolve then - function resolvers.resolve (s) return s end - function resolvers.unresolve(s) return s end - function resolvers.repath (s) return s end +local prefixes=utilities.storage.allocate() +resolvers.prefixes=prefixes +local resolved={} +local abstract={} +local dynamic={} +function resolvers.resetresolve(str) + resolved,abstract={},{} +end +function resolvers.allprefixes(separator) + local all=table.sortedkeys(prefixes) + if separator then + for i=1,#all do + all[i]=all[i]..":" + end + end + return all +end +local function _resolve_(method,target) + local action=prefixes[method] + if action then + return action(target) + else + return method..":"..target + end +end +function resolvers.unresolve(str) + return abstract[str] or str +end +function resolvers.setdynamic(str) + dynamic[str]=true +end +local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) +local prefix=C(R("az")^2)*P(":") +local target=C((1-S(" \"\';,"))^1) +local notarget=(#S(";,")+P(-1))*Cc("") +local p_resolve=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) +local p_simple=prefix*P(-1) +local function resolve(str) + if type(str)=="table" then + local res={} + for i=1,#str do + res[i]=resolve(str[i]) + end + return res + end + local res=resolved[str] + if res then + return res + end + local simple=lpegmatch(p_simple,str) + local action=prefixes[simple] + if action then + local res=action(res) + if not dynamic[simple] then + resolved[simple]=res + abstract[res]=simple + end + return res + end + res=lpegmatch(p_resolve,str) + resolved[str]=res + abstract[res]=str + return res +end +resolvers.resolve=resolve +if type(osuname)=="function" then + for k,v in next,osuname() do + if not prefixes[k] then + prefixes[k]=function() return v end + end + end +end +if ostype=="unix" then + local pattern + local function makepattern(t,k,v) + if t then + rawset(t,k,v) + end + local colon=P(":") + for k,v in table.sortedpairs(prefixes) do + if p then + p=P(k)+p + else + p=P(k) + end + end + pattern=Cs((p*colon+colon/";"+P(1))^0) + end + makepattern() + table.setmetatablenewindex(prefixes,makepattern) + function resolvers.repath(str) + return lpegmatch(pattern,str) + end +else + function resolvers.repath(str) + return str + end end @@ -12576,7 +13317,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-exp"] = package.loaded["data-exp"] or true --- original size: 15317, stripped down to: 9723 +-- original size: 17216, stripped down to: 10657 if not modules then modules={} end modules ['data-exp']={ version=1.001, @@ -12590,12 +13331,16 @@ local concat,sort=table.concat,table.sort local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns local Ct,Cs,Cc,Carg,P,C,S=lpeg.Ct,lpeg.Cs,lpeg.Cc,lpeg.Carg,lpeg.P,lpeg.C,lpeg.S local type,next=type,next +local isdir=lfs.isdir local ostype=os.type -local collapsepath=file.collapsepath +local collapsepath,joinpath,basename=file.collapsepath,file.join,file.basename local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local trace_expansions=false trackers.register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_globbing=true trackers.register("resolvers.globbing",function(v) trace_globbing=v end) local report_expansions=logs.reporter("resolvers","expansions") +local report_globbing=logs.reporter("resolvers","globbing") local resolvers=resolvers +local resolveprefix=resolvers.resolve local function f_both(a,b) local t,n={},0 for sb in gmatch(b,"[^,]+") do @@ -12685,35 +13430,27 @@ function resolvers.expandedpathfromlist(pathlist) end return newlist end -local cleanup=lpeg.replacer { - { "!","" }, - { "\\","/" }, -} -function resolvers.cleanpath(str) - local doslashes=(P("\\")/"/"+1)^0 - local donegation=(P("!")/"" )^0 - local homedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") - if homedir=="~" or homedir=="" or not lfs.isdir(homedir) then - if trace_expansions then - report_expansions("no home dir set, ignoring dependent paths") - end - function resolvers.cleanpath(str) - if not str or find(str,"~",1,true) then - return "" - else - return lpegmatch(cleanup,str) +local usedhomedir=nil +local donegation=(P("!")/"" )^0 +local doslashes=(P("\\")/"/"+1)^0 +local function expandedhome() + if not usedhomedir then + usedhomedir=lpegmatch(Cs(donegation*doslashes),environment.homedir or "") + if usedhomedir=="~" or usedhomedir=="" or not isdir(usedhomedir) then + if trace_expansions then + report_expansions("no home dir set, ignoring dependent path using current path") end - end - else - local dohome=((P("~")+P("$HOME"))/homedir)^0 - local cleanup=Cs(donegation*dohome*doslashes) - function resolvers.cleanpath(str) - return str and lpegmatch(cleanup,str) or "" + usedhomedir="." end end - return resolvers.cleanpath(str) + return usedhomedir +end +local dohome=((P("~")+P("$HOME")+P("%HOME%"))/expandedhome)^0 +local cleanup=Cs(donegation*dohome*doslashes) +resolvers.cleanpath=function(str) + return str and lpegmatch(cleanup,str) or "" end -local expandhome=P("~")/"$HOME" +local expandhome=P("~")/"$HOME" local dodouble=P('"')/""*(expandhome+(1-P('"')))^0*P('"')/"" local dosingle=P("'")/""*(expandhome+(1-P("'")))^0*P("'")/"" local dostring=(expandhome+1 )^0 @@ -12765,46 +13502,67 @@ function resolvers.splitpath(str) end function resolvers.joinpath(str) if type(str)=='table' then - return file.joinpath(str) + return joinpath(str) else return str end end local attributes,directory=lfs.attributes,lfs.dir local weird=P(".")^1+lpeg.anywhere(S("~`!#$%^&*()={}[]:;\"\'||<>,?\n\r\t")) +local lessweird=P(".")^1+lpeg.anywhere(S("~`#$%^&*:;\"\'||<>,?\n\r\t")) local timer={} local scanned={} local nofscans=0 local scancache={} -local function scan(files,spec,path,n,m,r) - local full=(path=="" and spec) or (spec..path..'/') +local fullcache={} +local nofsharedscans=0 +local function scan(files,remap,spec,path,n,m,r,onlyone,tolerant) + local full=path=="" and spec or (spec..path..'/') local dirs={} local nofdirs=0 + local pattern=tolerant and lessweird or weird for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then + if not lpegmatch(pattern,name) then + local mode=attributes(full..name,"mode") + if mode=="file" then n=n+1 - local f=files[name] - if f then - if type(f)=='string' then - files[name]={ f,path } + local lower=lower(name) + local paths=files[lower] + if paths then + if onlyone then else - f[#f+1]=path + if type(paths)=="string" then + files[lower]={ paths,path } + else + paths[#paths+1]=path + end + if name~=lower then + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end + end end else - files[name]=path - local lower=lower(name) + files[lower]=path if name~=lower then - files["remap:"..lower]=name - r=r+1 + local rl=remap[lower] + if not rl then + remap[lower]=name + r=r+1 + elseif trace_globbing and rl~=name then + report_globbing("confusing filename, name: %a, lower: %a, already: %a",name,lower,rl) + end end end - elseif mode=='directory' then + elseif mode=="directory" then m=m+1 nofdirs=nofdirs+1 if path~="" then - dirs[nofdirs]=path..'/'..name + dirs[nofdirs]=path.."/"..name else dirs[nofdirs]=name end @@ -12814,107 +13572,69 @@ local function scan(files,spec,path,n,m,r) if nofdirs>0 then sort(dirs) for i=1,nofdirs do - files,n,m,r=scan(files,spec,dirs[i],n,m,r) + files,remap,n,m,r=scan(files,remap,spec,dirs[i],n,m,r,onlyonce,tolerant) end end scancache[sub(full,1,-2)]=files - return files,n,m,r + return files,remap,n,m,r end -local fullcache={} -function resolvers.scanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) +function resolvers.scanfiles(path,branch,usecache,onlyonce,tolerant) + local realpath=resolveprefix(path) if usecache then - local files=fullcache[realpath] - if files then + local content=fullcache[realpath] + if content then if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) + report_expansions("using cached scan of path %a, branch %a",path,branch or path) end - return files + nofsharedscans=nofsharedscans+1 + return content end end + statistics.starttiming(timer) if trace_locating then report_expansions("scanning path %a, branch %a",path,branch or path) end - local files,n,m,r=scan({},realpath..'/',"",0,0,0) - files.__path__=path - files.__files__=n - files.__directories__=m - files.__remappings__=r - if trace_locating then - report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) - end - if usecache then - scanned[#scanned+1]=realpath - fullcache[realpath]=files - end - nofscans=nofscans+1 - statistics.stoptiming(timer) - return files -end -local function simplescan(files,spec,path) - local full=(path=="" and spec) or (spec..path..'/') - local dirs={} - local nofdirs=0 - for name in directory(full) do - if not lpegmatch(weird,name) then - local mode=attributes(full..name,'mode') - if mode=='file' then - if not files[name] then - files[name]=path - end - elseif mode=='directory' then - nofdirs=nofdirs+1 - if path~="" then - dirs[nofdirs]=path..'/'..name - else - dirs[nofdirs]=name - end - end - end - end - if nofdirs>0 then - sort(dirs) - for i=1,nofdirs do - files=simplescan(files,spec,dirs[i]) - end - end - return files -end -local simplecache={} -local nofsharedscans=0 -function resolvers.simplescanfiles(path,branch,usecache) - statistics.starttiming(timer) - local realpath=resolvers.resolve(path) - if usecache then - local files=simplecache[realpath] - if not files then - files=scancache[realpath] - if files then - nofsharedscans=nofsharedscans+1 - end + local content + if isdir(realpath) then + local files,remap,n,m,r=scan({},{},realpath..'/',"",0,0,0,onlyonce,tolerant) + content={ + metadata={ + path=path, + files=n, + directories=m, + remappings=r, + }, + files=files, + remap=remap, + } + if trace_locating then + report_expansions("%s files found on %s directories with %s uppercase remappings",n,m,r) end - if files then - if trace_locating then - report_expansions("using caches scan of path %a, branch %a",path,branch or path) - end - return files + else + content={ + metadata={ + path=path, + files=0, + directories=0, + remappings=0, + }, + files={}, + remap={}, + } + if trace_locating then + report_expansions("invalid path %a",realpath) end end - if trace_locating then - report_expansions("scanning path %a, branch %a",path,branch or path) - end - local files=simplescan({},realpath..'/',"") - if trace_locating then - report_expansions("%s files found",table.count(files)) - end if usecache then scanned[#scanned+1]=realpath - simplecache[realpath]=files + fullcache[realpath]=content end nofscans=nofscans+1 statistics.stoptiming(timer) - return files + return content +end +function resolvers.simplescanfiles(path,branch,usecache) + return resolvers.scanfiles(path,branch,usecache,true,true) end function resolvers.scandata() table.sort(scanned) @@ -12925,6 +13645,52 @@ function resolvers.scandata() paths=scanned, } end +function resolvers.get_from_content(content,path,name) + if not content then + return + end + local files=content.files + if not files then + return + end + local remap=content.remap + if not remap then + return + end + if name then + local used=lower(name) + return path,remap[used] or used + else + local name=path + local used=lower(name) + local path=files[used] + if path then + return path,remap[used] or used + end + end +end +local nothing=function() end +function resolvers.filtered_from_content(content,pattern) + if content and type(pattern)=="string" then + local pattern=lower(pattern) + local files=content.files + local remap=content.remap + if files and remap then + local n=next(files) + local function iterator() + while n do + local k=n + n=next(files,k) + if find(k,pattern) then + return files[k],remap and remap[k] or k + end + end + end + return iterator + end + end + return nothing +end end -- of closure @@ -12933,7 +13699,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-env"] = package.loaded["data-env"] or true --- original size: 8769, stripped down to: 6490 +-- original size: 9216, stripped down to: 6798 if not modules then modules={} end modules ['data-env']={ version=1.001, @@ -12951,10 +13717,12 @@ local formats=allocate() local suffixes=allocate() local dangerous=allocate() local suffixmap=allocate() +local usertypes=allocate() resolvers.formats=formats resolvers.suffixes=suffixes resolvers.dangerous=dangerous resolvers.suffixmap=suffixmap +resolvers.usertypes=usertypes local luasuffixes=utilities.lua.suffixes local relations=allocate { core={ @@ -13022,11 +13790,13 @@ local relations=allocate { names={ "mp" }, variable='MPINPUTS', suffixes={ 'mp','mpvi','mpiv','mpii' }, + usertype=true, }, tex={ names={ "tex" }, variable='TEXINPUTS', - suffixes={ 'tex',"mkvi","mkiv","mkii" }, + suffixes={ "tex","mkvi","mkiv","mkii","cld","lfg","xml" }, + usertype=true, }, icc={ names={ "icc","icc profile","icc profiles" }, @@ -13042,6 +13812,7 @@ local relations=allocate { names={ "lua" }, variable='LUAINPUTS', suffixes={ luasuffixes.lua,luasuffixes.luc,luasuffixes.tma,luasuffixes.tmc }, + usertype=true, }, lib={ names={ "lib" }, @@ -13050,11 +13821,15 @@ local relations=allocate { }, bib={ names={ 'bib' }, + variable='BIBINPUTS', suffixes={ 'bib' }, + usertype=true, }, bst={ names={ 'bst' }, + variable='BSTINPUTS', suffixes={ 'bst' }, + usertype=true, }, fontconfig={ names={ 'fontconfig','fontconfig file','fontconfig files' }, @@ -13136,8 +13911,9 @@ function resolvers.updaterelations() for name,relation in next,categories do local rn=relation.names local rv=relation.variable - local rs=relation.suffixes if rn and rv then + local rs=relation.suffixes + local ru=relation.usertype for i=1,#rn do local rni=lower(gsub(rn[i]," ","")) formats[rni]=rv @@ -13149,8 +13925,9 @@ function resolvers.updaterelations() end end end - end - if rs then + if ru then + usertypes[name]=true + end end end end @@ -13201,7 +13978,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmp"] = package.loaded["data-tmp"] or true --- original size: 15532, stripped down to: 11648 +-- original size: 15681, stripped down to: 11761 if not modules then modules={} end modules ['data-tmp']={ version=1.100, @@ -13220,6 +13997,7 @@ local trace_cache=false trackers.register("resolvers.cache",function(v) trace_ca local report_caches=logs.reporter("resolvers","caches") local report_resolvers=logs.reporter("resolvers","caching") local resolvers=resolvers +local cleanpath=resolvers.cleanpath local directive_cleanup=false directives.register("system.compile.cleanup",function(v) directive_cleanup=v end) local directive_strip=false directives.register("system.compile.strip",function(v) directive_strip=v end) local compile=utilities.lua.compile @@ -13241,7 +14019,7 @@ caches.relocate=false caches.defaults={ "TMPDIR","TEMPDIR","TMP","TEMP","HOME","HOMEPATH" } local writable,readables,usedreadables=nil,{},{} local function identify() - local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") + local texmfcaches=resolvers.cleanpathlist("TEXMFCACHE") if texmfcaches then for k=1,#texmfcaches do local cachepath=texmfcaches[k] @@ -13495,10 +14273,12 @@ local content_state={} function caches.contentstate() return content_state or {} end -function caches.loadcontent(cachename,dataname) - local name=caches.hashed(cachename) - local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.loadcontent(cachename,dataname,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.getfirstreadablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local blob=loadfile(addsuffix(filename,luasuffixes.luc)) or loadfile(addsuffix(filename,luasuffixes.lua)) if blob then local data=blob() @@ -13530,10 +14310,12 @@ function caches.collapsecontent(content) end end end -function caches.savecontent(cachename,dataname,content) - local name=caches.hashed(cachename) - local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") - local filename=file.join(path,name) +function caches.savecontent(cachename,dataname,content,filename) + if not filename then + local name=caches.hashed(cachename) + local full,path=caches.setfirstwritablefile(addsuffix(name,luasuffixes.lua),"trees") + filename=file.join(path,name) + end local luaname=addsuffix(filename,luasuffixes.lua) local lucname=addsuffix(filename,luasuffixes.luc) if trace_locating then @@ -13576,7 +14358,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-met"] = package.loaded["data-met"] or true --- original size: 5460, stripped down to: 4014 +-- original size: 5347, stripped down to: 4015 if not modules then modules={} end modules ['data-met']={ version=1.100, @@ -13604,7 +14386,7 @@ local function splitmethod(filename) if type(filename)=="table" then return filename end - filename=file.collapsepath(filename,".") + filename=file.collapsepath(filename,".") if not find(filename,"://",1,true) then return { scheme="file",path=filename,original=filename,filename=filename } end @@ -13695,7 +14477,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-res"] = package.loaded["data-res"] or true --- original size: 62045, stripped down to: 43116 +-- original size: 67003, stripped down to: 46291 if not modules then modules={} end modules ['data-res']={ version=1.001, @@ -13705,7 +14487,7 @@ if not modules then modules={} end modules ['data-res']={ license="see context related readme files", } local gsub,find,lower,upper,match,gmatch=string.gsub,string.find,string.lower,string.upper,string.match,string.gmatch -local concat,insert,sortedkeys=table.concat,table.insert,table.sortedkeys +local concat,insert,remove,sortedkeys,sortedhash=table.concat,table.insert,table.remove,table.sortedkeys,table.sortedhash local next,type,rawget=next,type,rawget local os=os local P,S,R,C,Cc,Cs,Ct,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Carg @@ -13714,17 +14496,23 @@ local formatters=string.formatters local filedirname=file.dirname local filebasename=file.basename local suffixonly=file.suffixonly +local addsuffix=file.addsuffix +local removesuffix=file.removesuffix local filejoin=file.join local collapsepath=file.collapsepath local joinpath=file.joinpath +local is_qualified_path=file.is_qualified_path local allocate=utilities.storage.allocate local settings_to_array=utilities.parsers.settings_to_array +local getcurrentdir=lfs.currentdir +local isfile=lfs.isfile +local isdir=lfs.isdir local setmetatableindex=table.setmetatableindex local luasuffixes=utilities.lua.suffixes -local getcurrentdir=lfs.currentdir local trace_locating=false trackers .register("resolvers.locating",function(v) trace_locating=v end) local trace_detail=false trackers .register("resolvers.details",function(v) trace_detail=v end) local trace_expansions=false trackers .register("resolvers.expansions",function(v) trace_expansions=v end) +local trace_paths=false trackers .register("resolvers.paths",function(v) trace_paths=v end) local resolve_otherwise=true directives.register("resolvers.otherwise",function(v) resolve_otherwise=v end) local report_resolving=logs.reporter("resolvers","resolving") local resolvers=resolvers @@ -13732,10 +14520,14 @@ local expandedpathfromlist=resolvers.expandedpathfromlist local checkedvariable=resolvers.checkedvariable local splitconfigurationpath=resolvers.splitconfigurationpath local methodhandler=resolvers.methodhandler +local filtered=resolvers.filtered_from_content +local lookup=resolvers.get_from_content +local cleanpath=resolvers.cleanpath +local resolveprefix=resolvers.resolve local initializesetter=utilities.setters.initialize local ostype,osname,osenv,ossetenv,osgetenv=os.type,os.name,os.env,os.setenv,os.getenv -resolvers.cacheversion='1.0.1' -resolvers.configbanner='' +resolvers.cacheversion="1.100" +resolvers.configbanner="" resolvers.homedir=environment.homedir resolvers.criticalvars=allocate { "SELFAUTOLOC","SELFAUTODIR","SELFAUTOPARENT","TEXMFCNF","TEXMF","TEXOS" } resolvers.luacnfname="texmfcnf.lua" @@ -13754,6 +14546,7 @@ end local unset_variable="unset" local formats=resolvers.formats local suffixes=resolvers.suffixes +local usertypes=resolvers.usertypes local dangerous=resolvers.dangerous local suffixmap=resolvers.suffixmap resolvers.defaultsuffixes={ "tex" } @@ -13762,7 +14555,7 @@ local instance=resolvers.instance or nil function resolvers.setenv(key,value,raw) if instance then instance.environment[key]=value - ossetenv(key,raw and value or resolvers.resolve(value)) + ossetenv(key,raw and value or resolveprefix(value)) end end local function getenv(key) @@ -13776,7 +14569,7 @@ local function getenv(key) end resolvers.getenv=getenv resolvers.env=getenv -local function resolve(k) +local function resolvevariable(k) return instance.expansions[k] end local dollarstripper=lpeg.stripper("$") @@ -13785,19 +14578,19 @@ local backslashswapper=lpeg.replacer("\\","/") local somevariable=P("$")/"" local somekey=C(R("az","AZ","09","__","--")^1) local somethingelse=P(";")*((1-S("!{}/\\"))^1*P(";")/"")+P(";")*(P(";")/"")+P(1) -local variableexpander=Cs((somevariable*(somekey/resolve)+somethingelse)^1 ) +local variableexpander=Cs((somevariable*(somekey/resolvevariable)+somethingelse)^1 ) local cleaner=P("\\")/"/"+P(";")*S("!{}/\\")^0*P(";")^1/";" local variablecleaner=Cs((cleaner+P(1))^0) -local somevariable=R("az","AZ","09","__","--")^1/resolve +local somevariable=R("az","AZ","09","__","--")^1/resolvevariable local variable=(P("$")/"")*(somevariable+(P("{")/"")*somevariable*(P("}")/"")) local variableresolver=Cs((variable+P(1))^0) local function expandedvariable(var) return lpegmatch(variableexpander,var) or var end -function resolvers.newinstance() - if trace_locating then +function resolvers.newinstance() + if trace_locating then report_resolving("creating instance") - end + end local environment,variables,expansions,order=allocate(),allocate(),allocate(),allocate() local newinstance={ environment=environment, @@ -13810,6 +14603,7 @@ function resolvers.newinstance() foundintrees=allocate(), hashes=allocate(), hashed=allocate(), + pathlists=false, specification=allocate(), lists=allocate(), data=allocate(), @@ -13822,6 +14616,7 @@ function resolvers.newinstance() savelists=true, pattern=nil, force_suffixes=true, + pathstack={}, } setmetatableindex(variables,function(t,k) local v @@ -13871,8 +14666,13 @@ function resolvers.reset() end local function reset_hashes() instance.lists={} + instance.pathlists=false instance.found={} end +local function reset_caches() + instance.lists={} + instance.pathlists=false +end local slash=P("/") local pathexpressionpattern=Cs ( Cc("^")*( @@ -13924,13 +14724,13 @@ local function identify_configuration_files() for i=1,#cnfpaths do local filepath=cnfpaths[i] local filename=collapsepath(filejoin(filepath,luacnfname)) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) if trace_locating then - local fullpath=gsub(resolvers.resolve(collapsepath(filepath)),"//","/") + local fullpath=gsub(resolveprefix(collapsepath(filepath)),"//","/") local weirdpath=find(fullpath,"/texmf.+/texmf") or not find(fullpath,"/web2c",1,true) report_resolving("looking for %a on %s path %a from specification %a",luacnfname,weirdpath and "weird" or "given",fullpath,filepath) end - if lfs.isfile(realname) then + if isfile(realname) then specification[#specification+1]=filename if trace_locating then report_resolving("found configuration file %a",realname) @@ -13952,7 +14752,7 @@ local function load_configuration_files() local filename=specification[i] local pathname=filedirname(filename) local filename=filejoin(pathname,luacnfname) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local setups=instance.setups @@ -13960,7 +14760,7 @@ local function load_configuration_files() local parent=data and data.parent if parent then local filename=filejoin(pathname,parent) - local realname=resolvers.resolve(filename) + local realname=resolveprefix(filename) local blob=loadfile(realname) if blob then local parentdata=blob() @@ -13985,7 +14785,7 @@ local function load_configuration_files() elseif variables[k]==nil then if trace_locating and not warning then report_resolving("variables like %a in configuration file %a should move to the 'variables' subtable", - k,resolvers.resolve(filename)) + k,resolveprefix(filename)) warning=true end variables[k]=v @@ -14045,7 +14845,7 @@ local function locate_file_databases() local stripped=lpegmatch(inhibitstripper,path) if stripped~="" then local runtime=stripped==path - path=resolvers.cleanpath(path) + path=cleanpath(path) local spec=resolvers.splitmethod(stripped) if runtime and (spec.noscheme or spec.scheme=="file") then stripped="tree:///"..stripped @@ -14108,8 +14908,8 @@ function resolvers.renew(hashname) report_resolving("identifying tree %a",hashname) end end - local realpath=resolvers.resolve(hashname) - if lfs.isdir(realpath) then + local realpath=resolveprefix(hashname) + if isdir(realpath) then if trace_locating then report_resolving("using path %a",realpath) end @@ -14210,19 +15010,53 @@ end function resolvers.unexpandedpath(str) return joinpath(resolvers.unexpandedpathlist(str)) end +function resolvers.pushpath(name) + local pathstack=instance.pathstack + local lastpath=pathstack[#pathstack] + local pluspath=filedirname(name) + if lastpath then + lastpath=collapsepath(filejoin(lastpath,pluspath)) + else + lastpath=collapsepath(pluspath) + end + insert(pathstack,lastpath) + if trace_paths then + report_resolving("pushing path %a",lastpath) + end +end +function resolvers.poppath() + local pathstack=instance.pathstack + if trace_paths and #pathstack>0 then + report_resolving("popping path %a",pathstack[#pathstack]) + end + remove(pathstack) +end +function resolvers.stackpath() + local pathstack=instance.pathstack + local currentpath=pathstack[#pathstack] + return currentpath~="" and currentpath or nil +end local done={} function resolvers.resetextrapath() local ep=instance.extra_paths if not ep then - ep,done={},{} - instance.extra_paths=ep + done={} + instance.extra_paths={} elseif #ep>0 then - instance.lists,done={},{} + done={} + reset_caches() end end function resolvers.registerextrapath(paths,subpaths) - paths=settings_to_array(paths) - subpaths=settings_to_array(subpaths) + if not subpaths or subpaths=="" then + if not paths or path=="" then + return + elseif done[paths] then + return + end + end + local paths=settings_to_array(paths) + local subpaths=settings_to_array(subpaths) local ep=instance.extra_paths or {} local oldn=#ep local newn=oldn @@ -14237,7 +15071,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=p.."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14247,7 +15081,7 @@ function resolvers.registerextrapath(paths,subpaths) local p=paths[i] if not done[p] then newn=newn+1 - ep[newn]=resolvers.cleanpath(p) + ep[newn]=cleanpath(p) done[p]=true end end @@ -14259,7 +15093,7 @@ function resolvers.registerextrapath(paths,subpaths) local ps=ep[i].."/"..s if not done[ps] then newn=newn+1 - ep[newn]=resolvers.cleanpath(ps) + ep[newn]=cleanpath(ps) done[ps]=true end end @@ -14268,52 +15102,70 @@ function resolvers.registerextrapath(paths,subpaths) if newn>0 then instance.extra_paths=ep end - if newn>oldn then - instance.lists={} + if newn~=oldn then + reset_caches() end end -local function made_list(instance,list) - local ep=instance.extra_paths - if not ep or #ep==0 then - return list +function resolvers.pushextrapath(path) + local paths=settings_to_array(path) + if instance.extra_stack then + insert(instance.extra_stack,1,paths) else - local done,new,newn={},{},0 - for k=1,#list do - local v=list[k] - if not done[v] then - if find(v,"^[%.%/]$") then - done[v]=true - newn=newn+1 - new[newn]=v - else - break - end - end - end - for k=1,#ep do - local v=ep[k] + instance.extra_stack={ paths } + end + reset_caches() +end +function resolvers.popextrapath() + if instance.extra_stack then + reset_caches() + return remove(instance.extra_stack,1) + end +end +local function made_list(instance,list,extra_too) + local done={} + local new={} + local newn=0 + local function add(p) + for k=1,#p do + local v=p[k] if not done[v] then done[v]=true newn=newn+1 new[newn]=v end end - for k=1,#list do - local v=list[k] - if not done[v] then - done[v]=true - newn=newn+1 - new[newn]=v + end + for k=1,#list do + local v=list[k] + if done[v] then + elseif find(v,"^[%.%/]$") then + done[v]=true + newn=newn+1 + new[newn]=v + else + break + end + end + if extra_too then + local es=instance.extra_stack + if es and #es>0 then + for k=1,#es do + add(es[k]) end end - return new + local ep=instance.extra_paths + if ep and #ep>0 then + add(ep) + end end + add(list) + return new end function resolvers.cleanpathlist(str) local t=resolvers.expandedpathlist(str) if t then for i=1,#t do - t[i]=collapsepath(resolvers.cleanpath(t[i])) + t[i]=collapsepath(cleanpath(t[i])) end end return t @@ -14321,22 +15173,22 @@ end function resolvers.expandpath(str) return joinpath(resolvers.expandedpathlist(str)) end -function resolvers.expandedpathlist(str) +function resolvers.expandedpathlist(str,extra_too) if not str then return {} - elseif instance.savelists then + elseif instance.savelists then str=lpegmatch(dollarstripper,str) local lists=instance.lists local lst=lists[str] if not lst then - local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str))) + local l=made_list(instance,resolvers.splitpath(resolvers.expansion(str)),extra_too) lst=expandedpathfromlist(l) lists[str]=lst end return lst else local lst=resolvers.splitpath(resolvers.expansion(str)) - return made_list(instance,expandedpathfromlist(lst)) + return made_list(instance,expandedpathfromlist(lst),extra_too) end end function resolvers.expandedpathlistfromvariable(str) @@ -14347,6 +15199,13 @@ end function resolvers.expandpathfromvariable(str) return joinpath(resolvers.expandedpathlistfromvariable(str)) end +function resolvers.cleanedpathlist(v) + local t=resolvers.expandedpathlist(v) + for i=1,#t do + t[i]=resolvers.resolve(resolvers.cleanpath(t[i])) + end + return t +end function resolvers.expandbraces(str) local ori=str local pth=expandedpathfromlist(resolvers.splitpath(ori)) @@ -14363,7 +15222,7 @@ function resolvers.registerfilehash(name,content,someerror) end end local function isreadable(name) - local readable=lfs.isfile(name) + local readable=isfile(name) if trace_detail then if readable then report_resolving("file %a is readable",name) @@ -14373,70 +15232,57 @@ local function isreadable(name) end return readable end -local function collect_files(names) - local filelist,noffiles={},0 +local function collect_files(names) + local filelist={} + local noffiles=0 + local function check(hash,root,pathname,path,name) + if not pathname or find(path,pathname) then + local variant=hash.type + local search=filejoin(root,path,name) + local result=methodhandler('concatinators',variant,root,path,name) + if trace_detail then + report_resolving("match: variant %a, search %a, result %a",variant,search,result) + end + noffiles=noffiles+1 + filelist[noffiles]={ variant,search,result } + end + end for k=1,#names do - local fname=names[k] + local filename=names[k] if trace_detail then - report_resolving("checking name %a",fname) + report_resolving("checking name %a",filename) end - local bname=filebasename(fname) - local dname=filedirname(fname) - if dname=="" or find(dname,"^%.") then - dname=false + local basename=filebasename(filename) + local pathname=filedirname(filename) + if pathname=="" or find(pathname,"^%.") then + pathname=false else - dname=gsub(dname,"%*",".*") - dname="/"..dname.."$" + pathname=gsub(pathname,"%*",".*") + pathname="/"..pathname.."$" end local hashes=instance.hashes for h=1,#hashes do local hash=hashes[h] - local blobpath=hash.name - local files=blobpath and instance.files[blobpath] - if files then + local hashname=hash.name + local content=hashname and instance.files[hashname] + if content then if trace_detail then - report_resolving("deep checking %a, base %a, pattern %a",blobpath,bname,dname) - 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 + report_resolving("deep checking %a, base %a, pattern %a",hashname,basename,pathname) end - if blobfile then - local blobroot=files.__path__ or blobpath - if type(blobfile)=='string' then - if not dname or find(blobfile,dname) then - local variant=hash.type - local search=filejoin(blobroot,blobfile,bname) - local result=methodhandler('concatinators',hash.type,blobroot,blobfile,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + local path,name=lookup(content,basename) + if path then + local metadata=content.metadata + local realroot=metadata and metadata.path or hashname + if type(path)=="string" then + check(hash,realroot,pathname,path,name) else - for kk=1,#blobfile do - local vv=blobfile[kk] - if not dname or find(vv,dname) then - local variant=hash.type - local search=filejoin(blobroot,vv,bname) - local result=methodhandler('concatinators',hash.type,blobroot,vv,bname) - if trace_detail then - report_resolving("match: variant %a, search %a, result %a",variant,search,result) - end - noffiles=noffiles+1 - filelist[noffiles]={ variant,search,result } - end + for i=1,#path do + check(hash,realroot,pathname,path[i],name) end end end elseif trace_locating then - report_resolving("no match in %a (%s)",blobpath,bname) + report_resolving("no match in %a (%s)",hashname,basename) end end end @@ -14461,7 +15307,7 @@ end local function can_be_dir(name) local fakepaths=instance.fakepaths if not fakepaths[name] then - if lfs.isdir(name) then + if isdir(name) then fakepaths[name]=1 else fakepaths[name]=2 @@ -14477,10 +15323,11 @@ local function find_analyze(filename,askedformat,allresults) if askedformat=="" then if ext=="" or not suffixmap[ext] then local defaultsuffixes=resolvers.defaultsuffixes + local formatofsuffix=resolvers.formatofsuffix for i=1,#defaultsuffixes do local forcedname=filename..'.'..defaultsuffixes[i] wantedfiles[#wantedfiles+1]=forcedname - filetype=resolvers.formatofsuffix(forcedname) + filetype=formatofsuffix(forcedname) if trace_locating then report_resolving("forcing filetype %a",filetype) end @@ -14520,14 +15367,14 @@ local function find_wildcard(filename,allresults) if trace_locating then report_resolving("checking wildcard %a",filename) end - local method,result=resolvers.findwildcardfiles(filename) + local result=resolvers.findwildcardfiles(filename) if result then return "wildcard",result end end end local function find_qualified(filename,allresults,askedformat,alsostripped) - if not file.is_qualified_path(filename) then + if not is_qualified_path(filename) then return end if trace_locating then @@ -14601,33 +15448,66 @@ local function check_subpath(fname) return fname end end -local function find_intree(filename,filetype,wantedfiles,allresults) +local function makepathlist(list,filetype) local typespec=resolvers.variableofformat(filetype) - local pathlist=resolvers.expandedpathlist(typespec) - local method="intree" + local pathlist=resolvers.expandedpathlist(typespec,filetype and usertypes[filetype]) + local entry={} if pathlist and #pathlist>0 then - local filelist=collect_files(wantedfiles) + for k=1,#pathlist do + local path=pathlist[k] + local prescanned=find(path,'^!!') + local resursive=find(path,'//$') + local pathname=lpegmatch(inhibitstripper,path) + local expression=makepathexpression(pathname) + local barename=gsub(pathname,"/+$","") + barename=resolveprefix(barename) + local scheme=url.hasscheme(barename) + local schemename=gsub(barename,"%.%*$",'') + entry[k]={ + path=path, + pathname=pathname, + prescanned=prescanned, + recursive=recursive, + expression=expression, + barename=barename, + scheme=scheme, + schemename=schemename, + } + end + entry.typespec=typespec + list[filetype]=entry + else + list[filetype]=false + end + return entry +end +local function find_intree(filename,filetype,wantedfiles,allresults) + local pathlists=instance.pathlists + if not pathlists then + pathlists=setmetatableindex(allocate(),makepathlist) + instance.pathlists=pathlists + end + local pathlist=pathlists[filetype] + if pathlist then + local method="intree" + local filelist=collect_files(wantedfiles) local dirlist={} + local result={} if filelist then for i=1,#filelist do dirlist[i]=filedirname(filelist[i][3]).."/" end end if trace_detail then - report_resolving("checking filename %a",filename) + report_resolving("checking filename %a in tree",filename) end - local resolve=resolvers.resolve - local result={} for k=1,#pathlist do - local path=pathlist[k] - local pathname=lpegmatch(inhibitstripper,path) - local doscan=path==pathname - if not find (pathname,'//$') then - doscan=false - end + local entry=pathlist[k] + local path=entry.path + local pathname=entry.pathname local done=false if filelist then - local expression=makepathexpression(pathname) + local expression=entry.expression if trace_detail then report_resolving("using pattern %a for path %a",expression,pathname) end @@ -14635,8 +15515,8 @@ local function find_intree(filename,filetype,wantedfiles,allresults) local fl=filelist[k] local f=fl[2] local d=dirlist[k] - if find(d,expression) or find(resolve(d),expression) then - result[#result+1]=resolve(fl[3]) + if find(d,expression) or find(resolveprefix(d),expression) then + result[#result+1]=resolveprefix(fl[3]) done=true if allresults then if trace_detail then @@ -14657,56 +15537,62 @@ local function find_intree(filename,filetype,wantedfiles,allresults) method="database" else method="filesystem" - pathname=gsub(pathname,"/+$","") - pathname=resolve(pathname) - local scheme=url.hasscheme(pathname) + local scheme=entry.scheme if not scheme or scheme=="file" then - local pname=gsub(pathname,"%.%*$",'') + local pname=entry.schemename if not find(pname,"*",1,true) then if can_be_dir(pname) then - for k=1,#wantedfiles do - local w=wantedfiles[k] - local fname=check_subpath(filejoin(pname,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + if not done and not entry.prescanned then + if trace_detail then + report_resolving("quick root scan for %a",pname) end - end - if not done and doscan then - local files=resolvers.simplescanfiles(pname,false,true) for k=1,#wantedfiles do local w=wantedfiles[k] - local subpath=files[w] - if not subpath or subpath=="" then - elseif type(subpath)=="string" then - local fname=check_subpath(filejoin(pname,subpath,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break - end + local fname=check_subpath(filejoin(pname,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break end - else - for i=1,#subpath do - local sp=subpath[i] - if sp=="" then - else - local fname=check_subpath(filejoin(pname,sp,w)) - if fname then - result[#result+1]=fname - done=true - if not allresults then - break + end + end + if not done and entry.recursive then + if trace_detail then + report_resolving("scanning filesystem for %a",pname) + end + local files=resolvers.simplescanfiles(pname,false,true) + for k=1,#wantedfiles do + local w=wantedfiles[k] + local subpath=files[w] + if not subpath or subpath=="" then + elseif type(subpath)=="string" then + local fname=check_subpath(filejoin(pname,subpath,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + else + for i=1,#subpath do + local sp=subpath[i] + if sp=="" then + else + local fname=check_subpath(filejoin(pname,sp,w)) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end end end end - end - if done and not allresults then - break + if done and not allresults then + break + end end end end @@ -14714,6 +15600,18 @@ local function find_intree(filename,filetype,wantedfiles,allresults) end else end + else + for k=1,#wantedfiles do + local pname=entry.barename + local fname=methodhandler('finders',pname.."/"..wantedfiles[k]) + if fname then + result[#result+1]=fname + done=true + if not allresults then + break + end + end + end end end if done and not allresults then @@ -14748,10 +15646,13 @@ local function find_otherwise(filename,filetype,wantedfiles,allresults) local filelist=collect_files(wantedfiles) local fl=filelist and filelist[1] if fl then - return "otherwise",{ resolvers.resolve(fl[3]) } + return "otherwise",{ resolveprefix(fl[3]) } end end collect_instance_files=function(filename,askedformat,allresults) + if not filename or filename=="" then + return {} + end askedformat=askedformat or "" filename=collapsepath(filename,".") filename=gsub(filename,"^%./",getcurrentdir().."/") @@ -14786,7 +15687,11 @@ collect_instance_files=function(filename,askedformat,allresults) else local method,result,stamp,filetype,wantedfiles if instance.remember then - stamp=formatters["%s--%s"](filename,askedformat) + if askedformat=="" then + stamp=formatters["%s::%s"](suffixonly(filename),filename) + else + stamp=formatters["%s::%s"](askedformat,filename) + end result=stamp and instance.found[stamp] if result then if trace_locating then @@ -14821,7 +15726,7 @@ collect_instance_files=function(filename,askedformat,allresults) end if stamp then if trace_locating then - report_resolving("remembering file %a",filename) + report_resolving("remembering file %a using hash %a",filename,stamp) end instance.found[stamp]=result end @@ -14829,6 +15734,9 @@ collect_instance_files=function(filename,askedformat,allresults) end end local function findfiles(filename,filetype,allresults) + if not filename or filename=="" then + return {} + end local result,status=collect_instance_files(filename,filetype or "",allresults) if not result or #result==0 then local lowered=lower(filename) @@ -14848,39 +15756,30 @@ function resolvers.findpath(filename,filetype) return filedirname(findfiles(filename,filetype,false)[1] or "") end local function findgivenfiles(filename,allresults) - local bname,result=filebasename(filename),{} + local base=filebasename(filename) + local result={} local hashes=instance.hashes - local noffound=0 + local function okay(hash,path,name) + local found=methodhandler('concatinators',hash.type,hash.name,path,name) + if found and found~="" then + result[#result+1]=resolveprefix(found) + return not allresults + end + end for k=1,#hashes do local hash=hashes[k] - local files=instance.files[hash.name] or {} - 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 - local found=methodhandler('concatinators',hash.type,hash.name,blist,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then - break - end + local content=instance.files[hash.name] + if content then + local path,name=lookup(content,base) + if not path then + elseif type(path)=="string" then + if okay(hash,path,name) then + return result end else - for kk=1,#blist do - local vv=blist[kk] - local found=methodhandler('concatinators',hash.type,hash.name,vv,bname) or "" - if found~="" then - noffound=noffound+1 - result[noffound]=resolvers.resolve(found) - if not allresults then break end + for i=1,#path do + if okay(hash,path[i],name) then + return result end end end @@ -14894,64 +15793,80 @@ end function resolvers.findgivenfile(filename) return findgivenfiles(filename,false)[1] or "" end -local function doit(path,blist,bname,tag,variant,result,allresults) - local done=false - if blist and variant then - local resolve=resolvers.resolve - if type(blist)=='string' then - if find(lower(blist),path) then - local full=methodhandler('concatinators',variant,tag,blist,bname) or "" - result[#result+1]=resolve(full) - done=true - end - else - for kk=1,#blist do - local vv=blist[kk] - if find(lower(vv),path) then - local full=methodhandler('concatinators',variant,tag,vv,bname) or "" - result[#result+1]=resolve(full) - done=true - if not allresults then break end - end - end - end - end - return done -end local makewildcard=Cs( (P("^")^0*P("/")*P(-1)+P(-1))/".*"+(P("^")^0*P("/")/"")^0*(P("*")/".*"+P("-")/"%%-"+P(".")/"%%."+P("?")/"."+P("\\")/"/"+P(1))^0 ) function resolvers.wildcardpattern(pattern) return lpegmatch(makewildcard,pattern) or pattern end -local function findwildcardfiles(filename,allresults,result) - result=result or {} +local function findwildcardfiles(filename,allresults,result) + local result=result or {} local base=filebasename(filename) local dirn=filedirname(filename) local path=lower(lpegmatch(makewildcard,dirn) or dirn) local name=lower(lpegmatch(makewildcard,base) or base) - local files,done=instance.files,false + local files=instance.files if find(name,"*",1,true) then local hashes=instance.hashes + local function okay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - for kk,hh in next,files[hashname] do - if not find(kk,"^remap:") then - if find(lower(kk),name) then - if doit(path,hh,kk,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + for found,base in filtered(files[hashname],name) do + if type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end end end end end else + local function okayokay(found,path,base,hashname,hashtype) + if find(found,path) then + local full=methodhandler('concatinators',hashtype,hashname,found,base) + if full and full~="" then + result[#result+1]=resolveprefix(full) + return not allresults + end + end + end local hashes=instance.hashes for k=1,#hashes do local hash=hashes[k] - local hashname,hashtype=hash.name,hash.type - if doit(path,files[hashname][base],base,hashname,hashtype,result,allresults) then done=true end - if done and not allresults then break end + local hashname=hash.name + local hashtype=hash.type + if hashname and hashtype then + local found,base=lookup(content,base) + if not found then + elseif type(found)=='string' then + if okay(found,path,base,hashname,hashtype) then + break + end + else + for i=1,#found do + if okay(found[i],path,base,hashname,hashtype) then + break + end + end + end + end end end return result @@ -15024,7 +15939,7 @@ end function resolvers.dowithpath(name,func) local pathlist=resolvers.expandedpathlist(name) for i=1,#pathlist do - func("^"..resolvers.cleanpath(pathlist[i])) + func("^"..cleanpath(pathlist[i])) end end function resolvers.dowithvariable(name,func) @@ -15032,23 +15947,23 @@ function resolvers.dowithvariable(name,func) end function resolvers.locateformat(name) local engine=environment.ownmain or "luatex" - local barename=file.removesuffix(name) - local fullname=file.addsuffix(barename,"fmt") + local barename=removesuffix(name) + local fullname=addsuffix(barename,"fmt") local fmtname=caches.getfirstreadablefile(fullname,"formats",engine) or "" if fmtname=="" then fmtname=resolvers.findfile(fullname) - fmtname=resolvers.cleanpath(fmtname) + fmtname=cleanpath(fmtname) end if fmtname~="" then - local barename=file.removesuffix(fmtname) - local luaname=file.addsuffix(barename,luasuffixes.lua) - local lucname=file.addsuffix(barename,luasuffixes.luc) - local luiname=file.addsuffix(barename,luasuffixes.lui) - if lfs.isfile(luiname) then + local barename=removesuffix(fmtname) + local luaname=addsuffix(barename,luasuffixes.lua) + local lucname=addsuffix(barename,luasuffixes.luc) + local luiname=addsuffix(barename,luasuffixes.lui) + if isfile(luiname) then return barename,luiname - elseif lfs.isfile(lucname) then + elseif isfile(lucname) then return barename,lucname - elseif lfs.isfile(luaname) then + elseif isfile(luaname) then return barename,luaname end end @@ -15070,29 +15985,24 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) local hash=hashes[i] local blobtype=hash.type local blobpath=hash.name - if blobpath then + if blobtype and blobpath then + local total=0 + local checked=0 + local done=0 if before then before(blobtype,blobpath,pattern) end - local files=instance.files[blobpath] - local total,checked,done=0,0,0 - if files then - for k,v in table.sortedhash(files) do - total=total+1 - if find(k,"^remap:") then - elseif find(k,pattern) then - if type(v)=="string" then - checked=checked+1 - if handle(blobtype,blobpath,v,k) then - done=done+1 - end - else - checked=checked+#v - for i=1,#v do - if handle(blobtype,blobpath,v[i],k) then - done=done+1 - end - end + for path,name in filtered(instance.files[blobpath],pattern) do + if type(path)=="string" then + checked=checked+1 + if handle(blobtype,blobpath,path,name) then + done=done+1 + end + else + checked=checked+#path + for i=1,#path do + if handle(blobtype,blobpath,path[i],name) then + done=done+1 end end end @@ -15103,8 +16013,8 @@ function resolvers.dowithfilesintree(pattern,handle,before,after) end end end -resolvers.obsolete=resolvers.obsolete or {} -local obsolete=resolvers.obsolete +local obsolete=resolvers.obsolete or {} +resolvers.obsolete=obsolete resolvers.find_file=resolvers.findfile obsolete.find_file=resolvers.findfile resolvers.find_files=resolvers.findfiles obsolete.find_files=resolvers.findfiles @@ -15115,7 +16025,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-pre"] = package.loaded["data-pre"] or true --- original size: 6643, stripped down to: 4401 +-- original size: 3950, stripped down to: 2935 if not modules then modules={} end modules ['data-pre']={ version=1.001, @@ -15125,44 +16035,51 @@ if not modules then modules={} end modules ['data-pre']={ license="see context related readme files" } local resolvers=resolvers -local prefixes=utilities.storage.allocate() -resolvers.prefixes=prefixes -local cleanpath,findgivenfile,expansion=resolvers.cleanpath,resolvers.findgivenfile,resolvers.expansion +local prefixes=resolvers.prefixes +local cleanpath=resolvers.cleanpath +local findgivenfile=resolvers.findgivenfile +local expansion=resolvers.expansion local getenv=resolvers.getenv -local P,S,R,C,Cs,Cc,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.match -local joinpath,basename,dirname=file.join,file.basename,file.dirname -local getmetatable,rawset,type=getmetatable,rawset,type +local basename=file.basename +local dirname=file.dirname +local joinpath=file.join +local isfile=lfs.isfile prefixes.environment=function(str) return cleanpath(expansion(str)) end -prefixes.relative=function(str,n) - if io.exists(str) then - 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.."../" +local function relative(str,n) + if not isfile(str) then + local pstr="./"..str + if isfile(pstr) then + str=pstr + else + local p="../" + for i=1,n or 2 do + local pstr=p..str + if isfile(pstr) then + str=pstr + break + else + p=p.."../" + end end end end return cleanpath(str) end +local function locate(str) + local fullname=findgivenfile(str) or "" + return cleanpath(fullname~="" and fullname or str) +end +prefixes.relative=relative +prefixes.locate=locate prefixes.auto=function(str) - local fullname=prefixes.relative(str) - if not lfs.isfile(fullname) then - fullname=prefixes.locate(str) + local fullname=relative(str) + if not isfile(fullname) then + fullname=locate(str) end return fullname end -prefixes.locate=function(str) - local fullname=findgivenfile(str) or "" - return cleanpath((fullname~="" and fullname) or str) -end prefixes.filename=function(str) local fullname=findgivenfile(str) or "" return cleanpath(basename((fullname~="" and fullname) or str)) @@ -15183,6 +16100,13 @@ end prefixes.home=function(str) return cleanpath(joinpath(getenv('HOME'),str)) end +prefixes.env=prefixes.environment +prefixes.rel=prefixes.relative +prefixes.loc=prefixes.locate +prefixes.kpse=prefixes.locate +prefixes.full=prefixes.locate +prefixes.file=prefixes.filename +prefixes.path=prefixes.pathname local function toppath() local inputstack=resolvers.inputstack if not inputstack then @@ -15195,98 +16119,22 @@ local function toppath() return pathname end end -resolvers.toppath=toppath -prefixes.toppath=function(str) - return cleanpath(joinpath(toppath(),str)) -end -prefixes.env=prefixes.environment -prefixes.rel=prefixes.relative -prefixes.loc=prefixes.locate -prefixes.kpse=prefixes.locate -prefixes.full=prefixes.locate -prefixes.file=prefixes.filename -prefixes.path=prefixes.pathname -function resolvers.allprefixes(separator) - local all=table.sortedkeys(prefixes) - if separator then - for i=1,#all do - all[i]=all[i]..":" - end - end - return all -end -local function _resolve_(method,target) - local action=prefixes[method] - if action then - return action(target) - else - return method..":"..target - end -end -local resolved,abstract={},{} -function resolvers.resetresolve(str) - resolved,abstract={},{} -end -local pattern=Cs((C(R("az")^2)*P(":")*C((1-S(" \"\';,"))^1)/_resolve_+P(1))^0) -local prefix=C(R("az")^2)*P(":") -local target=C((1-S(" \"\';,"))^1) -local notarget=(#S(";,")+P(-1))*Cc("") -local pattern=Cs(((prefix*(target+notarget))/_resolve_+P(1))^0) -local function resolve(str) - if type(str)=="table" then - local t={} - for i=1,#str do - t[i]=resolve(str[i]) - end - return t +local function jobpath() + local path=resolvers.stackpath() + if not path or path=="" then + return "." else - local res=resolved[str] - if not res then - res=lpegmatch(pattern,str) - resolved[str]=res - abstract[res]=str - end - return res - end -end -local function unresolve(str) - return abstract[str] or str -end -resolvers.resolve=resolve -resolvers.unresolve=unresolve -if type(os.uname)=="function" then - for k,v in next,os.uname() do - if not prefixes[k] then - prefixes[k]=function() return v end - end - end -end -if os.type=="unix" then - local pattern - local function makepattern(t,k,v) - if t then - rawset(t,k,v) - end - local colon=P(":") - for k,v in table.sortedpairs(prefixes) do - if p then - p=P(k)+p - else - p=P(k) - end - end - pattern=Cs((p*colon+colon/";"+P(1))^0) - end - makepattern() - getmetatable(prefixes).__newindex=makepattern - function resolvers.repath(str) - return lpegmatch(pattern,str) - end -else - function resolvers.repath(str) - return str + return path end end +resolvers.toppath=toppath +resolvers.jobpath=jobpath +prefixes.toppath=function(str) return cleanpath(joinpath(toppath(),str)) end +prefixes.jobpath=function(str) return cleanpath(joinpath(jobpath(),str)) end +resolvers.setdynamic("toppath") +resolvers.setdynamic("jobpath") +prefixes.jobfile=prefixes.jobpath +resolvers.setdynamic("jobfile") end -- of closure @@ -15348,7 +16196,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-fil"] = package.loaded["data-fil"] or true --- original size: 3801, stripped down to: 3231 +-- original size: 3863, stripped down to: 3310 if not modules then modules={} end modules ['data-fil']={ version=1.001, @@ -15360,30 +16208,31 @@ if not modules then modules={} end modules ['data-fil']={ local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_files=logs.reporter("resolvers","files") local resolvers=resolvers +local resolveprefix=resolvers.resolve local finders,openers,loaders,savers=resolvers.finders,resolvers.openers,resolvers.loaders,resolvers.savers local locators,hashers,generators,concatinators=resolvers.locators,resolvers.hashers,resolvers.generators,resolvers.concatinators local checkgarbage=utilities.garbagecollector and utilities.garbagecollector.check function locators.file(specification) - local name=specification.filename - local realname=resolvers.resolve(name) + local filename=specification.filename + local realname=resolveprefix(filename) if realname and realname~='' and lfs.isdir(realname) then if trace_locating then - report_files("file locator %a found as %a",name,realname) + report_files("file locator %a found as %a",filename,realname) end - resolvers.appendhash('file',name,true) + resolvers.appendhash('file',filename,true) elseif trace_locating then - report_files("file locator %a not found",name) + report_files("file locator %a not found",filename) end end function hashers.file(specification) - local name=specification.filename - local content=caches.loadcontent(name,'files') - resolvers.registerfilehash(name,content,content==nil) + local pathname=specification.filename + local content=caches.loadcontent(pathname,'files') + resolvers.registerfilehash(pathname,content,content==nil) end function generators.file(specification) - local path=specification.filename - local content=resolvers.scanfiles(path,false,true) - resolvers.registerfilehash(path,content,true) + local pathname=specification.filename + local content=resolvers.scanfiles(pathname,false,true) + resolvers.registerfilehash(pathname,content,true) end concatinators.file=file.join function finders.file(specification,filetype) @@ -15665,7 +16514,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-zip"] = package.loaded["data-zip"] or true --- original size: 8489, stripped down to: 6757 +-- original size: 8772, stripped down to: 6841 if not modules then modules={} end modules ['data-zip']={ version=1.001, @@ -15684,16 +16533,6 @@ zip.archives=zip.archives or {} local archives=zip.archives zip.registeredfiles=zip.registeredfiles or {} local registeredfiles=zip.registeredfiles -local limited=false -directives.register("system.inputmode",function(v) - if not limited then - local i_limiter=io.i_limiter(v) - if i_limiter then - zip.open=i_limiter.protect(zip.open) - limited=true - end - end -end) local function validzip(str) if not find(str,"^zip://") then return "zip:///"..str @@ -15708,7 +16547,7 @@ function zip.openarchive(name) local arch=archives[name] if not arch then local full=resolvers.findfile(name) or "" - arch=(full~="" and zip.open(full)) or false + arch=full~="" and zip.open(full) or false archives[name]=arch end return arch @@ -15867,31 +16706,42 @@ function resolvers.usezipfile(archive) end end function resolvers.registerzipfile(z,tree) - local files,filter={},"" - if tree=="" then - filter="^(.+)/(.-)$" - else - filter=format("^%s/(.+)/(.-)$",tree) - end + local names={} + local files={} + local remap={} + local n=0 + local filter=tree=="" and "^(.+)/(.-)$" or format("^%s/(.+)/(.-)$",tree) + local register=resolvers.registerfile if trace_locating then report_zip("registering: using filter %a",filter) end - local register,n=resolvers.registerfile,0 for i in z:files() do - local path,name=match(i.filename,filter) - if path then - if name and name~='' then - register(files,name,path) - n=n+1 - else + local filename=i.filename + local path,name=match(filename,filter) + if not path then + n=n+1 + register(names,filename,"") + local usedname=lower(filename) + files[usedname]="" + if usedname~=filename then + remap[usedname]=filename end - else - register(files,i.filename,'') + elseif name and name~="" then n=n+1 + register(names,name,path) + local usedname=lower(name) + files[usedname]=path + if usedname~=name then + remap[usedname]=name + end + else end end report_zip("registering: %s files registered",n) - return files + return { + files=files, + remap=remap, + } end @@ -15901,7 +16751,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tre"] = package.loaded["data-tre"] or true --- original size: 2508, stripped down to: 2074 +-- original size: 8479, stripped down to: 5580 if not modules then modules={} end modules ['data-tre']={ version=1.001, @@ -15910,42 +16760,64 @@ if not modules then modules={} end modules ['data-tre']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,gsub,format=string.find,string.gsub,string.format +local find,gsub,lower=string.find,string.gsub,string.lower +local basename,dirname,joinname=file.basename,file.dirname,file .join +local globdir,isdir,isfile=dir.glob,lfs.isdir,lfs.isfile +local P,lpegmatch=lpeg.P,lpeg.match local trace_locating=false trackers.register("resolvers.locating",function(v) trace_locating=v end) local report_trees=logs.reporter("resolvers","trees") local resolvers=resolvers -local done,found,notfound={},{},resolvers.finders.notfound -function resolvers.finders.tree(specification) +local resolveprefix=resolvers.resolve +local notfound=resolvers.finders.notfound +local lookup=resolvers.get_from_content +local collectors={} +local found={} +function resolvers.finders.tree(specification) local spec=specification.filename - local fnd=found[spec] - if fnd==nil then + local okay=found[spec] + if okay==nil then if spec~="" then - local path,name=file.dirname(spec),file.basename(spec) - if path=="" then path="." end - local hash=done[path] - if not hash then - local pattern=path.."/*" - hash=dir.glob(pattern) - done[path]=hash + local path=dirname(spec) + local name=basename(spec) + if path=="" then + path="." + end + local names=collectors[path] + if not names then + local pattern=find(path,"/%*+$") and path or (path.."/*") + names=globdir(pattern) + collectors[path]=names end local pattern="/"..gsub(name,"([%.%-%+])","%%%1").."$" - for k=1,#hash do - local v=hash[k] - if find(v,pattern) then - found[spec]=v - return v + for i=1,#names do + local fullname=names[i] + if find(fullname,pattern) then + found[spec]=fullname + return fullname + end + end + local pattern=lower(pattern) + for i=1,#names do + local fullname=lower(names[i]) + if find(fullname,pattern) then + if isfile(fullname) then + found[spec]=fullname + return fullname + else + break + end end end end - fnd=notfound() - found[spec]=fnd + okay=notfound() + found[spec]=okay end - return fnd + return okay end function resolvers.locators.tree(specification) local name=specification.filename - local realname=resolvers.resolve(name) - if realname and realname~='' and lfs.isdir(realname) then + local realname=resolveprefix(name) + if realname and realname~='' and isdir(realname) then if trace_locating then report_trees("locator %a found",realname) end @@ -15956,16 +16828,110 @@ function resolvers.locators.tree(specification) end function resolvers.hashers.tree(specification) local name=specification.filename - if trace_locating then - report_trees("analysing %a",name) - end + report_trees("analyzing %a",name) resolvers.methodhandler("hashers",name) resolvers.generators.file(specification) end -resolvers.concatinators.tree=resolvers.concatinators.file -resolvers.generators.tree=resolvers.generators.file -resolvers.openers.tree=resolvers.openers.file -resolvers.loaders.tree=resolvers.loaders.file +local collectors={} +local splitter=lpeg.splitat("/**/") +local stripper=lpeg.replacer { [P("/")*P("*")^1*P(-1)]="" } +table.setmetatableindex(collectors,function(t,k) + local rootname=lpegmatch(stripper,k) + local dataname=joinname(rootname,"dirlist") + local content=caches.loadcontent(dataname,"files",dataname) + if not content then + content=resolvers.scanfiles(rootname,nil,nil,false,true) + caches.savecontent(dataname,"files",content,dataname) + end + t[k]=content + return content +end) +local function checked(root,p,n) + if p then + if type(p)=="table" then + for i=1,#p do + local fullname=joinname(root,p[i],n) + if isfile(fullname) then + return fullname + end + end + else + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + end + return notfound() +end +local function resolve(specification) + local filename=specification.filename + if filename~="" then + local root,rest=lpegmatch(splitter,filename) + if root and rest then + local path,name=dirname(rest),basename(rest) + if name~=rest then + local content=collectors[root] + local p,n=lookup(content,name) + if not p then + return notfound() + end + local pattern=".*/"..path.."$" + local istable=type(p)=="table" + if istable then + for i=1,#p do + local pi=p[i] + if pi==path or find(pi,pattern) then + local fullname=joinname(root,pi,n) + if isfile(fullname) then + return fullname + end + end + end + elseif p==path or find(p,pattern) then + local fullname=joinname(root,p,n) + if isfile(fullname) then + return fullname + end + end + local queries=specification.queries + if queries and queries.option=="fileonly" then + return checked(root,p,n) + else + return notfound() + end + end + end + local path,name=dirname(filename),basename(filename) + local root=lpegmatch(stripper,path) + local content=collectors[path] + local p,n=lookup(content,name) + if p then + return checked(root,p,n) + end + end + return notfound() +end +resolvers.finders .dirlist=resolve +resolvers.locators .dirlist=resolvers.locators .tree +resolvers.hashers .dirlist=resolvers.hashers .tree +resolvers.generators.dirlist=resolvers.generators.file +resolvers.openers .dirlist=resolvers.openers .file +resolvers.loaders .dirlist=resolvers.loaders .file +function resolvers.finders.dirfile(specification) + local queries=specification.queries + if queries then + queries.option="fileonly" + else + specification.queries={ option="fileonly" } + end + return resolve(specification) +end +resolvers.locators .dirfile=resolvers.locators .dirlist +resolvers.hashers .dirfile=resolvers.hashers .dirlist +resolvers.generators.dirfile=resolvers.generators.dirlist +resolvers.openers .dirfile=resolvers.openers .dirlist +resolvers.loaders .dirfile=resolvers.loaders .dirlist end -- of closure @@ -15974,7 +16940,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-sch"] = package.loaded["data-sch"] or true --- original size: 6213, stripped down to: 5160 +-- original size: 6569, stripped down to: 5304 if not modules then modules={} end modules ['data-sch']={ version=1.001, @@ -16000,8 +16966,13 @@ directives.register("schemes.threshold",function(v) threshold=tonumber(v) or thr function cleaners.none(specification) return specification.original end -function cleaners.strip(specification) - return (gsub(specification.original,"[^%a%d%.]+","-")) +function cleaners.strip(specification) + local path,name=file.splitbase(specification.original) + if path=="" then + return (gsub(name,"[^%a%d%.]+","-")) + else + return (gsub((gsub(path,"%.","-").."-"..name),"[^%a%d%.]+","-")) + end end function cleaners.md5(specification) return file.addsuffix(md5.hex(specification.original),file.suffix(specification.path)) @@ -16018,7 +16989,7 @@ end local cached,loaded,reused,thresholds,handlers={},{},{},{},{} local function runcurl(name,cachename) local command="curl --silent --insecure --create-dirs --output "..cachename.." "..name - os.spawn(command) + os.execute(command) end local function fetch(specification) local original=specification.original @@ -16150,7 +17121,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lua"] = package.loaded["data-lua"] or true --- original size: 4237, stripped down to: 3177 +-- original size: 4313, stripped down to: 3227 if not modules then modules={} end modules ['data-lua']={ version=1.001, @@ -16159,7 +17130,7 @@ if not modules then modules={} end modules ['data-lua']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local resolvers,package=resolvers,package +local package,lpeg=package,lpeg local gsub=string.gsub local concat=table.concat local addsuffix=file.addsuffix @@ -16170,9 +17141,11 @@ local luaformats={ 'TEXINPUTS','LUAINPUTS' } local libformats={ 'CLUAINPUTS' } local helpers=package.helpers or {} local methods=helpers.methods or {} +local resolvers=resolvers +local resolveprefix=resolvers.resolve +helpers.report=logs.reporter("resolvers","libraries") trackers.register("resolvers.libraries",function(v) helpers.trace=v end) trackers.register("resolvers.locating",function(v) helpers.trace=v end) -helpers.report=logs.reporter("resolvers","libraries") helpers.sequence={ "already loaded", "preload table", @@ -16187,7 +17160,7 @@ helpers.sequence={ } local pattern=Cs(P("!")^0/""*(P("/")*P(-1)/"/"+P("/")^1/"/"+1)^0) function helpers.cleanpath(path) - return resolvers.resolve(lpegmatch(pattern,path)) + return resolveprefix(lpegmatch(pattern,path)) end local loadedaslib=helpers.loadedaslib local getextraluapaths=package.extraluapaths @@ -16324,7 +17297,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-tmf"] = package.loaded["data-tmf"] or true --- original size: 2600, stripped down to: 1627 +-- original size: 2601, stripped down to: 1627 if not modules then modules={} end modules ['data-tmf']={ version=1.001, @@ -16380,7 +17353,7 @@ do -- create closure to overcome 200 locals limit package.loaded["data-lst"] = package.loaded["data-lst"] or true --- original size: 2654, stripped down to: 2301 +-- original size: 2734, stripped down to: 2354 if not modules then modules={} end modules ['data-lst']={ version=1.001, @@ -16389,10 +17362,13 @@ if not modules then modules={} end modules ['data-lst']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local find,concat,upper,format=string.find,table.concat,string.upper,string.format +local rawget,type,next=rawget,type,next +local find,concat,upper=string.find,table.concat,string.upper local fastcopy,sortedpairs=table.fastcopy,table.sortedpairs -resolvers.listers=resolvers.listers or {} local resolvers=resolvers +local listers=resolvers.listers or {} +resolvers.listers=listers +local resolveprefix=resolvers.resolve local report_lists=logs.reporter("resolvers","lists") local function tabstr(str) if type(str)=='table' then @@ -16401,7 +17377,7 @@ local function tabstr(str) return str end end -function resolvers.listers.variables(pattern) +function listers.variables(pattern) local instance=resolvers.instance local environment=instance.environment local variables=instance.variables @@ -16422,10 +17398,10 @@ function resolvers.listers.variables(pattern) for key,value in sortedpairs(configured) do if key~="" and (pattern=="" or find(upper(key),pattern)) then report_lists(key) - report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") - report_lists(" var: %s",tabstr(configured[key]) or "unset") - report_lists(" exp: %s",tabstr(expansions[key]) or "unset") - report_lists(" res: %s",tabstr(resolvers.resolve(expansions[key])) or "unset") + report_lists(" env: %s",tabstr(rawget(environment,key)) or "unset") + report_lists(" var: %s",tabstr(configured[key]) or "unset") + report_lists(" exp: %s",tabstr(expansions[key]) or "unset") + report_lists(" res: %s",tabstr(resolveprefix(expansions[key])) or "unset") end end instance.environment=fastcopy(env) @@ -16433,15 +17409,15 @@ function resolvers.listers.variables(pattern) instance.expansions=fastcopy(exp) end local report_resolved=logs.reporter("system","resolved") -function resolvers.listers.configurations() +function listers.configurations() local configurations=resolvers.instance.specification for i=1,#configurations do - report_resolved("file : %s",resolvers.resolve(configurations[i])) + report_resolved("file : %s",resolveprefix(configurations[i])) end report_resolved("") local list=resolvers.expandedpathfromlist(resolvers.splitpath(resolvers.luacnfspec)) for i=1,#list do - local li=resolvers.resolve(list[i]) + local li=resolveprefix(list[i]) if lfs.isdir(li) then report_resolved("path - %s",li) else @@ -16746,7 +17722,7 @@ do -- create closure to overcome 200 locals limit package.loaded["luat-fmt"] = package.loaded["luat-fmt"] or true --- original size: 5951, stripped down to: 4922 +-- original size: 5955, stripped down to: 4926 if not modules then modules={} end modules ['luat-fmt']={ version=1.001, @@ -16834,7 +17810,7 @@ function environment.make_format(name) end local command=format("%s --ini %s --lua=%s %s %sdump",engine,primaryflags(),quoted(usedluastub),quoted(fulltexsourcename),os.platform=="unix" and "\\\\" or "\\") report_format("running command: %s\n",command) - os.spawn(command) + os.execute(command) local pattern=file.removesuffix(file.basename(usedluastub)).."-*.mem" local mp=dir.glob(pattern) if mp then @@ -16869,7 +17845,7 @@ function environment.run_format(name,data,more) else local command=format("%s %s --fmt=%s --lua=%s %s %s",engine,primaryflags(),quoted(barename),quoted(luaname),quoted(data),more~="" and quoted(more) or "") report_format("running command: %s",command) - os.spawn(command) + os.execute(command) end end end @@ -16880,8 +17856,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 695180 --- stripped bytes : 246794 +-- original bytes : 739251 +-- stripped bytes : 264497 -- end library merge @@ -17107,6 +18083,7 @@ local helpinfo = [[ <category name="basic"> <subcategory> <flag name="script"><short>run an mtx script (lua prefered method) (<ref name="noquotes"/>), no script gives list</short></flag> + <flag name="evaluate"><short>run code passed on the commandline (between quotes)</short></flag> <flag name="execute"><short>run a script or program (texmfstart method) (<ref name="noquotes"/>)</short></flag> <flag name="resolve"><short>resolve prefixed arguments</short></flag> <flag name="ctxlua"><short>run internally (using preloaded libs)</short></flag> @@ -17132,6 +18109,7 @@ local helpinfo = [[ <flag name="verbose"><short>give a bit more info</short></flag> <flag name="trackers" value="list"><short>enable given trackers</short></flag> <flag name="progname" value="str"><short>format or backend</short></flag> + <flag name="systeminfo" value="str"><short>show current operating system, processor, etc</short></flag> </subcategory> <subcategory> <flag name="edit"><short>launch editor with found file</short></flag> @@ -17761,6 +18739,39 @@ function runners.associate(filename) os.launch(filename) end +function runners.evaluate(code,filename) -- for Luigi + if code == "loop" then + while true do + io.write("> ") + local code = io.read() + if code ~= "" then + local temp = string.match(code,"^= (.*)$") + if temp then + code = "print("..temp..")" + end + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("! " .. (message or code).."\n") + else + io.write(compiled()) + end + end + end + else + if type(code) ~= "string" or code == "" then + code = filename + end + if code ~= "" then + local compiled, message = loadstring(code) + if type(compiled) ~= "function" then + io.write("invalid lua code: " .. (message or code)) + return + end + io.write(compiled()) + end + end +end + function runners.gethelp(filename) local url = environment.argument("url") if url and url ~= "" then @@ -17772,6 +18783,15 @@ function runners.gethelp(filename) end end +function runners.systeminfo() + report("architecture : %s",os.platform or "<unset>") + report("operating system : %s",os.name or "<unset>") + report("file architecture : %s",os.type or "<unset>") + report("binary path : %s",os.selfdir or "<unset>") + report("binary suffix : %s",os.binsuffix or "<unset>") + report("library suffix : %s",os.libsuffix or "<unset>") +end + -- this is a bit dirty ... first we store the first filename and next we -- split the arguments so that we only see the ones meant for this script -- ... later we will use the second half @@ -17887,16 +18907,7 @@ end if e_argument("ansi") then - local formatters = string.formatters - - logs.setformatters { - report_yes = formatters["[1;32m%-15s [0;1m|[0m %s"], - report_nop = formatters["[1;32m%-15s [0;1m|[0m"], - subreport_yes = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m %s"], - subreport_nop = formatters["[1;32m%-15s [0;1m|[1;31m %s [0;1m|[0m"], - status_yes = formatters["[1;32m%-15s [0;1m:[0m %s\n"], - status_nop = formatters["[1;32m%-15s [0;1m:[0m\n"], - } + logs.setformatters("ansi") local script = e_argument("script") or e_argument("scripts") @@ -17921,6 +18932,10 @@ if e_argument("script") or e_argument("scripts") then ok = runners.execute_ctx_script(filename) end +elseif e_argument("evaluate") then + + runners.evaluate(e_argument("evaluate"),filename) + elseif e_argument("selfmerge") then -- embed used libraries @@ -18213,6 +19228,10 @@ elseif e_argument("exporthelp") then runners.loadbase() application.export(e_argument("exporthelp"),filename) +elseif e_argument("systeminfo") then + + runners.systeminfo() + elseif e_argument("help") or filename=='help' or filename == "" then application.help() |