Module:Franchise

local p = {} local h = {}

local ListPages = require("Module:List Pages") local utilsArg = require("Module:UtilsArg") local utilsCargo = require("Module:UtilsCargo") local utilsLayout = require("Module:UtilsLayout") local utilsMarkup = require("Module:UtilsMarkup") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable") local utilsVar = require("Module:UtilsVar")

local cache = mw.loadData("Module:Franchise/Cache") local orderCounter = utilsVar.counter("canonOrder")

local CARGO = { tables = { Game = "Games3", Book = "Books", TV = "TelevisionShows", },	fields = { common = { canonOrder = "Integer", code = "String", page = "Page", shortName = "String", releaseDate = "Date", canonicity = "String", type = "String", display = "Wikitext", link = "Wikitext", },		Game = { graphics = "String", family = "String", remakeOf = "String", supersededBy = "String", },		Book = { phraseLink = "Wikitext", authors = "List of String", basedOn = "String", },		TV = {} } } CARGO.fields.Game = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Game) CARGO.fields.Book = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Book) CARGO.fields.TV = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.TV)

local DATA = { common = {"canonOrder", "code", "link", "display", "releaseDate", "canonicity", "type"}, Game = {"graphics", "family", "remakeOf", "supersededBy"}, Book = {"phraseLink", "authors", "basedOn"}, TV = {}, } DATA.Game = utilsTable.concat(DATA.common, DATA.Game) DATA.Book = utilsTable.concat(DATA.common, DATA.Book) DATA.TV = utilsTable.concat(DATA.common, DATA.TV)

function p.CargoDeclare(frame) local entryType = frame.args[1] return utilsCargo.declare(CARGO.tables[entryType], CARGO.fields[entryType]) end

function p.CargoStore(frame) local entryType = frame.args[1] local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Franchise/Store " .. entryType]) if err then return utilsMarkup.categories(err.categories) end args = h.derive(args, entryType) utilsVar.add("rows", args) return utilsCargo.store(CARGO.tables[entryType], args) end

function p.CargoPreview(frame) local rows = utilsVar.get("rows") local rowGroups = utilsTable.groupBy(rows, "entryType") local titles = utilsLayout.table({		sortable = true,		headers = DATA.common,		rows = utilsTable.map(rows, utilsTable._toArray(DATA.common, ""))	}) local games = utilsLayout.table({		sortable = true,		headers = DATA.Game,		rows = utilsTable.map(rowGroups.Game, utilsTable._toArray(DATA.Game, ""))	}) local books = utilsLayout.table({		sortable = true,		headers = DATA.Book,		rows = utilsTable.map(rowGroups.Book, utilsTable._toArray(DATA.Book, ""))	}) local tv = utilsLayout.table({		sortable = true,		headers = DATA.TV,		rows = utilsTable.map(rowGroups.TV, utilsTable._toArray(DATA.TV, ""))	}) local preview = utilsLayout.tabs({		{			label = "All Titles",			content = titles,		},		{			label = "Games",			content = games,		},		{			label = "Books",			content = books,		},		{			label = "TV Shows",			content = tv,		}	}) return preview end

function h.derive(args, type) local derived = {} if type == "Game" then derived = h.deriveGameFields(args) elseif type == "Book" then derived = h.deriveBookFields(args) elseif type == "TV" then derived = h.deriveGameFields(args) -- logic is the same for TV as it is for games end return utilsTable.merge({}, args, derived, {		entryType = type,		canonOrder = orderCounter.increment,	}) end

function h.deriveGameFields(args) return { link = args.link or ("%s"):format(args.page, args.shortName), display = args.display or ("%s"):format(args.shortName), } end

function h.deriveBookFields(args) 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.page, display)		local authors = ListPages.main(utilsString.split(args.authors))		phraseLink = ("%s %s by %s"):format(args.page, subtitle, args.type, authors)	else		display = ("%s"):format(args.shortName)		link = ("%s"):format(args.page, args.shortName)		phraseLink = link	end	return {		display = display,		link = link,		phraseLink = phraseLink,	} end

-- QUERIES: ALL

function p.enum(includeSeries) if includeSeries then local result = utilsTable.clone(cache.enum) table.insert(result, 1, "Series") return result end return cache.enum end

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

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

function p.display(code) if code == "Series" then return "The Legend of Zelda series" end return h.get(code, "display") end

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

function p.ShortName(frame) local args, err = utilsArg.parse(frame.args, {		params = {			[1] = {				name = "code",				enum = p.enum(true),			}		}	}) if err then return utilsMarkup.categories(err.categories) end return p.shortName(args.code) end function p.shortName(code) if code == "Series" then return "The Legend of Zelda Series" end return h.get(code, "shortName") end

-- QUERIES: GAMES

function p.enumGames(includeSeries) if includeSeries then return utilsTable.concat({"Series"}, cache.enumGames, "Series") end return cache.enumGames 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) return p.type(code) == "remake" end

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

-- QUERIES: BOOKS

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

