缺氧 wiki 编辑团队提示:注册账号并登录后体验更佳,且可通过参数设置定制优化您的浏览体验!
该站点为镜像站点,如果你想帮助这个由玩家志愿编辑的 wiki 站点,请前往原站点参与编辑,
同时欢迎加入编辑讨论群 851803695 与其他编辑者一起参与建设!
全站通知:
模块:Utils
刷
历
编
跳到导航
跳到搜索
本模块为本站其他模块提供一些通用的函数。 正确情况下除了数据模块,大多数本站模块都应导入本模块。
文档
程序包项
Utils.fs()
(函数)string. format
的快捷方式Utils.fstr()
(函数)mw. ustring.format
的快捷方式Utils.K0
(member;number)- 绝对零度对应的摄氏度值
Utils.specialUnit
(member;table)- 不使用千克作为单位的例外
Utils.jsFormat(pattern, params, default)
(函数)- 按照 JS 的mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals 模板字符串样式对字符串进行格式化
- 参数:
- 返回:含有输入中的各个单词的 Iterable(函数)
Utils.maintenanceCats
(表)- 维护分类
Utils.autoMaintenanceCats
(表)- 自动维护分类(显示在Special:追踪分类中)
Utils.splitCamel(words)
(函数)- 将驼峰命名的字符串切开
- 参数:
words
输入的驼峰命名字符串。(字符串) - 返回:含有输入中的各个单词的 Iterable.(函数)
Utils.getMsg(msg, ...)
(函数)- 以 I18n 的形式接收消息和数据
- 参数:
Utils.table.concat(...)
(函数)- 合并多个 table
- 参数:
...
被合并的若干个 table(可选) Utils.table.iconcat(...)
(函数)- 合并多个 sequence
- 参数:
...
被合并的若干个 sequence(可选) Utils.table.ihas(t, x, pred)
(函数)- 检查一个 sequence 中的指定内容
- 参数:
- 返回:查找失败则返回 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 ("<" .. s .. ">") == 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