Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

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