if not modules then modules = { } end modules ['mtx-context'] = { version = 1.001, comment = "companion to mtxrun.lua", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } scripts = scripts or { } scripts.context = scripts.context or { } -- a demo cld file: -- -- context.starttext() -- context.chapter("Hello There") -- context.readfile("tufte","","not found") -- context.stoptext() -- l-file / todo function file.needsupdate(oldfile,newfile) return true end function file.syncmtimes(oldfile,newfile) end -- l-io function io.copydata(fromfile,tofile) io.savedata(tofile,io.loaddata(fromfile) or "") end -- ctx ctxrunner = { } do 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.expand_path(str) end return str:gsub("\\","/") end 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 function ctxrunner.reflag(flags) local t = { } for _, flag in pairs(flags) do local key, value = flag:match("^(.-)=(.+)$") 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 function ctxrunner.new() return { ctxname = "", jobname = "", xmldata = nil, suffix = "prep", locations = { '..', '../..' }, variables = { }, messages = { }, environments = { }, modules = { }, filters = { }, flags = { }, modes = { }, prepfiles = { }, paths = { }, } 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 logs.simple("invalid ctl name: %s",ctlname or "?") return end end if table.is_empty(ctxdata.prepfiles) then logs.simple("nothing prepared, no ctl file saved") os.remove(ctlname) else logs.simple("saving logdata in: %s",ctlname) f = io.open(ctlname,'w') if f then f:write("\n\n") f:write(string.format("\n",yn(ctxdata.runlocal))) --~ for name, value in pairs(ctxdata.prepfiles) do for _, name in ipairs(table.sortedkeys(ctxdata.prepfiles)) do f:write(string.format("\t%s\n",yn(ctxdata.prepfiles[name]),name)) end f:write("\n") f:close() end 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.serialize(ctxdata.xmldata)) end function ctxrunner.manipulate(ctxdata,ctxname,defaultname) if not ctxdata.jobname or ctxdata.jobname == "" then return end ctxdata.ctxname = ctxname or file.removesuffix(ctxdata.jobname) or "" if ctxdata.ctxname == "" then return end ctxdata.jobname = file.addsuffix(ctxdata.jobname,'tex') ctxdata.ctxname = file.addsuffix(ctxdata.ctxname,'ctx') logs.simple("jobname: %s",ctxdata.jobname) logs.simple("ctxname: %s",ctxdata.ctxname) -- mtxrun should resolve kpse: and file: local usedname = ctxdata.ctxname local found = lfs.isfile(usedname) if not found then for _, path in pairs(ctxdata.locations) do local fullname = file.join(path,ctxdata.ctxname) if lfs.isfile(fullname) then usedname, found = fullname, true break end end end if not found and defaultname and defaultname ~= "" and lfs.isfile(defaultname) then usedname, found = defaultname, true end if not found then return end ctxdata.xmldata = xml.load(usedname) if not ctxdata.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 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) ctxdata.flags = ctxrunner.reflag(ctxdata.flags) for _, message in ipairs(ctxdata.messages) do logs.simple("ctx comment: %s", xml.tostring(message)) end xml.each(ctxdata.xmldata,"ctx:value[@name='job']", function(ek,e,k) e[k] = ctxdata.variables['job'] or "" end) local commands = { } xml.each(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:processors/ctx:processor", function(r,d,k) local ek = d[k] commands[ek.at and ek.at['name'] or "unknown"] = ek 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 ipairs(xml.filters.elements(ctxdata.xmldata,"/ctx:job/ctx:preprocess/ctx:files")) do for _, pattern in ipairs(xml.filters.elements(files,"ctx:file")) do preprocessor = pattern.at['processor'] or "" if preprocessor ~= "" then ctxdata.variables['old'] = ctxdata.jobname xml.each(ctxdata.xmldata,"ctx:value", function(r,d,k) 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 for _, p in ipairs(ctxdata.paths) do 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 _, oldfile in ipairs(oldfiles) do 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) for _, pp in ipairs(preprocessor:split(',')) do 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 xml.each(command,"ctx:old", function(r,d,k) d[k] = ctxrunner.substitute(oldfile) end) xml.each(command,"ctx:new", function(r,d,k) d[k] = ctxrunner.substitute(newfile) end) -- message: preprocessing #{oldfile} into #{newfile} using #{pp} ctxdata.variables['old'] = oldfile ctxdata.variables['new'] = newfile xml.each(command,"ctx:value", function(r,d,k) 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.text(command) command = ctxrunner.justtext(command) -- command is still xml element here logs.simple("command: %s",command) local result = os.spawn(command) or 0 if result > 0 then logs.simple("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 logs.simple("error, check target location of new file: %s", newfile) ctxdata.prepfiles[oldfile] = false end else logs.simple("old file needs no preprocessing") ctxdata.prepfiles[oldfile] = lfs.isfile(newfile) end end end end end end ctxrunner.savelog(ctxdata) end end -- rest scripts.context.multipass = { -- suffixes = { ".tuo", ".tuc" }, suffixes = { ".tuc" }, nofruns = 8, } function scripts.context.multipass.hashfiles(jobname) local hash = { } for _, suffix in ipairs(scripts.context.multipass.suffixes) do local full = jobname .. suffix hash[full] = md5.hex(io.loaddata(full) or "unknown") end return hash end function scripts.context.multipass.changed(oldhash, newhash) for k,v in pairs(oldhash) do if v ~= newhash[k] then return true end end return false end scripts.context.backends = { pdftex = 'pdftex', luatex = 'pdftex', pdf = 'pdftex', dvi = 'dvipdfmx', dvips = 'dvips' } function scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,currentrun,finalrun) -- take jobname from ctx jobname = file.removesuffix(jobname) local f = io.open(jobname..".top","w") if f then local function someflag(flag) return (ctxdata and ctxdata.flags[flag]) or environment.argument(flag) end local function setvalue(flag,format,hash,default) local a = someflag(flag) or default if a and a ~= "" then if hash then if hash[a] then f:write(format:format(a),"\n") end else f:write(format:format(a),"\n") end end end local function setvalues(flag,format,plural) if type(flag) == "table" then for k, v in pairs(flag) do f:write(format:format(v),"\n") end else local a = someflag(flag) or (plural and someflag(flag.."s")) if a and a ~= "" then for v in a:gmatch("%s*([^,]+)") do f:write(format:format(v),"\n") end end end end local function setfixed(flag,format,...) if someflag(flag) then f:write(format:format(...),"\n") end end local function setalways(format,...) f:write(format:format(...),"\n") end -- setalways("%% runtime options files (command line driven)") -- setalways("\\unprotect") -- setalways("%% special commands, mostly for the ctx development team") -- if environment.argument("dumpdelta") then setalways("\\tracersdumpdelta") elseif environment.argument("dumphash") then setalways("\\tracersdumphash") end setalways("%% feedback and basic job control") if type(environment.argument("track")) == "string" then setvalue ("track" , "\\enabletrackers[%s]") end setfixed ("timing" , "\\usemodule[timing]") setfixed ("batchmode" , "\\batchmode") setfixed ("nonstopmode" , "\\nonstopmode") setfixed ("tracefiles" , "\\tracefilestrue") setfixed ("nostats" , "\\nomkivstatistics") setfixed ("paranoid" , "\\def\\maxreadlevel{1}") -- setalways("%% handy for special styles") -- setalways("\\startluacode") setalways("document = document or { }") setalways(table.serialize(environment.arguments, "document.arguments")) setalways(table.serialize(environment.files, "document.files")) setalways("\\stopluacode") -- setalways("%% process info") -- setalways( "\\setupsystem[\\c!n=%s,\\c!m=%s]", kindofrun or 0, currentrun or 0) setalways( "\\setupsystem[\\c!type=%s]",os.platform) setvalue ("inputfile" , "\\setupsystem[inputfile=%s]") setvalue ("result" , "\\setupsystem[file=%s]") setvalues("path" , "\\usepath[%s]") setvalue ("setuppath" , "\\setupsystem[\\c!directory={%s}]") setvalue ("randomseed" , "\\setupsystem[\\c!random=%s]") setvalue ("arguments" , "\\setupenv[%s]") setalways("%% modes") setvalues("modefile" , "\\readlocfile{%s}{}{}") setvalues("mode" , "\\enablemode[%s]", true) if ctxdata then setvalues(ctxdata.modes, "\\enablemode[%s]") end -- setalways("%% options (not that important)") -- setalways("\\startsetups *runtime:options") setvalue ('output' , "\\setupoutput[%s]", scripts.context.backends, 'pdftex') setfixed ("color" , "\\setupcolors[\\c!state=\\v!start]") setvalue ("separation" , "\\setupcolors[\\c!split=%s]") setfixed ("noarrange" , "\\setuparranging[\\v!disable]") if environment.argument('arrange') and not finalrun then setalways( "\\setuparranging[\\v!disable]") end setalways("\\stopsetups") -- setalways("%% styles and modules") -- setalways("\\startsetups *runtime:modules") setvalues("filter" , "\\useXMLfilter[%s]", true) setvalues("usemodule" , "\\usemodule[%s]", true) setvalues("environment" , "\\environment %s ", true) if ctxdata then setvalues(ctxdata.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) -- io.savedata(jobname..".tuc",io.loaddata(jobname..".tua") or "") local tuaname, tucname = jobname..".tua", jobname..".tuc" if lfs.isfile(tuaname) then os.remove(tucname) os.rename(tuaname,tucname) end end -- obsolete: -- -- function scripts.context.multipass.copytuifile(jobname) -- local tuiname, tuoname = jobname .. ".tui", jobname .. ".tuo" -- if lfs.isfile(tuiname) then -- local f, g = io.open(tuiname), io.open(tuoname,'w') -- if f and g then -- g:write("% traditional utility file, only commands written by mtxrun/context\n%\n") -- for line in f:lines() do -- if line:find("^c ") then -- g:write((line:gsub("^c ","")),"%\n") -- end -- end -- g:write("\\endinput\n") -- f:close() -- g:close() -- end -- else -- -- os.remove(tuoname) -- end -- end scripts.context.cldsuffixes = table.tohash { "cld", } scripts.context.xmlsuffixes = table.tohash { "xml", } scripts.context.luasuffixes = table.tohash { "lua", } scripts.context.beforesuffixes = { "tuo", "tuc" } scripts.context.aftersuffixes = { "pdf", "tuo", "tuc", "log" } scripts.context.interfaces = { en = "cont-en", uk = "cont-uk", de = "cont-de", fr = "cont-fr", nl = "cont-nl", cz = "cont-cz", it = "cont-it", ro = "cont-ro", pe = "cont-pe", -- for taco and me -- xp = "cont-xp", } scripts.context.defaultformats = { "cont-en", "cont-nl", -- "cont-xp", "mptopdf", -- "metatex", "metafun", "plain" } local function analyze(filename) local f = io.open(file.addsuffix(filename,"tex")) if f then local t = { } local line = f:read("*line") or "" local preamble = line:match("[\254\255]*%%%s+(.+)$") -- there can be an utf bomb in front if preamble then for key, value in preamble:gmatch("(%S+)=(%S+)") do t[key] = value end t.type = "tex" elseif line:find("^&1', file.replacesuffix(name,"pdf"))) end function scripts.context.closepdf(name) os.spawn(string.format('pdfclose --file "%s" 2>&1', file.replacesuffix(name,"pdf"))) end --~ function scripts.context.openpdf(name) --~ -- somehow two instances start up, one with a funny filename --~ os.spawn(string.format("\"c:/program files/kde/bin/okular.exe\" --unique %s",file.replacesuffix(name,"pdf"))) --~ end --~ function scripts.context.closepdf(name) --~ -- --~ end function scripts.context.run(ctxdata,filename) -- filename overloads environment.files local files = (filename and { filename }) or environment.files if ctxdata then -- todo: interface for k,v in pairs(ctxdata.flags) do environment.setargument(k,v) end end if #files > 0 then -- local interface = environment.argument("interface") -- todo: environment.argument("interface","en") interface = (type(interface) == "string" and interface) or "en" -- local formatname = scripts.context.interfaces[interface] or "cont-en" local formatfile, scriptfile = resolvers.locate_format(formatname) -- this catches the command line if not formatfile or not scriptfile then logs.simple("warning: no format found, forcing remake (commandline driven)") scripts.context.generate() scripts.context.make(formatname) formatfile, scriptfile = resolvers.locate_format(formatname) end -- if formatfile and scriptfile then for _, filename in ipairs(files) do local basename, pathname = file.basename(filename), file.dirname(filename) local jobname = file.removesuffix(basename) if pathname == "" then filename = "./" .. filename end -- look at the first line local a = analyze(filename) if a and (a.engine == 'pdftex' or a.engine == 'xetex' or environment.argument("pdftex") or environment.argument("xetex")) then local texexec = resolvers.find_file("texexec.rb") or "" if texexec ~= "" then os.setenv("RUBYOPT","") local command = string.format("ruby %s %s",texexec,environment.reconstruct_commandline(environment.arguments_after)) os.exec(command) end else if a and a.interface and a.interface ~= interface then formatname = scripts.context.interfaces[a.interface] or formatname formatfile, scriptfile = resolvers.locate_format(formatname) end -- this catches the command line if not formatfile or not scriptfile then logs.simple("warning: no format found, forcing remake (source driven)") scripts.context.generate() scripts.context.make(formatname) formatfile, scriptfile = resolvers.locate_format(formatname) end if formatfile and scriptfile then -- we default to mkiv xml ! local suffix = file.extname(filename) or "?" if scripts.context.xmlsuffixes[suffix] or environment.argument("forcexml") then if environment.argument("mkii") then filename = makestub("\\processXMLfilegrouped{%s}",filename) else filename = makestub("\\xmlprocess{\\xmldocument}{%s}{}",filename) end elseif scripts.context.cldsuffixes[suffix] or environment.argument("forcecld") then filename = makestub("\\ctxlua{context.runfile('%s')}",filename) elseif scripts.context.luasuffixes[suffix] or environment.argument("forcelua") then filename = makestub("\\ctxlua{dofile('%s')}",filename) end -- -- todo: also other stubs -- local suffix, resultname = environment.argument("suffix"), environment.argument("result") if type(suffix) == "string" then resultname = file.removesuffix(jobname) .. suffix end local oldbase, newbase = "", "" if type(resultname) == "string" then oldbase = file.removesuffix(jobname) newbase = file.removesuffix(resultname) if oldbase ~= newbase then for _, suffix in pairs(scripts.context.beforesuffixes) do local oldname = file.addsuffix(oldbase,suffix) local newname = file.addsuffix(newbase,suffix) local tmpname = "keep-"..oldname os.remove(tmpname) os.rename(oldname,tmpname) os.remove(oldname) os.rename(newname,oldname) end else resultname = nil end else resultname = nil end -- if environment.argument("autopdf") then scripts.context.closepdf(filename) if resultname then scripts.context.closepdf(resultname) end end -- local okay = statistics.check_fmt_status(formatfile) if okay ~= true then logs.simple("warning: %s, forcing remake",tostring(okay)) scripts.context.generate() scripts.context.make(formatname) end -- local flags = { } if environment.argument("batchmode") then flags[#flags+1] = "--interaction=batchmode" end flags[#flags+1] = "--fmt=" .. string.quote(formatfile) flags[#flags+1] = "--lua=" .. string.quote(scriptfile) flags[#flags+1] = "--backend=pdf" local command = string.format("luatex %s %s", table.concat(flags," "), string.quote(filename)) local oldhash, newhash = scripts.context.multipass.hashfiles(jobname), { } local once = environment.argument("once") local maxnofruns = (once and 1) or scripts.context.multipass.nofruns local arrange = environment.argument("arrange") for i=1,maxnofruns do -- 1:first run, 2:successive run, 3:once, 4:last of maxruns local kindofrun = (once and 3) or (i==1 and 1) or (i==maxnofruns and 4) or 2 scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,false) -- kindofrun, currentrun, final logs.simple("run %s: %s",i,command) local returncode, errorstring = os.spawn(command) --~ if returncode == 3 then --~ scripts.context.generate() --~ scripts.context.make(formatname) --~ returncode, errorstring = os.spawn(command) --~ if returncode == 3 then --~ logs.simple("fatal error, return code 3, message: %s",errorstring or "?") --~ os.exit(1) --~ end --~ end if not returncode then logs.simple("fatal error, no return code, message: %s",errorstring or "?") os.exit(1) break elseif returncode > 0 then logs.simple("fatal error, return code: %s",returncode or "?") os.exit(returncode) break else scripts.context.multipass.copyluafile(jobname) -- scripts.context.multipass.copytuifile(jobname) newhash = scripts.context.multipass.hashfiles(jobname) if scripts.context.multipass.changed(oldhash,newhash) then oldhash = newhash else break end end end -- if arrange then local kindofrun = 3 scripts.context.multipass.makeoptionfile(jobname,ctxdata,kindofrun,i,true) -- kindofrun, currentrun, final logs.simple("arrange run: %s",command) local returncode, errorstring = os.spawn(command) if not returncode then logs.simple("fatal error, no return code, message: %s",errorstring or "?") os.exit(1) elseif returncode > 0 then logs.simple("fatal error, return code: %s",returncode or "?") os.exit(returncode) end end -- if environment.argument("purge") then scripts.context.purge_job(filename) elseif environment.argument("purgeall") then scripts.context.purge_job(filename,true) end -- os.remove(jobname..".top") -- if resultname then for _, suffix in pairs(scripts.context.aftersuffixes) do local oldname = file.addsuffix(oldbase,suffix) local newname = file.addsuffix(newbase,suffix) local tmpname = "keep-"..oldname os.remove(newname) os.rename(oldname,newname) os.rename(tmpname,oldname) end logs.simple("result renamed to: %s",newbase) end -- if environment.argument("purge") then scripts.context.purge_job(resultname) elseif environment.argument("purgeall") then scripts.context.purge_job(resultname,true) end -- if environment.argument("autopdf") then scripts.context.openpdf(resultname or filename) end -- if environment.argument("timing") then logs.line() logs.simple("you can process (timing) statistics with:",jobname) logs.line() logs.simple("context --extra=timing '%s'",jobname) logs.simple("mtxrun --script timing --xhtml [--launch --remove] '%s'",jobname) logs.line() end else if formatname then logs.simple("error, no format found with name: %s, skipping",formatname) else logs.simple("error, no format found (provide formatname or interface)") end break end end end else if formatname then logs.simple("error, no format found with name: %s, aborting",formatname) else logs.simple("error, no format found (provide formatname or interface)") end end end end function scripts.context.pipe() -- context --pipe -- context --pipe --purge --dummyfile=whatever.tmp local interface = environment.argument("interface") interface = (type(interface) == "string" and interface) or "en" local formatname = scripts.context.interfaces[interface] or "cont-en" local formatfile, scriptfile = resolvers.locate_format(formatname) if not formatfile or not scriptfile then logs.simple("warning: no format found, forcing remake (commandline driven)") scripts.context.generate() scripts.context.make(formatname) formatfile, scriptfile = resolvers.locate_format(formatname) end if formatfile and scriptfile then local okay = statistics.check_fmt_status(formatfile) if okay ~= true then logs.simple("warning: %s, forcing remake",tostring(okay)) scripts.context.generate() scripts.context.make(formatname) end local flags = { "--interaction=scrollmode", "--fmt=" .. string.quote(formatfile), "--lua=" .. string.quote(scriptfile), "--backend=pdf", } local filename = environment.argument("dummyfile") or "" if filename == "" then filename = "\\relax" logs.simple("entering scrollmode, end job with \\end") else filename = file.addsuffix(filename,"tmp") io.savedata(filename,"\\relax") scripts.context.multipass.makeoptionfile(filename,{ flags = flags },3,1,false) -- kindofrun, currentrun, final logs.simple("entering scrollmode using '%s' with optionfile, end job with \\end",filename) end local command = string.format("luatex %s %s", table.concat(flags," "), string.quote(filename)) os.spawn(command) if environment.argument("purge") then scripts.context.purge_job(filename) elseif environment.argument("purgeall") then scripts.context.purge_job(filename,true) os.remove(filename) end else if formatname then logs.simple("error, no format found with name: %s, aborting",formatname) else logs.simple("error, no format found (provide formatname or interface)") end end end function scripts.context.make(name) local runners = { "luatools --make --compile ", (environment.argument("pdftex") and "mtxrun texexec.rb --make --pdftex ") or false, (environment.argument("xetex") and "mtxrun texexec.rb --make --xetex " ) or false, } local list = (name and { name }) or (environment.files[1] and environment.files) or scripts.context.defaultformats for _, name in ipairs(list) do name = scripts.context.interfaces[name] or name for _, runner in ipairs(runners) do if runner then local command = runner .. name logs.simple("running command: %s",command) os.spawn(command) end end end end function scripts.context.generate() -- hack, should also be a shared function local command = "luatools --generate " logs.simple("running command: %s",command) os.spawn(command) end function scripts.context.ctx() local ctxdata = ctxrunner.new() ctxdata.jobname = environment.files[1] ctxrunner.manipulate(ctxdata,environment.argument("ctx")) scripts.context.run(ctxdata) end function scripts.context.autoctx() local ctxdata = nil local files = (filename and { filename }) or environment.files local firstfile = #files > 0 and files[1] if firstfile and file.extname(firstfile) == "xml" then local f = io.open(firstfile) if f then local chunk = f:read(512) or "" f:close() local ctxname = string.match(chunk,"<%?context%-directive%s+job%s+ctxfile%s+([^ ]-)%s*?>") if ctxname then ctxdata = ctxrunner.new() ctxdata.jobname = firstfile ctxrunner.manipulate(ctxdata,ctxname) end end end scripts.context.run(ctxdata) end -- todo: quite after first image local template = [[ \starttext \startMPpage %% %s input "%s" ; \stopMPpage \stoptext ]] local loaded = false function scripts.context.metapost() local filename = environment.files[1] or "" --~ local tempname = "mtx-context-metapost.tex" --~ local tempdata = string.format(template,"metafun",filename) --~ io.savedata(tempname,tempdata) --~ environment.files[1] = tempname --~ environment.setargument("result",file.removesuffix(filename)) --~ environment.setargument("once",true) --~ scripts.context.run() if not loaded then dofile(resolvers.find_file("mlib-run.lua")) loaded = true commands = commands or { } commands.writestatus = logs.report end local formatname = environment.arguments("format") or "metafun" if formatname == "" or type(format) == "boolean" then formatname = "metafun" end if environment.arguments("svg") then metapost.directrun(formatname,filename,"svg") else metapost.directrun(formatname,filename,"mps") end end function scripts.context.version() local name = resolvers.find_file("context.tex") if name ~= "" then logs.simple("main context file: %s",name) local data = io.loaddata(name) if data then local version = data:match("\\edef\\contextversion{(.-)}") if version then logs.simple("current version: %s",version) else logs.simple("context version: unknown, no timestamp found") end else logs.simple("context version: unknown, load error") end else logs.simple("main context file: unknown, 'context.tex' not found") end end local generic_files = { "texexec.tex", "texexec.tui", "texexec.tuo", "texexec.tuc", "texexec.tua", "texexec.ps", "texexec.pdf", "texexec.dvi", "cont-opt.tex", "cont-opt.bak" } local obsolete_results = { "dvi", } local temporary_runfiles = { "tui", "tua", "tup", "ted", "tes", "top", "log", "tmp", "run", "bck", "rlg", "mpt", "mpx", "mpd", "mpo", "mpb", "ctl", "synctex.gz", "pgf" } local persistent_runfiles = { "tuo", "tub", "top", "tuc" } local function purge_file(dfile,cfile) if cfile and lfs.isfile(cfile) then if os.remove(dfile) then return file.basename(dfile) end else if os.remove(dfile) then return file.basename(dfile) end end end function scripts.context.purge_job(jobname,all) jobname = file.basename(jobname) local filebase = file.removesuffix(jobname) local deleted = { } for _, suffix in ipairs(obsolete_results) do deleted[#deleted+1] = purge_file(filebase.."."..suffix,filebase..".pdf") end for _, suffix in ipairs(temporary_runfiles) do deleted[#deleted+1] = purge_file(filebase.."."..suffix) end if all then for _, suffix in ipairs(persistent_runfiles) do deleted[#deleted+1] = purge_file(filebase.."."..suffix) end end if #deleted > 0 then logs.simple("purged files: %s", table.join(deleted,", ")) end end function scripts.context.purge(all) local all = all or environment.argument("all") local pattern = environment.argument("pattern") or "*.*" local files = dir.glob(pattern) local obsolete = table.tohash(obsolete_results) local temporary = table.tohash(temporary_runfiles) local persistent = table.tohash(persistent_runfiles) local generic = table.tohash(generic_files) local deleted = { } for _, name in ipairs(files) do local suffix = file.extname(name) local basename = file.basename(name) if obsolete[suffix] or temporary[suffix] or persistent[suffix] or generic[basename] then deleted[#deleted+1] = purge_file(name) end end if #deleted > 0 then logs.simple("purged files: %s", table.join(deleted,", ")) end end --~ purge_for_files("test",true) --~ purge_all_files() local function touch(name,pattern) local name = resolvers.find_file(name) local olddata = io.loaddata(name) if olddata then local oldversion, newversion = "", os.date("%Y.%m.%d %H:%M") local newdata, ok = olddata:gsub(pattern,function(pre,mid,post) oldversion = mid return pre .. newversion .. post end) if ok > 0 then local backup = file.replacesuffix(name,"tmp") os.remove(backup) os.rename(name,backup) io.savedata(name,newdata) return true, oldversion, newversion, name else return false end end end function scripts.context.touch() if environment.argument("expert") then local done, oldversion, newversion, foundname = touch("context.tex", "(\\edef\\contextversion{)(.-)(})") if done then logs.simple("old version : %s", oldversion) logs.simple("new version : %s", newversion) logs.simple("touched file: %s", foundname) local ok, _, _, foundname = touch("cont-new.tex", "(\\newcontextversion{)(.-)(})") if ok then logs.simple("touched file: %s", foundname) end local ok, _, _, foundname = touch("cont-xp.tex", "(\\edef\\contextversion{)(.-)(})") if ok then logs.simple("touched file: %s", foundname) end end end end -- extras function scripts.context.extras(pattern) local found = resolvers.find_file("context.tex") if found == "" then logs.simple("unknown extra: %s", extra) else pattern = file.join(dir.expand_name(file.dirname(found)),string.format("mtx-context-%s.tex",pattern or "*")) local list = dir.glob(pattern) if not extra or extra == "" then logs.extendbanner("extras") else logs.extendbanner(extra) end for k,v in ipairs(list) do local data = io.loaddata(v) or "" data = string.match(data,"begin help(.-)end help") if data then local h = { string.format("extra: %s (%s)",string.gsub(v,"^.*mtx%-context%-(.-)%.tex$","%1"),v) } for s in string.gmatch(data,"%% *(.-)[\n\r]") do h[#h+1] = s end logs.help(table.concat(h,"\n"),"nomoreinfo") end end end end function scripts.context.extra() local extra = environment.argument("extra") if type(extra) == "string" then if environment.argument("help") then scripts.context.extras(extra) else local fullextra = extra if not string.find(fullextra,"mtx%-context%-") then fullextra = "mtx-context-" .. extra end local foundextra = resolvers.find_file(fullextra) if foundextra == "" then scripts.context.extras() return else logs.simple("processing extra: %s", foundextra) end environment.setargument("purgeall",true) local result = environment.setargument("result") or "" if result == "" then environment.setargument("result","context-extra") end scripts.context.run(nil,foundextra) end else scripts.context.extras() end end -- todo: we need to do a dummy run function scripts.context.track() environment.files = { "m-track" } scripts.context.multipass.nofruns = 1 scripts.context.run() -- maybe filter from log end function scripts.context.timed(action) statistics.timed(action) end local zipname = "cont-tmf.zip" local mainzip = "http://www.pragma-ade.com/context/latest/" .. zipname local validtrees = { "texmf-local", "texmf-context" } function zip.loaddata(zipfile,filename) -- should be in zip lib local f = zipfile:open(filename) if f then local data = f:read("*a") f:close() return data end return nil end function scripts.context.update() local force = environment.argument("force") local socket = require("socket") local http = require("socket.http") local basepath = resolvers.find_file("context.tex") or "" if basepath == "" then logs.simple("quiting, no 'context.tex' found") return end local basetree = basepath.match(basepath,"^(.-)tex/context/base/context.tex$") or "" if basetree == "" then logs.simple("quiting, no proper tds structure (%s)",basepath) return end local function is_okay(basetree) for _, tree in next, validtrees do local pattern = string.gsub(tree,"%-","%%-") if basetree:find(pattern) then return tree end end return false end local okay = is_okay(basetree) if not okay then logs.simple("quiting, tree '%s' is protected",okay) return else logs.simple("updating tree '%s'",okay) end if not lfs.chdir(basetree) then logs.simple("quiting, unable to change to '%s'",okay) return end logs.simple("fetching '%s'",mainzip) local latest = http.request(mainzip) if not latest then logs.simple("context tree '%s' can be updated, use --force",okay) return end io.savedata("cont-tmf.zip",latest) if false then -- variant 1 os.execute("mtxrun --script unzip cont-tmf.zip") else -- variant 2 local zipfile = zip.open(zipname) if not zipfile then logs.simple("quiting, unable to open '%s'",zipname) return end local newfile = zip.loaddata(zipfile,"tex/context/base/context.tex") if not newfile then logs.simple("quiting, unable to open '%s'","context.tex") return end local oldfile = io.loaddata(resolvers.find_file("context.tex")) or "" local function versiontonumber(what,str) local version = str:match("\\edef\\contextversion{(.-)}") or "" local year, month, day, hour, minute = str:match("\\edef\\contextversion{(%d+)%.(%d+)%.(%d+) *(%d+)%:(%d+)}") if year and minute then local time = os.time { year=year,month=month,day=day,hour=hour,minute=minute} logs.simple("%s version: %s (%s)",what,version,time) return time else logs.simple("%s version: %s (unknown)",what,version) return nil end end local oldversion = versiontonumber("old",oldfile) local newversion = versiontonumber("new",newfile) if not oldversion or not newversion then logs.simple("quiting, version cannot be determined") return elseif oldversion == newversion then logs.simple("quiting, your current version is up-to-date") return elseif oldversion > newversion then logs.simple("quiting, your current version is newer") return end for k in zipfile:files() do local filename = k.filename if filename:find("/$") then lfs.mkdir(filename) else local data = zip.loaddata(zipfile,filename) if data then if force then io.savedata(filename,data) end logs.simple(filename) end end end for _, scriptname in next, { "luatools.lua", "mtxrun.lua" } do local oldscript = resolvers.find_file(scriptname) or "" if oldscript ~= "" and is_okay(oldscript) then local newscript = "./scripts/context/lua/" .. scriptname local data = io.loaddata(newscript) or "" if data ~= "" then logs.simple("replacing script '%s' by '%s'",oldscript,newscript) if force then io.savedata(oldscript,data) end end else logs.simple("keeping script '%s'",oldscript) end end if force then os.execute("context --generate") os.execute("context --make") end end if force then logs.simple("context tree '%s' has been updated",okay) else logs.simple("context tree '%s' can been updated (use --force)",okay) end end logs.extendbanner("ConTeXt Tools 0.51",true) messages.help = [[ --run process (one or more) files (default action) --make create context formats --generate generate file database etc. --ctx=name use ctx file --version report installed context version --forcexml force xml stub (optional flag: --mkii) --forcecld force cld (context lua document) stub --autopdf close pdf file in viewer and start pdf viewer afterwards --once only one run --purge(all) purge files (--pattern=...) --result=name rename result to given name --arrange run extra arrange pass --expert expert options --interface use specified user interface ]] messages.expert = [[ expert options: --touch update context version number (remake needed afterwards, also provide --expert) --update update context from website (not to be confused with contextgarden) --profile profile job (use: mtxrun --script profile --analyse) --track show/set tracker variables --timing generate timing and statistics overview --extra=name process extra (mtx-context- in distribution) ]] messages.private = [[ private options: --dumphash dump hash table afterwards --dumpdelta dump hash table afterwards (only new entries) ]] messages.special = [[ special options: --pdftex process file with texexec using pdftex --xetex process file with texexec using xetex --pipe don't check for file and enter scroll mode (--dummyfile=whatever.tmp) ]] if environment.argument("once") then scripts.context.multipass.nofruns = 1 end if environment.argument("profile") then os.setenv("MTX_PROFILE_RUN","YES") end if environment.argument("run") then -- scripts.context.timed(scripts.context.run) scripts.context.timed(scripts.context.autoctx) elseif environment.argument("make") or environment.argument("generate") then scripts.context.timed(function() if environment.argument("generate") then scripts.context.generate() end if environment.argument("make") then scripts.context.make() end end) elseif environment.argument("ctx") then scripts.context.timed(scripts.context.ctx) elseif environment.argument("mp") or environment.argument("metapost") then scripts.context.timed(scripts.context.metapost) elseif environment.argument("version") then scripts.context.version() elseif environment.argument("touch") then scripts.context.touch() elseif environment.argument("update") then scripts.context.update() elseif environment.argument("expert") then logs.help(table.join({ messages.expert, messages.private, messages.special },"\n")) elseif environment.argument("extra") then scripts.context.extra() elseif environment.argument("help") then logs.help(messages.help) elseif environment.argument("track") and type(environment.argument("track")) == "boolean" then scripts.context.track() elseif environment.files[1] then -- scripts.context.timed(scripts.context.run) scripts.context.timed(scripts.context.autoctx) elseif environment.argument("pipe") then scripts.context.timed(scripts.context.pipe) elseif environment.argument("purge") then -- only when no filename given, supports --pattern scripts.context.purge() elseif environment.argument("purgeall") then -- only when no filename given, supports --pattern scripts.context.purge(true) else logs.help(messages.help) end if environment.argument("profile") then os.setenv("MTX_PROFILE_RUN","NO") end