Module:AgesEras

From NPOWiki
Revision as of 03:35, 19 August 2020 by Bobogoobo (talk | contribs) (first prototype)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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 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
	return '<indicator name="' .. gameNum .. '-' .. mw.ustring.lower(game) .. '-' .. prefix .. '-' .. matchNum .. '-' .. mw.ustring.lower(name) .. '">' ..
		'[' .. '[' .. 'File:' .. kind .. ' icon ' .. game .. ' ' .. name .. '.png|30px|' .. desc .. ']]' ..
		'</indicator>'
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