Module:Infobox

local p = {} local h = {}

local File = require("Module:File") local Franchise = require("Module:Franchise") local Term = require("Module:Term") local utilsArg = require("Module:UtilsArg") local utilsMarkup = require("Module:UtilsMarkup") local utilsPackage = require("Module:UtilsPackage") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable") local _utilsError = utilsPackage.lazyLoad("Module:UtilsError") local utilsVar = require("Module:UtilsVar")

local CATEGORY_INVALID_ARGS = "" local CATEGORY_PARAM_CAPTION = "" local CATEGORY_PARAM_NAME = "" local CATEGORY_BR_TAGS = "" local CATEGORY_BR_TAGS_GAME = "" local CATEGORY_EXP_GAME = "" local CLASS_TOOLTIP = require("Module:Constants/class/tooltip") local DEFAULT_IMG_SIZE = "320x320px" local MSG_BR_TAGS = "Using  tags to create lists is discouraged. See Category:Infoboxes using br tags for more information." local MSG_COMMA_ESCAPE = "Lowercase character detected following comma. Within, commas are used to separate list items. All list items should be in sentence case. Literal commas can be escaped using ." local MSG_EXP_GAME = "One or more infobox fields are using when they should be using . See Category:Infoboxes using exp game for more information" local VAR_IS_AFTER_INFOBOX = require("Module:Constants/var/isAfterInfobox")

function p.Main(frame) local templateName = "Infobox "..frame:getParent:getTitle local args, err, categories local templateSpec = p.Templates[templateName] if templateSpec then args, err = utilsArg.parse(frame:getParent.args, templateSpec) else args = frame:getParent.args end categories = err and err.categoryText or "" if args.name and args.name ~= "" then categories = categories..CATEGORY_PARAM_NAME end if args.caption and args.caption ~= "" then categories = categories..CATEGORY_PARAM_CAPTION end local styles = frame:extensionTag({		name = "templatestyles",		args = { src = "Module:Infobox/Styles.css" }	})

utilsVar.set(VAR_IS_AFTER_INFOBOX, true) return styles, categories end

function p.Image(frame) local file = frame.args[1] local caption = frame.args[2] if file == nil or file == "" then return nil elseif not utilsString.startsWith(file, "File:") then return file else local image, exists = File.image(file, {			size = frame.args.size or DEFAULT_IMG_SIZE, -- unclear whether we should even support custom sizing or force them all to 320x320px.			scale = 10,		}) if exists then -- Set this image as the article's representative image for things like page previews mw.ext.seo.set({				image = file			}) end if caption and caption ~= "" then local html = mw.html.create("div") :addClass("infobox__image-caption") :wikitext(caption) image = image .. tostring(html) end return image end end

-- Deprecated - to be removed once is cleared function p.List(frame) local categories = "" listItems = frame.args[1] listItems = listItems and utilsString.trim(listItems) if listItems == nil or listItems == "" then return nil end if string.find(listItems, "plainlist") then return listItems -- input is already a list end

if string.find(listItems, "") else if string.find(listItems, ",[^%d]") then categories = categories.."" end listItems = utilsString.split(listItems, '%s*,%f[^,%d]%s*') -- %f[^,%d] is so we don't split numbers on their thousands separator (e.g., 1,500) end local listItemsUsingExpGame = utilsTable.filter(listItems, function(listItem)		return string.find(listItem, "exp%-game")	end) if #listItems > 1 and #listItemsUsingExpGame == #listItems then h.warn(MSG_EXP_GAME) categories = categories..CATEGORY_EXP_GAME end if #listItems == 1 then return listItems[1], categories else return utilsMarkup.list(listItems), categories end end

function p.Games(frame) local games = frame.args[1] local categories = ""

games = games and utilsString.trim(games) if games == nil or games == "" then return nil end

if string.find(games, "<br") then h.warn(MSG_BR_TAGS) categories = categories..CATEGORY_BR_TAGS..CATEGORY_BR_TAGS_GAME games = utilsString.split(games, " ") games = utilsTable.flatMap(games, utilsString._split(" ")) else games = utilsString.split(games) end

