Module:Franchise

local p = {} local h = {}

local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable")

local cache = mw.loadData("Module:Franchise/Cache")

local CATEGORY_INVALID_ARGS = "Category:"..require("Module:Constants/category/invalidArgs")

-- Many templates need "Series" as if it were a game. Since it does not fit into the data model of Data:Franchise, it is manually defined here. local series = { article = "The Legend of Zelda (Series)", shortName = "The Legend of Zelda Series", logo = "File:Zelda Logo TP.png", link = "The Legend of Zelda series", display = "The Legend of Zelda series", canonicity = "canon", code = "Series", }

function p.Article(frame) return h.templateQuery("article", frame) end function p.BaseGame(frame) return h.templateQuery("baseGame", frame) end function p.Display(frame) return h.templateQuery("display", frame) end function p.Link(frame) return h.templateQuery("link", frame) end function p.ShortName(frame) return h.templateQuery("shortName", frame) end function h.templateQuery(fn, frame) local args = frame.args local game, nowarn = args[1], args.nowarn if game == nil or game == "" then return "", h.error("No game provided", CATEGORY_INVALID_ARGS, nowarn) end game = utilsString.trim(game) local value = p[fn](game) if not value then return "", h.error(string.format("Invalid entry ", game), CATEGORY_INVALID_ARGS, nowarn, "See Data:Franchise for a list of valid entries.") end local correctGameCode = p.code(game) if game ~= correctGameCode then local utilsError = require("Module:UtilsError") utilsError.warn(string.format(" should be written as  ", game, correctGameCode)) return value, ""..CATEGORY_INVALID_ARGS.."" else return value end end function h.error(errorMsg, category, nowarn, additionalWarningInfo) local utilsError = require("Module:UtilsError") local warnMsg = not nowarn and additionalWarningInfo and (errorMsg.. ". ".. additionalWarningInfo) return utilsError.error(errorMsg, warnMsg)..""..category.."" end

-- Guidelines:Main function p.ListTitlesByCanonicity(frame) local utilsLayout = require("Module:UtilsLayout")

local canonicity = frame.args[1] local rows = {} for i, code in ipairs(p.enum) do		if canonicity == p.canonicity(code) then local link = p.link(code) local releaseDate = p.releaseDate(code) table.insert(rows, {link, releaseDate}) end end local wikitable = utilsLayout.table({		sortable = true,		headers = {"Media", "Release Date"},		rows = rows,	}) return wikitable end function p.ListAllTitles(frame) local utilsLayout = require("Module:UtilsLayout") local canonicityStatuses = { ["canon"] = "Canon", ["non-canon"] = "Non-canon", ["ambiguous"] = "Ambiguously-canon", }	local rows = {} for i, code in ipairs(p.enum) do		local link = p.link(code) local releaseDate = p.releaseDate(code) local canonicity = p.canonicity(code) local canonicityStatus = canonicityStatuses[canonicity] local supersededBy = h.get(code, "supersededBy") if canonicityStatus and supersededBy ~= nil and supersededBy ~= "" then local supersederLink = p.link(supersededBy) canonicityStatus = canonicityStatus .. string.format(" (superseded by %s) ", supersederLink) end table.insert(rows, {link, code, releaseDate, canonicityStatus}) end local rows = utilsTable.sortBy(rows, 3) -- sort by release date local wikitable = utilsLayout.table({		sortable = true,		headers = {"Media", "Abbreviation", "Release Date", "Status"},		rows = rows,	}) return wikitable end

