mtx-context.lua /size: 80 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules['mtx-context'] = {
2    version   = 1.001,
3    comment   = "companion to mtxrun.lua",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- todo: more local functions
10-- todo: pass jobticket/ctxdata table around
11
12local type, next, tostring, tonumber = type, next, tostring, tonumber
13local format, gmatch, match, gsub, find = string.format, string.gmatch, string.match, string.gsub, string.find
14local quote, validstring, splitstring, splitlines = string.quote, string.valid, string.split, string.splitlines
15local sort, concat, insert, sortedhash, tohash = table.sort, table.concat, table.insert, table.sortedhash, table.tohash
16local settings_to_array = utilities.parsers.settings_to_array
17local appendtable = table.append
18local lpegpatterns, lpegmatch, Cs, P = lpeg.patterns, lpeg.match, lpeg.Cs, lpeg.P
19
20local getargument   = environment.getargument or environment.argument
21local setargument   = environment.setargument
22
23local filejoinname  = file.join
24local filebasename  = file.basename
25local filenameonly  = file.nameonly
26local filepathpart  = file.pathpart
27local filesuffix    = file.suffix
28local fileaddsuffix = file.addsuffix
29local filenewsuffix = file.replacesuffix
30local removesuffix  = file.removesuffix
31local validfile     = lfs.isfile
32local removefile    = os.remove
33local renamefile    = os.rename
34local formatters    = string.formatters
35
36local starttiming   = statistics.starttiming
37local stoptiming    = statistics.stoptiming
38local resettiming   = statistics.resettiming
39local elapsedtime   = statistics.elapsedtime
40
41local application = logs.application {
42    name     = "mtx-context",
43    banner   = "ConTeXt Process Management 1.06",
44 -- helpinfo = helpinfo, -- table with { category_a = text_1, category_b = text_2 } or helpstring or xml_blob
45    helpinfo = "mtx-context.xml",
46}
47
48-- local luatexflags = {
49--     ["8bit"]                        = true,  -- ignored, input is assumed to be in UTF-8 encoding
50--     ["default-translate-file"]      = true,  -- ignored, input is assumed to be in UTF-8 encoding
51--     ["translate-file"]              = true,  -- ignored, input is assumed to be in UTF-8 encoding
52--     ["etex"]                        = true,  -- ignored, the etex extensions are always active
53--     ["parse-first-line"]            = true,  -- ignored, enable parsing of the first line of the input file
54--     ["no-parse-first-line"]         = true,  -- ignored, disable parsing of the first line of the input file
55--
56--     ["credits"]                     = true,  -- display credits and exit
57--     ["debug-format"]                = true,  -- enable format debugging
58--     ["disable-write18"]             = true,  -- disable \write18{SHELL COMMAND}
59--     ["draftmode"]                   = true,  -- switch on draft mode (generates no output PDF)
60--     ["enable-write18"]              = true,  -- enable \write18{SHELL COMMAND}
61--     ["file-line-error"]             = true,  -- enable file:line:error style messages
62--     ["file-line-error-style"]       = true,  -- aliases of --file-line-error
63--     ["no-file-line-error"]          = true,  -- disable file:line:error style messages
64--     ["no-file-line-error-style"]    = true,  -- aliases of --no-file-line-error
65--     ["fmt"]                         = true,  -- load the format file FORMAT
66--     ["halt-on-error"]               = true,  -- stop processing at the first error
67--     ["help"]                        = true,  -- display help and exit
68--     ["ini"]                         = true,  -- be iniluatex, for dumping formats
69--     ["interaction"]                 = true,  -- set interaction mode (STRING=batchmode/nonstopmode/scrollmode/errorstopmode)
70--     ["jobname"]                     = true,  -- set the job name to STRING
71--     ["kpathsea-debug"]              = true,  -- set path searching debugging flags according to the bits of NUMBER
72--     ["lua"]                         = true,  -- load and execute a lua initialization script
73--     ["mktex"]                       = true,  -- enable mktexFMT generation (FMT=tex/tfm)
74--     ["no-mktex"]                    = true,  -- disable mktexFMT generation (FMT=tex/tfm)
75--     ["nosocket"]                    = true,  -- disable the lua socket library
76--     ["output-comment"]              = true,  -- use STRING for DVI file comment instead of date (no effect for PDF)
77--     ["output-directory"]            = true,  -- use existing DIR as the directory to write files in
78--     ["output-format"]               = true,  -- use FORMAT for job output; FORMAT is 'dvi' or 'pdf'
79--     ["progname"]                    = true,  -- set the program name to STRING
80--     ["recorder"]                    = true,  -- enable filename recorder
81--     ["safer"]                       = true,  -- disable easily exploitable lua commands
82--     ["shell-escape"]                = true,  -- enable \write18{SHELL COMMAND}
83--     ["no-shell-escape"]             = true,  -- disable \write18{SHELL COMMAND}
84--     ["shell-restricted"]            = true,  -- restrict \write18 to a list of commands given in texmf.cnf
85--     ["nodates"]                     = true,  -- no production dates in pdf file
86--     ["trailerid"]                   = true,  -- alternative trailer id
87--     ["synctex"]                     = true,  -- enable synctex
88--     ["version"]                     = true,  -- display version and exit
89--     ["luaonly"]                     = true,  -- run a lua file, then exit
90--     ["luaconly"]                    = true,  -- byte-compile a lua file, then exit
91--     ["jiton"]                       = false, -- not supported (makes no sense, slower)
92-- }
93
94local report = application.report
95
96scripts         = scripts         or { }
97scripts.context = scripts.context or { }
98
99-- for the moment here
100
101if jit then -- already luajittex
102    setargument("engine","luajittex")
103    setargument("jit",nil)
104elseif getargument("luatex") then -- relaunch luajittex
105    setargument("engine","luatex")
106elseif getargument("jit") or getargument("luajittex") then -- relaunch luajittex
107    -- bonus shortcut, we assume that --jit also indicates the engine
108    -- although --jit and --engine=luajittex are independent
109    setargument("engine","luajittex")
110end
111
112-- -- The way we use stubs will change in a bit in 2019 (mtxrun and context). We also normalize
113-- -- the platforms to use a similar approach to this.
114
115local engine_new = filenameonly(getargument("engine") or directives.value("system.engine"))
116local engine_old = filenameonly(environment.ownmain) or filenameonly(environment.ownbin)
117
118local function restart(engine_old,engine_new)
119    local generate  = environment.arguments.generate and (engine_new == "luatex" or engine_new == "luajittex")
120    local arguments = generate and  "--generate" or environment.reconstructcommandline()
121    local ownname   = filejoinname(filepathpart(environment.ownname),"mtxrun.lua")
122    local command   = format("%s --luaonly --socket %q %s --redirected",engine_new,ownname,arguments)
123    report(format("redirect %s -> %s: %s",engine_old,engine_new,command))
124    local result = os.execute(command)
125    os.exit(result == 0 and 0 or 1)
126end
127
128-- if getargument("redirected") then
129--     setargument("engine",engine_old) -- later on we need this
130-- elseif engine_new == engine_old then
131--     setargument("engine",engine_new) -- later on we need this
132-- elseif environment.validengines[engine_new] and engine_new ~= environment.basicengines[engine_old] then
133--     restart(engine_old,engine_new)
134-- else
135--     setargument("engine",engine_new) -- later on we need this
136-- end
137
138if environment.validengines[engine_new] and engine_new ~= environment.basicengines[engine_old] then
139    restart(engine_old,engine_new)
140end
141
142-- so far
143
144-- constants
145
146local usedfiles = {
147    nop = "cont-nop.mkiv",
148    yes = "cont-yes.mkiv",
149}
150
151local usedsuffixes = {
152    before = {
153        "tuc"
154    },
155    after = {
156        "pdf", "tuc", "log"
157    },
158    keep = {
159        "log"
160    },
161}
162
163local formatofinterface = {
164    en = "cont-en",
165    uk = "cont-uk",
166    de = "cont-de",
167    fr = "cont-fr",
168    nl = "cont-nl",
169    cs = "cont-cs",
170    it = "cont-it",
171    ro = "cont-ro",
172    pe = "cont-pe",
173}
174
175local defaultformats = {
176    "cont-en",
177 -- "cont-nl",
178}
179
180-- purging files (we should have an mkii and mkiv variants)
181
182local generic_files = {
183    "texexec.tex", "texexec.tui", "texexec.tuo",
184    "texexec.tuc", "texexec.tua",
185    "texexec.ps", "texexec.pdf", "texexec.dvi",
186    "cont-opt.tex", "cont-opt.bak"
187}
188
189local obsolete_results = {
190    "dvi",
191}
192
193local temporary_runfiles = {
194    "tui",                             -- mkii two pass file
195    "tua",                             -- mkiv obsolete
196    "tup", "ted", "tes",               -- texexec
197    "top",                             -- mkii options file
198    "log",                             -- tex log file
199    "tmp",                             -- mkii buffer file
200    "run",                             -- mkii stub
201    "bck",                             -- backup (obsolete)
202    "rlg",                             -- resource log
203    "ctl",                             --
204    "mpt", "mpx", "mpd", "mpo", "mpb", -- metafun
205    "prep",                            -- context preprocessed
206    "pgf",                             -- tikz
207    "aux", "blg",                      -- bibtex
208}
209
210local temporary_suffixes = {
211    "prep",                            -- context preprocessed
212}
213
214local synctex_runfiles = {
215    "synctex",    -- should not be created by engine
216    "synctex.gz", -- not used
217 -- "syncctx"     --
218}
219
220local persistent_runfiles = {
221    "tuo", -- mkii two pass file
222    "tub", -- mkii buffer file
223    "top", -- mkii options file
224    "tuc", -- mkiv two pass file
225    "bbl", -- bibtex
226}
227
228local special_runfiles = {
229    "%-mpgraph", "%-mprun", "%-temp%-",
230}
231
232local extra_runfiles = {
233    "^m_k_i_v_.-%.pdf$",
234    "^l_m_t_x_.-%.pdf$",
235}
236
237local function purge_file(dfile,cfile)
238    if cfile and validfile(cfile) then
239        if removefile(dfile) then
240            return filebasename(dfile)
241        end
242    elseif dfile then
243        if removefile(dfile) then
244            return filebasename(dfile)
245        end
246    end
247end
248
249-- process information
250
251local ctxrunner = { } -- namespace will go
252
253local ctx_locations = { '..', '../..' }
254
255function ctxrunner.new()
256    return {
257        ctxname   = "",
258        jobname   = "",
259        flags     = { },
260    }
261end
262
263function ctxrunner.checkfile(ctxdata,ctxname,defaultname)
264
265    if not ctxdata.jobname or ctxdata.jobname == "" or getargument("noctx") then
266        return
267    end
268
269    ctxdata.ctxname = ctxname or removesuffix(ctxdata.jobname) or ""
270
271    if ctxdata.ctxname == "" then
272        return
273    end
274
275    ctxdata.jobname = fileaddsuffix(ctxdata.jobname,'tex')
276    ctxdata.ctxname = fileaddsuffix(ctxdata.ctxname,'ctx')
277
278    report("jobname: %s",ctxdata.jobname)
279    report("ctxname: %s",ctxdata.ctxname)
280
281    -- mtxrun should resolve kpse: and file:
282
283    local usedname = ctxdata.ctxname
284    local found    = validfile(usedname)
285
286    -- no further test if qualified path
287
288    if not found then
289        for _, path in next, ctx_locations do
290            local fullname = filejoinname(path,ctxdata.ctxname)
291            if validfile(fullname) then
292                usedname = fullname
293                found    = true
294                break
295            end
296        end
297    end
298
299    if not found then
300        usedname = resolvers.findfile(ctxdata.ctxname,"tex")
301        found    = usedname ~= ""
302    end
303
304    if not found and defaultname and defaultname ~= "" and validfile(defaultname) then
305        usedname = defaultname
306        found    = true
307    end
308
309    if not found then
310        return
311    end
312
313    local xmldata = xml.load(usedname)
314
315    if not xmldata then
316        return
317    else
318        -- test for valid, can be text file
319    end
320
321    local ctxpaths = table.append({'.', filepathpart(ctxdata.ctxname)}, ctx_locations)
322
323    xml.include(xmldata,'ctx:include','name', ctxpaths)
324
325    local flags = ctxdata.flags
326
327    for e in xml.collected(xmldata,"/ctx:job/ctx:flags/ctx:flag") do
328        local flag = xml.text(e) or ""
329        local key, value = match(flag,"^(.-)=(.+)$")
330        if key and value then
331            flags[key] = value
332        else
333            flags[flag] = true
334        end
335    end
336
337end
338
339function ctxrunner.checkflags(ctxdata)
340    if ctxdata then
341        for k,v in next, ctxdata.flags do
342            if getargument(k) == nil then
343                setargument(k,v)
344            end
345        end
346    end
347end
348
349-- multipass control
350
351local multipass_suffixes   = { ".tuc" }
352local multipass_nofruns    = 9 -- better for tracing oscillation
353local multipass_forcedruns = false
354
355local function multipass_hashfiles(jobname)
356    local hash = { }
357    for i=1,#multipass_suffixes do
358        local suffix = multipass_suffixes[i]
359        local full = jobname .. suffix
360        hash[full] = md5.hex(io.loaddata(full) or "unknown")
361    end
362    return hash
363end
364
365local function multipass_changed(oldhash, newhash)
366    for k,v in next, oldhash do
367        if v ~= newhash[k] then
368            return true
369        end
370    end
371    return false
372end
373
374local f_tempfile_i = formatters["%s-%s-%02d.tmp"]
375local f_tempfile_s = formatters["%s-%s-keep.%s"]
376
377local function backup(jobname,run,kind,filename)
378    if run then
379        if run == 1 then
380            for i=1,10 do
381                local tmpname = f_tempfile_i(jobname,kind,i)
382                if validfile(tmpname) then
383                    removefile(tmpname)
384                    report("removing %a",tmpname)
385                end
386            end
387        end
388        if validfile(filename) then
389            local tmpname = f_tempfile_i(jobname,kind,run or 1)
390            report("copying %a into %a",filename,tmpname)
391            file.copy(filename,tmpname)
392        else
393            report("no file %a, nothing kept",filename)
394        end
395    elseif validfile(filename) then
396        local tmpname = f_tempfile_s(jobname,kind,kind)
397        report("copying %a into %a",filename,tmpname)
398        file.copy(filename,tmpname)
399    else
400        report("no file %a, nothing kept",filename)
401    end
402end
403
404local function multipass_copyluafile(jobname,run)
405    local tuaname, tucname = jobname..".tua", jobname..".tuc"
406    if validfile(tuaname) then
407        if run then
408            backup(jobname,run,"tuc",tucname)
409            report("copying %a into %a",tuaname,tucname)
410            report()
411        end
412        removefile(tucname)
413        renamefile(tuaname,tucname)
414    end
415end
416
417local function multipass_copypdffile(jobname,run)
418    if run then
419        local pdfname = jobname..".pdf"
420        if validfile(pdfname) then
421            backup(jobname,false,"pdf",pdfname)
422            report()
423        end
424    end
425end
426
427local function multipass_copylogfile(jobname,run)
428    if run then
429        local logname = jobname..".log"
430        if validfile(logname) then
431            backup(jobname,run,"log",logname)
432            report()
433        end
434    end
435end
436
437--
438
439local pattern = lpegpatterns.utfbom^-1 * (P("%% ") + P("% ")) * Cs((1-lpegpatterns.newline)^1)
440
441local prefile = nil
442local predata = nil
443
444local function preamble_analyze(filename) -- only files on current path
445    filename = fileaddsuffix(filename,"tex") -- to be sure
446    if predata and prefile == filename then
447        return predata
448    end
449    prefile = filename
450    predata = { }
451    local line = io.loadlines(prefile)
452    if line then
453        local preamble = lpegmatch(pattern,line)
454        if preamble then
455            utilities.parsers.options_to_hash(preamble,predata)
456            predata.type = "tex"
457        elseif find(line,"^<?xml ") then
458            predata.type = "xml"
459        end
460        if predata.nofruns then
461            multipass_nofruns = predata.nofruns
462        end
463        if not predata.engine then
464            predata.engine = environment.basicengines[engine_old] --'luatex'
465        end
466        if predata.engine ~= engine_old then -- hack
467            if environment.validengines[predata.engine] and predata.engine ~= environment.basicengines[engine_old] then
468                restart(engine_old,predata.engine)
469            end
470        end
471    end
472    return predata
473end
474
475-- automatically opening and closing pdf files
476
477local pdfview -- delayed
478
479local function pdf_open(name,method)
480    starttiming("pdfview")
481    pdfview = pdfview or dofile(resolvers.findfile("l-pdfview.lua","tex"))
482    pdfview.setmethod(method)
483    report(pdfview.status())
484    local pdfname = filenewsuffix(name,"pdf")
485    if not lfs.isfile(pdfname) then
486        pdfname = name .. ".pdf" -- agressive
487    end
488    pdfview.open(pdfname)
489    stoptiming("pdfview")
490    report("pdfview overhead: %s seconds",elapsedtime("pdfview"))
491end
492
493local function pdf_close(name,method)
494    starttiming("pdfview")
495    pdfview = pdfview or dofile(resolvers.findfile("l-pdfview.lua","tex"))
496    pdfview.setmethod(method)
497    local pdfname = filenewsuffix(name,"pdf")
498    if lfs.isfile(pdfname) then
499        pdfview.close(pdfname)
500    end
501    pdfname = name .. ".pdf" -- agressive
502    pdfview.close(pdfname)
503    stoptiming("pdfview")
504end
505
506-- result file handling
507
508local function result_push_purge(oldbase,newbase)
509    for _, suffix in next, usedsuffixes.after do
510        local oldname = fileaddsuffix(oldbase,suffix)
511        local newname = fileaddsuffix(newbase,suffix)
512        removefile(newname)
513        removefile(oldname)
514    end
515end
516
517local function result_push_keep(oldbase,newbase)
518    for _, suffix in next, usedsuffixes.before do
519        local oldname = fileaddsuffix(oldbase,suffix)
520        local newname = fileaddsuffix(newbase,suffix)
521        local tmpname = "keep-"..oldname
522        removefile(tmpname)
523        renamefile(oldname,tmpname)
524        removefile(oldname)
525        renamefile(newname,oldname)
526    end
527end
528
529local function result_save_error(oldbase,newbase)
530    for _, suffix in next, usedsuffixes.keep do
531        local oldname = fileaddsuffix(oldbase,suffix)
532        local newname = fileaddsuffix(newbase,suffix)
533        removefile(newname) -- to be sure
534        renamefile(oldname,newname)
535    end
536end
537
538local function result_save_purge(oldbase,newbase)
539    for _, suffix in next, usedsuffixes.after do
540        local oldname = fileaddsuffix(oldbase,suffix)
541        local newname = fileaddsuffix(newbase,suffix)
542        removefile(newname) -- to be sure
543        renamefile(oldname,newname)
544    end
545end
546
547local function result_save_keep(oldbase,newbase)
548    for _, suffix in next, usedsuffixes.after do
549        local oldname = fileaddsuffix(oldbase,suffix)
550        local newname = fileaddsuffix(newbase,suffix)
551        local tmpname = "keep-"..oldname
552        removefile(newname)
553        renamefile(oldname,newname)
554        renamefile(tmpname,oldname)
555    end
556end
557
558-- use mtx-plain instead
559
560local plain_formats = {
561    ["plain"]        = "plain",
562    ["luatex-plain"] = "luatex-plain",
563}
564
565local function plain_format(plainformat)
566    return plainformat and plain_formats[plainformat]
567end
568
569local function run_plain(plainformat,filename)
570    local plainformat = plain_formats[plainformat]
571    if plainformat then
572        local command = format("mtxrun --script --texformat=%s plain %s",plainformat,filename)
573        report("running command: %s\n\n",command)
574        -- todo: load and run
575        local resultname = filenewsuffix(filename,"pdf")
576        local pdfview = getargument("autopdf") or getargument("closepdf")
577        if pdfview then
578            pdf_close(resultname,pdfview)
579            os.execute(command) -- maybe also a proper runner
580            pdf_open(resultname,pdfview)
581        else
582            os.execute(command) -- maybe also a proper runner
583        end
584    end
585end
586
587local function run_texexec(filename,a_purge,a_purgeall)
588    if false then
589        -- we need to write a top etc too and run mp etc so it's not worth the
590        -- trouble, so it will take a while before the next is finished
591        --
592        -- context --extra=texutil --convert myfile
593    else
594        local texexec = resolvers.findfile("texexec.rb") or ""
595        if texexec ~= "" then
596            os.setenv("RUBYOPT","")
597            local options = environment.reconstructcommandline(environment.arguments_after)
598            options = gsub(options,"--purge","")
599            options = gsub(options,"--purgeall","")
600            local command = format("ruby %s %s",texexec,options)
601            report("running command: %s\n\n",command)
602            if a_purge then
603                os.execute(command)
604                scripts.context.purge_job(filename,false,true)
605            elseif a_purgeall then
606                os.execute(command)
607                scripts.context.purge_job(filename,true,true)
608            else
609                os.execute(command) -- we can use os.exec but that doesn't give back timing
610            end
611        end
612    end
613end
614
615-- executing luatex
616
617local function flags_to_string(flags,prefix)
618    -- context flags get prepended by c: ... this will move to the sbx module
619    local t = { }
620    for k, v in table.sortedhash(flags) do
621        local p
622        if prefix then
623            p = format("c:%s",k)
624        else
625            p = k
626        end
627        if not v or v == "" or v == '""' then
628            -- no need to flag false
629        elseif v == true then
630            t[#t+1] = format('--%s',p)
631        elseif type(v) == "string" then
632            t[#t+1] = format('--%s=%s',p,quote(v))
633        else
634            t[#t+1] = format('--%s=%s',p,tostring(v))
635        end
636    end
637    return concat(t," ")
638end
639
640function scripts.context.run(ctxdata,filename)
641    --
642    local verbose  = false
643    --
644    local a_nofile = getargument("nofile")
645    local a_engine = getargument("engine")
646    --
647    local files    = environment.filenames or { }
648    --
649    local filelist, mainfile
650    --
651    if filename then
652        -- the given forced name is processed, the filelist is passed to context
653        mainfile = filename
654        filelist = { filename }
655     -- files    = files
656    elseif a_nofile then
657        -- the list of given files is processed using the dummy file
658        mainfile = usedfiles.nop
659        filelist = { usedfiles.nop }
660     -- files    = { }
661    elseif #files > 0 then
662        -- the list of given files is processed using the stub file
663        mainfile = usedfiles.yes -- this can become "" for luametatex/lmtx
664        filelist = files
665        files    = { }
666    else
667        return
668    end
669    --
670    local interface  = validstring(getargument("interface")) or "en"
671    local formatname = formatofinterface[interface] or "cont-en"
672    local formatfile,
673          scriptfile = resolvers.locateformat(formatname) -- regular engine !
674    if not formatfile or not scriptfile then
675        report("warning: no format found, forcing remake (commandline driven)")
676        scripts.context.make(formatname)
677        formatfile, scriptfile = resolvers.locateformat(formatname) -- variant
678    end
679    if formatfile and scriptfile then
680        -- okay
681    elseif formatname then
682        report("error, no format found with name: %s, aborting",formatname)
683        return
684    else
685        report("error, no format found (provide formatname or interface)")
686        return
687    end
688    --
689    local a_mkii          = getargument("mkii") or getargument("pdftex") or getargument("xetex")
690    local a_purge         = getargument("purge")
691    local a_purgeall      = getargument("purgeall")
692    local a_purgeresult   = getargument("purgeresult")
693    local a_global        = getargument("global")
694    local a_runpath       = getargument("runpath")
695    local a_timing        = getargument("timing")
696    local a_profile       = getargument("profile")
697    local a_batchmode     = getargument("batchmode")
698    local a_nonstopmode   = getargument("nonstopmode")
699    local a_scollmode     = getargument("scrollmode")
700    local a_once          = getargument("once")
701    local a_backend       = getargument("backend")
702    local a_arrange       = getargument("arrange")
703    local a_noarrange     = getargument("noarrange")
704    local a_jithash       = getargument("jithash")
705    local a_permitloadlib = getargument("permitloadlib")
706    local a_texformat     = getargument("texformat")
707    local a_notuc         = getargument("notuc")
708    local a_keeptuc       = getargument("keeptuc")
709    local a_keeplog       = getargument("keeplog")
710    local a_keeppdf       = getargument("keeppdf")
711    local a_export        = getargument("export")
712    local a_nodates       = getargument("nodates")
713    local a_trailerid     = getargument("trailerid")
714    local a_nocompression = getargument("nocompression")
715    --
716    a_batchmode = (a_batchmode and "batchmode") or (a_nonstopmode and "nonstopmode") or (a_scrollmode and "scrollmode") or nil
717    --
718    local changed = { }
719    --
720    for i=1,#filelist do
721        --
722        local filename = filelist[i]
723
724        if filename == "" then
725            report("warning: bad filename")
726            break
727        end
728
729        local basename = filebasename(filename) -- use splitter
730        local pathname = filepathpart(filename)
731        --
732        if filesuffix(filename) == "" then
733            filename = fileaddsuffix(filename,"tex")
734        end
735        --
736        if pathname == "" and not a_global and filename ~= usedfiles.nop then
737            filename = "./" .. filename
738            if not validfile(filename) then
739                report("warning: no (local) file %a, proceeding",filename)
740            end
741        end
742        --
743        local jobname  = removesuffix(basename)
744     -- local jobname  = removesuffix(filename)
745        local ctxname  = ctxdata and ctxdata.ctxname
746        --
747        if changed[jobname] == nil then
748            changed[jobname] = false
749        end
750        --
751        local analysis = preamble_analyze(filename)
752        --
753        if a_mkii or analysis.engine == 'pdftex' or analysis.engine == 'xetex' then
754            run_texexec(filename,a_purge,a_purgeall)
755        elseif plain_format(a_texformat or analysis.texformat) then
756            run_plain(a_texformat or analysis.texformat,filename)
757        else
758            if analysis.interface and analysis.interface ~= interface then
759                formatname = formatofinterface[analysis.interface] or formatname
760                formatfile, scriptfile = resolvers.locateformat(formatname)
761            end
762            --
763            local runpath = a_runpath or analysis.runpath
764            if type(runpath) == "string" and runpath ~= "" then
765                runpath = resolvers.resolve(runpath)
766                local currentdir = dir.current()
767                if not lfs.isdir(runpath) then
768                    if dir.makedirs(runpath) then
769                        report("runpath %a has been created",runpath)
770                    else
771                        report("error: runpath %a cannot be created",runpath)
772                        os.exit()
773                    end
774                end
775                if lfs.chdir(runpath) then
776                    report("changing to runpath %a",runpath)
777                else
778                    report("error: changing to runpath %a is impossible",runpath)
779                    os.exit()
780                end
781                environment.arguments.path    = currentdir
782                environment.arguments.runpath = runpath
783                if filepathpart(filename) == "." then
784                    filename = filebasename(filename)
785                end
786            end
787            --
788            a_jithash       = validstring(a_jithash or analysis.jithash) or nil
789            a_permitloadlib = a_permitloadlib or analysis.permitloadlib or nil
790            --
791            if not formatfile or not scriptfile then
792                report("warning: no format found, forcing remake (source driven)")
793                scripts.context.make(formatname,a_engine)
794                formatfile, scriptfile = resolvers.locateformat(formatname)
795            end
796            --
797            local function combine(key)
798                local flag = validstring(environment[key])
799                local plus = analysis[key]
800                if flag and plus then
801                    return plus .. "," .. flag -- flag wins
802                else
803                    return flag or plus -- flag wins
804                end
805            end
806            ----- a_trackers    = analysis.trackers
807            ----- a_experiments = analysis.experiments
808            local directives    = combine("directives")
809            local trackers      = combine("trackers")
810            local experiments   = combine("experiments")
811            --
812            local ownerpassword = environment.ownerpassword or analysis.ownerpassword
813            local userpassword  = environment.userpassword  or analysis.userpassword
814            local permissions   = environment.permissions   or analysis.permissions
815            --
816            if formatfile and scriptfile then
817                local suffix     = validstring(getargument("suffix"))
818                local resultname = validstring(getargument("result"))
819                if not resultname or resultname == "" then
820                    resultname = validstring(analysis.result)
821                end
822                local resultpath = filepathpart(resultname)
823                if resultpath ~= "" then
824                    resultname  = nil
825                elseif suffix then
826                    resultname = removesuffix(jobname) .. suffix
827                end
828                local oldbase = ""
829                local newbase = ""
830                if resultname then
831                    oldbase = removesuffix(jobname)
832                    newbase = removesuffix(resultname)
833                    if oldbase ~= newbase then
834                        if a_purgeresult then
835                            result_push_purge(oldbase,newbase)
836                        else
837                            result_push_keep(oldbase,newbase)
838                        end
839                    else
840                        resultname = nil
841                    end
842                end
843                --
844                local pdfview = getargument("autopdf") or getargument("closepdf")
845                if pdfview then
846                    pdf_close(filename,pdfview)
847                    if resultname then
848                        pdf_close(resultname,pdfview)
849                    end
850                end
851                --
852                -- we could do this when locating the format and exit from luatex when
853                -- there is a version mismatch .. that way we can use stock luatex
854                -- plus mtxrun to run luajittex instead .. this saves a restart but is
855                -- also cleaner as then mtxrun only has to check for a special return
856                -- code (signaling a make + rerun) .. maybe some day
857                --
858                local okay = statistics.checkfmtstatus(formatfile,a_engine)
859                if okay ~= true then
860                    report("warning: %s, forcing remake",tostring(okay))
861                    scripts.context.make(formatname)
862                end
863                --
864                local oldhash     = multipass_hashfiles(jobname)
865                local newhash     = { }
866                local maxnofruns  = once and 1 or multipass_nofruns
867                local fulljobname = validstring(filename)
868                --
869                local c_flags = {
870                    directives     = directives,   -- gets passed via mtxrun
871                    trackers       = trackers,     -- gets passed via mtxrun
872                    experiments    = experiments,  -- gets passed via mtxrun
873                    --
874                    result         = validstring(resultname),
875                    input          = validstring(getargument("input") or filename), -- alternative input
876                    fulljobname    = fulljobname,
877                    files          = concat(files,","),
878                    ctx            = validstring(ctxname),
879                    export         = a_export and true or nil,
880                    nocompression  = a_nocompression and true or nil,
881                    texmfbinpath   = os.selfdir,
882                    --
883                    ownerpassword  = ownerpassword,
884                    userpassword   = userpassword,
885                    permissions    = permissions,
886                }
887                --
888                for k, v in next, environment.arguments do
889                    -- the raw arguments
890                    if c_flags[k] == nil then
891                        c_flags[k] = v
892                    end
893                end
894                --
895                -- todo: --output-file=... in luatex
896                --
897                local usedname = jobname
898                local engine   = analysis.engine or "luametatex"
899                if engine == "luametatex" and (mainfile == usedfiles.yes or mainfile == usedfiles.nop) and not getargument("redirected") then
900                    mainfile = "" -- we don't need that
901                    usedname = fulljobname
902                end
903                --
904                --
905                local l_flags = {
906                    ["interaction"]           = a_batchmode,
907                    ["synctex"]               = false,       -- context has its own way
908                 -- ["no-parse-first-line"]   = true,        -- obsolete
909                 -- ["safer"]                 = a_safer,     -- better use --sandbox
910                 -- ["no-mktex"]              = true,
911                 -- ["file-line-error-style"] = true,
912--                  ["fmt"]                   = formatfile,
913--                  ["lua"]                   = scriptfile,
914--                  ["jobname"]               = jobname,
915                    ["jobname"]               = usedname,
916                    ["jithash"]               = a_jithash,
917                    ["permitloadlib"]         = a_permitloadlib,
918                }
919                --
920                local directives = { }
921                --
922                -- todo: handle these at the tex end
923                --
924                if a_nodates then
925                    directives[#directives+1] = format("backend.date=%s",type(a_nodates) == "string" and a_nodates or "no")
926                end
927                --
928                if type(a_trailerid) == "string" then
929                    directives[#directives+1] = format("backend.trailerid=%s",a_trailerid)
930                end
931                --
932                if a_profile then
933                    directives[#directives+1] = format("system.profile=%s",tonumber(a_profile) or 0)
934                end
935                --
936             -- if a_notuc then
937             --     removefile(fileaddsuffix(jobname,"tuc"))
938             --     directives[#directives+1] = "job.save=no" -- handled at tex end
939             -- end
940                --
941                for i=1,#synctex_runfiles do
942                    removefile(fileaddsuffix(jobname,synctex_runfiles[i]))
943                end
944                --
945                if #directives > 0 then
946                    c_flags.directives = concat(directives,",")
947                end
948                --
949                -- kindofrun: 1:first run, 2:successive run, 3:once, 4:last of maxruns
950                --
951                -- can be used to include pages from a previous run, --keeppdf or "% keeppdf" on first-line
952                --
953                multipass_copypdffile(jobname,a_keeppdf or analysis.keeppdf)
954                --
955                for currentrun=1,maxnofruns do
956                    --
957                    c_flags.final      = false
958                    c_flags.kindofrun  = (a_once and 3) or (currentrun==1 and 1) or (currentrun==maxnofruns and 4) or 2
959                    c_flags.maxnofruns = maxnofruns
960                    c_flags.forcedruns = multipass_forcedruns and multipass_forcedruns > 0 and multipass_forcedruns or nil
961                    c_flags.currentrun = currentrun
962                    c_flags.noarrange  = a_noarrange or a_arrange or nil
963                    c_flags.profile    = a_profile and (tonumber(a_profile) or 0) or nil
964                    --
965                    print("") -- cleaner, else continuation on same line
966                    --
967                    local returncode = environment.run_format(
968                        formatfile,
969                        scriptfile,
970                        mainfile,
971                        flags_to_string(l_flags),
972                        flags_to_string(c_flags,true),
973                        verbose
974                    )
975                    -- todo: remake format when no proper format is found
976                    if not returncode then
977                        report("fatal error: no return code")
978                        if resultname then
979                            result_save_error(oldbase,newbase)
980                        end
981                        os.exit(1)
982                        break
983                    elseif returncode == 0 then
984                        multipass_copyluafile(jobname,a_keeptuc and currentrun)
985                        multipass_copylogfile(jobname,a_keeplog and currentrun)
986                        if not multipass_forcedruns then
987                            newhash = multipass_hashfiles(jobname)
988                            if multipass_changed(oldhash,newhash) then
989                                changed[jobname] = true
990                                oldhash = newhash
991                            else
992                                break
993                            end
994                        elseif currentrun == multipass_forcedruns then
995                            report("quitting after force %i runs",multipass_forcedruns)
996                            break
997                        end
998                    else
999                        report("fatal error: return code: %s",returncode or "?")
1000                        if resultname then
1001                            result_save_error(oldbase,newbase)
1002                        end
1003                        os.exit(1) -- (returncode)
1004                        break
1005                    end
1006                    --
1007                end
1008                --
1009                if environment.arguments["ansilog"] then
1010                    local logfile = filenewsuffix(jobname,"log")
1011                    local logdata = io.loaddata(logfile) or ""
1012                    if logdata ~= "" then
1013                        io.savedata(logfile,(gsub(logdata,"%[.-m","")))
1014                    end
1015                end
1016             -- local syncctx = fileaddsuffix(jobname,"syncctx")
1017             -- if validfile(syncctx) then
1018             --     renamefile(syncctx,fileaddsuffix(jobname,"synctex"))
1019             -- end
1020                if a_arrange then
1021                    --
1022                    c_flags.final      = true
1023                    c_flags.kindofrun  = 3
1024                    c_flags.currentrun = c_flags.currentrun + 1
1025                    c_flags.noarrange  = nil
1026                    --
1027                    report("arrange run: %s",command)
1028                    --
1029                    local returncode = environment.run_format(
1030                        formatfile,
1031                        scriptfile,
1032                        mainfile,
1033                        flags_to_string(l_flags),
1034                        flags_to_string(c_flags,true),
1035                        verbose
1036                    )
1037                    --
1038                    if not returncode then
1039                        report("fatal error: no return code, message: %s",errorstring or "?")
1040                        os.exit(1)
1041                    elseif returncode > 0 then
1042                        report("fatal error: return code: %s",returncode or "?")
1043                        os.exit(returncode)
1044                    end
1045                    --
1046                end
1047                --
1048                if a_purge then
1049                    scripts.context.purge_job(jobname,false,false,fulljobname)
1050                elseif a_purgeall then
1051                    scripts.context.purge_job(jobname,true,false,fulljobname)
1052                end
1053                --
1054                if resultname then
1055                    if a_purgeresult then
1056                        -- so, if there is no result then we don't get the old one, but
1057                        -- related files (log etc) are still there for tracing purposes
1058                        result_save_purge(oldbase,newbase)
1059                    else
1060                        result_save_keep(oldbase,newbase)
1061                    end
1062                    report("result renamed to: %s",newbase)
1063                elseif resultpath ~= "" then
1064                    report()
1065                    report("results are to be on the running path, not on %a, ignoring --result",resultpath)
1066                    report()
1067                end
1068                --
1069             -- -- needs checking
1070             --
1071             -- if a_purge then
1072             --     scripts.context.purge_job(resultname)
1073             -- elseif a_purgeall then
1074             --     scripts.context.purge_job(resultname,true)
1075             -- end
1076                --
1077                local pdfview = getargument("autopdf")
1078                if pdfview then
1079                    pdf_open(resultname or jobname,pdfview)
1080                end
1081                --
1082                local epub = analysis.epub
1083                if epub then
1084                    if type(epub) == "string" then
1085                        local t = settings_to_array(epub)
1086                        for i=1,#t do
1087                            t[i] = "--" .. gsub(t[i],"^%-*","")
1088                        end
1089                        epub = concat(t," ")
1090                    else
1091                        epub = "--make"
1092                    end
1093                    local command = "mtxrun --script epub " .. epub .. " " .. jobname
1094                    report()
1095                    report("making epub file: ",command)
1096                    report()
1097                    os.execute(command) -- todo: also a runner
1098                end
1099                --
1100                if a_timing then
1101                    report()
1102                    report("you can process (timing) statistics with:",jobname)
1103                    report()
1104                    report("context --extra=timing '%s'",jobname)
1105                 -- report("mtxrun --script timing --xhtml [--launch --remove] '%s'",jobname)
1106                    report()
1107                end
1108            else
1109                if formatname then
1110                    report("error, no format found with name: %s, skipping",formatname)
1111                else
1112                    report("error, no format found (provide formatname or interface)")
1113                end
1114                break
1115            end
1116        end
1117    end
1118    --
1119    if #filelist > 1 then
1120        local done = false
1121        for k, v in sortedhash(changed) do
1122            if v then
1123                if not done then
1124                    report()
1125                    done = true
1126                end
1127                report("file %a was changed",k)
1128            end
1129        end
1130        if done then
1131            report()
1132        end
1133    end
1134end
1135
1136function scripts.context.pipe() -- still used?
1137    -- context --pipe
1138    -- context --pipe --purge --dummyfile=whatever.tmp
1139    local interface = getargument("interface")
1140    interface = (type(interface) == "string" and interface) or "en"
1141    local formatname = formatofinterface[interface] or "cont-en"
1142    local formatfile, scriptfile = resolvers.locateformat(formatname)
1143    if not formatfile or not scriptfile then
1144        report("warning: no format found, forcing remake (commandline driven)")
1145        scripts.context.make(formatname)
1146        formatfile, scriptfile = resolvers.locateformat(formatname)
1147    end
1148    if formatfile and scriptfile then
1149        local okay = statistics.checkfmtstatus(formatfile)
1150        if okay ~= true then
1151            report("warning: %s, forcing remake",tostring(okay))
1152            scripts.context.make(formatname)
1153        end
1154        local l_flags = {
1155            interaction = "scrollmode",
1156            fmt         = formatfile,
1157            lua         = scriptfile,
1158        }
1159        local c_flags = {
1160            backend     = "pdf",
1161            final       = false,
1162            kindofrun   = 3,
1163            currentrun  = 1,
1164        }
1165        local filename = getargument("dummyfile") or ""
1166        if filename == "" then
1167            filename = "\\relax"
1168            report("entering scrollmode, end job with \\end")
1169        else
1170            filename = fileaddsuffix(filename,"tmp")
1171            io.savedata(filename,"\\relax")
1172            report("entering scrollmode using '%s' with optionfile, end job with \\end",filename)
1173        end
1174        local returncode = environment.run_format(
1175            formatfile,
1176            scriptfile,
1177            filename,
1178            flags_to_string(l_flags),
1179            flags_to_string(c_flags,true),
1180            verbose
1181        )
1182        if getargument("purge") then
1183            scripts.context.purge_job(filename)
1184        elseif getargument("purgeall") then
1185            scripts.context.purge_job(filename,true)
1186            removefile(filename)
1187        end
1188    elseif formatname then
1189        report("error, no format found with name: %s, aborting",formatname)
1190    else
1191        report("error, no format found (provide formatname or interface)")
1192    end
1193end
1194
1195local function make_mkiv_format(name,engine)
1196    environment.make_format(name) -- jit is picked up later
1197end
1198
1199local make_mkii_format
1200
1201do -- more or less copied from mtx-plain.lua:
1202
1203    local function mktexlsr()
1204        if environment.arguments.silent then
1205            local result = os.execute("mktexlsr --quiet > temp.log")
1206            if result ~= 0 then
1207                print("mktexlsr silent run > fatal error") -- we use a basic print
1208            else
1209                print("mktexlsr silent run") -- we use a basic print
1210            end
1211            removefile("temp.log")
1212        else
1213            report("running mktexlsr")
1214            os.execute("mktexlsr")
1215        end
1216    end
1217
1218    local function engine(texengine,texformat)
1219        local command = string.format('%s --ini --etex --8bit %s \\dump',texengine,fileaddsuffix(texformat,"mkii"))
1220        if environment.arguments.silent then
1221            starttiming()
1222            local command = format("%s > temp.log",command)
1223            local result  = os.execute(command)
1224            local runtime = stoptiming()
1225            if result ~= 0 then
1226                print(format("%s silent make > fatal error when making format %q",texengine,texformat)) -- we use a basic print
1227            else
1228                print(format("%s silent make > format %q made in %.3f seconds",texengine,texformat,runtime)) -- we use a basic print
1229            end
1230            removefile("temp.log")
1231        else
1232            report("running command: %s",command)
1233            os.execute(command)
1234        end
1235    end
1236
1237    local function resultof(...)
1238        local command = string.format(...)
1239        report("running command %a",command)
1240        return string.strip(os.resultof(command) or "")
1241    end
1242
1243    local function make(texengine,texformat)
1244        report("generating kpse file database")
1245        mktexlsr()
1246        local fmtpathspec = resultof("kpsewhich --var-value=TEXFORMATS --engine=%s",texengine)
1247        if fmtpathspec ~= "" then
1248            report("using path specification %a",fmtpathspec)
1249            fmtpathspec = resultof('kpsewhich -expand-braces="%s"',fmtpathspec)
1250        end
1251        if fmtpathspec ~= "" then
1252            report("using path expansion %a",fmtpathspec)
1253        else
1254            report("no valid path reported, trying alternative")
1255            fmtpathspec = resultof("kpsewhich --show-path=fmt --engine=%s",texengine)
1256            if fmtpathspec ~= "" then
1257                report("using path expansion %a",fmtpathspec)
1258            else
1259                report("no valid path reported, falling back to current path")
1260                fmtpathspec = "."
1261            end
1262        end
1263        fmtpathspec = splitlines(fmtpathspec)[1] or fmtpathspec
1264        fmtpathspec = fmtpathspec and file.splitpath(fmtpathspec)
1265        local fmtpath = nil
1266        if fmtpathspec then
1267            for i=1,#fmtpathspec do
1268                local path = fmtpathspec[i]
1269                if path ~= "." then
1270                    dir.makedirs(path)
1271                    if lfs.isdir(path) and file.is_writable(path) then
1272                        fmtpath = path
1273                        break
1274                    end
1275                end
1276            end
1277        end
1278        if not fmtpath or fmtpath == "" then
1279            fmtpath = "."
1280        else
1281            lfs.chdir(fmtpath)
1282        end
1283        engine(texengine,texformat)
1284        report("generating kpse file database")
1285        mktexlsr()
1286        report("format %a saved on path %a",texformat,fmtpath)
1287    end
1288
1289    local function run(texengine,texformat,filename)
1290        local t = { }
1291        for k, v in next, environment.arguments do
1292            t[#t+1] = string.format("--mtx:%s=%s",k,v)
1293        end
1294        execute('%s --fmt=%s %s "%s"',texengine,removesuffix(texformat),table.concat(t," "),filename)
1295    end
1296
1297    make_mkii_format = function(name,engine)
1298
1299        -- let the binary sort it out
1300
1301        os.setenv('SELFAUTOPARENT', "")
1302        os.setenv('SELFAUTODIR',    "")
1303        os.setenv('SELFAUTOLOC',    "")
1304        os.setenv('TEXROOT',        "")
1305        os.setenv('TEXOS',          "")
1306        os.setenv('TEXMFOS',        "")
1307        os.setenv('TEXMFCNF',       "")
1308
1309        make(engine,name)
1310    end
1311
1312end
1313
1314function scripts.context.generate()
1315    resolvers.renewcache()
1316    trackers.enable("resolvers.locating")
1317    resolvers.load()
1318end
1319
1320function scripts.context.make(name)
1321    if not getargument("fast") then -- as in texexec
1322        scripts.context.generate()
1323    end
1324    local list = (name and { name }) or (environment.filenames[1] and environment.filenames) or defaultformats
1325    local engine = getargument("engine") or (status and status.luatex_engine) or "luatex"
1326    if getargument("jit") then
1327        engine = "luajittex"
1328    end
1329    for i=1,#list do
1330        local name = list[i]
1331        name = formatofinterface[name] or name or ""
1332        if name == "" then
1333            -- nothing
1334        elseif engine == "luametatex" or engine == "luatex" or engine == "luajittex" then
1335            make_mkiv_format(name,engine)
1336        elseif engine == "pdftex" or engine == "xetex" then
1337            make_mkii_format(name,engine)
1338        end
1339    end
1340end
1341
1342function scripts.context.ctx()
1343    local ctxdata = ctxrunner.new()
1344    ctxdata.jobname = environment.filenames[1]
1345    ctxrunner.checkfile(ctxdata,getargument("ctx"))
1346    ctxrunner.checkflags(ctxdata)
1347    scripts.context.run(ctxdata)
1348end
1349
1350function scripts.context.autoctx()
1351    local ctxdata   = nil
1352    local files     = environment.filenames
1353    local firstfile = #files > 0 and files[1]
1354    if firstfile then
1355        local suffix  = filesuffix(firstfile)
1356        local ctxname = nil
1357        if suffix == "xml" then
1358            local chunk = io.loadchunk(firstfile) -- 1024
1359            if chunk then
1360                ctxname = match(chunk,"<%?context%-directive%s+job%s+ctxfile%s+([^ ]-)%s*?>")
1361            end
1362        elseif suffix == "tex" or suffix == "mkiv" or suffix == "mkxl" then
1363            local analysis = preamble_analyze(firstfile)
1364            ctxname = analysis.ctxfile or analysis.ctx
1365        end
1366        if ctxname then
1367            ctxdata = ctxrunner.new()
1368            ctxdata.jobname = firstfile
1369            ctxrunner.checkfile(ctxdata,ctxname)
1370            ctxrunner.checkflags(ctxdata)
1371        end
1372    end
1373    scripts.context.run(ctxdata)
1374end
1375
1376function scripts.context.version()
1377    local list = { "context.mkiv", "context.mkxl" }
1378    for i=1,#list do
1379        local base = list[i]
1380        local name = resolvers.findfile(base)
1381        if name ~= "" then
1382            report("main context file: %s",name)
1383            local data = io.loaddata(name)
1384            if data then
1385                local version = match(data,"\\edef\\contextversion{(.-)}")
1386                if version then
1387                    report("current version: %s",version)
1388                else
1389                    report("context version: unknown, no timestamp found")
1390                end
1391            else
1392                report("context version: unknown, load error")
1393            end
1394        else
1395            report("main context file: unknown, %a not found",base)
1396        end
1397    end
1398end
1399
1400function scripts.context.purge_job(jobname,all,mkiitoo,fulljobname)
1401    if jobname and jobname ~= "" then
1402        jobname = filebasename(jobname)
1403        local filebase = removesuffix(jobname)
1404        if mkiitoo then
1405            scripts.context.purge(all,filebase,true) -- leading "./"
1406        else
1407            local deleted = { }
1408            for i=1,#obsolete_results do
1409                deleted[#deleted+1] = purge_file(fileaddsuffix(filebase,obsolete_results[i]),fileaddsuffix(filebase,"pdf"))
1410            end
1411            for i=1,#temporary_runfiles do
1412                deleted[#deleted+1] = purge_file(fileaddsuffix(filebase,temporary_runfiles[i]))
1413            end
1414            if fulljobname and fulljobname ~= jobname then
1415                for i=1,#temporary_suffixes do
1416                    deleted[#deleted+1] = purge_file(fileaddsuffix(fulljobname,temporary_suffixes[i],true))
1417                end
1418            end
1419            if all then
1420                for i=1,#persistent_runfiles do
1421                    deleted[#deleted+1] = purge_file(fileaddsuffix(filebase,persistent_runfiles[i]))
1422                end
1423            end
1424            if #deleted > 0 then
1425                report("purged files: %s", concat(deleted,", "))
1426            end
1427        end
1428    end
1429end
1430
1431function scripts.context.purge(all,pattern,mkiitoo)
1432    local all        = all or getargument("all")
1433    local pattern    = getargument("pattern") or (pattern and (pattern.."*")) or "*.*"
1434    local files      = dir.glob(pattern)
1435    local obsolete   = tohash(obsolete_results)
1436    local temporary  = tohash(temporary_runfiles)
1437    local synctex    = tohash(synctex_runfiles)
1438    local persistent = tohash(persistent_runfiles)
1439    local generic    = tohash(generic_files)
1440    local deleted    = { }
1441    for i=1,#files do
1442        local name = files[i]
1443        local suffix = filesuffix(name)
1444        local basename = filebasename(name)
1445        if obsolete[suffix] or temporary[suffix] or synctex[suffix] or persistent[suffix] or generic[basename] then
1446            deleted[#deleted+1] = purge_file(name)
1447        elseif mkiitoo then
1448            for i=1,#special_runfiles do
1449                if find(name,special_runfiles[i]) then
1450                    deleted[#deleted+1] = purge_file(name)
1451                end
1452            end
1453        end
1454        for i=1,#extra_runfiles do
1455            if find(basename,extra_runfiles[i]) then
1456                deleted[#deleted+1] = purge_file(name)
1457            end
1458        end
1459    end
1460    if #deleted > 0 then
1461        report("purged files: %s", concat(deleted,", "))
1462    end
1463end
1464
1465-- touching files (signals regeneration of formats)
1466
1467local newversion = false
1468
1469local function touch(path,name,versionpattern,kind,kindpattern)
1470    if path and path ~= "" then
1471        name = filejoinname(path,name)
1472    else
1473        name = resolvers.findfile(name)
1474    end
1475    local olddata = io.loaddata(name)
1476    if olddata then
1477        local oldkind = ""
1478        local newkind = kind or ""
1479        local oldversion = ""
1480        local newdata
1481              newversion = newversion or os.date("%Y.%m.%d %H:%M")
1482        if versionpattern then
1483            newdata = gsub(olddata,versionpattern,function(pre,mid,post)
1484                oldversion = mid
1485                return pre .. newversion .. post
1486            end) or olddata
1487        end
1488        if kind and kindpattern then
1489            newdata = gsub(newdata,kindpattern,function(pre,mid,post)
1490                oldkind = mid
1491                return pre .. newkind .. post
1492            end) or newdata
1493        end
1494        if newdata ~= "" and (oldversion ~= newversion or oldkind ~= newkind or newdata ~= olddata) then
1495            local backup = filenewsuffix(name,"tmp")
1496            removefile(backup)
1497            renamefile(name,backup)
1498            io.savedata(name,newdata)
1499            return name, oldversion, newversion, oldkind, newkind
1500        end
1501    end
1502end
1503
1504local p_contextkind       = "(\\edef\\contextkind%s*{)(.-)(})"
1505local p_contextversion    = "(\\edef\\contextversion%s*{)(.-)(})"
1506local p_newcontextversion = "(\\newcontextversion%s*{)(.-)(})"
1507
1508local function touchfiles(suffix,kind,path)
1509    local foundname, oldversion, newversion, oldkind, newkind = touch(path,fileaddsuffix("context",suffix),p_contextversion,kind,p_contextkind)
1510    if foundname then
1511        report("old version  : %s (%s)",oldversion,oldkind)
1512        report("new version  : %s (%s)",newversion,newkind)
1513        report("touched file : %s",foundname)
1514        local foundname = touch(path,fileaddsuffix("cont-new",suffix),p_newcontextversion)
1515        if foundname then
1516            report("touched file : %s", foundname)
1517        end
1518    else
1519        report("nothing touched")
1520    end
1521end
1522
1523local tobetouched = tohash { "mkii", "mkiv", "mkvi", "mkxl", "mklx" }
1524
1525function scripts.context.touch()
1526    if getargument("expert") then
1527        local touch = getargument("touch")
1528        local kind  = getargument("kind")
1529        local path  = getargument("basepath")
1530        if tobetouched[touch] then -- mkix mkxi ctix ctxi
1531            touchfiles(touch,kind,path)
1532        else
1533            for touch in sortedhash(tobetouched) do
1534                touchfiles(touch,kind,path)
1535            end
1536        end
1537    else
1538        report("touching needs --expert")
1539    end
1540end
1541
1542function scripts.context.pages()
1543    local filename = environment.files[1]
1544    if filename then
1545        local u = table.load(fileaddsuffix(filename,"tuc"))
1546        if u then
1547            local p = u.structures.pages.collected
1548            local l = u.structures.lists.collected
1549            local page = environment.arguments.page
1550            local list = environment.arguments.list
1551            if type(page) == "string" then
1552                page = settings_to_array(page)
1553            end
1554            if type(list) == "string" then
1555                list = settings_to_array(list)
1556            end
1557            if page or list then
1558                if page then
1559                    for i=1,#page do
1560                        page[i] = string.topattern(page[i])
1561                    end
1562                    for i=1,#p do
1563                        local pi = p[i]
1564                        local m = pi.marked
1565                        if m then
1566                            local ml = #m
1567                            for j=1,#page do
1568                                local n = page[j]
1569                                for k=1,ml do
1570                                    if find(m[k],n) then
1571                                        report("page : %04i %s",i,m[k])
1572                                    end
1573                                end
1574                            end
1575                        end
1576                    end
1577                end
1578                if list then
1579                    for i=1,#list do
1580                        list[i] = string.topattern(list[i])
1581                    end
1582                    for i=1,#l do
1583                        local li = l[i]
1584                        local r = li.references
1585                        if r then
1586                            local rr = r.reference
1587                            if rr then
1588                                rr = splitstring(rr,",")
1589                                local rrl = #rr
1590                                for j=1,#list do
1591                                    local n = list[j]
1592                                    for k=1,rrl do
1593                                        if find(rr[k],n) then
1594                                            report("list : %04i %s",r.realpage,rr[k])
1595                                        end
1596                                    end
1597                                end
1598                            end
1599                        end
1600                    end
1601                end
1602            else
1603                for i=1,#p do
1604                    local pi = p[i]
1605                    local m = pi.marked
1606                    if m then
1607                        report("page : %04i % t",i,m)
1608                    end
1609                end
1610            end
1611        end
1612    end
1613end
1614
1615-- modules
1616
1617local labels = { "title", "comment", "status" }
1618local cards  = { "*.mkiv", "*.mkvi",  "*.mkix", "*.mkxi", "*.mkxl", "*.mklx", "*.tex" }
1619local valid  = tohash { "mkiv", "mkvi", "mkix", "mkxi", "mkxl", "mklx", "tex" }
1620
1621function scripts.context.modules(pattern)
1622    local list = { }
1623    local found = resolvers.findfile("context.mkiv")
1624    if not pattern or pattern == "" then
1625        -- official files in the tree
1626        for i=1,#cards do
1627            resolvers.findwildcardfiles(cards[i],list)
1628        end
1629        -- my dev path
1630        for i=1,#cards do
1631            dir.glob(filejoinname(filepathpart(found),cards[i]),list)
1632        end
1633    else
1634        resolvers.findwildcardfiles(pattern,list)
1635        dir.glob(filejoinname(filepathpart(found,pattern)),list)
1636    end
1637    local done = { } -- todo : sort
1638    local none = { x = { }, m = { }, s = { }, t = { } }
1639    for i=1,#list do
1640        local v = list[i]
1641        local base = filebasename(v)
1642        if not done[base] then
1643            done[base] = true
1644            local suffix = filesuffix(base)
1645            if valid[suffix] then
1646                local prefix, rest = match(base,"^([xmst])%-(.*)")
1647                if prefix then
1648                    v = resolvers.findfile(base) -- so that files on my dev path are seen
1649                    local data = io.loaddata(v) or ""
1650                    data = match(data,"%% begin info(.-)%% end info")
1651                    if data then
1652                        local info = { }
1653                        for label, text in gmatch(data,"%% +([^ ]+) *: *(.-)[\n\r]") do
1654                            info[label] = text
1655                        end
1656                        report()
1657                        report("%-7s : %s","module",base)
1658                        report()
1659                        for i=1,#labels do
1660                            local l = labels[i]
1661                            if info[l] then
1662                                report("%-7s : %s",l,info[l])
1663                            end
1664                        end
1665                        report()
1666                    else
1667                        insert(none[prefix],rest)
1668                    end
1669                end
1670            end
1671        end
1672    end
1673
1674    local function show(k,v)
1675        sort(v)
1676        if #v > 0 then
1677            report()
1678            for i=1,#v do
1679                report("%s : %s",k,v[i])
1680            end
1681        end
1682    end
1683    for k, v in sortedhash(none) do
1684        show(k,v)
1685    end
1686end
1687
1688-- extras
1689
1690function scripts.context.extras(pattern)
1691    -- only in base path, i.e. only official ones
1692    if type(pattern) ~= "string" then
1693        pattern = "*"
1694    end
1695    local found = resolvers.findfile("context.mkiv")
1696    if found ~= "" then
1697        pattern = filejoinname(dir.expandname(filepathpart(found)),format("mtx-context-%s.tex",pattern or "*"))
1698        local list = dir.glob(pattern)
1699        for i=1,#list do
1700            local v = list[i]
1701            local data = io.loaddata(v) or ""
1702            data = match(data,"%% begin help(.-)%% end help")
1703            if data then
1704                report()
1705                report("extra: %s (%s)",(gsub(v,"^.*mtx%-context%-(.-)%.tex$","%1")),v)
1706                for s in gmatch(data,"%% *(.-)[\n\r]") do
1707                    report(s)
1708                end
1709                report()
1710            end
1711        end
1712    end
1713end
1714
1715function scripts.context.extra()
1716    local extra = getargument("extra")
1717    if type(extra) ~= "string" then
1718        scripts.context.extras()
1719    elseif getargument("help") then
1720        scripts.context.extras(extra)
1721    else
1722        local fullextra = extra
1723        if not find(fullextra,"mtx%-context%-") then
1724            fullextra = "mtx-context-" .. extra
1725        end
1726        local foundextra = resolvers.findfile(fullextra)
1727        if foundextra == "" then
1728            scripts.context.extras()
1729            return
1730        else
1731            report("processing extra: %s", foundextra)
1732        end
1733        setargument("purgeall",true)
1734        local result = getargument("result") or ""
1735        if result == "" then
1736            setargument("result","context-extra")
1737        end
1738        scripts.context.run(nil,foundextra)
1739    end
1740end
1741
1742-- experiment
1743
1744do
1745
1746    local popen   = io.popen
1747    local close   = io.close
1748    ----- read    = io.read
1749    local gobble  = io.gobble or function(f) f:read("l") end
1750    ----- clock   = os.clock
1751    local ticks   = lua.getpreciseticks
1752    local seconds = lua.getpreciseseconds
1753
1754    local f_runner  = formatters['context %s "%s"']
1755    local f_command = formatters['%s %s']
1756
1757    local function getline(line,name)
1758        if string.find(line,"^context ") then
1759            return line
1760        elseif string.find(line,"^mtxrun ") then
1761            return line
1762        end
1763    end
1764
1765    function scripts.context.parallel()
1766        if getargument("pattern") then
1767            environment.files = dir.glob(getargument("pattern"))
1768        end
1769        local files    = environment.files
1770        local total    = files and #files or 0
1771        local lists    = nil
1772        local whattodo = "filename"
1773        local terminal = environment.argument("terminal")
1774        local parallel = environment.argument("parallel")
1775        local runners  = tonumber(parallel) or 8
1776
1777        if type(parallel) == "string" then
1778            local specification = table.load(parallel)
1779            if specification then
1780                local parallel = specification.parallel
1781                if parallel then
1782                    specification = parallel
1783                end
1784                runners  = tonumber(specification.runners) or runners
1785                terminal = specification.terminal or terminal
1786                sequence = specification.sequence
1787                commands = specification.commands
1788                if commands then
1789                    if sequence then
1790                        lists = commands
1791                        for i=1,#commands do
1792                            total = total + #commands[i]
1793                        end
1794                    else
1795                        local list = commands[1]
1796                        for i=2,#commands do
1797                            local c = commands[i]
1798                            for i=1,#c do
1799                                list[#list+1] = c[i]
1800                            end
1801                            total = total + #c
1802                        end
1803                        lists = { list }
1804                    end
1805                    whattodo = "command"
1806                else
1807                    report("missing commands list in %a",parallel)
1808                    return
1809                end
1810            else
1811                report("invalid specification %a",parallel)
1812                return
1813            end
1814        elseif getargument("parallelsequence") then
1815            total = 0
1816            for i=1,#files do
1817                local list = { }
1818                local name = files[i]
1819                local data = splitlines(io.loaddata(name) or "")
1820                for i=1,#data do
1821                    local line = getline(data[i],name)
1822                    if line then
1823                        list[#list+1] = line
1824                    end
1825                end
1826                if #list > 0 then
1827                    if not lists then
1828                        lists = { list, filename = name }
1829                    else
1830                        lists[#lists+1] = list
1831                    end
1832                    total = total + #list
1833                end
1834            end
1835            whattodo = "command"
1836        elseif getargument("parallellist") then
1837            total = 0
1838            local list = { }
1839            for i=1,#files do
1840                local name = files[i]
1841                local data = splitlines(io.loaddata(name) or "")
1842                for i=1,#data do
1843                    local line = getline(data[i],name)
1844                    if line then
1845                        list[#list+1] = line
1846                    end
1847                end
1848            end
1849            if #list > 0 then
1850                lists = { list }
1851                total = total + #list
1852            end
1853            whattodo = "command"
1854        elseif total > 1 then
1855            lists = { files }
1856        end
1857
1858        if not lists and total == 1 then
1859            -- Beware: "--parallel" and "--terminal" are passed to the single run but this
1860            -- is normally harmless.
1861            scripts.context.autoctx()
1862        elseif total > 0 then
1863            local allresults = { }
1864            local start      = starttiming("parallel")
1865            local counts     = 0
1866            local totals     = 0
1867            -- a hack
1868            local passthese = environment.arguments_after
1869            for i=1,#passthese do
1870                local a = passthese[i]
1871                if string.find(a,"^%-%-parallel") or string.find(a,"^%-%-terminal") or not find(a,"^%-%-") then
1872                    passthese[i] = ""
1873                end
1874            end
1875            passthese = table.unique(passthese)
1876            -- end of hack
1877            local arguments = environment.reconstructcommandline(passthese)
1878            for set=1,#lists do
1879                local files   = lists[set]
1880                local process = { }
1881                local results = { }
1882                local count   = 0
1883                local total   = #files
1884                totals = totals + total
1885                allresults[set] = results
1886                while true do
1887                    local done = false
1888                    for i=1,runners do
1889                        local pi = process[i]
1890                        if pi then
1891                            local s
1892                            if terminal then
1893                                s = pi.handle:read("l")
1894                                if s then
1895                                    done = true
1896                                    report("%02i : %s",i,s)
1897                                    goto done
1898                                end
1899                            else
1900                                s = gobble(pi.handle)
1901                                if s then
1902                                    done = true
1903                                    goto done
1904                                end
1905                            end
1906                            if not s then
1907                                local r, detail, n = close(pi.handle)
1908                                stoptiming(pi.timer)
1909                                pi.result = (not r or n > 0) and "error" or "done"
1910                                pi.time   = elapsedtime(pi.timer)
1911                                pi.handle = nil
1912                                pi.timer  = nil
1913                                if terminal then
1914                                    report()
1915                                end
1916                                report("process %02i, index %02i, %s %a, status %a, runtime %0.3f",i,pi.count,whattodo,pi.filename,pi.result,pi.time)
1917                                if terminal then
1918                                    report()
1919                                end
1920                                process[i] = false
1921                                results[pi.count] = pi
1922                            end
1923                        end
1924                        count  = count + 1
1925                        counts = counts + 1
1926                        if count > total then
1927                            -- we're done
1928                        else
1929                            local timer    = "parallel:" .. set .. ":" .. i
1930                            local filename = files[count]
1931                            local dirname  = file.dirname(filename)
1932                            local basename = file.basename(filename)
1933                            if dirname ~= "." and dirname ~= "./" then
1934                                dir.push(dirname)
1935                            end
1936                            local command  = whattodo == "command" and f_command(basename,arguments) or f_runner(arguments,basename)
1937                            resettiming(timer)
1938                            starttiming(timer)
1939                            local result  = popen(command)
1940                            if dirname ~= "." then
1941                                dir.pop()
1942                            end
1943                            local status  = nil
1944                            if result then
1945                                process[i] = {
1946                                    handle   = result,
1947                                    result   = "start",
1948                                    filename = filename,
1949                                    count    = count,
1950                                    time     = 0,
1951                                    timer    = timer,
1952                                }
1953                                status = process[i]
1954                            else
1955                                status = {
1956                                    result   = "error",
1957                                    count    = count,
1958                                    filename = filename,
1959                                    time     = 0,
1960                                }
1961                                stoptiming(timer)
1962                            end
1963                            results[count] = status
1964                            if terminal then
1965                                report()
1966                            end
1967                            report("process %02i, index %02i, %s %a, status %a",i,status.count,whattodo,status.filename,status.result)
1968                            if terminal then
1969                                report()
1970                            end
1971                            done = true
1972                        end
1973                      ::done::
1974                    end
1975                    if not done then
1976                        break
1977                    end
1978                end
1979            end
1980            stoptiming("parallel")
1981            report()
1982            report("files: %i, runtime: %s",totals,elapsedtime("parallel"))
1983            report()
1984            local errors = { }
1985            for set=1,#allresults do
1986                if set > 1 then
1987                    report()
1988                end
1989                local results = allresults[set]
1990                local total   = #results
1991                for i=1,total do
1992                    local ri = results[i]
1993                    local result   = ri.result
1994                    local filename = ri.filename
1995                    if result == "error" then
1996                        errors[#errors+1] = filename
1997                    end
1998                    report("index %02i, %s %a, status %a, runtime %0.3f ",ri.count,whattodo,filename,result,ri.time)
1999                end
2000            end
2001            if #errors > 0 then
2002                report()
2003                report("errors in:")
2004                report()
2005                for i=1,#errors do
2006                    report("  %s",errors[i])
2007                end
2008            end
2009            report()
2010        end
2011    end
2012
2013end
2014
2015-- todo: we need to do a dummy run
2016
2017local function showsetter()
2018    environment.files = { resolvers.findfile("mtx-context-setters.tex") }
2019    multipass_nofruns = 1
2020    setargument("purgeall",true)
2021    scripts.context.run()
2022end
2023
2024scripts.context.trackers    = showsetter
2025scripts.context.directives  = showsetter
2026scripts.context.experiments = showsetter
2027
2028function scripts.context.logcategories()
2029    environment.files = { resolvers.findfile("m-logcategories.mkiv") }
2030    multipass_nofruns = 1
2031    setargument("purgeall",true)
2032    scripts.context.run()
2033end
2034
2035function scripts.context.find(pattern)
2036    if pattern ~= "" then
2037        local found = resolvers.findfile("luametatex.tex")
2038        if found and found ~= "" then
2039            for i=1,6 do
2040                found = file.pathpart(found)
2041            end
2042        end
2043        local files = dir.glob(found.."**.tex")
2044        for i=1,#files do
2045            local f = files[i]
2046            local d = io.loaddata(f)
2047            if find(d,pattern) then
2048                local l = splitlines(d)
2049                report(f)
2050                report("")
2051                for i=1,#l do
2052                    if find(l[i],pattern) then
2053                        report("%5i  %s",i,l[i])
2054                    end
2055                end
2056                report("")
2057            end
2058        end
2059    else
2060        report("there is nothing to search for")
2061    end
2062end
2063
2064function scripts.context.timed(action)
2065    statistics.timed(action,true)
2066end
2067
2068function scripts.context.runlua(filename)
2069    if filename and filesuffix(filename) == "lua" then
2070        local chunk = io.loadchunk(filename) -- 1024
2071        if chunk and find(chunk,"^%-%- +runner=mtxrun") then
2072            local result = os.execute("luametatex --luaonly " .. filename)
2073            os.exit(result)
2074            return true
2075        end
2076    end
2077    return false
2078end
2079
2080-- We don't want this as we might want to expand later on:
2081
2082-- environment.files = environment.globfiles()
2083
2084-- getting it done
2085
2086if getargument("pdftex") then
2087    setargument("engine","pdftex")
2088elseif getargument("xetex") then
2089    setargument("engine","xetex")
2090end
2091
2092if getargument("timedlog") then
2093    logs.settimedlog()
2094end
2095
2096if getargument("nostats") then
2097    setargument("nostatistics",true)
2098    setargument("nostat",nil)
2099end
2100
2101if getargument("batch") then
2102    setargument("batchmode",true)
2103    setargument("batch",nil)
2104end
2105
2106if getargument("nonstop") then
2107    setargument("nonstopmode",true)
2108    setargument("nonstop",nil)
2109end
2110
2111do
2112
2113    local htmlerrorpage = getargument("htmlerrorpage")
2114    if htmlerrorpage == "scite" then
2115        directives.enable("system.showerror=scite")
2116    elseif htmlerrorpage then
2117        directives.enable("system.showerror")
2118    end
2119
2120end
2121
2122do
2123
2124    local silent = getargument("silent")
2125    if type(silent) == "string" then
2126        directives.enable(format("logs.blocked={%s}",silent))
2127    elseif silent then
2128        directives.enable("logs.blocked")
2129    end
2130
2131    local errors = getargument("errors")
2132    if type(errors) == "errors" then
2133        directives.enable(format("logs.errors={%s}",silent))
2134    elseif errors then
2135        directives.enable("logs.errors")
2136    end
2137
2138end
2139
2140if getargument("once") then
2141    multipass_nofruns = 1
2142else
2143    if getargument("runs") then
2144        multipass_nofruns = tonumber(getargument("runs")) or nil
2145    end
2146    multipass_forcedruns = tonumber(getargument("forcedruns")) or nil
2147end
2148
2149if getargument("parallel") or getargument("parallellist") then
2150    scripts.context.timed(scripts.context.parallel)
2151elseif getargument("run") then
2152    scripts.context.timed(scripts.context.autoctx)
2153elseif getargument("make") then
2154    scripts.context.timed(function() scripts.context.make() end)
2155elseif getargument("generate") then
2156    scripts.context.timed(function() scripts.context.generate() end)
2157elseif getargument("ctx") and not getargument("noctx") then
2158    scripts.context.timed(scripts.context.ctx)
2159elseif getargument("version") then
2160    application.identify()
2161    scripts.context.version()
2162elseif getargument("touch") then
2163    scripts.context.touch()
2164elseif getargument("pages") then
2165    scripts.context.pages()
2166elseif getargument("expert") then -- or getargument("special") then
2167    application.help("expert", "special")
2168elseif getargument("showmodules") or getargument("modules") then
2169    scripts.context.modules()
2170elseif getargument("showextras") or getargument("extras") then
2171    scripts.context.extras(environment.filenames[1] or getargument("extras"))
2172elseif getargument("extra") then
2173    scripts.context.extra()
2174elseif getargument("exporthelp") then
2175 -- application.export(getargument("exporthelp"),environment.filenames[1])
2176    application.export()
2177elseif getargument("help") then
2178    if environment.filenames[1] == "extras" then
2179        scripts.context.extras()
2180    else
2181        application.help("basic")
2182    end
2183elseif getargument("showtrackers") or getargument("trackers") == true then
2184    scripts.context.trackers()
2185elseif getargument("showdirectives") or getargument("directives") == true then
2186    scripts.context.directives()
2187elseif getargument("showlogcategories") then
2188    scripts.context.logcategories()
2189elseif environment.filenames[1] or getargument("nofile") then
2190 -- --
2191 -- -- How compatible is this ... we might want to resolve the wildcard at the TeX, so
2192 -- -- we just keep this as unsuported feature (but at least we know of this case):
2193 --
2194 -- if not getargument("pattern") and find(environment.filenames[1],"%*") then
2195 --     environment.filenames = dir.glob(environment.filenames[1])
2196 --  -- setargument("pattern",dir.glob(environment.filenames[1]))
2197 -- end
2198 --
2199    if scripts.context.runlua(environment.filenames[1]) then
2200        os.exit()
2201    else
2202        scripts.context.timed(scripts.context.autoctx)
2203    end
2204elseif getargument("pipe") then
2205    scripts.context.timed(scripts.context.pipe)
2206elseif getargument("purge") then
2207    -- only when no filename given, supports --pattern
2208    scripts.context.purge()
2209elseif getargument("purgeall") then
2210    -- only when no filename given, supports --pattern
2211    scripts.context.purge(true,nil,true)
2212elseif getargument("find") then
2213    -- context --find="%\framed"
2214    scripts.context.find(getargument("find"))
2215elseif getargument("pattern") then
2216    environment.filenames = dir.glob(getargument("pattern"))
2217    scripts.context.timed(scripts.context.autoctx)
2218else
2219    application.help("basic")
2220end
2221
2222-- we can wipe a signal file when done
2223
2224do
2225
2226    if getargument("wipebusy") then
2227        removefile("context-is-busy.tmp")
2228    end
2229
2230end
2231