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 = "Games2", Book = "Books", },	fields = { common = { canonOrder = "Integer", code = "String (size=255;unique)", --Cargo bug: without size=255, the #cargo_declare fails 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", },	} } CARGO.fields.Game = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Game) CARGO.fields.Book = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Book)

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

local validators = { common = { code = { nonEmpty = true }, page = { nonEmpty = true }, shortName = { nonEmpty = true }, releaseDate = {}, canonicity = { required = true, enum = {"canon", "ambiguous", "non-canon"} },		link = {}, display = {} },	Game = { type = { enum = {"main", "remake", "spin-off"}, },		graphics = { enum = {"2D", "3D"}, },	},	Book = { type = { enum = {"book", "comic", "manga"} },		authors = { nonEmpty = true }, basedOn = { --TODO: enum = p.gameEnum, },	} } validators.Game = utilsTable.merge({}, validators.common, validators.Game) validators.Book = utilsTable.merge({}, validators.common, validators.Book)

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 = frame:getParent.args args = utilsTable.mapValues(args, utilsString.nilIfEmpty) args = h.derive(args, entryType) local err = utilsArg.validate(args, validators[entryType]) if err then return utilsMarkup.categories(err) end utilsVar.add("rows", utilsTable.merge(args, { entryType = entryType }))	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 = FIELDS.common,		rows = utilsTable.map(rows, utilsTable._toArray(FIELDS.common, ""))	}) local games = utilsLayout.table({		sortable = true,		headers = FIELDS.Game,		rows = utilsTable.map(rowGroups.Game, utilsTable._toArray(FIELDS.Game, ""))	}) local books = utilsLayout.table({		sortable = true,		headers = FIELDS.Book,		rows = utilsTable.map(rowGroups.Book, utilsTable._toArray(FIELDS.Book, ""))	}) local preview = utilsLayout.tabs({		{			label = "All Titles",			content = titles,		},		{			label = "Games",			content = games,		},		{			label = "Books",			content = books,		},	}) 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) end return utilsTable.merge({}, args, derived, {		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)		phraseLink = ("%s %s"):format(args.page, subtitle, args.type)	else		display = ("%s"):format(args.shortName)		link = ("%s"):format(args.page, args.shortName)		phraseLink = link	end	phraseLink = phraseLink .. " by " .. ListPages.main(utilsString.split(args.authors))	return {		display = display,		link = link,		phraseLink = phraseLink,	} end

-- QUERIES: ALL

function p.enum 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.type(code) return h.get(code, "type") end

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

-- QUERIES: GAMES

function p.getGames(options) local options = options or {} local filter = options.filter local groupBy = options.groupBy -- TODO 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.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", boilerplate = { commentBefore = true },		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.", },			page = { required = true, type = "wiki-page-name", desc = "The wiki page for the game.", },			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", },			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)." },			canonicity = { required = true, type = "string", enum = {"canon", "ambiguous", "non-canon"}, desc = "The title's canon status.", },			type = { type = "string", enum = {"main", "remake", "spin-off"}, desc = "Leave blank for cross-overs and cameos. Used to group games on the Main Page.", },			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).", },			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).", },			graphics = { type = "string", enum = {"2D", "3D"}, desc = "Indicates whether the game is 2D or 3D. Used for image handling." -- TODO: what image handling? },			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", },			remakeOf = { enum = p.enum, type = "string", desc = "The code of the game that this game is a remake of, if any.", },			supersededBy = { enum = p.enum, type = "string", desc = "The name of the game that supersedes this one in terms of canon.", },		},		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", boilerplate = { commentBefore = true },		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.", },			page = { required = true, type = "wiki-page-name", desc = "The wiki page for the book, comic, or manga.", },			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.", },			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).", },			canonicity = { required = true, type = "string", enum = {"canon", "ambiguous", "non-canon", "supplementary"}, desc = "The title's canon status.", },			type = { type = "string", enum = {"book", "comic", "manga"}, desc = "Identifies the type of book." },			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.", },			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.", },			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.", },			authors = { type = "string", desc = "Comma-separated list of the book's authors.", },			illustrators = { type = "string", desc = "Comma-separted list of the book's illustrators.", },			basedOn = { type = "string", enum = p.enum, desc = "The game that this book is based on.", },		},	}, }

p.Documentation = { sections = { {			heading = "All Media", section = { enum = { params = {}, returns = "An array of all codes in canon order, plus a  key so that it can be used for utilsArg.enum.", cases = { outputOnly = true, {							snippet = 1, expect = {"TLoZ", "TAoL", "ALttP", "ALttP&FS", "LA", "LADX", "LANS", "OoT", "OoTMQ", "OoT3D"}, },						{							snippet = 2, expect = "Data:Franchise", },					},				},				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 = {"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, },					}				},				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 = { family = { params = {"code"}, returns = "A grouping name used for certain non-canon games on the Main Page.", cases = { outputOnly = true, {							args = {"OoT"}, expect = nil, },						{							args = {"LCT"}, expect = nil, },						{							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 by Mitsunori Kitadono and Ikuo Miyazoe", },						{							args = {"fake"}, expect = nil, },					},				},			},		},	}, }

return p