Documentation for this module may be created at Module:Term/doc
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, ",", ",") 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