Module:Chapter

From Yugipedia
Revision as of 22:25, 19 December 2023 by Deltaneos (talk | contribs) (This can be ignored for unnumbered chapters, which would otherwise give an error at this point.)
Jump to: navigation, search

local Chapter = {
	pageName = nil,
	seriesLink = nil,
	seriesName = nil,
	subseries = nil,
	number = nil,
	subseriesNumber = nil,
	chapterWord = 'Chapter',
	isSpecial = false,
	name = nil,
}

local DUELIST_OFFSET = 59
local MW_OFFSET = 278
local TRANSCEND_GAME_OFFSET = 343

local seriesData = {
	['Yu-Gi-Oh!']                = { link = 'Yu-Gi-Oh! (manga)',           chapterWord = 'Duel' },
	['Yu-Gi-Oh! R']              = { link = 'Yu-Gi-Oh! R',                 chapterWord = 'Duel Round' },
	['Yu-Gi-Oh! GX']             = { link = 'Yu-Gi-Oh! GX (manga)',        chapterWord = 'Chapter' },
	['Yu-Gi-Oh! 5D\'s']          = { link = 'Yu-Gi-Oh! 5D\'s (manga)',     chapterWord = 'Ride' },
	['Yu-Gi-Oh! ZEXAL']          = { link = 'Yu-Gi-Oh! ZEXAL (manga)',     chapterWord = 'Rank' },
	['Yu-Gi-Oh! D Team ZEXAL']   = { link = 'Yu-Gi-Oh! D-Team ZEXAL',      chapterWord = 'Chapter' },
	['Yu-Gi-Oh! ARC-V']          = { link = 'Yu-Gi-Oh! ARC-V (manga)',     chapterWord = 'Scale' },
	['Yu-Gi-Oh! ARC-V The Strongest Duelist Yuya!!']        = { link = 'Yu-Gi-Oh! ARC-V The Strongest Duelist Yuya!!',        chapterWord = 'Chapter' },
	['Yu-Gi-Oh! SEVENS Luke! Explosive Supremacy Legend!!'] = { link = 'Yu-Gi-Oh! SEVENS Luke! Explosive Supremacy Legend!!', chapterWord = 'Supremacy' },
	['Yu-Gi-Oh! Rush Duel LP']   = { link = 'Yu-Gi-Oh! Rush Duel LP',      chapterWord = 'Broadcast' },
	['Yu-Gi-Oh! OCG Structures'] = { link = 'Yu-Gi-Oh! OCG Structures',    chapterWord = 'Chapter' },
	['Nururin Charisma! GO! GO! Gokibore!!'] = { link = 'Nururin Charisma! GO! GO! Gokibore!!', chapterWord = 'Chapter' },
	['Yu-Gi-Oh! GO RUSH!!']      = { link = 'Yu-Gi-Oh! GO RUSH!! (manga)', chapterWord = 'Rush' },
	['Yu-Gi-Oh! OCG Stories']    = { link = 'Yu-Gi-Oh! OCG Stories',       chapterWord = 'Chapter' },
}

-- Create a new instance of a chapter object
-- @param args table (include either [`series` and `number`] OR [`page`] to look up the chapter)
-- @return Chapter
function Chapter:new(args)
	args = args or {}

	-- Lookup information on the chapter
	local chapterData = self.lookup(args)

	-- Create a new instance of a chapter object
	local c = mw.clone(Chapter)

	-- If nothing was found, return the empty object
	-- @todo: fill in some defaults
	if (not chapterData.pageName) then
		return c
	end

	-- Get data on the series
	local seriesData = seriesData[chapterData.series] or {}

	-- Fill in the chapter data
	c.pageName    = chapterData.pageName
	c.name        = chapterData.name
	c.seriesName  = chapterData.series
	c.seriesLink  = seriesData.link or chapterData.series
	c.chapterWord = chapterData.chapterWord or seriesData.chapterWord or 'Chapter'
	c.number      = chapterData.number
	c.isSpecial   = not chapterData.number
	c:setSubseriesData()

	return c
end

-- Lookup data for a chapter
-- @param args table (include either [`series` and `number`] OR [`page`] to look up the chapter)
-- @return table
function Chapter.lookup(args)
	local query = ''

	if args.page then
		query = query .. '[[' .. args.page .. ']]'
	end

	if args.series then
		query = query .. '[[Chapter series::' .. args.series .. ']]'
	end

	if args.number then
		query = query .. '[[Chapter number::' .. args.number .. ']]'
	end

	local result = mw.smw.ask{
		query,
		'?               = pageName#',
		'?Chapter series = series',
		'?Chapter number = number',
		'?Chapter word#  = chapterWord',
		'?English name   = name',
		'?Release date#  = releaseDate',
		'?Release date   = releaseDateFormatted',
	}

	-- Get the first result. If nothing is found, use an empty table.
	result = result and result[1] or {}
	
	-- If the page name was passed into the template, make sure it gets used
	-- So if it's a redirect, it gets tracked in Special:WhatLinksHere
	if (args.page) then
		result.pageName = args.page
	end
	
	return result
