diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/luat-log.lmt')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkxl/luat-log.lmt | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/luat-log.lmt b/Master/texmf-dist/tex/context/base/mkxl/luat-log.lmt new file mode 100644 index 00000000000..522f4ac4ff6 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkxl/luat-log.lmt @@ -0,0 +1,739 @@ +if not modules then modules = { } end modules ['luat-log'] = { + version = 1.001, + comment = "companion to trac-log.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- In fact all writes could go through lua and we could write the console and +-- terminal handler in lua then. Ok, maybe it's slower then, so a no-go. + +-- This used to be combined in trac-log but as we also split between mkiv and lmtx +-- we now have dedicated files. A side effect is a smaller format and a smaller +-- mtxrun. +-- +-- We use different targets: "terminal" "logfile", "both" and have no number +-- channel. + +local next, type, select, print = next, type, select, print +local format, gmatch, find = string.format, string.gmatch, string.find +local concat, insert, remove = table.concat, table.insert, table.remove +local topattern = string.topattern +local utfchar = utf.char +local datetime = os.date +local openfile = io.open + +local write_nl = texio.write_nl +local write = texio.write + +local setmetatableindex = table.setmetatableindex +local formatters = string.formatters +local settings_to_hash = utilities.parsers.settings_to_hash +local sortedkeys = table.sortedkeys + +-- variant is set now + +local variant = "default" +----- variant = "ansi" + +logs = logs or { } +local logs = logs + +-- we extend the formatters: + +formatters.add ( + formatters, "unichr", + [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]] +) + +formatters.add ( + formatters, "chruni", + [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]] +) + +-- basic loggers + +local function ignore() end + +setmetatableindex(logs, function(t,k) t[k] = ignore ; return ignore end) + +local report, subreport, status, settarget, setformats, settranslations + +local direct, subdirect, writer, pushtarget, poptarget, setlogfile, settimedlog, setprocessor, setformatters, newline + +-- we use formatters but best check for % then because for simple messages but +-- we don't want this overhead for single messages (not that there are that +-- many; we could have a special weak table) + +local function ansisupported(specification) + if specification ~= "ansi" and specification ~= "ansilog" then + return false + elseif os and os.enableansi then + return os.enableansi() + else + return false + end +end + +do + + if arg and ansisupported then + -- we're don't have environment.arguments yet + for k, v in next, arg do -- k can be negative ! + if v == "--ansi" or v == "--c:ansi" then + if ansisupported("ansi") then + variant = "ansi" + end + break + elseif v == "--ansilog" or v == "--c:ansilog" then + if ansisupported("ansilog") then + variant = "ansilog" + end + break + end + end + end + + local whereto = "both" + local target = nil + local targets = nil + + local formats = table.setmetatableindex("self") + local translations = table.setmetatableindex("self") + + local report_yes, subreport_yes, direct_yes, subdirect_yes, status_yes + local report_nop, subreport_nop, direct_nop, subdirect_nop, status_nop + + local variants = { + default = { + formats = { + report_yes = formatters["%-15s > %s\n"], + report_nop = formatters["%-15s >\n"], + direct_yes = formatters["%-15s > %s"], + direct_nop = formatters["%-15s >"], + subreport_yes = formatters["%-15s > %s > %s\n"], + subreport_nop = formatters["%-15s > %s >\n"], + subdirect_yes = formatters["%-15s > %s > %s"], + subdirect_nop = formatters["%-15s > %s >"], + status_yes = formatters["%-15s : %s\n"], + status_nop = formatters["%-15s :\n"], + }, + targets = setmetatableindex( { + logfile = "logfile", + log = "logfile", + file = "logfile", + console = "terminal", + terminal = "terminal", + both = "terminal_and_logfile", + }, function(t,k) t[k] = "terminal_and_logfile" end), + }, + ansi = { + formats = { + report_yes = formatters["[0;33m%-15s [0;1m>[0m %s\n"], + report_nop = formatters["[0;33m%-15s [0;1m>[0m\n"], + direct_yes = formatters["[0;33m%-15s [0;1m>[0m %s"], + direct_nop = formatters["[0;33m%-15s [0;1m>[0m"], + subreport_yes = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"], + subreport_nop = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"], + subdirect_yes = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"], + subdirect_nop = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"], + status_yes = formatters["[0;33m%-15s [0;1m:[0m %s\n"], + status_nop = formatters["[0;33m%-15s [0;1m:[0m\n"], + }, + targets = setmetatableindex( { + logfile = false, + log = false, + file = false, + console = "terminal", + terminal = "terminal", + both = "terminal", + }, function(t,k) t[k] = "terminal" end), + } + } + + variants.ansilog = { + formats = variants.ansi.formats, + targets = variants.default.targets, + } + + logs.flush = io.flush + + writer = function(...) + if target then + write_nl(target,...) + end + end + + newline = function() + if target then + write_nl(target,"\n") + end + end + + report = function(a,b,c,...) + if not target then + -- ignore + elseif c ~= nil then + write_nl(target,report_yes(translations[a],formatters[formats[b]](c,...))) + elseif b then + write_nl(target,report_yes(translations[a],formats[b])) + elseif a then + write_nl(target,report_nop(translations[a])) + else + write_nl(target,"\n") + end + end + + direct = function(a,b,c,...) + if not target then + return "" + elseif c ~= nil then + return direct_yes(translations[a],formatters[formats[b]](c,...)) + elseif b then + return direct_yes(translations[a],formats[b]) + elseif a then + return direct_nop(translations[a]) + else + return "" + end + end + + subreport = function(a,s,b,c,...) + if not target then + -- ignore + elseif c ~= nil then + write_nl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...))) + elseif b then + write_nl(target,subreport_yes(translations[a],translations[s],formats[b])) + elseif a then + write_nl(target,subreport_nop(translations[a],translations[s])) + else + write_nl(target,"\n") + end + end + + subdirect = function(a,s,b,c,...) + if not target then + return "" + elseif c ~= nil then + return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...)) + elseif b then + return subdirect_yes(translations[a],translations[s],formats[b]) + elseif a then + return subdirect_nop(translations[a],translations[s]) + else + return "" + end + end + + status = function(a,b,c,...) + if not target then + -- ignore + elseif c ~= nil then + write_nl(target,status_yes(translations[a],formatters[formats[b]](c,...))) + elseif b then + write_nl(target,status_yes(translations[a],formats[b])) + elseif a then + write_nl(target,status_nop(translations[a])) + else + write_nl(target,"\n") + end + end + + settarget = function(askedwhereto) + whereto = askedwhereto or whereto or "both" + target = targets[whereto] + if not target then + whereto = "both" + target = targets[whereto] + end + if target == "terminal" or target == "terminal_and_logfile" then + logs.flush = io.flush + else + logs.flush = ignore + end + end + + local stack = { } + + pushtarget = function(newtarget) + insert(stack,target) + settarget(newtarget) + end + + poptarget = function() + if #stack > 0 then + settarget(remove(stack)) + end + end + + setformats = function(f) + formats = f + end + + settranslations = function(t) + translations = t + end + + setprocessor = function(f) + local writeline = write_nl + write_nl = function(target,...) + if target then + writeline(target,f(...)) + end + end + end + + setformatters = function(specification) + local t = nil + local f = nil + local d = variants.default + if not specification then + -- + elseif type(specification) == "table" then + t = specification.targets + f = specification.formats or specification + else + if not ansisupported(specification) then + specification = "default" + end + local v = variants[specification] + if v then + t = v.targets + f = v.formats + variant = specification + end + end + targets = t or d.targets + target = targets[whereto] + if f then + d = d.formats + else + f = d.formats + d = f + end + setmetatableindex(f,d) + report_yes = f.report_yes + report_nop = f.report_nop + subreport_yes = f.subreport_yes + subreport_nop = f.subreport_nop + direct_yes = f.direct_yes + direct_nop = f.direct_nop + subdirect_yes = f.subdirect_yes + subdirect_nop = f.subdirect_nop + status_yes = f.status_yes + status_nop = f.status_nop + settarget(whereto) + end + + setformatters(variant) + + setlogfile = ignore + settimedlog = ignore + +end + +logs.report = report +logs.subreport = subreport +logs.status = status +logs.settarget = settarget +logs.pushtarget = pushtarget +logs.poptarget = poptarget +logs.setformats = setformats +logs.settranslations = settranslations + +logs.setlogfile = setlogfile +logs.settimedlog = settimedlog +logs.setprocessor = setprocessor +logs.setformatters = setformatters + +logs.direct = direct +logs.subdirect = subdirect +logs.writer = writer +logs.newline = newline + +local data = { } +local states = nil +local force = false + +function logs.reporter(category,subcategory) + local logger = data[category] + if not logger then + local state = states == true + if not state and type(states) == "table" then + for c, _ in next, states do + if find(category,c) then + state = true + break + end + end + end + logger = { + reporters = { }, + state = state, + } + data[category] = logger + end + local reporter = logger.reporters[subcategory or "default"] + if not reporter then + if subcategory then + reporter = function(...) + if force or not logger.state then + subreport(category,subcategory,...) + end + end + logger.reporters[subcategory] = reporter + else + local tag = category + reporter = function(...) + if force or not logger.state then + report(category,...) + end + end + logger.reporters.default = reporter + end + end + return reporter +end + +logs.new = logs.reporter -- for old times sake + +-- context specicific: this ends up in the macro stream + +local ctxreport = logs.writer + +function logs.setmessenger(m) + ctxreport = m +end + +function logs.messenger(category,subcategory) + -- we need to avoid catcode mess (todo: fast context) + if subcategory then + return function(...) + ctxreport(subdirect(category,subcategory,...)) + end + else + return function(...) + ctxreport(direct(category,...)) + end + end +end + +-- so far + +local function setblocked(category,value) -- v.state == value == true : disable + if category == true or category == "all" then + -- lock all + category, value = "*", true + elseif category == false then + -- unlock all + category, value = "*", false + elseif value == nil then + -- lock selective + value = true + end + if category == "*" then + states = value + for k, v in next, data do + v.state = value + end + else + alllocked = false + states = settings_to_hash(category,type(states)=="table" and states or nil) + for c in next, states do + local v = data[c] + if v then + v.state = value + else + c = topattern(c,true,true) + for k, v in next, data do + if find(k,c) then + v.state = value + end + end + end + end + end +end + +function logs.disable(category,value) + setblocked(category,value == nil and true or value) +end + +function logs.enable(category) + setblocked(category,false) +end + +function logs.categories() + return sortedkeys(data) +end + +function logs.show() + local n, c, s, max = 0, 0, 0, 0 + for category, v in table.sortedpairs(data) do + n = n + 1 + local state = v.state + local reporters = v.reporters + local nc = #category + if nc > c then + c = nc + end + for subcategory, _ in next, reporters do + local ns = #subcategory + if ns > c then + s = ns + end + local m = nc + ns + if m > max then + max = m + end + end + local subcategories = concat(sortedkeys(reporters),", ") + if state == true then + state = "disabled" + elseif state == false then + state = "enabled" + else + state = "unknown" + end + -- no new here + report("logging","category %a, subcategories %a, state %a",category,subcategories,state) + end + report("logging","categories: %s, max category: %s, max subcategory: %s, max combined: %s",n,c,s,max) +end + +local delayed_reporters = { } + +setmetatableindex(delayed_reporters,function(t,k) + local v = logs.reporter(k.name) + t[k] = v + return v +end) + +function utilities.setters.report(setter,...) + delayed_reporters[setter](...) +end + +directives.register("logs.blocked", function(v) + setblocked(v,true) +end) + +directives.register("logs.target", function(v) + settarget(v) +end) + +do + + local report = logs.reporter("pages") -- not needed but saves checking when we grep for it + local texgetcount = tex and tex.getcount + + local real, user, sub = 0, 0, 0 + + function logs.start_page_number() + real = texgetcount("realpageno") + user = texgetcount("userpageno") + sub = texgetcount("subpageno") + end + + local timing = false + local usage = false + local lasttime = nil + + logs.private = { + enablepagetiming = function() + usage = true + end, + getpagetiming = function() + return type(usage) == "table" and usage + end, + } + + trackers.register("pages.timing", function() timing = "" end) + + function logs.stop_page_number() -- the first page can includes the initialization so we omit this in average + if timing or usage then + local elapsed = statistics.currenttime(statistics) + local average, page + if not lasttime or real < 2 then + average = elapsed + page = elapsed + else + average = elapsed / (real - 1) + page = elapsed - lasttime + end + lasttime = elapsed + if timing then + timing = formatters[", total %0.03f, page %0.03f, average %0.03f"](elapsed,page,average) + end + if usage then + usage = { + page = { + real = real, + user = user, + sub = sub, + }, + time = { + elapsed = elapsed, + page = page, + average = average, + } + } + end + end + if real <= 0 then + report("flushing page%s",timing) + elseif user <= 0 then + report("flushing realpage %s%s",real,timing) + elseif sub <= 0 then + report("flushing realpage %s, userpage %s%s",real,user,timing) + else + report("flushing realpage %s, userpage %s, subpage %s%s",real,user,sub,timing) + end + logs.flush() + end + +end + +-- we don't have show_open and show_close callbacks yet + +do + local texerror = tex and tex.error or print + local formatters = string.formatters + + function logs.texerrormessage(fmt,first,...) -- for the moment we put this function here + texerror(first and formatters[fmt](first,...) or fmt) + end + +end + +-- this is somewhat slower but prevents out-of-order messages when print is mixed +-- with texio.write + +-- io.stdout:setvbuf('no') +-- io.stderr:setvbuf('no') + +-- windows: > nul 2>&1 +-- unix : > null 2>&1 + +if package.helpers.report then + package.helpers.report = logs.reporter("package loader") -- when used outside mtxrun +end + +do + + local finalactions = { } + local fatalerrors = { } + local possiblefatal = { } + local loggingerrors = false + + function logs.loggingerrors() + return loggingerrors + end + + directives.register("logs.errors",function(v) + loggingerrors = v + if type(v) == "string" then + fatalerrors = settings_to_hash(v) + else + fatalerrors = { } + end + end) + + function logs.registerfinalactions(...) + insert(finalactions,...) -- so we can force an order if needed + end + + local what = nil + local report = nil + local state = nil + local target = nil + + local function startlogging(t,r,w,s) + target = t + state = force + force = true + report = type(r) == "function" and r or logs.reporter(r) + what = w + pushtarget(target) + newline() + if s then + report("start %s: %s",what,s) + else + report("start %s",what or "") + end + if target == "logfile" then + newline() + end + return report + end + + local function stoplogging() + if target == "logfile" then + newline() + end + report("stop %s",what or "") + if target == "logfile" then + newline() + end + poptarget() + state = oldstate + end + + function logs.startfilelogging(...) + return startlogging("logfile", ...) + end + + logs.stopfilelogging = stoplogging + + local done = false + + function logs.starterrorlogging(r,w,...) + if not done then + pushtarget("terminal") + newline() + logs.report("error logging","start possible issues") + poptarget() + done = true + end + if fatalerrors[w] then + possiblefatal[w] = true + end + return startlogging("terminal",r,w,...) + end + + logs.stoperrorlogging = stoplogging + + function logs.finalactions() + if #finalactions > 0 then + for i=1,#finalactions do + finalactions[i]() + end + if done then + pushtarget("terminal") + newline() + logs.report("error logging","stop possible issues") + poptarget() + end + return next(possiblefatal) and sortedkeys(possiblefatal) or false + end + end + +end + +-- just in case we load from context + +local dummy = function() end + +function logs.application(t) + return { + name = t.name or tex.jobname, + banner = t.banner, + report = logs.reporter(t.name), + moreinfo = dummy, + export = dummy, + help = dummy, + identify = dummy, + version = dummy, + } +end |