local gameLinks = utilsTable.map(games, p.link) local gameLinks = utilsTable.compact(gameLinks) if #gameLinks ~= #games then categories = categories..CATEGORY_INVALID_ARGS end if #gameLinks == 1 then return gameLinks[1], categories else local gameList = utilsMarkup.list(gameLinks) return gameList, categories end end function p.link(game) if utilsMarkup.containsLink(game) then return game end local link = Franchise.link(game) local properCode = Franchise.code(game) if not link then h.warn(string.format("Invalid entry . See Data:Franchise for a list of valid entries.", game)) return nil elseif properCode and properCode ~= game then h.warn(string.format(" should be written as  ", game, properCode)) end return link end

function p.GameBlocks(frame) local args = frame:getParent.args local blocks, categories = h.parseBlocks(args) local html = mw.html.create("ul"):addClass("infobox-game-blocks") for i, block in ipairs(blocks) do		local gameText = html :tag("li") :addClass("infobox-game-blocks__game") :tag("span") :addClass("infobox-game-blocks__game-text") local game = gameText:done if blocks.compact then gameText:wikitext(Franchise.display(block.game)) local gameList = game :tag("ul") :addClass("infobox-game-blocks__game-list") for j, listItem in ipairs(block.listItems) do				gameList :tag("li") :addClass("infobox-game-blocks__game-list-item") :wikitext(listItem) end else html:addClass("infobox-game-blocks--compact") gameText :tag("span") :addClass(CLASS_TOOLTIP) :attr("title", Franchise.shortName(block.game)) :wikitext(block.game) :done :wikitext(": ") game:wikitext(block.listItems[1]) end end

return tostring(html), categories end function h.parseBlocks(args) local categories = ""

local seenParams = {} local blocks = {} blocks.compact = false for i, game in ipairs(Franchise.enum({ includeSeries = true })) do		seenParams[game] = true local listItems = args[game] listItems = listItems and utilsString.trim(listItems) listItems = listItems and utilsString.nilIfEmpty(listItems) if listItems and string.find(listItems, ", %l") then h.warn(MSG_COMMA_ESCAPE) categories = categories..CATEGORY_INVALID_ARGS end if listItems and string.find(listItems, "<br") then h.warn(MSG_BR_TAGS) categories = categories..CATEGORY_BR_TAGS listItems = utilsString.split(listItems, " ") listItems = utilsTable.flatMap(listItems, utilsString._split(" ")) elseif listItems then listItems = utilsString.split(listItems, '%s*,%f[^,%d]%s*') -- %f[^,%d] is so we don't split numbers on their thousands separator (e.g., 1,500) end if listItems then table.insert(blocks, {				game = game, 				listItems = listItems,			}) end local hasDiv = listItems and listItems[1] and string.find(listItems[1], " 1 or hasDiv then blocks.compact = true end if listItems and listItems[1] and string.find(listItems[1], "plainlist") then blocks.compact = true end end

for k, v in pairs(args) do		if not seenParams[k] then local errorMessage = string.format("Invalid game ", k)			h.warn(errorMessage) categories = categories..CATEGORY_INVALID_ARGS end end return blocks, categories end

function p.Title(frame) local subpageName = mw.title.getCurrentTitle.subpageText local term = Term.fetchTerm(subpageName, "Series") return term or utilsString.stripTrailingParentheses(subpageName) end

function h.warn(msg) _utilsError.warn(msg, {		includeInstance = false,	}) end

local templateSpec = function(args) local singular = args.singular local plural = args.plural local params = args.params local productionParams = args.productionParams local imageSuchAs = args.imageSuchAs

