luat-fmt.lua /size: 11 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['luat-fmt'] = {
2    version   = 1.001,
3    comment   = "companion to mtxrun",
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-- The original idea was to have a generic format builder and as a result the code
10-- here (and some elsewhere) is bit more extensive that we really need for context.
11-- For instance, in the real beginning we had runtime loading because we had no
12-- bytecode registers yet. We also had multiple files as stubs and the context.lus
13-- file specified these. More than a decade only the third method was used, just
14-- loading luat-cod, so in the end we could get rid of the lus file. In due time
15-- I'll strip the code here because something generic will never take of and we
16-- moved on to luametatex anyway.
17
18-- Per 2023-04-25 we need to explicitly pass --socket and --shell-escape because
19-- other macro packages need these libraries to be disabled due to lack of control.
20-- So a quite drastic break of downward compatibility (context could not generate a
21-- format otherwise). Yet another reason to move on to luametatex.
22
23local format = string.format
24local concat = table.concat
25local quoted = string.quoted
26local luasuffixes = utilities.lua.suffixes
27
28local report_format = logs.reporter("resolvers","formats")
29
30local function primaryflags(arguments)
31    local flags      = { }
32    if arguments.silent then
33        flags[#flags+1] = "--interaction=batchmode"
34    end
35    return concat(flags," ")
36end
37
38local function secondaryflags(arguments)
39    local trackers   = arguments.trackers
40    local directives = arguments.directives
41    local flags      = { }
42    if trackers and trackers ~= "" then
43        flags[#flags+1] = "--c:trackers=" .. quoted(trackers)
44    end
45    if directives and directives ~= "" then
46        flags[#flags+1] = "--c:directives=" .. quoted(directives)
47    end
48    if arguments.silent then
49        flags[#flags+1] = "--c:silent"
50    end
51    if arguments.errors then
52        flags[#flags+1] = "--c:errors"
53    end
54    if arguments.ansi then
55        flags[#flags+1] = "--c:ansi"
56    end
57    if arguments.ansilog then
58        flags[#flags+1] = "--c:ansilog"
59    end
60    if arguments.strip then
61        flags[#flags+1] = "--c:strip"
62    end
63    if arguments.lmtx then
64        flags[#flags+1] = "--c:lmtx"
65    end
66    return concat(flags," ")
67end
68
69-- The silent option is for Taco. It's a bit of a hack because we cannot yet mess
70-- with directives. In fact, I could probably clean up the maker a bit by now.
71
72local template = [[--ini %primaryflags% --socket --shell-escape --lua=%luafile% %texfile% %secondaryflags% %redirect%]]
73
74local checkers = {
75    primaryflags   = "verbose",  -- "flags"
76    secondaryflags = "verbose",  -- "flags"
77    luafile        = "readable", -- "cache"
78    texfile        = "readable", -- "cache"
79    redirect       = "string",
80    binarypath     = "string",
81}
82
83local runners = {
84    luametatex = sandbox.registerrunner {
85        name     = "make luametatex format",
86        program  = "luametatex",
87        template = template,
88        checkers = checkers,
89        reporter = report_format,
90    },
91    luatex = sandbox.registerrunner {
92        name     = "make luatex format",
93        program  = "luatex",
94        template = template,
95        checkers = checkers,
96        reporter = report_format,
97    },
98    luajittex = sandbox.registerrunner {
99        name     = "make luajittex format",
100        program  = "luajittex",
101        template = template,
102        checkers = checkers,
103        reporter = report_format,
104    },
105}
106
107local stubfiles = {
108    luametatex = "luat-cod.lmt",
109    luatex     = "luat-cod.lua",
110    luajittex  = "luat-cod.lua",
111}
112
113local suffixes = {
114    luametatex = "mkxl",
115    luatex     = "mkiv",
116    luajittex  = "mkiv",
117}
118
119local function validbinarypath()
120 -- if environment.arguments.addbinarypath then
121    if not environment.arguments.nobinarypath then
122        local path = environment.ownpath or file.dirname(environment.ownname)
123        if path and path ~= "" then
124            path = dir.expandname(path)
125            if path ~= "" and lfs.isdir(path) then
126                return path
127            end
128        end
129    end
130end
131
132local function fatalerror(startupdir,...)
133    report_format(...)
134    lfs.chdir(startupdir)
135end
136
137function environment.make_format(formatname)
138    local arguments  = environment.arguments
139    local engine     = environment.ownmain or "luatex"
140    local silent     = arguments.silent
141    local errors     = arguments.errors
142    local runner     = runners[engine]
143    local startupdir = dir.current()
144    if not runner then
145        return fatalerror(startupdir,"the format %a cannot be generated, no runner available for engine %a",name,engine)
146    end
147    -- now we locate the to be used source files ... there are some variants that we
148    -- need to take care
149    local luasourcename = stubfiles[engine]
150    if not luasourcename then
151        return fatalerror(startupdir,"no lua stub file specified for %a",engine)
152    end
153    local texsourcename     = file.addsuffix(formatname,suffixes[engine])
154    local fulltexsourcename = resolvers.findfile(texsourcename,"tex") or ""
155    if fulltexsourcename == "" then
156        return fatalerror(startupdir,"no tex source file with name %a (mkiv or tex)",formatname)
157    end
158    -- this is tricky: we normally have an expanded path but when we don't have one,
159    -- the current path gets appended
160    local fulltexsourcename = dir.expandname(fulltexsourcename)
161    local texsourcepath     = file.dirname(fulltexsourcename)
162    if lfs.isfile(fulltexsourcename) then
163        report_format("using tex source file %a",fulltexsourcename)
164    else
165        return fatalerror(startupdir,"no accessible tex source file with name %a",fulltexsourcename)
166    end
167    -- we're getting there, that is: we have a file that specifies the context format;
168    -- in addition to that file we need a stub for setting up lua as we start rather
169    -- minimalistic ..
170    local fullluasourcename = dir.expandname(file.join(texsourcepath,luasourcename) or "")
171    if lfs.isfile(fullluasourcename) then
172        report_format("using lua stub file %a",fullluasourcename)
173    else
174        return fatalerror(startupdir,"no accessible lua stub file with name %a",fulltexsourcename)
175    end
176    -- we will change tot the format path because some local files will be created
177    -- in the process and we don't want clutter
178    local validformatpath = caches.getwritablepath("formats",engine) or ""
179    if validformatpath == "" then
180        return fatalerror(startupdir,"invalid format path, insufficient write access")
181    end
182    -- in case we have a qualified path, we need to do this before we change
183    -- because we can have half qualified paths (in lxc)
184    local binarypath = validbinarypath()
185    report_format("changing to format path %a",validformatpath)
186 -- lfs.chdir(validformatpath)
187 -- if dir.current() ~= validformatpath then
188    if not lfs.chdir(validformatpath) then
189        return fatalerror(startupdir,"unable to change to format path %a",validformatpath)
190    end
191    -- now we can generate the format, where we use a couple of flags,
192    -- split into two categories
193    local primaryflags   = primaryflags(arguments)
194    local secondaryflags = secondaryflags(arguments)
195    local specification  = {
196        binarypath     = binarypath,
197        primaryflags   = primaryflags,
198        secondaryflags = secondaryflags,
199        luafile        = quoted(fullluasourcename),
200        texfile        = quoted(fulltexsourcename),
201    }
202    if silent then
203        specification.redirect = "> temp.log"
204    end
205    statistics.starttiming("format")
206    local result  = runner(specification)
207    statistics.stoptiming("format")
208    if silent then
209        os.remove("temp.log")
210    end
211    -- some final report
212    report_format()
213  if binarypath and binarypath ~= "" then
214    report_format("binary path      : %s",binarypath or "?")
215  end
216    report_format("format path      : %s",validformatpath)
217    report_format("luatex engine    : %s",engine)
218    report_format("lua startup file : %s",fullluasourcename)
219  if primaryflags ~= "" then
220    report_format("primary flags    : %s",primaryflags)
221  end
222  if secondaryflags ~= "" then
223    report_format("secondary flags  : %s",secondaryflags)
224  end
225    report_format("context file     : %s",fulltexsourcename)
226    report_format("run time         : %.3f seconds",statistics.elapsed("format"))
227    report_format("return value     : %s",result == 0 and "okay" or "error")
228    report_format()
229    -- last we go back to the home base
230    lfs.chdir(startupdir)
231end
232
233local template = [[%primaryflags% --socket --shell-escape --fmt=%fmtfile% --lua=%luafile% %texfile% %secondaryflags%]]
234
235local checkers = {
236    primaryflags   = "verbose",
237    secondaryflags = "verbose",
238    fmtfile        = "readable", -- "cache"
239    luafile        = "readable", -- "cache"
240    texfile        = "readable", -- "cache"
241}
242
243local runners = {
244    luatex = sandbox.registerrunner {
245        name     = "run luatex format",
246        program  = "luatex",
247        template = template,
248        checkers = checkers,
249        reporter = report_format,
250    },
251    luametatex = sandbox.registerrunner {
252        name     = "run luametatex format",
253        program  = "luametatex",
254        template = template,
255        checkers = checkers,
256        reporter = report_format,
257    },
258    luajittex = sandbox.registerrunner {
259        name     = "run luajittex format",
260        program  = "luajittex",
261        template = template,
262        checkers = checkers,
263        reporter = report_format,
264    },
265}
266
267function environment.run_format(formatname,scriptname,filename,primaryflags,secondaryflags,verbose)
268    local engine = environment.ownmain or "luatex"
269    if not formatname or formatname == "" then
270        report_format("missing format name")
271        return
272    end
273    if not scriptname or scriptname == "" then
274        report_format("missing script name")
275        return
276    end
277    if not lfs.isfile(formatname) or not lfs.isfile(scriptname) then
278        formatname, scriptname = resolvers.locateformat(formatname)
279    end
280    if not formatname or formatname == "" then
281        report_format("invalid format name")
282        return
283    end
284    if not scriptname or scriptname == "" then
285        report_format("invalid script name")
286        return
287    end
288    local runner = runners[engine]
289    if not runner then
290        report_format("format %a cannot be run, no runner available for engine %a",file.nameonly(name),engine)
291        return
292    end
293    if not filename then
294        filename ""
295    end
296    local binarypath = validbinarypath()
297    local specification = {
298        binarypath     = binarypath,
299        primaryflags   = primaryflags or "",
300        secondaryflags = secondaryflags or "",
301        fmtfile        = quoted(formatname),
302        luafile        = quoted(scriptname),
303        texfile        = filename ~= "" and quoted(filename) or "",
304    }
305    statistics.starttiming("make format")
306    local result  = runner(specification)
307    statistics.stoptiming("make format")
308    if verbose then
309        report_format()
310      if binarypath and binarypath ~= "" then
311        report_format("binary path      : %s",binarypath)
312      end
313        report_format("luatex engine    : %s",engine)
314        report_format("lua startup file : %s",scriptname)
315        report_format("tex format file  : %s",formatname)
316      if filename ~= "" then
317        report_format("tex input file   : %s",filename)
318      end
319      if primaryflags ~= "" then
320        report_format("primary flags    : %s",primaryflags)
321      end
322      if secondaryflags ~= "" then
323        report_format("secondary flags  : %s",secondaryflags)
324      end
325        report_format("run time         : %0.3f seconds",statistics.elapsed("make format"))
326        report_format("return value     : %s",result == 0 and "okay" or "error")
327        report_format()
328    end
329    return result
330end
331