欢迎大家来到沙石镇时光中文维基!本站编辑权限开放,欢迎加入中文维基 QQ 群「沙海时光」:372816689
目前正在进行全站数据更新,期间可能会存在显示异常的问题。

全站通知:

模块:Helper

来自沙石镇时光维基
跳到导航 跳到搜索

此模块的文档可以在模块:Helper/doc创建

local VERSION = 'V4.1.2.4'
local REVISION = 0

local cache = require("mw.ext.LuaCache")
local KEY_PREFIX = "Module:Helper"
local EXP_TIME = 172800

-- ----------------------------------------
-- <pre> Module:Helper
-- Collection of small helper functions
-- ----------------------------------------
local Self = {} -- table of functions
local ustr = mw.ustring -- quick/short access to ustring table

-- ----------------------------------------
-- Variables
-- ----------------------------------------
local g_IsDesktop = true

-- ----------------------------------------
-- Helper Functions
-- ----------------------------------------
function Self.GetArgs(frame)
    local f
    for k, v in pairs(frame.args) do
        return frame.args
    end
    return frame:getParent().args
end

function Self.EscapeMagicCharacters(item)
    item = ustr.gsub(item, "&#(%d+);", function(n)
        return ustr.char(n)
    end)
    item = ustr.gsub(item, "%%", "%%%%")
    item = ustr.gsub(item, "^%^", "%%^")
    item = ustr.gsub(item, "%$$", "%%$")
    item = ustr.gsub(item, "%(", "%%(")
    item = ustr.gsub(item, "%)", "%%)")
    item = ustr.gsub(item, "%.", "%%.")
    item = ustr.gsub(item, "%[", "%%[")
    item = ustr.gsub(item, "%]", "%%]")
    item = ustr.gsub(item, "%*", "%%*")
    item = ustr.gsub(item, "%+", "%%+")
    item = ustr.gsub(item, "%-", "%%-")
    item = ustr.gsub(item, "%?", "%%?")
    return item
end

function Self.IfDesktop(a, b)
    if a ~= nil then
        return a or (b or "")
    else
        return true
    end
end

function Self.IfMobile(a, b)
    if a ~= nil then
        return not a or (b or "")
    else
        return not true
    end
end

function Self.Exp(frame)
    return Self.Explode(frame.args[1], frame, frame.args[2])
end

function Self.Explode(text, frame, skippre)
    -- adds a middot between each letter to allow seeing what we are dealing with
    frame = frame or mw.getCurrentFrame()
    if not skippre then
        text = frame:preprocess(text or "")
    end
    local str = ""
    for l in ustr.gmatch(text, ".") do
        str = str .. "&middot;" .. l
    end
    return "Code: " .. str
end

function Self.Dump(o)
    -- Dumps the contents of a variable into a <pre>
    local str = ""
    local serialize
    serialize = function(o, s)
        local t = type(o)
        if o == nil then
            str = str .. "nil"
        elseif t == "number" then
            str = str .. o
        elseif t == "string" then
            str = str .. ustr.format("%q", o)
        elseif t == "boolean" then
            str = str .. (o and "true" or "false")
        elseif t == "table" then
            str = str .. "{\n"
            local l = s
            s = s .. "&nbsp;&nbsp;&nbsp;&nbsp;"
            for k, v in pairs(o) do
                str = str .. s .. "["
                serialize(k, s)
                str = str .. "] = "
                serialize(v, s)
                str = str .. ",\n"
            end
            str = str .. l .. "}"
        else
            error("cannot serialize a " .. type(v))
        end
    end
    serialize(o, "")
    return "<pre>" .. str .. "</pre>"
end

function isSeq(obj)
	if type(obj) ~= "table" then return false end
	local max = 0
	local count = 0
	for k, v in pairs(obj) do
		if type(k) ~= "number" or k < 1 then return false end
		if max < k then max = k end
		count = count + 1
	end
	return count == max
end

