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