diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/context/lua/mtx-context.lua | 1430 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-epub.lua | 12 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-fcd.lua | 366 | ||||
| -rw-r--r-- | scripts/context/lua/mtx-grep.lua | 4 | ||||
| -rw-r--r-- | scripts/context/lua/mtxrun.lua | 136 | ||||
| -rw-r--r-- | scripts/context/ruby/fcd_start.rb | 472 | ||||
| -rw-r--r-- | scripts/context/stubs/mswin/mtxrun.lua | 136 | ||||
| -rw-r--r-- | scripts/context/stubs/unix/mtxrun | 136 | 
8 files changed, 1257 insertions, 1435 deletions
| diff --git a/scripts/context/lua/mtx-context.lua b/scripts/context/lua/mtx-context.lua index e07ecdfc7..fdca3c89a 100644 --- a/scripts/context/lua/mtx-context.lua +++ b/scripts/context/lua/mtx-context.lua @@ -6,11 +6,18 @@ if not modules then modules = { } end modules ['mtx-context'] = {      license   = "see context related readme files"  } +-- todo: more local functions +-- todo: pass jobticket/ctxdata table around +  local format, gmatch, match, gsub, find = string.format, string.gmatch, string.match, string.gsub, string.find  local quote = string.quote  local concat = table.concat +local settings_to_array = utilities.parsers.settings_to_array +local appendtable = table.append +local lpegpatterns, lpegmatch, Cs, P = lpeg.patterns, lpeg.match, lpeg.Cs, lpeg.P -local getargument = environment.argument +local getargument = environment.getargument or environment.argument +local setargument = environment.setargument  local basicinfo = [[  --run                 process (one or more) files (default action) @@ -35,7 +42,7 @@ local basicinfo = [[  --noconsole           disable logging to the console (logfile only)  --purgeresult         purge result file before run ---forcexml            force xml stub (optional flag: --mkii) +--forcexml            force xml stub  --forcecld            force cld (context lua document) stub  --arrange             run extra imposition pass, given that the style sets up imposition @@ -50,23 +57,16 @@ local basicinfo = [[  --version             report installed context version  --global              assume given file present elsewhere +--nofile              use dummy file as jobname  --expert              expert options  ]] --- filter=list      is kind of obsolete --- color            is obsolete for mkiv, always on --- separation       is obsolete for mkiv, no longer available --- output           is currently obsolete for mkiv --- setuppath=list   must check --- modefile=name    must check --- input=name   load the given inputfile (must check) -  local expertinfo = [[  expert options:  --touch               update context version number (remake needed afterwards, also provide --expert) ---nostats             omit runtime statistics at the end of the run +--nostatistics        omit runtime statistics at the end of the run  --update              update context from website (not to be confused with contextgarden)  --profile             profile job (use: mtxrun --script profile --analyze)  --timing              generate timing and statistics overview @@ -80,13 +80,14 @@ special options:  --pdftex              process file with texexec using pdftex  --xetex               process file with texexec using xetex +--mkii                process file with texexec  --pipe                don't check for file and enter scroll mode (--dummyfile=whatever.tmp)  ]]  local application = logs.application {      name     = "mtx-context", -    banner   = "ConTeXt Process Management 0.52", +    banner   = "ConTeXt Process Management 0.60",      helpinfo = {          basic  = basicinfo,          extra  = extrainfo, @@ -94,159 +95,105 @@ local application = logs.application {      }  } +-- local luatexflags = { +--     ["8bit"]                        = true, -- ignored, input is assumed to be in UTF-8 encoding +--     ["default-translate-file"]      = true, -- ignored, input is assumed to be in UTF-8 encoding +--     ["translate-file"]              = true, -- ignored, input is assumed to be in UTF-8 encoding +--     ["etex"]                        = true, -- ignored, the etex extensions are always active +-- +--     ["credits"]                     = true, -- display credits and exit +--     ["debug-format"]                = true, -- enable format debugging +--     ["disable-write18"]             = true, -- disable \write18{SHELL COMMAND} +--     ["draftmode"]                   = true, -- switch on draft mode (generates no output PDF) +--     ["enable-write18"]              = true, -- enable \write18{SHELL COMMAND} +--     ["file-line-error"]             = true, -- enable file:line:error style messages +--     ["file-line-error-style"]       = true, -- aliases of --file-line-error +--     ["no-file-line-error"]          = true, -- disable file:line:error style messages +--     ["no-file-line-error-style"]    = true, -- aliases of --no-file-line-error +--     ["fmt"]                         = true, -- load the format file FORMAT +--     ["halt-on-error"]               = true, -- stop processing at the first error +--     ["help"]                        = true, -- display help and exit +--     ["ini"]                         = true, -- be iniluatex, for dumping formats +--     ["interaction"]                 = true, -- set interaction mode (STRING=batchmode/nonstopmode/scrollmode/errorstopmode) +--     ["jobname"]                     = true, -- set the job name to STRING +--     ["kpathsea-debug"]              = true, -- set path searching debugging flags according to the bits of NUMBER +--     ["lua"]                         = true, -- load and execute a lua initialization script +--     ["mktex"]                       = true, -- enable mktexFMT generation (FMT=tex/tfm) +--     ["no-mktex"]                    = true, -- disable mktexFMT generation (FMT=tex/tfm) +--     ["nosocket"]                    = true, -- disable the lua socket library +--     ["output-comment"]              = true, -- use STRING for DVI file comment instead of date (no effect for PDF) +--     ["output-directory"]            = true, -- use existing DIR as the directory to write files in +--     ["output-format"]               = true, -- use FORMAT for job output; FORMAT is 'dvi' or 'pdf' +--     ["parse-first-line"]            = true, -- enable parsing of the first line of the input file +--     ["no-parse-first-line"]         = true, -- disable parsing of the first line of the input file +--     ["progname"]                    = true, -- set the program name to STRING +--     ["recorder"]                    = true, -- enable filename recorder +--     ["safer"]                       = true, -- disable easily exploitable lua commands +--     ["shell-escape"]                = true, -- enable \write18{SHELL COMMAND} +--     ["no-shell-escape"]             = true, -- disable \write18{SHELL COMMAND} +--     ["shell-restricted"]            = true, -- restrict \write18 to a list of commands given in texmf.cnf +--     ["synctex"]                     = true, -- enable synctex +--     ["version"]                     = true, -- display version and exit +--     ["luaonly"]                     = true, -- run a lua file, then exit +--     ["luaconly"]                    = true, -- byte-compile a lua file, then exit +-- } +  local report = application.report  scripts         = scripts         or { }  scripts.context = scripts.context or { } --- a demo cld file: --- --- context.starttext() --- context.chapter("Hello There") --- context.readfile("tufte","","not found") --- context.stoptext() +-- constants --- l-file / todo +local usedfiles = { +    nop = "cont-nop.mkiv", +    yes = "cont-yes.mkiv", +} -function file.needsupdate(oldfile,newfile) -    return true -end -function file.syncmtimes(oldfile,newfile) -end +local usedsuffixes = { +    before = { +        "tuc" +    }, +    after = { +        "pdf", "tuc", "log" +    }, +    keep = { +        "log" +    }, +} --- l-io +local formatofinterface = { +    en = "cont-en", +    uk = "cont-uk", +    de = "cont-de", +    fr = "cont-fr", +    nl = "cont-nl", +    cs = "cont-cs", +    it = "cont-it", +    ro = "cont-ro", +    pe = "cont-pe", +} -function io.copydata(fromfile,tofile) -    io.savedata(tofile,io.loaddata(fromfile) or "") -end +local defaultformats = { +    "cont-en", +    "cont-nl", +} --- ctx (will become util-ctx) - -local ctxrunner = { } - -function ctxrunner.filtered(str,method) -    str = tostring(str) -    if     method == 'name'     then str = file.removesuffix(file.basename(str)) -    elseif method == 'path'     then str = file.dirname(str) -    elseif method == 'suffix'   then str = file.extname(str) -    elseif method == 'nosuffix' then str = file.removesuffix(str) -    elseif method == 'nopath'   then str = file.basename(str) -    elseif method == 'base'     then str = file.basename(str) ---  elseif method == 'full'     then ---  elseif method == 'complete' then ---  elseif method == 'expand'   then -- str = file.expandpath(str) -    end -    return str:gsub("\\","/") -end +-- process information -function ctxrunner.substitute(e,str) -    local attributes = e.at -    if str and attributes then -        if attributes['method'] then -            str = ctxrunner.filtered(str,attributes['method']) -        end -        if str == "" and attributes['default'] then -            str = attributes['default'] -        end -    end -    return str -end +local ctxrunner = { } -- namespace will go -function ctxrunner.reflag(flags) -    local t = { } -    for _, flag in next, flags do -        local key, value = match(flag,"^(.-)=(.+)$") -        if key and value then -            t[key] = value -        else -            t[flag] = true -        end -    end -    return t -end - -function ctxrunner.substitute(str) -    return str -end - -function ctxrunner.justtext(str) -    str = xml.unescaped(tostring(str)) -    str = xml.cleansed(str) -    str = str:gsub("\\+",'/') -    str = str:gsub("%s+",' ') -    return str -end +local ctx_locations = { '..', '../..' }  function ctxrunner.new()      return { -        ctxname      = "", -        jobname      = "", -        xmldata      = nil, -        suffix       = "prep", -        locations    = { '..', '../..' }, -        variables    = { }, -        messages     = { }, -        environments = { }, -        modules      = { }, -        filters      = { }, -        flags        = { }, -        modes        = { }, -        prepfiles    = { }, -        paths        = { }, +        ctxname   = "", +        jobname   = "", +        flags     = { },      }  end -function ctxrunner.savelog(ctxdata,ctlname) -    local function yn(b) -        if b then return 'yes' else return 'no' end -    end -    if not ctlname or ctlname == "" or ctlname == ctxdata.jobname then -        if ctxdata.jobname then -            ctlname = file.replacesuffix(ctxdata.jobname,'ctl') -        elseif ctxdata.ctxname then -            ctlname = file.replacesuffix(ctxdata.ctxname,'ctl') -        else -            report("invalid ctl name: %s",ctlname or "?") -            return -        end -    end -    local prepfiles = ctxdata.prepfiles -    if prepfiles and next(prepfiles) then -        report("saving logdata in: %s",ctlname) -        f = io.open(ctlname,'w') -        if f then -            f:write("<?xml version='1.0' standalone='yes'?>\n\n") -            f:write(format("<ctx:preplist local='%s'>\n",yn(ctxdata.runlocal))) -            local sorted = table.sortedkeys(prepfiles) -            for i=1,#sorted do -                local name = sorted[i] -                f:write(format("\t<ctx:prepfile done='%s'>%s</ctx:prepfile>\n",yn(prepfiles[name]),name)) -            end -            f:write("</ctx:preplist>\n") -            f:close() -        end -    else -        report("nothing prepared, no ctl file saved") -        os.remove(ctlname) -    end -end - -function ctxrunner.register_path(ctxdata,path) -    -- test if exists -    ctxdata.paths[ctxdata.paths+1] = path -end - -function ctxrunner.trace(ctxdata) -    print(table.serialize(ctxdata.messages)) -    print(table.serialize(ctxdata.flags)) -    print(table.serialize(ctxdata.environments)) -    print(table.serialize(ctxdata.modules)) -    print(table.serialize(ctxdata.filters)) -    print(table.serialize(ctxdata.modes)) -    print(xml.tostring(ctxdata.xmldata)) -end - -function ctxrunner.manipulate(ctxdata,ctxname,defaultname) +function ctxrunner.checkfile(ctxdata,ctxname,defaultname)      if not ctxdata.jobname or ctxdata.jobname == "" then          return @@ -269,13 +216,14 @@ function ctxrunner.manipulate(ctxdata,ctxname,defaultname)      local usedname = ctxdata.ctxname      local found    = lfs.isfile(usedname) -    -- no futher test if qualified path +    -- no further test if qualified path      if not found then -        for _, path in next, ctxdata.locations do +        for _, path in next, ctx_locations do              local fullname = file.join(path,ctxdata.ctxname)              if lfs.isfile(fullname) then -                usedname, found = fullname, true +                usedname = fullname +                found    = true                  break              end          end @@ -283,194 +231,69 @@ function ctxrunner.manipulate(ctxdata,ctxname,defaultname)      if not found then          usedname = resolvers.findfile(ctxdata.ctxname,"tex") -        found = usedname ~= "" +        found    = usedname ~= ""      end      if not found and defaultname and defaultname ~= "" and lfs.isfile(defaultname) then -        usedname, found = defaultname, true +        usedname = defaultname +        found    = true      end      if not found then          return      end -    ctxdata.xmldata = xml.load(usedname) +    local xmldata = xml.load(usedname) -    if not ctxdata.xmldata then +    if not xmldata then          return      else          -- test for valid, can be text file      end -    xml.include(ctxdata.xmldata,'ctx:include','name', table.append({'.', file.dirname(ctxdata.ctxname)},ctxdata.locations)) - -    ctxdata.variables['job'] = ctxdata.jobname +    local ctxpaths = table.append({'.', file.dirname(ctxdata.ctxname)}, ctx_locations) -    ctxdata.flags        = xml.collect_texts(ctxdata.xmldata,"/ctx:job/ctx:flags/ctx:flag",true) -    ctxdata.environments = xml.collect_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:environment",true) -    ctxdata.modules      = xml.collect_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:module",true) -    ctxdata.filters      = xml.collect_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:filter",true) -    ctxdata.modes        = xml.collect_texts(ctxdata.xmldata,"/ctx:job/ctx:process/ctx:resources/ctx:mode",true) -    ctxdata.messages     = xml.collect_texts(ctxdata.xmldata,"ctx:message",true) +    xml.include(xmldata,'ctx:include','name', ctxpaths) -    ctxdata.flags = ctxrunner.reflag(ctxdata.flags) +    local flags = ctxdata.flags -    local messages = ctxdata.messages -    for i=1,#messages do -        report("ctx comment: %s", xml.tostring(messages[i])) -    end - -    for r, d, k in xml.elements(ctxdata.xmldata,"ctx:value[@name='job']") do -        d[k] = ctxdata.variables['job'] or "" -    end - -    local commands = { } -    for e in xml.collected(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/ctx:processor") do -        commands[e.at and e.at['name'] or "unknown"] = e -    end - -    local suffix   = xml.filter(ctxdata.xmldata,"/ctx:job/ctx:preprocess/attribute('suffix')") or ctxdata.suffix -    local runlocal = xml.filter(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/attribute('local')") - -    runlocal = toboolean(runlocal) - -    for files in xml.collected(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:files") do -        for pattern in xml.collected(files,"ctx:file") do - -            preprocessor = pattern.at['processor'] or "" - -            if preprocessor ~= "" then - -                ctxdata.variables['old'] = ctxdata.jobname -                for r, d, k in xml.elements(ctxdata.xmldata,"ctx:value") do -                    local ek = d[k] -                    local ekat = ek.at['name'] -                    if ekat == 'old' then -                        d[k] = ctxrunner.substitute(ctxdata.variables[ekat] or "") -                    end -                end - -                pattern = ctxrunner.justtext(xml.tostring(pattern)) - -                local oldfiles = dir.glob(pattern) - -                local pluspath = false -                if #oldfiles == 0 then -                    -- message: no files match pattern -                    local paths = ctxdata.paths -                    for i=1,#paths do -                        local p = paths[i] -                        local oldfiles = dir.glob(path.join(p,pattern)) -                        if #oldfiles > 0 then -                            pluspath = true -                            break -                        end -                    end -                end -                if #oldfiles == 0 then -                    -- message: no old files -                else -                    for i=1,#oldfiles do -                        local oldfile = oldfiles[i] -                        local newfile = oldfile .. "." .. suffix -- addsuffix will add one only -                        if ctxdata.runlocal then -                            newfile = file.basename(newfile) -                        end -                        if oldfile ~= newfile and file.needsupdate(oldfile,newfile) then -                        --  message: oldfile needs preprocessing -                        --  os.remove(newfile) -                            local splitted = preprocessor:split(',') -                            for i=1,#splitted do -                                local pp = splitted[i] -                                local command = commands[pp] -                                if command then -                                    command = xml.copy(command) -                                    local suf = (command.at and command.at['suffix']) or ctxdata.suffix -                                    if suf then -                                        newfile = oldfile .. "." .. suf -                                    end -                                    if ctxdata.runlocal then -                                        newfile = file.basename(newfile) -                                    end -                                    for r, d, k in xml.elements(command,"ctx:old") do -                                        d[k] = ctxrunner.substitute(oldfile) -                                    end -                                    for r, d, k in xml.elements(command,"ctx:new") do -                                        d[k] = ctxrunner.substitute(newfile) -                                    end -                                    ctxdata.variables['old'] = oldfile -                                    ctxdata.variables['new'] = newfile -                                    for r, d, k in xml.elements(command,"ctx:value") do -                                        local ek = d[k] -                                        local ekat = ek.at and ek.at['name'] -                                        if ekat then -                                            d[k] = ctxrunner.substitute(ctxdata.variables[ekat] or "") -                                        end -                                    end -                                    -- potential optimization: when mtxrun run internal -                                    command = xml.content(command) -                                    command = ctxrunner.justtext(command) -                                    report("command: %s",command) -                                    local result = os.spawn(command) or 0 -                                    -- somehow we get the wrong return value -                                    if result > 0 then -                                        report("error, return code: %s",result) -                                    end -                                    if ctxdata.runlocal then -                                        oldfile = file.basename(oldfile) -                                    end -                                end -                            end -                            if lfs.isfile(newfile) then -                                file.syncmtimes(oldfile,newfile) -                                ctxdata.prepfiles[oldfile] = true -                            else -                                report("error, check target location of new file: %s", newfile) -                                ctxdata.prepfiles[oldfile] = false -                            end -                        else -                            report("old file needs no preprocessing") -                            ctxdata.prepfiles[oldfile] = lfs.isfile(newfile) -                        end -                    end -                end -            end +    for e in xml.collected(xmldata,"/ctx:job/ctx:flags/ctx:flag") do +        local key, value = match(flag,"^(.-)=(.+)$") +        if key and value then +            flags[key] = value +        else +            flags[flag] = true          end      end -    ctxrunner.savelog(ctxdata) -  end -function ctxrunner.preppedfile(ctxdata,filename) -    if ctxdata.prepfiles[file.basename(filename)] then -        return filename .. ".prep" -    else -        return filename +function ctxrunner.checkflags(ctxdata) +    if ctxdata then +        for k,v in next, ctxdata.flags do +            if getargument(k) == nil then +                setargument(k,v) +            end +        end      end  end --- rest +-- multipass control -scripts.context.multipass = { ---  suffixes = { ".tuo", ".tuc" }, -    suffixes = { ".tuc" }, -    nofruns = 8, ---     nofruns = 7, -- test oscillation -} +local multipass_suffixes = { ".tuc" } +local multipass_nofruns  = 8 -- or 7 to test oscillation -function scripts.context.multipass.hashfiles(jobname) +local function multipass_hashfiles(jobname)      local hash = { } -    local suffixes = scripts.context.multipass.suffixes -    for i=1,#suffixes do -        local suffix = suffixes[i] +    for i=1,#multipass_suffixes do +        local suffix = multipass_suffixes[i]          local full = jobname .. suffix          hash[full] = md5.hex(io.loaddata(full) or "unknown")      end      return hash  end -function scripts.context.multipass.changed(oldhash, newhash) +local function multipass_changed(oldhash, newhash)      for k,v in next, oldhash do          if v ~= newhash[k] then              return true @@ -479,126 +302,7 @@ function scripts.context.multipass.changed(oldhash, newhash)      return false  end -function scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,currentrun,finalrun,once) -    -- take jobname from ctx -    jobname = file.removesuffix(jobname) -    local f = io.open(jobname..".top","w") -    if f then -        local function someflag(flag) -            return (ctxdata and ctxdata.flags[flag]) or getargument(flag) -        end -        local function setvalue(flag,template,hash,default) -            local a = someflag(flag) or default -            if a and a ~= "" then -                if hash then -                    if hash[a] then -                        f:write(format(template,a),"\n") -                    end -                else -                    f:write(format(template,a),"\n") -                end -            end -        end -        local function setvalues(flag,template,plural) -            if type(flag) == "table"  then -                for k, v in next, flag do -                    f:write(format(template,v),"\n") -                end -            else -                local a = someflag(flag) or (plural and someflag(flag.."s")) -                if a and a ~= "" then -                    for v in gmatch(a,"%s*([^,]+)") do -                        f:write(format(template,v),"\n") -                    end -                end -            end -        end -        local function setfixed(flag,template,...) -            if someflag(flag) then -                f:write(format(template,...),"\n") -            end -        end -        local function setalways(template,...) -            f:write(format(template,...),"\n") -        end -        -- -        -- This might change ... we can just pass the relevant flags directly. -        -- -        setalways("%% runtime options files (command line driven)") -        -- -        setalways("\\unprotect") -        -- -        setalways("%% feedback and basic job control") -        -- -        -- Option file, we can pass more on the commandline some day soon. Actually we -        -- should use directives and trackers. -        -- -        setfixed ("timing"       , "\\usemodule[timing]") -        setfixed ("batchmode"    , "\\batchmode") -        setfixed ("batch"        , "\\batchmode") -        setfixed ("nonstopmode"  , "\\nonstopmode") -        setfixed ("nonstop"      , "\\nonstopmode") -     -- setfixed ("tracefiles"   , "\\tracefilestrue") -        setfixed ("nostats"      , "\\nomkivstatistics") -        setfixed ("paranoid"     , "\\def\\maxreadlevel{1}") -        -- -        setalways("%% handy for special styles") -        -- -        setalways("\\startluacode") -        setalways("document = document or { }") -        setalways(table.serialize(environment.arguments, "document.arguments")) -        setalways(table.serialize(environment.files,     "document.files")) -        setalways("\\stopluacode") -        -- -        setalways("%% process info") -        -- -        setalways(                 "\\setupsystem[inputfile=%s]",getargument("input") or environment.files[1] or "\\jobname") -        setvalue ("result"       , "\\setupsystem[file=%s]") -        setalways(                 "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun or 0, currentrun or 0) -        setvalues("path"         , "\\usepath[%s]") -        setvalue ("setuppath"    , "\\setupsystem[\\c!directory={%s}]") -        setvalue ("randomseed"   , "\\setupsystem[\\c!random=%s]") -        setvalue ("arguments"    , "\\setupenv[%s]") -        if once then -            setalways("\\enabledirectives[system.runonce]") -        end -        setalways("%% modes") -        setvalues("modefile"     , "\\readlocfile{%s}{}{}") -        setvalues("mode"         , "\\enablemode[%s]", true) -        if ctxdata then -            setvalues(ctxdata.modes, "\\enablemode[%s]") -        end -        -- -        setalways("%% options (not that important)") -        -- -        setalways("\\startsetups *runtime:options") -        setfixed ("color"        , "\\setupcolors[\\c!state=\\v!start]") -        setvalue ("separation"   , "\\setupcolors[\\c!split=%s]") -        setfixed ("noarrange"    , "\\setuparranging[\\v!disable]") -        if getargument('arrange') and not finalrun then -            setalways(             "\\setuparranging[\\v!disable]") -        end -        setalways("\\stopsetups") -        -- -        setalways("%% styles and modules") -        -- -        setalways("\\startsetups *runtime:modules") -        setvalues("usemodule"    , "\\usemodule[%s]", true) -        setvalues("environment"  , "\\environment %s ", true) -        if ctxdata then -            setvalues(ctxdata.modules,      "\\usemodule[%s]") -            setvalues(ctxdata.environments, "\\environment %s ") -        end -        setalways("\\stopsetups") -        -- -        setalways("%% done") -        -- -        setalways("\\protect \\endinput") -        f:close() -    end -end - -function scripts.context.multipass.copyluafile(jobname) -- obsolete +local function multipass_copyluafile(jobname)      local tuaname, tucname = jobname..".tua", jobname..".tuc"      if lfs.isfile(tuaname) then          os.remove(tucname) @@ -606,120 +310,54 @@ function scripts.context.multipass.copyluafile(jobname) -- obsolete      end  end -scripts.context.cldsuffixes = table.tohash { -    "cld", -} - -scripts.context.xmlsuffixes = table.tohash { -    "xml", -} - -scripts.context.luasuffixes = table.tohash { -    "lua", -} - -scripts.context.beforesuffixes = { -    "tuo", "tuc" -} -scripts.context.aftersuffixes = { -    "pdf", "tuo", "tuc", "log" -} - -scripts.context.errorsuffixes = { -    "log" -} - -scripts.context.interfaces = { -    en = "cont-en", -    uk = "cont-uk", -    de = "cont-de", -    fr = "cont-fr", -    nl = "cont-nl", -    cs = "cont-cs", -    it = "cont-it", -    ro = "cont-ro", -    pe = "cont-pe", -} - -scripts.context.defaultformats  = { -    "cont-en", -    "cont-nl", ---  "mptopdf", -- todo: mak emkiv variant ---  "metatex", -- will show up soon ---  "metafun", -- todo: mp formats ---  "plain" -} - -local lpegpatterns, Cs, P = lpeg.patterns, lpeg.Cs, lpeg.P +--  local pattern = lpegpatterns.utfbom^-1 * (P("%% ") + P("% ")) * Cs((1-lpegpatterns.newline)^1) -local function analyze(filename) -- only files on current path -    local f = io.open(file.addsuffix(filename,"tex")) -    if f then -        local t = { } -        local line = f:read("*line") or "" -        local preamble = lpeg.match(pattern,line) +local function preamble_analyze(filename) -- only files on current path +    local t = { } +    local line = io.loadlines(file.addsuffix(filename,"tex")) +    if line then +        local preamble = lpegmatch(pattern,line)          if preamble then              for key, value in gmatch(preamble,"(%S+)%s*=%s*(%S+)") do                  t[key] = value              end              t.type = "tex" -        elseif line:find("^<?xml ") then +        elseif find(line,"^<?xml ") then              t.type = "xml"          end          if t.nofruns then -            scripts.context.multipass.nofruns = t.nofruns +            multipass_nofruns = t.nofruns          end          if not t.engine then              t.engine = 'luatex'          end -        f:close() -        return t -    end -end - -local function makestub(wrap,template,filename,prepname) -    local stubname = file.replacesuffix(file.basename(filename),'run') -    local f = io.open(stubname,'w') -    if f then -        if wrap then -            f:write("\\starttext\n") -        end -        f:write(format(template,prepname or filename),"\n") -        if wrap then -            f:write("\\stoptext\n") -        end -        f:close() -        filename = stubname      end -    return filename +    return t  end ---~ function scripts.context.openpdf(name) ---~     os.spawn(format('pdfopen --file "%s" 2>&1', file.replacesuffix(name,"pdf"))) ---~ end ---~ function scripts.context.closepdf(name) ---~     os.spawn(format('pdfclose --file "%s" 2>&1', file.replacesuffix(name,"pdf"))) ---~ end +-- automatically opening and closing pdf files -local pdfview -- delayed loading +local pdfview -- delayed -function scripts.context.openpdf(name,method) +local function pdf_open(name,method)      pdfview = pdfview or dofile(resolvers.findfile("l-pdfview.lua","tex"))      pdfview.setmethod(method)      report(pdfview.status())      pdfview.open(file.replacesuffix(name,"pdf"))  end -function scripts.context.closepdf(name,method) +local function pdf_close(name,method)      pdfview = pdfview or dofile(resolvers.findfile("l-pdfview.lua","tex"))      pdfview.setmethod(method)      pdfview.close(file.replacesuffix(name,"pdf"))  end -local function push_result_purge(oldbase,newbase) -    for _, suffix in next, scripts.context.aftersuffixes do +-- result file handling + +local function result_push_purge(oldbase,newbase) +    for _, suffix in next, usedsuffixes.after do          local oldname = file.addsuffix(oldbase,suffix)          local newname = file.addsuffix(newbase,suffix)          os.remove(newname) @@ -727,8 +365,8 @@ local function push_result_purge(oldbase,newbase)      end  end -local function push_result_keep(oldbase,newbase) -    for _, suffix in next, scripts.context.beforesuffixes do +local function result_push_keep(oldbase,newbase) +    for _, suffix in next, usedsuffixes.before do          local oldname = file.addsuffix(oldbase,suffix)          local newname = file.addsuffix(newbase,suffix)          local tmpname = "keep-"..oldname @@ -739,8 +377,8 @@ local function push_result_keep(oldbase,newbase)      end  end -local function save_result_error(oldbase,newbase) -    for _, suffix in next, scripts.context.errorsuffixes do +local function result_save_error(oldbase,newbase) +    for _, suffix in next, usedsuffixes.keep do          local oldname = file.addsuffix(oldbase,suffix)          local newname = file.addsuffix(newbase,suffix)          os.remove(newname) -- to be sure @@ -748,8 +386,8 @@ local function save_result_error(oldbase,newbase)      end  end -local function save_result_purge(oldbase,newbase) -    for _, suffix in next, scripts.context.aftersuffixes do +local function result_save_purge(oldbase,newbase) +    for _, suffix in next, usedsuffixes.after do          local oldname = file.addsuffix(oldbase,suffix)          local newname = file.addsuffix(newbase,suffix)          os.remove(newname) -- to be sure @@ -757,8 +395,8 @@ local function save_result_purge(oldbase,newbase)      end  end -local function save_result_keep(oldbase,newbase) -    for _, suffix in next, scripts.context.aftersuffixes do +local function result_save_keep(oldbase,newbase) +    for _, suffix in next, usedsuffixes.after do          local oldname = file.addsuffix(oldbase,suffix)          local newname = file.addsuffix(newbase,suffix)          local tmpname = "keep-"..oldname @@ -768,313 +406,342 @@ local function save_result_keep(oldbase,newbase)      end  end -function scripts.context.run(ctxdata,filename) -    -- filename overloads environment.files -    local files = (filename and { filename }) or environment.files -    if ctxdata then -        -- todo: interface -        for k,v in next, ctxdata.flags do -            environment.setargument(k,v) +-- executing luatex + +local function flags_to_string(flags,prefix) -- context flags get prepended by c: +    local t = { } +    for k, v in table.sortedhash(flags) do +        if prefix then +            k = format("c:%s",k) +        end +        if not v or v == "" or v == '""' then +            -- no need to flag false +        elseif v == true then +            t[#t+1] = format('--%s',k) +        elseif type(v) == "string" then +            t[#t+1] = format('--%s=%s',k,quote(v)) +        else +            t[#t+1] = format('--%s=%s',k,tostring(v))          end      end -    if #files > 0 then +    return concat(t," ") +end + +local function luatex_command(l_flags,c_flags,filename) +    return format('luatex %s %s "%s"', +        flags_to_string(l_flags), +        flags_to_string(c_flags,true), +        filename +    ) +end + +local function run_texexec(filename,a_purge,a_purgeall) +    if false then +        -- we need to write a top etc too and run mp etc so it's not worth the +        -- trouble, so it will take a while before the next is finished          -- -        local interface = getargument("interface") -        -- todo: getargument("interface","en") -        interface = (type(interface) == "string" and interface) or "en" +        -- context --extra=texutil --convert myfile +    else +        local texexec = resolvers.findfile("texexec.rb") or "" +        if texexec ~= "" then +            os.setenv("RUBYOPT","") +            local options = environment.reconstructcommandline(environment.arguments_after) +            options = gsub(options,"--purge","") +            options = gsub(options,"--purgeall","") +            local command = format("ruby %s %s",texexec,options) +            if a_purge then +                os.execute(command) +                scripts.context.purge_job(filename,false,true) +            elseif a_purgeall then +                os.execute(command) +                scripts.context.purge_job(filename,true,true) +            else +                os.exec(command) +            end +        end +    end +end + +-- + +local function validstring(s) +    return type(s) == "string" and s ~= "" and s or nil +end + +function scripts.context.run(ctxdata,filename) +    -- +    local a_nofile = getargument("nofile") +    -- +    local files    = environment.files or { } +    -- +    local filelist, mainfile +    -- +    if filename then +        -- the given forced name is processed, the filelist is passed to context +        mainfile = filename +        filelist = { filename } +     -- files    = files +    elseif a_nofile then +        -- the list of given files is processed using the dummy file +        mainfile = usedfiles.nop +        filelist = { usedfiles.nop } +     -- files    = { } +    elseif #files > 0 then +        -- the list of given files is processed using the stub file +        mainfile = usedfiles.yes +        filelist = files +        files    = { } +    else +        return +    end +    -- +    local interface = validstring(getargument("interface")) or "en" +    local formatname = formatofinterface[interface] or "cont-en" +    local formatfile, scriptfile = resolvers.locateformat(formatname) +    if not formatfile or not scriptfile then +        report("warning: no format found, forcing remake (commandline driven)") +        scripts.context.make(formatname) +        formatfile, scriptfile = resolvers.locateformat(formatname) +    end +    if formatfile and scriptfile then +        -- okay +    elseif formatname then +        report("error, no format found with name: %s, aborting",formatname) +        return +    else +        report("error, no format found (provide formatname or interface)") +        return +    end +    -- +    local a_mkii        = getargument("mkii") or getargument("pdftex") or getargument("xetex") +    local a_purge       = getargument("purge") +    local a_purgeall    = getargument("purgeall") +    local a_purgeresult = getargument("purgeresult") +    local a_global      = getargument("global") +    local a_timing      = getargument("timing") +    local a_batchmode   = getargument("batchmode") +    local a_nonstopmode = getargument("nonstopmode") +    local a_once        = getargument("once") +    local a_synctex     = getargument("synctex") +    local a_backend     = getargument("backend") +    local a_arrange     = getargument("arrange") +    local a_noarrange   = getargument("noarrange") +    -- +    for i=1,#filelist do          -- -        local formatname = scripts.context.interfaces[interface] or "cont-en" -        local formatfile, scriptfile = resolvers.locateformat(formatname) -        -- this catches the command line -        if not formatfile or not scriptfile then -            report("warning: no format found, forcing remake (commandline driven)") -            scripts.context.make(formatname) -            formatfile, scriptfile = resolvers.locateformat(formatname) +        local filename = filelist[i] +        local basename = file.basename(filename) +        local pathname = file.dirname(filename) +        local jobname  = file.removesuffix(basename) +        local ctxname  = ctxdata and ctxdata.ctxname +        -- +        if pathname == "" and not a_global and filename ~= usedfiles.nop then +            filename = "./" .. filename          end          -- -        if formatfile and scriptfile then -            for i=1,#files do -                local filename = files[i] -                local basename, pathname = file.basename(filename), file.dirname(filename) -                local jobname = file.removesuffix(basename) -                if pathname == "" and not getargument("global") then -                    filename = "./" .. filename +        local analysis = preamble_analyze(filename) +        -- +        if a_mkii or analysis.engine == 'pdftex' or analysis.engine == 'xetex' then +            run_texexec(filename,a_purge,a_purgeall) +        else +            if analysis.interface and analysis.interface ~= interface then +                formatname = formatofinterface[analysis.interface] or formatname +                formatfile, scriptfile = resolvers.locateformat(formatname) +            end +            if not formatfile or not scriptfile then +                report("warning: no format found, forcing remake (source driven)") +                scripts.context.make(formatname) +                formatfile, scriptfile = resolvers.locateformat(formatname) +            end +            if formatfile and scriptfile then +                -- +                local suffix     = validstring(getargument("suffix")) +                local resultname = validstring(getargument("result")) +                if suffix then +                    resultname = file.removesuffix(jobname) .. suffix                  end -                -- look at the first line -                local a = analyze(filename) -                if a and (a.engine == 'pdftex' or a.engine == 'xetex' or getargument("pdftex") or getargument("xetex")) then -                    if false then -                        -- we need to write a top etc too and run mp etc so it's not worth the -                        -- trouble, so it will take a while before the next is finished -                        -- -                        -- context --extra=texutil --convert myfile -                    else -                        local texexec = resolvers.findfile("texexec.rb") or "" -                        if texexec ~= "" then -                            os.setenv("RUBYOPT","") -                            local options = environment.reconstructcommandline(environment.arguments_after) -                            options = gsub(options,"--purge","") -                            options = gsub(options,"--purgeall","") -                            local command = format("ruby %s %s",texexec,options) -                            if getargument("purge") then -                                os.execute(command) -                                scripts.context.purge_job(filename,false,true) -                            elseif getargument("purgeall") then -                                os.execute(command) -                                scripts.context.purge_job(filename,true,true) -                            else -                                os.exec(command) -                            end +                local oldbase = "" +                local newbase = "" +                if resultname then +                    oldbase = file.removesuffix(jobname) +                    newbase = file.removesuffix(resultname) +                    if oldbase ~= newbase then +                        if a_purgeresult then +                            result_push_purge(oldbase,newbase) +                        else +                            result_push_keep(oldbase,newbase)                          end +                    else +                        resultname = nil                      end -                else -                    if a and a.interface and a.interface ~= interface then -                        formatname = scripts.context.interfaces[a.interface] or formatname -                        formatfile, scriptfile = resolvers.locateformat(formatname) +                end +                -- +                local pdfview = getargument("autopdf") or getargument("closepdf") +                if pdfview then +                    pdf_close(filename,pdfview) +                    if resultname then +                        pdf_close(resultname,pdfview)                      end -                    -- this catches the command line -                    if not formatfile or not scriptfile then -                        report("warning: no format found, forcing remake (source driven)") -                        scripts.context.make(formatname) -                        formatfile, scriptfile = resolvers.locateformat(formatname) +                end +                -- +                local okay = statistics.checkfmtstatus(formatfile) +                if okay ~= true then +                    report("warning: %s, forcing remake",tostring(okay)) +                    scripts.context.make(formatname) +                end +                -- +                local oldhash    = multipass_hashfiles(jobname) +                local newhash    = { } +                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 +                    -- +                    result      = validstring(resultname), +                    input       = validstring(filename), +                    files       = concat(files,","), +                    ctx         = validstring(ctxname), +                } +                -- +                for k, v in next, environment.arguments do +                    if c_flags[k] == nil then +                        c_flags[k] = v                      end -                    if formatfile and scriptfile then -                        -- we default to mkiv xml ! -                        -- the --prep argument might become automatic (and noprep) -                        local suffix = file.extname(filename) or "?" -                        if scripts.context.xmlsuffixes[suffix] or getargument("forcexml") then -                            if getargument("mkii") then -                                filename = makestub(true,"\\processXMLfilegrouped{%s}",filename) -                            else -                                filename = makestub(true,"\\xmlprocess{\\xmldocument}{%s}{}",filename) -                            end -                        elseif scripts.context.cldsuffixes[suffix] or getargument("forcecld") then -                            -- self contained cld files need to have a starttext/stoptext (less fontloading) -                            filename = makestub(false,"\\ctxlua{context.runfile('%s')}",filename) -                        elseif scripts.context.luasuffixes[suffix] or getargument("forcelua") then -                            filename = makestub(true,"\\ctxlua{dofile('%s')}",filename) -                        elseif getargument("prep") then -                            -- we need to keep the original jobname -                            filename = makestub(true,"\\readfile{%s}{}{}",filename,ctxrunner.preppedfile(ctxdata,filename)) -                        end -                        -- -                        -- todo: also other stubs -                        -- -                        local suffix, resultname = getargument("suffix"), getargument("result") -                        if type(suffix) == "string" then -                            resultname = file.removesuffix(jobname) .. suffix -                        end -                        local oldbase, newbase = "", "" -                        if type(resultname) == "string" then -                            oldbase = file.removesuffix(jobname) -                            newbase = file.removesuffix(resultname) -                            if oldbase ~= newbase then -                                if getargument("purgeresult") then -                                    push_result_purge(oldbase,newbase) -                                else -                                    push_result_keep(oldbase,newbase) -                                end -                            else -                                resultname = nil -                            end -                        else -                            resultname = nil -                        end -                        -- -                        local pdfview = getargument("autopdf") or getargument("closepdf") -                        if pdfview then -                            scripts.context.closepdf(filename,pdfview) -                            if resultname then -                                scripts.context.closepdf(resultname,pdfview) -                            end -                        end -                        -- -                        local okay = statistics.checkfmtstatus(formatfile) -                        if okay ~= true then -                            report("warning: %s, forcing remake",tostring(okay)) -                            scripts.context.make(formatname) -                        end -                        -- -                        local flags = { } -                        if getargument("batchmode") or getargument("batch") then -                            flags[#flags+1] = "--interaction=batchmode" -                        end -                        if getargument("synctex") then -                            -- this should become a directive -                            report("warning: synctex is enabled") -- can add upto 5% runtime -                            flags[#flags+1] = "--synctex=1" -                        end -                        flags[#flags+1] = "--fmt=" .. quote(formatfile) -                        flags[#flags+1] = "--lua=" .. quote(scriptfile) -                        -- -                        -- We pass these directly. -                        -- - ---~                         local silent     = getargument("silent") ---~                         local noconsole  = getargument("noconsole") ---~                         local directives = getargument("directives") ---~                         local trackers   = getargument("trackers") ---~                         if silent == true then ---~                             silent = "*" ---~                         end ---~                         if type(silent) == "string" then ---~                             if type(directives) == "string" then ---~                                 directives = format("%s,logs.blocked={%s}",directives,silent) ---~                             else ---~                                 directives = format("logs.blocked={%s}",silent) ---~                             end ---~                         end ---~                         if noconsole then ---~                             if type(directives) == "string" then ---~                                 directives = format("%s,logs.target=file",directives) ---~                             else ---~                                 directives = format("logs.target=file") ---~                             end ---~                         end - -                        local directives  = environment.directives -                        local trackers    = environment.trackers -                        local experiments = environment.experiments - -                        -- -                        if type(directives) == "string" then -                            flags[#flags+1] = format('--directives="%s"',directives) -                        end -                        if type(trackers) == "string" then -                            flags[#flags+1] = format('--trackers="%s"',trackers) -                        end -                        -- -                        local backend = getargument("backend") -                        if type(backend) ~= "string" then -                            backend = "pdf" -                        end -                        flags[#flags+1] = format('--backend="%s"',backend) -                        -- -                        local command = format("luatex %s %s \\stoptext", concat(flags," "), quote(filename)) -                        local oldhash, newhash = scripts.context.multipass.hashfiles(jobname), { } -                        local once = getargument("once") -                        local maxnofruns = (once and 1) or scripts.context.multipass.nofruns -                        local arrange = getargument("arrange") -                        for i=1,maxnofruns do -                            -- 1:first run, 2:successive run, 3:once, 4:last of maxruns -                            local kindofrun = (once and 3) or (i==1 and 1) or (i==maxnofruns and 4) or 2 -                            scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,false,once) -- kindofrun, currentrun, final -                            report("run %s: %s",i,command) ---~                             print("\n") -- cleaner, else continuation on same line -                            print("") -- cleaner, else continuation on same line -                            local returncode, errorstring = os.spawn(command) -                        --~ if returncode == 3 then -                        --~     scripts.context.make(formatname) -                        --~     returncode, errorstring = os.spawn(command) -                        --~     if returncode == 3 then -                        --~         report("ks: return code 3, message: %s",errorstring or "?") -                        --~         os.exit(1) -                        --~     end -                        --~ end -                            if not returncode then -                                report("fatal error: no return code, message: %s",errorstring or "?") -                                if resultname then -                                    save_result_error(oldbase,newbase) -                                end -                                os.exit(1) -                                break -                            elseif returncode > 0 then -                                report("fatal error: return code: %s",returncode or "?") -                                if resultname then -                                    save_result_error(oldbase,newbase) -                                end -                                os.exit(returncode) -                                break -                            else -                                scripts.context.multipass.copyluafile(jobname) -                            --  scripts.context.multipass.copytuifile(jobname) -                                newhash = scripts.context.multipass.hashfiles(jobname) -                                if scripts.context.multipass.changed(oldhash,newhash) then -                                    oldhash = newhash -                                else -                                    break -                                end -                            end -                        end -                        -- -                        if arrange then -                            local kindofrun = 3 -                            scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,true) -- kindofrun, currentrun, final -                            report("arrange run: %s",command) -                            local returncode, errorstring = os.spawn(command) -                            if not returncode then -                                report("fatal error: no return code, message: %s",errorstring or "?") -                                os.exit(1) -                            elseif returncode > 0 then -                                report("fatal error: return code: %s",returncode or "?") -                                os.exit(returncode) -                            end -                        end -                        -- -                        if getargument("purge") then -                            scripts.context.purge_job(jobname) -                        elseif getargument("purgeall") then -                            scripts.context.purge_job(jobname,true) -                        end -                        -- -                        os.remove(jobname..".top") -                        -- +                end +                -- +                local l_flags = { +                    ["interaction"]         = (a_batchmode and "batchmode") or (a_nonstopmode and "nonstopmode") or nil, +                    ["synctex"]             = a_synctex and 1 or nil, +                    ["no-parse-first-line"] = true, +                 -- ["no-mktex"]            = true, +                 -- ["file-line-error"]     = true, +                    ["fmt"]                 = formatfile, +                    ["lua"]                 = scriptfile, +                    ["jobname"]             = jobname, +                } +                -- +                if a_synctex then +                    report("warning: synctex is enabled") -- can add upto 5% runtime +                end +                -- +                -- kindofrun: 1:first run, 2:successive run, 3:once, 4:last of maxruns +                -- +                for currentrun=1,maxnofruns do +                    -- +                    c_flags.final      = false +                    c_flags.kindofrun  = (a_once and 3) or (currentrun==1 and 1) or (currentrun==maxnofruns and 4) or 2 +                    c_flags.currentrun = currentrun +                    c_flags.noarrange  = a_noarrange or a_arrange or nil +                    -- +                    local command = luatex_command(l_flags,c_flags,mainfile) +                    -- +                    report("run %s: %s",i,command) +                    print("") -- cleaner, else continuation on same line +                    local returncode, errorstring = os.spawn(command) +                    if not returncode then +                        report("fatal error: no return code, message: %s",errorstring or "?")                          if resultname then -                            if getargument("purgeresult") then -                                -- so, if there is no result then we don't get the old one, but -                                -- related files (log etc) are still there for tracing purposes -                                save_result_purge(oldbase,newbase) -                            else -                                save_result_keep(oldbase,newbase) -                            end -                            report("result renamed to: %s",newbase) +                            result_save_error(oldbase,newbase)                          end -                        -- -                        if getargument("purge") then -                            scripts.context.purge_job(resultname) -                        elseif getargument("purgeall") then -                            scripts.context.purge_job(resultname,true) -                        end -                        -- -                        local pdfview = getargument("autopdf") -                        if pdfview then -                            scripts.context.openpdf(resultname or filename,pdfview) -                        end -                        -- -                        if getargument("timing") then -                            report() -                            report("you can process (timing) statistics with:",jobname) -                            report() -                            report("context --extra=timing '%s'",jobname) -                            report("mtxrun --script timing --xhtml [--launch --remove] '%s'",jobname) -                            report() +                        os.exit(1) +                        break +                    elseif returncode > 0 then +                        report("fatal error: return code: %s",returncode or "?") +                        if resultname then +                            result_save_error(oldbase,newbase)                          end +                        os.exit(returncode) +                        break                      else -                        if formatname then -                            report("error, no format found with name: %s, skipping",formatname) +                        multipass_copyluafile(jobname) +                        newhash = multipass_hashfiles(jobname) +                        if multipass_changed(oldhash,newhash) then +                            oldhash = newhash                          else -                            report("error, no format found (provide formatname or interface)") +                            break                          end -                        break                      end +                    -- +                end +                -- +                if a_arrange then +                    -- +                    c_flags.final      = true +                    c_flags.kindofrun  = 3 +                    c_flags.currentrun = c_flags.currentrun + 1 +                    c_flags.noarrange  = a_arrange and true or nil +                    -- +                    local command = luatex_command(l_flags,c_flags,mainfile) +                    -- +                    report("arrange run: %s",command) +                    local returncode, errorstring = os.spawn(command) +                    if not returncode then +                        report("fatal error: no return code, message: %s",errorstring or "?") +                        os.exit(1) +                    elseif returncode > 0 then +                        report("fatal error: return code: %s",returncode or "?") +                        os.exit(returncode) +                    end +                    -- +                end +                -- +                if a_purge then +                    scripts.context.purge_job(jobname) +                elseif a_purgeall then +                    scripts.context.purge_job(jobname,true) +                end +                -- +                if resultname then +                    if a_purgeresult then +                        -- so, if there is no result then we don't get the old one, but +                        -- related files (log etc) are still there for tracing purposes +                        result_save_purge(oldbase,newbase) +                    else +                        result_save_keep(oldbase,newbase) +                    end +                    report("result renamed to: %s",newbase) +                end +                -- +                if purge then +                    scripts.context.purge_job(resultname) +                elseif purgeall then +                    scripts.context.purge_job(resultname,true) +                end +                -- +                local pdfview = getargument("autopdf") +                if pdfview then +                    pdf_open(resultname or jobname,pdfview) +                end +                -- +                if a_timing then +                    report() +                    report("you can process (timing) statistics with:",jobname) +                    report() +                    report("context --extra=timing '%s'",jobname) +                    report("mtxrun --script timing --xhtml [--launch --remove] '%s'",jobname) +                    report()                  end -            end -        else -            if formatname then -                report("error, no format found with name: %s, aborting",formatname)              else -                report("error, no format found (provide formatname or interface)") +                if formatname then +                    report("error, no format found with name: %s, skipping",formatname) +                else +                    report("error, no format found (provide formatname or interface)") +                end +                break              end          end      end +    --  end -function scripts.context.pipe() +function scripts.context.pipe() -- still used?      -- context --pipe      -- context --pipe --purge --dummyfile=whatever.tmp      local interface = getargument("interface")      interface = (type(interface) == "string" and interface) or "en" -    local formatname = scripts.context.interfaces[interface] or "cont-en" +    local formatname = formatofinterface[interface] or "cont-en"      local formatfile, scriptfile = resolvers.locateformat(formatname)      if not formatfile or not scriptfile then          report("warning: no format found, forcing remake (commandline driven)") @@ -1087,11 +754,16 @@ function scripts.context.pipe()              report("warning: %s, forcing remake",tostring(okay))              scripts.context.make(formatname)          end -        local flags = { -            "--interaction=scrollmode", -            "--fmt=" .. quote(formatfile), -            "--lua=" .. quote(scriptfile), -            "--backend=pdf", +        local l_flags = { +            interaction = "scrollmode", +            fmt         = formatfile, +            lua         = scriptfile, +        } +        local c_flags = { +            backend     = "pdf", +            final       = false, +            kindofrun   = 3, +            currentrun  = 1,          }          local filename = getargument("dummyfile") or ""          if filename == "" then @@ -1100,10 +772,9 @@ function scripts.context.pipe()          else              filename = file.addsuffix(filename,"tmp")              io.savedata(filename,"\\relax") -            scripts.context.multipass.makeoptionfile(filename,{ flags = flags },3,1,false) -- kindofrun, currentrun, final              report("entering scrollmode using '%s' with optionfile, end job with \\end",filename)          end -        local command = format("luatex %s %s", concat(flags," "), quote(filename)) +        local command = luatex_command(l_flags,c_flags,filename)          os.spawn(command)          if getargument("purge") then              scripts.context.purge_job(filename) @@ -1123,11 +794,9 @@ end  local make_mkiv_format = environment.make_format  local function make_mkii_format(name,engine) -    if getargument(engine) then -        local command = format("mtxrun texexec.rb --make --%s %s",name,engine) -        report("running command: %s",command) -        os.spawn(command) -    end +    local command = format("mtxrun texexec.rb --make --%s %s",name,engine) +    report("running command: %s",command) +    os.spawn(command)  end  function scripts.context.generate() @@ -1140,14 +809,17 @@ function scripts.context.make(name)      if not getargument("fast") then -- as in texexec          scripts.context.generate()      end -    local list = (name and { name }) or (environment.files[1] and environment.files) or scripts.context.defaultformats +    local list = (name and { name }) or (environment.files[1] and environment.files) or defaultformats +    local engine = getargument("engine") or "luatex"      for i=1,#list do          local name = list[i] -        name = scripts.context.interfaces[name] or name or "" -        if name ~= "" then +        name = formatofinterface[name] or name or "" +        if name == "" then +            -- nothing +        elseif engine == "luatex" then              make_mkiv_format(name) -            make_mkii_format(name,"pdftex") -            make_mkii_format(name,"xetex") +        elseif engine == "pdftex" or engine == "xetex" then +            make_mkii_format(name,engine)          end      end  end @@ -1155,25 +827,30 @@ end  function scripts.context.ctx()      local ctxdata = ctxrunner.new()      ctxdata.jobname = environment.files[1] -    ctxrunner.manipulate(ctxdata,getargument("ctx")) +    ctxrunner.checkfile(ctxdata,getargument("ctx")) +    ctxrunner.checkflags(ctxdata)      scripts.context.run(ctxdata)  end  function scripts.context.autoctx()      local ctxdata = nil -    local files = (filename and { filename }) or environment.files +    local files = environment.files      local firstfile = #files > 0 and files[1] -    if firstfile and file.extname(firstfile) == "xml" then -        local f = io.open(firstfile) -        if f then -            local chunk = f:read(512) or "" -            f:close() -            local ctxname = match(chunk,"<%?context%-directive%s+job%s+ctxfile%s+([^ ]-)%s*?>") -            if ctxname then -                ctxdata = ctxrunner.new() -                ctxdata.jobname = firstfile -                ctxrunner.manipulate(ctxdata,ctxname) +    if firstfile then +        local suffix = file.suffix(firstfile) +        if suffix == "xml" then +            local chunk = io.loadchunk(firstfile) -- 1024 +            if chunk then +                local ctxname = match(chunk,"<%?context%-directive%s+job%s+ctxfile%s+([^ ]-)%s*?>") +                if ctxname then +                    ctxdata = ctxrunner.new() +                    ctxdata.jobname = firstfile +                    ctxrunner.checkfile(ctxdata,ctxname) +                    ctxrunner.checkflags(ctxdata) +                end              end +        elseif suffix == "tex" then +            -- maybe but we scan the preamble later too          end      end      scripts.context.run(ctxdata) @@ -1206,8 +883,8 @@ function scripts.context.metapost()          local tempname = file.addsuffix(jobname,"tex")          io.savedata(tempname,format(template,"metafun",filename))          environment.files[1] = tempname -        environment.setargument("result",resultname) -        environment.setargument("once",true) +        setargument("result",resultname) +        setargument("once",true)          scripts.context.run()          scripts.context.purge_job(jobname,true)          scripts.context.purge_job(resultname,true) @@ -1238,6 +915,8 @@ function scripts.context.version()      end  end +-- purging files +  local generic_files = {      "texexec.tex", "texexec.tui", "texexec.tuo",      "texexec.tuc", "texexec.tua", @@ -1262,7 +941,6 @@ local persistent_runfiles = {  }  local special_runfiles = { ---~     "-mpgraph*", "-mprun*", "-temp-*" -- hm, wasn't this escaped?      "-mpgraph", "-mprun", "-temp-"  } @@ -1278,9 +956,6 @@ local function purge_file(dfile,cfile)      end  end -local function remove_special_files(pattern) -end -  function scripts.context.purge_job(jobname,all,mkiitoo)      if jobname and jobname ~= "" then          jobname = file.basename(jobname) @@ -1335,12 +1010,14 @@ function scripts.context.purge(all,pattern,mkiitoo)      end  end +-- touching files (signals regeneration of formats) +  local function touch(name,pattern)      local name = resolvers.findfile(name)      local olddata = io.loaddata(name)      if olddata then          local oldversion, newversion = "", os.date("%Y.%m.%d %H:%M") -        local newdata, ok = olddata:gsub(pattern,function(pre,mid,post) +        local newdata, ok = gsub(olddata,pattern,function(pre,mid,post)              oldversion = mid              return pre .. newversion .. post          end) @@ -1374,6 +1051,8 @@ function scripts.context.touch()          touchfiles("mkii")          touchfiles("mkiv")          touchfiles("mkvi") +    else +        report("touching needs --expert")      end  end @@ -1387,12 +1066,12 @@ function scripts.context.modules(pattern)      local found = resolvers.findfile("context.mkiv")      if not pattern or pattern == "" then          -- official files in the tree -        for _, card in ipairs(cards) do -            resolvers.findwildcardfiles(card,list) +        for i=1,#cards do +            resolvers.findwildcardfiles(cards[i],list)          end          -- my dev path -        for _, card in ipairs(cards) do -            dir.glob(file.join(file.dirname(found),card),list) +        for i=1,#cards do +            dir.glob(file.join(file.dirname(found),cards[i]),list)          end      else          resolvers.findwildcardfiles(pattern,list) @@ -1462,30 +1141,28 @@ end  function scripts.context.extra()      local extra = getargument("extra") -    if type(extra) == "string" then -        if getargument("help") then -            scripts.context.extras(extra) +    if type(extra) ~= "string" then +        scripts.context.extras() +    elseif getargument("help") then +        scripts.context.extras(extra) +    else +        local fullextra = extra +        if not find(fullextra,"mtx%-context%-") then +            fullextra = "mtx-context-" .. extra +        end +        local foundextra = resolvers.findfile(fullextra) +        if foundextra == "" then +            scripts.context.extras() +            return          else -            local fullextra = extra -            if not find(fullextra,"mtx%-context%-") then -                fullextra = "mtx-context-" .. extra -            end -            local foundextra = resolvers.findfile(fullextra) -            if foundextra == "" then -                scripts.context.extras() -                return -            else -                report("processing extra: %s", foundextra) -            end -            environment.setargument("purgeall",true) -            local result = environment.setargument("result") or "" -            if result == "" then -                environment.setargument("result","context-extra") -            end -            scripts.context.run(nil,foundextra) +            report("processing extra: %s", foundextra)          end -    else -        scripts.context.extras() +        setargument("purgeall",true) +        local result = getargument("result") or "" +        if result == "" then +            setargument("result","context-extra") +        end +        scripts.context.run(nil,foundextra)      end  end @@ -1493,25 +1170,27 @@ end  function scripts.context.trackers()      environment.files = { resolvers.findfile("m-trackers.mkiv") } -    scripts.context.multipass.nofruns = 1 -    environment.setargument("purgeall",true) +    multipass_nofruns = 1 +    setargument("purgeall",true)      scripts.context.run()  end  function scripts.context.directives()      environment.files = { resolvers.findfile("m-directives.mkiv") } -    scripts.context.multipass.nofruns = 1 -    environment.setargument("purgeall",true) +    multipass_nofruns = 1 +    setargument("purgeall",true)      scripts.context.run()  end  function scripts.context.logcategories()      environment.files = { resolvers.findfile("m-logcategories.mkiv") } -    scripts.context.multipass.nofruns = 1 -    environment.setargument("purgeall",true) +    multipass_nofruns = 1 +    setargument("purgeall",true)      scripts.context.run()  end +-- updating (often one will use mtx-update instead) +  function scripts.context.timed(action)      statistics.timed(action)  end @@ -1548,7 +1227,7 @@ function scripts.context.update()      local function is_okay(basetree)          for _, tree in next, validtrees do              local pattern = gsub(tree,"%-","%%-") -            if basetree:find(pattern) then +            if find(basetree,pattern) then                  return tree              end          end @@ -1614,7 +1293,7 @@ function scripts.context.update()          end          for k in zipfile:files() do              local filename = k.filename -            if filename:find("/$") then +            if find(filename,"/$") then                  lfs.mkdir(filename)              else                  local data = zip.loaddata(zipfile,filename) @@ -1652,6 +1331,23 @@ function scripts.context.update()      end  end +-- getting it done + +if getargument("nostats") then +    setargument("nostatistics",true) +    setargument("nostat",nil) +end + +if getargument("batch") then +    setargument("batchmode",true) +    setargument("batch",nil) +end + +if getargument("nonstop") then +    setargument("nonstopmode",true) +    setargument("nonstop",nil) +end +  do      local silent = getargument("silent") @@ -1664,9 +1360,9 @@ do  end  if getargument("once") then -    scripts.context.multipass.nofruns = 1 +    multipass_nofruns = 1  elseif getargument("runs") then -    scripts.context.multipass.nofruns = tonumber(getargument("runs")) or nil +    multipass_nofruns = tonumber(getargument("runs")) or nil  end  if getargument("profile") then @@ -1674,7 +1370,6 @@ if getargument("profile") then  end  if getargument("run") then ---  scripts.context.timed(scripts.context.run)      scripts.context.timed(scripts.context.autoctx)  elseif getargument("make") then      scripts.context.timed(function() scripts.context.make() end) @@ -1711,10 +1406,7 @@ elseif getargument("showdirectives") or getargument("directives") == true then      scripts.context.directives()  elseif getargument("showlogcategories") then      scripts.context.logcategories() -elseif getargument("track") and type(getargument("track")) == "boolean" then -- for old times sake, will go -    scripts.context.trackers() -elseif environment.files[1] then ---  scripts.context.timed(scripts.context.run) +elseif environment.files[1] or getargument("nofile") then      scripts.context.timed(scripts.context.autoctx)  elseif getargument("pipe") then      scripts.context.timed(scripts.context.pipe) diff --git a/scripts/context/lua/mtx-epub.lua b/scripts/context/lua/mtx-epub.lua index 7d1c15774..000ac0670 100644 --- a/scripts/context/lua/mtx-epub.lua +++ b/scripts/context/lua/mtx-epub.lua @@ -263,20 +263,18 @@ function scripts.epub.make()          application.report("creating archive\n\n") -        local done = false -        local list = { } -          lfs.chdir(epubpath)          os.remove(epubfile) +        local done = false +          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,"OPS"))                  done = zipper.name -            else -                list[#list+1] = zipper.name +                break              end          end @@ -285,6 +283,10 @@ function scripts.epub.make()          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," "))          end diff --git a/scripts/context/lua/mtx-fcd.lua b/scripts/context/lua/mtx-fcd.lua new file mode 100644 index 000000000..7ab48707a --- /dev/null +++ b/scripts/context/lua/mtx-fcd.lua @@ -0,0 +1,366 @@ +if not modules then modules = { } end modules ['mtx-fcd'] = { +    version   = 1.002, +    comment   = "companion to mtxrun.lua", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +    comment   = "based on the ruby version from 2005", +} + +-- This is a kind of variant of the good old ncd (norton change directory) program. This +-- script uses the same indirect cmd trick as Erwin Waterlander's wcd program. +-- +-- The program is called via the stubs fcd.cmd or fcd.sh. On unix one should probably source +-- the file: ". fcd args" in order to make the chdir persistent. +-- +-- You need to create a stub with: +-- +--   mtxrun --script fcd --stub > fcd.cmd +--   mtxrun --script fcd --stub > fcd.sh +-- +-- The stub starts this script and afterwards runs the created directory change script as +-- part if the same run, so that indeed we change. + +local helpinfo = [[ +--clear                      clear the cache +--clear --history [entyr]    clear the history +--scan                       clear the cache and add given path(s) +--add                        add given path(s) +--find                       file given path (can be substring) +--find --nohistory           file given path (can be substring) but don't use history +--stub                       print platform stub file +--list                       show roots of cached dirs +--list --history             show history of chosen dirs +--help                       show this help + +usage: + +  fcd --scan t:\ +  fcd --add f:\project +  fcd [--find] whatever +  fcd --list +]] + +local application = logs.application { +    name     = "mtx-fcd", +    banner   = "Fast Directory Change", +    helpinfo = helpinfo, +} + +local report  = application.report +local writeln = print -- texio.write_nl + +local find, char, byte, lower, gsub, format = string.find, string.char, string.byte, string.lower, string.gsub, string.format + +local mswinstub = [[@echo off + +rem this is: fcd.cmd + +@echo off + +if not exist "%HOME%" goto homepath + +:home + +mtxrun --script mtx-fcd.lua %1 %2 %3 %4 %5 %6 %7 %8 %9 + +if exist "%HOME%\mtx-fcd-goto.cmd" call "%HOME%\mtx-fcd-goto.cmd" + +goto end + +:homepath + +if not exist "%HOMEDRIVE%\%HOMEPATH%" goto end + +mtxrun --script mtx-fcd.lua %1 %2 %3 %4 %5 %6 %7 %8 %9 + +if exist "%HOMEDRIVE%\%HOMEPATH%\mtx-fcd-goto.cmd" call "%HOMEDRIVE%\%HOMEPATH%\mtx-fcd-goto.cmd" + +goto end + +:end +]] + +local unixstub = [[#!/usr/bin/env sh + +# this is: fcd.sh + +# mv fcd.sh fcd +# chmod fcd 755 +# . fcd [args] + +ruby -S fcd_start.rb $1 $2 $3 $4 $5 $6 $7 $8 $9 + +if test -f "$HOME/fcd_stage.sh" ; then +  . $HOME/fcd_stage.sh ; +fi; + +]] + +local gotofile +local datafile +local stubfile +local stubdata +local stubdummy +local stubchdir + +if os.platform == 'mswin' then +    gotofile  = 'mtx-fcd-goto.cmd' +    datafile  = 'mtx-fcd-data.lua' +    stubfile  = 'fcd.cmd' +    stubdata  = mswinstub +    stubdummy = 'rem no dir to change to' +    stubchdir = 'cd /d "%s"' +else +    gotofile  = 'mtx-fcd-goto.sh' +    datafile  = 'mtx-fcd-data.lua' +    stubfile  = 'fcd.sh' +    stubdata  = unixstub +    stubdummy = '# no dir to change to' +    stubchdir = '# cd "%s"' +end + +local homedir = os.env["HOME"] or "" -- no longer TMP etc + +if homedir == "" then +    homedir = format("%s/%s",os.env["HOMEDRIVE"] or "",os.env["HOMEPATH"] or "") +end + +if homedir == "/" or not lfs.isdir(homedir) then +    os.exit() +end + +local datafile = file.join(homedir,datafile) +local gotofile = file.join(homedir,gotofile) +local hash     = nil +local found    = { } +local pattern  = "" +local version  = modules['mtx-fcd'].version + +io.savedata(gotofile,stubdummy) + +if not lfs.isfile(gotofile) then +    -- write error +    os.exit() +end + +local function fcd_clear(onlyhistory,what) +    if onlyhistory and hash and hash.history then +        if what and what ~= "" then +            hash.history[what] = nil +        else +            hash.history = { } +        end +    else +        hash = { +            name    = "fcd cache", +            comment = "generated by mtx-fcd.lua", +            created = os.date(), +            version = version, +            paths   = { }, +            history = { }, +        } +    end +end + +local function fcd_changeto(dir) +    if dir and dir ~= "" then +        io.savedata(gotofile,format(stubchdir,dir)) +    end +end + +local function fcd_load(forcecreate) +    if lfs.isfile(datafile) then +        hash = dofile(datafile) +    end +    if not hash or hash.version ~= version then +        if forcecache then +            fcd_clear() +        else +            writeln("empty dir cache") +            fcd_clear() +            os.exit() +        end +    end +end + +local function fcd_save() +    if hash then +        io.savedata(datafile,table.serialize(hash,true)) +    end +end + +local function fcd_list(onlyhistory) +    if hash then +        writeln("") +        if onlyhistory then +            if next(hash.history) then +                for k, v in table.sortedhash(hash.history) do +                    writeln(format("%s => %s",k,v)) +                end +            else +                writeln("no history") +            end +        else +            local paths = hash.paths +            if #paths > 0 then +                for i=1,#paths do +                    local path = paths[i] +                    writeln(format("%4i  %s",#path[2],path[1])) +                end +            else +                writeln("empty cache") +            end +        end +    end +end + +local function fcd_find() +    found = { } +    pattern = environment.files[1] or "" +    if pattern ~= "" then +        pattern = string.escapedpattern(pattern) +        local paths = hash.paths +        for i=1,#paths do +            local paths = paths[i][2] +            for i=1,#paths do +                local path = paths[i] +                if find(path,pattern) then +                    found[#found+1] = path +                end +            end +        end +    end +end + +local function fcd_choose(new) +    if pattern == "" then +        writeln(format("staying in dir %q",(gsub(lfs.currentdir(),"\\","/")))) +        return +    end +    if #found == 0 then +        writeln(format("dir %q not found",pattern)) +        return +    end +    local okay = #found == 1 and found[1] or (not new and hash.history[pattern]) +    if okay then +        writeln(format("changing to %q",okay)) +        fcd_changeto(okay) +        return +    end +    local offset = 0 +    while true do +        if not found[offset] then +            offset = 0 +        end +        io.write("\n") +        for i=1,26 do +            local v = found[i+offset] +            if v then +                writeln(format("%s  %3i  %s",char(i+96),offset+i,v)) +            else +                break +            end +        end +        offset = offset + 26 +        if found[offset+1] then +            io.write("\n[press enter for more or select letter]\n\n>> ") +        else +            io.write("\n[select letter]\n\n>> ") +        end +        local answer = lower(io.read() or "") +        if not answer or answer == 'quit' then +            break +        elseif #answer > 0 then +            local choice = tonumber(answer) +            if not choice then +                if answer >= "a" and answer <= "z" then +                    choice = byte(answer) - 96 + offset - 26 +                end +            end +            local newdir = found[choice] +            if newdir then +                hash.history[pattern] = newdir +                writeln(format("changing to %q",newdir)) +                fcd_changeto(newdir) +                fcd_save() +                return +            end +        else +            -- try again +        end +    end +end + +local function globdirs(path,dirs) +    local dirs = dirs or { } +    for name in lfs.dir(path) do +        if not find(name,"%.$") then +            local fullname = path .. "/" .. name +            if lfs.isdir(fullname) then +                dirs[#dirs+1] = fullname +                globdirs(fullname,dirs) +            end +        end +    end +    return dirs +end + +local function fcd_scan() +    if hash then +        local paths = hash.paths +        for i=1,#environment.files do +            local name = environment.files[i] +            local name = gsub(name,"\\","/") +            local name = gsub(name,"/$","") +            local list = globdirs(name) +            local done = false +            for i=1,#paths do +                if paths[i][1] == name then +                    paths[i][2] = list +                    done = true +                    break +                end +            end +            if not done then +                paths[#paths+1] = { name, list } +            end +        end +    end +end + +local argument = environment.argument + +if argument("clear") then +    if argument("history") then +        fcd_load() +        fcd_clear(true) +    else +        fcd_clear() +    end +    fcd_save() +elseif argument("scan") then +    fcd_clear() +    fcd_scan() +    fcd_save() +elseif argument("add") then +    fcd_load(true) +    fcd_scan() +    fcd_save() +elseif argument("stub") then +    writeln(stubdata) +elseif argument("list") then +    fcd_load() +    if argument("history") then +        fcd_list(true) +    else +        fcd_list() +    end +elseif argument("help") then +    application.help() +else -- also argument("find") +    fcd_load() +    fcd_find() +    fcd_choose(argument("nohistory")) +end + diff --git a/scripts/context/lua/mtx-grep.lua b/scripts/context/lua/mtx-grep.lua index 3cbc1421a..98a97279d 100644 --- a/scripts/context/lua/mtx-grep.lua +++ b/scripts/context/lua/mtx-grep.lua @@ -60,7 +60,7 @@ function scripts.grep.find(pattern, files, offset)                              if m > 0 then                                  nofmatches = nofmatches + m                                  nofmatchedfiles = nofmatchedfiles + 1 -                                write_nl(format("%s: %s",name,m)) +                                write_nl(format("%5i  %s",m,name))                                  io.flush()                              end                          else @@ -127,7 +127,7 @@ function scripts.grep.find(pattern, files, offset)                          if count and m > 0 then                              nofmatches = nofmatches + m                              nofmatchedfiles = nofmatchedfiles + 1 -                            write_nl(format("%s: %s",name,m)) +                            write_nl(format("%5i  %s",m,name))                              io.flush()                          end                      end diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua index 108f2a8a1..cc29845b5 100644 --- a/scripts/context/lua/mtxrun.lua +++ b/scripts/context/lua/mtxrun.lua @@ -1341,12 +1341,16 @@ function lpeg.split(separator,str)  end  function string.split(str,separator) -    local c = cache[separator] -    if not c then -        c = tsplitat(separator) -        cache[separator] = c +    if separator then +        local c = cache[separator] +        if not c then +            c = tsplitat(separator) +            cache[separator] = c +        end +        return match(c,str) +    else +        return { str }      end -    return match(c,str)  end  local spacing  = patterns.spacer^0 * newline -- sort of strip @@ -1851,14 +1855,14 @@ else      io.fileseparator, io.pathseparator = "/" , ":"  end -function io.loaddata(filename,textmode) +function io.loaddata(filename,textmode) -- return nil if empty      local f = io.open(filename,(textmode and 'r') or 'rb')      if f then          local data = f:read('*all')          f:close() -        return data -    else -        return nil +        if #data > 0 then +            return data +        end      end  end @@ -1880,6 +1884,45 @@ function io.savedata(filename,data,joiner)      end  end +function io.loadlines(filename,n) -- return nil if empty +    local f = io.open(filename,'r') +    if f then +        if n then +            local lines = { } +            for i=1,n do +                local line = f:read("*lines") +                if line then +                    lines[#lines+1] = line +                else +                    break +                end +            end +            f:close() +            lines = concat(lines,"\n") +            if #lines > 0 then +                return lines +            end +        else +            local line = f:read("*line") or "" +            assert(f:close()) +            if #line > 0 then +                return line +            end +        end +    end +end + +function io.loadchunk(filename,n) +    local f = io.open(filename,'rb') +    if f then +        local data = f:read(n or 1024) +        f:close() +        if #data > 0 then +            return data +        end +    end +end +  function io.exists(filename)      local f = io.open(filename)      if f == nil then @@ -3081,15 +3124,30 @@ 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 -function file.needs_updating(oldname,newname,threshold) -- size modification access change -    local oldtime = lfs.attributes(oldname, modification) -    local newtime = lfs.attributes(newname, modification) -    if newtime >= oldtime then -        return false -    elseif oldtime - newtime < (threshold or 1) then -        return false +function file.needsupdating(oldname,newname,threshold) -- size modification access change +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime then +        local newtime = lfs.attributes(newname,"modification") +        if not newtime then +            return true -- no new file, so no updating needed +        elseif newtime >= oldtime then +            return false -- new file definitely needs updating +        elseif oldtime - newtime < (threshold or 1) then +            return false -- new file is probably still okay +        else +            return true -- new file has to be updated +        end      else -        return true +        return false -- no old file, so no updating needed +    end +end + +file.needs_updating = file.needsupdating + +function file.syncmtimes(oldname,newname) +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime and lfs.isfile(newname) then +        lfs.touch(newname,oldtime,oldtime)      end  end @@ -3111,7 +3169,7 @@ function file.loadchecksum(name)      return nil  end -function file.savechecksum(name, checksum) +function file.savechecksum(name,checksum)      if not checksum then checksum = file.checksum(name) end      if checksum then          io.savedata(name .. ".md5",checksum) @@ -5586,7 +5644,7 @@ function setters.show(t)              local value, default, modules = functions.value, functions.default, #functions              value   = value   == nil and "unset" or tostring(value)              default = default == nil and "unset" or tostring(default) -            t.report("%-30s   modules: %2i   default: %6s   value: %6s",name,modules,default,value) +            t.report("%-50s   modules: %2i   default: %6s   value: %6s",name,modules,default,value)          end      end      t.report() @@ -5678,17 +5736,31 @@ end)  -- experiment -local flags = environment and environment.engineflags +if environment then -if flags then -    if trackers and flags.trackers then -        setters.initialize("flags","trackers", settings_to_hash(flags.trackers)) -     -- t_enable(flags.trackers) -    end -    if directives and flags.directives then -        setters.initialize("flags","directives", settings_to_hash(flags.directives)) -     -- d_enable(flags.directives) +    -- The engineflags are known earlier than environment.arguments but maybe we +    -- need to handle them both as the later are parsed differently. The c: prefix +    -- is used by mtx-context to isolate the flags from those that concern luatex. + +    local engineflags = environment.engineflags + +    if engineflags then +        if trackers then +            local list = engineflags["c:trackers"] or engineflags["trackers"] +            if type(list) == "string" then +                setters.initialize("flags","trackers",settings_to_hash(list)) +             -- t_enable(list) +            end +        end +        if directives then +            local list = engineflags["c:directives"] or engineflags["directives"] +            if type(list) == "string" then +                setters.initialize("flags","directives", settings_to_hash(list)) +             -- d_enable(list) +            end +        end      end +  end  -- here @@ -6619,6 +6691,8 @@ local mt = {  setmetatable(environment,mt) +-- context specific arguments (in order not to confuse the engine) +  function environment.initializearguments(arg)      local arguments, files = { }, { }      environment.arguments, environment.files, environment.sortedflags = arguments, files, nil @@ -6627,10 +6701,12 @@ function environment.initializearguments(arg)          if index > 0 then              local flag, value = match(argument,"^%-+(.-)=(.-)$")              if flag then +                flag = gsub(flag,"^c:","")                  arguments[flag] = unquoted(value or "")              else                  flag = match(argument,"^%-+(.+)")                  if flag then +                    flag = gsub(flag,"^c:","")                      arguments[flag] = true                  else                      files[#files+1] = argument @@ -6650,7 +6726,7 @@ end  -- tricky: too many hits when we support partials unless we add  -- a registration of arguments so from now on we have 'partial' -function environment.argument(name,partial) +function environment.getargument(name,partial)      local arguments, sortedflags = environment.arguments, environment.sortedflags      if arguments[name] then          return arguments[name] @@ -6673,6 +6749,8 @@ function environment.argument(name,partial)      return nil  end +environment.argument = environment.getargument +  function environment.splitarguments(separator) -- rather special, cut-off before separator      local done, before, after = false, { }, { }      local originalarguments = environment.originalarguments diff --git a/scripts/context/ruby/fcd_start.rb b/scripts/context/ruby/fcd_start.rb deleted file mode 100644 index b1fa42a2a..000000000 --- a/scripts/context/ruby/fcd_start.rb +++ /dev/null @@ -1,472 +0,0 @@ -# Hans Hagen / PRAGMA ADE / 2005 / www.pragma-ade.com -# -# Fast Change Dir -# -# This is a kind of variant of the good old ncd -# program. This script uses the same indirect cmd -# trick as Erwin Waterlander's wcd program. -# -# === windows: fcd.cmd === -# -# @echo off -# ruby -S fcd_start.rb %1 %2 %3 %4 %5 %6 %7 %8 %9 -# if exist "%HOME%/fcd_stage.cmd" call %HOME%/fcd_stage.cmd -# -# === linux: fcd (fcd.sh) === -# -# !/usr/bin/env sh -# ruby -S fcd_start.rb $1 $2 $3 $4 $5 $6 $7 $8 $9 -# if test -f "$HOME/fcd_stage.sh" ; then -#   . $HOME/fcd_stage.sh ; -# fi; -# -# === -# -# On linux, one should source the file: ". fcd args" in order -# to make the chdir persistent. -# -# You can create a stub with: -# -# ruby fcd_start.rb --stub --verbose -# -# usage: -# -# fcd --make t:\ -# fcd --add f:\project -# fcd [--find] whatever -# fcd [--find] whatever c (c being a list entry) -# fcd [--find] whatever . (last choice with this pattern) -# fcd --list - -# todo: HOMEDRIVE\HOMEPATH - -require 'rbconfig' - -class FastCD - -    @@rootpath = nil - -    ['HOME','TEMP','TMP','TMPDIR'].each do |key| -        if ENV[key] then -            if FileTest.directory?(ENV[key]) then -                @@rootpath = ENV[key] -                break -            end -        end -    end - -    exit unless @@rootpath - -    @@mswindows = Config::CONFIG['host_os'] =~ /mswin/ -    @@maxlength = 26 - -    require 'Win32API' if @@mswindows - -    if @@mswindows then -        @@stubcode = [ -            '@echo off', -            '', -            'if not exist "%HOME%" goto temp', -            '', -            ':home', -            '', -            'ruby -S fcd_start.rb %1 %2 %3 %4 %5 %6 %7 %8 %9', -            '', -            'if exist "%HOME%\fcd_stage.cmd" call %HOME%\fcd_stage.cmd', -            'goto end', -            '', -            ':temp', -            '', -            'ruby -S fcd_start.rb %1 %2 %3 %4 %5 %6 %7 %8 %9', -            '', -            'if exist "%TEMP%\fcd_stage.cmd" call %TEMP%\fcd_stage.cmd', -            'goto end', -            '', -            ':end' -        ].join("\n") -    else -        @@stubcode = [ -            '#!/usr/bin/env sh', -            '', -            'ruby -S fcd_start.rb $1 $2 $3 $4 $5 $6 $7 $8 $9', -            '', -            'if test -f "$HOME/fcd_stage.sh" ; then', -            '  . $HOME/fcd_stage.sh ;', -            'fi;' -        ].join("\n") -    end - -    @@selfpath = File.dirname($0) -    @@datafile = File.join(@@rootpath,'fcd_state.dat') -    @@histfile = File.join(@@rootpath,'fcd_state.his') -    @@cdirfile = File.join(@@rootpath,if @@mswindows then 'fcd_stage.cmd' else 'fcd_stage.sh' end) -    @@stubfile = File.join(@@selfpath,if @@mswindows then 'fcd.cmd'       else 'fcd'          end) - -    def initialize(verbose=false) -        @list = Array.new -        @hist = Hash.new -        @result = Array.new -        @pattern = '' -        @result = '' -        @verbose = verbose -        if f = File.open(@@cdirfile,'w') then -            f << "#{if @@mswindows then 'rem' else '#' end} no dir to change to" -            f.close -        else -            report("unable to create stub #{@@cdirfile}") -        end -    end - -    def filename(name) -        File.join(@@root,name) -    end - -    def report(str,verbose=@verbose) -        puts(">> #{str}") if verbose -    end - -    def flush(str,verbose=@verbose) -        print(str) if verbose -    end - -    def clear -        if FileTest.file?(@@histfile) -            begin -                File.delete(@@histfile) -            rescue -                report("error in deleting history file '#{@histfile}'") -            else -                report("history file '#{@histfile}' is deleted") -            end -        else -            report("no history file '#{@histfile}'") -        end -    end - -    def scan(dir='.') -        begin -            [dir].flatten.sort.uniq.each do |dir| -                begin -                    Dir.chdir(dir) -                    report("scanning '#{dir}'") -                    # flush(">> ") -                    Dir.glob("**/*").each do |d| -                        if FileTest.directory?(d) then -                            @list << File.expand_path(d) -                            # flush(".") -                        end -                    end -                    # flush("\n") -                    @list = @list.sort.uniq -                    report("#{@list.size} entries found") -                rescue -                    report("unknown directory '#{dir}'") -                end -            end -        rescue -            report("invalid dir specification   ") -        end -    end - -    def save -        begin -            if f = File.open(@@datafile,'w') then -                @list.each do |l| -                    f.puts(l) -                end -                f.close -                report("#{@list.size} status bytes saved in #{@@datafile}") -            else -                report("unable to save status in #{@@datafile}") -            end -        rescue -            report("error in saving status in #{@@datafile}") -        end -    end - -    def remember -        if @hist[@pattern] == @result then -            # no need to save result -        else -            begin -                if f = File.open(@@histfile,'w') then -                    @hist[@pattern] = @result -                    @hist.keys.each do |k| -                        f.puts("#{k} #{@hist[k]}") -                    end -                    f.close -                    report("#{@hist.size} history entries saved in #{@@histfile}") -                else -                    report("unable to save history in #{@@histfile}") -                end -            rescue -                report("error in saving history in #{@@histfile}") -            end -        end -    end - -    def load -        begin -            @list = IO.read(@@datafile).split("\n") -            report("#{@list.length} status bytes loaded from #{@@datafile}") -        rescue -            report("error in loading status from #{@@datafile}") -        end -        begin -            IO.readlines(@@histfile).each do |line| -                if line =~ /^(.*?)\s+(.*)$/i then -                    @hist[$1] = $2 -                end -            end -            report("#{@hist.length} history entries loaded from #{@@histfile}") -        rescue -            report("error in loading history from #{@@histfile}") -        end -    end - -    def show -        begin -            puts("directories:") -            puts("\n") -            if @list.length > 0 then -                @list.each do |l| -                    puts(l) -                end -            else -                puts("no entries") -            end -            puts("\n") -            puts("history:") -            puts("\n") -            if @hist.length > 0 then -                @hist.keys.sort.each do |h| -                    puts("#{h} >> #{@hist[h]}") -                end -            else -                puts("no entries") -            end -        rescue -        end -    end - -    def find(pattern=nil) -        begin -            if pattern = [pattern].flatten.first then -                if  pattern.length > 0 and @pattern = pattern then -                    @result = @list.grep(/\/#{@pattern}$/i) -                    if @result.length == 0 then -                        @result = @list.grep(/\/#{@pattern}[^\/]*$/i) -                    end -                end -            else -                puts(Dir.pwd.gsub(/\\/o, '/')) -            end -        rescue -            puts("some error") -        end -    end - -    def chdir(dir) -        begin -            if dir then -                if f = File.open(@@cdirfile,'w') then -                    if @@mswindows then -                        f.puts("cd /d #{dir.gsub('/','\\')}") -                    else -                        f.puts("cd #{dir.gsub("\\",'/')}") -                    end -                    f.close -                end -                @result = dir -                report("changing to #{dir}",true) -            else -                report("not changing dir") -            end -        rescue -        end -    end - -    def choose(args=[]) -        offset = 97 -        unless @pattern.empty? then -            begin -                case @result.size -                    when 0 then -                        report("dir '#{@pattern}' not found",true) -                    when 1 then -                        chdir(@result[0]) -                    else -                        list = @result.dup -                        begin -                            if answer = args[1] then # assignment & test -                                if answer == '.' and @hist.key?(@pattern) then -                                    if FileTest.directory?(@hist[@pattern]) then -                                        print("last choice ") -                                        chdir(@hist[@pattern]) -                                        return -                                    end -                                else -                                    index = answer[0] - offset -                                    if dir = list[index] then -                                        chdir(dir) -                                        return -                                    end -                                end -                            end -                        rescue -                            puts("some error") -                        end -                        loop do -                            print("\n") -                            list.each_index do |i| -begin -                                if i < @@maxlength then -                                    # puts("#{(i+?a).chr}  #{list[i]}") -                                    puts("#{(i+offset).chr}  #{list[i]}") -                                else -                                    puts("\n   there are #{list.length-@@maxlength} entries more") -                                    break -                                end -rescue -                            puts("some error") -end -                            end -                            print("\n>> ") -                            if answer = wait then -                                if answer >= offset and answer <= offset+25 then -                                    index = answer - offset -                                    if dir = list[index] then -                                        print("#{answer.chr} ") -                                        chdir(dir) -                                    elsif @hist.key?(@pattern) and FileTest.directory?(@hist[@pattern]) then -                                        print("last choice ") -                                        chdir(@hist[@pattern]) -                                    else -                                        print("quit\n") -                                    end -                                    break -                                elsif list.length >= @@maxlength then -                                    @@maxlength.times do |i| list.shift end -                                    print("next set") -                                    print("\n") -                                elsif @hist.key?(@pattern) and FileTest.directory?(@hist[@pattern]) then -                                    print("last choice ") -                                    chdir(@hist[@pattern]) -                                    break -                                else -                                    print("quit\n") -                                    break -                                end -                            end -                        end -                    end -            rescue -                 report($!) -            end -        end -    end - -    def wait -        begin -            $stdout.flush -            return getc -        rescue -            return nil -        end -    end - -    def getc -        begin -            if @@mswindows then -                ch = Win32API.new('crtdll','_getch',[],'L').call -            else -                system('stty raw -echo') -                ch = $stdin.getc -                system('stty -raw echo') -            end -        rescue -            ch = nil -        end -        return ch -    end - -    def check -        unless FileTest.file?(@@stubfile) then -            report("creating stub #{@@stubfile}") -            begin -                if f = File.open(@@stubfile,'w') then -                    f.puts(@@stubcode) -                    f.close -                end -            rescue -                report("unable to create stub #{@@stubfile}") -            else -                unless @mswindows then -                    begin -                        File.chmod(0755,@@stubfile) -                    rescue -                        report("unable to change protections on #{@@stubfile}") -                    end -                end -            end -        else -            report("stub #{@@stubfile} already present") -        end -    end - -end - -$stdout.sync = true - -verbose, action, args = false, :find, Array.new - -usage   = "fcd [--add|clear|find|list|make|show|stub] [--verbose] [pattern]" -version = "1.0.2" - -def quit(message) -    puts(message) -    exit -end - -ARGV.each do |a| -    case a -        when '-a', '--add'     then action = :add -        when '-c', '--clear'   then action = :clear -        when '-f', '--find'    then action = :find -        when '-l', '--list'    then action = :show -        when '-m', '--make'    then action = :make -        when '-s', '--show'    then action = :show -        when       '--stub'    then action = :stub -        when '-v', '--verbose' then verbose = true -        when       '--version' then quit("version: #{version}") -        when '-h', '--help'    then quit("usage: #{usage}") -        when /^\-\-.*/         then quit("error: unknown switch #{a}, try --help") -                               else args << a -    end -end - -fcd = FastCD.new(verbose) -fcd.report("Fast Change Dir / version #{version}") - -case action -    when :make then -        fcd.clear -        fcd.scan(args) -        fcd.save -    when :clear then -        fcd.clear -    when :add then -        fcd.load -        fcd.scan(args) -        fcd.save -    when :show then -        fcd.load -        fcd.show -    when :find then -        fcd.load -        fcd.find(args) -        fcd.choose(args) -        fcd.remember -    when :stub -        fcd.check -end diff --git a/scripts/context/stubs/mswin/mtxrun.lua b/scripts/context/stubs/mswin/mtxrun.lua index 108f2a8a1..cc29845b5 100644 --- a/scripts/context/stubs/mswin/mtxrun.lua +++ b/scripts/context/stubs/mswin/mtxrun.lua @@ -1341,12 +1341,16 @@ function lpeg.split(separator,str)  end  function string.split(str,separator) -    local c = cache[separator] -    if not c then -        c = tsplitat(separator) -        cache[separator] = c +    if separator then +        local c = cache[separator] +        if not c then +            c = tsplitat(separator) +            cache[separator] = c +        end +        return match(c,str) +    else +        return { str }      end -    return match(c,str)  end  local spacing  = patterns.spacer^0 * newline -- sort of strip @@ -1851,14 +1855,14 @@ else      io.fileseparator, io.pathseparator = "/" , ":"  end -function io.loaddata(filename,textmode) +function io.loaddata(filename,textmode) -- return nil if empty      local f = io.open(filename,(textmode and 'r') or 'rb')      if f then          local data = f:read('*all')          f:close() -        return data -    else -        return nil +        if #data > 0 then +            return data +        end      end  end @@ -1880,6 +1884,45 @@ function io.savedata(filename,data,joiner)      end  end +function io.loadlines(filename,n) -- return nil if empty +    local f = io.open(filename,'r') +    if f then +        if n then +            local lines = { } +            for i=1,n do +                local line = f:read("*lines") +                if line then +                    lines[#lines+1] = line +                else +                    break +                end +            end +            f:close() +            lines = concat(lines,"\n") +            if #lines > 0 then +                return lines +            end +        else +            local line = f:read("*line") or "" +            assert(f:close()) +            if #line > 0 then +                return line +            end +        end +    end +end + +function io.loadchunk(filename,n) +    local f = io.open(filename,'rb') +    if f then +        local data = f:read(n or 1024) +        f:close() +        if #data > 0 then +            return data +        end +    end +end +  function io.exists(filename)      local f = io.open(filename)      if f == nil then @@ -3081,15 +3124,30 @@ 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 -function file.needs_updating(oldname,newname,threshold) -- size modification access change -    local oldtime = lfs.attributes(oldname, modification) -    local newtime = lfs.attributes(newname, modification) -    if newtime >= oldtime then -        return false -    elseif oldtime - newtime < (threshold or 1) then -        return false +function file.needsupdating(oldname,newname,threshold) -- size modification access change +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime then +        local newtime = lfs.attributes(newname,"modification") +        if not newtime then +            return true -- no new file, so no updating needed +        elseif newtime >= oldtime then +            return false -- new file definitely needs updating +        elseif oldtime - newtime < (threshold or 1) then +            return false -- new file is probably still okay +        else +            return true -- new file has to be updated +        end      else -        return true +        return false -- no old file, so no updating needed +    end +end + +file.needs_updating = file.needsupdating + +function file.syncmtimes(oldname,newname) +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime and lfs.isfile(newname) then +        lfs.touch(newname,oldtime,oldtime)      end  end @@ -3111,7 +3169,7 @@ function file.loadchecksum(name)      return nil  end -function file.savechecksum(name, checksum) +function file.savechecksum(name,checksum)      if not checksum then checksum = file.checksum(name) end      if checksum then          io.savedata(name .. ".md5",checksum) @@ -5586,7 +5644,7 @@ function setters.show(t)              local value, default, modules = functions.value, functions.default, #functions              value   = value   == nil and "unset" or tostring(value)              default = default == nil and "unset" or tostring(default) -            t.report("%-30s   modules: %2i   default: %6s   value: %6s",name,modules,default,value) +            t.report("%-50s   modules: %2i   default: %6s   value: %6s",name,modules,default,value)          end      end      t.report() @@ -5678,17 +5736,31 @@ end)  -- experiment -local flags = environment and environment.engineflags +if environment then -if flags then -    if trackers and flags.trackers then -        setters.initialize("flags","trackers", settings_to_hash(flags.trackers)) -     -- t_enable(flags.trackers) -    end -    if directives and flags.directives then -        setters.initialize("flags","directives", settings_to_hash(flags.directives)) -     -- d_enable(flags.directives) +    -- The engineflags are known earlier than environment.arguments but maybe we +    -- need to handle them both as the later are parsed differently. The c: prefix +    -- is used by mtx-context to isolate the flags from those that concern luatex. + +    local engineflags = environment.engineflags + +    if engineflags then +        if trackers then +            local list = engineflags["c:trackers"] or engineflags["trackers"] +            if type(list) == "string" then +                setters.initialize("flags","trackers",settings_to_hash(list)) +             -- t_enable(list) +            end +        end +        if directives then +            local list = engineflags["c:directives"] or engineflags["directives"] +            if type(list) == "string" then +                setters.initialize("flags","directives", settings_to_hash(list)) +             -- d_enable(list) +            end +        end      end +  end  -- here @@ -6619,6 +6691,8 @@ local mt = {  setmetatable(environment,mt) +-- context specific arguments (in order not to confuse the engine) +  function environment.initializearguments(arg)      local arguments, files = { }, { }      environment.arguments, environment.files, environment.sortedflags = arguments, files, nil @@ -6627,10 +6701,12 @@ function environment.initializearguments(arg)          if index > 0 then              local flag, value = match(argument,"^%-+(.-)=(.-)$")              if flag then +                flag = gsub(flag,"^c:","")                  arguments[flag] = unquoted(value or "")              else                  flag = match(argument,"^%-+(.+)")                  if flag then +                    flag = gsub(flag,"^c:","")                      arguments[flag] = true                  else                      files[#files+1] = argument @@ -6650,7 +6726,7 @@ end  -- tricky: too many hits when we support partials unless we add  -- a registration of arguments so from now on we have 'partial' -function environment.argument(name,partial) +function environment.getargument(name,partial)      local arguments, sortedflags = environment.arguments, environment.sortedflags      if arguments[name] then          return arguments[name] @@ -6673,6 +6749,8 @@ function environment.argument(name,partial)      return nil  end +environment.argument = environment.getargument +  function environment.splitarguments(separator) -- rather special, cut-off before separator      local done, before, after = false, { }, { }      local originalarguments = environment.originalarguments diff --git a/scripts/context/stubs/unix/mtxrun b/scripts/context/stubs/unix/mtxrun index 108f2a8a1..cc29845b5 100644 --- a/scripts/context/stubs/unix/mtxrun +++ b/scripts/context/stubs/unix/mtxrun @@ -1341,12 +1341,16 @@ function lpeg.split(separator,str)  end  function string.split(str,separator) -    local c = cache[separator] -    if not c then -        c = tsplitat(separator) -        cache[separator] = c +    if separator then +        local c = cache[separator] +        if not c then +            c = tsplitat(separator) +            cache[separator] = c +        end +        return match(c,str) +    else +        return { str }      end -    return match(c,str)  end  local spacing  = patterns.spacer^0 * newline -- sort of strip @@ -1851,14 +1855,14 @@ else      io.fileseparator, io.pathseparator = "/" , ":"  end -function io.loaddata(filename,textmode) +function io.loaddata(filename,textmode) -- return nil if empty      local f = io.open(filename,(textmode and 'r') or 'rb')      if f then          local data = f:read('*all')          f:close() -        return data -    else -        return nil +        if #data > 0 then +            return data +        end      end  end @@ -1880,6 +1884,45 @@ function io.savedata(filename,data,joiner)      end  end +function io.loadlines(filename,n) -- return nil if empty +    local f = io.open(filename,'r') +    if f then +        if n then +            local lines = { } +            for i=1,n do +                local line = f:read("*lines") +                if line then +                    lines[#lines+1] = line +                else +                    break +                end +            end +            f:close() +            lines = concat(lines,"\n") +            if #lines > 0 then +                return lines +            end +        else +            local line = f:read("*line") or "" +            assert(f:close()) +            if #line > 0 then +                return line +            end +        end +    end +end + +function io.loadchunk(filename,n) +    local f = io.open(filename,'rb') +    if f then +        local data = f:read(n or 1024) +        f:close() +        if #data > 0 then +            return data +        end +    end +end +  function io.exists(filename)      local f = io.open(filename)      if f == nil then @@ -3081,15 +3124,30 @@ 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 -function file.needs_updating(oldname,newname,threshold) -- size modification access change -    local oldtime = lfs.attributes(oldname, modification) -    local newtime = lfs.attributes(newname, modification) -    if newtime >= oldtime then -        return false -    elseif oldtime - newtime < (threshold or 1) then -        return false +function file.needsupdating(oldname,newname,threshold) -- size modification access change +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime then +        local newtime = lfs.attributes(newname,"modification") +        if not newtime then +            return true -- no new file, so no updating needed +        elseif newtime >= oldtime then +            return false -- new file definitely needs updating +        elseif oldtime - newtime < (threshold or 1) then +            return false -- new file is probably still okay +        else +            return true -- new file has to be updated +        end      else -        return true +        return false -- no old file, so no updating needed +    end +end + +file.needs_updating = file.needsupdating + +function file.syncmtimes(oldname,newname) +    local oldtime = lfs.attributes(oldname,"modification") +    if oldtime and lfs.isfile(newname) then +        lfs.touch(newname,oldtime,oldtime)      end  end @@ -3111,7 +3169,7 @@ function file.loadchecksum(name)      return nil  end -function file.savechecksum(name, checksum) +function file.savechecksum(name,checksum)      if not checksum then checksum = file.checksum(name) end      if checksum then          io.savedata(name .. ".md5",checksum) @@ -5586,7 +5644,7 @@ function setters.show(t)              local value, default, modules = functions.value, functions.default, #functions              value   = value   == nil and "unset" or tostring(value)              default = default == nil and "unset" or tostring(default) -            t.report("%-30s   modules: %2i   default: %6s   value: %6s",name,modules,default,value) +            t.report("%-50s   modules: %2i   default: %6s   value: %6s",name,modules,default,value)          end      end      t.report() @@ -5678,17 +5736,31 @@ end)  -- experiment -local flags = environment and environment.engineflags +if environment then -if flags then -    if trackers and flags.trackers then -        setters.initialize("flags","trackers", settings_to_hash(flags.trackers)) -     -- t_enable(flags.trackers) -    end -    if directives and flags.directives then -        setters.initialize("flags","directives", settings_to_hash(flags.directives)) -     -- d_enable(flags.directives) +    -- The engineflags are known earlier than environment.arguments but maybe we +    -- need to handle them both as the later are parsed differently. The c: prefix +    -- is used by mtx-context to isolate the flags from those that concern luatex. + +    local engineflags = environment.engineflags + +    if engineflags then +        if trackers then +            local list = engineflags["c:trackers"] or engineflags["trackers"] +            if type(list) == "string" then +                setters.initialize("flags","trackers",settings_to_hash(list)) +             -- t_enable(list) +            end +        end +        if directives then +            local list = engineflags["c:directives"] or engineflags["directives"] +            if type(list) == "string" then +                setters.initialize("flags","directives", settings_to_hash(list)) +             -- d_enable(list) +            end +        end      end +  end  -- here @@ -6619,6 +6691,8 @@ local mt = {  setmetatable(environment,mt) +-- context specific arguments (in order not to confuse the engine) +  function environment.initializearguments(arg)      local arguments, files = { }, { }      environment.arguments, environment.files, environment.sortedflags = arguments, files, nil @@ -6627,10 +6701,12 @@ function environment.initializearguments(arg)          if index > 0 then              local flag, value = match(argument,"^%-+(.-)=(.-)$")              if flag then +                flag = gsub(flag,"^c:","")                  arguments[flag] = unquoted(value or "")              else                  flag = match(argument,"^%-+(.+)")                  if flag then +                    flag = gsub(flag,"^c:","")                      arguments[flag] = true                  else                      files[#files+1] = argument @@ -6650,7 +6726,7 @@ end  -- tricky: too many hits when we support partials unless we add  -- a registration of arguments so from now on we have 'partial' -function environment.argument(name,partial) +function environment.getargument(name,partial)      local arguments, sortedflags = environment.arguments, environment.sortedflags      if arguments[name] then          return arguments[name] @@ -6673,6 +6749,8 @@ function environment.argument(name,partial)      return nil  end +environment.argument = environment.getargument +  function environment.splitarguments(separator) -- rather special, cut-off before separator      local done, before, after = false, { }, { }      local originalarguments = environment.originalarguments | 
