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, 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
45 helpinfo = "mtx-context.xml",
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
93
94local report = application.report
95
96scripts = scripts or { }
97scripts.context = scripts.context or { }
98
99
100
101if jit then
102 setargument("engine","luajittex")
103 setargument("jit",nil)
104elseif getargument("luatex") then
105 setargument("engine","luatex")
106elseif getargument("jit") or getargument("luajittex") then
107
108
109 setargument("engine","luajittex")
110end
111
112
113
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
129
130
131
132
133
134
135
136
137
138if environment.validengines[engine_new] and engine_new ~= environment.basicengines[engine_old] then
139 restart(engine_old,engine_new)
140end
141
142
143
144
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
178}
179
180
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",
195 "tua",
196 "tup", "ted", "tes",
197 "top",
198 "log",
199 "tmp",
200 "run",
201 "bck",
202 "rlg",
203 "ctl",
204 "mpt", "mpx", "mpd", "mpo", "mpb",
205 "prep",
206 "pgf",
207 "aux", "blg",
208}
209
210local temporary_suffixes = {
211 "prep",
212}
213
214local synctex_runfiles = {
215 "synctex",
216 "synctex.gz",
217
218}
219
220local persistent_runfiles = {
221 "tuo",
222 "tub",
223 "top",
224 "tuc",
225 "bbl",
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
250
251local ctxrunner = { }
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
282
283 local usedname = ctxdata.ctxname
284 local found = validfile(usedname)
285
286
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
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
350
351local multipass_suffixes = { ".tuc" }
352local multipass_nofruns = 9
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)
445 filename = fileaddsuffix(filename,"tex")
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]
465 end
466 if predata.engine ~= engine_old then
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
476
477local pdfview
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"
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"
502 pdfview.close(pdfname)
503 stoptiming("pdfview")
504end
505
506
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)
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)
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
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
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)
580 pdf_open(resultname,pdfview)
581 else
582 os.execute(command)
583 end
584 end
585end
586
587local function run_texexec(filename,a_purge,a_purgeall)
588 if false then
589
590
591
592
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)
610 end
611 end
612 end
613end
614
615
616
617local function flags_to_string(flags,prefix)
618
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
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
653 mainfile = filename
654 filelist = { filename }
655
656 elseif a_nofile then
657
658 mainfile = usedfiles.nop
659 filelist = { usedfiles.nop }
660
661 elseif #files > 0 then
662
663 mainfile = usedfiles.yes
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)
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)
678 end
679 if formatfile and scriptfile then
680
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)
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
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
802 else
803 return flag or plus
804 end
805 end
806
807
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
853
854
855
856
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,
871 trackers = trackers,
872 experiments = experiments,
873
874 result = validstring(resultname),
875 input = validstring(getargument("input") or filename),
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
890 if c_flags[k] == nil then
891 c_flags[k] = v
892 end
893 end
894
895
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 = ""
901 usedname = fulljobname
902 end
903
904
905 local l_flags = {
906 ["interaction"] = a_batchmode,
907 ["synctex"] = false,
908
909
910
911
912
913
914
915 ["jobname"] = usedname,
916 ["jithash"] = a_jithash,
917 ["permitloadlib"] = a_permitloadlib,
918 }
919
920 local directives = { }
921
922
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
937
938
939
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
950
951
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("")
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
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)
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
1017
1018
1019
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
1057
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
1070
1071
1072
1073
1074
1075
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)
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
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()
1137
1138
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)
1197end
1198
1199local make_mkii_format
1200
1201do
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")
1208 else
1209 print("mktexlsr silent run")
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))
1227 else
1228 print(format("%s silent make > format %q made in %.3f seconds",texengine,texformat,runtime))
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
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
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
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)
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)
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
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
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
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
1626 for i=1,#cards do
1627 resolvers.findwildcardfiles(cards[i],list)
1628 end
1629
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 = { }
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)
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
1689
1690function scripts.context.extras(pattern)
1691
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
1743
1744do
1745
1746 local popen = io.popen
1747 local close = io.close
1748
1749 local gobble = io.gobble or function(f) f:read("l") end
1750
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
1860
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
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
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
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
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)
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
2081
2082
2083
2084
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
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
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
2192
2193
2194
2195
2196
2197
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
2208 scripts.context.purge()
2209elseif getargument("purgeall") then
2210
2211 scripts.context.purge(true,nil,true)
2212elseif getargument("find") then
2213
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
2223
2224do
2225
2226 if getargument("wipebusy") then
2227 removefile("context-is-busy.tmp")
2228 end
2229
2230end
2231 |