×
Create a new article
Write your page title here:
We currently have 152 articles on Grow Song of The Evertree Wiki. Type your article name above or create one of the articles listed here!



    Grow Song of The Evertree Wiki

    Module:Hatnote: Difference between revisions

    Content added Content deleted
    m (1 revision imported)
    No edit summary
     
    Line 1: Line 1:
    -- <nowiki>
    -- This Module is used for making templates based in the Lua language.
    --------------------------------------------------------------------------------
    -- See more details about Lua in [[w:Help:Lua]].
    -- Module:Hatnote --
    -- The Fandom Developer's Wiki hosts Global Lua Modules that can be imported and locally overridden.
    -- --
    -- The next line imports the Hatnote module from the [[w:c:dev:Global Lua Modules]].
    -- This module produces hatnote links and links to related articles. It --
    local H = require('Dev:Hatnote')
    -- implements the {{hatnote}} and {{format link}} meta-templates and includes --
    -- See more details about this module at [[w:c:dev:Global_Lua_Modules/Hatnote]]
    -- helper functions for other Lua hatnote modules. --
    --------------------------------------------------------------------------------
    -- The last line produces the output for the template

    return H
    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'),
    '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'),
    '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, i18n:msg('space'))
    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 = i18n:msg('space') .. 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'),
    '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),
    '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),
    '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),
    '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

    Latest revision as of 16:39, 21 November 2022

    This module is invoked by the {{Hatnote}} template, which is used by a number of Notice templates.


    -- <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'),
                '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'),
                '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, i18n:msg('space'))
    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 = i18n:msg('space') .. 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'),
                '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),
                '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),
                '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),
                '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
    
    Cookies help us deliver our services. By using our services, you agree to our use of cookies.
    Cookies help us deliver our services. By using our services, you agree to our use of cookies.