function Self.dumpPretty(obj, indent)
	if type(obj) == "table" then
		local entries = {}
		local big = false
		if isSeq(obj) then
			for j, v in ipairs(obj) do
				entries[j] = Self.dumpPretty(v, indent + 1)
				if type(v) == "table" then big = true end
				if string.match(entries[j], "\n") then big = true end
			end
		else
			for k, v in pairs(obj) do
				if k ~= "id" and k ~= "name" then
					local kk = k
					if type(k) ~= "string" or string.match(k, "[^%w_]") then
						kk = "[" .. Self.dumpPretty(k, indent + 1) .. "]"
					end
					local vv = Self.dumpPretty(v, indent + 1)
					table.insert(entries, kk .. " = " .. vv)
					if string.match(vv, "\n") then big = true end
					if type(v) == "table" then big = true end
				end
			end
			table.sort(entries)
			if obj.id then table.insert(entries, 1, "id = " .. Self.dumpPretty(obj.id, indent + 1)) end
			if obj.name then table.insert(entries, 1, "name = " .. Self.dumpPretty(obj.name, indent + 1)) end
		end
		if big or #entries > 4 then
			return "{\n" .. string.rep("\t", indent + 1) .. table.concat(entries, ",\n" .. string.rep("\t", indent + 1)) .. "\n" .. string.rep("\t", indent) .. "}"
		elseif #entries == 0 then
			return "{}"
		else
			return "{ " .. table.concat(entries, ", ") .. " }"
		end
	elseif type(obj) == "string" then
		if not string.match(obj, "[%c\\\"]") then return '"' .. obj .. '"' else
			return '"' .. string.gsub(obj, "[%c\\\"]", function(c)
				if c == '"' or c == "\\" then return "\\" .. c
				elseif c == "\t" then return "\\t"
				elseif c == "\n" then return "\\n"
				else return "\\" .. string.byte(c)
				end
			end) .. '"'
		end
	else
		return tostring(obj)
	end
end


function Self.HashArgs(...)
    local a = {}
    local Hash
    Hash = function(key, value)
        local t = type(value)
        if t == "table" then
            for k, v in pairs(value) do
                Hash(key .. tostring(k) .. "=", v)
            end
        else
            table.insert(a, tostring(key) .. tostring(value) .. t)
        end
    end
    Hash("", {
        ...
    })
    table.sort(a)
    return table.concat(a, ",")
end

function Self.Map(array, callbackFn)
    local ret = {}
    for i, x in ipairs(array) do
        ret[i] = callbackFn(x)
    end
    return ret
end

function Self.Filter(array, callbackFn)
    local ret = {}
    for _, x in ipairs(array) do
        if callbackFn(x) then
            table.insert(ret, x)
        end
    end
    return ret
end

function Self.Exists(array, func)
	-- This cannot be implemented with Find
	-- in case the matching element is itself nil
	for _, x in ipairs(array) do
		if func(x) then
			return true
		end
	end
	return false
end

function Self.Find(array, func)
	-- There is no way for the caller to distinguish
	-- "we found the nil" from "we didn't find anything"
    for _, x in ipairs(array) do
        if func(x) then
            return x
        end
    end
    return nil
end

function Self.Contains(array, value)
	for _, x in ipairs(array) do
		if value == x then
			return true
		end
	end
	return false
end

function Self.MapT(t, callbackFn)
    local ret = {}
    for i, x in pairs(t) do
        ret[i] = callbackFn(x)
    end
    return ret
end

function Self.FilterT(t, callbackFn)
    local ret = {}
    for i, x in pairs(t) do
        if callbackFn(x) then
            ret[i] = x
        end
    end
    return ret
end

function Self.Values(t)
    local ret = {}
    for _, x in pairs(t) do
        table.insert(ret, x)
    end
    return ret
end

function Self.StartsWith(str, prefix)
    if (str) then
        return str:sub(1, prefix:len()) == prefix
    else
    end
end

function Self.ExpandTemplate(title, args)
    return mw.getCurrentFrame():expandTemplate{
        title = title,
        args = args
    }
end

function Self.ExpandTemplateDebug(title, args)
    local ret = "{{" .. title
    i = 1
    while args[i] ~= nil do
        ret = ret .. "|" .. args[i]
        i = i + 1
    end
    for k, v in pairs(args) do
        if type(k) ~= "number" or k > i then
            ret = ret .. "|" .. k .. "=" .. tostring(v)
        end
    end
    return ret .. "}}"
end

function Self.LazyLoad(moduleName)
    local loaded = nil

    local function doLoad()
        if loaded == nil then
            loaded = mw.loadData(moduleName)
            if loaded.configList then
                loaded = loaded.configList
            end
        end
        return loaded
    end

    local meta = {}
    meta.__index = function(_table, key)
        return doLoad()[key]
    end
    meta.__pairs = function(_table)
        return pairs(doLoad())
    end
    meta.__ipairs = function(_table)
        return ipairs(doLoad())
    end

    local ret = {}
    setmetatable(ret, meta)
    return ret
