×
Create a new article
Write your page title here:
We currently have 2,521 articles on Polcompball Wiki. Type your article name above or click on one of the titles below and start writing!



Polcompball Wiki

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

-- <nowiki>
--------------------------------------------------------------------------------
--                              Module:Hatnote                                --
--                                                                            --
-- This module produces hatnote links and links to related articles. It       --
-- implements the {{hatnote}} and {{format link}} meta-templates and includes --
-- helper functions for other Lua hatnote modules.                            --
--------------------------------------------------------------------------------

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local mArguments = require('Module:Arguments')
local yesno = require('Module:Yesno')
local mTableTools = require('Module:TableTools')
local i18n = require('Module:I18n').loadMessages('Hatnote')
local hatnote = {}

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function getArgs(frame)
    -- Fetches the arguments from the parent frame. Whitespace is trimmed and
    -- blanks are removed.
    return mArguments.getArgs(frame, {parentOnly = true})
end

local function removeInitialColon(s)
    -- Removes the initial colon from a string, if present.
    return s:match('^:?(.*)')
end

function hatnote.findNamespaceId(link, removeColon)
    -- Finds the namespace id (namespace number) of a link or a pagename. This
    -- function will not work if the link is enclosed in double brackets. Colons
    -- are trimmed from the start of the link by default. To skip colon
    -- trimming, set the removeColon parameter to false.
    checkType('findNamespaceId', 1, link, 'string')
    checkType('findNamespaceId', 2, removeColon, 'boolean', true)
    if removeColon ~= false then
        link = removeInitialColon(link)
    end
    local namespace = link:match('^(.-):')
    if namespace then
        local nsTable = mw.site.namespaces[namespace]
        if nsTable then
            return nsTable.id
        end
    end
    return 0
end

function hatnote.formatPages(...)
    -- Formats a list of pages using formatLink and returns it as an array. Nil
    -- values are not allowed.
    local pages = {...}
    local ret = {}
    for i, page in ipairs(pages) do
        ret[i] = hatnote._formatLink(page)
    end
    return ret
end

function hatnote.formatPageTables(...)
    -- Takes a list of page/display tables and returns it as a list of
    -- formatted links. Nil values are not allowed.
    local pages = {...}
    local links = {}
    for i, t in ipairs(pages) do
        checkType('formatPageTables', i, t, 'table')
        local link = t[1]
        local display = t[2]
        links[i] = hatnote._formatLink(link, display)
    end
    return links
end

function hatnote.makeWikitextError(msg, helpLink, addTrackingCategory, title)
    -- Formats an error message to be returned to wikitext. If
    -- addTrackingCategory is not false after being returned from
    -- [[Module:Yesno]], and if we are not on a talk page, a tracking category
    -- is added.
    checkType('makeWikitextError', 1, msg, 'string')
    checkType('makeWikitextError', 2, helpLink, 'string', true)
    title = title or mw.title.getCurrentTitle()
    -- Make the help link text.
    local helpText
    if helpLink then
        helpText = ' ([[' .. helpLink .. '|' .. i18n:msg('help') .. ']])'
    else
        helpText = ''
    end
    -- Make the category text.
    local category
    if not title.isTalkPage and yesno(addTrackingCategory) ~= false then
        category = i18n:msg('cat-errors')
        category = string.format(
            '[[%s:%s]]',
            mw.site.namespaces[14].name,
            category
        )
    else
        category = ''
    end
    return string.format(
        i18n:msg('error'),
        msg,
        helpText,
        category
    )
end

function hatnote.disambiguate(page, disambiguator)
    -- Formats a page title with a disambiguation parenthetical,
    -- i.e. "Example" → "Example (disambiguation)".
    checkType('disambiguate', 1, page, 'string')
    checkType('disambiguate', 2, disambiguator, 'string', true)
    disambiguator = disambiguator or i18n:msg('disambiguation')
    return string.format(i18n:msg('brackets'), page, disambiguator)
