Module:Format

From Remilia Wiki
Jump to navigation Jump to search

Shared implementation for formatting helpers.


--------------------------------------------------------------------------------
-- Module:Format
-- Simple formatting utilities for inline and block-level elements.
--
-- INSTALLATION:
-- 1. Create page: Module:Format
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Format/styles.css
--
-- USAGE:
-- {{#invoke:Format|abbr|abbreviation|full text}}
-- {{#invoke:Format|nowrap|text that should not wrap}}
-- {{#invoke:Format|lang|fr|Bonjour}}
-- {{#invoke:Format|sic}}
-- {{#invoke:Format|clear}}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------

local p = {}

--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------

local function trim(s)
    if not s then return nil end
    return tostring(s):match("^%s*(.-)%s*$")
end

local function isEmpty(s)
    if s == nil then return true end
    if type(s) == 'string' then
        return trim(s) == ''
    end
    return false
end

local function hasValue(s)
    return not isEmpty(s)
end

local function getArg(args, name, default)
    local val = args[name]
    if isEmpty(val) then return default end
    return trim(val)
end

local function getFrameArgs(frame)
    return require('Module:Arguments').getArgs(frame, {
        trim = false,
        removeBlanks = false,
    })
end

--------------------------------------------------------------------------------
-- ABBREVIATION
--------------------------------------------------------------------------------

function p.abbr(frame)
    local args = getFrameArgs(frame)

    local abbr = getArg(args, 1)
    local title = getArg(args, 2) or getArg(args, 'title')

    if isEmpty(abbr) then
        return '<span class="error">Error: Abbreviation required</span>'
    end

    local span = mw.html.create('abbr')
        :addClass('abbr')

    if hasValue(title) then
        span:attr('title', title)
    end

    span:wikitext(abbr)

    return tostring(span)
end

--------------------------------------------------------------------------------
-- NOWRAP
--------------------------------------------------------------------------------

function p.nowrap(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1)
    if isEmpty(text) then
        return ''
    end

    local span = mw.html.create('span')
        :addClass('nowrap')
        :wikitext(text)

    return tostring(span)
end

--------------------------------------------------------------------------------
-- LANGUAGE MARKUP
--------------------------------------------------------------------------------

function p.lang(frame)
    local args = getFrameArgs(frame)

    local code = getArg(args, 1) or getArg(args, 'code')
    local text = getArg(args, 2) or getArg(args, 'text')

    if isEmpty(text) then
        return '<span class="error">Error: Text required</span>'
    end

    local span = mw.html.create('span')
        :addClass('lang')

    if hasValue(code) then
        span:attr('lang', code)
        span:addClass('lang-' .. code)
    end

    span:wikitext(text)

    return tostring(span)
end

--------------------------------------------------------------------------------
-- SIC
--------------------------------------------------------------------------------

function p.sic(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1)
    local reason = getArg(args, 'reason')

    local result = ''

    if hasValue(text) then
        result = text
    end

    local span = mw.html.create('span')
        :addClass('sic')
        :addClass('noprint')

    if hasValue(reason) then
        span:attr('title', reason)
    end

    span:wikitext('[<i>sic</i>]')

    return result .. tostring(span)
end

--------------------------------------------------------------------------------
-- CLEAR
--------------------------------------------------------------------------------

function p.clear(frame)
    local args = getFrameArgs(frame)

    local side = getArg(args, 1, 'both')

    local div = mw.html.create('div')
        :addClass('clear')

    if side == 'left' then
        div:addClass('clear-left')
    elseif side == 'right' then
        div:addClass('clear-right')
    else
        div:addClass('clear-both')
    end

    return tostring(div)
end

--------------------------------------------------------------------------------
-- BLOCK INDENT
--------------------------------------------------------------------------------

function p.indent(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1) or getArg(args, 'text')
    local level = math.max(1, math.min(5, math.floor(tonumber(getArg(args, 'level', '1')) or 1)))
    local amount = tonumber(getArg(args, 'em'))

    if isEmpty(text) then
        return ''
    end

    local div = mw.html.create('div')
        :addClass('block-indent')
        :wikitext(text)

    if amount then
        amount = math.max(0, math.min(20, amount))
        div:css('margin-left', amount .. 'em'):css('margin-right', amount .. 'em')
    else
        div:addClass('indent-' .. level)
    end

    return tostring(div)
end

--------------------------------------------------------------------------------
-- EMPHASIS
--------------------------------------------------------------------------------

function p.em(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1)
    if isEmpty(text) then
        return ''
    end

    local em = mw.html.create('em')
        :wikitext(text)

    return tostring(em)
end

--------------------------------------------------------------------------------
-- STRONG
--------------------------------------------------------------------------------

function p.strong(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1)
    if isEmpty(text) then
        return ''
    end

    local strong = mw.html.create('strong')
        :wikitext(text)

    return tostring(strong)
end

--------------------------------------------------------------------------------
-- ANCHOR
--------------------------------------------------------------------------------

function p.anchor(frame)
    local args = getFrameArgs(frame)

    local name = getArg(args, 1) or getArg(args, 'name')
    if isEmpty(name) then
        return ''
    end

    local span = mw.html.create('span')
        :attr('id', name)

    return tostring(span)
end

--------------------------------------------------------------------------------
-- UNBULLETED LIST
--------------------------------------------------------------------------------

function p.unbulleted(frame)
    local args = getFrameArgs(frame)

    local ul = mw.html.create('ul')
        :addClass('unbulleted-list')

    for i = 1, 30 do
        local item = getArg(args, i)
        if hasValue(item) then
            ul:tag('li'):wikitext(item)
        end
    end

    return tostring(ul)
end

--------------------------------------------------------------------------------
-- PLAINLIST
--------------------------------------------------------------------------------

function p.plainlist(frame)
    local args = getFrameArgs(frame)

    local content = getArg(args, 1) or getArg(args, 'content')
    if isEmpty(content) then
        return ''
    end

    local div = mw.html.create('div')
        :addClass('plainlist')
        :wikitext(content)

    return tostring(div)
end

--------------------------------------------------------------------------------
-- CIRCA
--------------------------------------------------------------------------------

function p.circa(frame)
    local args = getFrameArgs(frame)

    local date = getArg(args, 1)
    local lc = require('Module:Yesno')(getArg(args, 'lc'), false)

    local prefix = lc and 'c.' or 'C.'

    local abbr = mw.html.create('abbr')
        :attr('title', 'circa')
        :wikitext(prefix)

    local result = tostring(abbr)

    if hasValue(date) then
        result = result .. '&thinsp;' .. date
    end

    return result
end

--------------------------------------------------------------------------------
-- IPA
--------------------------------------------------------------------------------

function p.ipa(frame)
    local args = getFrameArgs(frame)

    local text = getArg(args, 1)
    if isEmpty(text) then
        return ''
    end

    local span = mw.html.create('span')
        :addClass('ipa')
        :attr('title', 'Representation in the International Phonetic Alphabet')
        :wikitext('/' .. text .. '/')

    return tostring(span)
end

--------------------------------------------------------------------------------
-- APOSTROPHE (after italics)
--------------------------------------------------------------------------------

function p.apostrophe(frame)
    -- Returns a properly formatted apostrophe that won't be
    -- interpreted as wiki italic markup
    return "<span class=\"apostrophe\">'</span>"
end

--------------------------------------------------------------------------------
-- APOSTROPHE-S (possessive after italics)
--------------------------------------------------------------------------------

function p.apostrophe_s(frame)
    -- Returns a properly formatted possessive 's that won't be
    -- interpreted as wiki italic markup
    return "<span class=\"apostrophe\">'s</span>"
end

--------------------------------------------------------------------------------
-- MATH (inline mathematical expression)
--------------------------------------------------------------------------------

function p.math(frame)
    local args = getFrameArgs(frame)

    local expr = getArg(args, 1)
    if isEmpty(expr) then
        return ''
    end

    local span = mw.html.create('span')
        :addClass('texhtml')
        :wikitext(expr)

    return tostring(span)
end

--------------------------------------------------------------------------------
-- GALLERY (wrapper for MediaWiki gallery tag)
--------------------------------------------------------------------------------

function p.gallery(frame)
    local args = getFrameArgs(frame)

    local content = getArg(args, 1)
    if isEmpty(content) then
        return ''
    end

    return frame:extensionTag('gallery', content, {
        mode = getArg(args, 'mode'),
        widths = getArg(args, 'widths'),
        heights = getArg(args, 'heights'),
        caption = getArg(args, 'caption'),
    })
end

-- A trait's reference images are independent slots, including sparse input.
function p.traitReferences(frame)
    local args = getFrameArgs(frame)
    local lines = {}
    for index = 1, 8 do
        local key = 'ref' .. index
        local file = getArg(args, key)
        if file then
            local title = mw.title.new(file, 6)
            if title and title.namespace == 6 and title.interwiki == '' then
                local caption = getArg(args, key .. '_caption') or getArg(args, key .. '_subject') or ''
                lines[#lines + 1] = title.prefixedText .. '|' .. caption
            else
                return '<strong class="error">Invalid reference image in ' .. key .. '.</strong>'
            end
        end
    end
    if #lines == 0 then return '' end
    return frame:extensionTag('gallery', table.concat(lines, '\n'), {
        mode = getArg(args, 'mode', 'nolines'),
        widths = getArg(args, 'widths', '240'),
        heights = getArg(args, 'heights', '200'),
    })
end

-- Keep the template's missing/blank/default outputs distinct. Only evaluate
-- the selected result, so references in unused branches are not registered.
function p.yesno(frame)
    local args = getFrameArgs(frame)
    local value = args[1]
    if value == nil then return args['¬'] or '' end
    value = mw.ustring.lower(mw.text.trim(value))
    if value == '¬' then return args['¬'] or '' end
    if value == '' then return args.blank or args.no or '' end
    local trueValues = { yes = true, y = true, t = true, ['true'] = true, on = true, ['1'] = true }
    local falseValues = { no = true, n = true, f = true, ['false'] = true, off = true, ['0'] = true }
    if trueValues[value] then return args.yes or 'yes' end
    if falseValues[value] then return args.no or '' end
    return args.def or args.yes or 'yes'
end

return p