function h.get(code, prop) local title = cache.titlesByCode[string.lower(code)] return title and title[prop] end

p.Schemas = { enum = { includeSeries = { type = "boolean", desc = "If true, then  is included at the very beginning of the list.", },	},	enumGames = { includeSeries = { type = "boolean", desc = "If true, then  is included at the very beginning of the list.", },	} }

p.Templates = { ["Franchise/Store Game"] = { purpose = "Each instance of this template describes an entry in The Legend of Zelda franchise (or a game related to the franchise).", storesData = "Data:Franchise", format = "block", indent = 1, boilerplate = { before = "", },		paramOrder = {"code", "page", "shortName", "releaseDate", "canonicity", "type", "link", "display", "graphics", "family", "remakeOf", "supersededBy"}, params = { code = { required = true, type = "string", desc = "A string that uniquely identifies the game. Usually an initialism of its subtitle.", trim = true, nilIfEmpty = true, },			page = { required = true, type = "wiki-page-name", desc = "The wiki page for the game.", trim = true, nilIfEmpty = true, },			shortName = { required = true, type = "string", desc = "The name for the game used in categories such as Category:Items in Link's Awakening (Nintendo Switch). Usually the game's subtitle, possibly with a parenthetical disambiguator.", trim = true, nilIfEmpty = true, },			releaseDate = { type = "date", desc = "The date of the game's initial release in North America, in YYYY-MM-DD format. Leave blank for future games. If not released in North America, use the earliest release date of any region (most likely Japan).", trim = true, nilIfEmpty = true, },			canonicity = { required = true, type = "string", enum = {"canon", "ambiguous", "non-canon"}, desc = "The title's canon status.", trim = true, nilIfEmpty = true, },			type = { type = "string", enum = {"main", "remake", "spin-off"}, desc = "Leave blank for cross-overs and cameos. Used to group games on the Main Page.", trim = true, nilIfEmpty = true, },			link = { type = "content", desc = "Wikitext used when linking the game in articles. By default, this is derived from  and  . Should be left blank unless the game's link is a special case (see examples).", trim = true, nilIfEmpty = true, },			display = { type = "content", desc = "Wikitext used when mentioning the game in articles, sans link. By default, this is derived from . Should be left blank unless the game's display text is a special case (see examples).", trim = true, nilIfEmpty = true, },			graphics = { type = "string", enum = {"2D", "3D", "4D"}, desc = 'Indicates whether the game is 2D or 3D. Used by Module:File for image handling, among other things. For live-action games, put down "4D".', -- TODO: more specific trim = true, nilIfEmpty = true, },			family = { type = "string", desc = "Identifies the game as being part of a sub-group in the franchise. Leave blank for canon games. This is used to group certain games on the Main Page", trim = true, nilIfEmpty = true, },			remakeOf = { --enum = p.enum, type = "string", desc = "The code of the game that this game is a remake of, if any.", trim = true, nilIfEmpty = true, },			supersededBy = { --enum = p.enum, type = "string", desc = "The name of the game that supersedes this one in terms of canon.", trim = true, nilIfEmpty = true, },		},		examples = { {				code = "LA", page = "The Legend of Zelda: Link's Awakening", shortName = "Link's Awakening", releaseDate = "1993-06-06", canonicity = "canon", type = "main", link = "", display = "", graphics = "2D", family = "", remakeOf = "", supersededBy = "LANS", },			{				code = "LANS", page = "The Legend of Zelda: Link's Awakening (Nintendo Switch)", shortName = "Link's Awakening (Nintendo Switch)", releaseDate = "2019-09-20", canonicity = "canon", type = "remake", link = "Link's Awakening for Nintendo Switch.", display = "Link's Awakening for Nintendo Switch", graphics = "3D", family = "", remakeOf = "LA", supersededBy = "", },			{				code = "FPTRR", page = "Freshly-Picked Tingle's Rosy Rupeeland", shortName = "Freshly-Picked Tingle's Rosy Rupeeland", releaseDate = "2006-09-02", canonicity = "ambiguous", type = "spin-off", link = "", display = "", graphics = "2D", family = "Tingle", remakeOf = "", supersededBy = "", },			{				code = "Skyrim", page = "The Elder Scrolls V: Skyrim", shortName = "Skyrim", releaseDate = "2017-11-17", canonicity = "non-canon", type = "", link = "", display = "", graphics = "3D", family = "", remakeOf = "", supersededBy = "", },		},	},	["Franchise/Store Book"] = { purpose = "Each instance of this template describes a The Legend of Zelda book, comic, or manga.", storesData = "Data:Franchise", format = "block", indent = 1, boilerplate = { before = "", },		paramOrder = {"code", "page", "shortName", "releaseDate", "canonicity", "type", "link", "display", "phraseLink", "authors", "illustrators", "basedOn"}, params = { code = { required = true, type = "string", desc = "A string that uniquely identifies the book. Usually an initialism of its subtitle.", trim = true, nilIfEmpty = true, },			page = { required = true, type = "wiki-page-name", desc = "The wiki page for the book, comic, or manga.", trim = true, nilIfEmpty = true, },			shortName = { required = true, type = "string", desc = "The name for the book used in categories such as Characters in Majora's Mask (Himekawa). Usually the book's subtitle, possibly with a parenthetical disambiguator of the primary author's last name.", trim = true, nilIfEmpty = true, },			releaseDate = { type = "date", desc = "The date of the book's initial release in North America, in YYYY-MM-DD format. Leave blank for unreleased books. If not released in North America, use the earliest release date of any region (most likely Japan).", trim = true, nilIfEmpty = true, },			canonicity = { required = true, type = "string", enum = {"canon", "ambiguous", "non-canon", "supplementary"}, desc = "The title's canon status.", trim = true, nilIfEmpty = true, },			type = { type = "string", enum = {"book", "comic", "manga"}, desc = "Identifies the type of book.", trim = true, nilIfEmpty = true, },			link = { type = "content", desc = "Wikitext used when linking the book in article infoboxes (use  for article content). By default, this is derived from   and  . Should be left blank unless the link is a special case.", trim = true, nilIfEmpty = true, },			display = { type = "content", desc = "Wikitext used when mentioning the book in articles, sans link. By default, this is derived from . Should be left blank unless the display text is a special case.", trim = true, nilIfEmpty = true, },			phraseLink = { type = "content", desc = "Wikitext to be used when linking to a book. By default, this is derived from,   and  . Should be left blank unless the link is a special case.", trim = true, nilIfEmpty = true, },			authors = { type = "string", desc = "Comma-separated list of the book's authors.", trim = true, nilIfEmpty = true, },			illustrators = { type = "string", desc = "Comma-separted list of the book's illustrators.", trim = true, nilIfEmpty = true, },			basedOn = { type = "string", --enum = p.enum, desc = "The game that this book is based on.", trim = true, nilIfEmpty = true, },		},	},	["Franchise/Store TV"] = { purpose = "Each instance of this template describes a The Legend of Zelda-related TV show.", storesData = "Data:Franchise", format = "block", indent = 1, boilerplate = { before = "", },		paramOrder = {"code", "page", "shortName", "releaseDate", "canonicity", "type", "link", "display"}, params = { code = { required = true, type = "string", desc = "A string that uniquely identifies the TV series. Usually an initialism of its title.", trim = true, nilIfEmpty = true, },			page = { required = true, type = "wiki-page-name", desc = "The wiki page for the TV series.", trim = true, nilIfEmpty = true, },			shortName = { required = true, type = "string", desc = "The name for the TV series used in categories such as Category:The Legend of Zelda TV Series Screenshots.", trim = true, nilIfEmpty = true, },			releaseDate = { type = "date", desc = "The date of the TV series' first air date in North America, in YYYY-MM-DD format. If not released in North America, use the earliest release date of any region (most likely Japan).", trim = true, nilIfEmpty = true, },			canonicity = { required = true, type = "string", enum = {"non-canon"}, desc = "The title's canon status.", trim = true, nilIfEmpty = true, },			type = { type = "string", enum = {"animated"}, desc = "Identifies the type of TV series .", trim = true, nilIfEmpty = true, },			link = { type = "content", desc = "Wikitext used when linking the TV series in articles. By default, this is derived from  and  . Should be left blank unless the link is a special case.", trim = true, nilIfEmpty = true, },			display = { type = "content", desc = "Wikitext used when mentioning the TV series in articles, sans link. By default, this is derived from . Should be left blank unless the display text is a special case.", trim = true, nilIfEmpty = true, },		}	} }

