Module:TagBuilder: Difference between revisions
From Teriock
More actions
// via Wikitext Extension for VSCode |
// via Wikitext Extension for VSCode |
||
| (8 intermediate revisions by the same user not shown) | |||
| Line 12: | Line 12: | ||
local function ucfirst(s) | local function ucfirst(s) | ||
return mw.getContentLanguage():ucfirst(s or '') | return mw.getContentLanguage():ucfirst(s or '') | ||
end | |||
-- Helper function to sanitize text for CSS class names | |||
local function sanitizeForClass(text) | |||
if not text or text == '' then return '' end | |||
-- Convert to lowercase and replace spaces/special chars with hyphens | |||
return mw.ustring.gsub(mw.ustring.lower(text), '[^%w%-]', '-') | |||
end | |||
-- Decide if we should include categories, based on |name= and |ns= | |||
local function shouldAddCategories(opts) | |||
local title = mw.title.getCurrentTitle() | |||
local page = title.text or '' | |||
local nsNow = title.nsText or '' | |||
local okName = true | |||
if opts and opts.name and opts.name ~= '' then | |||
local want = mw.text.trim(opts.name) | |||
okName = (want == page) or (want == "''" .. page .. "''") | |||
end | |||
local okNs = true | |||
if opts and opts.ns and opts.ns ~= '' then | |||
okNs = (mw.text.trim(opts.ns) == nsNow) | |||
end | |||
return okName and okNs | |||
end | end | ||
| Line 21: | Line 48: | ||
return nil | return nil | ||
end | end | ||
for k, v in pairs(data) do | for k, v in pairs(data) do | ||
if type(v) == 'table' and v[key] then | if type(v) == 'table' and v[key] then | ||
| Line 36: | Line 62: | ||
seen[key] = true | seen[key] = true | ||
local spec | local spec = findTagSpec(data, key, scope) | ||
-- Unknown key → "Key." | -- Unknown key → "Key." | ||
if not spec then | if not spec then | ||
local txt = ucfirst(key) | local txt = ucfirst(key) | ||
| Line 47: | Line 73: | ||
-- Composite tag? | -- Composite tag? | ||
if spec.tags then | if spec.tags then | ||
local out = {} | local out, cats = {}, {} | ||
for _, sub in ipairs(spec.tags) do | for _, sub in ipairs(spec.tags) do | ||
local t, c = expandKey(sub, data, scope, opts, seen) | local t, c = expandKey(sub, data, scope, opts, seen) | ||
| Line 62: | Line 87: | ||
if spec.link then tagText = tagText .. '|l=:Category:' .. ucfirst(spec.link) end | if spec.link then tagText = tagText .. '|l=:Category:' .. ucfirst(spec.link) end | ||
if opts and opts.transparent == '1' then tagText = tagText .. '|o=1' end | if opts and opts.transparent == '1' then tagText = tagText .. '|o=1' end | ||
-- Add class parameter with scope and text | |||
local classValues = {} | |||
if scope and scope ~= '' then | |||
table.insert(classValues, sanitizeForClass(scope) .. '-tagged') | |||
end | |||
if spec.text and spec.text ~= '' then | |||
table.insert(classValues, sanitizeForClass(spec.text) .. '') | |||
end | |||
if #classValues > 0 then | |||
tagText = tagText .. '|class=' .. table.concat(classValues, ' ') | |||
end | |||
tagText = tagText .. '}}' | tagText = tagText .. '}}' | ||
-- Category | -- Category tokens | ||
local cats = {} | local cats = {} | ||
if spec.link then | if spec.link then | ||
| Line 73: | Line 111: | ||
end | end | ||
-- Core builder: keys (array of strings) → concatenated tags, concatenated categories | -- Core builder: keys (array of strings) → concatenated tags, concatenated categories (conditionally) | ||
function B.buildFromKeys(keys, dataModuleName, scope, opts) | function B.buildFromKeys(keys, dataModuleName, scope, opts) | ||
local moduleName = normalizeModuleName(dataModuleName) | local moduleName = normalizeModuleName(dataModuleName) | ||
local data = mw.loadData(moduleName) | local data = mw.loadData(moduleName) | ||
local out = {} | local out, cats, seen = {}, {}, {} | ||
for _, key in ipairs(keys or {}) do | for _, key in ipairs(keys or {}) do | ||
local t, c = expandKey(key, data, scope, opts, seen) | local t, c = expandKey(key, data, scope, opts, seen) | ||
| Line 87: | Line 122: | ||
for _, cc in ipairs(c) do table.insert(cats, cc) end | for _, cc in ipairs(c) do table.insert(cats, cc) end | ||
end | end | ||
-- Apply the |name= / |ns= gate for categories | |||
if not shouldAddCategories(opts) then | |||
cats = {} | |||
end | |||
return table.concat(out), table.concat(cats) | return table.concat(out), table.concat(cats) | ||
end | end | ||
| Line 107: | Line 148: | ||
local data = args.data or 'TagCreatures' | local data = args.data or 'TagCreatures' | ||
local scope = args.scope -- optional (e.g., "traits") | local scope = args.scope -- optional (e.g., "traits") | ||
local opts = { transparent = args.o or args.transparent } | local opts = { | ||
transparent = args.o or args.transparent, | |||
name = args.name, | |||
ns = args.ns, | |||
} | |||
local tags = B.build(t, data, scope, opts) | local tags = B.build(t, data, scope, opts) | ||
return frame:preprocess(tags or '') | return frame:preprocess(tags or '') | ||
end | end | ||
-- {{#invoke:TagBuilder|categories|t=...|data=TagCreatures|scope=traits}} | -- {{#invoke:TagBuilder|categories|t=...|data=TagCreatures|scope=traits|name=Page Name|ns=Creature}} | ||
function p.categories(frame) | function p.categories(frame) | ||
local args = frame.args or {} | local args = frame.args or {} | ||
| Line 118: | Line 163: | ||
local data = args.data or 'TagCreatures' | local data = args.data or 'TagCreatures' | ||
local scope = args.scope | local scope = args.scope | ||
local _, cats = B.build(t, data, scope, | local opts = { | ||
name = args.name, | |||
ns = args.ns, | |||
} | |||
local _, cats = B.build(t, data, scope, opts) | |||
return cats or '' | return cats or '' | ||
end | end | ||
Latest revision as of 07:40, 18 August 2025
Documentation for this module may be created at Module:TagBuilder/doc
-- Module:TagBuilder
local TagParser = require('Module:TagParser')
local B = {}
local function normalizeModuleName(name)
if not name or name == '' then return 'Module:TagCreatures' end
if mw.ustring.find(name, '^Module:') then return name end
return 'Module:' .. name
end
local function ucfirst(s)
return mw.getContentLanguage():ucfirst(s or '')
end
-- Helper function to sanitize text for CSS class names
local function sanitizeForClass(text)
if not text or text == '' then return '' end
-- Convert to lowercase and replace spaces/special chars with hyphens
return mw.ustring.gsub(mw.ustring.lower(text), '[^%w%-]', '-')
end
-- Decide if we should include categories, based on |name= and |ns=
local function shouldAddCategories(opts)
local title = mw.title.getCurrentTitle()
local page = title.text or ''
local nsNow = title.nsText or ''
local okName = true
if opts and opts.name and opts.name ~= '' then
local want = mw.text.trim(opts.name)
okName = (want == page) or (want == "''" .. page .. "''")
end
local okNs = true
if opts and opts.ns and opts.ns ~= '' then
okNs = (mw.text.trim(opts.ns) == nsNow)
end
return okName and okNs
end
-- Find a tag spec by key, either under a specific scope or across all top-level tables
local function findTagSpec(data, key, scope)
if scope then
local tbl = data[scope]
if type(tbl) == 'table' then return tbl[key] end
return nil
end
for k, v in pairs(data) do
if type(v) == 'table' and v[key] then
return v[key], k
end
end
return nil
end
-- Recursively expand a key into tag wikitext and categories.
local function expandKey(key, data, scope, opts, seen)
seen = seen or {}
if seen[key] then return '', {} end
seen[key] = true
local spec = findTagSpec(data, key, scope)
-- Unknown key → "Key."
if not spec then
local txt = ucfirst(key)
if txt ~= '' and txt:sub(-1) ~= '.' then txt = txt .. '.' end
return txt, {}
end
-- Composite tag?
if spec.tags then
local out, cats = {}, {}
for _, sub in ipairs(spec.tags) do
local t, c = expandKey(sub, data, scope, opts, seen)
table.insert(out, t)
for _, cc in ipairs(c) do table.insert(cats, cc) end
end
return table.concat(out), cats
end
-- Build {{Tag|...}}
local tagText = string.format('{{Tag|%s', ucfirst(spec.text))
if spec.symbol then tagText = tagText .. '|' .. spec.symbol end
if spec.link then tagText = tagText .. '|l=:Category:' .. ucfirst(spec.link) end
if opts and opts.transparent == '1' then tagText = tagText .. '|o=1' end
-- Add class parameter with scope and text
local classValues = {}
if scope and scope ~= '' then
table.insert(classValues, sanitizeForClass(scope) .. '-tagged')
end
if spec.text and spec.text ~= '' then
table.insert(classValues, sanitizeForClass(spec.text) .. '')
end
if #classValues > 0 then
tagText = tagText .. '|class=' .. table.concat(classValues, ' ')
end
tagText = tagText .. '}}'
-- Category tokens
local cats = {}
if spec.link then
table.insert(cats, '[[Category:' .. ucfirst(spec.link) .. ']]')
end
return tagText, cats
end
-- Core builder: keys (array of strings) → concatenated tags, concatenated categories (conditionally)
function B.buildFromKeys(keys, dataModuleName, scope, opts)
local moduleName = normalizeModuleName(dataModuleName)
local data = mw.loadData(moduleName)
local out, cats, seen = {}, {}, {}
for _, key in ipairs(keys or {}) do
local t, c = expandKey(key, data, scope, opts, seen)
if t and t ~= '' then table.insert(out, t) end
for _, cc in ipairs(c) do table.insert(cats, cc) end
end
-- Apply the |name= / |ns= gate for categories
if not shouldAddCategories(opts) then
cats = {}
end
return table.concat(out), table.concat(cats)
end
-- Convenience: raw ";;" string → tags, cats
function B.build(raw, dataModuleName, scope, opts)
local keys = TagParser.parseList(raw)
return B.buildFromKeys(keys, dataModuleName, scope, opts)
end
---------------------------
-- #invoke entrypoints
---------------------------
local p = {}
-- {{#invoke:TagBuilder|tags|t=undead;; skeletal;;|data=TagCreatures|scope=traits|o=1}}
function p.tags(frame)
local args = frame.args or {}
local t = args.t or args[1] or ''
local data = args.data or 'TagCreatures'
local scope = args.scope -- optional (e.g., "traits")
local opts = {
transparent = args.o or args.transparent,
name = args.name,
ns = args.ns,
}
local tags = B.build(t, data, scope, opts)
return frame:preprocess(tags or '')
end
-- {{#invoke:TagBuilder|categories|t=...|data=TagCreatures|scope=traits|name=Page Name|ns=Creature}}
function p.categories(frame)
local args = frame.args or {}
local t = args.t or args[1] or ''
local data = args.data or 'TagCreatures'
local scope = args.scope
local opts = {
name = args.name,
ns = args.ns,
}
local _, cats = B.build(t, data, scope, opts)
return cats or ''
end
-- For other modules: require('Module:TagBuilder').build(...)
return setmetatable(p, { __index = B })