Module:AgesEras: Difference between revisions
Jump to navigation
Jump to search
(first prototype) |
m (use a non-special character to sort game icon) |
||
(7 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local list = mw.loadData('Module:AgesEras/list') | local list = mw.loadData('Module:AgesEras/list') | ||
local | 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: 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 | 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">' .. | 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 = frame: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 = ' | prefix = 'a' .. kind | ||
kind = 'era' | kind = 'era' | ||
link = 'New Pacific Order (' .. gameFull .. ')' | |||
else | else | ||
prefix = kind | 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 | ||
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 38: | Line 59: | ||
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 77: | Line 101: | ||
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 85: | Line 110: | ||
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 = | |||
finish = | --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 | local itemStart, itemFinish, nextItem | ||
for i, item in ipairs(itemList) do | for i, item in ipairs(itemList) do | ||
itemStart = | itemStart = tonumber(lang:formatDate('U', item[1])) | ||
nextItem = itemList[i + 1] | nextItem = itemList[i + 1] | ||
if nextItem then | if nextItem then | ||
Line 98: | Line 138: | ||
nextItem = '' | nextItem = '' | ||
end | end | ||
itemFinish = | itemFinish = tonumber(lang:formatDate('U', nextItem)) | ||
--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 119: | Line 159: | ||
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 133: | Line 181: | ||
function p.main(frame) | function p.main(frame) | ||
return p.call (frame:getParent().args) | return p.call(frame:getParent().args) | ||
end | end | ||
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( | if args[game] then | ||
code = code .. generateCode(argToTable(args[game]), game, i) | |||
end | |||
end | end | ||
return code | return code | ||
end | |||
function p.test(args) | |||
testMode = true | |||
return p.call(args) | |||
end | end | ||
return p | return p |
Latest revision as of 11:56, 1 August 2021
Documentation [edit]
This page is a stub - it only covers the very basics of its subject. More information should be added to make the page informative and useful. If no more information is available, the page should be considered for a merge, redirect, or deletion.
Details: documentation to be written
If you can correct the issue, please edit the page to do so, then remove this notice.
Please see Template:AgesEras for usage information. Use the template rather than invoking the module. Data for this module is imported from Module:AgesEras/list.Details: documentation to be written
If you can correct the issue, please edit the page to do so, then remove this notice.
The above documentation is transcluded from 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