Module:AgesEras

From NPOWiki
Revision as of 04:17, 19 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()
--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
--caution: pairs/ipairs and direct access are about the only things that will work on loadData tables

local function icon(kind, data, game, matchNum, gameNum)
	if not data[1] or not data[2] or not data[3] or data[4] then
		return '<span class="error">' .. 
			'invalid ' .. game .. ' ' .. kind .. ' data in [[Module:AgesEras/list]]' ..
			'</span>'
	end
	local name = data[2]
	local desc = data[3]
	game = mw.ustring.upper(game)
	local prefix
	if kind == 'game' then
		name = 'NPO'
		prefix = '!' .. kind
		kind = 'era'
	else
		prefix = kind
	end
	--[=[ Use this instead for console debugging:
	return '<indicator name="' .. gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. matchNum .. '-' .. mw.ustring.lower(name) .. '">' ..
		'[' .. '[' .. 'File:' .. kind .. ' icon ' .. game .. ' ' .. name .. '.png|30px|' .. desc .. ']]' ..
		'</indicator>'
	]=]
	return mw.getCurrentFrame():extensionTag(
		'indicator',
		'[' .. '[' .. 'File:' .. kind .. ' icon ' .. game .. ' ' .. name .. '.png|30px|' .. desc .. ']]',
		{ name = gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. matchNum .. '-' .. mw.ustring.lower(name) }
	)
end

--Returns { { start, end }, ... }
local function argToTable(arg)
	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 = '&'
	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

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
	--formatDate works same as #time, U is Unix time
	start = time:formatDate('U', start)
	finish = time:formatDate('U', finish)
	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, 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', gameList.game, game, i, num)
			end
			for j, ageMatch in ipairs(findMatches(gameList.ages, range)) do
				code = code .. icon('age', ageMatch, game, j, num)
			end
			for k, eraMatch in ipairs(findMatches(gameList.eras, range)) do
				code = code .. icon('era', eraMatch, game, k, num)
			end
		end
	end
	return code
end

function p.main(frame)
	return p.call(frame:getParent().args)
end

function p.call(args)
	local code = ''
	--Should be in order of branch establishment, this will determine display order.
	local games = {
		argToTable(args.ns),
		argToTable(args.cn),
	}
	--This is a map to /list keys, must be in same order as above.
	local gameCodes = { 'ns', 'cn', }
	
	for i, gameTable in ipairs(games) do
		code = code .. generateCode(gameTable, gameCodes[i], i)
	end
	return code
end

return p