trac-log.lua /size: 15 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['trac-log'] = {
2    version   = 1.001,
3    comment   = "companion to trac-log.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 fact all writes could go through lua and we could write the console and
10-- terminal handler in lua then. Ok, maybe it's slower then, so a no-go.
11--
12-- This is the version for mtxrun. The alternative functions for the TeX engines
13-- have been separated. In order to keep in sync we use tex specific witer names.
14--
15-- Todo: some cleanup (less local needed).
16
17local next, type, select, print = next, type, select, print
18local format, gmatch, find = string.format, string.gmatch, string.find
19local concat, insert, remove = table.concat, table.insert, table.remove
20local topattern = string.topattern
21local utfchar = utf.char
22local datetime = os.date
23local openfile = io.open
24
25local write_nl = print
26local write    = io.write
27
28local setmetatableindex = table.setmetatableindex
29local formatters        = string.formatters
30local settings_to_hash  = utilities.parsers.settings_to_hash
31local sortedkeys        = table.sortedkeys
32
33local variant = "default"
34----- variant = "ansi"
35
36logs       = logs or { }
37local logs = logs
38
39local moreinfo = [[
40More information about ConTeXt and the tools that come with it can be found at:
41]] .. "\n" .. [[
42maillist : ntg-context@ntg.nl / http://www.ntg.nl/mailman/listinfo/ntg-context
43webpage  : http://www.pragma-ade.nl / http://tex.aanhet.net
44wiki     : http://contextgarden.net
45]]
46
47formatters.add (
48    formatters, "unichr",
49    [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]]
50)
51
52formatters.add (
53    formatters, "chruni",
54    [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]]
55)
56
57-- basic loggers
58
59local function ignore() end
60
61setmetatableindex(logs, function(t,k) t[k] = ignore ; return ignore end)
62
63local report, subreport, status, settarget, setformats, settranslations
64
65local direct, subdirect, writer, pushtarget, poptarget, setlogfile, settimedlog, setprocessor, setformatters, newline
66
67-- we use formatters but best check for % then because for simple messages but
68-- we don't want this overhead for single messages (not that there are that
69-- many; we could have a special weak table)
70
71local function ansisupported(specification)
72    if specification ~= "ansi" and specification ~= "ansilog" then
73        return false
74    elseif os and os.enableansi then
75        return os.enableansi()
76    else
77        return false
78    end
79end
80
81do
82
83    local report_yes, subreport_yes, status_yes
84    local report_nop, subreport_nop, status_nop
85
86    local variants = {
87        default = {
88            formats = {
89                report_yes    = formatters["%-15s | %s"],
90                report_nop    = formatters["%-15s |"],
91                subreport_yes = formatters["%-15s | %s | %s"],
92                subreport_nop = formatters["%-15s | %s |"],
93                status_yes    = formatters["%-15s : %s\n"],
94                status_nop    = formatters["%-15s :\n"],
95            },
96        },
97        ansi = {
98            formats = {
99                report_yes    = formatters["%-15s | %s"],
100                report_nop    = formatters["%-15s |"],
101                subreport_yes = formatters["%-15s | %s | %s"],
102                subreport_nop = formatters["%-15s | %s |"],
103                status_yes    = formatters["%-15s : %s\n"],
104                status_nop    = formatters["%-15s :\n"],
105            },
106        },
107    }
108
109    logs.flush = ignore
110
111    writer = function(s)
112        write_nl(s)
113    end
114
115    newline = function()
116        write_nl("\n")
117    end
118
119    report = function(a,b,c,...)
120        if c then
121            write_nl(report_yes(a,formatters[b](c,...)))
122        elseif b then
123            write_nl(report_yes(a,b))
124        elseif a then
125            write_nl(report_nop(a))
126        else
127            write_nl("")
128        end
129    end
130
131    subreport = function(a,sub,b,c,...)
132        if c then
133            write_nl(subreport_yes(a,sub,formatters[b](c,...)))
134        elseif b then
135            write_nl(subreport_yes(a,sub,b))
136        elseif a then
137            write_nl(subreport_nop(a,sub))
138        else
139            write_nl("")
140        end
141    end
142
143    status = function(a,b,c,...) -- not to be used in lua anyway
144        if c then
145            write_nl(status_yes(a,formatters[b](c,...)))
146        elseif b then
147            write_nl(status_yes(a,b)) -- b can have %'s
148        elseif a then
149            write_nl(status_nop(a))
150        else
151            write_nl("\n")
152        end
153    end
154
155    direct          = ignore
156    subdirect       = ignore
157
158    settarget       = ignore
159    pushtarget      = ignore
160    poptarget       = ignore
161    setformats      = ignore
162    settranslations = ignore
163
164    setprocessor = function(f)
165        local writeline = write_nl
166        write_nl = function(s)
167            writeline(f(s))
168        end
169    end
170
171    setformatters = function(specification)
172        local f = nil
173        local d = variants.default
174        if specification then
175            if type(specification) == "table" then
176                f = specification.formats or specification
177            else
178                if not ansisupported(specification) then
179                    specification = "default"
180                end
181                local v = variants[specification]
182                if v then
183                    f = v.formats
184                end
185            end
186        end
187        if f then
188            d = d.formats
189        else
190            f = d.formats
191            d = f
192        end
193        setmetatableindex(f,d)
194        report_yes    = f.report_yes
195        report_nop    = f.report_nop
196        subreport_yes = f.subreport_yes
197        subreport_nop = f.subreport_nop
198        status_yes    = f.status_yes
199        status_nop    = f.status_nop
200    end
201
202    setformatters(variant)
203
204    setlogfile = function(name,keepopen)
205        if name and name ~= "" then
206            local localtime = os.localtime
207            local writeline = write_nl
208            if keepopen then
209                local f = io.open(name,"ab")
210                write_nl = function(s)
211                    writeline(s)
212                    f:write(localtime()," | ",s,"\n")
213                end
214            else
215                write_nl = function(s)
216                    writeline(s)
217                    local f = io.open(name,"ab")
218                    f:write(localtime()," | ",s,"\n")
219                    f:close()
220                end
221            end
222        end
223        setlogfile = ignore
224    end
225
226    settimedlog = function()
227        local localtime = os.localtime
228        local writeline = write_nl
229        write_nl = function(s)
230            writeline(localtime() .. " | " .. s)
231        end
232        settimedlog = ignore
233    end
234
235end
236
237logs.report          = report
238logs.subreport       = subreport
239logs.status          = status
240logs.settarget       = settarget
241logs.pushtarget      = pushtarget
242logs.poptarget       = poptarget
243logs.setformats      = setformats
244logs.settranslations = settranslations
245
246logs.setlogfile      = setlogfile
247logs.settimedlog     = settimedlog
248logs.setprocessor    = setprocessor
249logs.setformatters   = setformatters
250
251logs.direct          = direct
252logs.subdirect       = subdirect
253logs.writer          = writer
254logs.newline         = newline
255
256-- installer
257
258-- todo: renew (un) locks when a new one is added and wildcard
259
260local data   = { }
261local states = nil
262local force  = false
263
264function logs.reporter(category,subcategory)
265    local logger = data[category]
266    if not logger then
267        local state = states == true
268        if not state and type(states) == "table" then
269            for c, _ in next, states do
270                if find(category,c) then
271                    state = true
272                    break
273                end
274            end
275        end
276        logger = {
277            reporters = { },
278            state     = state,
279        }
280        data[category] = logger
281    end
282    local reporter = logger.reporters[subcategory or "default"]
283    if not reporter then
284        if subcategory then
285            reporter = function(...)
286                if force or not logger.state then
287                    subreport(category,subcategory,...)
288                end
289            end
290            logger.reporters[subcategory] = reporter
291        else
292            local tag = category
293            reporter = function(...)
294                if force or not logger.state then
295                    report(category,...)
296                end
297            end
298            logger.reporters.default = reporter
299        end
300    end
301    return reporter
302end
303
304logs.new = logs.reporter -- for old times sake
305
306-- context specicific: this ends up in the macro stream
307
308local ctxreport = logs.writer
309
310function logs.setmessenger(m)
311    ctxreport = m
312end
313
314function logs.messenger(category,subcategory)
315    -- we need to avoid catcode mess (todo: fast context)
316    if subcategory then
317        return function(...)
318            ctxreport(subdirect(category,subcategory,...))
319        end
320    else
321        return function(...)
322            ctxreport(direct(category,...))
323        end
324    end
325end
326
327-- so far
328
329local function setblocked(category,value) -- v.state == value == true : disable
330    if category == true or category == "all" then
331        -- lock all
332        category, value = "*", true
333    elseif category == false then
334        -- unlock all
335        category, value = "*", false
336    elseif value == nil then
337        -- lock selective
338        value = true
339    end
340    if category == "*" then
341        states = value
342        for k, v in next, data do
343            v.state = value
344        end
345    else
346        alllocked = false
347        states = settings_to_hash(category,type(states)=="table" and states or nil)
348        for c in next, states do
349            local v = data[c]
350            if v then
351                v.state = value
352            else
353                c = topattern(c,true,true)
354                for k, v in next, data do
355                    if find(k,c) then
356                        v.state = value
357                    end
358                end
359            end
360        end
361    end
362end
363
364function logs.disable(category,value)
365    setblocked(category,value == nil and true or value)
366end
367
368function logs.enable(category)
369    setblocked(category,false)
370end
371
372function logs.categories()
373    return sortedkeys(data)
374end
375
376function logs.show()
377    local n, c, s, max = 0, 0, 0, 0
378    for category, v in table.sortedpairs(data) do
379        n = n + 1
380        local state = v.state
381        local reporters = v.reporters
382        local nc = #category
383        if nc > c then
384            c = nc
385        end
386        for subcategory, _ in next, reporters do
387            local ns = #subcategory
388            if ns > c then
389                s = ns
390            end
391            local m = nc + ns
392            if m > max then
393                max = m
394            end
395        end
396        local subcategories = concat(sortedkeys(reporters),", ")
397        if state == true then
398            state = "disabled"
399        elseif state == false then
400            state = "enabled"
401        else
402            state = "unknown"
403        end
404        -- no new here
405        report("logging","category %a, subcategories %a, state %a",category,subcategories,state)
406    end
407    report("logging","categories: %s, max category: %s, max subcategory: %s, max combined: %s",n,c,s,max)
408end
409
410local delayed_reporters = { }
411
412setmetatableindex(delayed_reporters,function(t,k)
413    local v = logs.reporter(k.name)
414    t[k] = v
415    return v
416end)
417
418function utilities.setters.report(setter,...)
419    delayed_reporters[setter](...)
420end
421
422directives.register("logs.blocked", function(v)
423    setblocked(v,true)
424end)
425
426directives.register("logs.target", function(v)
427    settarget(v)
428end)
429
430-- we don't have show_open and show_close callbacks yet
431
432----- report_files = logs.reporter("files")
433local nesting      = 0
434local verbose      = false
435local hasscheme    = url.hasscheme
436
437-- there may be scripts out there using this:
438
439local simple = logs.reporter("comment")
440
441logs.simple     = simple
442logs.simpleline = simple
443
444-- obsolete
445
446logs.setprogram   = ignore -- obsolete
447logs.extendbanner = ignore -- obsolete
448logs.reportlines  = ignore -- obsolete
449logs.reportbanner = ignore -- obsolete
450logs.reportline   = ignore -- obsolete
451logs.simplelines  = ignore -- obsolete
452logs.help         = ignore -- obsolete
453
454-- applications
455
456local Carg, C, lpegmatch = lpeg.Carg, lpeg.C, lpeg.match
457local p_newline = lpeg.patterns.newline
458
459local linewise = (
460    Carg(1) * C((1-p_newline)^1) / function(t,s) t.report(s) end
461  + Carg(1) * p_newline^2        / function(t)   t.report()  end
462  + p_newline
463)^1
464
465local function reportlines(t,str)
466    if str then
467        lpegmatch(linewise,str,1,t)
468    end
469end
470
471local function reportbanner(t)
472    local banner = t.banner
473    if banner then
474        t.report(banner)
475        t.report()
476    end
477end
478
479local function reportversion(t)
480    local banner = t.banner
481    if banner then
482        t.report(banner)
483    end
484end
485
486local function reporthelp(t,...)
487    local helpinfo = t.helpinfo
488    if type(helpinfo) == "string" then
489        reportlines(t,helpinfo)
490    elseif type(helpinfo) == "table" then
491        for i=1,select("#",...) do
492            reportlines(t,t.helpinfo[select(i,...)])
493            if i < n then
494                t.report()
495            end
496        end
497    end
498end
499
500local function reportinfo(t)
501    t.report()
502    reportlines(t,t.moreinfo)
503end
504
505local function reportexport(t,method)
506    report(t.helpinfo)
507end
508
509local reporters = {
510    lines   = reportlines, -- not to be overloaded
511    banner  = reportbanner,
512    version = reportversion,
513    help    = reporthelp,
514    info    = reportinfo,
515    export  = reportexport,
516}
517
518local exporters = {
519    -- empty
520}
521
522logs.reporters = reporters
523logs.exporters = exporters
524
525function logs.application(t)
526    --
527    local arguments = environment and environment.arguments
528    if arguments then
529        local ansi = arguments.ansi or arguments.ansilog
530        if ansi then
531            logs.setformatters(arguments.ansi and "ansi" or "ansilog")
532        end
533    end
534    --
535    t.name     = t.name   or "unknown"
536    t.banner   = t.banner
537    t.moreinfo = moreinfo
538    t.report   = logs.reporter(t.name)
539    t.help     = function(...)
540        reporters.banner(t)
541        reporters.help(t,...)
542        reporters.info(t)
543    end
544    t.export   = function(...)
545        reporters.export(t,...)
546    end
547    t.identify = function()
548        reporters.banner(t)
549    end
550    t.version  = function()
551        reporters.version(t)
552    end
553    return t
554end
555
556-- somewhat special .. will be redone (already a better solution in place in lmx)
557
558-- logging to a file
559
560-- local syslogname = "oeps.xxx"
561--
562-- for i=1,10 do
563--     logs.system(syslogname,"context","test","fonts","font %s recached due to newer version (%s)","blabla","123")
564-- end
565
566local f_syslog = formatters["%s %s => %s => %s => %s\r"]
567
568function logs.system(whereto,process,jobname,category,fmt,arg,...)
569    local message = f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg == nil and fmt or format(fmt,arg,...))
570    for i=1,10 do
571        local f = openfile(whereto,"a") -- we can consider keeping the file open
572        if f then
573            f:write(message)
574            f:close()
575            break
576        else
577            sleep(0.1)
578        end
579    end
580end
581
582local report_system = logs.reporter("system","logs")
583
584if utilities then
585    utilities.report = report_system
586end
587
588-- this is somewhat slower but prevents out-of-order messages when print is mixed
589-- with texio.write
590
591-- io.stdout:setvbuf('no')
592-- io.stderr:setvbuf('no')
593
594-- windows: > nul  2>&1
595-- unix   : > null 2>&1
596
597if package.helpers.report then
598    package.helpers.report = logs.reporter("package loader") -- when used outside mtxrun
599end
600