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