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