Zelda Wiki

Want to contribute to this wiki?
Sign up for an account, and get started!

Come join the Zelda Wiki community Discord server!

READ MORE

Zelda Wiki
Zelda Wiki
This is the main module for the following templates: In addition, this module exports the following functions.

color

color(quote, source, game)

Given a valid game and character (source), this function applies the main color used for that character's dialogue in-game.

Parameters

Returns

  • The colored text

Examples

#InputOutputResultStatus
1
color(
  "Only the true ruler of the Twili can destroy the Mirror of Twilight.",
  "Midna",
  "TPHD"
)
'<span class="colored-text" style="color:#64b1b3">Only the true ruler of the Twili can destroy the Mirror of Twilight.</span>'
Only the true ruler of the Twili can destroy the Mirror of Twilight.
Green check
Not all characters have a unique color.
2
color("Tingle, Tingle! Kooloo-Limpah!", "Tingle", "MM")
"Tingle, Tingle! Kooloo-Limpah!"
Tingle, Tingle! Kooloo-Limpah!
Green check

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 utilsLanguage = require("Module:UtilsLanguage")
local utilsMarkup = require("Module:UtilsMarkup")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")

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

local CAT_BOOK_QUOTES = "[[Category:Book Citations Using Quotes]]"
local CAT_INVALID_ARGS = "[[Category:"..Constants.category.invalidArgs.."]]"

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
		utilsError.warn(utilsMarkup.code(mw.dumpObject(args.game)) .." is neither a valid [[Data:Franchise|code]] nor an interwiki link.")
		categories = CAT_INVALID_ARGS
	end
	if args.game and Franchise.hasRemakes(args.game) then
		categories = categories .. "[[Category:Articles Citing Games with Remakes]]"
	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
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
			utilsError.warn(string.format("<code>%s</code> 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 then
		local byAuthor = string.find(phraseLink, " by ")
		if byAuthor then
			phraseLink = phraseLink.sub(phraseLink, 1, byAuthor - 1) -- strip the "by <author>" 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|%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
		utilsError.warn(string.format("Publisher shortcuts are a deprecated feature. Please enter the full publisher name <code>%s</code> instead of <code>%s</code>", 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 (%s)|%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
			utilsError.warn(string.format("Invalid game <code>%s</code>. 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 = "<sup>&#91;[[:Category:Pages with Vague Citations|''which page?'']]&#93;</sup>[[Category:Pages with Vague Citations]]"
	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 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("<code>[[%s|%s]]</code>", 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|%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("<b>The quick brown fox jumps over the lazy dog.</b>", 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 = '<span class="colored-text" style="color:#64b1b3">Only the true ruler of the Twili can destroy the Mirror of Twilight.</span>',
				},
				{
					desc = "Not all characters have a unique color.",
					args = {"Tingle, Tingle! Kooloo-Limpah!", "Tingle", "MM"},
					expect = "Tingle, Tingle! Kooloo-Limpah!"
				}
			}
		}
	}
end

function p.Schemas()
	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 <code>N/A</code>. The former should refer to a page on the wiki, using parentheses if necessary. For example, the entry for [[Wood (Character)|Wood]] in {{ST|-}} would be <code>Wood (Character)</code>.",
								},
								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