Permanently protected module

Difference between revisions of "Module:Card/models/Limitation history"

From Yugipedia
Jump to: navigation, search
(function to render the status history as a table)
(Allow for debut date to be specified. Use that to fill in period of being Unlimited before first limitation, if applicable.)
Line 1: Line 1:
 
-- @field card          string    Page name of the card whose history this is for
 
-- @field card          string    Page name of the card whose history this is for
 
-- @field format        string    The format the history is for (e.g. "Advanced Format", "OCG", "Korean")
 
-- @field format        string    The format the history is for (e.g. "Advanced Format", "OCG", "Korean")
 +
-- @field debutDate      string    ISO date of when the card was first released in the format
 
-- @field history        table    List with details on each limitation list that the card was on and the status it had on each list
 
-- @field history        table    List with details on each limitation list that the card was on and the status it had on each list
 
-- @field groupedHistory table    Shorter version of `history` where consecutive lists in which the card had the same status are grouped together
 
-- @field groupedHistory table    Shorter version of `history` where consecutive lists in which the card had the same status are grouped together
Line 7: Line 8:
 
card = nil,
 
card = nil,
 
format = nil,
 
format = nil,
 +
debutDate = nil,
 
history = {},
 
history = {},
 
groupedHistory = {},
 
groupedHistory = {},
Line 16: Line 18:
 
-- @param format string  Page name of the format
 
-- @param format string  Page name of the format
 
-- @return LimitationHistory
 
-- @return LimitationHistory
function LimitationHistory:new(card, format)
+
function LimitationHistory:new(card, format, debutDate)
 
local lh = mw.clone(LimitationHistory)
 
local lh = mw.clone(LimitationHistory)
 
lh.card = card
 
lh.card = card
 
lh.format = format
 
lh.format = format
 +
lh.debutDate = debutDate
 
lh.history = lh:lookupHistory()
 
lh.history = lh:lookupHistory()
 
lh.groupedHistory = lh:getGroupedHistory()
 
lh.groupedHistory = lh:getGroupedHistory()
Line 126: Line 129:
 
thead:tag('th'):attr('scope', 'col'):wikitext('Start date')
 
thead:tag('th'):attr('scope', 'col'):wikitext('Start date')
 
thead:tag('th'):attr('scope', 'col'):wikitext('End date')
 
thead:tag('th'):attr('scope', 'col'):wikitext('End date')
 +
 +
local firstListDate = self.groupedHistory[1].startDate
 +
 +
-- If the card was released before the first limitation status,
 +
-- Add a row saying it was 'Unlimited' in that period
 +
if (self.debutDate and self.debutDate < firstListDate) then
 +
local tr = historyTable:tag('tr')
 +
tr:tag('td'):addClass('status-unlimited'):wikitext('[[Unlimited]]')
 +
-- Start when the card was released
 +
tr:tag('td'):wikitext(self.debutDate)
 +
-- End the day before its first limitation
 +
tr:tag('td'):wikitext(addDaysToDate(firstListDate, -1))
 +
end
  
 
-- Loop through each change in status
 
-- Loop through each change in status
Line 169: Line 185:
 
function statusToClass(status)
 
function statusToClass(status)
 
return 'status-' .. (status:gsub(' ', '-')):lower()
 
return 'status-' .. (status:gsub(' ', '-')):lower()
 +
end
 +
 +
-- Add a number of days to a  date
 +
-- @param date string    Date in the format YYYY-MM-DD
 +
-- @param days number    Number of days to add. Use a negative number to subtract
 +
-- @return string        New date in the format YYYY-MM-DD
 +
function addDaysToDate(date, days)
 +
local dateParts = mw.text.split(date, '%-')
 +
 +
return os.date('%Y-%m-%d', os.time{
 +
year  = dateParts[1],
 +
month = dateParts[2],
 +
day  = dateParts[3] + days
 +
})
 
end
 
end
  
  
 
return LimitationHistory
 
return LimitationHistory

Revision as of 16:53, 10 December 2023

-- @field card           string    Page name of the card whose history this is for
-- @field format         string    The format the history is for (e.g. "Advanced Format", "OCG", "Korean")
-- @field debutDate      string    ISO date of when the card was first released in the format
-- @field history        table     List with details on each limitation list that the card was on and the status it had on each list
-- @field groupedHistory table     Shorter version of `history` where consecutive lists in which the card had the same status are grouped together
-- @field currentStatus  string    The card's status on the list that's in effect
local LimitationHistory = {
	card = nil,
	format = nil,
	debutDate = nil,
	history = {},
	groupedHistory = {},
	currentStatus = nil,
}

-- Create a new instance of a LimitationHistory object
-- @param card   string   Page name of the card
-- @param format string   Page name of the format
-- @return LimitationHistory
function LimitationHistory:new(card, format, debutDate)
	local lh = mw.clone(LimitationHistory)
	lh.card = card
	lh.format = format
	lh.debutDate = debutDate
	lh.history = lh:lookupHistory()
	lh.groupedHistory = lh:getGroupedHistory()
	lh.currentStatus = lh:getCurrentStatus()

	return lh
end

