Module:AgesEras
Jump to navigation
Jump to search
Documentation for this module may be created at Module:AgesEras/doc
local p = {}
local list = mw.loadData('Module:AgesEras/list')
local lang = mw.language.getContentLanguage()
local testMode = false
--arg names must match gameorder keys in /list module
--note: if multiple ranges match the same age/era, the tag will duplicate, but will have the same name so won't be shown
--note: ages come before eras intentionally due to alphabetical sorting, but this could be changed in icon()
--caution: pairs/ipairs and direct access are about the only things that will work on loadData tables
local function icon(kind, data, game, gameNum)
local frame = mw.getCurrentFrame()
--Pad to two digits
gameNum = mw.ustring.format('%02d', gameNum)
local index = mw.ustring.format('%02d', data[1])
local item = data[2]
if not item[1] or not item[2] or not item[3] or item[4] then
return '<span class="error">' ..
'invalid ' .. mw.ustring.upper(game) .. ' ' .. kind .. ' data in [[Module:AgesEras/list]]' ..
'</span>'
end
game = mw.ustring.upper(game)
local gameFull = frame:expandTemplate({ title='abbrgame', args={ game, 'full' } })
local name = item[2]
local desc = item[3]
local prefix, link
if kind == 'game' then
name = 'NPO'
prefix = 'a' .. kind
kind = 'era'
link = 'New Pacific Order (' .. gameFull .. ')'
else
prefix = kind
link = 'History of the New Pacific Order in ' .. gameFull .. '#' .. desc
end
local indicatorName = gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. index .. '-' .. mw.ustring.lower(name)
local indicatorText = '[' .. '[' .. 'File:' ..
lang:ucfirst(kind) .. ' icon ' .. game .. ' ' .. name ..
'.png|30px|' .. desc .. '|link=' .. link ..
']]'
if testMode then
return '<indicator name="' .. indicatorName .. '">' .. indicatorText .. '</indicator>'
else
return frame:extensionTag('indicator', indicatorText, { name = indicatorName })
end
end
--Returns { { start, end } or { error text }, ... }
local function argToTable(arg)
--This attempts to support most ways of writing ranges of dates, as long as they're consistent.
if not arg or mw.text.trim(arg) == '' then return {} end
local parsed = {}
arg = mw.ustring.lower(arg)
local rangeSep = ','
if mw.ustring.find(arg, ';', 1, true) then
rangeSep = ';'
elseif not mw.ustring.find(arg, '-', 1, true) and #mw.text.split(arg, ',', true) == 2 then
--This looks like a single date, don't want to split on comma
rangeSep = '&'
elseif mw.ustring.find(arg, ',', 1, true) and (mw.ustring.find(arg, ' - ', 1, true) or mw.ustring.find(arg, ' to ', 1, true)) then
--Looks like potentially one range with commas in dates
rangeSep = '&'
end
dateSep = ' - '
if mw.ustring.find(arg, ' to ', 1, true) then
dateSep = ' to '
end
mw.ustring.gsub(arg, ' until ', dateSep)
local ranges = mw.text.split(arg, rangeSep, true)
local dates
for i, range in ipairs(ranges) do
dates = mw.text.split(mw.text.trim(range), dateSep, true)
if #dates == 1 then
table.insert(dates, 'present')
elseif #dates > 2 then
dates = { 'invalid date range: ' .. range }
else
dates[1] = mw.text.trim(dates[1])
dates[2] = mw.text.trim(dates[2])
if dates[1] == '' then dates[1] = 'unknown' end
if dates[2] == '' then dates[2] = 'unknown' end
if dates[1] == 'present' then dates[1] = 'unknown' end
if dates[1] == 'unknown' then
dates[1] = dates[2]
end
if dates[2] == 'unknown' then
if dates[1] == 'unknown' then
dates = { 'invalid date range: ' .. range }
else
dates[2] = dates[1]
end
end
end
table.insert(parsed, dates)
end
return parsed
end
--Returns { { age/era index, age/era object } or { 'error', error text }, ... }
local function findMatches(itemList, dates)
if not itemList or not itemList[1] then return {} end
local matches = {}
local start = dates[1]
--Note: start can be present if it was unknown and finish is present
if start == 'present' then start = '' end
local finish = dates[2]
if finish == 'present' then finish = '' end
local isValid
--formatDate works same as #time, U is Unix time. Have to pass instance to pcall.
--Passing empty string gets current time.
isValid, start = pcall(lang.formatDate, lang, 'U', start)
if not isValid then
return { { 'error', 'invalid date: ' .. dates[1] } }
end
isValid, finish = pcall(lang.formatDate, lang, 'U', finish)
if not isValid then
return { { 'error', 'invalid date: ' .. dates[2] } }
end
start = tonumber(start)
finish = tonumber(finish)
local now = tonumber(lang:formatDate('U'))
if start > finish or start > now or finish > now then
return { { 'error', 'invalid date range: ' .. dates[1] .. ' to ' .. dates[2] } }
end
local itemStart, itemFinish, nextItem
for i, item in ipairs(itemList) do
itemStart = tonumber(lang:formatDate('U', item[1]))
nextItem = itemList[i + 1]
if nextItem then
nextItem = nextItem[1]
else
nextItem = ''
end
itemFinish = tonumber(lang:formatDate('U', nextItem))
--Note: this won't count being present for only the first or last day
if (start <= itemStart and finish > itemStart) or (start > itemStart and start < itemFinish) then
table.insert(matches, { i, item })
end
end
return matches
end
local function generateCode(dates, game, num)
if #dates == 0 then return '' end
local code = ''
local gameList = list[game]
if not gameList then return '' end
for i, range in ipairs(dates) do
if #range == 1 then
code = code .. '<span class="error">' .. range[1] .. '</span>'
else
if i == 1 and gameList.game then
code = code .. icon('game', { 1, gameList.game }, game, num)
end
for j, ageMatch in ipairs(findMatches(gameList.ages, range)) do
if ageMatch[1] == 'error' then
code = code .. '<span class="error">' .. ageMatch[2] .. '</span>'
else
code = code .. icon('age', ageMatch, game, num)
end
end
for k, eraMatch in ipairs(findMatches(gameList.eras, range)) do
if eraMatch[1] == 'error' then
code = code .. '<span class="error">' .. eraMatch[2] .. '</span>'
else
code = code .. icon('era', eraMatch, game, num)
end
end
end
end
return code
end
function p.main(frame)
return p.call(frame:getParent().args)
end
function p.call(args)
local code = ''
local gameOrder = list.gameorder
for i, game in ipairs(gameOrder) do
if args[game] then
code = code .. generateCode(argToTable(args[game]), game, i)
end
end
return code
end
function p.test(args)
testMode = true
return p.call(args)
end
return p