Module:Cite

local p = {}

local Color = require("Module:Color") local Franchise = require("Module:Franchise") local Guide = require("Module:Guide") local Magazine = require("Module:Magazine") local Term = require("Module:Term") local utilsArg = require("Module:UtilsArg") local utilsError = require("Module:UtilsError") local utilsMarkup = require("Module:UtilsMarkup") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable")

local data = mw.loadData("Module:Cite/Data") p.Templates = mw.loadData("Module:Cite/TemplateData")

local CAT_BOOK_QUOTES = "" local CAT_INVALID_ARGS = ""

local function warn(msg) local utilsError = require("Module:UtilsError") utilsError.warn(msg) end

function p.Main(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates.Cite) local categories = err and err.categoryText or "" local gameLink = args.game and Franchise.link(args.game) local speakerDisplay = args.source or "" if gameLink and args.source ~= "N/A" then -- if gameLink is not null here, it means `game` should be a valid term context speakerDisplay = Term.printTerm(args.source, args.game, {			link = true,			plural = plural,		}) end

if args.game and not gameLink and not utilsMarkup.containsLink(args.game) then warn(utilsMarkup.code(mw.dumpObject(args.game)) .." is neither a valid code nor an interwiki link.") categories = CAT_INVALID_ARGS end if args.game and Franchise.hasRemakes(args.game) then categories = categories .. ""	end local gameDisplay = gameLink or utilsMarkup.italic(args.game) local sourceDisplay = table.concat({gameDisplay, args.version}, ", ") local quoteDisplay = args.quote and p.color(args.quote, args.source, args.game) local result = p.printCitation(sourceDisplay, quoteDisplay, speakerDisplay) return result, categories end

