模組:ACGaward
外观
![]() | 此模块使用Lua语言: |
本模块用于处理維基ACG專題獎分数相关内容。请在Module:ACGaward/list登记用户得分。
用法
[编辑]本模组包括若干函数:
score
- 根据页面名獲得用戶专题奖得分。亦可用|user=
调取指定用户得分,或透过|mapping=
从其他模组调取分数(如Module:ACGaward/list/old
)。level
- 獲得特定用戶的专题奖等级。特性同上。ranking
- 获取用户专题奖等级列表,效果见WikiProject:ACG/維基ACG專題獎/分數排名榜。可透过|mapping=
从其他模组调取分数生成名单。diff
- 和Module:ACGaward/list/old搭配使用,比較兩筆記錄間的用戶分数變化情況。亦可使用|mapping1=
和|mapping2=
指定两个页面来对比。
備註
[编辑]以下子模组和本模組功能無關,係爲避免「浪費」而借用本模組空間:
- /nominee check — 用於
{{ACG提名/check}}
。 - /nominee check new — 用於
{{ACG提名2/request}}
、{{ACG提名2/check}}
等处
require("strict")
local __all__ = { "score", "level", "ranking", "diff" }
local strf = mw.ustring.format
local getArgs = require("Module:Arguments").getArgs
--- @alias Username string
--- @alias Score number
--- @alias Level number
--- @alias RecordMapping table<Username, Score>
--- @alias Wikitext string
--- @class UserScore
--- @field user Username
--- @field score Score
--- @class UserScoreDelta
--- @field user Username
--- @field delta Score
--- @field current Score
--- @type Level[]
local LEVEL_NODES = { math.huge, 40, 20, 10, 5, 4, 3, 2, 1, 0 }
--- @type string
local CURRENT_RECORD_MODULE = "Module:ACGaward/list"
--- @type string
local OLD_RECORD_MODULE = "Module:ACGaward/list/old"
--- Whether a value is empty (`nil` or "").
--- Other values, including 0, `false` and `{}`, are treated as falsy.
--- @param value any @ The value to be evaluated.
--- @return boolean @The result.
local function is_empty(value)
if value == nil or value == "" then
return true
end
return false
end
--- Load the record mapping from a module.
--- @param module string | nil @The module name, "OLD", or empty.
--- @return RecordMapping @The loaded record mapping.
local function load_record_module(module)
if is_empty(module) then
return mw.loadData(CURRENT_RECORD_MODULE)
end
if module == "OLD" then
return mw.loadData(OLD_RECORD_MODULE)
end
return mw.loadData(module)
end
--- Return a normalized username or current page base name if nil.
--- @param user Username | nil @Optional username to normalize.
--- @return Username @The normalized username or current page base name.
local function fetch_username(user)
if is_empty(user) then
return mw.title.getCurrentTitle().baseText
end
local username = user -- TODO: Implement normalization
return username
end
--- Return name and recorded score from the given record mapping.
--- If the username is not recorded, the score will be 0.
--- @param mapping RecordMapping @Mapping of username to score.
--- @param user Username | nil @Optional username to look up.
--- @return Username, Score @Username and associated score.
local function get_record(mapping, user)
local name = fetch_username(user)
return name, (mapping[name] or 0)
end
--- Convert an internal score to an integer score.
--- @param score Score @The score that might have a decimal part.
--- @return Score @The score as an integer (floor value).
local function calculate_public_score(score)
return math.floor(score)
end
--- Convert a score to its corresponding ACG-award level.
--- @param score Score @The score to convert to a level.
--- @return Level @The level as an integer.
local function calculate_level(score)
return math.floor(score / 10)
end
--- Get a normalized score for a user from record mapping.
--- @param mapping RecordMapping @Mapping of username to score.
--- @param user Username | nil @Optional username to look up.
--- @return Score @The normalized score for the user.
local function get_score(mapping, user)
local _, score = get_record(mapping, user)
return calculate_public_score(score)
end
--- Get a normalized level for a user from record mapping.
--- @param mapping RecordMapping @Mapping of username to score.
--- @param user Username | nil @Optional username to look up.
--- @return Level @The level calculated from the user's score.
local function get_level(mapping, user)
local _, score = get_record(mapping, user)
return calculate_level(score)
end
--- Create wikicode ranking for users within level bounds.
--- @param list UserScore[] @Sequence of UserScore tuples to include.
--- @param lbound Level @The lower bound of level (included).
--- @param ubound Level @The upper bound of level (excluded).
--- @param start number @Starting number for the ordered list.
--- @return Wikitext @Formatted wikicode for the subset ranking.
local function create_subset_ranking(list, lbound, ubound, start)
if #list == 0 then
return
end
local lines = {}
-- generate header based on level bounds
local header
if ubound == math.huge then
header = tostring(lbound) .. "級或以上維基ACG獎"
elseif ubound - lbound == 1 then
header = tostring(lbound) .. "級維基ACG獎"
else
header = tostring(lbound) .. "至" .. tostring(ubound - 1) .. "級維基ACG獎"
end
header = "<b>" .. header .. "</b>"
table.insert(lines, header)
-- generate ordered list with the proper start number
local line = strf('<ol start="%d">', start)
table.insert(lines, line)
for _, v in ipairs(list) do
line = strf("<li>[[User:%s|%s]]:%d分", v.user, v.user, v.score)
table.insert(lines, line)
end
table.insert(lines, "</ol>")
-- return the result
return table.concat(lines, "\n")
end
--- Build a complete ranking of users grouped by level in wikicode.
--- @param mapping RecordMapping @Mapping of username to score.
--- @return Wikitext @Formatted wikicode with the complete ranking.
local function build_ranking(mapping)
local score_list = {}
for user, score in pairs(mapping) do
local item = { user = user, score = calculate_public_score(score) }
table.insert(score_list, item)
end
local sorter = function(a, b)
if a.score == b.score then
return a.user < b.user
end
return a.score > b.score
end
table.sort(score_list, sorter)
-- process users by level groups
local subset_ranking_parts = {}
local sub_score_list = {}
local rank = 0
local node_index = 1
for i, user in ipairs(score_list) do
local user_level = calculate_level(user.score)
while user_level < LEVEL_NODES[node_index] do
local subset_ranking = create_subset_ranking(
sub_score_list,
LEVEL_NODES[node_index],
LEVEL_NODES[node_index - 1],
rank
)
table.insert(subset_ranking_parts, subset_ranking)
-- reset for the next level
sub_score_list = {}
rank = i
node_index = node_index + 1
end
table.insert(sub_score_list, user)
end
return table.concat(subset_ranking_parts, "\n")
end
--- Build a difference report listing users with score change.
--- @param mapping1 RecordMapping @The record mapping with old data.
--- @param mapping2 RecordMapping @The record mapping with new data.
--- @return Wikitext @Formatted wikicode with the report.
local function build_difference(mapping1, mapping2)
local users = {}
for k, _ in pairs(mapping1) do
table.insert(users, k)
end
for k, _ in pairs(mapping2) do
if mapping1[k] == nil then
table.insert(users, k)
end
end
-- process data list
local diff_score_list = {}
for _, user in ipairs(users) do
local score1 = get_score(mapping1, user)
local score2 = get_score(mapping2, user)
local score_delta = score2 - score1
if score_delta ~= 0 then
--- @type UserScoreDelta
local item = { user = user, delta = score_delta, current = score2 }
table.insert(diff_score_list, item)
end
end
local sorter = function(a, b)
if a.delta == b.delta then
return a.current > b.current
end
return a.delta > b.delta
end
table.sort(diff_score_list, sorter)
-- generate result
local lines = {}
local ptn = '# <code%s>* [[User:%s|%s]]:共得%d分'
.. '<small>(累計分數%d分)</small>%s</code>'
for _, v in ipairs(diff_score_list) do
local user, delta, current = v.user, v.delta, v.current
local style, tag = "", ""
if delta < 0 then
style = ' style="color: red;"'
elseif delta == current then
style = ' style="color: green;'
tag = " {{color|green|N}}"
end
local line = strf(ptn, style, user, user, delta, current, tag)
table.insert(lines, line)
end
return table.concat(lines, "\n")
end
local p = {}
function p._score(args)
local module = args.module
local user = args.user
local mapping = load_record_module(module)
return tostring(get_score(mapping, user))
end
function p._level(args)
local module = args.module
local user = args.user
local mapping = load_record_module(module)
return tostring(get_level(mapping, user))
end
function p._ranking(args)
local module = args.module
local mapping = load_record_module(module)
return build_ranking(mapping)
end
function p._diff(args)
local module1 = args[1] or "OLD"
local module2 = args[2]
local mapping1 = load_record_module(module1)
local mapping2 = load_record_module(module2)
return build_difference(mapping1, mapping2)
end
local function makeInvokeFunc(func_name)
return function(frame)
local args = getArgs(frame)
return p[func_name](args)
end
end
for _, func_name in ipairs(__all__) do
p[func_name] = makeInvokeFunc("_" .. func_name)
end
return p