Fire Emblem Heroes Wiki
Advertisement
Template-info Documentation

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)
Notes on limitations:
  • 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 as mw.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.
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,
}
Advertisement