end

-- Fill in the Yu-Gi-Oh! Duelist or Yu-Gi-Oh! Millennium World information
-- if applicable for Yu-Gi-Oh! chapters
function Chapter:setSubseriesData()
	-- Exit early if the series isn't Yu-Gi-Oh!
	if self.seriesName ~= 'Yu-Gi-Oh!' then return end

	-- If the chapter is in the Duelist range
	-- Set the subseries to "Yu-Gi-Oh! Duelist" and calculate its number
	if (self.number and self.number > DUELIST_OFFSET and self.number <= MW_OFFSET) then
		self.subseries = 'Yu-Gi-Oh! Duelist'
		self.subseriesNumber = self.number - DUELIST_OFFSET
	end

	-- If the chapter is in the Millennium World range
	-- Set the subseries to "Yu-Gi-Oh! Millennium World" and calculate its number
	if (self.number and self.number > MW_OFFSET and self.number <= TRANSCEND_GAME_OFFSET) then
		self.subseries = 'Yu-Gi-Oh! Millennium World'
		self.subseriesNumber = self.number - MW_OFFSET
	end
end

-- Normalize input
-- @param table args
-- @return args
function normalizeArgs(args)
	-- If a parameter contains an empty string, ignore it
	local normalizedArgs = {}
	for param, value in pairs(args) do
	    if (value and mw.text.trim(value) ~= '') then
	    	normalizedArgs[param] = value
	    end
	end

	-- Relabel the unnamed params
	if (normalizedArgs[1]) then normalizedArgs.series = normalizedArgs[1] end
	if (normalizedArgs[2]) then normalizedArgs.number = normalizedArgs[2] end
	if (normalizedArgs[3]) then normalizedArgs.format = normalizedArgs[3] end

	local series = normalizedArgs.series

	-- If the series does not exist unless a 'Yu-Gi-Oh!' prefix is added, add the prefix
	if (series and not seriesData[series] and seriesData['Yu-Gi-Oh! ' .. series]) then
		normalizedArgs.series = 'Yu-Gi-Oh! ' .. series
	end

	-- If the supplied series is 'Yu-Gi-Oh! Duelist',
	-- change to 'Yu-Gi-Oh!' and use overall series numbering
	if (normalizedArgs.series and normalizedArgs.series == 'Yu-Gi-Oh! Duelist') then
		normalizedArgs.series = 'Yu-Gi-Oh!'
		normalizedArgs.number = normalizedArgs.number and (normalizedArgs.number + DUELIST_OFFSET) or nil
	end

	-- If the supplied series is 'Yu-Gi-Oh! Millennium World',
	-- change to 'Yu-Gi-Oh!' and use overall series numbering
	if (normalizedArgs.series and normalizedArgs.series == 'Yu-Gi-Oh! Millennium World') then
		normalizedArgs.series = 'Yu-Gi-Oh!'
		normalizedArgs.number = normalizedArgs.number and (normalizedArgs.number + MW_OFFSET) or nil
	end

	return normalizedArgs
end

-- Check if the input is okay
-- @param args table - The input from the template parameters
-- @return table - An array of error messages
function validateArgs(args)
	args = args or {}

	local errors = {}

	if (not args.page and (not args.series or not args.number)) then
		table.insert(errors, 'Either a page name or both a series and number must be specified.')
	end

	if (args.number and tonumber(args.number) == nil) then
		table.insert(errors, 'Number (<code>' .. args.number .. '</code>) must be numeric.')
	end

	if (args.format and args.format ~= 'ref' and args.format ~= 'number') then
		table.insert(errors, 'Format (<code>' .. args.format .. '</code>) was not recognized.')
	end

	return errors
end

-- Render the chapter as its name, linked and quoted
-- @return string
function Chapter:formatAsName()
	return '"[[' .. self.pageName .. '|' .. (self.name or self.pageName) .. ']]"'
end

-- Render the chapter as its number, linked
-- @return string
function Chapter:formatAsNumber()
	return '[[' .. self.pageName .. '|' .. (self.number or self.pageName) .. ']]'
end

-- Render the chapter in reference format
-- @return string
function Chapter:formatAsRef()
	local args = args or {}
	local output = ''

	if (self.seriesName) then
		output = output .. '<i>' .. self.seriesName .. '</i>'
	end

	if (self.chapterWord) then
		output = output .. ' ' .. self.chapterWord
	end

	if self.number then
		output = output .. ' ' .. self.number
	end

	if self.subseries then
		output = output .. ' (<i>' .. self.subseries .. '</i> ' .. self.chapterWord .. ' ' .. self.subseriesNumber .. ')'
	end

	output = output .. ': ' .. self:formatAsName()

	return mw.text.trim(output)
end

