Module:Hatnote list: Difference between revisions

From IxWiki
Jump to navigation Jump to search
wp>Mr. Stradivarius
switch back to Module:Hatnote instead of Module:Hatnote/sandbox
 
m 1 revision imported
 
(5 intermediate revisions by 4 users not shown)
Line 9: Line 9:


local mArguments --initialize lazily
local mArguments --initialize lazily
local mFormatLink = require('Module:Format link')
local mHatnote = require('Module:Hatnote')
local mHatnote = require('Module:Hatnote')
local libraryUtil = require('libraryUtil')
local libraryUtil = require('libraryUtil')
Line 23: Line 24:
--default options table used across the list stringification functions
--default options table used across the list stringification functions
local stringifyListDefaultOptions = {
local stringifyListDefaultOptions = {
conjunction = "and",
    conjunction = "and",
separator = ",",
    separator = ",",
altSeparator = ";",
    altSeparator = ";",
space = " ",
    space = " ",
formatted = false
    formatted = false
}
}
--Searches display text only
local function searchDisp(haystack, needle)
    return mw.ustring.find(
            mw.ustring.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
    )
end


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


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


-- Stringifies lists with "and" or "or"
-- Stringifies lists with "and" or "or"
function p.andList (...) return conjList("and", ...) end
function p.andList (...) return p.conjList("and", ...) end
function p.orList (...) return conjList("or", ...) end
function p.orList (...) return p.conjList("or", ...) end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 82: Line 90:
--default options table used across the forSee family of functions
--default options table used across the forSee family of functions
local forSeeDefaultOptions = {
local forSeeDefaultOptions = {
andKeyword = 'and',
    andKeyword = 'and',
title = mw.title.getCurrentTitle().text,
    title = mw.title.getCurrentTitle().text,
otherText = 'other uses',
    otherText = 'other uses',
forSeeForm = 'For %s, see %s.',
    forSeeForm = 'For %s, see %s.',
}
}


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


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


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


Line 185: Line 198:
-- but not blank/whitespace values. Ignores named args and args < "from".
-- but not blank/whitespace values. Ignores named args and args < "from".
function p._forSee (args, from, options)
function p._forSee (args, from, options)
local forSeeTable = p.forSeeArgsToTable(args, from, options)
    local forSeeTable = p.forSeeArgsToTable(args, from, options)
return p.forSeeTableToString(forSeeTable, options)
    return p.forSeeTableToString(forSeeTable, options)
end
end


-- As _forSee, but uses the frame.
-- As _forSee, but uses the frame.
function p.forSee (frame, from, options)
function p.forSee (frame, from, options)
mArguments = require('Module:Arguments')
    mArguments = require('Module:Arguments')
return p._forSee(mArguments.getArgs(frame), from, options)
    return p._forSee(mArguments.getArgs(frame), from, options)
end
end


return p
return p

Latest revision as of 14:37, 29 July 2024

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

--------------------------------------------------------------------------------
--                           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}}, {{redirect}}, and their variants. Also introduced    --
-- are andList & orList helpers for formatting lists with those conjunctions. --
--------------------------------------------------------------------------------

local mArguments --initialize lazily
local mFormatLink = require('Module:Format link')
local mHatnote = require('Module:Hatnote')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local p = {}

--------------------------------------------------------------------------------
-- 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 = "and",
    separator = ",",
    altSeparator = ";",
    space = " ",
    formatted = false
}

--Searches display text only
local function searchDisp(haystack, needle)
    return mw.ustring.find(
            mw.ustring.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
    )
end

-- Stringifies a list generically; probably shouldn't be used directly
local 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 = mFormatLink.formatPages(
                {categorizeMissing = mHatnote.missingTargetCat}, list
        )
    end
    -- Set the separator; if any item contains it, use the alternate separator
    local separator = options.separator
    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 conjunction = s .. options.conjunction .. s
    if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
        conjunction = separator .. conjunction
    end
    -- Return the formatted string
    return mw.text.listToText(list, separator .. s, conjunction)
end

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

-- Stringifies lists with "and" or "or"
function p.andList (...) return p.conjList("and", ...) end
function p.orList (...) return p.conjList("or", ...) end

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

--default options table used across the forSee family of functions
local forSeeDefaultOptions = {
    andKeyword = 'and',
    title = mw.title.getCurrentTitle().text,
    otherText = 'other uses',
    forSeeForm = 'For %s, see %s.',
}

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

-- Structures arguments into a table for stringification, & options
function p.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
    -- If there is extra text, and no arguments are given, give nil value
    -- to not produce default of "For other uses, see foo (disambiguation)"
    if options.extratext and i > maxArg then return nil end
    -- 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 p.forSeeTableToString (forSeeTable, options)
    -- Type-checks and defaults
    checkType("forSeeTableToString", 1, forSeeTable, "table", true)
    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 = {}
    if forSeeTable then
        for k, v in pairs(forSeeTable) do
            local useStr = v.use or options.otherText
            local pagesStr =
            p.andList(v.pages, true) or
                    mFormatLink._formatLink{
                        categorizeMissing = mHatnote.missingTargetCat,
                        link = mHatnote.disambiguate(options.title)
                    }
            local forSeeStr = mw.ustring.format(options.forSeeForm, useStr, pagesStr)
            forSeeStr = punctuationCollapse(forSeeStr)
            table.insert(strList, forSeeStr)
        end
    end
    if options.extratext then table.insert(strList, punctuationCollapse(options.extratext..'.')) 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 p._forSee (args, from, options)
    local forSeeTable = p.forSeeArgsToTable(args, from, options)
    return p.forSeeTableToString(forSeeTable, options)
end

-- As _forSee, but uses the frame.
function p.forSee (frame, from, options)
    mArguments = require('Module:Arguments')
    return p._forSee(mArguments.getArgs(frame), from, options)
end

return p