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", Comic = "Comics", },	fields = { common = { canonOrder = "Integer", code = "String (size=255;unique)", --Cargo bug: without size=255, the #cargo_declare fails page = "Page", shortName = "String", firstRelease = "Date", canonicity = "String", type = "String", display = "Wikitext", link = "Wikitext", },		Game = { graphics = "String", family = "String", remakeOf = "String", supersededBy = "String", },		Comic = { phraseLink = "Wikitext", authors = "List of String", basedOn = "String", },	} } CARGO.fields.Game = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Game) CARGO.fields.Comic = utilsTable.merge({}, CARGO.fields.common, CARGO.fields.Comic)

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

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

local doc = { common = { code = "Unique identifier for the title.", page = "The wiki page for the title.", shortName = "The short name for the title used in categories such as Category:Items in Link's Awakening (Nintendo Switch). Usually the title's subtitle.", firstRelease = "The date of the title's first release.", canonicity = "The title's canonicity.", },	Game = { type = "One of:,  ,  , or nothing for any other game. The first three types are show on the Main Page.", link = "Wikitext for how the game is to be linked in articles. If left empty, it is derived from  and  .", display = "Wikitext for how the game is to be linked in articles. If left empty, it is derived from .", remakeOf = "The game that this title is a remake of, if any.", supersededBy = "The name of the game that supersedes this one in terms of canon." },	Comic = { type = "One of:,  .", authors = "Comma-separated list of authors and illustrators", basedOn = "Game that the comic is based on.", } }

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 comics = utilsLayout.table({		sortable = true,		headers = FIELDS.Comic,		rows = utilsTable.map(rowGroups.Comic, utilsTable._toArray(FIELDS.Comic, ""))	}) local preview = utilsLayout.tabs({		{			label = "All Titles",			content = titles,		},		{			label = "Games",			content = games,		},		{			label = "Comics",			content = comics,		},	}) return preview end

function h.derive(args, type) local derived = {} if type == "Game" then derived = h.deriveGame(args) elseif type == "Comic" then derived = h.deriveComic(args) end return utilsTable.merge({}, args, derived, {		canonOrder = orderCounter.increment	}) end

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

function h.deriveComic(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: COMICS

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", "firstRelease", "canonicity", "type", "link", "display", "remakeOf", "supersededBy"}, params = { code = { required = true, type = "string", desc = "A string that uniquely identifies the game. Usually an initialism.", },			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", },			firstRelease = { type = "date", desc = "The date of the game's first release in YYYY-MM-DD format. Leave blank for future games.", },			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 for how the game is to be linked 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 for how the game is to be mentioned in articles when not being linked. By default, this is derived from . Should be left blank unless the game's display text is a special case (see examples).", },			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", firstRelease = "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)", firstRelease = "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", firstRelease = "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", firstRelease = "2017-11-17", canonicity = "non-canon", type = "", link = "", display = "", graphics = "3D", family = "", remakeOf = "", supersededBy = "", },		},	} }

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 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 = "Comics and Manga", 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