file-job.lmt /size: 44 Kb    last modification: 2025-02-21 11:03
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, 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----- v_outer           <const> = variables.outer
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-- main code .. there is some overlap .. here we have loc://
82
83local function findctxfile(name) -- loc ? any ?
84    if is_qualified_path(name) then -- maybe when no suffix do some test for tex
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-- moved from tex to lua:
202
203----- texpatterns = { "%s.mklx", "%s.mkxl", "%s.mkvi", "%s.mkiv", "%s.tex" }
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-- status
213--
214-- these need to be synced with input stream:
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     -- report("begin file %a at line %a",name,status.linenumber or 0)
258        context.dostarttextfile(name)
259    end
260end
261
262local function stopprocessing(notext)
263    if not notext then
264        context.dostoptextfile()
265     -- report("end file %a at line %a",name,status.linenumber or 0)
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     -- s(removesuffix(name),onlyonce)
377        s(name,onlyonce) -- so, first with suffix, then without
378    else
379        usetexfile(name,onlyonce) -- e.g. ctx file
380     -- resolvers.readfilename(name)
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     -- s(removesuffix(name),onlyonce,notext)
402        s(name,onlyonce,notext) -- so, first with suffix, then without
403    end
404end
405
406-- document structure
407
408local textlevel = 0 -- inaccessible for user, we need to define counter textlevel at the tex end
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-- project structure
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 -- saves space in tuc
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 -- saves space in tuc
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--     inspect(namestack)
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-- inspect(namehash)
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-- component: small unit, either or not components itself
709-- product  : combination of components
710
711local ctx_processfilemany = context.processfilemany
712local ctx_processfileonce = context.processfileonce
713local ctx_processfilenone = context.processfilenone
714
715-- we need a plug in the nested loaded, push pop pseudo current dir
716
717local function processfilecommon(name,action)
718    -- experiment, might go away
719--     if not hasscheme(name) then
720--         local path = dirname(name)
721--         if path ~= "" then
722--             registerextrapath(path)
723--             report_jobfiles("adding search path %a",path)
724--         end
725--     end
726    -- till here
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 -- [v_outer] = {
736 --     [v_text]        = { "many", processfilemany },
737 --     [v_project]     = { "once", processfileonce },
738 --     [v_environment] = { "once", processfileonce },
739 --     [v_product]     = { "once", processfileonce },
740 --     [v_component]   = { "many", processfilemany },
741 -- },
742    [v_text] = {
743        [v_text]        = { "many", processfilemany },
744        [v_project]     = { "once", processfileonce }, -- dubious
745        [v_environment] = { "once", processfileonce },
746        [v_product]     = { "many", processfilemany }, -- dubious
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() -- only when in product
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 -- too messy, mkii user with the wrong structure should adapt
832
833local function process(what,name)
834    local depth = #typestack
835    local process
836    --
837    name = resolveprefix(name)
838    --
839--  if not tolerant then
840        -- okay, would be best but not compatible with mkii
841        process = processors[currenttype][what]
842--  elseif depth == 0 then
843--      -- could be a component, product or (brr) project
844--      if trace_jobfiles then
845--          report_jobfiles("%s : %s > %s (case 1)",depth,currenttype,v_outer)
846--      end
847--      process = processors[v_outer][what]
848--  elseif depth == 1 and typestack[1] == v_text then
849--      -- we're still not doing a component or product
850--      if trace_jobfiles then
851--          report_jobfiles("%s : %s > %s (case 2)",depth,currenttype,v_outer)
852--      end
853--      process = processors[v_outer][what]
854--  else
855--      process = processors[currenttype][what]
856--  end
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            -- keep in mind that we also handle "once" at the file level
869            -- so there is a double catch
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) -- [name] or name<space> - use optional
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 } -- will be overloaded
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" } -- will be overloaded
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-- todo: setsystemmode to currenttype
920-- todo: make start/stop commands at the tex end
921
922-- local start = {
923--     [v_project]     = context.startprojectindeed,
924--     [v_product]     = context.startproductindeed,
925--     [v_component]   = context.startcomponentindeed,
926--     [v_environment] = context.startenvironmentindeed,
927-- }
928
929-- local stop = {
930--     [v_project]     = context.stopprojectindeed,
931--     [v_product]     = context.stopproductindeed,
932--     [v_component]   = context.stopcomponentindeed,
933--     [v_environment] = context.stopenvironmentindeed,
934-- }
935
936-- local start = {
937--     [v_project]     = "startprojectindeed",
938--     [v_product]     = "startproductindeed",
939--     [v_component]   = "startcomponentindeed",
940--     [v_environment] = "startenvironmentindeed",
941-- }
942
943-- local stop = {
944--     [v_project]     = "stopprojectindeed",
945--     [v_product]     = "stopproductindeed",
946--     [v_component]   = "stopcomponentindeed",
947--     [v_environment] = "stopenvironmentindeed",
948-- }
949
950local function gotonextlevel(what,how,name) -- todo: something with suffix 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]) -- not currenttype ... weak recovery
965 -- context.endinput() -- now at the tex end !
966end
967
968local function autoname()
969    local name = scan_delimited(91,93) or scan_delimited(0,32) -- [name] or name<space>
970    if name == "*" then
971     -- name = nameonly(toppath() or name)
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-- -- -- this will move -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
995--
996--  <?xml version='1.0' standalone='yes'?>
997--  <exa:variables xmlns:exa='htpp://www.pragma-ade.com/schemas/exa-variables.rng'>
998--      <exa:variable label='mode:pragma'>nee</exa:variable>
999--      <exa:variable label='mode:variant'>standaard</exa:variable>
1000--  </exa:variables>
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        -- todo: environment.fulljobnmame
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) -- todo: message system
1029        convertexamodes(io.loaddata(filename))
1030    else
1031        report_examodes("no mode file %a",filename) -- todo: message system
1032    end
1033end
1034
1035implement {
1036    name      = "loadexamodes",
1037    actions   = environment.loadexamodes,
1038    public    = true,
1039    protected = true,
1040    arguments = "optional"
1041}
1042
1043-- changed in mtx-context
1044-- code moved from luat-ini
1045
1046-- todo: locals when mtx-context is changed
1047
1048document = document or {
1049    arguments = allocate(),
1050    files     = allocate(),
1051    variables = allocate(), -- for templates
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() -- has to happen at the tex end in order to expand
1136
1137    -- the document[arguments|files] tables are copies
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:","") -- already done, but better be safe than sorry
1145        if arguments[a] == nil then
1146            arguments[a] = v
1147        end
1148    end
1149
1150    -- in the new mtx=context approach we always pass a stub file so we need to
1151    -- to trick the files table which actually only has one entry in a tex job
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        -- nicer in checks
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 -- context.setupsystem {
1203 --     [constants.directory] = validstring(arguments.setuppath),
1204 --     [constants.inputfile] = inputfile,
1205 --     [constants.file]      = validstring(arguments.result),
1206 --     [constants.random]    = validstring(arguments.randomseed),
1207 --     -- old:
1208 --     [constants.n]         = validstring(kindofrun),
1209 --     [constants.m]         = validstring(currentrun),
1210 -- }
1211
1212    context.setupsystem {
1213        directory = validstring(arguments.setuppath),
1214        inputfile = inputfile,
1215        file      = validstring(arguments.result),
1216        random    = validstring(arguments.randomseed),
1217        -- old:
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-- commandline wins over ctxfile
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() -- was setup: *runtime:modes
1288    apply(document.options.ctxfile    .modes,context.enablemode)
1289    apply(document.options.commandline.modes,context.enablemode)
1290end
1291
1292function document.setmodules() -- was setup: *runtime:modules
1293    apply(document.options.ctxfile    .modules,context.usemodule)
1294    apply(document.options.commandline.modules,context.usemodule)
1295end
1296
1297function document.setenvironments() -- was setup: *runtime:environments
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        -- fatal error
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 -- or environment.files
1332        local arguments = document.arguments -- or environment.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     -- if basetoo and not continue then
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--         ctx_doifelse(continue)
1410    end
1411}
1412
1413-- data-hsh.lmt:
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-- don't we have this already?
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