Jump to content

Requests for technical support from the VASP team should be posted in the VASP Forum.

Module:KnownIssue

From VASP Wiki
Revision as of 13:15, 26 June 2026 by Liebetreu (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:KnownIssue/doc

-- Module:KnownIssue
-- Two independent jobs:
--
--   p.rowhead   — renders the non-text cells of one Known Issues row
--                 (ID, Version fixed, Version first noticed, Date). The
--                 description text is NOT handled here: the template appends it
--                 as raw wikitext so {{TAG|...}}, {{FILE|...}} etc. expand once,
--                 in place, and render correctly. (Passing rich text through an
--                 #invoke argument and re-emitting it corrupts multi-argument
--                 templates, which is why text must stay out of Lua.)
--
--   p.checkdupes — scans the current page's own source via getContent() and
--                  warns if any integer ID is used by more than one
--                  {{KnownIssue}} (i.e. would emit the same #KnownIssueN anchor
--                  twice). This replaces render-time dedup, which is impossible
--                  here because Scribunto does not keep state across #invoke.
--
-- Styling:
--   * "Version fixed":  open -> --vred-bg ("Open"); resolved -> --vcyan-bg (the
--     version); obsolete -> --vgrey-bg ("Obsolete").
--   * "Version first noticed": --vred-bg (that version still has the issue),
--     or --vgrey-bg for obsolete entries.
--   * row anchors are <div id="KnownIssueXYZ" ...> with scroll-margin-top so
--     following the link leaves room above the row (see ANCHOR_SCROLL_MARGIN).

local p = {}

local RED  = "background-color:var(--vred-bg); color:var(--vdefault-text-nb)"
local CYAN = "background-color:var(--vcyan-bg); color:var(--vdefault-text-nb)"
local GREY = "background-color:var(--vgrey-bg); color:var(--vdefault-text-nb)"

-- Space left above a row when an #KnownIssueN link is followed, so the row does
-- not land flush against the top (or under the site header) and clip the
-- description's first line. Tune to taste.
local ANCHOR_SCROLL_MARGIN = "5em"

-- Page the ID links point at, e.g. [[Known_issues#KnownIssue33]]. Change this if
-- the Known Issues table lives on a differently-named page.
local ISSUE_PAGE = "Known_issues"

local function trim(s)
    if type(s) ~= "string" then return "" end
    return mw.ustring.match(s, "^%s*(.-)%s*$") or ""
end

local function isTruthy(val)
    val = mw.ustring.lower(trim(val))
    return val == "yes" or val == "1" or val == "true"
end

local function isFalsy(val)
    val = mw.ustring.lower(trim(val))
    return val == "no" or val == "0" or val == "false"
end

-- ── p.rowhead ───────────────────────────────────────────────────────────────
-- Called by {{KnownIssue}} with plain scalar args (no rich text). Returns the
-- "|-" row start plus the ID / Version fixed / Version first noticed / Date
-- cells. The template then appends "| {{{text}}}" as the final cell.
function p.rowhead(frame)
    local args     = frame.args
    local id       = trim(args.ID or args.id or "")
    local resolved = trim(args.resolved_in_version or "")
    local reported = trim(args.reported_in_version or "")
    local date     = trim(args.date_added or "")
    local obsolete = isTruthy(args.obsolete or "")
    local anchor_on = not isFalsy(args.anchor or "")
    local hidden   = isTruthy(args.hide or "")

    -- Version fixed cell. Obsolete > resolved > open.
    local fixed_style, fixed_text
    if obsolete then
        fixed_style, fixed_text = GREY, "Obsolete"
    elseif resolved ~= "" and resolved ~= "-" then
        fixed_style, fixed_text = CYAN, resolved
    else
        fixed_style, fixed_text = RED, "Open"
    end

    -- Version first noticed cell: red (still affected), grey if obsolete.
    local reported_style = obsolete and GREY or RED

    -- ID cell.
    local function anchorDiv(target)
        if not anchor_on then return "" end
        return '<div id="' .. target
            .. '" style="display:inline-block; scroll-margin-top:'
            .. ANCHOR_SCROLL_MARGIN .. ';"></div>'
    end
    local n = tonumber(id)
    local id_cell
    if id == "" then
        id_cell = '|style="' .. RED .. '; text-align:center; font-style:italic;" | None'
    elseif not n or n ~= math.floor(n) or n < 0 then
        id_cell = '|style="' .. RED .. '; text-align:center; font-weight:bold;" | '
            .. anchorDiv('KnownIssue' .. mw.uri.encode(id, "PATH"))
            .. '<span title="ID must be a positive integer">&#x26A0; '
            .. mw.text.nowiki(id) .. '</span>'
    else
        -- The number links to its own anchor, e.g. [[Known_issues#KnownIssue33|33]].
        id_cell = '|style="text-align:center;" | '
            .. anchorDiv('KnownIssue' .. n)
            .. '[[' .. ISSUE_PAGE .. '#KnownIssue' .. n .. '|' .. tostring(n) .. ']]'
    end

    -- hide -> render the row but collapse it with display:none, so the template
    -- never has to wrap the description in a parser function (which would
    -- reprocess and mangle rich markup like multi-argument {{TAG|...}}).
    -- The final '|' opens the Description cell; the template appends raw text.
    -- All cells are centred except the Description (the final raw cell).
    return table.concat({
        hidden and '|- style="display:none;"' or '|-',
        id_cell,
        '|style="' .. fixed_style .. '; text-align:center;" |' .. fixed_text,
        '|style="' .. reported_style .. '; text-align:center;" |' .. reported,
        '|style="text-align:center;" |' .. date,
        '|',
    }, '\n')
end

-- ── p.checkdupes ─────────────────────────────────────────────────────────────
-- Scans the current page source for EVERY {{KnownIssue}} call (regardless of
-- hide / anchor) and reports, as separate warnings: (1) any integer ID used more
-- than once, and (2) rows with no ID. Returns "" when both are clean.
-- Render via {{NB|<style>|<message>}}. NB's signature is
-- {{NB|<style>|<message>|<<indent>>|<<firstword>>}}; the "warning" style already
-- supplies its own "Warning:" first word, so only style + message are passed.
-- (Passing a 3rd arg here sets <indent>, which mangles the box.) expandTemplate
-- passes each arg discretely, so '|'/'=' in msg can't break NB's parsing.
local function banner(frame, msg)
    return frame:expandTemplate{ title = 'NB', args = { 'warning', msg, '', 'WARNING:' } }
end

function p.checkdupes(frame)
    local content = mw.title.getCurrentTitle():getContent()
    if not content then return "" end

    local counts, order, missing = {}, {}, 0
    -- Require a "|" right after the name so this matches {{KnownIssue|...}} only,
    -- never {{KnownIssueDupCheck}}. The capture runs to the first "}}"; the ID is
    -- an early parameter, so it survives even when text contains nested templates
    -- or spans multiple lines.
    for block in mw.ustring.gmatch(content, "{{%s*[Kk]nown[Ii]ssue%s*(|.-)}}") do
        local lb    = mw.ustring.lower(block)
        local idstr = mw.ustring.match(lb, "[|\n]%s*id%s*=%s*([^|}\n]*)")
        local id    = idstr and trim(idstr) or ""
        if id == "" then
            missing = missing + 1
        else
            local n = tonumber(id)
            if n and n == math.floor(n) and n >= 0 then
                if not counts[n] then
                    counts[n] = 0
                    order[#order + 1] = n
                end
                counts[n] = counts[n] + 1
            end
        end
    end

    local dups = {}
    for _, n in ipairs(order) do
        if counts[n] > 1 then
            dups[#dups + 1] = n .. ' (&times;' .. counts[n] .. ')'
        end
    end

    -- Lowest positive integer not yet used, to suggest for the next ID.
    local nextFree = 1
    while counts[nextFree] do nextFree = nextFree + 1 end
    local suggest = ' Next free ID integer: ' .. nextFree .. '.'

    local out = {}
    if #dups > 0 then
        out[#out + 1] = banner(frame, 'Duplicate Known Issue IDs on this page: '
            .. table.concat(dups, ', ') .. '. Each issue must have a unique ID.' .. suggest)
    end
    if missing > 0 then
        out[#out + 1] = banner(frame, missing .. ' Known Issue'
            .. (missing == 1 and ' has' or 's have') .. ' no ID assigned on this page.' .. suggest)
    end
    return table.concat(out, '\n')
end

return p