Difference between pages "Cross Attack" and "Module:Limitation status list"
(Difference between pages)
(`-` needs to be escaped in patterns) |
|||
Line 1: | Line 1: | ||
− | {{ | + | -- Shell object for the list itself |
+ | local List = { | ||
+ | name = nil, | ||
+ | medium = nil, | ||
+ | locality = nil, | ||
+ | startDate = nil, | ||
+ | prev = nil, | ||
+ | next = nil, | ||
+ | items = {}, | ||
− | {{ | + | -- If there is a column for non-English names |
− | + | -- The text to display in the column heading | |
− | + | localColumnHeading = nil, | |
− | + | -- The name of the SMW property to find the names in | |
− | + | localColumnProperty = nil, | |
− | + | ||
− | + | errors = {} | |
− | + | } | |
− | + | ||
− | + | -- Shell object for an item in the list | |
− | + | local ListItem = { | |
− | + | -- Link to card page. `nil` if not a specific card | |
− | + | card = nil, | |
− | + | -- Name of the card page, if applicable | |
− | + | cardName = nil, | |
− | + | localCardName = nil, | |
− | + | -- Final text to display in the cell | |
− | + | displayText = nil, | |
− | + | cardType = nil, | |
− | + | status = nil, | |
− | + | prevStatus = nil, | |
− | { | + | prevStatusNote = 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:setLocalisationAttributes() | |
− | + | ||
− | + | self.items = {} | |
− | + | self:addItems('Forbidden', args.forbidden) | |
− | + | self:addItems('Limited', args.limited) | |
− | + | self:addItems('Semi-Limited', args.semi_limited) | |
− | | | + | self:addItems('Unlimited', args.no_longer_on_list) |
− | + | ||
− | + | self:setSmwData() | |
− | + | end | |
+ | |||
+ | function List:setLocalisationAttributes() | ||
+ | if ( | ||
+ | self.locality == 'Simplified Chinese' or | ||
+ | self.locality == 'Traditional Chinese' or | ||
+ | self.locality == 'Japanese' or | ||
+ | self.locality == 'Korean' | ||
+ | ) then | ||
+ | self.localColumnHeading = self.locality | ||
+ | self.localColumnProperty = self.locality .. ' name' | ||
+ | |||
+ | elseif self.medium == 'OCG' or self.medium == 'Yu-Gi-Oh! Official Card Game' then | ||
+ | self.localColumnHeading = 'Japanese' | ||
+ | self.localColumnProperty = 'Japanese name' | ||
+ | |||
+ | else | ||
+ | self.localColumnHeading = nil | ||
+ | self.localColumnProperty = nil | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- Add items based on user input | ||
+ | -- @param status string | ||
+ | -- @param listInput string | ||
+ | function List:addItems(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 _, line in pairs(listItems) do | ||
+ | -- Split the line input by `//` and get data from each piece | ||
+ | local lineData = mw.text.split(line, '%s*//%s*') | ||
+ | local itemText = lineData[1] | ||
+ | local options = lineData[2] | ||
+ | |||
+ | -- Extract data from content after the `//` | ||
+ | local prevStatus = options and options:match('prev::([^;]*)') | ||
+ | local prevStatusNote = options and options:match('prev%-note::([^;]*)') | ||
+ | |||
+ | -- Create a new list item object | ||
+ | local listItem = mw.clone(ListItem) | ||
+ | |||
+ | listItem.status = status | ||
+ | listItem.prevStatus = prevStatus | ||
+ | listItem.prevStatusNote = prevStatusNote | ||
+ | listItem.displayText = itemText | ||
+ | |||
+ | -- Assume it's a card if there's no manual link syntax | ||
+ | local isCard = not itemText:find('%[%[') | ||
+ | |||
+ | -- If it's a card, look up the card's data and fill in more of the attributes | ||
+ | if isCard then | ||
+ | local cardData = self:lookupCardData(itemText) | ||
+ | |||
+ | if (not cardData) then | ||
+ | table.insert(self.errors, 'Failed to look up details for card: "' .. itemText .. '"') | ||
+ | end | ||
+ | |||
+ | listItem.card = itemText | ||
+ | listItem.cardName = cardData and cardData.name or itemText | ||
+ | listItem.localCardName = cardData and cardData.localName or '' | ||
+ | listItem.cardType = cardData and cardData.cardType | ||
+ | listItem.displayText = '"[[' .. listItem.card .. '|' .. listItem.cardName .. ']]"' | ||
+ | end | ||
+ | |||
+ | -- Add the card to the list | ||
+ | table.insert(self.items, listItem) | ||
+ | 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' | ||
+ | }) | ||
+ | |||
+ | -- Go through each list item to add a subobject | ||
+ | for _, item in pairs(self.items) do | ||
+ | -- If it's not a card, don't store any data | ||
+ | if (not item.card) then return end | ||
+ | |||
+ | mw.smw.subobject({ | ||
+ | 'List contains = ' .. item.card, | ||
+ | 'Status = ' .. item.status, | ||
+ | }) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | -- Find data on a card by suppyling its name | ||
+ | function List:lookupCardData(pageName) | ||
+ | local queryParams = { | ||
+ | '[[' .. pageName .. ']]', | ||
+ | '?English name = name', | ||
+ | '?Card type# = cardType', | ||
+ | } | ||
+ | |||
+ | if (self.localColumnProperty) then | ||
+ | table.insert(queryParams, '?' .. self.localColumnProperty .. ' = localName') | ||
+ | end | ||
+ | |||
+ | local cardData = mw.smw.ask(queryParams) | ||
+ | |||
+ | return cardData and cardData[1] or nil | ||
+ | end | ||
+ | |||
+ | -- Filter a list's `cards` attribute by a status | ||
+ | -- @return table | ||
+ | function List:getItemsByStatus(status) | ||
+ | -- New array to contain list items with just the specified status | ||
+ | local filteredItems = {} | ||
+ | |||
+ | -- Loop through the list of all cards | ||
+ | for _, item in pairs(self.items) do | ||
+ | -- If the item has the specified status, add it to the new array | ||
+ | if (item.status == status) then | ||
+ | table.insert(filteredItems, item) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | return filteredItems | ||
+ | end | ||
+ | |||
+ | -- Render the overall output | ||
+ | -- @return string | ||
+ | function List:render() | ||
+ | local output = '' | ||
+ | output = output .. self:renderErrors() | ||
+ | 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') | ||
+ | output = output .. self:renderCategories() | ||
+ | |||
+ | return output | ||
+ | end | ||
+ | |||
+ | function List:renderErrors() | ||
+ | -- Exit early with empty string if there are no errors. | ||
+ | if (#self.errors == 0) then return '' end; | ||
+ | |||
+ | local output = '<div style="color: #d33;">' | ||
+ | output = output .. '\nThis list contains the following issues:\n' | ||
+ | |||
+ | for _, error in pairs(self.errors) do | ||
+ | output = output .. '\n* ' .. error .. '' | ||
+ | end | ||
+ | |||
+ | output = output .. '\n</div>' | ||
+ | |||
+ | 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.next .. '|' .. self:pageNameToShortName(self.next) .. ']] →') or ' ' | ||
+ | |||
+ | local output = '<div class="toccolours" style="clear: both; display: flex; margin-bottom: .5em;">' | ||
+ | output = output .. '<div style="flex: 1; text-align: left;">' .. prevText .. '</div>' | ||
+ | output = output .. '<div style="flex: 1; text-align: center;">' .. currText .. '</div>' | ||
+ | output = output .. '<div style="flex: 1; 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 | ||
+ | |||
+ | function List:renderCategories() | ||
+ | -- Don't render any categories if this is outside of the main namespace | ||
+ | if (isFilled(mw.title.getCurrentTitle().nsText)) then return '' end | ||
+ | |||
+ | local output = '[[Category:' .. (self.region or '') .. ' ' .. (self.medium or '') .. ' Forbidden & Limited Lists]]' | ||
+ | |||
+ | if (#self.errors > 0) then | ||
+ | output = output .. '[[Category:Pages with validation errors]]' | ||
+ | end | ||
+ | |||
+ | return output | ||
+ | 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 list items for the given status | ||
+ | local items = self:getItemsByStatus(status) | ||
+ | |||
+ | -- If there are no cards, return empty string and end early | ||
+ | if (#items == 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') | ||
+ | if self.localColumnHeading then | ||
+ | headingRow:tag('th'):wikitext(self.localColumnHeading) | ||
+ | end | ||
+ | headingRow:tag('th'):wikitext('Card type') | ||
+ | headingRow:tag('th'):wikitext('Status') | ||
+ | headingRow:tag('th'):wikitext('Changes') | ||
+ | |||
+ | for _, item in pairs(items) do | ||
+ | local cardTypeLink = item.cardType | ||
+ | and ('[[' .. item.cardType .. '|' .. string.gsub(item.cardType, ' Card', '') .. ']]') | ||
+ | or '' | ||
+ | |||
+ | -- Form the CSS class that styles the cells in the status column | ||
+ | local statusClass = 'status-' .. (item.status):lower() | ||
+ | |||
+ | local row = list:tag('tr') | ||
+ | row:tag('td'):wikitext(item.displayText) | ||
+ | if self.localColumnHeading then | ||
+ | row:tag('td'):wikitext(item.localCardName) | ||
+ | end | ||
+ | row:tag('td'):wikitext(cardTypeLink) | ||
+ | row:tag('td'):addClass(statusClass):wikitext('[[' .. item.status .. ']]') | ||
+ | row:tag('td'):wikitext( | ||
+ | (item.prevStatus and 'was [[' .. item.prevStatus .. ']]' or '') .. | ||
+ | (item.prevStatusNote 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 |
Revision as of 15:38, 5 December 2023
Module:Limitation status list is implemented by Template:Limitation list
-- Shell object for the list itself
local List = {
name = nil,
medium = nil,
locality = nil,
startDate = nil,
prev = nil,
next = nil,
items = {},
-- If there is a column for non-English names
-- The text to display in the column heading
localColumnHeading = nil,
-- The name of the SMW property to find the names in
localColumnProperty = nil,
errors = {}
}
-- Shell object for an item in the list
local ListItem = {
-- Link to card page. `nil` if not a specific card
card = nil,
-- Name of the card page, if applicable
cardName = nil,
localCardName = nil,
-- Final text to display in the cell
displayText = nil,
cardType = nil,
status = nil,
prevStatus = nil,
prevStatusNote = 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:setLocalisationAttributes()
self.items = {}
self:addItems('Forbidden', args.forbidden)
self:addItems('Limited', args.limited)
self:addItems('Semi-Limited', args.semi_limited)
self:addItems('Unlimited', args.no_longer_on_list)
self:setSmwData()
end
function List:setLocalisationAttributes()
if (
self.locality == 'Simplified Chinese' or
self.locality == 'Traditional Chinese' or
self.locality == 'Japanese' or
self.locality == 'Korean'
) then
self.localColumnHeading = self.locality
self.localColumnProperty = self.locality .. ' name'
elseif self.medium == 'OCG' or self.medium == 'Yu-Gi-Oh! Official Card Game' then
self.localColumnHeading = 'Japanese'
self.localColumnProperty = 'Japanese name'
else
self.localColumnHeading = nil
self.localColumnProperty = nil
end
end
-- Add items based on user input
-- @param status string
-- @param listInput string
function List:addItems(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 _, line in pairs(listItems) do
-- Split the line input by `//` and get data from each piece
local lineData = mw.text.split(line, '%s*//%s*')
local itemText = lineData[1]
local options = lineData[2]
-- Extract data from content after the `//`
local prevStatus = options and options:match('prev::([^;]*)')
local prevStatusNote = options and options:match('prev%-note::([^;]*)')
-- Create a new list item object
local listItem = mw.clone(ListItem)
listItem.status = status
listItem.prevStatus = prevStatus
listItem.prevStatusNote = prevStatusNote
listItem.displayText = itemText
-- Assume it's a card if there's no manual link syntax
local isCard = not itemText:find('%[%[')
-- If it's a card, look up the card's data and fill in more of the attributes
if isCard then
local cardData = self:lookupCardData(itemText)
if (not cardData) then
table.insert(self.errors, 'Failed to look up details for card: "' .. itemText .. '"')
end
listItem.card = itemText
listItem.cardName = cardData and cardData.name or itemText
listItem.localCardName = cardData and cardData.localName or ''
listItem.cardType = cardData and cardData.cardType
listItem.displayText = '"[[' .. listItem.card .. '|' .. listItem.cardName .. ']]"'
end
-- Add the card to the list
table.insert(self.items, listItem)
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'
})
-- Go through each list item to add a subobject
for _, item in pairs(self.items) do
-- If it's not a card, don't store any data
if (not item.card) then return end
mw.smw.subobject({
'List contains = ' .. item.card,
'Status = ' .. item.status,
})
end
end
-- Find data on a card by suppyling its name
function List:lookupCardData(pageName)
local queryParams = {
'[[' .. pageName .. ']]',
'?English name = name',
'?Card type# = cardType',
}
if (self.localColumnProperty) then
table.insert(queryParams, '?' .. self.localColumnProperty .. ' = localName')
end
local cardData = mw.smw.ask(queryParams)
return cardData and cardData[1] or nil
end
-- Filter a list's `cards` attribute by a status
-- @return table
function List:getItemsByStatus(status)
-- New array to contain list items with just the specified status
local filteredItems = {}
-- Loop through the list of all cards
for _, item in pairs(self.items) do
-- If the item has the specified status, add it to the new array
if (item.status == status) then
table.insert(filteredItems, item)
end
end
return filteredItems
end
-- Render the overall output
-- @return string
function List:render()
local output = ''
output = output .. self:renderErrors()
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')
output = output .. self:renderCategories()
return output
end
function List:renderErrors()
-- Exit early with empty string if there are no errors.
if (#self.errors == 0) then return '' end;
local output = '<div style="color: #d33;">'
output = output .. '\nThis list contains the following issues:\n'
for _, error in pairs(self.errors) do
output = output .. '\n* ' .. error .. ''
end
output = output .. '\n</div>'
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.next .. '|' .. self:pageNameToShortName(self.next) .. ']] →') or ' '
local output = '<div class="toccolours" style="clear: both; display: flex; margin-bottom: .5em;">'
output = output .. '<div style="flex: 1; text-align: left;">' .. prevText .. '</div>'
output = output .. '<div style="flex: 1; text-align: center;">' .. currText .. '</div>'
output = output .. '<div style="flex: 1; 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
function List:renderCategories()
-- Don't render any categories if this is outside of the main namespace
if (isFilled(mw.title.getCurrentTitle().nsText)) then return '' end
local output = '[[Category:' .. (self.region or '') .. ' ' .. (self.medium or '') .. ' Forbidden & Limited Lists]]'
if (#self.errors > 0) then
output = output .. '[[Category:Pages with validation errors]]'
end
return output
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 list items for the given status
local items = self:getItemsByStatus(status)
-- If there are no cards, return empty string and end early
if (#items == 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')
if self.localColumnHeading then
headingRow:tag('th'):wikitext(self.localColumnHeading)
end
headingRow:tag('th'):wikitext('Card type')
headingRow:tag('th'):wikitext('Status')
headingRow:tag('th'):wikitext('Changes')
for _, item in pairs(items) do
local cardTypeLink = item.cardType
and ('[[' .. item.cardType .. '|' .. string.gsub(item.cardType, ' Card', '') .. ']]')
or ''
-- Form the CSS class that styles the cells in the status column
local statusClass = 'status-' .. (item.status):lower()
local row = list:tag('tr')
row:tag('td'):wikitext(item.displayText)
if self.localColumnHeading then
row:tag('td'):wikitext(item.localCardName)
end
row:tag('td'):wikitext(cardTypeLink)
row:tag('td'):addClass(statusClass):wikitext('[[' .. item.status .. ']]')
row:tag('td'):wikitext(
(item.prevStatus and 'was [[' .. item.prevStatus .. ']]' or '') ..
(item.prevStatusNote 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