Module:Limitation status list

From Yugipedia
Revision as of 16:08, 4 December 2023 by Deltaneos (talk | contribs) (Change input format for denoting previous status)
Jump to: navigation, search

-- Shell object for the list itself
local List = {
	name = nil,
	medium = nil,
	locality = nil,
	startDate = nil,
	prev = nil,
	next = nil,
	cards = {}
}

-- Shell object for a card in the list
local ListCard = {
	link = nil,
	name = nil,
	cardType = nil,
	status = nil,
	prevStatus = nil,
	note = nil
}

-- Main function that gets invoked
-- Create a new list from the user input, then render it
function List.list(frame)
	-- Get the template parameters
	local args = frame:getParent().args

	local l = List:new(args)
	return l:render()
end

-- Create a new instance of a list
-- @param args table
-- @return List
function List:new(args)
	local l = mw.clone(List)

	l:setData(args or {})

	return l
end

-- Main function for setting data for the list
-- @param args table - data from the user input
function List:setData(args)
	self.name = args.name or mw.title.getCurrentTitle().text
	self.medium = args.medium
	self.locality = args.locality
	self.startDate = args.start_date
	self.prev = args.prev
	self.next = args.next

	self.cards = {}
	self:addCards('Forbidden', args.forbidden)
	self:addCards('Limited', args.limited)
	self:addCards('Semi-Limited', args.semi_limited)
	self:addCards('Unlimited', args.no_longer_on_list)
end

-- Add cards based on user input
-- @param status string
-- @param listInput string
function List:addCards(status, listInput)
	-- Exit early if there is no input
	if not isFilled(listInput) then return end

	-- Split the input by new line and loop through each line
	local listItems = mw.text.split(listInput, '\n')
	for _, cardInput in pairs(listItems) do
		-- Split the line input by `//` and get data from each piece
		local cardInputData = mw.text.split(cardInput, '%s*//%s*')
		local link = cardInputData[1]
		local options = cardInputData[2]

		-- Get data from options
		local prevStatus = options:match('prev::([^;]*)')

		-- Look up data for the card
		local cardData = self:lookupCardData(link)

		-- Create a new card object and populate it
		local card = mw.clone(ListCard)
		card.link      = link
		card.name      = cardData.name
		card.cardType  = cardData.cardType
		card.status    = status
		card.prevStatus = prevStatus

		-- Add the card to the list
		table.insert(self.cards, card)
	end
end

function List:setSmwData()
	mw.smw.set({
		['Effective date'] = self.startDate,
		['Medium']         = self.medium,
		['Release']        = self.medium,
		['Locality']       = self.locality,
		['Page type']      = 'Status list'
	})

	for _, card in pairs(self.cards) do
		mw.smw.subobject({
			'List contains = ' .. card.link,
			'Status        = ' .. card.status,
		})
	end
end

-- Find data on a card by suppyling its name
function List:lookupCardData(pageName)
	local cardData = mw.smw.ask{
		'[[' .. pageName  .. ']]',
		'?English name = name',
		'?Card type#   = cardType',
		mainlabel = '-'
	}
	
	return cardData and cardData[1] or {}
end

-- Filter a list's `cards` attribute by a status
-- @return table
function List:getCardsByStatus(status)
	-- New array to contain cards with just the specified status
	local filteredCards = {}

	-- Loop through the list of all cards
	for _, card in pairs(self.cards) do
		-- If the card has the specified status, add it to the new array
		if (card.status == status) then
			table.insert(filteredCards, card)
		end
	end

	return filteredCards
end

-- Render the overall output
-- @return string
function List:render()
	local output = ''
	output = output .. self:renderNavigation()
	output = output .. self:renderStatusList('Forbidden')
	output = output .. self:renderStatusList('Limited')
	output = output .. self:renderStatusList('Semi-Limited')
	output = output .. self:renderStatusList('Unlimited', 'No longer on list')
	
	return output
end

-- Render the navigation section
-- @return string
function List:renderNavigation()
	-- If prev and next are both empty, don't show a navigation menu
	if (not isFilled(self.prev) and not isFilled(self.next)) then
		return ''
	end

	local currText = self:pageNameToShortName(self.name)
	local prevText = self.prev and ('← [[' .. self.prev .. '|' .. self:pageNameToShortName(self.prev) .. ']]') or ' '
	local nextText = self.next and ('[[' .. self.prev .. '|' .. self:pageNameToShortName(self.next) .. ']] →') or ' '

	local output = '<div style="border: 1px solid #aaa; display: flex; margin-bottom: .5em;">'
	output = output .. '<div style="flex: 1; padding: .25em; text-align: left;">' .. prevText .. '</div>'
	output = output .. '<div style="flex: 1; padding: .25em; text-align: center;">' .. currText .. '</div>'
	output = output .. '<div style="flex: 1; padding: .25em; text-align: right;">' .. nextText .. '</div>'
	output = output .. '</div>'

	return output
end

-- Convert a page name to a short name
-- e.g. "March 2020 Lists (Duel Links)" -> "March 2020 Lists"
-- e.g. "OCG April 2014 Lists" -> "April 2014 Lists"
-- @param pageName string
-- @return string
function List:pageNameToShortName(pageName)
	-- Strip out disambiguation text
	local shortName = mw.text.split(pageName, ' %(')[1]
	
	-- Remove mention of the medium
	shortName = shortName:gsub(self.medium, '')

	-- Clear any leading/trailing spaces
	return mw.text.trim(shortName)
end

-- Render a section with a list fora given status
-- @param status string
-- @param heading status - The text to appear in the heading, if different than the status
-- @return string
function List:renderStatusList(status, heading)
	-- Get all cards for the given status
	local cards = self:getCardsByStatus(status)

	-- If there are no cards, return empty string and end early
	if (#cards == 0) then return '' end

	local heading = mw.html.create('h2'):wikitext(heading or status)
	local list = mw.html.create('table'):addClass('wikitable sortable')

	local headingRow = list:tag('tr')
	headingRow:tag('th'):wikitext('Card')
	headingRow:tag('th'):wikitext('Card type')
	headingRow:tag('th'):wikitext('Status')
	headingRow:tag('th'):wikitext('Changes')

	for _, card in pairs(cards) do
		local cardTypeLink = card.cardType
			and ('[[' .. card.cardType .. '|' .. string.gsub(card.cardType, ' Card', '') .. ']]')
			or ''

		-- Form the CSS class that styles the cells in the status column
		local statusClass = 'status-' .. (card.status):lower()

		local row = list:tag('tr')
		row:tag('td'):wikitext('[[' .. card.link .. '|' .. (card.name or card.link) .. ']]')
		row:tag('td'):wikitext(cardTypeLink)
		row:tag('td'):addClass(statusClass):wikitext('[[' .. card.status .. ']]')
		row:tag('td'):wikitext(card.prevStatus and 'was [[' .. card.prevStatus .. ']]' or '')
	end

	return tostring(heading) .. tostring(list)
end

-- Check if something has been filled out
-- i.e. check that it's neither `nil` nor empty string
-- @param value mixed
-- @return bool
function isFilled(value)
	return value ~= nil and value ~= ''
end

return List