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

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)
"Bubbles"
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]]s"
Bubbles

printTerm

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

Parameters

Returns

  • A term with formatting.

Examples

#InputOutputResultStatus
6
printTerm("Dynalfos", "OoT")
"Dinolfos"
Dinolfos
7
printTerm("Kara Kara Bazaar", "BotW", { link = true })
"[[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" })
"Link[[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.
  • Error categories if no term was found.

Examples

#InputOutputStatus
12
fetchTerm("Dynalfos", "OoT")
"Dinolfos"
{}
Defaults to series term.
13
fetchTerm("Dinolfos")
"Dynalfos"
{}
Defaults to series term when term does not exist for specified game (nor its base game).
14
fetchTerm("Flying Tile", "TPHD")
"Flying Tile"
{}
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"
{}
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",
  "Articles with invalid or missing plural terms",
}
Returns singular when no plural form exists.
20
fetchTerm(
  "Hestu",
  "HWAoC",
  {
    plural = true,
    allowSingular = true,
  }
)
"Hestu"
{}

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")

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"

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

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

-- 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 err = err and err.categoryText or ""
	
	local games, invalidGames = Franchise.sort(args.games or {})
	if #invalidGames > 0 then
		for i, game in ipairs(invalidGames) do
			invalidGames[i] = "<code>"..game.."</code>"
			h.warn("Invalid [[:Category:Franchise codes|franchise code(s)]]: "..mw.text.listToText(invalidGames))
			err = err.."[[Category:"..CATEGORY_INVALID_ARGS.."]]"
		end
	end

	h.storeCache(args.singularTerm, args.pluralTerm, games)

	local result = args.singularTerm
	if args.plural and utilsString.isEmpty(args.pluralTerm) then
		h.warn("<code>plural</code> option specified yet no plural term is defined. Using singular form.")
		err = err.."[[Category:"..CATEGORY_INVALID_ARGS.."]]"
	elseif args.plural then
		result = args.pluralTerm
	end

	return result..err
end

function p.Singular(frame)
	local args, err = utilsArg.parse(frame.args, p.Templates.Term)
	local err = err and err.categoryText or ""

	local printErrorCategories = not utilsPage.inNamespace("User") and not args.subst
	local result = p.printTerm(args.page, args.game, {
		plural = false,
		link = args.link,
		section = args.section,
		display = args.display,
		subst = args.subst,
		printErrorCategories = printErrorCategories,
	})
	if printErrorCategories then 
		result = result .. err
	end
	return result
end

function p.Plural(frame)
	local args, err = utilsArg.parse(frame.args, p.Templates.Term)
	local err = err and err.categoryText or ""

	local printErrorCategories = not utilsPage.inNamespace("User") and not args.subst
	local result = p.printTerm(args.page, args.game, {
		plural = true,
		link = args.link,
		section = args.section,
		display = args.display,
		subst = args.subst,
		printErrorCategories = printErrorCategories,
	})
	if printErrorCategories then
		result = result .. err
	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

-- A way to clear invalid entries from the cache that can sometimes get stored via page previews.
function p.ClearCache(frame)
	local page = frame.args[1]
	local games = frame.args[2]
	if not page or not games then
		return
	end
	games = utilsString.split(games)

	for i, game in ipairs(games) do
		local termCacheKey = h.cacheKey(page, game, false)
		local pluralCacheKey = h.cacheKey(page, game, true)
		utilsCache.delete(termCacheKey)
		utilsCache.delete(pluralCacheKey)
	end
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
		return h.error("Invalid term", "page parameter cannot be empty").."[[Category:"..CATEGORY_INVALID_ARGS.."]]"
	end
	
	local term, fetchErrors = p.fetchTerm(page, game, options)
	
	local errorCategories = utilsMarkup.categories(fetchErrors or {})
	local result = ""
	if not term and not options.subst then
		result = utilsMarkup.sectionLink(page, options.section, options.display)
		result = utilsMarkup.inline(result, {
			class = "term--invalid",
			tooltip = "Invalid or missing term",
		})
	elseif options.link then
		local linkText = options.display or term
		local italics
		if linkText then
			linkText, italics = string.gsub(linkText or "", "^''([^']*)''$", "%1")
		end
		if options.section then
			linkText = linkText or page
		elseif page == linkText then
			linkText = nil
		end
		-- If linkText == page + suffix, then we use word-ending link syntax
		local overlapStart, overlapEnd = string.find(linkText or "", page, 1, true)
		local hasSuffix = overlapStart == 1 and not string.find(linkText or "", "%s", overlapEnd+1)
		local linkSuffix = ""
		if hasSuffix and not options.section then
			linkSuffix = string.sub(linkText, overlapEnd+1)
			linkText = nil
		end
		result = utilsMarkup.sectionLink(page, options.section, linkText)
		result = result..linkSuffix
		if italics and italics > 0 then
			result = "''"..result.."''"
		end
	else
		result = options.display or term or page
	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
	if options.subst and options.plural and not options.display and fetchErrors and #fetchErrors > 0 then
		result = result..h.error("Plural term undefined").."[[Category:"..CATEGORY_INVALID_ARGS.."]]"
			.."<!-- This error appeared because subst:Plural was used for a subject which does not have a plural term stored."
			.." As a result the term may appear in singular form where it should be plural."
			.." Please remove this error and comment and change the preceding word to its plural form.-->"
	end
	
	-- escape commas for the benefit of templates that split list items by comma, e.g. Module:Infobox
	result = string.gsub(result, ",", "&#44;")
	
	if options.printErrorCategories ~= false then
		result = result..errorCategories
	end

	return result
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
	
	local categories = {}
	local shortName = Franchise.shortName(game)
	if not shortName then
		h.warn("Invalid [[:Category:Franchise codes|franchise code]] <code>"..game.."</code>")
		table.insert(categories, CATEGORY_INVALID_ARGS)
	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, categories
	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

	if not term or invalidPlural then
		table.insert(categories, "Articles with invalid or missing terms")
		if shortName then
			table.insert(categories, string.format("%s articles with invalid or missing terms", shortName))
		end
		if invalidPlural then
			table.insert(categories, "Articles with invalid or missing plural terms")
		end
	end
	
	if term and term.game == game and utilsString.notEmpty(term.term) then
		local cacheKey = h.cacheKey(page, game, false)
		utilsCache.set(cacheKey, term.term)
	end
	if term and term.game == game 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(singularTerm, pluralTerm, games)
	local page = mw.title.getCurrentTitle().text
	
	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

return p