Module:Term

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
This is the main module for the following templates: In addition, this module exports the following functions.

FetchTerm

{{#invoke:Term|FetchTerm|page=|game=|plural=}}

Used by Template:Translation/Store to get the raw term without the extra output from Template:Term.

Parameters

ParameterStatus
pagerequired
gameoptional
pluraloptional

Examples

#InputOutput
1
{{#invoke:Term|FetchTerm|page= 2nd Potion|game= Series}}
Red Water of Life
2
{{#invoke:Term|FetchTerm|page= not a page}}

link

link(page, game, options)

Shorthand for printTerm(page, game, { link = true })

Returns

  • A link to a term page.

Examples

#InputOutputResultStatus
3
link("Bubble", nil, nil)
"[[Bubble|Bubble]]"
Bubble

plural

plural(page, game, options)

Shorthand for printTerm(page, game, { plural = true })

Returns

  • A term in plural form.

Examples

#InputOutputResultStatus
4
plural("Bubble", nil, nil)
'<span class="term">Bubbles</span>'
Bubbles

pluralLink

pluralLink(page, game, options)

Shorthand for printTerm(page, game, { plural = true, link = true })

Returns

  • A plural link to a term page.

Examples

#InputOutputResultStatus
5
pluralLink("Bubble", nil, nil)
"[[Bubble|Bubbles]]"
Bubbles

printTerm

printTerm(page, [game], [options])

Parameters

Returns

  • A term with formatting.

Examples

#InputOutputResultStatus
6
printTerm("Dynalfos", "OoT")
'<span class="term">Dinolfos</span>'
Dinolfos
7
printTerm("Kara Kara Bazaar", "BotW", { link = true })
"[[Kara Kara Bazaar#Breath of the Wild|Kara Kara Bazaar]]"
Kara Kara Bazaar
8
printTerm(
  "Kara Kara Bazaar",
  "BotW",
  {
    section = "Shaillu's General Store",
    link = true,
  }
)
"[[Kara Kara Bazaar#Shaillu's General Store|Kara Kara Bazaar]]"
Kara Kara Bazaar
9
printTerm(
  "Kara Kara Bazaar",
  "BotW",
  {
    display = "General Store",
    section = "Shaillu's General Store",
    link = true,
  }
)
"[[Kara Kara Bazaar#Shaillu's General Store|General Store]]"
General Store
10
printTerm("invalid term")
'<span class="term--invalid"><span title="Invalid or missing term" class="tooltip">[[invalid term]]</span></span>[[Category:Articles with invalid or missing terms]][[Category:The Legend of Zelda Series articles with invalid or missing terms]]'
invalid term
Checks for redundant display arguments.
11
printTerm("Link", "Series", { display = "Link" })
'<span class="term">Link</span>[[Category:Terms with redundant display arguments]]'
Link

fetchTerm

fetchTerm(page, [game], [options])

Parameters

Returns

  • The term for the given article and game, or nil if none found.
  • An error category if no term was found.

Examples

#InputOutputStatus
12
fetchTerm("Dynalfos", "OoT")
"Dinolfos"
nil
Defaults to series term.
13
fetchTerm("Dinolfos")
"Dynalfos"
nil
Defaults to series term when term does not exist for specified game (nor its base game).
14
fetchTerm("Flying Tile", "TPHD")
"Flying Tile"
nil
Error when page does store any terms (game specified).
15
fetchTerm("Flippityfloppito", "SS")
nil
{
  "Articles with invalid or missing terms",
  "Skyward Sword articles with invalid or missing terms",
}
Error when page does store any terms (no game specified).
16
fetchTerm("Flippityfloppityfloo")
nil
{
  "Articles with invalid or missing terms",
  "The Legend of Zelda Series articles with invalid or missing terms",
}
Error when page has wrong casing
17
fetchTerm("captain's hat")
nil
{
  "Articles with invalid or missing terms",
  "The Legend of Zelda Series articles with invalid or missing terms",
}
Plural
18
fetchTerm("Bubble", "Series", { plural = true })
"Bubbles"
nil
Returns singular when no plural form exists.
19
fetchTerm("A Brother's Roast", "BotW", { plural = true })
"A Brother's Roast"
{
  "Articles with invalid or missing terms",
  "Breath of the Wild articles with invalid or missing terms",
}
Returns singular when no plural form exists.
20
fetchTerm(
  "Hestu",
  "HWAoC",
  {
    plural = true,
    allowSingular = true,
  }
)
"Hestu"
nil

fetchSubjects

fetchSubjects(term, [game])

See Module:Translation Page for usage.

Parameters

Returns

  • Returns the names of wiki articles that store the given term. If game is specified, the function will only return articles that store the term for that game.

Examples

#InputOutputResult
21
fetchSubjects("Wood")
{"Wood", "Wood (Character)"}
22
fetchSubjects("Wood", "ST")
{"Wood (Character)"}
23
fetchSubjects("Fooloo Limpah")
{}

local p = {}
local h = {}

local Franchise = require("Module:Franchise")
local utilsArg = require("Module:UtilsArg")
local utilsCache = require("Module:UtilsCache")
local utilsCargo = require("Module:UtilsCargo")
local utilsMarkup = require("Module:UtilsMarkup")
local utilsPage = require("Module:UtilsPage")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")
local utilsVar = require("Module:UtilsVar")

p.Templates = mw.loadData("Module:Term/TemplateData")

local CATEGORY_INVALID_ARGS = require("Module:Constants/category/invalidArgs")
local CATEGORY_REDUNDANT_DISPLAY = "Terms with redundant display arguments"
local CARGO_TABLE = "Terminologies"

-- In the past Cargo has been iffy with storage from modules, so the actual Cargo store is still done on the actual template.
-- We still do the validation + caching layer here, though.
function p.TermStore(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Term/Store"])
	local errCategories = err and err.categories or {}
	local result = args.singularTerm
	if args.plural and utilsString.isEmpty(args.pluralTerm) then
		table.insert(errCategories, CATEGORY_INVALID_ARGS)
		h.warn("<code>plural</code> option specified yet no plural term is defined. Using singular form.")
	elseif args.plural then
		result = args.pluralTerm
	end
	h.storeCache(args)
	return result .. utilsMarkup.categories(errCategories)
end

function p.Singular(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates.Term)
	local printErrorCategories = not utilsPage.inNamespace("User")
	local result = p.printTerm(args.page, args.game, {
		plural = false,
		link = args.link,
		section = args.section,
		display = args.display,
		printErrorCategories = printErrorCategories,
	})
	if err and printErrorCategories then 
		result = result .. utilsMarkup.categories(err.categories)
	end
	return result
end

function p.Plural(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates.Plural)
	local printErrorCategories = not utilsPage.inNamespace("User")
	local result = p.printTerm(args.page, args.game, {
		plural = true,
		link = args.link,
		section = args.section,
		display = args.display,
		printErrorCategories = printErrorCategories,
	})
	if err and printErrorCategories then
		result = result .. utilsMarkup.categories(err.categories)
	end
	return result
end

function p.FetchTerm(frame)
	local args = frame.args
	args = utilsTable.mapValues(args, utilsString.trim)
	args = utilsTable.mapValues(args, utilsString.nilIfEmpty)
	local term = p.fetchTerm(args.page, args.game, {
		plural = args.plural
	})
	return term
end

function p.ClearCache(frame)
	local page = frame.args[1]
	h.clearCache(page)
end

function p.link(page, game, options)
	options = utilsTable.merge({}, options or {}, {
		link = true
	})
	return p.printTerm(page, game, options)
end

function p.plural(page, game, options)
	options = utilsTable.merge({}, options or {}, {
		plural = true,
	})
	return p.printTerm(page, game, options)
end

function p.pluralLink(page, game, options)
	local options = utilsTable.merge({}, options or {}, {
		link = true,
		plural = true,
	})
	return p.printTerm(page, game, options)
end

function p.printTerm(page, game, options)
	options = options or {}
	-- If page == nil, Template:Term would otherwise ouptut an empty string and the sentence it's in won't make sense.
	-- If page == "link", Template:Term would otherwise output "Link", which is almost certainly not what the editor intended
	-- This makes the sentence nonsensical at best and misinformative at worst. Better to display a bold red error.
	-- In the former case, it's usually that the editor accidentally added an extra pipe character after the game parameter, making the page argument empty 
	-- e.g. {{Term|BotW||Shield|link}}
	-- In the latter case, it's usually that editor meant to link to a page but forgot to add either the page parameter or game parameter
	-- so the link parameter (param #3) took the place of the page parameter (param #2)
	-- e.g. {{Term|Stalfos|link}}
	if not page or page == "link" then
		error("page parameter cannot be empty")
	end
	
	local term, fetchErrors = p.fetchTerm(page, game, options)
	
	local errorCategories = ""
	if options.printErrorCategories ~= false then
		local errors = utilsTable.concat(validationErrors or {}, fetchErrors or {})
		errorCategories = utilsMarkup.categories(errors)
	end
	local result = ""
	if not term then
		local errLink = utilsMarkup.sectionLink(page, options.section, options.display)
		result = utilsMarkup.inline(errLink, {
			class = "term--invalid",
			tooltip = "Invalid or missing term",
		})
	elseif options.link then
		local baseGame = game and Franchise.baseGame(game)
		local gameSub = baseGame and Franchise.shortName(baseGame)
		local section = options.section
		if not section and gameSub and game ~= "Series" then
			section = gameSub
		end
		result = utilsMarkup.sectionLink(page, section, options.display or term)
	else
		result = utilsMarkup.class("term", options.display or term)
	end
	if term and options.display == term then
		errorCategories = errorCategories.."[[Category:"..CATEGORY_REDUNDANT_DISPLAY.."]]"
		h.warn(string.format("Redundant display argument <code>%s</code> is the same as the term value.", options.display))
	end
	
	-- escape commas for the benefit of templates that split list items by comma, e.g. Module:Infobox
	result = string.gsub(result, ",", "&#44;")
	return result .. errorCategories
end

function p.fetchTerm(page, game, options)
	game = game or "Series"
	options = options or {}
	local plural = options.plural
	if not page then
		return nil
	end
	
	-- Cargo queries don't allow # and it's impossible to have a page with # anyway because of section anchors. 
	 -- Ideally, users should input the name of the page where the term is stored (e.g. Swordsman Newsletter 4 instead of Swordsman Newsletter #4)
	page = string.gsub(page, "#", "")
	
	-- Things like {{PAGENAME}} return HTML entities. These have to be removed as the "#" character cannot be used in Cargo queries.
	page = mw.text.decode(page) 
	
	local term
	local cacheKey = h.cacheKey(page, game, plural)
	term = utilsCache.get(cacheKey)
	if term ~= nil and term ~= "" then -- The cache shouldn't store empty terms, but it used to. It's a good safeguard anyway.
		return term
	end
	
	-- If a term does not exist for the specified game, we fallback to earlier versions of the game or to the Series term if all else fails
	-- local baseGame = game and Franchise.baseGame(game)
	-- local remakes = baseGame and Franchise.remakes(baseGame)
	-- local games = utilsTable.reverse(remakes or {})
	-- if baseGame then
	-- 	table.insert(games, #games + 1, baseGame)
	-- end
	-- table.insert(games, #games + 1, "Series")
	
	-- There's some uncertainty as to whether the above behaviour is desired.
	-- If that gets resolved, the next line can be deleted and the above lines uncommmented 
	-- There's a test case in Module:Term/Documentation/Data that should be uncommented as well
	local games = game ~= "Series" and {game, "Series"} or {"Series"}
	
	local rows = utilsCargo.query("Terminologies=terms, Terminologies__games=termGames", "termGames._value=game, terms.term=term, terms.plural=plural", {
		join = "terms._ID=termGames._rowID",
		where = utilsCargo.allOf(
			{ ["BINARY _pageName"] = page }, -- BINARY makes the search case-sensitive - we want to show a validation error when folks input the name with improper case
			utilsCargo.IN("termGames._value", games)
		)
	})
	local termsByGame = utilsTable.keyBy(rows, "game")
	
	for i, game in ipairs(games) do
		term = termsByGame[game]
		if term then
			break
		end
	end
	
	local invalidPlural = term and plural and utilsString.isEmpty(term.plural) and not options.allowSingular
	if invalidPlural then
		h.warn(string.format("<code>%s</code> term for <code>%s</code> has no plural form defined. Using singular form.", game, page))
	end
	
	local categories = {}
	if not term or invalidPlural then
		table.insert(categories, "Articles with invalid or missing terms")
		local subtitle = Franchise.shortName(game)
		if subtitle then
			table.insert(categories, string.format("%s articles with invalid or missing terms", subtitle))
		end
	end
	if #categories == 0 or #categories > 0 and mw.title.getCurrentTitle().nsText == "User" then
		categories = nil
	end
	
	if term and utilsString.notEmpty(term.term) then
		local cacheKey = h.cacheKey(page, game, false)
		utilsCache.set(cacheKey, term.term)
	end
	if term and utilsString.notEmpty(term.plural) then
		local cacheKey = h.cacheKey(page, game, true)
		utilsCache.set(cacheKey, term.plural)
	end
	
	if not term then
		result = nil
	elseif plural and utilsString.notEmpty(term.plural) then
		result = term.plural
	else
		result = term.term
	end
	
	return result, categories
end

function p.fetchSubjects(term, game)
	local rows = utilsCargo.query(CARGO_TABLE, "_pageName", {
		where = utilsCargo.allOf(
			{ term = term },
			game and ("games HOLDS '%s'"):format(game)
		)
	})
	return utilsTable.map(rows, "_pageName")
end

function h.cacheKey(page, game, plural)
	local key = string.format("%s.%s.%s", plural and "plural" or "term", game, page)
	return key
end

function h.storeCache(args)
	local singularTerm = args.singularTerm
	local pluralTerm = args.pluralTerm
	local games = args.games
	local page = mw.title.getCurrentTitle().text
	
	h.clearCache(page)
	
	local cacheTerms = {}
	for _, game in ipairs(games or {}) do
		if singularTerm and singularTerm ~= "" then
			local key = h.cacheKey(page, game, false)
			cacheTerms[key] = singularTerm
		end
		if pluralTerm and pluralTerm ~= "" then
			local key = h.cacheKey(page, game, true)
			cacheTerms[key] = pluralTerm
		end
	end
	utilsCache.setMulti(cacheTerms)
end

-- When loading a page, we clear the cache of its terms once to remove potentially stale cache entries
-- For example, say {{Term|PH|Links}} is called when no term is stored for PH
-- The Series term is returned as a fallback and that is cached as the term for PH
-- If the Series term is changed, the cache entry is updated but the PH entry is not. 
function h.clearCache(page)
	local termCacheCleared = utilsVar.get("Module:Term/termCacheCleared")
	if termCacheCleared then
		return
	end
	for i, game in ipairs(Franchise.enum()) do
		local termCacheKey = h.cacheKey(page, game, false)
		local pluralCacheKey = h.cacheKey(page, game, true)
		p.deleteCacheEntry(termCacheKey)
		p.deleteCacheEntry(pluralCacheKey)
	end
	utilsVar.set("Module:Term/termCacheCleared", "true")
end

-- Debug function to delete invalid entries that somehow make their way into the cache
-- For example, maybe new validation was added that didn't exist before 
function p.deleteCacheEntry(key)
	utilsCache.delete(key)
end

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

return p