1if not modules then modules = { } end modules ['core-uti'] = {
2 version = 1.001,
3 comment = "companion to core-uti.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
10
11
12
13
14
15
16
17local math = math
18local format, match = string.format, string.match
19local next, type, tostring, tonumber = next, type, tostring, tonumber
20local concat = table.concat
21
22local definetable = utilities.tables.definetable
23local accesstable = utilities.tables.accesstable
24local migratetable = utilities.tables.migratetable
25local serialize = table.serialize
26local packers = utilities.packers
27local allocate = utilities.storage.allocate
28local mark = utilities.storage.mark
29
30local getrandom = utilities.randomizer.get
31local setrandomseedi = utilities.randomizer.setseedi
32local getrandomseed = utilities.randomizer.getseed
33
34local implement = interfaces.implement
35
36local texgetcount = tex.getcount
37
38local report_passes = logs.reporter("job","passes")
39
40job = job or { }
41local job = job
42
43job.version = 1.32
44job.packversion = 1.02
45
46
47
48
49
50local savelist, comment = { }, { }
51
52function job.comment(key,value)
53 if type(key) == "table" then
54 for k, v in next, key do
55 comment[k] = v
56 end
57 else
58 comment[key] = value
59 end
60end
61
62job.comment("version",job.version)
63
64local enabled = true
65local initialized = false
66
67directives.register("job.save",function(v) enabled = v end)
68
69function job.disablesave()
70 enabled = false
71end
72
73function job.initialize(loadname,savename)
74 if not initialized then
75 if not loadname or loadname == "" then
76 loadname = tex.jobname .. ".tuc"
77 end
78 if not savename or savename == "" then
79 savename = tex.jobname .. ".tua"
80 end
81 job.load(loadname)
82 luatex.registerstopactions(function()
83 if enabled then
84 job.save(savename)
85 end
86 end)
87 initialized = true
88 end
89end
90
91function job.register(collected, tobesaved, initializer, finalizer, serializer)
92 savelist[#savelist+1] = { collected, tobesaved, initializer, finalizer, serializer }
93end
94
95
96
97local tobesaved, collected, checksums = allocate(), allocate(), allocate()
98
99local jobvariables = {
100 collected = collected,
101 tobesaved = tobesaved,
102 checksums = checksums,
103}
104
105
106
107
108job.variables = jobvariables
109
110local function initializer()
111 checksums = jobvariables.checksums
112end
113
114job.register('job.variables.checksums', 'job.variables.checksums', initializer)
115
116local rmethod, rvalue
117local collectedmacros, tobesavedmacros
118
119local ctx_setxvalue = context.setxvalue
120
121local function initializer()
122 tobesaved = jobvariables.tobesaved
123 collected = jobvariables.collected
124
125 rvalue = collected.randomseed
126 if not rvalue then
127 rvalue = getrandom("initialize")
128 setrandomseedi(rvalue)
129 rmethod = "initialized"
130 else
131 setrandomseedi(rvalue)
132 rmethod = "resumed"
133 end
134 tobesaved.randomseed = rvalue
135
136 collectedmacros = collected.macros
137 tobesavedmacros = tobesaved.macros
138 if not collectedmacros then
139 collectedmacros = { }
140 collected.macros = collectedmacros
141 end
142 if not tobesavedmacros then
143 tobesavedmacros = { }
144 tobesaved.macros = tobesavedmacros
145 end
146
147 for cs, value in next, collectedmacros do
148 if type(value) == "string" then
149 ctx_setxvalue(cs,value)
150 end
151 end
152end
153
154job.register('job.variables.collected', tobesaved, initializer)
155
156function jobvariables.save(cs,value)
157 tobesavedmacros[cs] = value
158end
159
160function jobvariables.restore(cs)
161 return collectedmacros[cs] or tobesavedmacros[cs]
162end
163
164function job.getrandomseed()
165 return tobesaved.randomseed or getrandomseed()
166end
167
168
169
170function jobvariables.getchecksum(tag)
171 return checksums[tag]
172end
173
174function jobvariables.makechecksum(data)
175 return data and md5.HEX(data)
176end
177
178function jobvariables.setchecksum(tag,checksum)
179 checksums[tag] = checksum
180end
181
182
183
184local packlist = {
185 "numbers",
186 "ownnumbers",
187 "metadata",
188 "sectiondata",
189 "prefixdata",
190 "numberdata",
191 "pagedata",
192 "directives",
193 "specification",
194 "processors",
195
196}
197
198local skiplist = {
199 "datasets",
200 "userdata",
201 "positions",
202}
203
204
205
206
207local jobpacker = packers.new(packlist,job.packversion,skiplist)
208
209job.pack = true
210
211
212directives.register("job.pack",function(v) job.pack = v end)
213
214local _save_, _load_, _others_ = { }, { }, { }
215
216function job.save(filename)
217 statistics.starttiming(_save_)
218 local f = io.open(filename,'w')
219 if f then
220 f:write("local utilitydata = { }\n\n")
221 f:write(serialize(comment,"utilitydata.comment",true),"\n\n")
222 for l=1,#savelist do
223
224 local list = savelist[l]
225 local target = format("utilitydata.%s",list[1])
226 local data = list[2]
227 local finalizer = list[4]
228 local serializer = list[5]
229 if type(data) == "string" then
230 data = utilities.tables.accesstable(data)
231 end
232 if type(finalizer) == "function" then
233 finalizer()
234 end
235 if job.pack then
236 packers.pack(data,jobpacker,true)
237 end
238 local definer, name = definetable(target,true,true)
239 if serializer then
240 f:write(definer,"\n\n",serializer(data,name,true),"\n\n")
241 else
242 f:write(definer,"\n\n",serialize(data,name,true),"\n\n")
243 end
244
245 end
246 if job.pack then
247 packers.strip(jobpacker)
248
249 f:write(serialize(jobpacker,"utilitydata.job.packed",true),"\n\n")
250
251 end
252 f:write("return utilitydata")
253 f:close()
254 end
255 statistics.stoptiming(_save_)
256end
257
258local function load(filename)
259 if lfs.isfile(filename) then
260
261 local function dofile(filename)
262 local result = loadstring(io.loaddata(filename))
263 if result then
264 return result()
265 else
266 return nil
267 end
268 end
269
270 local okay, data = pcall(dofile,filename)
271 if okay and type(data) == "table" then
272 local jobversion = job.version
273 local datacomment = data.comment
274 local dataversion = datacomment and datacomment.version or "?"
275 if dataversion ~= jobversion then
276 report_passes("version mismatch: %s <> %s",dataversion,jobversion)
277 else
278 return data
279 end
280 else
281 os.remove(filename)
282 report_passes("removing stale job data file %a, restart job, message: %s%s",filename,tostring(data),
283 jit and " (try luatex instead of luajittex)" or "")
284 os.exit(true)
285 end
286 end
287end
288
289function job.load(filename)
290 statistics.starttiming(_load_)
291 local utilitydata = load(filename)
292 if utilitydata then
293 local jobpacker = utilitydata.job.packed
294 local handlers = { }
295 for i=1,#savelist do
296 local list = savelist[i]
297 local target = list[1]
298 local initializer = list[3]
299 local result = accesstable(target,utilitydata)
300 if result then
301 local done = packers.unpack(result,jobpacker,true)
302 if done then
303 migratetable(target,mark(result))
304 if type(initializer) == "function" then
305 handlers[#handlers+1] = { initializer, result }
306 end
307 else
308 report_passes("pack version mismatch")
309 end
310 end
311 end
312
313 for i=1,#handlers do
314 local handler = handlers[i]
315 handler[1](handler[2])
316 end
317 end
318 statistics.stoptiming(_load_)
319end
320
321function job.loadother(filename)
322 statistics.starttiming(_load_)
323 _others_[#_others_+1] = file.nameonly(filename)
324 local utilitydata = load(filename)
325 if utilitydata then
326 local jobpacker = utilitydata.job.packed
327 local unpacked = { }
328 for l=1,#savelist do
329 local list = savelist[l]
330 local target = list[1]
331 local result = accesstable(target,utilitydata)
332 local done = packers.unpack(result,jobpacker,true)
333 if done then
334 migratetable(target,result,unpacked)
335 end
336 end
337 unpacked.job.packed = nil
338 return unpacked
339 end
340 statistics.stoptiming(_load_)
341end
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367statistics.register("startup time", function()
368 return statistics.elapsedseconds(statistics,"including runtime option file processing")
369end)
370
371statistics.register("jobdata time",function()
372 if enabled then
373 if #_others_ > 0 then
374 return format("%s seconds saving, %s seconds loading, other files: %s",statistics.elapsedtime(_save_),statistics.elapsedtime(_load_),concat(_others_," "))
375 else
376 return format("%s seconds saving, %s seconds loading",statistics.elapsedtime(_save_),statistics.elapsedtime(_load_))
377 end
378 else
379 if #_others_ > 0 then
380 return format("nothing saved, %s seconds loading, other files: %s",statistics.elapsedtime(_load_),concat(_others_," "))
381 else
382 return format("nothing saved, %s seconds loading",statistics.elapsedtime(_load_))
383 end
384 end
385end)
386
387statistics.register("callbacks", function()
388 local c_internal = status.callbacks or 0
389 local c_file = status.indirect_callbacks or 0
390 local c_direct = status.direct_callbacks or 0
391 local c_late = backends.getcallbackstate().count
392 local c_function = status.function_callbacks or 0
393 local c_total = c_internal + c_file + c_direct + c_late + c_function
394 local n_pages = structures.pages.nofpages or 0
395 local c_average = n_pages > 0 and math.round(c_total/n_pages) or 0
396 local result = format (
397 "internal: %s, file: %s, direct: %s, late: %s, function %s, total: %s (%s per page)",
398 c_internal, c_file, c_direct, c_late, c_function, c_total, c_average
399 )
400 statistics.callbacks = function()
401 return result
402 end
403 return result
404end)
405
406statistics.register("randomizer", function()
407 if rmethod and rvalue then
408 return format("%s with value %s",rmethod,rvalue)
409 end
410end)
411
412
413
414
415
416
417
418
419
420
421function statistics.formatruntime(runtime)
422 if not environment.initex then
423
424 local shipped = texgetcount('nofshipouts')
425 local pages = texgetcount('realpageno')
426 if pages > shipped then
427 pages = shipped
428 end
429 runtime = tonumber(runtime)
430 if shipped > 0 or pages > 0 then
431 local persecond = (runtime > 0) and (shipped/runtime) or pages
432 if pages == 0 then
433 pages = shipped
434 end
435 return format("%0.3f seconds, %i processed pages, %i shipped pages, %.3f pages/second",runtime,pages,shipped,persecond)
436 else
437 return format("%0.3f seconds",runtime)
438 end
439 end
440end
441
442implement {
443 name = "savevariable",
444 actions = job.variables.save,
445 arguments = "2 strings",
446}
447
448implement {
449 name = "setjobcomment",
450 actions = job.comment,
451 arguments = { { "*" } }
452}
453
454implement {
455 name = "initializejob",
456 actions = job.initialize
457}
458
459implement {
460 name = "disablejobsave",
461 actions = job.disablesave
462}
463 |