Module:Episodes is the Lua backend for Template:Episodes. It handles all logic, sorting, validation, and output formatting.
Data source[edit]
All episode metadata is stored in a separate data module:
This data module contains only Lua tables and no wiki markup.
Each episode entry defines:
- Season number
- Page title
- Category name
- Whether the episode is a bonus episode
Each category entry defines:
- The part of the category that goes after the season and episode information
Responsibilities[edit]
The module:
- Detects episode parameters (
SxEy,SxBy) - Sorts episodes numerically and by season
- Prints season headers automatically
- Prints the bonus episode header only when needed
- Adds episode and season categories
- Ignores unknown or malformed parameters safely
- Tracks invalid or missing episode data via maintenance categories
Example episode entry[edit]
S1E4 = {
season = 1,
page = "S1E4 - The Ghouls",
category = "Fallout Television Series S1E4"
}
Bonus episodes additionally include:
bonus = true
Example category entry[edit]
character = "Characters",
Editing and extending[edit]
To add or update episodes:
- Edit
Module:Episodes/Data - Add or modify the relevant episode entries
- No changes to
Module:Episodesor{{Episodes}}are required
This design allows new seasons or bonus episodes to be added without altering template or logic code.
Maintenance categories[edit]
The module may automatically add the following tracking categories:
Category:Pages with missing episode dataCategory:Pages with invalid episode parameters
These categories are intended for maintenance and do not affect normal page display.
Technical notes[edit]
- Uses
mw.loadDatafor performance and caching - Output order is deterministic
- Safe against duplicate headers and malformed input
- Designed for future expansion without breaking existing pages
local p = {}
local tData = mw.loadData('Module:Episodes/Data')
local data = tData.Episodes
local cData = tData.Categories
-- ======================
-- Helpers
-- ======================
local function hasValue(v)
return v ~= nil and v ~= ''
end
local function isSeasonToggle(key)
return key:match('^S%d+$') ~= nil
end
local function parseEpisodeKey(key)
local season, kind, number = key:match('^S(%d+)([EB])(%d+)$')
if not season then return nil end
return tonumber(season), kind, tonumber(number)
end
-- ======================
-- Core renderer
-- ======================
local function renderEpisodes(frame)
local args = frame:getParent().args
local output = {}
local categories = {}
local catType = 'character'
local episodesBySeason = {}
local invalid = {}
-- ======================
-- Collect enabled episodes
-- ======================
for key, value in pairs(args) do
if hasValue(value) and not isSeasonToggle(key) then
if key == 'type' then
catType = value
else
local epData = data[key]
if epData then
local season, kind, number = parseEpisodeKey(key)
if season then
episodesBySeason[season] = episodesBySeason[season] or { E = {}, B = {} }
table.insert(episodesBySeason[season][kind], {
link = epData.link,
category = epData.category,
number = number
})
end
else
table.insert(invalid, key)
end
end
end
end
-- ======================
-- Sort seasons
-- ======================
local seasons = {}
for season in pairs(episodesBySeason) do
table.insert(seasons, season)
end
table.sort(seasons)
if cData[catType] ~= nil then
catType = cData[catType]
else
catType = nil
end
-- ======================
-- Render per season
-- ======================
for _, season in ipairs(seasons) do
local seasonData = episodesBySeason[season]
-- Skip empty seasons entirely
if #seasonData.E > 0 or #seasonData.B > 0 then
table.sort(seasonData.E, function(a, b) return a.number < b.number end)
table.sort(seasonData.B, function(a, b) return a.number < b.number end)
-- Season header
table.insert(
output,
string.format("'''''Season %d'''''", season)
)
table.insert(
categories,
string.format(
"[[Category:Fallout Television Series Season %d ".. catType .. "]]",
season
)
)
-- Normal episodes
for _, ep in ipairs(seasonData.E) do
table.insert(output, string.format('[[%s]]', ep.link))
if ep.category then
table.insert(categories, '[[' .. ep.category .. ' ' .. catType .. ']]')
end
end
-- Bonus episodes
if #seasonData.B > 0 then
local headerData = data['S' .. season .. 'B']
if headerData and headerData.header then
table.insert(output, string.format("'''''%s'''''", headerData.header))
if headerData.category then
table.insert(categories, '[[' .. headerData.category .. ' ' .. catType .. ']]')
end
end
for _, ep in ipairs(seasonData.B) do
table.insert(output, string.format('[[%s]]', ep.link))
if ep.category then
table.insert(categories, '[[' .. ep.category .. ' ' .. catType .. ']]')
end
end
end
end
end
--mw.logObject(output)
--mw.logObject(categories)
-- ======================
-- Invalid parameter tracking
-- ======================
if catType == nil then
categories = {}
table.insert(categories, '[[Category:Pages with invalid episode category types]]')
end
if #invalid > 0 then
table.insert(categories, '[[Category:Pages with invalid episode parameters]]')
end
return table.concat(output, '<br />') .. table.concat(categories)
end
-- Public entry points
function p.render(frame)
return renderEpisodes(frame)
end
function p.main(frame)
return renderEpisodes(frame)
end
return p
