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_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. In infoboxes, 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

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, ", %l") then h.warn(MSG_COMMA_ESCAPE) categories = categories..CATEGORY_INVALID_ARGS end if string.find(listItems, "") else 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 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 h.warn("Template:List needn't be used within Template:Infobox Game Blocks as the latter also supports comma-separation. The surrounding  markers can be removed.") 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 = "Used by infobox templates to turn comma-separated input into lists.", 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