end

--------------------------------------------------------------------------------
-- Format link
--
-- Makes a wikilink from the given link and display values. Links are escaped
-- with colons if necessary, and links to sections are detected and displayed
-- with " § " as a separator rather than the standard MediaWiki "#". Used in
-- the {{format link}} template.
--------------------------------------------------------------------------------

function hatnote.formatLink(frame)
    local args = getArgs(frame)
    local link = args[1]
    local display = args[2]
    if not link then
        return hatnote.makeWikitextError(
            i18n:msg('error-link'),
            'w:c:Module:Template:Format link#Errors',-- there is no actual docs for this. not even on wikipedia
            args.category
        )
    end
    return hatnote._formatLink(link, display)
end

function hatnote._formatLink(link, display)
    checkType('_formatLink', 1, link, 'string')
    checkType('_formatLink', 2, display, 'string', true)

    -- Remove the initial colon for links where it was specified manually.
    link = removeInitialColon(link)

    -- Find whether a faux display value has been added with the {{!}} magic
    -- word.
    if not display then
        local prePipe, postPipe = link:match('^(.-)|(.*)$')
        link = prePipe or link
        display = postPipe
    end

    -- Find the display value.
    if not display then
        local page, section = link:match('^(.-)#(.*)$')
        if page then
            display = page .. ' §&nbsp;' .. section
        end
    end

    -- Assemble the link.
    if display then
        return string.format(
            '[[:%s|%s]]',
            string.gsub(link, '|(.*)$', ''), --display overwrites manual piping
            display
        )
    else
        return string.format('[[:%s]]', link)
    end
end

--------------------------------------------------------------------------------
-- Hatnote
--
-- Produces standard hatnote text. Implements the {{hatnote}} template.
--------------------------------------------------------------------------------

function hatnote.hatnote(frame)
    local args = getArgs(frame)
    local s = args[1]
    local options = {}
    if not s then
        return hatnote.makeWikitextError(
            i18n:msg('error-text'),
            'w:c:Module:Template:Hatnote#Errors',
            args.category
        )
    end
    options.extraclasses = args.extraclasses
    options.selfref = args.selfref
    return hatnote._hatnote(s, options)
end