-- Get the name of the category containing images for this chapter
-- @return string
function Chapter:getImageCategoryName()
	local output = ''

	if (self.seriesName)  then output = output .. self.seriesName end
	if (self.chapterWord) then output = output .. ' ' .. self.chapterWord end
	if (self.number)      then output = output .. ' ' .. self.number end

	output = output .. ' images'

	return output
end

-- Get the link to the image category as it is formatted in a navigation menu
-- @return string
function Chapter:formatImageCategoryNavLink()
	local output = ''

	if (self.chapterWord) then output = output .. self.chapterWord end
	if (self.number)      then output = output .. ' ' .. self.number end

	output = output .. ': "[[:Category:' .. self:getImageCategoryName() .. '|' .. self.name .. ']]"'

	return output
end

-- Get the previous chapter
-- @param prev {string|number|nil}
--     The page name or chapter number of the previous chapter
--     Leave blank to automatically find the previous chapter
-- @return Chapter
function Chapter:getPrev(prev)
	-- If the first chapter, there is no previous one
	if self.number == 1 then return {} end

	-- If a non-numeric `prev` is supplied, find the chapter based on that page name.
	if (prev and not tonumber(prev)) then
		return Chapter:new({ page = prev })
	end

	-- If a numeric `prev` is supplied, find the chapter based on that number.
	if (prev and tonumber(prev)) then
		return Chapter:new({ series = self.seriesName, number = tonumber(prev) })
	end

	-- Automatically find the previous chapter, by subtracting 1 from the current number
	if (self.number) then
		return Chapter:new({ series = self.seriesName, number = self.number - 1 })
	end

	return nil
end

-- Get the next chapter
-- @param next {string|number|nil}
--     The page name or chapter number of the next chapter
--     Leave blank to automatically find the next chapter
-- @return Chapter
function Chapter:getNext(next)
	-- If a non-numeric `next` is supplied, find the chapter based on that page name.
	if (next and not tonumber(next)) then
		return Chapter:new({ page = next })
	end

	-- If a numeric `next` is supplied, find the chapter based on that number.
	if (next and tonumber(next)) then
		return Chapter:new({ series = self.seriesName, number = tonumber(next) })
	end

	-- Find the next chapter, by adding 1 to the current number
	if (self.number) then
		return Chapter:new({ series = self.seriesName, number = self.number + 1 })
	end

	return nil
end

-- Function callable by templates for getting a chapter, formatted in various ways
-- @param frame
-- @return string
function Chapter.chapter(frame)
	-- Get the template parameters and format them
	local args = normalizeArgs(frame:getParent().args)

	-- Show error if there is a problem with the input.
	local errors = validateArgs(args)
	if (#errors > 0) then
		return renderErrors('chapter', errors)
	end

	-- Find the chapter data
	local c = Chapter:new(args)

	-- Show error if the chapter was not found.
	if (not c.pageName) then
		return renderErrors('chapter', { 'chapter not found' })
	end

	-- Return the output
	if (args.format and args.format == 'ref')    then return c:formatAsRef()    end
	if (args.format and args.format == 'number') then return c:formatAsNumber() end

	return c:formatAsName()
end

function renderErrors(templateName, errors)
	local output = '<span style="color: #d33;">Error rendering <code>{{' .. templateName .. '}}</code>'
		.. '<abbr title="' .. (table.concat(errors, ' ')) .. '">🛈</abbr>'
		.. '</span>'

	if (mw.title.getCurrentTitle().nsText ~= 'Template') then
 		output = output ..'[[Category:Pages with validation errors]]'
	end

	return output
end

-- Function callable by templates for getting a chapter and rendering the
-- wikitext for an images category for the chapter
--
-- @param frame
-- @return string
function Chapter.imageCategory(frame)
	-- Get the template parameters and normarize them
	local args = normalizeArgs(frame:getParent().args)

	-- Look up the current, previous and next chapters
	local curr = Chapter:new(args)

	if (not curr or not curr.pageName) then
		return '<div style="color: #d33;">Could not find chapter</div>' ..
			'[[Category:Pages with validation errors]]'
	end

	local prev = curr:getPrev(args.prev)
	local next = curr:getNext(args.next)

	-- Intro text
	local output = 'This category is for images from ' .. curr:formatAsRef() .. '.'

	-- Navigation
	output = output .. '<div class="toccolours" style="clear: both; display: flex; margin-bottom: .5em;">'
	if (prev and prev.pageName) then
		local prevText = prev:formatImageCategoryNavLink()
		output = output .. '<div style="flex: 1; text-align: left;">←' .. prevText .. '</div>'
	end
	if (next and next.pageName) then
		local nextText = next:formatImageCategoryNavLink()
		output = output .. '<div style="flex: 1; text-align: right;">' .. nextText .. '→</div>'
	end
	output = output .. '</div>'

	-- Category
	output = output .. '[[Category:' .. curr.seriesName .. ' images by chapter|' .. (curr.number or curr.chapterWord) ..  ']]'

	return output
end

return Chapter