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