p.Documentation = { sections = { {			heading = "All Media", section = { enum = { desc = "See also .", params = {"includeSeries"}, 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", "OoT3D", "MM", "MM3D"}, },						{							snippet = 2, expect = "Data:Franchise", },						{							snippet = "IncludeSeries", desc = "When  is true, then   is the first item in the enum.", expect = {"Series", "TLoZ", "TAoL"}, }					},				},				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 = {"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 = {"fakeGame"}, expect = nil, },					}				},				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 = {"Series"}, expect = "The Legend of Zelda series" },						{							args = {"fakeGame"}, expect = nil, },					}				},				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 = {"fake"}, expect = nil, }					},				},			},		},		{			heading = "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 = {"TMC", "TP", "TPHD"}, },						{							snippet = "2", expect = "Data:Franchise", },						{							snippet = "IncludeSeries", desc = "When  is true, then   is the first item in the enum.", expect = {"Series", "TLoZ", "TAoL"}, },					},				},				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 = {"la"}, 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 = {"lans"}, expect = true, },						{							args = {"LA"}, expect = false, },					},				},				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 = {"la"}, expect = {"LADX", "LANS"}, },						{							args = {"ST"}, expect = {} },						{							args = {"fake"}, expect = {}, },					},				},			},		},		{			heading = "Books", section = { 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 = {"TLoZAOV"}, expect = "‟The Legend of Zelda„ An Original Version", },						{							args = {"fake"}, expect = nil, },					},				},			},		},	}, }

return p