モジュール:サンドボックス/MysteryPedia/Utility

モジュールの解説[作成]
-- Utilities that do not depend on any project, and are mainly intended to be called by other modules.

local export = {}

function export.codepoint(frame)
	local a = frame.args
	return mw.ustring.codepoint(a[1] or error("Arguments need at least one."), a[2] or 1, a[3] or 1)
end

function export.categorizeFromData(data, title, sortKey)
	local cats = {}
	for catname, pages in pairs(data) do
		for i, t in ipairs(pages) do
			local id = table.concat({catname, title}, "|")
			if title == t and not cats[id] then
				cats[id] = true
				table.insert(cats, "[[Category:" .. catname .. (sortKey and "|" .. sortKey or "") .. "]]")
			end
		end
	end
	if #cats > 0 then
		return mw.getCurrentFrame():preprocess(table.concat(cats))
	else	-- Disables the line break just in case.
		return mw.getCurrentFrame():preprocess('<nowiki />')
	end
end

function export.startsWith(s, prefix)
	return (mw.ustring.sub(s, 1, mw.ustring.len(prefix)) == prefix)
end

function export.endsWith(s, suffix)
	return (mw.ustring.sub(s, mw.ustring.len(s) - mw.ustring.len(suffix) + 1) == suffix)
end

-- It's similar to IsValidPageName, but it returns 'true' or 'nil'.
function export.isValidTitle(str)
	local succeeded, t = pcall(mw.title.new, str)
	return (t and succeeded)
end

function export.pageExists(title)
	local succeeded, t = pcall(mw.title.new, title)
	return (succeeded and t.exists)
end

-- NOTE: the same purpose as [[w:en:Module:Array length]].
function export.size(data)
	if data[1] == nil then
		return 0
	end
	local low = 1
	local high = 0xFFFFFFFF
	while low <= high do
		n = low + (high - low) / 2
		if data[n] ~= nil and data[n + 1] == nil then
			return n
		elseif data[n] ~= nil then
			low = n + 1
		else
			high = n - 1
		end
	end
end

function export.import(fullName)
	local succeeded, instance = pcall(require, fullName)
	if succeeded then
		return instance
	else -- Ignores the exception message.
		mw.log("Could not load the module " .. fullName .. ".")
		return nil
	end
end

-- An ad hoc function. I'm too lazy to find out whether there's a standard way or not.
-- NOTE: There seems to be nothing.
local function getModuleNameByCallStack(depth)
	local s = debug.traceback()
	mw.log("Parsing: " .. s)
	for line in mw.text.gsplit(s, "\n", true) do
		local match = mw.ustring.match(line, "([^%c]+):%d+: in function") or mw.ustring.find(line, "(tail call):", 1, true)
		if match ~= nil then
			if depth > 1 then
				depth = depth - 1
			else
				if type(match) == "number" then
					error("Failed to detect the module name because of tail call optimization." +
						" Please make its code a bit verbose to avoid it.")
				end
				return match
			end
		end
	end
	return nil
end

function export.getCurrentModuleName()
	return getModuleNameByCallStack(3)
end

-- Distinguishing subpages and the sandbox's modules.
-- '(sandbox)/(username)' - in many of Wikipedias
-- '(User pseudo-namespace):(module name)' - in many of Wiktionaries
function export.isTopLevelModule(fullName)
	-- Removes the '{{ns:Module}}' namespace.
	return not mw.ustring.find(mw.title.new(fullName).text, '[/:]')
end

function getModuleName(baseModuleName, name)
	if export.isTopLevelModule(baseModuleName) then
		return "Module:" .. name
	else	-- NOTE: changes the last segment.
		return (mw.ustring.gsub(baseModuleName, "/[^/]+$", '/' .. name))
	end
end

-- PENDING:
function export.getModuleName(name)
	if not name then
		return getModuleNameByCallStack(3)
	end
	local fullName = getModuleName(getModuleNameByCallStack(3), name)
	if not export.pageExists(fullName) then
		fullName = getModuleName(getModuleNameByCallStack(1), name)
	end
	return fullName
end

local cache = {}

function export.const(expr)
	local value = cache[expr]
	if value ~= nil then
		return value
	end
	local new = mw.getCurrentFrame():preprocess(expr)
	cache[expr] = new
	return new
end

function export.extend(target, incognito)
	local mt = target
	while true do
		if incognito == mt then
			return false
		end
		local child = getmetatable(mt)
		if child == nil then
			setmetatable(mt, { __index = incognito })
			return true
		end
		mt = child
-- 		child = getmetatable(mt)
	end
end

local gmt = {}

-- PENDING:
-- This function will be useful in some cases. However, you can use this module as a usual utility class, without the deep mechanism.
function export.using(globals, parent, ...)
	export.extend(globals, gmt)
	
	for i, obj in ipairs({...}) do
		if type(obj) == "function" then
			local found = false
			for name, fun in pairs(parent) do
				if fun == obj then
					gmt[name] = fun
					found = true
					break
				end
			end
			if not found then
				error("Member not found.")
			end
		elseif type(obj) == "string" then
			if parent[obj] == nil then
				error("Invalid method name.")
			end
			gmt[obj] = parent[obj]
		else
			error("Unsupported type.")
		end
	end
end

local console = export.import(export.getModuleName("Console"))

do
	if console then
		console.attach(export)
		console.define(export)
		console.define({ ['cache'] = function() return cache end, })
	end
end

return export