<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh">
	<id>https://arolstar52-zhtest.hf.space/index.php?action=history&amp;feed=atom&amp;title=Module%3AProgression2</id>
	<title>Module:Progression2 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://arolstar52-zhtest.hf.space/index.php?action=history&amp;feed=atom&amp;title=Module%3AProgression2"/>
	<link rel="alternate" type="text/html" href="https://arolstar52-zhtest.hf.space/index.php?title=Module:Progression2&amp;action=history"/>
	<updated>2026-06-26T15:57:32Z</updated>
	<subtitle>在这个wiki上该页的修订历史</subtitle>
	<generator>MediaWiki 1.43.8</generator>
	<entry>
		<id>https://arolstar52-zhtest.hf.space/index.php?title=Module:Progression2&amp;diff=4884463&amp;oldid=prev</id>
		<title>imported&gt;For Each ... Next 来自 2026年4月11日 (六) 05:24</title>
		<link rel="alternate" type="text/html" href="https://arolstar52-zhtest.hf.space/index.php?title=Module:Progression2&amp;diff=4884463&amp;oldid=prev"/>
		<updated>2026-04-11T05:24:22Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--- Render a progression bar for wiki pages.&lt;br /&gt;
&lt;br /&gt;
require(&amp;quot;strict&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local getArgs = require(&amp;quot;Module:Arguments&amp;quot;).getArgs&lt;br /&gt;
local yesno = require(&amp;quot;Module:Yesno&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
--- @class Progression2Args&lt;br /&gt;
--- @field [integer] string?&lt;br /&gt;
--- @field total string?&lt;br /&gt;
--- @field task string?&lt;br /&gt;
--- @field hidecomplete string?&lt;br /&gt;
--- @field width string?&lt;br /&gt;
--- @field footer string?&lt;br /&gt;
&lt;br /&gt;
--- @class Progression2Module&lt;br /&gt;
--- @field main fun(frame: table): string&lt;br /&gt;
--- @field _main fun(args: Progression2Args, frame?: table): string&lt;br /&gt;
&lt;br /&gt;
--- @type Progression2Module&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
--- @type string&lt;br /&gt;
local TEMPLATE_STYLES = &amp;quot;Progression2/styles.css&amp;quot;&lt;br /&gt;
&lt;br /&gt;
--- @type string&lt;br /&gt;
local PROGRESS_PERCENT_FORMAT = &amp;quot;.1f&amp;quot;&lt;br /&gt;
&lt;br /&gt;
--- Return the TemplateStyles tag for this module.&lt;br /&gt;
---&lt;br /&gt;
--- @param frame table&lt;br /&gt;
--- @return string&lt;br /&gt;
local function renderStyles(frame)&lt;br /&gt;
    return frame:extensionTag(&amp;quot;templatestyles&amp;quot;, &amp;quot;&amp;quot;, {&lt;br /&gt;
        src = TEMPLATE_STYLES,&lt;br /&gt;
    })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Format the displayed progress percentage.&lt;br /&gt;
---&lt;br /&gt;
--- @param value number&lt;br /&gt;
--- @return string&lt;br /&gt;
local function formatProgressPercent(value)&lt;br /&gt;
    return string.format(&amp;quot;%&amp;quot; .. PROGRESS_PERCENT_FORMAT, value)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Trim leading and trailing whitespace.&lt;br /&gt;
---&lt;br /&gt;
--- @param value string&lt;br /&gt;
--- @return string&lt;br /&gt;
local function trim(value)&lt;br /&gt;
    return mw.text.trim(value)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Evaluate text via `{{#expr: ... }}`.&lt;br /&gt;
---&lt;br /&gt;
--- Returns nil when no frame is available or when evaluation fails.&lt;br /&gt;
---&lt;br /&gt;
--- @param frame? table&lt;br /&gt;
--- @param text string?&lt;br /&gt;
--- @return string?&lt;br /&gt;
local function evaluateExpression(frame, text)&lt;br /&gt;
    if not frame or text == nil then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    text = trim(tostring(text))&lt;br /&gt;
    if text == &amp;quot;&amp;quot; then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local ok, result = pcall(&lt;br /&gt;
        frame.callParserFunction,&lt;br /&gt;
        frame,&lt;br /&gt;
        &amp;quot;#expr&amp;quot;,&lt;br /&gt;
        text&lt;br /&gt;
    )&lt;br /&gt;
    if not ok or result == nil then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    result = trim(tostring(result))&lt;br /&gt;
    if result == &amp;quot;&amp;quot; then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if result:match(&amp;#039;class=&amp;quot;error&amp;quot;&amp;#039;) then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if result:match(&amp;quot;^Expression error:&amp;quot;) then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Parse a number from plain or localized formatted text.&lt;br /&gt;
---&lt;br /&gt;
--- @param text string?&lt;br /&gt;
--- @return number?&lt;br /&gt;
local function parseNumber(text)&lt;br /&gt;
    if text == nil then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local number = tonumber(text)&lt;br /&gt;
    if number ~= nil then&lt;br /&gt;
        return number&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local contentLang = mw.language.getContentLanguage()&lt;br /&gt;
    local ok, parsed = pcall(&lt;br /&gt;
        contentLang.parseFormattedNumber,&lt;br /&gt;
        contentLang,&lt;br /&gt;
        tostring(text)&lt;br /&gt;
    )&lt;br /&gt;
    if ok then&lt;br /&gt;
        return parsed&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Resolve one argument to display text and numeric value.&lt;br /&gt;
---&lt;br /&gt;
--- The display text is the evaluated `#expr` result when available,&lt;br /&gt;
--- otherwise the original text.&lt;br /&gt;
---&lt;br /&gt;
--- @param frame? table&lt;br /&gt;
--- @param rawValue string?&lt;br /&gt;
--- @param defaultText string&lt;br /&gt;
--- @param defaultNumber number&lt;br /&gt;
--- @return string displayText&lt;br /&gt;
--- @return number number&lt;br /&gt;
local function resolveValue(frame, rawValue, defaultText, defaultNumber)&lt;br /&gt;
    local sourceText = rawValue&lt;br /&gt;
    if sourceText == nil or sourceText == &amp;quot;&amp;quot; then&lt;br /&gt;
        sourceText = defaultText&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    sourceText = tostring(sourceText)&lt;br /&gt;
&lt;br /&gt;
    local evaluatedText = evaluateExpression(frame, sourceText)&lt;br /&gt;
    local displayText = evaluatedText or sourceText&lt;br /&gt;
    local number = parseNumber(displayText)&lt;br /&gt;
&lt;br /&gt;
    if number == nil then&lt;br /&gt;
        number = defaultNumber&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return displayText, number&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Return resolved display and numeric values.&lt;br /&gt;
---&lt;br /&gt;
--- @param args Progression2Args&lt;br /&gt;
--- @param frame? table&lt;br /&gt;
--- @return number done&lt;br /&gt;
--- @return number total&lt;br /&gt;
--- @return string doneText&lt;br /&gt;
--- @return string totalText&lt;br /&gt;
local function getResolvedValues(args, frame)&lt;br /&gt;
    local doneText, done = resolveValue(frame, args[1], &amp;quot;0&amp;quot;, 0)&lt;br /&gt;
    local totalText, total = resolveValue(&lt;br /&gt;
        frame,&lt;br /&gt;
        args[2] or args.total,&lt;br /&gt;
        &amp;quot;100&amp;quot;,&lt;br /&gt;
        100&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    if total &amp;lt;= 0 then&lt;br /&gt;
        total = 100&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return done, total, doneText, totalText&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Insert English-style thousands separators into a numeric string.&lt;br /&gt;
---&lt;br /&gt;
--- @param text string&lt;br /&gt;
--- @return string&lt;br /&gt;
local function groupThousands(text)&lt;br /&gt;
    local sign, integerPart, fractionPart = text:match(&lt;br /&gt;
        &amp;quot;^([%+%-]?)(%d+)(%.%d+)?$&amp;quot;&lt;br /&gt;
    )&lt;br /&gt;
&lt;br /&gt;
    if not integerPart then&lt;br /&gt;
        return text&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    integerPart = integerPart:reverse():gsub(&amp;quot;(%d%d%d)&amp;quot;, &amp;quot;%1,&amp;quot;)&lt;br /&gt;
    integerPart = integerPart:reverse():gsub(&amp;quot;^,&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return sign .. integerPart .. (fractionPart or &amp;quot;&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Format a numeric value for footer placeholders.&lt;br /&gt;
---&lt;br /&gt;
--- Supported specs:&lt;br /&gt;
---   * ``      -&amp;gt; raw resolved text&lt;br /&gt;
---   * `.2f`   -&amp;gt; `string.format(&amp;quot;%.2f&amp;quot;, value)`&lt;br /&gt;
---   * `,`     -&amp;gt; English-style grouping&lt;br /&gt;
---   * `,.2f`  -&amp;gt; grouping after decimal formatting&lt;br /&gt;
---&lt;br /&gt;
--- @param formatSpec string?&lt;br /&gt;
--- @param valueText string&lt;br /&gt;
--- @param valueNumber number&lt;br /&gt;
--- @return string&lt;br /&gt;
local function formatNumber(formatSpec, valueText, valueNumber)&lt;br /&gt;
    if not formatSpec or formatSpec == &amp;quot;&amp;quot; then&lt;br /&gt;
        return valueText&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local spec = tostring(formatSpec)&lt;br /&gt;
    local useGrouping = false&lt;br /&gt;
&lt;br /&gt;
    if spec:sub(1, 1) == &amp;quot;,&amp;quot; then&lt;br /&gt;
        useGrouping = true&lt;br /&gt;
        spec = spec:sub(2)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local formatted = valueText&lt;br /&gt;
&lt;br /&gt;
    if spec ~= &amp;quot;&amp;quot; then&lt;br /&gt;
        local formatString = spec&lt;br /&gt;
        if not formatString:match(&amp;quot;^%%&amp;quot;) then&lt;br /&gt;
            formatString = &amp;quot;%&amp;quot; .. formatString&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        local ok, result = pcall(string.format, formatString, valueNumber)&lt;br /&gt;
        if not ok then&lt;br /&gt;
            return valueText&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        formatted = result&lt;br /&gt;
    else&lt;br /&gt;
        formatted = tostring(valueNumber)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if useGrouping then&lt;br /&gt;
        formatted = groupThousands(formatted)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return formatted&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Expand footer placeholders using resolved argument values.&lt;br /&gt;
---&lt;br /&gt;
--- @param footer string?&lt;br /&gt;
--- @param doneText string&lt;br /&gt;
--- @param totalText string&lt;br /&gt;
--- @param done number&lt;br /&gt;
--- @param total number&lt;br /&gt;
--- @return string?&lt;br /&gt;
local function expandFooter(footer, doneText, totalText, done, total)&lt;br /&gt;
    if not footer or footer == &amp;quot;&amp;quot; then&lt;br /&gt;
        return nil&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local values = {&lt;br /&gt;
        [1] = {&lt;br /&gt;
            text = doneText,&lt;br /&gt;
            number = done,&lt;br /&gt;
        },&lt;br /&gt;
        [2] = {&lt;br /&gt;
            text = totalText,&lt;br /&gt;
            number = total,&lt;br /&gt;
        },&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    local text = tostring(footer)&lt;br /&gt;
&lt;br /&gt;
    text = text:gsub(&amp;quot;%$([12]):([^%s$]+)&amp;quot;, function(index, spec)&lt;br /&gt;
        local value = values[tonumber(index)]&lt;br /&gt;
        return formatNumber(spec, value.text, value.number)&lt;br /&gt;
    end)&lt;br /&gt;
&lt;br /&gt;
    text = text:gsub(&amp;quot;%$([12])&amp;quot;, function(index)&lt;br /&gt;
        return values[tonumber(index)].text&lt;br /&gt;
    end)&lt;br /&gt;
&lt;br /&gt;
    return text&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Render the progression table from a normalized argument table.&lt;br /&gt;
---&lt;br /&gt;
--- @param args Progression2Args&lt;br /&gt;
--- @param frame? table&lt;br /&gt;
--- @return string&lt;br /&gt;
function p._main(args, frame)&lt;br /&gt;
    local done, total, doneText, totalText = getResolvedValues(args, frame)&lt;br /&gt;
&lt;br /&gt;
    local percent = 0&lt;br /&gt;
    if done &amp;gt; 0 and total &amp;gt; 0 then&lt;br /&gt;
        percent = (done / total) * 100&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local barPercent = math.max(0, math.min(percent, 100))&lt;br /&gt;
&lt;br /&gt;
    local root = mw.html.create(&amp;quot;&amp;quot;)&lt;br /&gt;
    local tbl = root:tag(&amp;quot;table&amp;quot;)&lt;br /&gt;
        :attr(&amp;quot;role&amp;quot;, &amp;quot;presentation&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;progression&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if args.width then&lt;br /&gt;
        tbl:css(&amp;quot;width&amp;quot;, args.width)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local headerCell = tbl:tag(&amp;quot;tr&amp;quot;)&lt;br /&gt;
        :tag(&amp;quot;td&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;progression-header&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if args.task then&lt;br /&gt;
        headerCell:wikitext(args.task .. &amp;quot;：&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not yesno(args.hidecomplete) then&lt;br /&gt;
        headerCell:wikitext(&amp;quot;完成&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    headerCell:tag(&amp;quot;span&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;progression-progression&amp;quot;)&lt;br /&gt;
        :wikitext(formatProgressPercent(percent) .. &amp;quot;%&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    local barRow = tbl:tag(&amp;quot;tr&amp;quot;)&lt;br /&gt;
        :tag(&amp;quot;td&amp;quot;)&lt;br /&gt;
        :tag(&amp;quot;table&amp;quot;)&lt;br /&gt;
        :attr(&amp;quot;role&amp;quot;, &amp;quot;presentation&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;progression-bar&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;skin-invert&amp;quot;)&lt;br /&gt;
        :tag(&amp;quot;tr&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if done ~= 0 then&lt;br /&gt;
        local doneWidth = barPercent&lt;br /&gt;
&lt;br /&gt;
        if doneWidth &amp;gt; 0 and doneWidth &amp;lt; 1 then&lt;br /&gt;
            doneWidth = 1&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        barRow:tag(&amp;quot;td&amp;quot;)&lt;br /&gt;
            :addClass(&amp;quot;progression-done&amp;quot;)&lt;br /&gt;
            :css(&amp;quot;width&amp;quot;, tostring(doneWidth) .. &amp;quot;%&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if barPercent &amp;lt; 100 then&lt;br /&gt;
        barRow:tag(&amp;quot;td&amp;quot;)&lt;br /&gt;
            :addClass(&amp;quot;progression-undone&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local footer = expandFooter(&lt;br /&gt;
        args.footer,&lt;br /&gt;
        doneText,&lt;br /&gt;
        totalText,&lt;br /&gt;
        done,&lt;br /&gt;
        total&lt;br /&gt;
    )&lt;br /&gt;
    if footer then&lt;br /&gt;
        tbl:tag(&amp;quot;tr&amp;quot;)&lt;br /&gt;
            :tag(&amp;quot;td&amp;quot;)&lt;br /&gt;
            :addClass(&amp;quot;progression-footer&amp;quot;)&lt;br /&gt;
            :wikitext(footer)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Entry point for `#invoke`.&lt;br /&gt;
---&lt;br /&gt;
--- @param frame table&lt;br /&gt;
--- @return string&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
    local args = getArgs(frame, {&lt;br /&gt;
        wrappers = {&amp;quot;Template:Progression2&amp;quot;},&lt;br /&gt;
    })&lt;br /&gt;
&lt;br /&gt;
    return renderStyles(frame) .. p._main(args, frame)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;For Each ... Next</name></author>
	</entry>
</feed>