Lua metamodule containing functions useful for debugging.
log (frame)[]
Logs a message to the Lua console. This is the only function in this module that should be called from templates instead of Lua code Example:
{{#invoke:DebugUtil|log|123}}
timed (f)[]
Returns a wrapper of f
that logs the time taken in seconds for each invocation of f
to the Lua console.
cargo_counted (f)[]
Returns a wrapper of f
that logs the number of calls to mw.ext.cargo.query
inside each invocation of f
to the Lua console.
dumpObject (object)[]
Same as mw.dumpObject
, except the returned string contains no line breaks or indentations. Serializes object
to a human-readable representation, then returns the resulting string.
equal (x, y)[]
Compares if x
and y
have the same type and compare equal. Recursively calls itself for nested tables; does not support tables with self references. Ignores all metamethods.
preprocessWithUnsaved(str,p,title)[]
Preprocess wikitext in the debug console, but with unsaved modifications to the current module as well. Intended to be like frame:preprocess. Example usage:=(require "Module:DebugUtil").preprocessWithUnsaved("{{#invoke:SomeModuleYouAreEditingRightNow|main|arg1|arg2}}",p)
- Only able to preprocess a simple module invocation in the string currently. Breaks on multiple invocations (e.g. "{{#invoke:m1|main}}{{#invoke:m2|main}}") or more wikitext surrounding the invocation.
- The optional
title
parameter only sets the title associated with the frame. The context title for functions such asmw.title.getCurrentTitle()
will still be the current module the debug console is run on. - External modules that call the current module will still use the current saved version.
The above documentation is transcluded from Module:DebugUtil/doc. (edit | history)
local ScriptUtil = require "Module:ScriptUtil"
local pack = function (...)
return {n = select('#', ...), ...}
end
local timed = function (f)
return function (...)
local t0 = os.clock()
local ret = pack(f(...))
mw.log(os.clock() - t0)
return unpack(ret, 1, ret.n)
end
end
local cargo_counted = function (f)
local query = mw.ext.cargo.query
return function (...)
local c = 0
mw.ext.cargo.query = function (...)
c = c + 1
return query(...)
end
local ret = pack(f(...))
mw.ext.cargo.query = query
mw.log(c)
return unpack(ret, 1, ret.n)
end
end
-- Returns a string representation of the table t, recursively calls itself for nested table keys and values
local getTableString; do
local type = type
local tostring = tostring
local serfunc = function (x)
local typ = type(x)
if typ == 'boolean' then
return x and 'true' or 'false'
elseif typ == 'number' then
return tostring(x)
elseif typ == 'string' then
return ('%q'):format(x)
elseif typ == 'table' then
return getTableString(x)
elseif typ == nil then
return 'nil'
else
return "--[[" .. typ .. "]]"
end
end
getTableString = function (t)
if not t then
return 'nil'
end
if not next(t) then
return '{ }'
end
local str = {}
local keys = {}
for k in next, t do
keys[#keys + 1] = {k, serfunc(k)}
end
table.sort(keys, function (a, b) return a[2] < b[2] end)
for i = 1, #keys do
str[i] = ('[%s] = %s'):format(keys[i][2], serfunc(rawget(t, keys[i][1])))
end
return '{' .. table.concat(str, ', ') .. '}'
end; end
local dumpObject = function (object)
local doneTable = {}
local doneObj = {}
local ct = {}
local function sorter( a, b )
local ta, tb = type( a ), type( b )
if ta ~= tb then
return ta < tb
end
if ta == 'string' or ta == 'number' then
return a < b
end
if ta == 'boolean' then
return tostring( a ) < tostring( b )
end
return false -- Incomparable
end
local function _dumpObject( object, expandTable )
local tp = type( object )
if tp == 'number' or tp == 'nil' or tp == 'boolean' then
return tostring( object )
elseif tp == 'string' then
return string.format( "%q", object )
elseif tp == 'table' then
if not doneObj[object] then
local s = tostring( object )
if s == 'table' then
ct[tp] = ( ct[tp] or 0 ) + 1
doneObj[object] = 'table#' .. ct[tp]
else
doneObj[object] = s
doneTable[object] = true
end
end
if doneTable[object] or not expandTable then
return doneObj[object]
end
doneTable[object] = true
local ret = { doneObj[object], ' { ' }
local mt = getmetatable( object )
if mt then
ret[#ret + 1] = 'metatable = '
ret[#ret + 1] = _dumpObject( mt, false )
ret[#ret + 1] = ', '
end
local doneKeys = {}
for key, value in ipairs( object ) do
doneKeys[key] = true
ret[#ret + 1] = _dumpObject( value, true )
ret[#ret + 1] = ', '
end
local keys = {}
for key in pairs( object ) do
if not doneKeys[key] then
keys[#keys + 1] = key
end
end
table.sort( keys, sorter )
for i = 1, #keys do
local key = keys[i]
ret[#ret + 1] = '['
ret[#ret + 1] = _dumpObject( key, false )
ret[#ret + 1] = '] = '
ret[#ret + 1] = _dumpObject( object[key], true )
ret[#ret + 1] = ', '
end
if #ret == 2 then
ret[#ret + 1] = '}'
else
ret[#ret] = ' }'
end
return table.concat( ret )
else
if not doneObj[object] then
ct[tp] = ( ct[tp] or 0 ) + 1
doneObj[object] = tostring( object ) .. '#' .. ct[tp]
end
return doneObj[object]
end
end
return _dumpObject( object, true )
end
local equal; do
local next, rawequal, rawget, type = next, rawequal, rawget, type
equal = function (x, y)
local t1 = type(x)
local t2 = type(y)
if t1 ~= t2 then
return false
end
if rawequal(x, y) then
return true
elseif t1 ~= 'table' then
return false
end
for kx, vx in next, x do
if not equal(vx, rawget(y, kx)) then
return false
end
end
for ky in next, y do
if rawequal(rawget(x, ky), nil) then
return false
end
end
return true
end; end
local log = function (frame)
mw.log(frame.args[1])
end
local preprocessWithUnsaved = function(str,p,title)
local debugFrame = mw.getCurrentFrame()
local moduleToCall,functionToCall = mw.ustring.match( str, "#invoke:%s*([^}|]*)%s*|%s*([^}|]*)%s*" )
moduleToCall = mw.text.trim(moduleToCall)
functionToCall = mw.text.trim(functionToCall)
if ("Module:"..moduleToCall) == debugFrame:getTitle() then
--Piggybacking on ScriptUtil's template processor instead of writing a processor
templified_str = mw.ustring.gsub(str,"#invoke:([^}|]*)|[^|}]*","%1")
local t = ScriptUtil.Template.newFromWikitext(templified_str)
local args = t:getArgs()
return p[functionToCall](debugFrame:newChild{title = title,args = args})
else
local testedFrame
if context_title then
testedFrame = debugFrame:newChild{title = title}
else
testedFrame = debugFrame
end
return testedFrame:preprocess(str)
end
end
return {
timed = timed,
cargo_counted = cargo_counted,
dumpObject = dumpObject,
equal = equal,
log = log,
preprocessWithUnsaved = preprocessWithUnsaved,
}