local spec = { format = "block", purpose = string.format("Infobox for %s.", plural, string.lower(plural)), categories = {"Infobox Templates"}, boilerplate = { separateRequiredParams = false, },		paramOrder = {"name", "image", "caption"}, params = { name = { desc = " Name to use in the infobox header. Defaults to . In general, this parameter should be omitted unless the title requires italics. ", type = "content", },			image = { desc = string.format("An image to represent the %s, such as %s.", singular, imageSuchAs), type = "wiki-file-name", },			caption = { desc = "A caption for the image.", type = "content", }		},	}	for i, param in ipairs(params or {}) do		spec.params[param.name] = param table.insert(spec.paramOrder, param.name) end for i, param in ipairs(productionParams or {}) do		spec.params[param.name] = param table.insert(spec.paramOrder, param.name) end return spec end

local released = { name = "released", desc = "Release date(s) of the film. Use Template:Release.", type = "content", }

p.Templates = { ["Infobox Game Blocks"] = {}, ["Infobox Film"] = templateSpec({		singular = "film",		plural = "films",		imageSuchAs = "a release poster or a DVD cover",		params = {			{				name = "director",				desc = "The director(s) of the film.",				type = "content",			},			{				name = "producer",				desc = "The producer(s) of the film.",				type = "content",			},			{				name = "country",				desc = "Country or countries of production.",				},			released,		}	}), ["Infobox Television"] = templateSpec({		singular = "television",		plural = "television",		imageSuchAs = "the series' logo or title card",		params = {			{				name = "basedOn",				type = "string",				desc = "Comma separated list of games that the series is based on.",				enum = Franchise.enum,				trim = true,				split = true,			},			{				name = "seasons",				type = "content",				desc = "Number of seasons.",			},			{				name = "episodes",				type = "content",				desc = "Number of total episodes.",			},			{				name = "company",				type = "content",				desc = "Production comapany or companies.",			},			{				name = "distributor",				type = "content",				desc = "Distributor(s) of the television series.",			},			released,		}	}) }

function p.Documentation return { Games = { desc = "Used by infobox templates to turn comma-separated game codes into lists of games.", frameParams = { [1] = {					name = "param", desc = "The infobox parameter, usually  or  .", },			},			cases = { {					args = {"TLoZ, TAoL, ALttP"}, },				{					args = {"TLoZ (Ran), BoMC, TLoZ (Susumu)"}, },				{					args = {""}, },				{					args = {}, },				{					args = {"invalid game"}, },				{					args = {"OoT, invalid game, TP"}, },				{					desc = "br tags are discouraged due to the poor HTML semantics.", args = {" "}, },			}		},		Image = { desc = "Used by infobox templates to generate an image when Template:Media is not used.", frameParams = { [1] = {					name = "file", desc = "The image parameter - a file name.", },				[2] = {					name = "caption", desc = "The caption parameter.", },				size = { desc = "Image size in pixels. Sprites are scaled to a maximum of 10 times their original size.", default = DEFAULT_IMG_SIZE, },			},			cases = { {					args = {"File:TWW Great Fairy Figurine Model.png"}, },				{					args = {"File:TWW Great Fairy Figurine Model.png", "Great Fairy Figurine", size = "250px"} },				{					desc = "Sprites are scaled to a maximum of 10 times their original size.", args = {"File:ALttP Apple Sprite.png"}, },				{					desc = "Template:Media output is rendered as-is", args = {""} },				{					desc = "Anything other than a file name starting with  is rendered as-is", args = {"NaN Cyber Pico Blooms are never seen in-game"}, },			},		},		List = { desc = "This function is being deprecated in favor of Template:List. See Category:Infoboxes with commas for more information.", frameParams = { [1] = {					name = "param", desc = "The infobox parameter", },			},			cases = { {					args = {"A, B, C"} },				{					desc = "Spaces between list elements are optional, but recommended for readability.", args = {"A,B,C"} },				{					desc = "Digit separators don't count.", args = {"1,500 Rupees"}, },				{					desc = " can be used to escape commas when an item itself contains a comma.", args = {"The Way of Sumo, Part I, The Way of Sumo, Part II, The Way of Sumo, Part III"}, },				{					args = {"A"}, },				{					args = {""}, },				{					args = {}, },				{					desc = "br tags are discouraged due to the poor HTML semantics.", args = {"A B C"}, }			},		},	} end

return p