-- Template:Franchise/Store * function p.AddToPreview(frame) -- Performance optimization local utilsArg = require("Module:UtilsArg") local utilsVar = require("Module:UtilsVar") local utilsMarkup = require("Module:UtilsMarkup") local templateData = mw.loadData("Module:Franchise/TemplateData") local orderCounter = utilsVar.counter("canonOrder") local entryType = frame.args[1] local args, err = utilsArg.parse(frame:getParent.args, templateData["Franchise/Store " .. entryType]) if err then return utilsMarkup.categories(err.categories) end args = utilsTable.merge({}, args, {		entryType = entryType,		link = p.deriveLink(entryType, args),		display = p.deriveDisplay(entryType, args),	}) if entryType == "Game" or entryType == "Book" or entryType == "TV" then args.canonOrder = orderCounter.value else args.canonOrder = "—" end if entryType == "Book" then args.phraseLink = p.derivePhraseLink(args) end utilsVar.add("rows", args) end function p.StoreOrder(frame) -- Performance optimization local utilsVar = require("Module:UtilsVar") local orderCounter = utilsVar.counter("canonOrder") return orderCounter.increment end function p.StoreLink(frame) return p.deriveLink(frame.args[1], frame:getParent.args) end function p.StoreDisplay(frame) return p.deriveDisplay(frame.args[1], frame:getParent.args) end function p.StorePhraseLink(frame) return p.derivePhraseLink(frame:getParent.args) end function p.deriveDisplay(entryType, args) if args.display ~= nil and args.display ~= "" then return args.display elseif entryType == "Book" then return h.deriveBookFields(args).display else return ("%s"):format(args.shortName) end end function p.deriveLink(entryType, args) if args.display ~= nil and args.display ~= "" then return args.link elseif entryType == "Book" then return h.deriveBookFields(args).link else return ("%s"):format(args.article, args.shortName) end end function p.derivePhraseLink(args) return h.deriveBookFields(args).phraseLink end

function p.Preview(frame) -- Performance optimization local utilsMarkup = require("Module:UtilsMarkup") local utilsLayout = require("Module:UtilsLayout") local utilsVar = require("Module:UtilsVar") local previewColumns = { common = {"canonOrder", "code", "link", "display", "logo", "releaseDate", "canonicity"}, Game = {"type", "graphics", "family", "remakeOf", "supersededBy"}, Book = {"type", "phraseLink", "publisher", "authors", "basedOn"}, Nonfiction = {"publisher", "titles"}, TV = {"type"}, Group = {"games"}, }	previewColumns.Game = utilsTable.concat(previewColumns.common, previewColumns.Game) previewColumns.Book = utilsTable.concat(previewColumns.common, previewColumns.Book) previewColumns.TV = utilsTable.concat(previewColumns.common, previewColumns.TV) previewColumns.Nonfiction = utilsTable.concat(previewColumns.common, previewColumns.Nonfiction) previewColumns.Group = utilsTable.concat(previewColumns.common, previewColumns.Group) local rows = utilsVar.get("rows") for _, row in ipairs(rows) do		row.logo = utilsMarkup.link(row.logo) end local rowGroups = utilsTable.groupBy(rows, "entryType") local titles = utilsLayout.table({		sortable = true,		headers = previewColumns.common,		rows = utilsTable.map(rows, utilsTable._toArray(previewColumns.common, ""))	}) local games = utilsLayout.table({		sortable = true,		headers = previewColumns.Game,		rows = utilsTable.map(rowGroups.Game, utilsTable._toArray(previewColumns.Game, ""))	}) local books = utilsLayout.table({		sortable = true,		headers = previewColumns.Book,		rows = utilsTable.map(rowGroups.Book, utilsTable._toArray(previewColumns.Book, ""))	}) local tv = utilsLayout.table({		sortable = true,		headers = previewColumns.TV,		rows = utilsTable.map(rowGroups.TV, utilsTable._toArray(previewColumns.TV, ""))	}) local nonfiction = utilsLayout.table({		sortable = true,		headers = previewColumns.Nonfiction,		rows = utilsTable.map(rowGroups.Nonfiction or {}, utilsTable._toArray(previewColumns.Nonfiction, ""))	}) local groups = utilsLayout.table({		sortable = true,		headers = previewColumns.Group,		rows = utilsTable.map(rowGroups.Group or {}, utilsTable._toArray(previewColumns.Group, ""))	}) local preview = utilsLayout.tabs({		{			label = "All Titles",			content = titles,		},		{			label = "Games",			content = games,		},		{			label = "Books (fiction)",			content = books,		},		{			label = "Books (nonfiction)",			content = nonfiction		},		{			label = "Movies and TV Shows",			content = tv,		},		{			label = "Groups",			content = groups,		}	}, { columns = 15 }) return preview end

