缺氧 wiki 编辑团队提示:注册账号并登录后体验更佳,且可通过参数设置定制优化您的浏览体验!

该站点为镜像站点,如果你想帮助这个由玩家志愿编辑的 wiki 站点,请前往原站点参与编辑,
同时欢迎加入编辑讨论群 851803695 与其他编辑者一起参与建设!

全站通知:

模块:Utils

来自缺氧WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

本模块为本站其他模块提供一些通用的函数。 正确情况下除了数据模块,大多数本站模块都应导入本模块。

文档

程序包项

Utils.fs()函数
string. format 的快捷方式
Utils.fstr()函数
mw. ustring.format 的快捷方式
Utils.K0member;number
绝对零度对应的摄氏度值
Utils.specialUnitmember;table
不使用千克作为单位的例外
Utils.jsFormat(pattern, params, default)函数
按照 JS 的mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals 模板字符串样式对字符串进行格式化
参数
pattern 字符串模板(字符串
params 用于替换字符占位符的值(
default 没有在 params 里找到的占位符的默认值。nil 代表保留占位符(字符串
返回:含有输入中的各个单词的 Iterable(函数
Utils.maintenanceCats
维护分类
Utils.autoMaintenanceCats
自动维护分类(显示在Special:追踪分类中)
Utils.splitCamel(words)函数
将驼峰命名的字符串切开
参数words 输入的驼峰命名字符串。(字符串
返回:含有输入中的各个单词的 Iterable.(函数
Utils.getMsg(msg, ...)函数
以 I18n 的形式接收消息和数据
参数
msg 字符串模板(字符串
... 填入模板中的数据(可选)
Utils.table.concat(...)函数
合并多个 table
参数... 被合并的若干个 table(可选)
Utils.table.iconcat(...)函数
合并多个 sequence
参数... 被合并的若干个 sequence(可选)
Utils.table.ihas(t, x, pred)函数
检查一个 sequence 中的指定内容
参数
t 待查找的 sequence(
x 查找的目标;若为 nil 则实用 pred
pred 用函数判断查找的条件(函数;可选)
返回:查找失败则返回 false; 否则如果 x 不为 nil 则返回 true; 否则 返回第一个符合 pred 的项



-- Module:Utils
--- 本模块为本站其他模块提供一些通用的函数。正确情况下除了数据模块,大多数本站模块都应导入本模块。
--
--  @module  Utils
--  @author   DDElephant
--  @require Module:I18n
--  @require Module:Data/Elements
--  @require Module:Dev/Arguments
local p = {}
p.table = {}
require('Module:Dev/Arguments')
local getArgs = require('Module:Dev/Arguments').getArgs
local i18nit = require("Module:I18n").loadMessages("module:i18n/Items")
local i18nbu = require("Module:I18n").loadMessages("module:i18n/Buildings")
local i18ncr = require("Module:I18n").loadMessages("module:i18n/Creatures")
local i18nel = require("Module:I18n").loadMessages("module:i18n/Elements")
local i18neq = require("Module:I18n").loadMessages("module:i18n/Equipment")
local i18nmi = require("Module:I18n").loadMessages("module:i18n/Misc")
local i18nro = require("Module:I18n").loadMessages("module:i18n/Robots")
local i18ndu = require("Module:I18n").loadMessages("module:i18n/Duplicants")
local __ = {
    i = function(...) return i18nit:msg(...) end,
    b = function(...) return i18nbu:msg(...) end,
    c = function(...) return i18ncr:msg(...) end,
    e = function(...) return i18nel:msg(...) end,
    q = function(...) return i18neq:msg(...) end,
    m = function(...) return i18nmi:msg(...) end,
    r = function(...) return i18nro:msg(...) end,
    d = function(...) return i18ndu:msg(...) end
}
local entityIds = mw.loadData("Module:i18n/EntityIds")
local cate2I18nData = {
    ['BUILDINGS'] = "b",
    ['ELEMENTS'] = 'e',
    ['UI'] = 'u',
    ['ITEMS'] = 'i',
    ['NAMEGEN'] = 'm',
    ['DUPLICANTS'] = 'd',
    ['CODEX'] = 'm',
    ['CREATURES'] = 'c',
    ['MISC'] = 'm',
    ['ROBOTS'] = 'r',
    ['EQUIPMENT'] = 'q',
}
local elData = mw.loadData("Module:Data/Elements")

--- <code>string.format</code> 的快捷方式
--  @function p.fs
p.fs = string.format

--- <code>mw.ustring.format</code> 的快捷方式
--  @function p.fstr
p.fstr = mw.ustring.format

-- 将 fstr 变为 invocable
p._fstr = function(args) return p.fstr(args[1], args[2]) end

--- 绝对零度对应的摄氏度值
--  @member {number} p.K0 
p.K0 = -273.15

p.DLCS = {
    [""] = "STRINGS.UI.VANILLA.NAME",
    ["EXPANSION1_ID"] = "STRINGS.UI.DLC1.NAME",
    ["DLC2_ID"] = "STRINGS.UI.DLC2.NAME",
}
p.DLC_ICONS = {
    [""] = "{{游戏本体图标}}",
    ["EXPANSION1_ID"] = "{{眼冒金星图标}}",
    ["DLC2_ID"] = "{{寒霜行星包图标}}",
}

-- test by: = p.unitAmount(1.000005)
function p.unitAmount(n)
    if type(n) == 'number' then n = p.float2str(n, 2) end
    return tostring(n) .. ' 单位'
end

--- 不使用千克作为单位的例外
-- @member {table} p.specialUnit
p.specialUnit = { --
    HighEnergyParticle = p.unitAmount,
    PioneerLander = p.unitAmount,
    ScoutLander = p.unitAmount,
    ScoutRover = p.unitAmount,
    GeneShufflerRecharge = p.unitAmount,
    RailGunPayload = p.unitAmount,
    MissileBasic = p.unitAmount,
    Morb = p.unitAmount,
    Scout = p.unitAmount,
    SweepBot = p.unitAmount,
    ZombieSpores = p.unitAmount,
    ResearchDatabank = p.unitAmount,
    OrbitalResearchDatabank = p.unitAmount,
}

local function getMaintenanceCatsGetter(cat)
    return function(msg)
        local warningMsgTemplate =
            "注意,当前页面%s,如不能自行排除错误请[[Project:入站指引#相关群组|联系管理员]]"
        local warningMsg = p.fstr(warningMsgTemplate, cat)
        if msg ~= nil then
            warningMsg = warningMsg .. ":" .. tostring(msg)
        end
        local tracebackTemplate =
            '<div class="mw-collapsible mw-collapsed">%s<div class="mw-collapsible-content"><small>%s</small></div></div>'
        mw.addWarning(p.fstr(tracebackTemplate, warningMsg,
                             debug.traceback():gsub("\n", "<br/>")))
        return p.fstr("[[Category:%s的页面]]", cat)
    end
end

--- 按照 JS 的[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals 模板字符串]样式对字符串进行格式化
--  @function p.jsFormat
--  @param              {string} pattern 字符串模板
--  @param              {table}  params  用于替换字符占位符的值
--  @param              {string} default 没有在 params 里找到的占位符的默认值。nil 代表保留占位符 
--  @return             {function} 含有输入中的各个单词的 Iterable
function p.jsFormat(pattern, params, default)
    return mw.ustring.gsub(pattern, "%${(.-)}", function(m)
        -- replace with params
        return params[m] or default
    end)
end

--- 维护分类
p.maintenanceCats = {
    invalidParams = getMaintenanceCatsGetter("模板参数设置错误"),
    deprecatedParams = getMaintenanceCatsGetter("使用弃用模板参数"),
    deprecatedTemplate = getMaintenanceCatsGetter("使用弃用模板"),
    deprecatedModule = getMaintenanceCatsGetter("使用弃用模块"),
    missingData = getMaintenanceCatsGetter("缺少数据"),
    upgradableData = getMaintenanceCatsGetter("数据可更新"),
    getMaintenanceCatsGetter = getMaintenanceCatsGetter
}

--- 自动维护分类(显示在[[Special:追踪分类]]中)
p.autoMaintenanceCats = {
    "[[Category:已索引页面]]", "[[Category:不可索引页面]]",
    "[[Category:调用重复模板参数的页面]]",
    "[[Category:有过多高开销解析器函数调用的页面]]",
    "[[Category:含有略过模板参数的页面]]",
    "[[Category:模板包含上限已经超过的页面]]",
    "[[Category:隐藏分类]]",
    "[[Category:含有受损文件链接的页面]]",
    "[[Category:页面的节点数超出限制]]",
    "[[Category:扩展深度超出限制的页面]]",
    "[[Category:有忽略显示标题的页面]]",
    "[[Category:使用无效自封闭HTML标签的页面]]",
    "[[Category:有模板循环的页面]]",
    "[[Category:有参考文献错误的页面]]",
    "[[Category:有数学错误的页面]]",
    "[[Category:有数学渲染错误的页面]]",
    "[[Category:有脚本错误的页面]]",
    "[[Category:有错误的Scribunto模块]]",
    "[[Category:有语法高亮错误的页面]]",
    "[[Category:Pages using DynamicPageList parser tag]]",
    "[[Category:Pages using DynamicPageList Intersection parser tag]]",
    "[[Category:Pages using DynamicPageList parser function]]",
    "[[Category:Pages using DynamicPageList dplnum parser function]]",
    "[[Category:Pages using DynamicPageList dplvar parser function]]",
    "[[Category:Pages using DynamicPageList dplreplace parser function]]",
    "[[Category:Pages using DynamicPageList dplchapter parser function]]",
    "[[Category:Pages using DynamicPageList dplmatrix parser function]]",
    "[[Category:使用时间线的页面]]",
    "[[Category:使用ISBN魔术链接的页面]]"
}

--- 将驼峰命名的字符串切开
--  @function p.splitCamel
--  @param              {string} words 输入的驼峰命名字符串。
--  @return             {function} 含有输入中的各个单词的 Iterable.
function p.splitCamel(words)
    local s = words:gsub("^%l", string.upper)
    return s:gmatch("%u%l*")
end

-- convert a chamelCaseName to an array of its words
function p.camel2Array(words)
    local o = {}
    for w in p.splitCamel(words) do table.insert(o, w) end
    return o
end

-- Get the string context for a string s, with suggested type t
-- test: = p.getCode("industrial", "BabyCrabShell")
-- test: = p.getCode("pill", "IntermediateCure")
-- test: = p.getCode("seed", "SaltPlantSeed")
-- test: = p.getCode("variant", "PuftBleachstone")
function p.getCode(t, s)
    local codeFormat = {
        tag = "STRINGS.MISC.TAGS.%s",
        element = "STRINGS.ELEMENTS.%s.NAME",
        food = "STRINGS.ITEMS.FOOD.%s.NAME",
        industrial_ = "STRINGS.ITEMS.INDUSTRIAL_PRODUCTS.%s.NAME",
        pill = "STRINGS.ITEMS.PILLS.%s.NAME",
        building = "STRINGS.BUILDINGS.PREFABS.%s.NAME",
        family = "STRINGS.CREATURES.FAMILY.%s",
        item = "STRINGS.ITEMS.%s.NAME",
        creature = "STRINGS.CREATURES.SPECIES.%s.NAME",
        creature_ = "STRINGS.CREATURES.SPECIES.%s.NAME",
        equipment = "STRINGS.EQUIPMENT.PREFABS.%s.NAME",
        equipment_ = "STRINGS.EQUIPMENT.PREFABS.%s.NAME",
        seed = "STRINGS.CREATURES.SPECIES.SEEDS.%s.NAME",
        variant = "STRINGS.CREATURES.SPECIES.%s.VARIANT_%s.NAME",
        robot = "STRINGS.ROBOTS.MODELS.%s.NAME",
        disease = "STRINGS.DUPLICANTS.DISEASES.%s.NAME"
    }
    if codeFormat[t] == nil then return nil end
    if t:sub(-1) == "_" then s = table.concat(p.camel2Array(s), "_") end
    if t == "seed" then s = s:match("^(%w+)Seed$") or s end
    if t == "variant" then
        local worlds = p.camel2Array(s)
        if #worlds < 2 then return 'MISSING' end
        for i = 1, #worlds - 1 do
            local familyWords = {}
            for j, w in ipairs(worlds) do
                table.insert(familyWords, w)
                if j == i then break end
            end
            local familyCode = table.concat(familyWords, '')
            local variantCode = s:sub(#familyCode + 1)
            local locId = p.fs(codeFormat[t], familyCode:upper(),
                               variantCode:upper())
            local locStr = __['c'](locId)
            if not p.isDefaultT(locId, locStr) then return locId end
        end
        return 'MISSING'
    end
    return p.fs(codeFormat[t], s:upper())
end

-- Check if "t", the result of running i18n on "s", 
-- is not translated successfully
function p.isDefaultT(s, t) return ("&#60;" .. s .. "&#62;") == t end

-- Get wiki link 
function p.wikiLink(name, opts)
    if type(opts) ~= "table" then return p.fstr("[[%s]]", name) end

    local link, alt = name, name
    if opts.isCate then link = p.fstr(":Category:%s", name) end

    if opts.alt ~= nil then alt = opts.alt end

    if link == alt then
        return p.fstr("[[%s]]", alt)
    else
        return p.fstr("[[%s|%s]]", link, alt)
    end
end

-- Get dlc icons
function p.dlcIcons(dlcIds)
    local icons = {}
    for _, dlc in ipairs(dlcIds) do
        local icon = p.DLC_ICONS[dlc]
        if icon ~= nil then
            table.insert(icons, icon)
        end
    end
    return icons
end

-- 通过查表,快速获取Id对应的译名
function p.getEntryLocId(id)
    for cate, entities in pairs(entityIds) do
        local locId = entities[id]
        if locId ~= nil then
            local locStr = id
            local subStr = cate2I18nData[cate]
            if subStr ~= nil then
                locStr = __[subStr](locId)
            end
            return locStr, cate == "MISC", locId, cate
        end
    end
    return nil, nil, nil, nil
end
-- Get entry name, entry could be a:
-- - food
-- - element
-- - creature (critter or plant)
-- - equipment (e.g. pressure suit)
-- - tag (e.g. Metal Ore)
--
-- Return nil if no entry is matched, or the localized string and a 
-- boolean value indicating whether this entry is a wiki category and 
-- a string of the localizationID
--
-- test by: = p.getEntry("Electrum")
-- test by: = p.getEntry("Phosphorite")
-- test by: = p.getEntry("Ore")
-- test by: = p.getEntry("BeanPlant")
-- test by: = p.getEntry("Meat")
-- test by: = p.getEntry("SwampLily")
-- test by: = p.getEntry("BabyCrabShell")
-- test by: = p.getEntry("WoodLog")
-- test by: = p.getEntry("IntermediateCure")
-- test by: = p.getEntry("HighEnergyParticle")
-- test by: = p.getEntry("PioneerLander")
-- test by: = p.getEntry("ScoutRover")
-- test by: = p.getEntry("RailGunPayload")
-- test by: = p.getEntry("LeadSuit")
-- test by: = p.getEntry("ForestTree")
-- test by: = p.getEntry("SaltPlantSeed")
-- test by: = p.getEntry("PuftBleachstone")
function p.getEntry(id)
    -- try to table lookup
    local locStr, isCate, locId, entryType = p.getEntryLocId(id)
    if locId ~= nil and not p.isDefaultT(locId, locStr) then
        return locStr, isCate, locId, entryType
    end

    -- try to parse as an element
    if elData[id] then
        return __.e(elData[id].localizationID), false, elData[id].localizationID
    end

    -- try to parse as below types
    local entryIsCate = {
        tag = {isCate = true, subStr = "m"},
        food = {isCate = false, subStr = "i"},
        creature = {isCate = false, subStr = "c"},
        creature_ = {isCate = false, subStr = "c"},
        industrial_ = {isCate = false, subStr = "i"},
        pill = {isCate = false, subStr = "i"},
        building = {isCate = false, subStr = "b"},
        family = {isCate = false, subStr = "c"},
        item = {isCate = false, subStr = "i"},
        equipment = {isCate = false, subStr = "q"},
        equipment_ = {isCate = false, subStr = "q"},
        seed = {isCate = false, subStr = "c"},
        variant = {isCate = false, subStr = "c"},
        robot = {isCate = false, subStr = "r"},
        disease = {isCate = false, subStr = "d"}
    }
    for entryType, prop in pairs(entryIsCate) do
        local locId = p.getCode(entryType, id)
        local locStr = __[prop.subStr](locId)
        if not p.isDefaultT(locId, locStr) then
            return locStr, prop.isCate, locId, entryType
        end
    end

    -- check special list
    local special = {
    }


    if special[id:upper()] ~= nil then
        local sp = special[id:upper()]
        return __[sp[2]](sp[1]), sp[3], sp[1], sp[4] or "special"
    end

    mw.log(p.fstr("Can not find localization for id '%s'", id))
    return nil
end

-- Check of a string starts with a prefix
-- test by: = p.startswith("1", "1")
-- test by: = p.startswith("123", "23")
-- test by: = p.startswith("123", "12")
-- test by: = p.startswith("123", "12342322")
function p.startswith(str, pre) return mw.ustring.sub(str, 1, #pre) == pre end

-- Check of a string ends with a suffix
-- test by: = p.endswith("1", "1")
-- test by: = p.endswith("123", "23")
-- test by: = p.endswith("12345", "45")
-- test by: = p.endswith("123", "4")
-- test by: = p.endswith("123", "12342322")
function p.endswith(str, suf)
    if #str < #suf then return false end
    return mw.ustring.sub(str, #str - #suf + 1) == suf
end

-- round the input number to a maximun digits
-- test by: = p.float2str(1, 2)
-- test by: = p.float2str(10, 3)
-- test by: = p.float2str(.100000005, 2)
-- test by: = p.float2str(.19999999995, 2)
-- test by: = p.float2str(-.19999999995, 2)
-- test by: = p.float2str(.1213245, 3)
-- test by: = p.float2str(.1213245, 3, true)
function p.float2str(n, digits, plus)
    if digits == nil then
        digits = 2
    elseif digits > 5 then
        mw.log("Warning Too much digits")
    end
    for d = 0, digits - 1 do
        if math.abs(n % math.pow(10, -d) - math.pow(10, -d)) <
            math.pow(10, -digits - 2) --
        or math.abs(n % math.pow(10, -d)) < math.pow(10, -digits - 2) --  
        then
            return p.fstr(p.fstr("%%" .. (plus and "+" or "") .. ".%df", d), n)
        end
    end
    return p.fstr(p.fstr("%%" .. (plus and "+" or "") .. ".%df", digits), n)
end

-- test by: = p.kg2str(.000100000000003)
-- test by: = p.kg2str(.0100000000003)
-- test by: = p.kg2str(.0105100000003)
-- test by: = p.kg2str(.0105123000003)
-- test by: = p.kg2str(-0.1)
function p.kg2str(kg, digits)
    local num2str = function(n) return p.float2str(n, digits or 3) end
    kg = tonumber(kg)
    if math.abs(kg) > 1001 then -- prevent floating-point issue (hopefully)
        return p.fstr("%s 吨", num2str(kg / 1000))
    elseif math.abs(kg) > 1.001 then
        return p.fstr("%s 千克", num2str(kg))
    elseif math.abs(kg * 1000) > 1.001 then
        return p.fstr("%s 克", num2str(kg * 1000))
    else
        return p.fstr("%s 毫克", num2str(kg * 1000000))
    end
end

-- Get the height from image's resize oprtion
-- test by: = p.resize2height("100px")
-- test by: = p.resize2height("x100px")
-- test by: = p.resize2height("100x100px")
function p.resize2height(resize)
    local rawHeigt = resize:match("x%d+px")
    return rawHeigt and tonumber(rawHeigt:sub(2, -3))
end
function p._resize2height(args) return
    p.resize2height(args[1] or args["resize"]) end

-- test: mw.logObject(p.map({1,3,5}, function(x) return x + 1 end))
function p.map(t, f)
    local o = {}
    for _, v in ipairs(t) do table.insert(o, f(v)) end
    return o
end

--- 以 I18n 的形式接收消息和数据
--  @function p.getMsg
--  @param      {string} msg 字符串模板
--  @param[opt]          ... 填入模板中的数据

-- test by: = p.getMsg("hello, $1", "a")
-- test by: = p.getMsg("hello, $1, $1", "a", "b")
-- test by: = p.getMsg("hello, $1, $2", "a", "b")
-- test by: = p.getMsg("hello, $1, $2", "a")
function p.getMsg(msg, ...)
    for i, a in ipairs({...}) do
        if a ~= nil then
            msg = mw.ustring.gsub(msg, p.fstr("$%d", i), tostring(a))
        end
    end
    return msg
end

--- 合并多个 table
--  @function p.table.concat
--  @param[opt]          ... 被合并的若干个 table

-- test by: = mw.logObject(p.table.concat({a=1},{b=2}, {c=3, d=4, a=5}))
function p.table.concat(...)
    local dst = {}
    for i = 1, select('#', ...) do
        for k, v in pairs(select(i, ...)) do dst[k] = v end
    end
    return dst
end

--- 合并多个 sequence
--  @function p.table.iconcat
--  @param[opt]          ... 被合并的若干个 sequence

-- test by: = mw.logObject(p.table.iconcat({1},{2}, {3,4,5}))
function p.table.iconcat(...)
    local dst = {}
    for i = 1, select('#', ...) do
        for _, v in ipairs(select(i, ...)) do table.insert(dst, v) end
    end
    return dst
end

--- 检查一个 sequence 中的指定内容
--  @function p.table.ihas
--  @param              {table}    t    待查找的 sequence
--  @param                         x    查找的目标;若为 nil 则实用 pred
--  @param[opt]         {function} pred 用函数判断查找的条件
--  @return                        查找失败则返回 false; 否则如果 x 不为 nil 则返回 true; 否则 返回第一个符合 pred 的项

-- test by: = mw.logObject(p.table.ihas({1,2,3}, 3))
-- test by: = mw.logObject(p.table.ihas({1,2,3}, 4))
-- test by: = mw.logObject(p.table.ihas({1,2,3}, nil, function (x) return x % 2 == 0 end))
function p.table.ihas(t, x, pred)
    for _, v in ipairs(t) do
        if x ~= nil then
            if v == x then return true end
        elseif type(pred) == 'function' then
            if pred(v) then return v end
        else
            mw.logObject(pred)
            error("Invalid predicate!")
        end
    end
    return false
end

-- test by: = p.main(require("Module:debug").frame({"resize2height", "100x100px"},{}))
-- test by: = p.main(require("Module:debug").frame({"fstr", "%.2f", "100"},{}))
function p.main(frame)
    local args = getArgs(frame)
    local funcName = args[1]
    local funcArgs = {}
    local maxIdx = 0
    for i, v in ipairs(args) do
        if i > 1 then
            funcArgs[i - 1] = v
            maxIdx = i
        end
    end
    for k, v in pairs(args) do
        if k ~= maxIdx and funcArgs[k] == nil then funcArgs[k] = v end
    end
    local func = p["_" .. funcName]
    return func(funcArgs)
end

return p