-- Look up all limitation status lists that include the card for the format
-- @return table
function LimitationHistory:lookupHistory()
	local results = mw.smw.ask{
		'[[List contains::' .. self.card .. ']]' ..
		'[[-Has subobject.Page type::Status list]]' ..
		'[[-Has subobject.Format::' .. self.format .. ']]',
		'?Status#                    = status',
		'?-Has subobject#            = list',
		'?-Has subobject.Start date# = startDate',
		'?-Has subobject.Start date  = startDateFormatted',
		'?-Has subobject.End date#   = endDate',
		'?-Has subobject.End date    = endDateFormatted',
		'mainlabel = -',
		'limit = 500'
	} or {}

	-- SMW sorting doesn't work with inverse properties. Doing it in Lua instead.
	table.sort(results and results, orderHistory)

	return results
end

-- Merge consecutive history items with the same status together
-- @return table
function LimitationHistory:getGroupedHistory()
	local i = 0
	local groupedHistory = {}

	-- Can't use `for item in pairs(self.history) do`
	-- because the order will get messed up.
	for h = 1, #self.history do
		local item = self.history[h]

		-- Check if this item has a different status than the incumbent item
		-- Always true for the first item.
		local hasNewStatus = (i == 0 or item.status ~= groupedHistory[i].status)

		if (hasNewStatus) then
			-- Create a new object at the next space in the table
			i = i + 1
			groupedHistory[i] = mw.clone(item)
			groupedHistory[i].firstList = item.list
			groupedHistory[i].list = nil
		else
			-- Update the end date on the existing object
			groupedHistory[i].endDate = item.endDate
			groupedHistory[i].endDateFormatted = item.endDateFormatted
			groupedHistory[i].lastList = item.list
		end
	end

	return groupedHistory
end

function orderHistory(item1, item2)
	return (item1.startDate or 'a') < (item2.startDate or 'a')
end

-- Get the current limitation status
-- @return string
-- @todo: Avoid potential issue with non-active lists that don't specify an end date
function LimitationHistory:getCurrentStatus()
	local today = os.date('%Y-%m-%d')

	-- Loop through the history items
	-- If today falls within the start and end dates, that's the current status
	for _, item in pairs(self.groupedHistory) do
		local todayAfterStartDate = not item.startDate or item.startDate <= today
		local todayBeforeEndDate = not item.endDate or item.endDate >= today
	
		if (todayAfterStartDate and todayBeforeEndDate) then
			-- Return the status or `nil` if it's "Unlimited"
			return item.status == 'Unlimited'
				and nil
				or item.status
		end
	end

	return nil
end

-- Render the history as a table
-- Table has a row for each change in status,
-- including the start and end date of the time with that status
-- 
-- @return string
function LimitationHistory:renderHistoryTable()
	-- Return empty string if there is no history
	if not self.groupedHistory or #self.groupedHistory == 0 then return '' end

	-- Create a HTML table
	local historyTable = mw.html.create('table'):addClass('wikitable')

	-- Add heading row
	local thead = historyTable:tag('tr')
	thead:tag('th'):attr('scope', 'col'):wikitext('Status')
	thead:tag('th'):attr('scope', 'col'):wikitext('Start date')
	thead:tag('th'):attr('scope', 'col'):wikitext('End date')

	local firstListDate = self.groupedHistory[1].startDate

	-- If the card was released before the first limitation status,
	-- Add a row saying it was 'Unlimited' in that period
	if (self.debutDate and self.debutDate < firstListDate) then
		local tr = historyTable:tag('tr')
		tr:tag('td'):addClass('status-unlimited'):wikitext('[[Unlimited]]')
		-- Start when the card was released
		tr:tag('td'):wikitext(self.debutDate)
		-- End the day before its first limitation
		tr:tag('td'):wikitext(addDaysToDate(firstListDate, -1))
	end

	-- Loop through each change in status
	for _, item in pairs(self.groupedHistory) do
		-- Add a row for the status.
		local tr = historyTable:tag('tr')

		-- Add a cell for the status and add a class to style it per its status.
		tr:tag('td'):addClass(statusToClass(item.status)):wikitext('[[' .. item.status .. ']]')

		-- Add cells for the start and end dates.
		-- Show the ISO dates in the cells (YYYY-MM-DD). Show the formatted dates on hover.
		tr:tag('td'):attr('title', item.startDateFormatted):wikitext(item.startDate)
		tr:tag('td'):attr('title', item.endDateFormatted):wikitext(item.endDate)
	end

	return tostring(historyTable)
end

-- Render a given status as a badge
-- @param status  string
-- @param context string    Text to display in parentheses after the status 
function LimitationHistory.renderStatusBadge(status, context)
	local badge = mw.html.create('div')

	local statusText = '[[' .. status .. ']]'

	if (context and context ~= '') then
		statusText = statusText .. ' (' .. context .. ')'
	end

	badge
		:addClass(statusToClass(status))
		:css('border', '2px solid #666')
		:css('border-radius', '3px')
		:css('display', 'inline-block')
		:css('padding', '.1em .5em')
		:wikitext(statusText)

	return tostring(badge)
end

function statusToClass(status)
	return 'status-' .. (status:gsub(' ', '-')):lower()
end

-- Add a number of days to a  date
-- @param date string    Date in the format YYYY-MM-DD
-- @param days number    Number of days to add. Use a negative number to subtract
-- @return string        New date in the format YYYY-MM-DD
function addDaysToDate(date, days)
	local dateParts = mw.text.split(date, '%-')
	
	return os.date('%Y-%m-%d', os.time{
		year  = dateParts[1],
		month = dateParts[2],
		day   = dateParts[3] + days
	})
end


return LimitationHistory