1if not modules then modules = { } end modules ['file-job'] = {
2 version = 1.001,
3 comment = "companion to file-job.mkiv",
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 next, rawget, tostring, tonumber = next, rawget, tostring, tonumber
13local gsub, match, gmatch, ind = string.gsub, string.match, string.gmatch, string.find
14local insert, remove, concat, unique, imerged = table.insert, table.remove, table.concat, table.unique, table.imerged
15local validstring, formatters = string.valid, string.formatters
16local sortedhash, copytable = table.sortedhash, table.copy
17local setmetatableindex, setmetatablenewindex = table.setmetatableindex, table.setmetatablenewindex
18
19local commands = commands
20local resolvers = resolvers
21local context = context
22
23local ctx_doifelse = commands.doifelse
24
25local implement = interfaces.implement
26
27local trace_jobfiles = false trackers.register("system.jobfiles", function(v) trace_jobfiles = v end)
28local trace_structure = false trackers.register("system.structure", function(v) trace_structure = v end)
29
30local report = logs.reporter("system")
31local report_jobfiles = logs.reporter("system", "jobfiles")
32local report_structure = logs.reporter("system", "structure")
33local report_functions = logs.reporter("system", "functions")
34
35local texsetcount = tex.setcount
36local elements = interfaces.elements
37local constants = interfaces.constants
38local variables = interfaces.variables
39local settings_to_array = utilities.parsers.settings_to_array
40local allocate = utilities.storage.allocate
41
42local nameonly = file.nameonly
43local suffixonly = file.suffix
44local basename = file.basename
45local addsuffix = file.addsuffix
46local removesuffix = file.removesuffix
47local dirname = file.dirname
48local is_qualified_path = file.is_qualified_path
49
50local cleanpath = resolvers.cleanpath
51local toppath = resolvers.toppath
52local resolveprefix = resolvers.resolve
53
54local currentfile = luatex.currentfile
55
56local hasscheme = url.hasscheme
57
58local jobresolvers = resolvers.jobs
59
60local registerextrapath = resolvers.registerextrapath
61local resetextrapaths = resolvers.resetextrapaths
62local getextrapaths = resolvers.getextrapaths
63local pushextrapath = resolvers.pushextrapath
64local popextrapath = resolvers.popextrapath
65
66
67local v_text <const> = variables.text
68local v_project <const> = variables.project
69local v_environment <const> = variables.environment
70local v_product <const> = variables.product
71local v_component <const> = variables.component
72local v_yes <const> = variables.yes
73
74local v_local <const> = variables["local"]
75local v_global <const> = variables["global"]
76
77local e_start = elements.start
78local e_stop = elements.stop
79
80
81
82
83local function findctxfile(name)
84 if is_qualified_path(name) then
85 return name
86 elseif not hasscheme(name) then
87 return resolvers.finders.byscheme("loc",name) or ""
88 else
89 return resolvers.findtexfile(name) or ""
90 end
91end
92
93resolvers.findctxfile = findctxfile
94
95implement {
96 name = "processfile",
97 arguments = "string",
98 public = true,
99 protected = true,
100 actions = function(name)
101 name = findctxfile(name)
102 if name ~= "" then
103 context.input(name)
104 end
105 end
106}
107
108implement {
109 name = "doifelseinputfile",
110 arguments = "string",
111 public = true,
112 protected = true,
113 actions = function(name)
114 ctx_doifelse(findctxfile(name) ~= "")
115 end
116}
117
118implement {
119 name = "locatefilepath",
120 arguments = "string",
121 actions = function(name)
122 context(dirname(findctxfile(name)))
123 end
124}
125
126implement {
127 name = "usepath",
128 arguments = "optional",
129 public = true,
130 protected = true,
131 actions = function(paths)
132 report_jobfiles("using path: %s",paths)
133 registerextrapath(paths)
134 end
135}
136
137implement {
138 name = "pushpath",
139 arguments = "optional",
140 public = true,
141 protected = true,
142 actions = function(paths)
143 report_jobfiles("pushing path: %s",paths)
144 pushextrapath(paths)
145 end
146}
147
148implement {
149 name = "poppath",
150 public = true,
151 protected = true,
152 actions = function(paths)
153 popextrapath()
154 report_jobfiles("popping path")
155 end
156}
157
158implement {
159 name = "usesubpath",
160 arguments = "optional",
161 public = true,
162 protected = true,
163 actions = function(subpaths)
164 report_jobfiles("using subpath: %s",subpaths)
165 registerextrapath(nil,subpaths)
166 end
167}
168
169implement {
170 name = "resetpath",
171 public = true,
172 protected = true,
173 actions = function()
174 report_jobfiles("resetting path")
175 resetextrapaths()
176 end
177}
178
179implement {
180 name = "allinputpaths",
181 public = true,
182 actions = function()
183 context(concat(getextrapaths(),","))
184 end
185}
186
187implement {
188 name = "usezipfile",
189 public = true,
190 protected = true,
191 arguments = { "optional", "optional" },
192 actions = function(name,tree)
193 if tree and tree ~= "" then
194 resolvers.usezipfile(formatters["zip:///%s?tree=%s"](name,tree))
195 else
196 resolvers.usezipfile(formatters["zip:///%s"](name))
197 end
198 end
199}
200
201
202
203
204local texpatterns = { "%s.mkxl", "%s.mklx", "%s.mkiv", "%s.mkvi", "%s.tex" }
205local luapatterns = { "%s" .. utilities.lua.suffixes.luc, "%s.lmt", "%s.lua" }
206local cldpatterns = { "%s.cld" }
207local xmlpatterns = { "%s.xml" }
208
209local uselibrary = resolvers.uselibrary
210local input = context.input
211
212
213
214
215
216local processstack = { }
217local processedfile = ""
218local processedfiles = { }
219
220implement {
221 name = "processedfile",
222 actions = function()
223 context(processedfile)
224 end
225}
226
227implement {
228 name = "processedfiles",
229 actions = function()
230 context(concat(processedfiles,","))
231 end
232}
233
234implement {
235 name = "dostarttextfile",
236 public = true,
237 protected = true,
238 arguments = "string",
239 actions = function(name)
240 insert(processstack,name)
241 processedfile = name
242 insert(processedfiles,name)
243 end
244}
245
246implement {
247 name = "dostoptextfile",
248 public = true,
249 protected = true,
250 actions = function()
251 processedfile = remove(processstack) or ""
252 end
253}
254
255local function startprocessing(name,notext)
256 if not notext then
257
258 context.dostarttextfile(name)
259 end
260end
261
262local function stopprocessing(notext)
263 if not notext then
264 context.dostoptextfile()
265
266 end
267end
268
269
270
271local typestack = { }
272local currenttype = v_text
273local nofmissing = 0
274local missing = {
275 tex = setmetatableindex("number"),
276 lua = setmetatableindex("number"),
277 cld = setmetatableindex("number"),
278 xml = setmetatableindex("number"),
279}
280
281local function reportfailure(kind,name)
282 nofmissing = nofmissing + 1
283 missing[kind][name] = true
284 report_jobfiles("unknown %s file %a",kind,name)
285end
286
287
288
289local function action(name,foundname)
290 input(foundname)
291end
292local function failure(name,foundname)
293 reportfailure("tex",name)
294end
295local function usetexfile(name,onlyonce,notext)
296 startprocessing(name,notext)
297 uselibrary {
298 name = name,
299 patterns = texpatterns,
300 action = action,
301 failure = failure,
302 onlyonce = onlyonce,
303 }
304 stopprocessing(notext)
305end
306
307local function action(name,foundname)
308 dofile(foundname)
309end
310local function failure(name,foundname)
311 reportfailure("lua",name)
312end
313local function useluafile(name,onlyonce,notext)
314 uselibrary {
315 name = name,
316 patterns = luapatterns,
317 action = action,
318 failure = failure,
319 onlyonce = onlyonce,
320 }
321end
322
323local function action(name,foundname)
324 dofile(foundname)
325end
326local function failure(name,foundname)
327 reportfailure("cld",name)
328end
329local function usecldfile(name,onlyonce,notext)
330 startprocessing(name,notext)
331 uselibrary {
332 name = name,
333 patterns = cldpatterns,
334 action = action,
335 failure = failure,
336 onlyonce = onlyonce,
337 }
338 stopprocessing(notext)
339end
340
341local function action(name,foundname)
342 context.xmlprocess(foundname,"main","")
343end
344local function failure(name,foundname)
345 reportfailure("xml",name)
346end
347local function usexmlfile(name,onlyonce,notext)
348 startprocessing(name,notext)
349 uselibrary {
350 name = name,
351 patterns = xmlpatterns,
352 action = action,
353 failure = failure,
354 onlyonce = onlyonce,
355 }
356 stopprocessing(notext)
357end
358
359local suffixes = {
360 mkvi = usetexfile,
361 mkiv = usetexfile,
362 mklx = usetexfile,
363 mkxl = usetexfile,
364 tex = usetexfile,
365 luc = useluafile,
366 lua = useluafile,
367 cld = usecldfile,
368 xml = usexmlfile,
369 [""] = usetexfile,
370}
371
372local function useanyfile(name,onlyonce)
373 local s = suffixes[suffixonly(name)]
374 context(function() resolvers.pushpath(name) end)
375 if s then
376
377 s(name,onlyonce)
378 else
379 usetexfile(name,onlyonce)
380
381 end
382 context(resolvers.poppath)
383end
384
385implement { name = "loadtexfile", public = true, protected = true, actions = usetexfile, arguments = "optional" }
386implement { name = "loadluafile", public = true, protected = true, actions = useluafile, arguments = "optional" }
387implement { name = "loadcldfile", public = true, protected = true, actions = usecldfile, arguments = "optional" }
388implement { name = "loadxmlfile", public = true, protected = true, actions = usexmlfile, arguments = "optional" }
389
390implement { name = "loadtexfileonce", public = true, protected = true, actions = usetexfile, arguments = { "optional", true } }
391implement { name = "loadluafileonce", public = true, protected = true, actions = useluafile, arguments = { "optional", true } }
392implement { name = "loadcldfileonce", public = true, protected = true, actions = usecldfile, arguments = { "optional", true } }
393implement { name = "loadxmlfileonce", public = true, protected = true, actions = usexmlfile, arguments = { "optional", true } }
394
395implement { name = "useanyfile", actions = useanyfile, arguments = "string" }
396implement { name = "useanyfileonce", actions = useanyfile, arguments = { "string", true } }
397
398function jobresolvers.usefile(name,onlyonce,notext)
399 local s = suffixes[suffixonly(name)]
400 if s then
401
402 s(name,onlyonce,notext)
403 end
404end
405
406
407
408local textlevel = 0
409
410local c_textlevel <const> = tex.iscount("textlevel")
411
412local function dummyfunction() end
413
414local function startstoperror()
415 report("invalid \\%s%s ... \\%s%s structure",e_start,v_text,e_stop,v_text)
416 startstoperror = dummyfunction
417end
418
419local stopped
420
421local function starttext()
422 if textlevel == 0 then
423 if trace_jobfiles then
424 report_jobfiles("starting text")
425 end
426 context.dostarttext()
427 end
428 textlevel = textlevel + 1
429 texsetcount("global",c_textlevel,textlevel)
430end
431
432local function stoptext()
433 if not stopped then
434 if textlevel == 0 then
435 startstoperror()
436 elseif textlevel > 0 then
437 textlevel = textlevel - 1
438 end
439 texsetcount("global",c_textlevel,textlevel)
440 if textlevel <= 0 then
441 if trace_jobfiles then
442 report_jobfiles("stopping text")
443 end
444 context.dostoptext()
445 stopped = true
446 end
447 end
448end
449
450implement {
451 name = "starttext",
452 public = true,
453 protected = true,
454 actions = starttext
455}
456
457implement {
458 name = "stoptext",
459 public = true,
460 protected = true,
461 actions = stoptext
462}
463
464implement {
465 name = "forcequitjob",
466 arguments = "string",
467 public = true,
468 protected = true,
469 actions = function(reason)
470 if reason then
471 report("forcing quit: %s",reason)
472 else
473 report("forcing quit")
474 end
475 context.batchmode()
476 while textlevel >= 0 do
477 context.stoptext()
478 end
479 end
480}
481
482implement {
483 name = "forceendjob",
484 public = true,
485 protected = true,
486 actions = function()
487 report([[don't use \end to finish a document]])
488 context.stoptext()
489 end
490}
491
492implement {
493 name = "autostarttext",
494 public = true,
495 protected = true,
496 actions = function()
497 if textlevel == 0 then
498 report([[auto \starttext ... \stoptext]])
499 end
500 context.starttext()
501 end
502}
503
504implement {
505 name = "autostoptext",
506 public = true,
507 protected = true,
508 actions = stoptext
509}
510
511
512
513implement {
514 name = "processfilemany",
515 public = true,
516 protected = true,
517 arguments = { "string", false },
518 actions = useanyfile
519}
520
521implement {
522 name = "processfileonce",
523 public = true,
524 protected = true,
525 arguments = { "string", true },
526 actions = useanyfile
527}
528
529implement {
530 name = "processfilenone",
531 arguments = "string",
532 public = true,
533 protected = true,
534 actions = dummyfunction,
535}
536
537local tree = { type = "text", name = "", branches = { } }
538local treestack = { }
539local top = tree.branches
540local root = tree
541local namestack = { }
542local namelist = { }
543local namehash = { }
544local nametop = false
545
546local project_stack = { }
547local product_stack = { }
548local component_stack = { }
549local environment_stack = { }
550
551local stacks = {
552 [v_project ] = project_stack,
553 [v_product ] = product_stack,
554 [v_component ] = component_stack,
555 [v_environment] = environment_stack,
556}
557
558
559
560local function pushtree(what,name)
561 local t = { }
562 top[#top+1] = { type = what, name = name, branches = t }
563 insert(treestack,top)
564 top = t
565end
566
567local function pushcurrent(what,name)
568 if what == v_product or what == v_component or what == v_text then
569 insert(namestack,name)
570 nametop = copytable(namestack)
571 namelist[#namelist+1] = nametop
572 insert(namehash,#namelist)
573 end
574end
575
576local function poptree()
577 top = remove(treestack)
578 if top and top[#top].branches == 0 then
579 top[#top].branches = nil
580 end
581end
582
583local function popcurrent(what)
584 if what == v_product or what == v_component then
585 remove(namestack)
586 remove(namehash)
587 end
588end
589
590do
591
592 local function log_tree(report,top,depth)
593 report("%s%s: %s",depth,top.type,top.name)
594 local branches = top.branches
595 if branches then
596 local n = #branches
597 if n > 0 then
598 depth = depth .. " "
599 for i=1,n do
600 log_tree(report,branches[i],depth)
601 end
602 else
603 top.brances = nil
604 end
605 end
606 end
607
608 logs.registerfinalactions(function()
609 root.name = environment.jobname
610
611 logs.startfilelogging(report,"used files")
612 log_tree(report,root,"")
613 logs.stopfilelogging()
614
615 if nofmissing > 0 and logs.loggingerrors() then
616 logs.starterrorlogging(report,"missing files")
617 for kind, list in sortedhash(missing) do
618 for name in sortedhash(list) do
619 report("%w%s %s",6,kind,name)
620 end
621 end
622 logs.stoperrorlogging()
623 end
624 end)
625
626end
627
628local jobstructure = job.structure or { }
629job.structure = jobstructure
630jobstructure.collected = jobstructure.collected or { }
631jobstructure.tobesaved = root
632jobstructure.components = { }
633jobstructure.namestack = { }
634
635function collectstructure(collected)
636 local namestack = { }
637 local n = 0
638 local function collect(root,result,stack)
639 local branches = root.branches
640 if branches then
641 local s = copytable(stack)
642 local m = #s + 1
643 for i=1,#branches do
644 local branch = branches[i]
645 local what = branch.type
646 if what == v_component then
647 result[#result+1] = branch.name
648 end
649 s[m] = branch.name
650 if what == v_product or what == v_component then
651 n = n + 1
652 namestack[n] = copytable(s)
653 end
654 collect(branch,result,s)
655 end
656 end
657 return result
658 end
659 local components = collect(collected,{},{})
660
661 return components, namestack
662end
663
664local function initialize()
665 jobstructure.components, jobstructure.namestack = collectstructure(jobstructure.collected)
666end
667
668function jobresolvers.collectstructure(collected)
669 return collectstructure(collected or jobstructure.collected)
670end
671
672do
673 local reported = { }
674
675 function jobresolvers.currentstructure()
676
677 local n = namehash[#namehash]
678 if n then
679 if trace_structure and not reported[n] then
680 local namestack = jobstructure.namestack
681 local new = namelist [n]
682 local old = namestack[n]
683 new = new and concat(new," ") or ""
684 old = old and concat(old," ") or ""
685 if old == new then
686 report_structure("%02i : %a",n,old)
687 else
688 report_structure("%02i : %a <> %a",n,old,new)
689 end
690 reported[n] = true
691 end
692 return n, namelist[n]
693 end
694 end
695
696end
697
698function jobresolvers.namelist(n,utilitydata)
699 if utilitydata then
700 return utilitydata.namestack[n]
701 else
702 return namelist[n] or jobstructure.namestack[n]
703 end
704end
705
706job.register('job.structure.collected',root,initialize)
707
708
709
710
711local ctx_processfilemany = context.processfilemany
712local ctx_processfileonce = context.processfileonce
713local ctx_processfilenone = context.processfilenone
714
715
716
717local function processfilecommon(name,action)
718
719
720
721
722
723
724
725
726
727 action(name)
728end
729
730local function processfilemany(name) processfilecommon(name,ctx_processfilemany) end
731local function processfileonce(name) processfilecommon(name,ctx_processfileonce) end
732local function processfilenone(name) processfilecommon(name,ctx_processfilenone) end
733
734local processors = utilities.storage.allocate {
735
736
737
738
739
740
741
742 [v_text] = {
743 [v_text] = { "many", processfilemany },
744 [v_project] = { "once", processfileonce },
745 [v_environment] = { "once", processfileonce },
746 [v_product] = { "many", processfilemany },
747 [v_component] = { "many", processfilemany },
748 },
749 [v_project] = {
750 [v_text] = { "many", processfilemany },
751 [v_project] = { "none", processfilenone },
752 [v_environment] = { "once", processfileonce },
753 [v_product] = { "none", processfilenone },
754 [v_component] = { "none", processfilenone },
755 },
756 [v_environment] = {
757 [v_text] = { "many", processfilemany },
758 [v_project] = { "none", processfilenone },
759 [v_environment] = { "once", processfileonce },
760 [v_product] = { "none", processfilenone },
761 [v_component] = { "none", processfilenone },
762 },
763 [v_product] = {
764 [v_text] = { "many", processfilemany },
765 [v_project] = { "once", processfileonce },
766 [v_environment] = { "once", processfileonce },
767 [v_product] = { "many", processfilemany },
768 [v_component] = { "many", processfilemany },
769 },
770 [v_component] = {
771 [v_text] = { "many", processfilemany },
772 [v_project] = { "once", processfileonce },
773 [v_environment] = { "once", processfileonce },
774 [v_product] = { "none", processfilenone },
775 [v_component] = { "many", processfilemany },
776 }
777}
778
779local start = {
780 [v_text] = nil,
781 [v_project] = nil,
782 [v_environment] = context.startreadingfile,
783 [v_product] = context.starttext,
784 [v_component] = context.starttext,
785}
786
787local stop = {
788 [v_text] = nil,
789 [v_project] = nil,
790 [v_environment] = context.stopreadingfile,
791 [v_product] = context.stoptext,
792 [v_component] = context.stoptext,
793}
794
795jobresolvers.processors = processors
796
797local function topofstack(what)
798 local stack = stacks[what]
799 return stack and stack[#stack] or environment.jobname
800end
801
802local function productcomponent()
803 local product = product_stack[#product_stack]
804 if product and product ~= "" then
805 local component = component_stack[1]
806 if component and component ~= "" then
807 return component
808 end
809 end
810end
811
812local function justacomponent()
813 local product = product_stack[#product_stack]
814 if not product or product == "" then
815 local component = component_stack[1]
816 if component and component ~= "" then
817 return component
818 end
819 end
820end
821
822jobresolvers.productcomponent = productcomponent
823jobresolvers.justacomponent = justacomponent
824
825function jobresolvers.currentproject () return topofstack(v_project ) end
826function jobresolvers.currentproduct () return topofstack(v_product ) end
827function jobresolvers.currentcomponent () return topofstack(v_component ) end
828function jobresolvers.currentenvironment() return topofstack(v_environment) end
829
830local done = { }
831local tolerant = false
832
833local function process(what,name)
834 local depth = #typestack
835 local process
836
837 name = resolveprefix(name)
838
839
840
841 process = processors[currenttype][what]
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857 if process then
858 local method = process[1]
859 if method == "none" then
860 if trace_jobfiles then
861 report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"ignoring",what,name,currenttype,topofstack(currenttype))
862 end
863 elseif method == "once" and done[name] then
864 if trace_jobfiles then
865 report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"skipping",what,name,currenttype,topofstack(currenttype))
866 end
867 else
868
869
870 done[name] = true
871 local before = start[what]
872 local after = stop [what]
873 if trace_jobfiles then
874 report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"processing",what,name,currenttype,topofstack(currenttype))
875 end
876 if before then
877 before()
878 end
879 process[2](name)
880 if after then
881 after()
882 end
883 end
884 else
885 if trace_jobfiles then
886 report_jobfiles("%s : %s : %s %s %a in %s %a",depth,"none","ignoring",what,name,currenttype,topofstack(currenttype))
887 end
888 end
889end
890
891local scan_delimited = tokens.scanners.delimited
892
893local function getname()
894 return scan_delimited(91,93) or scan_delimited(0,32)
895end
896
897implement { name = "project", public = true, protected = true, actions = function() process(v_project, getname()) end }
898implement { name = "environment", public = true, protected = true, actions = function() process(v_environment,getname()) end }
899implement { name = "product", public = true, protected = true, actions = function() process(v_product, getname()) end }
900implement { name = "component", public = true, protected = true, actions = function() process(v_component, getname()) end }
901
902implement { name = "useproject", public = true, protected = true, actions = function(name) process(v_project, name) end, arguments = "optional" }
903implement { name = "useenvironment", public = true, protected = true, actions = function(name) process(v_environment,name) end, arguments = "optional" }
904implement { name = "useproduct", public = true, protected = true, actions = function(name) process(v_product, name) end, arguments = "optional" }
905implement { name = "usecomponent", public = true, protected = true, actions = function(name) process(v_component, name) end, arguments = "optional" }
906
907implement {
908 name = "ignorefile",
909 public = true,
910 protected = true,
911 arguments = "array",
912 actions = function(s)
913 for i=1,#s do
914 resolvers.ignorelibrary(s[i])
915 end
916 end
917}
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950local function gotonextlevel(what,how,name)
951 insert(stacks[what],name)
952 insert(typestack,currenttype)
953 currenttype = what
954 pushtree(what,name)
955 pushcurrent(what,name)
956 token.expandmacro(e_start..":"..what..":"..how)
957end
958
959local function gotopreviouslevel(what,how)
960 token.expandmacro(e_stop..":"..what..":"..how)
961 popcurrent(what)
962 poptree()
963 currenttype = remove(typestack) or v_text
964 remove(stacks[what])
965
966end
967
968local function autoname()
969 local name = scan_delimited(91,93) or scan_delimited(0,32)
970 if name == "*" then
971
972 name = nameonly(currentfile() or name)
973 end
974 return name
975end
976
977implement { name = "startproject", public = true, protected = true, actions = function() gotonextlevel(v_project, v_global, autoname()) end }
978implement { name = "startproduct", public = true, protected = true, actions = function() gotonextlevel(v_product, v_global, autoname()) end }
979implement { name = "startcomponent", public = true, protected = true, actions = function() gotonextlevel(v_component, v_global, autoname()) end }
980implement { name = "startlocalcomponent", public = true, protected = true, actions = function() gotonextlevel(v_component, v_local, autoname()) end }
981implement { name = "startenvironment", public = true, protected = true, actions = function() gotonextlevel(v_environment, v_global, autoname()) end }
982
983implement { name = "stopproject", public = true, protected = true, actions = function() gotopreviouslevel(v_project, v_global) end }
984implement { name = "stopproduct", public = true, protected = true, actions = function() gotopreviouslevel(v_product, v_global) end }
985implement { name = "stopcomponent", public = true, protected = true, actions = function() gotopreviouslevel(v_component, v_global) end }
986implement { name = "stoplocalcomponent", public = true, protected = true, actions = function() gotopreviouslevel(v_component, v_local ) end }
987implement { name = "stopenvironment", public = true, protected = true, actions = function() gotopreviouslevel(v_environment, v_global) end }
988
989implement { name = "currentproject", public = true, actions = function() context(topofstack(v_project )) end }
990implement { name = "currentproduct", public = true, actions = function() context(topofstack(v_product )) end }
991implement { name = "currentcomponent", public = true, actions = function() context(topofstack(v_component )) end }
992implement { name = "currentenvironment", public = true, actions = function() context(topofstack(v_environment)) end }
993
994
995
996
997
998
999
1000
1001
1002
1003
1004local report_examodes = logs.reporter("system","examodes")
1005
1006local function convertexamodes(str)
1007 local x = xml.convert(str)
1008 for e in xml.collected(x,"exa:variable") do
1009 local label = e.at and e.at.label
1010 if label and label ~= "" then
1011 local data = xml.text(e)
1012 local mode = match(label,"^mode:(.+)$")
1013 if mode then
1014 context.enablemode { formatters["%s:%s"](mode,data) }
1015 end
1016 context.setvariable("exa:variables",label,(gsub(data,"([{}])","\\%1")))
1017 end
1018 end
1019end
1020
1021function environment.loadexamodes(filename)
1022 if not filename or filename == "" then
1023
1024 filename = removesuffix(tex.jobname)
1025 end
1026 filename = resolvers.findfile(addsuffix(filename,'ctm')) or ""
1027 if filename ~= "" then
1028 report_examodes("loading %a",filename)
1029 convertexamodes(io.loaddata(filename))
1030 else
1031 report_examodes("no mode file %a",filename)
1032 end
1033end
1034
1035implement {
1036 name = "loadexamodes",
1037 actions = environment.loadexamodes,
1038 public = true,
1039 protected = true,
1040 arguments = "optional"
1041}
1042
1043
1044
1045
1046
1047
1048document = document or {
1049 arguments = allocate(),
1050 files = allocate(),
1051 variables = allocate(),
1052 options = {
1053 commandline = {
1054 environments = allocate(),
1055 modules = allocate(),
1056 modes = allocate(),
1057 },
1058 ctxfile = {
1059 environments = allocate(),
1060 modules = allocate(),
1061 modes = allocate(),
1062 },
1063 },
1064 functions = table.setmetatablenewindex(function(t,k,v)
1065 if rawget(t,k) then
1066 report_functions("overloading document function %a",k)
1067 end
1068 rawset(t,k,v)
1069 return v
1070 end),
1071}
1072
1073function document.setargument(key,value)
1074 document.arguments[key] = value
1075end
1076
1077function document.setdefaultargument(key,default)
1078 local v = document.arguments[key]
1079 if v == nil or v == "" then
1080 document.arguments[key] = default
1081 end
1082end
1083
1084function document.setfilename(i,name)
1085 if name then
1086 document.files[tonumber(i)] = name
1087 else
1088 document.files[#document.files+1] = tostring(i)
1089 end
1090end
1091
1092function document.getargument(key,default)
1093 local v = document.arguments[key]
1094 if type(v) == "boolean" then
1095 v = (v and "yes") or "no"
1096 document.arguments[key] = v
1097 end
1098 return v or default or ""
1099end
1100
1101function document.getfilename(i)
1102 return document.files[tonumber(i)] or ""
1103end
1104
1105implement {
1106 name = "setdocumentargument",
1107 actions = document.setargument,
1108 arguments = "2 strings"
1109}
1110
1111implement {
1112 name = "setdocumentdefaultargument",
1113 actions = document.setdefaultargument,
1114 arguments = "2 strings"
1115}
1116
1117implement {
1118 name = "setdocumentfilename",
1119 actions = document.setfilename,
1120 arguments = { "integer", "string" }
1121}
1122
1123implement {
1124 name = "getdocumentargument",
1125 actions = { document.getargument, context },
1126 arguments = "2 strings"
1127}
1128
1129implement {
1130 name = "getdocumentfilename",
1131 actions = { document.getfilename, context },
1132 arguments = "integer"
1133}
1134
1135function document.setcommandline()
1136
1137
1138
1139 local arguments = document.arguments
1140 local files = document.files
1141 local options = document.options
1142
1143 for k, v in next, environment.arguments do
1144 local a = gsub(k,"^c:","")
1145 if arguments[a] == nil then
1146 arguments[a] = v
1147 end
1148 end
1149
1150
1151
1152
1153
1154 if arguments.timing then
1155 context.usemodule { "timing" }
1156 end
1157
1158 if arguments.usage then
1159 trackers.enable("system.usage")
1160 end
1161
1162 if arguments.batchmode then
1163 context.batchmode(false)
1164 end
1165
1166 if arguments.nonstopmode then
1167 context.nonstopmode(false)
1168 end
1169
1170 if arguments.nostatistics then
1171 directives.enable("system.nostatistics")
1172 end
1173
1174 if arguments.notuc then
1175 directives.disable("job.save")
1176 end
1177
1178 if arguments.paranoid then
1179 context.setvalue("maxreadlevel",1)
1180 end
1181
1182 if validstring(arguments.path) then
1183 context.usepath { arguments.path }
1184 end
1185
1186 if arguments.export then
1187 context.setupbackend { export = v_yes }
1188 end
1189
1190 local inputfile = validstring(arguments.input)
1191
1192 if inputfile and dirname(inputfile) == "." and lfs.isfile(inputfile) then
1193
1194 inputfile = basename(inputfile)
1195 end
1196
1197 local forcedruns = arguments.forcedruns
1198 local kindofrun = arguments.kindofrun
1199 local currentrun = arguments.currentrun
1200 local maxnofruns = arguments.maxnofruns or arguments.runs
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212 context.setupsystem {
1213 directory = validstring(arguments.setuppath),
1214 inputfile = inputfile,
1215 file = validstring(arguments.result),
1216 random = validstring(arguments.randomseed),
1217
1218 n = validstring(kindofrun),
1219 m = validstring(currentrun),
1220 }
1221
1222 forcedruns = tonumber(forcedruns) or 0
1223 kindofrun = tonumber(kindofrun) or 0
1224 maxnofruns = tonumber(maxnofruns) or 0
1225 currentrun = tonumber(currentrun) or 0
1226
1227 local prerollrun = forcedruns > 0 and currentrun > 0 and currentrun < forcedruns
1228
1229 environment.forcedruns = forcedruns
1230 environment.kindofrun = kindofrun
1231 environment.maxnofruns = maxnofruns
1232 environment.currentrun = currentrun
1233 environment.prerollrun = prerollrun
1234
1235 context.setconditional("prerollrun",prerollrun)
1236
1237 if validstring(arguments.arguments) then
1238 context.setupenv { arguments.arguments }
1239 end
1240
1241 if arguments.once then
1242 directives.enable("system.runonce")
1243 end
1244
1245 if arguments.noarrange then
1246 context.setuparranging { variables.disable }
1247 end
1248
1249
1250
1251 local commandline = options.commandline
1252
1253 commandline.environments = table.append(commandline.environments,settings_to_array(validstring(arguments.environment)))
1254 commandline.modules = table.append(commandline.modules, settings_to_array(validstring(arguments.usemodule)))
1255 commandline.modes = table.append(commandline.modes, settings_to_array(validstring(arguments.mode)))
1256
1257
1258
1259 if #files == 0 then
1260 local list = settings_to_array(validstring(arguments.files))
1261 if list and #list > 0 then
1262 files = list
1263 end
1264 end
1265
1266 if #files == 0 then
1267 files = { validstring(arguments.input) }
1268 end
1269
1270
1271
1272 document.arguments = arguments
1273 document.files = files
1274
1275end
1276
1277
1278
1279local function apply(list,action)
1280 if list then
1281 for i=1,#list do
1282 action { list[i] }
1283 end
1284 end
1285end
1286
1287function document.setmodes()
1288 apply(document.options.ctxfile .modes,context.enablemode)
1289 apply(document.options.commandline.modes,context.enablemode)
1290end
1291
1292function document.setmodules()
1293 apply(document.options.ctxfile .modules,context.usemodule)
1294 apply(document.options.commandline.modules,context.usemodule)
1295end
1296
1297function document.setenvironments()
1298 apply(document.options.ctxfile .environments,context.environment)
1299 apply(document.options.commandline.environments,context.environment)
1300end
1301
1302function document.setfilenames()
1303 local initialize = environment.initializefilenames
1304 if initialize then
1305 initialize()
1306 else
1307
1308 end
1309end
1310
1311implement { name = "setdocumentcommandline", actions = document.setcommandline, onlyonce = true }
1312implement { name = "setdocumentmodes", actions = document.setmodes, onlyonce = true }
1313implement { name = "setdocumentmodules", actions = document.setmodules, onlyonce = true }
1314implement { name = "setdocumentenvironments", actions = document.setenvironments, onlyonce = true }
1315implement { name = "setdocumentfilenames", actions = document.setfilenames, onlyonce = true }
1316
1317do
1318
1319 logs.registerfinalactions(function()
1320 local foundintrees = resolvers.foundintrees()
1321 if #foundintrees > 0 then
1322 logs.startfilelogging(report,"used files")
1323 for i=1,#foundintrees do
1324 report("%4i: % T",i,foundintrees[i])
1325 end
1326 logs.stopfilelogging()
1327 end
1328 end)
1329
1330 logs.registerfinalactions(function()
1331 local files = document.files
1332 local arguments = document.arguments
1333
1334 logs.startfilelogging(report,"commandline options")
1335 if arguments and next(arguments) then
1336 for argument, value in sortedhash(arguments) do
1337 report(" %s=%A",argument,value)
1338 end
1339 else
1340 report("no arguments")
1341 end
1342 logs.stopfilelogging()
1343
1344 logs.startfilelogging(report,"commandline files")
1345 if files and #files > 0 then
1346 for i=1,#files do
1347 report("% 4i: %s",i,files[i])
1348 end
1349 else
1350 report("no files")
1351 end
1352 logs.stopfilelogging()
1353 end)
1354
1355end
1356
1357if environment.initex then
1358
1359 logs.registerfinalactions(function()
1360 local startfilelogging = logs.startfilelogging
1361 local stopfilelogging = logs.stopfilelogging
1362 startfilelogging(report,"stored tables")
1363 for k,v in sortedhash(storage.data) do
1364 report("%03i %s",k,v[1])
1365 end
1366 stopfilelogging()
1367 startfilelogging(report,"stored modules")
1368 for k,v in sortedhash(lua.bytedata) do
1369 report("%03i %s %s",k,v.name)
1370 end
1371 stopfilelogging()
1372 startfilelogging(report,"stored attributes")
1373 for k,v in sortedhash(attributes.names) do
1374 report("%03i %s",k,v)
1375 end
1376 stopfilelogging()
1377 startfilelogging(report,"stored catcodetables")
1378 for k,v in sortedhash(catcodes.names) do
1379 report("%03i % t",k,v)
1380 end
1381 stopfilelogging()
1382 startfilelogging(report,"stored corenamespaces")
1383 for k,v in sortedhash(interfaces.corenamespaces) do
1384 report("%03i %s",k,v)
1385 end
1386 stopfilelogging()
1387 end)
1388
1389end
1390
1391implement {
1392 name = "continueifinputfile",
1393 public = true,
1394 protected = true,
1395 arguments = "string",
1396 actions = function(inpname,basetoo)
1397 local inpnamefull = addsuffix(inpname,"tex")
1398 local inpfilefull = addsuffix(environment.inputfilename,"tex")
1399 local continue = inpnamefull == inpfilefull
1400
1401 if not continue then
1402 continue = inpnamefull == basename(inpfilefull)
1403 end
1404 if continue then
1405 report("continuing input file %a",inpname)
1406 else
1407 context.endinput()
1408 end
1409
1410 end
1411}
1412
1413
1414
1415local helpers = resolvers.finders.helpers
1416local validhashed = helpers.validhashed
1417local registerhashed = helpers.registerhashed
1418local registerfilescheme = helpers.registerfilescheme
1419
1420implement {
1421 name = "registerhashedfiles",
1422 public = true,
1423 protected = true,
1424 arguments = "optional",
1425 actions = function(list)
1426 for name in gmatch(list,"[^, ]+") do
1427 registerhashed(name)
1428 end
1429 end,
1430}
1431
1432implement {
1433 name = "registerfilescheme",
1434 public = true,
1435 protected = true,
1436 arguments = "optional",
1437 actions = function(list)
1438 for name in gmatch(list,"[^, ]+") do
1439 registerfilescheme(name)
1440 end
1441 end,
1442}
1443
1444implement {
1445 name = "doifelsevalidhashedfiles",
1446 public = true,
1447 protected = true,
1448 arguments = "string",
1449 actions = function(name)
1450 ctx_doifelse(validhashed(name))
1451 end,
1452}
1453
1454implement {
1455 name = "adddefaultsuffix",
1456 public = true,
1457 protected = true,
1458 actions = function(list)
1459 resolvers.defaultsuffixes = unique(
1460 imerged(
1461 resolvers.defaultsuffixes,
1462 list
1463 )
1464 )
1465 end,
1466 arguments = "array",
1467}
1468
1469
1470
1471implement {
1472 name = "filebasename",
1473 public = true,
1474 arguments = "argument",
1475 actions = { basename, context },
1476}
1477
1478implement {
1479 name = "filenameonly",
1480 public = true,
1481 arguments = "argument",
1482 actions = { nameonly, context },
1483}
1484
1485implement {
1486 name = "filedirname",
1487 public = true,
1488 arguments = "argument",
1489 actions = { dirname, context },
1490}
1491
1492implement {
1493 name = "filesuffix",
1494 public = true,
1495 arguments = "argument",
1496 actions = { dirname, context },
1497}
1498 |