Module:AgesEras: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
(various fixes and improvements) |
||
Line 2: | Line 2: | ||
local list = mw.loadData('Module:AgesEras/list') | local list = mw.loadData('Module:AgesEras/list') | ||
local time = mw.language.getContentLanguage() | local time = mw.language.getContentLanguage() | ||
local ucfirst = require('Module:ucfirst') | |||
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: 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 due to alphabetical sorting, but this could be changed in icon | --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 | --caution: pairs/ipairs and direct access are about the only things that will work on loadData tables | ||
local function icon(kind, data, game, | local function icon(kind, data, game, gameNum) | ||
if not | --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">' .. | return '<span class="error">' .. | ||
'invalid ' .. game .. ' ' .. kind .. ' data in [[Module:AgesEras/list]]' .. | 'invalid ' .. mw.ustring.upper(game) .. ' ' .. kind .. ' data in [[Module:AgesEras/list]]' .. | ||
'</span>' | '</span>' | ||
end | end | ||
game = mw.ustring.upper(game) | game = mw.ustring.upper(game) | ||
local prefix | local gameFull = mw.getCurrentFrame():expandTemplate({ title='abbrgame', args={ game, 'full' } }) | ||
local name = item[2] | |||
local desc = item[3] | |||
local prefix, link | |||
if kind == 'game' then | if kind == 'game' then | ||
name = 'NPO' | name = 'NPO' | ||
prefix = '!' .. kind | prefix = '!' .. kind | ||
kind = 'era' | kind = 'era' | ||
link = 'New Pacific Order (' .. gameFull .. ')' | |||
else | else | ||
prefix = kind | prefix = kind | ||
link = ucfirst(kind) .. 's (' .. gameFull .. ')#' .. desc | |||
end | |||
local indicatorName = gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. index .. '-' .. mw.ustring.lower(name) | |||
local indicatorText = '[' .. '[' .. 'File:' .. | |||
ucfirst(kind) .. ' icon ' .. game .. ' ' .. name .. | |||
'.png|30px|' .. desc .. '|link=' .. link .. | |||
']]' | |||
if testMode then | |||
return '<indicator name="' .. indicatorName .. '">' .. indicatorText .. '</indicator>' | |||
else | |||
return mw.getCurrentFrame():extensionTag('indicator', indicatorText, { name = indicatorName }) | |||
end | end | ||
end | end | ||
--Returns { { start, end }, ... } | --Returns { { start, end } or { error text }, ... } | ||
local function argToTable(arg) | 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 | if not arg or mw.text.trim(arg) == '' then return {} end | ||
local parsed = {} | local parsed = {} | ||
Line 45: | Line 58: | ||
elseif not mw.ustring.find(arg, '-', 1, true) and #mw.text.split(arg, ',', true) == 2 then | 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 | --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 = '&' | rangeSep = '&' | ||
end | end | ||
Line 84: | Line 100: | ||
end | end | ||
--Returns { { age/era index, age/era object } or { 'error', error text }, ... } | |||
local function findMatches(itemList, dates) | local function findMatches(itemList, dates) | ||
if not itemList or not itemList[1] then return {} end | if not itemList or not itemList[1] then return {} end | ||
Line 92: | Line 109: | ||
local finish = dates[2] | local finish = dates[2] | ||
if finish == 'present' then finish = '' end | if finish == 'present' then finish = '' end | ||
--formatDate works same as #time, U is Unix time | local isValid | ||
start = time | |||
finish = time:formatDate('U', | --formatDate works same as #time, U is Unix time. Have to pass instance to pcall. | ||
isValid, start = pcall(time.formatDate, time, 'U', start) | |||
if not isValid then | |||
return { { 'error', 'invalid date: ' .. dates[1] } } | |||
end | |||
isValid, finish = pcall(time.formatDate, time, 'U', finish) | |||
if not isValid then | |||
return { { 'error', 'invalid date: ' .. dates[2] } } | |||
end | |||
local now = time: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 | local itemStart, itemFinish, nextItem | ||
for i, item in ipairs(itemList) do | for i, item in ipairs(itemList) do | ||
itemStart = time:formatDate('U', item[1]) | itemStart = time:formatDate('U', item[1]) | ||
Line 108: | Line 137: | ||
--Note: this won't count being present for only the first or last day | --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 | if (start <= itemStart and finish > itemStart) or (start > itemStart and start < itemFinish) then | ||
table.insert(matches, item) | table.insert(matches, { i, item }) | ||
end | end | ||
end | end | ||
Line 126: | Line 155: | ||
else | else | ||
if i == 1 and gameList.game then | if i == 1 and gameList.game then | ||
code = code .. icon('game', gameList.game, game | code = code .. icon('game', { 1, gameList.game }, game, num) | ||
end | end | ||
for j, ageMatch in ipairs(findMatches(gameList.ages, range)) do | for j, ageMatch in ipairs(findMatches(gameList.ages, range)) do | ||
code = code .. icon('age', ageMatch, game | if ageMatch[1] == 'error' then | ||
code = code .. '<span class="error">' .. ageMatch[2] .. '</span>' | |||
else | |||
code = code .. icon('age', ageMatch, game, num) | |||
end | |||
end | end | ||
for k, eraMatch in ipairs(findMatches(gameList.eras, range)) do | for k, eraMatch in ipairs(findMatches(gameList.eras, range)) do | ||
code = code .. icon('era', eraMatch, game | 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 | end | ||
Line 145: | Line 182: | ||
function p.call(args) | function p.call(args) | ||
local code = '' | local code = '' | ||
local gameOrder = list.gameorder | |||
local | |||
for i, | for i, game in ipairs(gameOrder) do | ||
code = code .. generateCode( | code = code .. generateCode(argToTable(args[game]), game, i) | ||
end | end | ||
return code | return code | ||
end | |||
function p.test(args) | |||
testMode = true | |||
return p.call(args) | |||
end | end | ||
return p | return p |
Revision as of 01:28, 21 August 2020
Documentation for this module may be created at Module:AgesEras/doc
local p = {}
local list = mw.loadData('Module:AgesEras/list')
local time = mw.language.getContentLanguage()
local ucfirst = require('Module:ucfirst')
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)
--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 = mw.getCurrentFrame():expandTemplate({ title='abbrgame', args={ game, 'full' } })
local name = item[2]
local desc = item[3]
local prefix, link
if kind == 'game' then
name = 'NPO'
prefix = '!' .. kind
kind = 'era'
link = 'New Pacific Order (' .. gameFull .. ')'
else
prefix = kind
link = ucfirst(kind) .. 's (' .. gameFull .. ')#' .. desc
end
local indicatorName = gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. index .. '-' .. mw.ustring.lower(name)
local indicatorText = '[' .. '[' .. 'File:' ..
ucfirst(kind) .. ' icon ' .. game .. ' ' .. name ..
'.png|30px|' .. desc .. '|link=' .. link ..
']]'
if testMode then
return '<indicator name="' .. indicatorName .. '">' .. indicatorText .. '</indicator>'
else
return mw.getCurrentFrame():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.
isValid, start = pcall(time.formatDate, time, 'U', start)
if not isValid then
return { { 'error', 'invalid date: ' .. dates[1] } }
end
isValid, finish = pcall(time.formatDate, time, 'U', finish)
if not isValid then
return { { 'error', 'invalid date: ' .. dates[2] } }
end
local now = time: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 = time:formatDate('U', item[1])
nextItem = itemList[i + 1]
if nextItem then
nextItem = nextItem[1]
else
nextItem = ''
end
itemFinish = time: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
code = code .. generateCode(argToTable(args[game]), game, i)
end
return code
end
function p.test(args)
testMode = true
return p.call(args)
end
return p