function p.UploadField(frame) -- Performance optimization local utilsMarkup = require("Module:UtilsMarkup") local utilsVar = require("Module:UtilsVar") local rows = utilsVar.get("rows") local groups = utilsTable.groupBy(rows, "entryType") local mainGames, otherGames = utilsTable.partition(groups["Game"], {		canonicity = "canon"	}) local gamesByBaseGame = utilsTable.groupBy(mainGames, function(game)		return p.baseGame(game.code)	end) local books = groups["Book"] local tvShows = groups["TV"] local sortedMainGames = {} for _, mainGame in ipairs(utilsTable.reverse(mainGames)) do		for _, game in ipairs(gamesByBaseGame[mainGame.code] or {}) do			table.insert(sortedMainGames, game) end end local result = "" result = result .. "**|None\n" result = result .. "**Series|The Legend of Zelda Series\n" result = h.append(result, "Main Series", sortedMainGames) result = h.append(result, "Other Games", otherGames) result = h.append(result, "Books, Comics, and Manga", books) result = h.append(result, "TV Shows", tvShows) return utilsMarkup.pre(result) end function h.append(result, title, entries) result = result .. "\n*"..title.."\n" for _, entry in ipairs(entries) do result = result .. string.format("**%s|%s\n", entry.code, entry.shortName) end return result end

function h.deriveBookFields(args) local ListPages = require("Module:List Pages") local utilsString = require("Module:UtilsString")

local subtitle, display, link, phraseLink local parens = string.find(args.shortName, "%s%([^)]+%)")	if parens then		subtitle = string.sub(args.shortName, 1, parens - 1)		local descriptor = string.sub(args.shortName, parens)		display = ("%s%s"):format(subtitle, descriptor)		link = ("%s"):format(args.article, display)		local authors = ListPages.main(utilsString.split(args.authors))		phraseLink = ("%s %s by %s"):format(args.article, subtitle, args.type, authors)	else		display = ("%s"):format(args.shortName)		link = ("%s"):format(args.article, args.shortName)		phraseLink = link	end	return {		display = display,		link = link,		phraseLink = phraseLink,	} end

-- QUERIES: ALL

function p.enum(options) if not options then return cache.enum end enum = utilsTable.clone(cache.enum) -- clone the read-only cache item so that we can modify it	if options.includeSeries then table.insert(enum, 1, "Series") end if options.includeNonfiction then local codes = utilsTable.map(cache.nonfiction, "code") enum = utilsTable.concat(codes, enum) end if options.includeGroups then -- insert "groups" so as to not disrupt the release order. This matters for Template:Media (e.g. the Fighter page, which uses SSB4) for _, group in ipairs(cache.groups) do			local i = 1 repeat i = i + 1 until i == #enum or p.isCanon(enum[i]) == p.isCanon(group.code) and p.releaseDate(enum[i]) and p.releaseDate(enum[i]) >= p.releaseDate(group.code) table.insert(enum, i, group.code) end end enum.reference = "Data:Franchise" return enum end

function p.article(code) return h.get(code, "article") end

function p.canonicity(code) return h.get(code, "canonicity") end

function p.canonOrder(code) local canonOrder = h.get(code, "canonOrder") return canonOrder and tonumber(canonOrder) end

function p.code(code) return h.get(code, "code") end

function p.display(code) return h.get(code, "display") end

function p.isCanon(code) return p.canonicity(code) == "canon" end

function p.link(code) return h.get(code, "link") end

function p.logo(code) return h.get(code, "logo") end

function p.releaseDate(code) return h.get(code, "releaseDate") end

function p.shortName(code) return h.get(code, "shortName") end

function p.type(code) return h.get(code, "type") end

-- QUERIES: GAMES

function p.enumGames(includeSeries) if includeSeries then local enum = utilsTable.concat({"Series"}, cache.enumGames) enum.reference = "Data:Franchise" return enum end return cache.enumGames end

function p.baseGame(code) local baseGames = h.get(code, "remakeOf") if baseGames == nil then -- game not found return nil elseif baseGames == "" then -- game has no remakes return code else return utilsString.split(baseGames)[1] end end

function p.family(code) return h.get(code, "family") end

function p.graphics(code) return h.get(code, "graphics") end

function p.hasRemakes(code) return utilsTable.hasKey(cache.remakes, string.lower(code)) end

function p.isRemake(code) local remakeOf = h.get(code, "remakeOf") return remakeOf ~= nil and remakeOf ~= "" end

function p.remakes(code) return utilsTable.clone(cache.remakes[string.lower(code)]) or {} end