end

function Self.LoadAsset(moduleName, purgeCache)
    local cacheKey = KEY_PREFIX .. 'LoadAsset' .. moduleName
    if true then
        cache.delete(cacheKey)
    end
    --local cachedAsset = cache.get(cacheKey) or {}
    --if cachedAsset.version == VERSION or cachedAsset.version == 'legacy' then
    --    if cachedAsset.chunks then
    --        -- metatable will not be cached
    --        setupChunkedAssetMetatable(cachedAsset)
    --    end
    --    -- mw.log('Loaded cache', cacheKey)
    --    return cachedAsset.configList
    --end

    local asset = mw.loadData(moduleName)

    -- for backward compatibility
    if asset.version == nil then
        --cache.set(cacheKey, { version = 'legacy', configList = asset }, EXP_TIME)
        return asset
    end

    -- AssetItemEnglish is obfuscated
    --if asset.key == 'Text' then
    --    for key, value in pairs(asset.configList) do
    --        asset.configList[key] = Encoding.decode2(value)
    --    end
    --end

    -- EXP_TIME here is only used to prevent leak
    --cache.set(cacheKey, asset, EXP_TIME)

    -- some modules are too large and are splitted into chunks
    if asset.chunks then
        local copy = { key = asset.key, chunks = asset.chunks, configList = {} }
        for key, value in pairs(asset.configList) do
            copy.configList[key] = value
        end
        setupChunkedAssetMetatable(copy)
        asset = copy
    end

    return asset.configList
end

function setupChunkedAssetMetatable(asset)
    local meta = {}
    meta.__index = function(_table, key)
        for _, chunk in ipairs(asset.chunks) do
            if chunk.low <= key and key <= chunk.high then
                local value = Self.LoadAsset('Module:' .. chunk.name)[key]
                if asset.key == 'Text' then
                    -- do nothing
                    -- value = Encoding.decode2(value)
                end
                return value
            end
        end
    end
    setmetatable(asset.configList, meta)
end

--  Example:
--
--      local function foo(a, b, c)
--          return a + b
--      end
--
--      p.foo = cached(foo, 'ModuleName|foo', function(a, b, c) return { a, b } end)
--
--  argsKeyGenerator: function or nil.
--      When it is a function, it should return a string or an array.
--      When it is nil, the default generator concatenates all args with tostring().
--  revision: number, string, or nil.
--      Change the value will invalidate the cache.
--
--  TODO: Remember dependency and auto-invalidate accordingly?
Self.Cached = function(func, funcKey, argsKeyGenerator, revision)
    if not argsKeyGenerator then
        argsKeyGenerator = defaultArgsKeyGenerator
    end

    return function(...)
        local args = {...}

        local argsKey = argsKeyGenerator(unpack(args))
        if type(argsKey) == 'table' then
            argsKey = table.concat(argsKey, '|')
        elseif type(argsKey) ~= 'string' then
            error('VersionCache: function ' .. funcKey .. ' generated bad args key type: '
                .. type(argsKey) .. ' ' .. tostring(argsKey))
        end

        local key = funcKey .. '|' .. argsKey

        local data = cache.get(key)
        if data ~= nil and data.r == revision then
            return data.v
        end

        local value = func(unpack(args))

        data = {
            r = revision,
            v = value,
        }
        cache.set(key, data, EXP_TIME)

        return value
    end
end

function defaultArgsKeyGenerator(...)
    local n = select('#', ...)
    if n == 0 then
        return ''
    end
    local key = tostring(select(1, ...))
    for i = 2, n do
        key = key .. '|' .. tostring(select(i, ...))
    end
    return key
end

Self.debug = function()
	local Text = Self.LoadAsset('Module:AssetItemChinese')
	mw.log(Text[80008828])
	mw.log(Text[1001])
	if true then return end
	function foo(a, b, c)
		mw.log('a:', a, ' b:', b, 'c:', c)
		return a + b
	end

	bar = Self.Cached(foo, 'foo', nil, 1)
	mw.log(bar(1, 2, 3))
	
	cache.delete('foo|1|2|3')
end

-- ----------------------------------------
-- Required for Modules to function
-- ----------------------------------------
return Self