Module:AgesEras

From NPOWiki
Revision as of 01:36, 21 August 2020 by Bobogoobo (talk | contribs)
Jump to navigation Jump to search

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.

The above documentation is transcluded from 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)
	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 = '!' .. 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 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.
	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