function p.Book(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Book"]) local categories = err and err.categoryText or "" if args.quote then categories = categories..CAT_BOOK_QUOTES end -- Backcompat for deprecated parameters if args.game and args.author then args.book = args.game .. " ("..args.author..")" end

local bookTitle = args.book if args.book and not p.hasItalics(args.book) then local bookLink = Franchise.link(args.book) if not bookLink then warn(string.format(" must be a code from Data:Franchise or else be written with italics.", bookTitle)) categories = categories..CAT_INVALID_ARGS else bookTitle = bookLink end end local phraseLink = args.book and Franchise.phraseLink(args.book) -- for manga and comics and such if phraseLink and phraseLink ~= "" then local byAuthor = string.find(phraseLink, " by ") if byAuthor then phraseLink = phraseLink.sub(phraseLink, 1, byAuthor - 1) -- strip the "by " part end bookTitle = phraseLink end if not bookTitle then bookTitle = utilsError.error("book title required", true) categories = categories..CAT_INVALID_ARGS end local publisher if args.book and args.lang then local bookData = data.books[args.book] and data.books[args.book][args.lang] if not bookData then utilsEror.warn(string.format("No data exists for book %s in language %s. See Module:Cite/Data.", args.book, args.lang)) categories = categories..CAT_INVALID_ARGS end bookTitle = string.format("%s", Franchise.article(args.book) or bookData.display, bookData.display) publisher = bookData.publisher end if not publisher then publisher = args.publisher and p.getPublisherFromShortcut(args.publisher) or (args.book and Franchise.publisher(args.book)) end if not publisher then -- if after all that we still didn't manage to get a publisher... publisher = utilsError.error("publisher required", true) categories = categories..CAT_INVALID_ARGS end local source = args source.title = bookTitle source.publisher = publisher local citation = p.printCitation(source, args.quote, args.character)

return citation, categories end -- Backwards compatibility for deprecated feature function p.getPublisherFromShortcut(publisher) local publishers = { ["enix"] = "Enix Corporation", ["nintendo"] = "Nintendo Co., Ltd.", ["piggyback"] = "Piggyback Interactive Limited", ["prima"] = "Prima Games", ["soleil"] = "Les Éditions Soleil", ["tokuma shoten"] = "Tokuma Shoten Publishing Co., Ltd.", }	local fullName = publishers[string.lower(publisher)] if fullName then warn(string.format("Publisher shortcuts are a deprecated feature. Please enter the full publisher name  instead of  ", fullName, publisher)) return fullName..CAT_INVALID_ARGS else return publisher end end

function p.Guide(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Guide"]) local categories = err and err.categoryText or "" if args.quote then categories = categories..CAT_BOOK_QUOTES end local guideArgs = {args.game, args.guide, "-"} local guideTitle, guideCategories, guidePublisher = Guide.guide(guideArgs, "Guide", true) categories = categories..guideCategories if args.year or args.edition then local editionYear = p.concat(", ", {args.edition, args.year}) guideTitle = guideTitle.." ("..editionYear..")" end local source = { title = guideTitle, publisher = guidePublisher, page = args.page, }	return p.printCitation(source, args.quote)..categories end

function p.Magazine(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Magazine"]) local categories = err and err.categoryText or "" if not args.magazine then return utilsError.error("Magazine name required")..categories end -- We use italics as the cue for a custom magazine name - otherwise the magazine must be one supported by Template:Magazine if p.hasItalics(args.magazine) then args.title = args.magazine else local err = utilsArg.enum(Magazine.enum, args.magazine, "magazine") if err then categories = categories..""..err.category.."" end args.title = ""..args.magazine.."" if args.date then args.date = string.format("%s", args.magazine, args.date, args.date) end end return p.printCitation(args, args.quote, args.interviewee, args.url)..categories end

function p.Manual(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Cite Manual"]) local categories = err and err.categoryText or "" if not args.game and not args.product then categories = categories..CAT_INVALID_ARGS return utilsError.error("game required"), categories end

if args.game then local gameLink = Franchise.link(args.game) if not gameLink then warn(string.format("Invalid game . See Data:Franchise for supported games.", args.game)) categories = categories..CAT_INVALID_ARGS else args.title = gameLink end end args.title = (args.title or args.game or args.product).." manual" if args.version then args.title = p.concat(", ", {args.title, args.version.." version"}) end return p.printCitation(args, args.quote), categories end

function p.color(quote, source, game) local colorId = data.dialogueColors[game] and data.dialogueColors[game][source or "default"] if colorId then local coloredText, errCategories = Color.color(colorId, quote) return coloredText .. (errCategories or "") else return quote end end

function p.printCitation(source, quote, speaker, archive) if type(source) == "table" then source = p.formatCitationSource(source) end local citation if not utilsString.isBlank(quote) and not utilsString.isBlank(speaker) then citation = string.format("%s" — %s (%s), quote, speaker, source) elseif not utilsString.isBlank(quote) then citation = string.format("%s" (%s), quote, source) elseif not utilsString.isBlank(speaker) then citation = string.format('%s (%s)', speaker, source) else citation = source end if archive then citation = string.format("%s ([%s archive])", citation, archive) end return citation end function p.formatCitationSource(source) local volume, issue, page = source.volume, source.issue, source.page if volume then volume = "vol. "..volume end if issue then issue = "no. "..issue end if page then page = "pg. "..page else page = "&#91;which page?&#93;" end local title = mw.getCurrentFrame:preprocess(source.title) local titleVolumeIssue = p.concat(" ", {title, volume, issue}) local sourceText = p.concat(", ", {titleVolumeIssue, source.publisher, source.edition, source.date, page}) return sourceText end function p.concat(separator, items) items = utilsTable.compact(items) items = utilsTable.filter(items, utilsString.notBlank) return table.concat(items, separator) end

function p.hasItalics(str) return string.find(str, ".*") end

function p.Data -- Performance optimization; importing this at the top with the others adds processing time to Template:Cite -- these dependencies are only needed on Module:Cite/Data/Documentation local utilsLanguage = require("Module:UtilsLanguage") local utilsLayout = require("Module:UtilsLayout") local utilsTable = require("Module:UtilsTable") local tableRows = {} for bookCode, bookData in pairs(data.books) do		table.insert(tableRows, {			{				content = string.format(" ", Franchise.article(bookCode) or bookCode, bookCode),				rowspan = utilsTable.size(bookData) + 1,				styles = {					["text-align"] = "center",				},			},		}) for i, langCode in ipairs(utilsLanguage.enum) do			local bookLangData = bookData[langCode] if bookLangData then local language, flag = utilsLanguage.printLanguage(langCode) table.insert(tableRows, {					{						content = flag .. " " .. language,						sortValue = language,					},					string.format("%s", Franchise.article(bookCode) or bookLangData.display, bookLangData.display),					bookLangData.publisher				}) end end end local booksTable = utilsLayout.table({		headers = {"Book", "Language", "Displayed Title", "Publisher"},		rows = tableRows,		sortable = true,	}) booksTable = utilsMarkup.heading(3, utilsMarkup.anchor("books", "Books in Other Languages"))..booksTable local tableRows = {} for _, game in ipairs(Franchise.enumGames) do		local gameColors = data.dialogueColors[game] if gameColors then local colorKeys = utilsTable.keys(gameColors) local sortedColorKeys = utilsTable.sortBy(colorKeys, function(key)				if key == "default" then					return "0" -- show default color first				elseif key == "N/A" then					return "1" -- then show color for N/A (i.e. in-game narration)				else					return key -- then show characters in alphabetical order				end 			end) for i, colorKey in ipairs(sortedColorKeys) do				local row = {} if i == 1 then table.insert(row, {						rowspan = #colorKeys,						content = utilsMarkup.code(utilsMarkup.link(Franchise.article(game), game)),					}) end if colorKey == "default" or colorKey == "N/A" then table.insert(row, utilsMarkup.code(colorKey)) else table.insert(row, utilsMarkup.link(colorKey)) end local colorSample = p.color("The quick brown fox jumps over the lazy dog.", colorKey, game) table.insert(row, colorSample) table.insert(tableRows, row) end end end local colorsTable = utilsLayout.table({		headers = {"Game", "Character/Source", "Color Sample"},		rows = tableRows,		styles = {			["text-align"] = "center"		},		sortable = true,	}) colorsTable = utilsMarkup.heading(3, utilsMarkup.anchor("colors", "Text Colors"))..colorsTable

return booksTable .. "\n" .. colorsTable end

function p.Documentation return { color = { desc = "Given a valid game and character (source), this function applies the main color used for that character's dialogue in-game.", params = {"quote", "source", "game"}, returns = "The colored text", cases = { {					args = {"Only the true ruler of the Twili can destroy the Mirror of Twilight.", "Midna", "TPHD"}, expect = ' Only the true ruler of the Twili can destroy the Mirror of Twilight. ',				},				{					desc = "Not all characters have a unique color.", args = {"Tingle, Tingle! Kooloo-Limpah!", "Tingle", "MM"}, expect = "Tingle, Tingle! Kooloo-Limpah!" }			}		}	} end

function p.Schemas local utilsLanguage = require("Module:UtilsLanguage") return { Data = { type = "record", required = true, properties = { {					name = "books", required = true, desc = "Associates a book code from Data:Franchise to information about the publication of the book in other languages. Used by Template:Cite Book.", type = "map", keyPlaceholder = "bookCode", keys = { type = "string" }, values = { type = "map", keyPlaceholder = "langCode", keys = { type = "string", enum = utilsLanguage.enum, desc = "A language code from Module:UtilsLanguage/Data.", },						values = { type = "record", properties = { {									name = "display", required = true, type = "string", desc = "The text to display when referring to the book - typically its subtitle.", },								{									name = "publisher", required = true, type = "string", desc = "The book's publisher." },							},						},					},				},				{					name = "dialogueColors", required = true, desc = "Associates game characters to color IDs from Template:Color. Used by Template:Cite to determine the character's default quote text color.", type = "map", keyPlaceholder = "gameCode", keys = { type = "string", desc = "A game code from Data:Franchise.", },					values = { allOf = { {								type = "map", keyPlaceholder = "character", keys = { type = "string", desc = "The name of a character in the game, or the special value . The former should refer to a page on the wiki, using parentheses if necessary. For example, the entry for Wood in  would be  .", },								values = { type = "string" }, },							{								type = "record", properties = { {										name = "default", type = "string", desc = "Sets the default text color for quotes from the given game.", },								},							},						},					},				},			},		},		color = { quote = { required = true, type = "string", desc = "The quote to color", },			game = { required = true, type = "string", desc = "A game code. See Module:Franchise.", },			source = { required = true, type = "string", desc = "The name of the character who speaks the quote.", },		}	} end

return p