trac-set.lua /size: 14 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['trac-set'] = { -- might become util-set.lua
2    version   = 1.001,
3    comment   = "companion to luat-lib.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-- maybe this should be util-set.lua
10
11local type, next, tostring, tonumber = type, next, tostring, tonumber
12local print = print
13local concat, sortedhash = table.concat, table.sortedhash
14local formatters, find, lower, gsub, topattern = string.formatters, string.find, string.lower, string.gsub, string.topattern
15local is_boolean = string.is_boolean
16local settings_to_hash = utilities.parsers.settings_to_hash
17local allocate = utilities.storage.allocate
18
19utilities         = utilities or { }
20local utilities   = utilities
21
22local setters     = utilities.setters or { }
23utilities.setters = setters
24
25local data        = { }
26
27-- We can initialize from the cnf file. This is sort of tricky as
28-- later defined setters also need to be initialized then. If set
29-- this way, we need to ensure that they are not reset later on.
30--
31-- The sorting is needed to get a predictable setters in case of *.
32
33local trace_initialize = false -- only for testing during development
34local frozen           = true  -- this needs checking
35
36local function initialize_setter(filename,name,values) -- filename only for diagnostics
37    local setter = data[name]
38    if setter then
39     -- trace_initialize = true
40        local data = setter.data
41        if data then
42            for key, newvalue in sortedhash(values) do
43                local newvalue = is_boolean(newvalue,newvalue,true) -- strict
44                local functions = data[key]
45                if functions then
46                    local oldvalue = functions.value
47                    if functions.frozen then
48                        if trace_initialize then
49                            setter.report("%s: %a is %s to %a",filename,key,"frozen",oldvalue)
50                        end
51                    elseif #functions > 0 and not oldvalue then
52--                     elseif #functions > 0 and oldvalue == nil then
53                        if trace_initialize then
54                            setter.report("%s: %a is %s to %a",filename,key,"set",newvalue)
55                        end
56                        for i=1,#functions do
57                            functions[i](newvalue)
58                        end
59                        functions.value = newvalue
60                        functions.frozen = functions.frozen or frozen
61                    else
62                        if trace_initialize then
63                            setter.report("%s: %a is %s as %a",filename,key,"kept",oldvalue)
64                        end
65                    end
66                else
67                    -- we do a simple preregistration i.e. not in the
68                    -- list as it might be an obsolete entry
69                    functions = { default = newvalue, frozen = frozen }
70                    data[key] = functions
71                    if trace_initialize then
72                        setter.report("%s: %a is %s to %a",filename,key,"defaulted",newvalue)
73                    end
74                end
75            end
76            return true
77        end
78    end
79end
80
81-- user interface code
82
83local function set(t,what,newvalue)
84    local data = t.data -- somehow this can be nil
85    if data and not data.frozen then
86        local done = t.done
87        if type(what) == "string" then
88            what = settings_to_hash(what) -- inefficient but ok
89        end
90        if type(what) ~= "table" then
91            return
92        end
93        if not done then -- catch ... why not set?
94            done = { }
95            t.done = done
96        end
97        for w, value in sortedhash(what) do
98            if value == "" then
99                value = newvalue
100            elseif not value then
101                value = false -- catch nil
102            else
103                value = is_boolean(value,value,true) -- strict
104            end
105            w = topattern(w,true,true)
106            for name, functions in sortedhash(data) do
107                if done[name] then
108                    -- prevent recursion due to wildcards
109                elseif find(name,w) then
110                    done[name] = true
111                    for i=1,#functions do
112                        functions[i](value)
113                    end
114                    functions.value = value
115                end
116            end
117        end
118    end
119end
120
121local function reset(t)
122    local data = t.data
123    if data and not data.frozen then
124        for name, functions in sortedthash(data) do
125            for i=1,#functions do
126                functions[i](false)
127            end
128            functions.value = false
129        end
130    end
131end
132
133local function enable(t,what)
134    set(t,what,true)
135end
136
137local function disable(t,what)
138    local data = t.data
139    if not what or what == "" then
140        t.done = { }
141        reset(t)
142    else
143        set(t,what,false)
144    end
145end
146
147local function register_setter(t,what,...)
148    local data = t.data
149    what = lower(what)
150    local functions = data[what]
151    if not functions then
152        functions = { }
153        data[what] = functions
154        if trace_initialize then
155            t.report("defining %a",what)
156        end
157    end
158    local default = functions.default -- can be set from cnf file
159    for i=1,select("#",...) do
160        local fnc = select(i,...)
161        local typ = type(fnc)
162        if typ == "string" then
163            if trace_initialize then
164                t.report("coupling %a to %a",what,fnc)
165            end
166            local s = fnc -- else wrong reference
167            fnc = function(value) set(t,s,value) end
168        elseif typ == "table" then
169            functions.values = fnc
170            fnc = nil
171        elseif typ ~= "function" then
172            fnc = nil
173        end
174        if fnc then
175            functions[#functions+1] = fnc
176            -- default: set at command line or in cnf file
177            -- value  : set in tex run (needed when loading runtime)
178            local value = functions.value or default
179            if value ~= nil then
180                fnc(value)
181                functions.value = value
182            end
183        end
184    end
185    return false -- so we can use it in an assignment
186end
187
188local function enable_setter(t,what)
189    local e = t.enable
190    t.enable, t.done = enable, { }
191    set(t,what,true)
192    enable(t,what)
193    t.enable, t.done = e, { }
194end
195
196local function disable_setter(t,what)
197    local e = t.disable
198    t.disable, t.done = disable, { }
199    disable(t,what)
200    t.disable, t.done = e, { }
201end
202
203local function reset_setter(t)
204    t.done = { }
205    reset(t)
206end
207
208local function list_setter(t) -- pattern
209    local list = table.sortedkeys(t.data)
210    local user, system = { }, { }
211    for l=1,#list do
212        local what = list[l]
213        if find(what,"^%*") then
214            system[#system+1] = what
215        else
216            user[#user+1] = what
217        end
218    end
219    return user, system
220end
221
222local function show_setter(t,pattern)
223    local list = list_setter(t)
224    t.report()
225    for k=1,#list do
226        local name = list[k]
227        if not pattern or find(name,pattern) then
228            local functions = t.data[name]
229            if functions then
230                local value   = functions.value
231                local default = functions.default
232                local values  = functions.values
233                local modules = #functions
234                if default == nil then
235                    default = "unset"
236                elseif type(default) == "table" then
237                    default = concat(default,"|")
238                else
239                    default = tostring(default)
240                end
241                if value == nil then
242                    value = "unset"
243                elseif type(value) == "table" then
244                    value = concat(value,"|")
245                else
246                    value = tostring(value)
247                end
248                t.report(name)
249                t.report("    modules : %i",modules)
250                t.report("    default : %s",default)
251                t.report("    value   : %s",value)
252            if values then
253                local v = { } for i=1,#values do v[i] = tostring(values[i]) end
254                t.report("    values  : % t",v)
255            end
256                t.report()
257            end
258        end
259    end
260end
261
262-- we could have used a bit of oo and the trackers:enable syntax but
263-- there is already a lot of code around using the singular tracker
264
265-- we could make this into a module but we also want the rest avaliable
266
267function setters.report(setter,fmt,...)
268    if fmt then
269        print(formatters["%-15s : %s"](setter.name,formatters[fmt](...)))
270    else
271        print("")
272    end
273end
274
275local function setter_default(setter,name)
276    local d = setter.data[name]
277    return d and d.default
278end
279
280local function setter_value(setter,name)
281    local d = setter.data[name]
282    return d and (d.value or d.default)
283end
284
285local function setter_values(setter,name)
286    local d = setter.data[name]
287    return d and d.values
288end
289
290local function new_setter(name) -- we could use foo:bar syntax (but not used that often)
291    local setter -- we need to access it in setter itself
292    setter = {
293        data     = allocate(), -- indexed, but also default and value fields
294        name     = name,
295        report   = function(...)         setters.report (setter,...) end, -- setters.report gets implemented later
296        enable   = function(...)         enable_setter  (setter,...) end,
297        disable  = function(...)         disable_setter (setter,...) end,
298        reset    = function(...)         reset_setter   (setter,...) end, -- can be dangerous
299        register = function(...)         register_setter(setter,...) end,
300        list     = function(...)  return list_setter    (setter,...) end,
301        show     = function(...)         show_setter    (setter,...) end,
302        default  = function(...)  return setter_default (setter,...) end,
303        value    = function(...)  return setter_value   (setter,...) end,
304        values   = function(...)  return setter_values  (setter,...) end,
305    }
306    data[name] = setter
307    return setter
308end
309
310setters.enable     = enable_setter
311setters.disable    = disable_setter
312-------.report     = report_setter -- todo: adapt after call (defaults to print)
313setters.register   = register_setter
314setters.list       = list_setter
315setters.show       = show_setter
316setters.reset      = reset_setter
317setters.new        = new_setter
318setters.initialize = initialize_setter
319
320trackers    = new_setter("trackers")
321directives  = new_setter("directives")
322experiments = new_setter("experiments")
323
324local t_enable, t_disable = trackers   .enable, trackers   .disable
325local d_enable, d_disable = directives .enable, directives .disable
326local e_enable, e_disable = experiments.enable, experiments.disable
327
328-- nice trick: we overload two of the directives related functions with variants that
329-- do tracing (itself using a tracker) .. proof of concept
330
331local trace_directives  = false local trace_directives  = false  trackers.register("system.directives",  function(v) trace_directives  = v end)
332local trace_experiments = false local trace_experiments = false  trackers.register("system.experiments", function(v) trace_experiments = v end)
333
334function directives.enable(...)
335    if trace_directives then
336        directives.report("enabling: % t",{...})
337    end
338    d_enable(...)
339end
340
341function directives.disable(...)
342    if trace_directives then
343        directives.report("disabling: % t",{...})
344    end
345    d_disable(...)
346end
347
348function experiments.enable(...)
349    if trace_experiments then
350        experiments.report("enabling: % t",{...})
351    end
352    e_enable(...)
353end
354
355function experiments.disable(...)
356    if trace_experiments then
357        experiments.report("disabling: % t",{...})
358    end
359    e_disable(...)
360end
361
362-- a useful example
363
364directives.register("system.nostatistics", function(v)
365    if statistics then
366        statistics.enable = not v
367    else
368        -- forget about it
369    end
370end)
371
372directives.register("system.nolibraries", function(v)
373    if libraries then
374        libraries = nil -- we discard this tracing for security
375    else
376        -- no libraries defined
377    end
378end)
379
380-- experiment
381
382if environment then
383
384    -- The engineflags are known earlier than environment.arguments but maybe we
385    -- need to handle them both as the later are parsed differently. The c: prefix
386    -- is used by mtx-context to isolate the flags from those that concern luatex.
387
388    local engineflags = environment.engineflags
389
390    if engineflags then
391        local list = engineflags["c:trackers"] or engineflags["trackers"]
392        if type(list) == "string" then
393            initialize_setter("commandline flags","trackers",settings_to_hash(list))
394         -- t_enable(list)
395        end
396        local list = engineflags["c:directives"] or engineflags["directives"]
397        if type(list) == "string" then
398            initialize_setter("commandline flags","directives", settings_to_hash(list))
399         -- d_enable(list)
400        end
401    end
402
403end
404
405-- here
406
407if texconfig then
408
409    -- this happens too late in ini mode but that is no problem
410
411    local function set(k,v)
412        local v = tonumber(v)
413        if v then
414            texconfig[k] = v
415        end
416    end
417
418    directives.register("luatex.expanddepth",  function(v) set("expand_depth",v)   end)
419    directives.register("luatex.hashextra",    function(v) set("hash_extra",v)     end)
420    directives.register("luatex.nestsize",     function(v) set("nest_size",v)      end)
421    directives.register("luatex.maxinopen",    function(v) set("max_in_open",v)    end)
422    directives.register("luatex.maxprintline", function(v) set("max_print_line",v) end)
423    directives.register("luatex.maxstrings",   function(v) set("max_strings",v)    end)
424    directives.register("luatex.paramsize",    function(v) set("param_size",v)     end)
425    directives.register("luatex.savesize",     function(v) set("save_size",v)      end)
426    directives.register("luatex.stacksize",    function(v) set("stack_size",v)     end)
427
428end
429
430-- for now here:
431
432local data = table.setmetatableindex("table")
433
434updaters = {
435    register = function(what,f)
436        local d = data[what]
437        d[#d+1] = f
438    end,
439    apply = function(what,...)
440        local d = data[what]
441        for i=1,#d do
442            d[i](...)
443        end
444    end,
445}
446