file-job.lua /size: 35 Kb    last modification: 2023-12-21 09:44
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-- in retrospect dealing it's not that bad to deal with the nesting
10-- and push/poppign at the tex end
11
12local next, rawget, tostring, tonumber = next, rawget, tostring, tonumber
13local gsub, match, find = string.gsub, string.match, string.find
14local insert, remove, concat = table.insert, table.remove, table.concat
15local validstring, formatters = string.valid, string.formatters
16local sortedhash = table.sortedhash
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)
28
29local report            = logs.reporter("system")
30local report_jobfiles   = logs.reporter("system", "jobfiles")
31local report_functions  = logs.reporter("system", "functions")
32
33local texsetcount       = tex.setcount
34local elements          = interfaces.elements
35local constants         = interfaces.constants
36local variables         = interfaces.variables
37local settings_to_array = utilities.parsers.settings_to_array
38local allocate          = utilities.storage.allocate
39
40local nameonly          = file.nameonly
41local suffixonly        = file.suffix
42local basename          = file.basename
43local addsuffix         = file.addsuffix
44local removesuffix      = file.removesuffix
45local dirname           = file.dirname
46local is_qualified_path = file.is_qualified_path
47
48local cleanpath         = resolvers.cleanpath
49local toppath           = resolvers.toppath
50local resolveprefix     = resolvers.resolve
51
52local currentfile       = luatex.currentfile
53
54local hasscheme         = url.hasscheme
55
56local jobresolvers      = resolvers.jobs
57
58local registerextrapath = resolvers.registerextrapath
59local resetextrapaths   = resolvers.resetextrapaths
60local getextrapaths     = resolvers.getextrapath
61local pushextrapath     = resolvers.pushextrapath
62local popextrapath      = resolvers.popextrapath
63
64----- v_outer           = variables.outer
65local v_text            = variables.text
66local v_project         = variables.project
67local v_environment     = variables.environment
68local v_product         = variables.product
69local v_component       = variables.component
70local v_yes             = variables.yes
71
72-- main code .. there is some overlap .. here we have loc://
73
74local function findctxfile(name) -- loc ? any ?
75    if is_qualified_path(name) then -- maybe when no suffix do some test for tex
76        return name
77    elseif not hasscheme(name) then
78        return resolvers.finders.byscheme("loc",name) or ""
79    else
80        return resolvers.findtexfile(name) or ""
81    end
82end
83
84resolvers.findctxfile = findctxfile
85
86implement {
87    name      = "processfile",
88    arguments = "string",
89    actions   = function(name)
90        name = findctxfile(name)
91        if name ~= "" then
92            context.input(name)
93        end
94    end
95}
96
97implement {
98    name      = "doifelseinputfile",
99    arguments = "string",
100    actions   = function(name)
101        ctx_doifelse(findctxfile(name) ~= "")
102    end
103}
104
105implement {
106    name      = "locatefilepath",
107    arguments = "string",
108    actions   = function(name)
109        context(dirname(findctxfile(name)))
110    end
111}
112
113implement {
114    name      = "usepath",
115    arguments = "string",
116    actions   = function(paths)
117        report_jobfiles("using path: %s",paths)
118        registerextrapath(paths)
119    end
120}
121
122implement {
123    name      = "pushpath",
124    arguments = "string",
125    actions   = function(paths)
126        report_jobfiles("pushing path: %s",paths)
127        pushextrapath(paths)
128    end
129}
130
131implement {
132    name      = "poppath",
133    actions   = function(paths)
134        popextrapath()
135        report_jobfiles("popping path")
136    end
137}
138
139implement {
140    name      = "usesubpath",
141    arguments = "string",
142    actions   = function(subpaths)
143        report_jobfiles("using subpath: %s",subpaths)
144        registerextrapath(nil,subpaths)
145    end
146}
147
148implement {
149    name      = "resetpath",
150    actions   = function()
151        report_jobfiles("resetting path")
152        resetextrapaths()
153    end
154}
155
156implement {
157    name      = "allinputpaths",
158    actions   = function()
159        context(concat(getextrapaths(),","))
160    end
161}
162
163implement {
164    name      = "usezipfile",
165    arguments = "2 strings",
166    actions   = function(name,tree)
167        if tree and tree ~= "" then
168            resolvers.usezipfile(formatters["zip:///%s?tree=%s"](name,tree))
169        else
170            resolvers.usezipfile(formatters["zip:///%s"](name))
171        end
172    end
173}
174
175-- moved from tex to lua:
176
177local texpatterns = { "%s.mkvi", "%s.mkiv", "%s.mklx", "%s.mkxl", "%s.tex" }
178local luapatterns = { "%s" .. utilities.lua.suffixes.luc, "%s.lua", "%s.lmt" }
179local cldpatterns = { "%s.cld" }
180local xmlpatterns = { "%s.xml" }
181
182local uselibrary = resolvers.uselibrary
183local input      = context.input
184
185-- status
186--
187-- these need to be synced with input stream:
188
189local processstack   = { }
190local processedfile  = ""
191local processedfiles = { }
192
193implement {
194    name    = "processedfile",
195    actions = function()
196        context(processedfile)
197    end
198}
199
200implement {
201    name    = "processedfiles",
202    actions = function()
203        context(concat(processedfiles,","))
204    end
205}
206
207implement {
208    name      = "dostarttextfile",
209    arguments = "string",
210    actions   = function(name)
211        insert(processstack,name)
212        processedfile = name
213        insert(processedfiles,name)
214    end
215}
216
217implement {
218    name      = "dostoptextfile",
219    actions   = function()
220        processedfile = remove(processstack) or ""
221    end
222}
223
224local function startprocessing(name,notext)
225    if not notext then
226     -- report("begin file %a at line %a",name,status.linenumber or 0)
227        context.dostarttextfile(name)
228    end
229end
230
231local function stopprocessing(notext)
232    if not notext then
233        context.dostoptextfile()
234     -- report("end file %a at line %a",name,status.linenumber or 0)
235    end
236end
237
238--
239
240local typestack   = { }
241local currenttype = v_text
242local nofmissing  = 0
243local missing     = {
244    tex = setmetatableindex("number"),
245    lua = setmetatableindex("number"),
246    cld = setmetatableindex("number"),
247    xml = setmetatableindex("number"),
248}
249
250local function reportfailure(kind,name)
251    nofmissing = nofmissing + 1
252    missing[kind][name] = true
253    report_jobfiles("unknown %s file %a",kind,name)
254end
255
256--
257
258local function action(name,foundname)
259    input(foundname)
260end
261local function failure(name,foundname)
262    reportfailure("tex",name)
263end
264local function usetexfile(name,onlyonce,notext)
265    startprocessing(name,notext)
266    uselibrary {
267        name     = name,
268        patterns = texpatterns,
269        action   = action,
270        failure  = failure,
271        onlyonce = onlyonce,
272    }
273    stopprocessing(notext)
274end
275
276local function action(name,foundname)
277    dofile(foundname)
278end
279local function failure(name,foundname)
280    reportfailure("lua",name)
281end
282local function useluafile(name,onlyonce,notext)
283    uselibrary {
284        name     = name,
285        patterns = luapatterns,
286        action   = action,
287        failure  = failure,
288        onlyonce = onlyonce,
289    }
290end
291
292local function action(name,foundname)
293    dofile(foundname)
294end
295local function failure(name,foundname)
296    reportfailure("cld",name)
297end
298local function usecldfile(name,onlyonce,notext)
299    startprocessing(name,notext)
300    uselibrary {
301        name     = name,
302        patterns = cldpatterns,
303        action   = action,
304        failure  = failure,
305        onlyonce = onlyonce,
306    }
307    stopprocessing(notext)
308end
309
310local function action(name,foundname)
311    context.xmlprocess(foundname,"main","")
312end
313local function failure(name,foundname)
314    reportfailure("xml",name)
315end
316local function usexmlfile(name,onlyonce,notext)
317    startprocessing(name,notext)
318    uselibrary {
319        name     = name,
320        patterns = xmlpatterns,
321        action   = action,
322        failure  = failure,
323        onlyonce = onlyonce,
324    }
325    stopprocessing(notext)
326end
327
328local suffixes = {
329    mkvi = usetexfile,
330    mkiv = usetexfile,
331    mklx = usetexfile,
332    mkxl = usetexfile,
333    tex  = usetexfile,
334    luc  = useluafile,
335    lua  = useluafile,
336    cld  = usecldfile,
337    xml  = usexmlfile,
338    [""] = usetexfile,
339}
340
341local function useanyfile(name,onlyonce)
342    local s = suffixes[suffixonly(name)]
343    context(function() resolvers.pushpath(name) end)
344    if s then
345     -- s(removesuffix(name),onlyonce)
346        s(name,onlyonce) -- so, first with suffix, then without
347    else
348        usetexfile(name,onlyonce) -- e.g. ctx file
349     -- resolvers.readfilename(name)
350    end
351    context(resolvers.poppath)
352end
353
354implement { name = "usetexfile",     actions = usetexfile, arguments = "string" }
355implement { name = "useluafile",     actions = useluafile, arguments = "string" }
356implement { name = "usecldfile",     actions = usecldfile, arguments = "string" }
357implement { name = "usexmlfile",     actions = usexmlfile, arguments = "string" }
358
359implement { name = "usetexfileonce", actions = usetexfile, arguments = { "string", true } }
360implement { name = "useluafileonce", actions = useluafile, arguments = { "string", true } }
361implement { name = "usecldfileonce", actions = usecldfile, arguments = { "string", true } }
362implement { name = "usexmlfileonce", actions = usexmlfile, arguments = { "string", true } }
363
364implement { name = "useanyfile",     actions = useanyfile, arguments = "string" }
365implement { name = "useanyfileonce", actions = useanyfile, arguments = { "string", true } }
366
367function jobresolvers.usefile(name,onlyonce,notext)
368    local s = suffixes[suffixonly(name)]
369    if s then
370     -- s(removesuffix(name),onlyonce,notext)
371        s(name,onlyonce,notext) -- so, first with suffix, then without
372    end
373end
374
375-- document structure
376
377local textlevel = 0 -- inaccessible for user, we need to define counter textlevel at the tex end
378
379local function dummyfunction() end
380
381local function startstoperror()
382    report("invalid \\%s%s ... \\%s%s structure",elements.start,v_text,elements.stop,v_text)
383    startstoperror = dummyfunction
384end
385
386local stopped
387
388local function starttext()
389    if textlevel == 0 then
390        if trace_jobfiles then
391            report_jobfiles("starting text")
392        end
393        context.dostarttext()
394    end
395    textlevel = textlevel + 1
396    texsetcount("global","textlevel",textlevel)
397end
398
399local function stoptext()
400    if not stopped then
401        if textlevel == 0 then
402            startstoperror()
403        elseif textlevel > 0 then
404            textlevel = textlevel - 1
405        end
406        texsetcount("global","textlevel",textlevel)
407        if textlevel <= 0 then
408            if trace_jobfiles then
409                report_jobfiles("stopping text")
410            end
411            context.dostoptext()
412            stopped = true
413        end
414    end
415end
416
417implement { name = "starttext", actions = starttext }
418implement { name = "stoptext",  actions = stoptext  }
419
420implement {
421    name      = "forcequitjob",
422    arguments = "string",
423    actions   = function(reason)
424        if reason then
425            report("forcing quit: %s",reason)
426        else
427            report("forcing quit")
428        end
429        context.batchmode()
430        while textlevel >= 0 do
431            context.stoptext()
432        end
433    end
434}
435
436implement {
437    name    = "forceendjob",
438    actions = function()
439        report([[don't use \end to finish a document]])
440        context.stoptext()
441    end
442}
443
444implement {
445    name    = "autostarttext",
446    actions = function()
447        if textlevel == 0 then
448            report([[auto \starttext ... \stoptext]])
449        end
450        context.starttext()
451    end
452}
453
454implement {
455    name    = "autostoptext",
456    actions = stoptext
457}
458
459-- project structure
460
461implement {
462    name      = "processfilemany",
463    arguments = { "string", false },
464    actions   = useanyfile
465}
466
467implement {
468    name      = "processfileonce",
469    arguments = { "string", true },
470    actions   = useanyfile
471}
472
473implement {
474    name      = "processfilenone",
475    arguments = "string",
476    actions   = dummyfunction,
477}
478
479local tree               = { type = "text", name = "", branches = { } }
480local treestack          = { }
481local top                = tree.branches
482local root               = tree
483
484local project_stack      = { }
485local product_stack      = { }
486local component_stack    = { }
487local environment_stack  = { }
488
489local stacks = {
490    [v_project    ] = project_stack,
491    [v_product    ] = product_stack,
492    [v_component  ] = component_stack,
493    [v_environment] = environment_stack,
494}
495
496--
497
498local function pushtree(what,name)
499    local t = { }
500    top[#top+1] = { type = what, name = name, branches = t }
501    insert(treestack,top)
502    top = t
503end
504
505local function poptree()
506    top = remove(treestack)
507 -- inspect(top)
508end
509
510do
511
512    local function log_tree(report,top,depth)
513        report("%s%s: %s",depth,top.type,top.name)
514        local branches = top.branches
515        if #branches > 0 then
516            depth = depth .. "  "
517            for i=1,#branches do
518                log_tree(report,branches[i],depth)
519            end
520        end
521    end
522
523    logs.registerfinalactions(function()
524        root.name = environment.jobname
525        --
526        logs.startfilelogging(report,"used job files")
527        log_tree(report,root,"")
528        logs.stopfilelogging()
529        --
530        if nofmissing > 0 and logs.loggingerrors() then
531            logs.starterrorlogging(report,"missing files")
532            for kind, list in sortedhash(missing) do
533                for name in sortedhash(list) do
534                    report("%w%s  %s",6,kind,name)
535                end
536            end
537            logs.stoperrorlogging()
538        end
539    end)
540
541end
542
543local jobstructure      = job.structure or { }
544job.structure           = jobstructure
545jobstructure.collected  = jobstructure.collected or { }
546jobstructure.tobesaved  = root
547jobstructure.components = { }
548
549local function initialize()
550    local function collect(root,result)
551        local branches = root.branches
552        if branches then
553            for i=1,#branches do
554                local branch = branches[i]
555                if branch.type == "component" then
556                    result[#result+1] = branch.name
557                end
558                collect(branch,result)
559            end
560        end
561        return result
562    end
563    jobstructure.components = collect(jobstructure.collected,{})
564end
565
566job.register('job.structure.collected',root,initialize)
567
568-- component: small unit, either or not components itself
569-- product  : combination of components
570
571local ctx_processfilemany = context.processfilemany
572local ctx_processfileonce = context.processfileonce
573local ctx_processfilenone = context.processfilenone
574
575-- we need a plug in the nested loaded, push pop pseudo current dir
576
577local function processfilecommon(name,action)
578    -- experiment, might go away
579--     if not hasscheme(name) then
580--         local path = dirname(name)
581--         if path ~= "" then
582--             registerextrapath(path)
583--             report_jobfiles("adding search path %a",path)
584--         end
585--     end
586    -- till here
587    action(name)
588end
589
590local function processfilemany(name) processfilecommon(name,ctx_processfilemany) end
591local function processfileonce(name) processfilecommon(name,ctx_processfileonce) end
592local function processfilenone(name) processfilecommon(name,ctx_processfilenone) end
593
594local processors = utilities.storage.allocate {
595 -- [v_outer] = {
596 --     [v_text]        = { "many", processfilemany },
597 --     [v_project]     = { "once", processfileonce },
598 --     [v_environment] = { "once", processfileonce },
599 --     [v_product]     = { "once", processfileonce },
600 --     [v_component]   = { "many", processfilemany },
601 -- },
602    [v_text] = {
603        [v_text]        = { "many", processfilemany },
604        [v_project]     = { "once", processfileonce }, -- dubious
605        [v_environment] = { "once", processfileonce },
606        [v_product]     = { "many", processfilemany }, -- dubious
607        [v_component]   = { "many", processfilemany },
608    },
609    [v_project] = {
610        [v_text]        = { "many", processfilemany },
611        [v_project]     = { "none", processfilenone },
612        [v_environment] = { "once", processfileonce },
613        [v_product]     = { "none", processfilenone },
614        [v_component]   = { "none", processfilenone },
615    },
616    [v_environment] = {
617        [v_text]        = { "many", processfilemany },
618        [v_project]     = { "none", processfilenone },
619        [v_environment] = { "once", processfileonce },
620        [v_product]     = { "none", processfilenone },
621        [v_component]   = { "none", processfilenone },
622    },
623    [v_product] = {
624        [v_text]        = { "many", processfilemany },
625        [v_project]     = { "once", processfileonce },
626        [v_environment] = { "once", processfileonce },
627        [v_product]     = { "many", processfilemany },
628        [v_component]   = { "many", processfilemany },
629    },
630    [v_component] = {
631        [v_text]        = { "many", processfilemany },
632        [v_project]     = { "once", processfileonce },
633        [v_environment] = { "once", processfileonce },
634        [v_product]     = { "none", processfilenone },
635        [v_component]   = { "many", processfilemany },
636    }
637}
638
639local start = {
640    [v_text]        = nil,
641    [v_project]     = nil,
642    [v_environment] = context.startreadingfile,
643    [v_product]     = context.starttext,
644    [v_component]   = context.starttext,
645}
646
647local stop = {
648    [v_text]        = nil,
649    [v_project]     = nil,
650    [v_environment] = context.stopreadingfile,
651    [v_product]     = context.stoptext,
652    [v_component]   = context.stoptext,
653}
654
655jobresolvers.processors = processors
656
657local function topofstack(what)
658    local stack = stacks[what]
659    return stack and stack[#stack] or environment.jobname
660end
661
662local function productcomponent() -- only when in product
663    local product = product_stack[#product_stack]
664    if product and product ~= "" then
665        local component = component_stack[1]
666        if component and component ~= "" then
667            return component
668        end
669    end
670end
671
672local function justacomponent()
673    local product = product_stack[#product_stack]
674    if not product or product == "" then
675        local component = component_stack[1]
676        if component and component ~= "" then
677            return component
678        end
679    end
680end
681
682jobresolvers.productcomponent = productcomponent
683jobresolvers.justacomponent   = justacomponent
684
685function jobresolvers.currentproject    () return topofstack(v_project    ) end
686function jobresolvers.currentproduct    () return topofstack(v_product    ) end
687function jobresolvers.currentcomponent  () return topofstack(v_component  ) end
688function jobresolvers.currentenvironment() return topofstack(v_environment) end
689
690local done     = { }
691local tolerant = false -- too messy, mkii user with the wrong structure should adapt
692
693local function process(what,name)
694    local depth = #typestack
695    local process
696    --
697    name = resolveprefix(name)
698    --
699--  if not tolerant then
700        -- okay, would be best but not compatible with mkii
701        process = processors[currenttype][what]
702--  elseif depth == 0 then
703--      -- could be a component, product or (brr) project
704--      if trace_jobfiles then
705--          report_jobfiles("%s : %s > %s (case 1)",depth,currenttype,v_outer)
706--      end
707--      process = processors[v_outer][what]
708--  elseif depth == 1 and typestack[1] == v_text then
709--      -- we're still not doing a component or product
710--      if trace_jobfiles then
711--          report_jobfiles("%s : %s > %s (case 2)",depth,currenttype,v_outer)
712--      end
713--      process = processors[v_outer][what]
714--  else
715--      process = processors[currenttype][what]
716--  end
717    if process then
718        local method = process[1]
719        if method == "none" then
720            if trace_jobfiles then
721                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"ignoring",what,name,currenttype,topofstack(currenttype))
722            end
723        elseif method == "once" and done[name] then
724            if trace_jobfiles then
725                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"skipping",what,name,currenttype,topofstack(currenttype))
726            end
727        else
728            -- keep in mind that we also handle "once" at the file level
729            -- so there is a double catch
730            done[name] = true
731            local before = start[what]
732            local after  = stop [what]
733            if trace_jobfiles then
734                report_jobfiles("%s : %s : %s %s %a in %s %a",depth,method,"processing",what,name,currenttype,topofstack(currenttype))
735            end
736            if before then
737                before()
738            end
739            process[2](name)
740            if after then
741                after()
742            end
743        end
744    else
745        if trace_jobfiles then
746            report_jobfiles("%s : %s : %s %s %a in %s %a",depth,"none","ignoring",what,name,currenttype,topofstack(currenttype))
747        end
748    end
749end
750
751implement { name = "useproject",     actions = function(name) process(v_project,    name) end, arguments = "string" }
752implement { name = "useenvironment", actions = function(name) process(v_environment,name) end, arguments = "string" }
753implement { name = "useproduct",     actions = function(name) process(v_product,    name) end, arguments = "string" } -- will be overloaded
754implement { name = "usecomponent",   actions = function(name) process(v_component,  name) end, arguments = "string" }
755
756-- todo: setsystemmode to currenttype
757-- todo: make start/stop commands at the tex end
758
759local start = {
760    [v_project]     = context.startprojectindeed,
761    [v_product]     = context.startproductindeed,
762    [v_component]   = context.startcomponentindeed,
763    [v_environment] = context.startenvironmentindeed,
764}
765
766local stop = {
767    [v_project]     = context.stopprojectindeed,
768    [v_product]     = context.stopproductindeed,
769    [v_component]   = context.stopcomponentindeed,
770    [v_environment] = context.stopenvironmentindeed,
771}
772
773local function gotonextlevel(what,name) -- todo: something with suffix name
774    insert(stacks[what],name)
775    insert(typestack,currenttype)
776    currenttype = what
777    pushtree(what,name)
778    if start[what] then
779        start[what]()
780    end
781end
782
783local function gotopreviouslevel(what)
784    if stop[what] then
785        stop[what]()
786    end
787    poptree()
788    currenttype = remove(typestack) or v_text
789    remove(stacks[what]) -- not currenttype ... weak recovery
790 -- context.endinput() -- does not work
791    context.signalendofinput(what)
792end
793
794local function autoname(name)
795    if name == "*" then
796     -- name = nameonly(toppath() or name)
797        name = nameonly(currentfile() or name)
798    end
799    return name
800end
801
802implement { name = "startproject",       actions = function(name) gotonextlevel(v_project,    autoname(name)) end, arguments = "string" }
803implement { name = "startproduct",       actions = function(name) gotonextlevel(v_product,    autoname(name)) end, arguments = "string" }
804implement { name = "startcomponent",     actions = function(name) gotonextlevel(v_component,  autoname(name)) end, arguments = "string" }
805implement { name = "startenvironment",   actions = function(name) gotonextlevel(v_environment,autoname(name)) end, arguments = "string" }
806
807implement { name = "stopproject",        actions = function() gotopreviouslevel(v_project    ) end }
808implement { name = "stopproduct",        actions = function() gotopreviouslevel(v_product    ) end }
809implement { name = "stopcomponent",      actions = function() gotopreviouslevel(v_component  ) end }
810implement { name = "stopenvironment",    actions = function() gotopreviouslevel(v_environment) end }
811
812implement { name = "currentproject",     actions = function() context(topofstack(v_project    )) end }
813implement { name = "currentproduct",     actions = function() context(topofstack(v_product    )) end }
814implement { name = "currentcomponent",   actions = function() context(topofstack(v_component  )) end }
815implement { name = "currentenvironment", actions = function() context(topofstack(v_environment)) end }
816
817-- -- -- this will move -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
818--
819--  <?xml version='1.0' standalone='yes'?>
820--  <exa:variables xmlns:exa='htpp://www.pragma-ade.com/schemas/exa-variables.rng'>
821--      <exa:variable label='mode:pragma'>nee</exa:variable>
822--      <exa:variable label='mode:variant'>standaard</exa:variable>
823--  </exa:variables>
824--
825-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
826
827local report_examodes = logs.reporter("system","examodes")
828
829local function convertexamodes(str)
830    local x = xml.convert(str)
831    for e in xml.collected(x,"exa:variable") do
832        local label = e.at and e.at.label
833        if label and label ~= "" then
834            local data = xml.text(e)
835            local mode = match(label,"^mode:(.+)$")
836            if mode then
837                context.enablemode { formatters["%s:%s"](mode,data) }
838            end
839            context.setvariable("exa:variables",label,(gsub(data,"([{}])","\\%1")))
840        end
841    end
842end
843
844function environment.loadexamodes(filename)
845    if not filename or filename == "" then
846        filename = removesuffix(tex.jobname)
847    end
848    filename = resolvers.findfile(addsuffix(filename,'ctm')) or ""
849    if filename ~= "" then
850        report_examodes("loading %a",filename) -- todo: message system
851        convertexamodes(io.loaddata(filename))
852    else
853        report_examodes("no mode file %a",filename) -- todo: message system
854    end
855end
856
857implement {
858    name      = "loadexamodes",
859    actions   = environment.loadexamodes,
860    arguments = "string"
861}
862
863-- changed in mtx-context
864-- code moved from luat-ini
865
866-- todo: locals when mtx-context is changed
867
868document = document or {
869    arguments = allocate(),
870    files     = allocate(),
871    variables = allocate(), -- for templates
872    options   = {
873        commandline = {
874            environments = allocate(),
875            modules      = allocate(),
876            modes        = allocate(),
877        },
878        ctxfile = {
879            environments = allocate(),
880            modules      = allocate(),
881            modes        = allocate(),
882        },
883    },
884    functions = table.setmetatablenewindex(function(t,k,v)
885        if rawget(t,k) then
886            report_functions("overloading document function %a",k)
887        end
888        rawset(t,k,v)
889        return v
890    end),
891}
892
893function document.setargument(key,value)
894    document.arguments[key] = value
895end
896
897function document.setdefaultargument(key,default)
898    local v = document.arguments[key]
899    if v == nil or v == "" then
900        document.arguments[key] = default
901    end
902end
903
904function document.setfilename(i,name)
905    if name then
906        document.files[tonumber(i)] = name
907    else
908        document.files[#document.files+1] = tostring(i)
909    end
910end
911
912function document.getargument(key,default)
913    local v = document.arguments[key]
914    if type(v) == "boolean" then
915        v = (v and "yes") or "no"
916        document.arguments[key] = v
917    end
918    return v or default or ""
919end
920
921function document.getfilename(i)
922    return document.files[tonumber(i)] or ""
923end
924
925implement {
926    name      = "setdocumentargument",
927    actions   = document.setargument,
928    arguments = "2 strings"
929}
930
931implement {
932    name      = "setdocumentdefaultargument",
933    actions   = document.setdefaultargument,
934    arguments = "2 strings"
935}
936
937implement {
938    name      = "setdocumentfilename",
939    actions   = document.setfilename,
940    arguments = { "integer", "string" }
941}
942
943implement {
944    name      = "getdocumentargument",
945    actions   = { document.getargument, context },
946    arguments = "2 strings"
947}
948
949implement {
950    name      = "getdocumentfilename",
951    actions   = { document.getfilename, context },
952    arguments = "integer"
953}
954
955function document.setcommandline() -- has to happen at the tex end in order to expand
956
957    -- the document[arguments|files] tables are copies
958
959    local arguments = document.arguments
960    local files     = document.files
961    local options   = document.options
962
963    for k, v in next, environment.arguments do
964        k = gsub(k,"^c:","") -- already done, but better be safe than sorry
965        if arguments[k] == nil then
966            arguments[k] = v
967        end
968    end
969
970    -- in the new mtx=context approach we always pass a stub file so we need to
971    -- to trick the files table which actually only has one entry in a tex job
972
973    if arguments.timing then
974        context.usemodule { "timing" }
975    end
976
977    if arguments.usage then
978       trackers.enable("system.usage")
979    end
980
981    if arguments.batchmode then
982        context.batchmode(false)
983    end
984
985    if arguments.nonstopmode then
986        context.nonstopmode(false)
987    end
988
989    if arguments.nostatistics then
990        directives.enable("system.nostatistics")
991    end
992
993    if arguments.paranoid then
994        context.setvalue("maxreadlevel",1)
995    end
996
997    if validstring(arguments.path)  then
998        context.usepath { arguments.path }
999    end
1000
1001    if arguments.export then
1002        context.setupbackend { export = v_yes }
1003    end
1004
1005    local inputfile = validstring(arguments.input)
1006
1007    if inputfile and dirname(inputfile) == "." and lfs.isfile(inputfile) then
1008        -- nicer in checks
1009        inputfile = basename(inputfile)
1010    end
1011
1012    local forcedruns = arguments.forcedruns
1013    local kindofrun  = arguments.kindofrun
1014    local currentrun = arguments.currentrun
1015    local maxnofruns = arguments.maxnofruns or arguments.runs
1016
1017 -- context.setupsystem {
1018 --     [constants.directory] = validstring(arguments.setuppath),
1019 --     [constants.inputfile] = inputfile,
1020 --     [constants.file]      = validstring(arguments.result),
1021 --     [constants.random]    = validstring(arguments.randomseed),
1022 --     -- old:
1023 --     [constants.n]         = validstring(kindofrun),
1024 --     [constants.m]         = validstring(currentrun),
1025 -- }
1026
1027    context.setupsystem {
1028        directory = validstring(arguments.setuppath),
1029        inputfile = inputfile,
1030        file      = validstring(arguments.result),
1031        random    = validstring(arguments.randomseed),
1032        -- old:
1033        n         = validstring(kindofrun),
1034        m         = validstring(currentrun),
1035    }
1036
1037    forcedruns = tonumber(forcedruns) or 0
1038    kindofrun  = tonumber(kindofrun)  or 0
1039    maxnofruns = tonumber(maxnofruns) or 0
1040    currentrun = tonumber(currentrun) or 0
1041
1042    local prerollrun = forcedruns > 0 and currentrun > 0 and currentrun < forcedruns
1043
1044    environment.forcedruns = forcedruns
1045    environment.kindofrun  = kindofrun
1046    environment.maxnofruns = maxnofruns
1047    environment.currentrun = currentrun
1048    environment.prerollrun = prerollrun
1049
1050    context.setconditional("prerollrun",prerollrun)
1051
1052    if validstring(arguments.arguments) then
1053        context.setupenv { arguments.arguments }
1054    end
1055
1056    if arguments.once then
1057        directives.enable("system.runonce")
1058    end
1059
1060    if arguments.noarrange then
1061        context.setuparranging { variables.disable }
1062    end
1063
1064    --
1065
1066    local commandline  = options.commandline
1067
1068    commandline.environments = table.append(commandline.environments,settings_to_array(validstring(arguments.environment)))
1069    commandline.modules      = table.append(commandline.modules,     settings_to_array(validstring(arguments.usemodule)))
1070    commandline.modes        = table.append(commandline.modes,       settings_to_array(validstring(arguments.mode)))
1071
1072    --
1073
1074    if #files == 0 then
1075        local list = settings_to_array(validstring(arguments.files))
1076        if list and #list > 0 then
1077            files = list
1078        end
1079    end
1080
1081    if #files == 0 then
1082        files = { validstring(arguments.input) }
1083    end
1084
1085    --
1086
1087    document.arguments = arguments
1088    document.files     = files
1089
1090end
1091
1092-- commandline wins over ctxfile
1093
1094local function apply(list,action)
1095    if list then
1096        for i=1,#list do
1097            action { list[i] }
1098        end
1099    end
1100end
1101
1102function document.setmodes() -- was setup: *runtime:modes
1103    apply(document.options.ctxfile    .modes,context.enablemode)
1104    apply(document.options.commandline.modes,context.enablemode)
1105end
1106
1107function document.setmodules() -- was setup: *runtime:modules
1108    apply(document.options.ctxfile    .modules,context.usemodule)
1109    apply(document.options.commandline.modules,context.usemodule)
1110end
1111
1112function document.setenvironments() -- was setup: *runtime:environments
1113    apply(document.options.ctxfile    .environments,context.environment)
1114    apply(document.options.commandline.environments,context.environment)
1115end
1116
1117function document.setfilenames()
1118    local initialize = environment.initializefilenames
1119    if initialize then
1120        initialize()
1121    else
1122        -- fatal error
1123    end
1124end
1125
1126implement { name = "setdocumentcommandline",  actions = document.setcommandline,  onlyonce = true }
1127implement { name = "setdocumentmodes",        actions = document.setmodes,        onlyonce = true }
1128implement { name = "setdocumentmodules",      actions = document.setmodules,      onlyonce = true }
1129implement { name = "setdocumentenvironments", actions = document.setenvironments, onlyonce = true }
1130implement { name = "setdocumentfilenames",    actions = document.setfilenames,    onlyonce = true }
1131
1132do
1133
1134    logs.registerfinalactions(function()
1135        local foundintrees = resolvers.foundintrees()
1136        if #foundintrees > 0 then
1137            logs.startfilelogging(report,"used files")
1138            for i=1,#foundintrees do
1139                report("%4i: % T",i,foundintrees[i])
1140            end
1141            logs.stopfilelogging()
1142        end
1143    end)
1144
1145    logs.registerfinalactions(function()
1146        local files = document.files -- or environment.files
1147        local arguments = document.arguments -- or environment.arguments
1148        --
1149        logs.startfilelogging(report,"commandline options")
1150        if arguments and next(arguments) then
1151            for argument, value in sortedhash(arguments) do
1152                report("%s=%A",argument,value)
1153            end
1154        else
1155            report("no arguments")
1156        end
1157        logs.stopfilelogging()
1158        --
1159        logs.startfilelogging(report,"commandline files")
1160        if files and #files > 0 then
1161            for i=1,#files do
1162                report("% 4i: %s",i,files[i])
1163            end
1164        else
1165            report("no files")
1166        end
1167        logs.stopfilelogging()
1168    end)
1169
1170end
1171
1172if environment.initex then
1173
1174    logs.registerfinalactions(function()
1175        local startfilelogging = logs.startfilelogging
1176        local stopfilelogging  = logs.stopfilelogging
1177        startfilelogging(report,"stored tables")
1178        for k,v in sortedhash(storage.data) do
1179            report("%03i %s",k,v[1])
1180        end
1181        stopfilelogging()
1182        startfilelogging(report,"stored modules")
1183        for k,v in sortedhash(lua.bytedata) do
1184            report("%03i %s %s",k,v.name)
1185        end
1186        stopfilelogging()
1187        startfilelogging(report,"stored attributes")
1188        for k,v in sortedhash(attributes.names) do
1189            report("%03i %s",k,v)
1190        end
1191        stopfilelogging()
1192        startfilelogging(report,"stored catcodetables")
1193        for k,v in sortedhash(catcodes.names) do
1194            report("%03i % t",k,v)
1195        end
1196        stopfilelogging()
1197        startfilelogging(report,"stored corenamespaces")
1198        for k,v in sortedhash(interfaces.corenamespaces) do
1199            report("%03i %s",k,v)
1200        end
1201        stopfilelogging()
1202    end)
1203
1204end
1205
1206implement {
1207    name      = "doifelsecontinuewithfile",
1208    arguments = "string",
1209    actions   = function(inpname,basetoo)
1210        local inpnamefull = addsuffix(inpname,"tex")
1211        local inpfilefull = addsuffix(environment.inputfilename,"tex")
1212        local continue = inpnamefull == inpfilefull
1213     -- if basetoo and not continue then
1214        if not continue then
1215            continue = inpnamefull == basename(inpfilefull)
1216        end
1217        if continue then
1218            report("continuing input file %a",inpname)
1219        end
1220        ctx_doifelse(continue)
1221    end
1222}
1223