Module:TemplateStarter
Appearance
Documentation for this module may be created at Module:TemplateStarter/doc
-- Module:TemplateStarter
-- Generates configurable template structures for new pages using ConfigRepository
local p = {}
-- Required modules
local ConfigRepository = require('Module:ConfigRepository')
local ErrorHandling = require('Module:ErrorHandling')
-- Cache for template lists
local templateListCache = nil
local templateListCacheTime = 0
local CACHE_DURATION = 300 -- 5 minutes
-- Generate empty template wikitext from template type
function p.generateTemplate(templateType)
-- Input validation and sanitization
if not templateType or templateType == "" then
return "Error: Template type is required"
end
-- Sanitize input: remove any potentially harmful characters
templateType = mw.text.trim(tostring(templateType))
if not templateType:match("^[%w_%-]+$") then
return "Error: Invalid template type format"
end
-- Create error context
local errorContext = ErrorHandling.createContext("TemplateStarter")
-- Protected function to get configuration
local function getTemplateConfig()
local config = ConfigRepository.getConfig(templateType)
if not config then
return nil, string.format("Template type '%s' not found in ConfigRepository", templateType)
end
if not config.fields then
return nil, string.format("Template type '%s' has no fields configuration", templateType)
end
return config
end
-- Get configuration with error protection
local config = ErrorHandling.protect(errorContext, 'getTemplateConfig', getTemplateConfig, nil)
if not config then
return "Error: Failed to load template configuration"
end
-- Pre-allocate lines table for better performance
local lines = {}
local lineCount = 0
-- Check for boilerplate configuration
local hasBoilerplate = config.boilerplate and (config.boilerplate.intro or config.boilerplate.outro)
-- Add debug comment
if hasBoilerplate then
lineCount = lineCount + 1
lines[lineCount] = "<!-- Boilerplate available for " .. templateType .. " template -->"
else
lineCount = lineCount + 1
lines[lineCount] = "<!-- No boilerplate for this template -->"
end
-- Start the template
lineCount = lineCount + 1
lines[lineCount] = "{{" .. templateType
-- Process each field with error protection
local function processFields()
for _, field in ipairs(config.fields) do
-- Skip hidden fields
if not field.hidden then
-- Handle fields with multiple keys (prefer single key, fallback to first of multiple)
local fieldKey = field.key or (field.keys and field.keys[1])
if fieldKey and fieldKey ~= "" then
lineCount = lineCount + 1
lines[lineCount] = string.format("|%s = ", fieldKey)
end
end
end
return true
end
-- Process fields with error protection
ErrorHandling.protect(errorContext, 'processFields', processFields, false)
-- Close the template
lineCount = lineCount + 1
lines[lineCount] = "}}"
-- Add intro boilerplate after template (if available)
if config.boilerplate and config.boilerplate.intro then
lineCount = lineCount + 1
lines[lineCount] = "" -- First empty line for separation
lineCount = lineCount + 1
lines[lineCount] = "" -- Second empty line for distance
lineCount = lineCount + 1
lines[lineCount] = config.boilerplate.intro
end
-- Join with newlines
return table.concat(lines, "\n")
end
-- Main function to be called from wiki (for testing/preview)
function p.main(frame)
-- Simple validation without complex error handling for this function
local args = frame.args
local parent = frame:getParent()
local pargs = parent and parent.args or {}
-- Get parameters (check both direct and parent args)
local articleName = args.articleName or pargs.articleName or args[1] or pargs[1]
local templateType = args.templateType or pargs.templateType or args[2] or pargs[2]
-- Validate inputs
if not articleName or mw.text.trim(tostring(articleName)) == "" then
return "Error: Article name is required"
end
if not templateType or mw.text.trim(tostring(templateType)) == "" then
return "Error: Template type is required"
end
-- Generate the template content
local content = p.generateTemplate(mw.text.trim(tostring(templateType)))
-- For testing, return the generated content in a pre block
return string.format('<pre>Page: %s\n\n%s</pre>',
mw.text.nowiki(mw.text.trim(tostring(articleName))),
mw.text.nowiki(content))
end
-- Generate a dynamic preload template (main function used by JS)
function p.preload(frame)
local args = frame.args
local templateType = args.templateType or args[1]
if not templateType or mw.text.trim(tostring(templateType)) == "" then
return "<!-- No template type specified -->"
end
templateType = mw.text.trim(tostring(templateType))
-- Parse template to check if it's a variant
local parsedTemplate = p.parseVariantTemplate(templateType)
if parsedTemplate and parsedTemplate.variant then
-- Generate combined template for variant
return p.generateCombinedTemplate(parsedTemplate.baseTemplate, parsedTemplate.variant)
else
-- Generate regular template
return p.generateTemplate(templateType)
end
end
-- Get list of available templates with caching
function p.getAvailableTemplates()
local currentTime = os.time()
-- Check if cache is valid
if templateListCache and (currentTime - templateListCacheTime) < CACHE_DURATION then
return templateListCache
end
-- No complex error handling here
local templates = {}
-- Check if ConfigRepository.templates exists
if ConfigRepository.templates then
local index = 1
for templateName, _ in pairs(ConfigRepository.templates) do
if templateName and templateName ~= "" then
templates[index] = templateName
index = index + 1
end
end
table.sort(templates)
end
-- No fallback! If ConfigRepository fails, return empty list (single source of truth)
-- Update cache
templateListCache = templates
templateListCacheTime = currentTime
return templates
end
-- Get list of available templates including variants
function p.getAvailableTemplatesWithVariants()
local templates = p.getAvailableTemplates()
local templatesWithVariants = {}
local index = 1
-- Add base templates first
for _, templateName in ipairs(templates) do
templatesWithVariants[index] = templateName
index = index + 1
end
-- Add variants
if ConfigRepository.templates then
for templateName, config in pairs(ConfigRepository.templates) do
if config.variants then
for variantKey, variant in pairs(config.variants) do
-- Only include active variants
if variant.state and variant.name then
templatesWithVariants[index] = variant.name
index = index + 1
end
end
end
end
end
-- Sort the combined list
table.sort(templatesWithVariants)
return templatesWithVariants
end
-- Parse variant template name to get base template and variant info
function p.parseVariantTemplate(templateName)
if not templateName or templateName == "" then
return nil
end
-- Check if this is a variant by looking for the pattern in all templates
if ConfigRepository.templates then
for baseTemplateName, config in pairs(ConfigRepository.templates) do
if config.variants then
for variantKey, variant in pairs(config.variants) do
if variant.state and variant.name == templateName then
return {
baseTemplate = baseTemplateName,
variantKey = variantKey,
variant = variant
}
end
end
end
end
end
-- Not a variant, return as base template
return {
baseTemplate = templateName,
variantKey = nil,
variant = nil
}
end
-- Generate combined template for variants
function p.generateCombinedTemplate(baseTemplate, variant)
if not baseTemplate or not variant then
return "Error: Invalid template combination"
end
-- Generate base template
local baseContent = p.generateTemplate(baseTemplate)
if not baseContent or baseContent:match("^Error:") then
return baseContent or "Error: Failed to generate base template"
end
-- Generate campaign template if specified
local campaignContent = ""
if variant.campaign_template then
campaignContent = string.format("{{#invoke:T-Campaign|render|campaign_name=%s}}", variant.campaign_template)
end
-- Combine templates with blank line separator
local combinedLines = {}
local lineCount = 0
-- Add base template
lineCount = lineCount + 1
combinedLines[lineCount] = baseContent
-- Add separator
lineCount = lineCount + 1
combinedLines[lineCount] = ""
-- Add campaign template
if campaignContent ~= "" then
lineCount = lineCount + 1
combinedLines[lineCount] = campaignContent
end
return table.concat(combinedLines, "\n")
end
-- Test function to list available templates (clean output for JS consumption)
function p.listTemplates(frame)
local templates = p.getAvailableTemplatesWithVariants()
if #templates == 0 then
return "No templates available"
end
return table.concat(templates, ", ")
end
-- Get creator fields for a specific template type
function p.getCreatorFields(templateType)
if not templateType or templateType == "" then
return {}
end
-- Parse template to resolve variants to base template
local parsedTemplate = p.parseVariantTemplate(templateType)
local baseTemplateName = parsedTemplate and parsedTemplate.baseTemplate or templateType
local config = ConfigRepository.getConfig(baseTemplateName)
return config.creatorFields or {}
end
-- Get field definitions for creator fields
function p.getCreatorFieldDefinitions(templateType)
if not templateType or templateType == "" then
return {}
end
local creatorFields = p.getCreatorFields(templateType)
local fieldDefinitions = {}
for _, fieldKey in ipairs(creatorFields) do
local fieldDef = ConfigRepository.pageCreatorFields[fieldKey]
if fieldDef then
fieldDefinitions[fieldKey] = fieldDef
end
end
return fieldDefinitions
end
-- Replace placeholders in boilerplate text with user values
function p.replacePlaceholders(text, values)
if not text or not values then
return text
end
local result = text
for key, value in pairs(values) do
if value and value ~= "" then
result = result:gsub("%$" .. key .. "%$", value)
end
end
return result
end
-- Remove empty placeholders from text (for cases where no values are provided)
function p.removeEmptyPlaceholders(text)
if not text then
return text
end
-- Remove any remaining $VARIABLE$ placeholders that weren't filled
local result = text:gsub("%$[A-Z_]+%$", "")
-- Clean up any resulting double spaces or awkward punctuation
result = result:gsub("%s+", " ") -- Multiple spaces to single space
result = result:gsub("^%s+", "") -- Leading whitespace
result = result:gsub("%s+$", "") -- Trailing whitespace
result = result:gsub("%s+%.", ".") -- Space before period
result = result:gsub("is a%s+based", "is based") -- Fix "is a based" to "is based"
result = result:gsub("is a%s+in", "is in") -- Fix "is a in" to "is in"
return result
end
-- Generate template with placeholder replacement
function p.generateTemplateWithValues(templateType, values)
if not templateType or templateType == "" then
return "Error: Template type is required"
end
-- Get base template
local baseTemplate = p.generateTemplate(templateType)
-- Get boilerplate and replace placeholders
local config = ConfigRepository.getConfig(templateType)
if config.boilerplate and config.boilerplate.intro then
local processedIntro = p.replacePlaceholders(config.boilerplate.intro, values)
-- Replace the boilerplate in the template
baseTemplate = baseTemplate:gsub(config.boilerplate.intro, processedIntro)
end
return baseTemplate
end
-- Get creator field definitions as JSON for JavaScript consumption
function p.getCreatorFieldDefinitionsJSON(frame)
local args = frame.args
local templateType = args.templateType or args[1]
if not templateType or templateType == "" then
return "{}"
end
local fieldDefinitions = p.getCreatorFieldDefinitions(templateType)
-- Convert to JSON-like string manually (simple approach)
local jsonParts = {}
for fieldKey, fieldDef in pairs(fieldDefinitions) do
local fieldJson = string.format(
'"%s":{"key":"%s","label":"%s","placeholder":"%s","required":%s}',
fieldKey,
fieldDef.key or "",
fieldDef.label or "",
fieldDef.placeholder or "",
fieldDef.required and "true" or "false"
)
table.insert(jsonParts, fieldJson)
end
return "{" .. table.concat(jsonParts, ",") .. "}"
end
return p