-- QUERIES: BOOKS function p.publisher(code) return h.get(code, "publisher") end

function p.phraseLink(code) return h.get(code, "phraseLink") end

function h.get(code, prop) code = string.lower(code) if code == "series" then return series[prop] end local title = cache.titlesByCode[code] if not title then return nil -- we return nil here to indicate an invalid code else return title[prop] or "" -- we return "" here to indicate the absence of an optional property on a valid code end end

local nowarnParam = { desc = " If present, the template will not issue an editor warning when  is invalid. Use this when a template needs custom error handling (see examples).  However, an editor warning will still be issued if a game code is written with incorrect casing (e.g.   instead of  ) ", }

function p.Documentation return { sections = { {				heading = "Template functions", section = { Article = { frameParams = { [1] = {								name = "game", required = true, desc = "The code for a game or other entry in Data:Franchise.", },							nowarn = nowarnParam, },						cases = { resultOnly = true, {								args = {"OoT"}, },							{								args = {"OoT (Himekawa)"}, },							{								desc = "Error handling", args = {"oot"}, },							{								args = {"notAGame"}, },							{								args = {""}, },							{								input = "", },						},					},					BaseGame = { frameParams = { [1] = {								name = "game", required = true, desc = "The code for a game entry in Data:Franchise.", },							nowarn = nowarnParam, },						cases = { resultOnly = true, {								args = {"TWW"}, },							{								args = {"TWWHD"}, },							{								desc = "Error handling", args = {"notAGame"}, },							{								args = {"twwhd"}, },							{								args = {""}, },							{								input = "", },						},					},					Display = { frameParams = { [1] = {								name = "game", required = true, desc = "The code for a game or other Zelda-related title defined at Data:Franchise.", },							nowarn = nowarnParam, },						cases = { resultOnly = true, {								args = {"LA"}, },							{								args = {"LANS"}, },							{								args = {"LA (Cagiva)"}, },							{								args = {"invalid game"}, },							{								args = {""}, },						},					},					Link = { frameParams = { [1] = {								name = "game", required = true, desc = "The code for a game or other Zelda-related title defined at Data:Franchise.", },							nowarn = nowarnParam, },						cases = { resultOnly = true, {								args = {"OoT"}, },							{								args = {"OoT (Himekawa)"}, },							{								desc = "Error handling", args = {"oot"}, },							{								args = {"notAGame"}, },							{								args = {""}, },							{								input = "", },						},					},					ShortName = { frameParams = { [1] = {								name = "game", required = true, desc = "The code for a game or other Zelda-related title defined at Data:Franchise.", },							nowarn = nowarnParam, },						cases = { resultOnly = true, {								args = {"OoT"}, },							{								args = {"OoT (Himekawa)"}, },							{								args = {"E"}, },							{								desc = "Error handling", args = {"oot"}, },							{								args = {"notAGame"}, },							{								args = {""}, },							{								desc = "Custom error handling", input = "", },							{								input = "", },							{								input = "", },						}					}				},			},			{				heading = "Module functions - all media", section = { enum = { desc = "See also .", params = {"options"}, returns = "An array of all codes in canon order, plus a  key so that it can be used for documentation and validation.", cases = { outputOnly = true, {								snippet = 1, expect = {"TLoZ", "TAoL", "ALttP", "LA", "LADX", "LANS", "OoT"}, },							{								snippet = 2, expect = "Data:Franchise", },							{								snippet = "IncludeSeries", desc = "When  is true, then   is the first item in the enum.", expect = {"Series", "TLoZ", "TAoL"}, },							{								snippet = "IncludeNonfiction", desc = "When  is true, then books such as  are in the list.", expect = true, },							{								snippet = "IncludeGroups", desc = "When  is true, then collective terms such as  are included in the list.", expect = true, },						},					},					canonOrder = { params = {"code"}, returns = "Position of the franchise title in the Canon Order.", cases = { {								args = {"LA"}, expect = 4, },							{								args = {"LADX"}, expect = 5, },							{								args = {"Not a Game"}, expect = nil, },							{								desc = "Non-ficition books such as have no position in the canon order.", args = {"E"}, expect = nil, },						},					},					code = { params = {"code"}, returns = "The same code but correctly formatted, or  if no such code exists", cases = { outputOnly = true, {								args = {"alttp"}, expect = "ALttP", },							{								args = {"fakegame"}, expect = nil, },						},					},					shortName = { params = {"code"}, returns = "Short name for franchise title used in category names. Usually the subtitle.", cases = { {								args = {"LA"}, expect = "Link's Awakening", },							{								args = {"la"}, expect = "Link's Awakening" },							{								args = {"LANS"}, expect = "Link's Awakening (Nintendo Switch)", },							{								args = {"LA (Cagiva)"}, expect = "Link's Awakening (Cagiva)", },							{								args = {"E"}, expect = "Encyclopedia", },							{								args = {"ALttP&FS"}, expect = "A Link to the Past & Four Swords", },							{								args = {"Series"}, expect = "The Legend of Zelda Series" },							{								args = {"fakeGame"}, expect = nil, },						}					},					link = { params = {"code"}, returns = "Formatted link used in infoboxes and so on.", cases = { {								args = {"LA"}, expect = "Link's Awakening", },							{								args = {"la"}, expect = "Link's Awakening", },							{								args = {"LADX"}, expect = "Link's Awakening DX", },							{								args = {"LANS"}, expect = "Link's Awakening for Nintendo Switch", },							{								desc = "For books, comics and manga, see also .", args = {"LA (Cagiva)"}, expect = "Link's Awakening (Cagiva)", },							{								args = {"E"}, expect = "Encyclopedia", },							{								args = {"ALttP&FS"}, expect = "A Link to the Past & Four Swords" },							{								args = {"Series"}, expect = "The Legend of Zelda series" },							{								args = {"fakeGame"}, expect = nil, },						}					},					isCanon = { params = {"code"}, returns = "True if title is canon, else false.", cases = { {								args = {"LANS"}, expect = true, },							{								args = {"lans"}, expect = true, },							{								args = {"CoH"}, expect = false, },							{								args = {"SSBU"}, expect = false, },							{								args = {"E"}, expect = true, },							{								args = {"Series"}, expect = true, },						},					},					display = { params = {"code"}, returns = "Formatted text for the title.", cases = { {								args = {"LA"}, expect = "Link's Awakening", },							{								args = {"la"}, expect = "Link's Awakening", },							{								args = {"LANS"}, expect = "Link's Awakening for Nintendo Switch", },							{								args = {"E"}, expect = "Encyclopedia" },							{								args = {"ALttP&FS"}, expect = "A Link to the Past & Four Swords", },							{								args = {"Series"}, expect = "The Legend of Zelda series" },							{								args = {"fakeGame"}, expect = nil, },						}					},					logo = { params = {"code"}, returns = "Filename for the title's logo.", cases = { {								args = {"TWW"}, expect = "File:TWW English Logo.png", },							{								args = {"tww"}, expect = "File:TWW English Logo.png" },							{								args = {"E"}, expect = "File:The Legend of Zelda Encyclopedia Cover.png" },							{								args = {"Series"}, expect = "File:Zelda Logo TP.png" },							{								args = {"fakeGame"}, expect = nil }						}					},					article = { params = {"code"}, returns = "Wiki article name for the title", cases = { outputOnly = true, {								args = {"LA"}, expect = "The Legend of Zelda: Link's Awakening", },							{								args = {"la"}, expect = "The Legend of Zelda: Link's Awakening", },							{								args = {"LANS"}, expect = "The Legend of Zelda: Link's Awakening (Nintendo Switch)", },							{								args = {"TAoL"}, expect = "Zelda II: The Adventure of Link", },							{								args = {"TLoZ"}, expect = "The Legend of Zelda", },							{								args = {"Series"}, expect = "The Legend of Zelda (Series)", },							{								args = {"E"}, expect = "The Legend of Zelda: Encyclopedia" },							{								args = {"SSB4"}, expect = "Super Smash Bros. for Nintendo 3DS / Wii U", },						},					},					canonicity = { params = {"code"}, returns = "A string:,  , or  .", cases = { outputOnly = true, {								args = {"LA"}, expect = "canon", },							{								args = {"la"}, expect = "canon", },							{								args = {"CoH"}, expect = "ambiguous", },							{								args = {"LA (Cagiva)"}, expect = "non-canon", },							{								args = {"E"}, expect = "canon", },							{								args = {"fake"}, expect = nil, }						},					},					releaseDate = { params = {"code"}, returns = 'The "main" release date for a title.', cases = { outputOnly = true, {								args = {"LA"}, expect = "1993-08-06", },							{								args = {"LANS"}, expect = "2019-09-20", },							{								args = {"LA (Cagiva)"}, expect = "1994-05-01", },							{								args = {"E"}, expect = "2018-06-19", },							{								args = {"ALttP&FS"}, expect = "2002-12-02", },							{								args = {"Series"}, expect = nil },							{								args = {"fakeGame"}, expect = nil, },						},					},				},			},			{				heading = "Module functions - games", section = { enumGames = { params = {"includeSeries"}, returns = "An array of all game codes in canon order, plus a  key so that it can be used for documentation and validation.", cases = { outputOnly = true, {								snippet = "1", expect = {"TLoZ", "TAoL", "ALttP"}, },							{								snippet = "2", expect = "Data:Franchise", },							{								snippet = "IncludeSeries", desc = "When  is true, then   is the first item in the enum.", expect = {"Series", "TLoZ", "TAoL"}, },						},					},					baseGame = { params = {"code"}, returns = "If  is a remake, returns the code for the original game, else returns   as-is.", cases = { outputOnly = true, {								args = {"LANS"}, expect = "LA", },							{								args = {"LA"}, expect = "LA", },							{								args = {"fake"}, expect = nil, },						},					},					family = { params = {"code"}, returns = "A grouping name used for certain non-canon games on the Main Page.", cases = { outputOnly = true, {								args = {"OoT"}, expect = "", },							{								args = {"LCT"}, expect = "", },							{								args = {"FPTRR"}, expect = "Tingle", },							{								args = {"HWDE"}, expect = "Hyrule Warriors", },						},					},					graphics = { params = {"code"}, returns = "A string:  or  .", cases = { outputOnly = true, {								args = {"LA"}, expect = "2D", },							{								args = {"la"}, expect = "2D", },							{								args = {"LANS"}, expect = "3D", },							{								args = {"fake"}, expect = nil, },						},					},					hasRemakes = { params = {"code"}, returns = "True if game has at least one remake, remaster, or enhanced port. Else false.", cases = { {								args = {"LA"}, expect = true, },							{								args = {"LADX"}, expect = true, },							{								args = {"ST"}, expect = false },							{								args = {"fakeGame"}, expect = false, },						},					},					isRemake = { params = {"code"}, returns = "True if game is a remake, remaster, or enhanced port. Else false.", cases = { {								args = {"LANS"}, expect = true, },							{								args = {"LADX"}, expect = true, },							{								args = {"LA"}, expect = false, },							{								args = {"HWDE"}, expect = true, }						},					},					remakes = { params = {"code"}, returns = "List of remakes for a specific game, or a table of all remakes if no game specified", cases = { {								args = {"LA"}, expect = {"LADX", "LANS"}, },							{								args = {"LADX"}, expect = {"LANS"}, },							{								args = {"ST"}, expect = {} },							{								args = {"fake"}, expect = {}, },						},					},					type = { params = {"code"}, returns = 'One of:,  ,  ,   (other games)', cases = { {								args = {"BotW"}, expect = "main", },							{								args = {"HWAoC"}, expect = "spin-off", },							{								args = {"SSBU"}, expect = "", },						},					},				},			},			{				heading = "Module functions - books", section = { publisher = { params = {"code"}, returns = "The book's publisher - in North America if available.", cases = { {								args = {"OoT (Himekawa)"}, expect = "VIZ Media", },							{								args = {"E"}, expect = "Dark Horse Books", },						},					},					phraseLink = { params = {"code"}, returns = "Formatted link to page and authors.", cases = { {								args = {"TLoZ (Ran)"}, expect = "The Legend of Zelda manga by Maru Ran", },							{								args = {"tloz (ran)"}, expect = "The Legend of Zelda manga by Maru Ran", },							{								args = {"AOV"}, expect = "The Legend of Zelda: An Original Version", },							{								args = {"fake"}, expect = nil, },							{								desc = "Nonfiction books such as are not supported by this function.", args = {"E"}, expect = "", },						},					},				},			},		},	} end

return p