function hatnote._hatnote(s, options)
    checkType('_hatnote', 1, s, 'string')
    checkType('_hatnote', 2, options, 'table', true)
    options = options or {}
    local classes = {'notice', 'hatnote'}
    local extraclasses = options.extraclasses
    local selfref = options.selfref
    if type(extraclasses) == 'string' then
        classes[#classes + 1] = extraclasses
    end
    if selfref then
        classes[#classes + 1] = 'selfref'
    end
    return string.format(
        '<div role="note" class="%s">%s</div>',
        table.concat(classes, ' '),
        s
    )
end

--------------------------------------------------------------------------------
--                           Module:Hatnote list                              --
--                                                                            --
-- This module produces and formats lists for use in hatnotes. In particular, --
-- it implements the for-see list, i.e. lists of "For X, see Y" statements,   --
-- as used in {{about}}, and its variants. Also introduced are andList &      --
-- orList helpers for formatting lists with those conjunctions.               --
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- List stringification helper functions
--
-- These functions are used for stringifying lists, usually page lists inside
-- the "Y" portion of "For X, see Y" for-see items.
--------------------------------------------------------------------------------

--default options table used across the list stringification functions
local stringifyListDefaultOptions = {
    conjunction = i18n:msg('conjunction'),
    separator = i18n:msg('separator'),
    altSeparator = i18n:msg('altSeparator'),
    space = i18n:msg('space'),
    formatted = false
}

-- Stringifies a list generically; probably shouldn't be used directly
function stringifyList(list, options)
    -- Type-checks, defaults, and a shortcut
    checkType("stringifyList", 1, list, "table")
    if #list == 0 then return nil end
    checkType("stringifyList", 2, options, "table", true)
    options = options or {}
    for k, v in pairs(stringifyListDefaultOptions) do
        if options[k] == nil then options[k] = v end
    end
    local s = options.space
    -- Format the list if requested
    if options.formatted then list = hatnote.formatPages(unpack(list)) end
    -- Set the separator; if any item contains it, use the alternate separator
    local separator = options.separator
    --searches display text only
    function searchDisp(t, f)
        return string.find(string.sub(t, (string.find(t, '|') or 0) + 1), f)
    end
    for k, v in pairs(list) do
        if searchDisp(v, separator) then
            separator = options.altSeparator
            break
        end
    end
    -- Set the conjunction, apply Oxford comma, and force a comma if #1 has "§"
    local oxfordLangs = {
        -- list of languages that does respect oxford commas
        ['en'] = true,
        ['en-us'] = true,
        ['en-gb'] = true,
    }

    local conjunction = s .. options.conjunction .. s
    if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
        conjunction = (oxfordLangs[i18n.defaultLang] and separator or '') .. conjunction
    end
    -- Return the formatted string
    return mw.text.listToText(list, separator .. s, conjunction)
end

--DRY function
function conjList (conj, list, fmt)
    return stringifyList(list, {conjunction = conj, formatted = fmt})
end

-- Stringifies lists with "and" or "or"
function hatnote.andList (...) return conjList(i18n:msg('conj-and'), ...) end
function hatnote.orList (...) return conjList(i18n:msg('conj-or'), ...) end

--------------------------------------------------------------------------------
-- For see
--
-- Makes a "For X, see [[Y]]." list from raw parameters. Intended for the
-- {{about}} templates and their variants.
--------------------------------------------------------------------------------

--default options table used across the forSee family of functions
local forSeeDefaultOptions = {
    andKeyword = i18n:msg('conj-and'),
    title = mw.title.getCurrentTitle().text,
    otherText = i18n:msg('other-uses'),
    forSeeForm = i18n:msg('for')
}

--Collapses duplicate punctuation
function punctuationCollapse (text)
    local replacements = {
        ["%.%.$"] = ".",
        ["%?%.$"] = "?",
        ["%!%.$"] = "!",
        ["%.%]%]%.$"] = ".]]",
        ["%?%]%]%.$"] = "?]]",
        ["%!%]%]%.$"] = "!]]"
    }
    for k, v in pairs(replacements) do text = string.gsub(text, k, v) end
    return text
end

-- Structures arguments into a table for stringification, & options
function hatnote.forSeeArgsToTable (args, from, options)
    -- Type-checks and defaults
    checkType("forSeeArgsToTable", 1, args, 'table')
    checkType("forSeeArgsToTable", 2, from, 'number', true)
    from = from or 1
    checkType("forSeeArgsToTable", 3, options, 'table', true)
    options = options or {}
    for k, v in pairs(forSeeDefaultOptions) do
        if options[k] == nil then options[k] = v end
    end
    -- maxArg's gotten manually because getArgs() and table.maxn aren't friends
    local maxArg = 0
    for k, v in pairs(args) do
        if type(k) == 'number' and k > maxArg then maxArg = k end
    end
    -- Structure the data out from the parameter list:
    -- * forTable is the wrapper table, with forRow rows
    -- * Rows are tables of a "use" string & a "pages" table of pagename strings
    -- * Blanks are left empty for defaulting elsewhere, but can terminate list
    local forTable = {}
    local i = from
    local terminated = false
    -- Loop to generate rows
    repeat
        -- New empty row
        local forRow = {}
        -- On blank use, assume list's ended & break at end of this loop
        forRow.use = args[i]
        if not args[i] then terminated = true end
        -- New empty list of pages
        forRow.pages = {}
        -- Insert first pages item if present
        table.insert(forRow.pages, args[i + 1])
        -- If the param after next is "and", do inner loop to collect params
        -- until the "and"'s stop. Blanks are ignored: "1|and||and|3" → {1, 3}
        while args[i + 2] == options.andKeyword do
            if args[i + 3] then
                table.insert(forRow.pages, args[i + 3])
            end
            -- Increment to next "and"
            i = i + 2
        end
        -- Increment to next use
        i = i + 2
        -- Append the row
        table.insert(forTable, forRow)
    until terminated or i > maxArg

    return forTable
end

-- Stringifies a table as formatted by forSeeArgsToTable
function hatnote.forSeeTableToString (forSeeTable, options)
    -- Type-checks and defaults
    checkType("forSeeTableToString", 1, forSeeTable, "table")
    checkType("forSeeTableToString", 2, options, "table", true)
    options = options or {}
    for k, v in pairs(forSeeDefaultOptions) do
        if options[k] == nil then options[k] = v end
    end
    -- Stringify each for-see item into a list
    local strList = {}
    for k, v in pairs(forSeeTable) do
        local useStr = v.use or options.otherText
        local pagesStr = hatnote.andList(v.pages, true) or
            hatnote._formatLink(hatnote.disambiguate(options.title))
        local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
        forSeeStr = punctuationCollapse(forSeeStr)
        table.insert(strList, forSeeStr)
    end
    -- Return the concatenated list
    return table.concat(strList, ' ')
end

-- Produces a "For X, see [[Y]]" string from arguments. Expects index gaps
-- but not blank/whitespace values. Ignores named args and args < "from".
function hatnote._forSee (args, from, options)
    local forSeeTable = hatnote.forSeeArgsToTable(args, from, options)
    return hatnote.forSeeTableToString(forSeeTable, options)
end

-- As _forSee, but uses the frame.
function hatnote.forSee (frame, from, options)
    return hatnote._forSee(mArguments.getArgs(frame), from, options)
end

--------------------------------------------------------------------------------
-- Produces a labelled pages-list hatnote.
-- The main frame (template definition) takes 1 or 2 arguments, for a singular
-- and (optionally) plural label respectively:
-- * {{#invoke:Hatnote|labelledList|Singular label|Plural label}}
-- The resulting template takes pagename & label parameters normally.
--------------------------------------------------------------------------------
-- Defaults global to this module
local LPLHdefaults = {
    label = i18n:msg('see-also'), --Final fallback for label argument
    labelForm = i18n:msg('colon'),
    prefixes = {'label', 'label ', 'l'},
    template = 'Module:Hatnote'
}

-- Helper function that pre-combines display parameters into page arguments.
-- Also compresses sparse arrays, as a desirable side-effect.
function hatnote.preprocessDisplays (args, prefixes)
    -- Prefixes specify which parameters, in order, to check for display options
    -- They each have numbers auto-appended, e.g. 'label1', 'label 1', & 'l1'
    prefixes = prefixes or LPLHdefaults.prefixes
    local pages = {}
    for k, v in pairs(args) do
        if type(k) == 'number' then
            local display
            for i = 1, #prefixes do
                display = args[prefixes[i] .. k]
                if display then break end
            end
            local page = display and
                string.format('%s|%s', string.gsub(v, '|.*$', ''), display) or v
            pages[#pages + 1] = page
        end
    end
    return pages
end

function hatnote.labelledList (frame)
    local labels = {frame.args[1] or LPLHdefaults.label}
    labels[2] = frame.args[2] or labels[1]
    local template = frame:getParent():getTitle()
    local args = mArguments.getArgs(frame, {parentOnly = true})
    local pages = hatnote.preprocessDisplays(args)
    local options = {
        extraclasses = frame.args.extraclasses,
        category = args.category,
        selfref = frame.args.selfref or args.selfref,
        template = template
    }
    return hatnote._labelledList(pages, labels, options)
end

function hatnote._labelledList (pages, labels, options)
    labels = labels or {}
    if #pages == 0 then
        return hatnote.makeWikitextError(
            i18n:msg('error-pagename', 2),
            (options.template or LPLHdefaults.template) .. '#Errors',
            options.category
        )
    end
    label = (#pages == 1 and labels[1] or labels[2]) or LPLHdefaults.label
    local text = string.format(
        options.labelForm or LPLHdefaults.labelForm,
        label,
        hatnote.andList(pages, true)
    )
    local hnOptions = {
        extraclasses = options.extraclasses,
        selfref = options.selfref
    }
    return hatnote._hatnote(text, hnOptions)
end


--------------------------------------------------------------------------------
-- About
--
-- These functions implement the {{about}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.about (frame)
    -- A passthrough that gets args from the frame and all
    args = mArguments.getArgs(frame)
    return hatnote._about(args)
end


function hatnote._about (args, options)
    -- Produces "about" hatnote.

    -- Type checks and defaults
    checkType('_about', 1, args, 'table', true)
    args = args or {}
    checkType('_about', 2, options, 'table', true)
    options = options or {}
    local defaultOptions = {
        aboutForm = i18n:msg('about', mw.title.getCurrentTitle().namespace),
        defaultPageType = i18n:msg('page'),
        namespace = mw.title.getCurrentTitle().namespace,
        otherText = nil, --included for complete list
        pageTypesByNamespace = {
            [0] =  i18n:msg('pagetype-0'),
            [14] = i18n:msg('pagetype-14')
        },
        sectionString = i18n:msg('section')
    }
    for k, v in pairs(defaultOptions) do
        if options[k] == nil then options[k] = v end
    end

    -- Set initial "about" string
    local pageType = (args.section and options.sectionString) or
        options.pageTypesByNamespace[options.namespace] or
        options.defaultPageType
    local about = ''
    if args[1] then
        about = string.format(options.aboutForm, pageType, args[1])
    end

    --Allow passing through certain options
    local fsOptions = {
        otherText = options.otherText
    }

    -- Set for-see list
    local forSee = " " .. hatnote._forSee(args, 2, fsOptions)

    -- Concatenate and return
    return hatnote._hatnote(about .. forSee, {extraclasses = 'context-link about dablink'})
end

--------------------------------------------------------------------------------
-- Details
--
-- These functions implement the {{details}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.details (frame)
    local args = mArguments.getArgs(frame, {parentOnly = true})
    local topic, category = args.topic, args.category
    local options = {
        selfref = args.selfref,
        extraclasses = 'context-link details dablink'
    }
    args = mTableTools.compressSparseArray(args)
    if #args == 0 then
        return hatnote.makeWikitextError(
            i18n:msg('error-pagename'),
            'w:c:Module:Template:Details#Errors',-- another undocumented thing
            category
        )
    end
    return hatnote._details(args, topic, options)
end

function hatnote._details (list, topic, options)
    list = hatnote.andList(list, true)
    topic = topic or i18n:msg('topic')
    local text = string.format(i18n:msg('details'), topic, list)
    return hatnote._hatnote(text, options)
end

--------------------------------------------------------------------------------
-- For
--
-- These functions implement the {{for}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.For (frame)
    return hatnote._For(mArguments.getArgs(frame))
end

--Implements {{For}} but takes a manual arguments table
function hatnote._For (args)
    local use = args[1]
    local category = ''
    if (not use or use == i18n:msg('other-uses')) and
        (not args.category or yesno(args.category)) then
        category = '[[Category:' .. i18n:msg('cat-unusual-parameters') .. ']]'
    end
    local pages = {}
    function two (a, b) return a, b, 1 end --lets us run ipairs from 2
    for k, v in two(ipairs(args)) do table.insert(pages, v) end
    return hatnote._hatnote(
        hatnote.forSeeTableToString({{use = use, pages = pages}}),
        {selfref = args.selfref}
    ) .. category
end

--------------------------------------------------------------------------------
-- Further
--
-- These functions implement the {{further}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.further(frame)
    local args = mArguments.getArgs(frame, {parentOnly = true})
    local pages = mTableTools.compressSparseArray(args)
    if #pages < 1 then
        return hatnote.makeWikitextError(
            i18n:msg('error-pagename', 2),
            'w:c:Module:Template:Further#Errors',-- undocumented thing #3
            args.category
        )
    end
    local options = {
        selfref = args.selfref,
        extraclasses = 'context-link further dablink'
    }
    return hatnote._further(pages, options)
end

function hatnote._further(pages, options)
    local text = i18n:msg('further2') .. hatnote.andList(pages, true)
    return hatnote._hatnote(text, options)
end

--------------------------------------------------------------------------------
-- Main
--
-- These functions implement the {{main}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.main(frame)
    local args = mArguments.getArgs(frame, {parentOnly = true})
    local pages = {}
    for k, v in pairs(args) do
        if type(k) == 'number' then
            local display = args['label ' .. k] or args['l' .. k]
            local page = display and
                string.format('%s|%s', string.gsub(v, '|.*$', ''), display) or v
            pages[#pages + 1] = page
        end
    end
    if #pages == 0 and mw.title.getCurrentTitle().namespace == 0 then
        return hatnote.makeWikitextError(
            i18n:msg('error-pagename', 2),
            'w:c:Module:Template:Main#Errors',-- undocumented thing #4
            args.category
        )
    end
    local options = {
        selfref = args.selfref
    }
    return hatnote._main(pages, options)
end

function hatnote._main(args, options)
    -- Get the list of pages. If no first page was specified we use the current
    -- page name.
    local currentTitle = mw.title.getCurrentTitle()
    if #args == 0 then args = {currentTitle.text} end
    local firstPage = string.gsub(args[1], '|.*$', '')
    -- Make the formatted link text
    list = hatnote.andList(args, true)
    -- Build the text.
    local isPlural = #args > 1
    -- Find the pagetype.
    local pageType = hatnote.findNamespaceId(firstPage) == 0 and i18n:msg('article', isPlural and 2 or 1) or i18n:msg('page', isPlural and 2 or 1)
    local mainForm
    local curNs = currentTitle.namespace
    if (curNs == 14) or (curNs == 15) then --category/talk namespaces
        mainForm = isPlural and i18n:msg('main-category', 2)
         or
         i18n:msg('main-category', 1)
    else
        mainForm = isPlural and i18n:msg('main', 2) or i18n:msg('main', 1)
    end
    local text = string.format(mainForm, pageType, list)
    options = options or {}
    local hnOptions = {
        selfref = options.selfref,
        extraclasses = 'context-link main dablink'
    }
    return hatnote._hatnote(text, hnOptions)
end

--------------------------------------------------------------------------------
-- See also
--
-- These functions implement the {{see also}} hatnote template.
--------------------------------------------------------------------------------
function hatnote.seeAlso(frame)
    local args = mArguments.getArgs(frame, {parentOnly = true})
    local pages = {}
    for k, v in pairs(args) do
        if type(k) == 'number' then
            local display = args['label ' .. k] or args['l' .. k]
            local page = display and
                string.format('%s|%s', string.gsub(v, '|.*$', ''), display) or v
            pages[#pages + 1] = page
        end
    end
    if not pages[1] then
        return hatnote.makeWikitextError(
            i18n:msg('error-pagename', 2),
            'w:c:Module:Template:See also#Errors',-- undocumented thing #5
            args.category
        )
    end
    local options = {
        selfref = args.selfref
    }
    return hatnote._seeAlso(pages, options)
end

function hatnote._seeAlso(args, options)
    checkType('_seeAlso', 1, args, 'table')
    checkType('_seeAlso', 2, options, 'table', true)
    options = options or {}
    local list = hatnote.andList(args, true)
    local text = string.format(i18n:msg('see-also2'), list)
    -- Pass options through.
    local hnOptions = {
        selfref = options.selfref,
        extraclasses = 'context-link seealso dablink'
    }
    return hatnote._hatnote(text, hnOptions)
end

hatnote['for'] = hatnote.For

return hatnote