Documentation for this module may be created at Module:Creators/doc
local p = {}
-- Replace the old "args = frame:getParent() and ..." line with this:
local function getArgs(frame)
local out = {}
-- 1) arguments passed directly to #invoke
for k, v in pairs(frame.args or {}) do
if v ~= '' then out[k] = v end
end
-- 2) fall back to parent template arguments only if not already set
local parent = frame:getParent()
if parent and parent.args then
for k, v in pairs(parent.args) do
if (out[k] == nil or out[k] == '') and v ~= '' then
out[k] = v
end
end
end
return out
end
-- ========= utilities =========
local u = mw.ustring
local function trim(s) return (u.gsub(s or "", "^%s*(.-)%s*$", "%1")) end
-- Remove nicknames in straight or curly quotes: Jack "King" Kirby / Jack “King” Kirby
local function stripQuotedNicknames(s)
s = u.gsub(s or "", '%b""', "") -- remove "…"
s = u.gsub(s, '“[^”]*”', "") -- remove “…” (curly)
s = u.gsub(s, "%s+", " ") -- collapse double spaces
return trim(s)
end
-- Remove nicknames for linking (but keep them for display)
local function stripQuotesForCategory(name)
local clean = u.gsub(name or "", '%b""', "") -- remove "…"
clean = u.gsub(clean, '“[^”]*”', "") -- remove “…” (curly)
clean = u.gsub(clean, "%s+", " ") -- collapse double spaces
return trim(clean)
end
-- Split "Name (suffix)" -> base, " (suffix)" (suffix kept for display only)
local function splitBaseAndSuffix(name)
local base, paren = u.match(name or "", "^(.-)%s*(%b())%s*$")
if base then
return trim(base), paren or ""
else
return trim(name or ""), ""
end
end
-- Display text: underscores -> nbsp, bold
local function displayFromBase(base)
base = base or ""
local disp = u.gsub(base, "_", " ")
return string.format("'''%s'''", disp)
end
local function makeCategoryLink(base, display)
base = trim(base or "")
display = display or displayFromBase(base) -- fallback if caller passed nil
return string.format("[[:Category:%s|%s]]", base, display)
end
-- Collect role, role2, role3… (stop at first missing after role2)
local function collectRoleArgs(args, key)
local out = {}
if args[key] and args[key] ~= "" then table.insert(out, args[key]) end
for i = 2, 50 do
local k = key .. i
local v = args[k]
if v and v ~= "" then
table.insert(out, v)
else
if i > 2 then break end
end
end
return out
end
-- ========= row builder =========
local function buildRoleRow(args, key, label, width, roleCatSuffix)
local vals = collectRoleArgs(args, key)
if #vals == 0 then return "" end
local pieces, seen, cats = {}, {}, {}
for _, raw in ipairs(vals) do
-- Keep quotes for display, but strip them for the link/categories
local baseForCategory, suffix = splitBaseAndSuffix(stripQuotesForCategory(raw))
-- For the link *text*, remove the parentheses but keep any quotes
local displayBase = select(1, splitBaseAndSuffix(raw)) -- raw minus (...) only
if baseForCategory ~= "" then
local dedupKey = baseForCategory .. (suffix or "")
if not seen[dedupKey] then
seen[dedupKey] = true
local link = makeCategoryLink(baseForCategory, displayFromBase(displayBase))
if suffix and suffix ~= "" then
link = link .. " " .. suffix
end
table.insert(pieces, link)
local cat = string.format("[[Category:%s%s]]", baseForCategory, roleCatSuffix)
cats[cat] = true
end
end
end
if #pieces == 0 then return "" end
local row = string.format(
'<tr><td width="%d">\'\'\'%s\'\'\'</td><td>%s</td></tr>',
width, label, table.concat(pieces, ", ")
)
-- emit categories once each
local catList = {}
for c,_ in pairs(cats) do table.insert(catList, c) end
table.sort(catList)
return row .. table.concat(catList)
end
-- Map of roles -> {label, width, role-category-suffix}
local ROLE_MAP = {
plotter = { "Plot", 20, " (plotter)" },
writer = { "Story", 20, " (writer)" },
story_consultant = { "Story consults", 100, " (story consultant)" },
breakdown_penciller = { "Breakdown pencils", 140, " (breakdown penciller)" },
layout_penciller = { "Layout pencils", 100, " (layout penciller)" },
artist = { "Art", 20, " (artist)" },
penciller = { "Pencils", 40, " (penciller)" },
finisher = { "Finishes", 40, " (finisher)" },
inker = { "Inks", 20, " (inker)" },
colorist = { "Colors", 30, " (colorist)" },
color_separator = { "Color separations", 140, " (color separator)" },
letterer = { "Lettering", 50, " (letterer)" },
consulting_editor = { "Consulting editing", 140, " (consulting editor)" },
assistant_editor = { "Assistant editing", 140, " (assistant editor)" },
associate_editor = { "Associate editing", 140, " (associate editor)" },
editor = { "Editing", 40, " (editor)" },
senior_editor = { "Senior editing", 110, " (senior editor)" },
group_editor = { "Group editing", 100, " (group editor)" },
design = { "Design", 30, " (design)" },
consultant = { "Consultant", 50, " (consultant)" },
special_thanks = { "Special thanks", 110, " (group editor)" },
}
function p.render(frame)
local args = getArgs(frame)
-- debug:
if not args or (not args.writer and not args.plotter and not args.artist) then
return '<div style="color:red">Creators: saw no args</div>'
end
local out = {}
-- keep rows in the order defined above
local keysInOrder = {
"plotter","writer","story_consultant","breakdown_penciller","layout_penciller",
"artist","penciller","finisher","inker","colorist","color_separator",
"letterer","consulting_editor","assistant_editor","associate_editor",
"editor","senior_editor","group_editor","design","consultant","special_thanks"
}
for _, key in ipairs(keysInOrder) do
local cfg = ROLE_MAP[key]
local row = buildRoleRow(args, key, cfg[1], cfg[2], cfg[3])
if row ~= "" then table.insert(out, row) end
end
return table.concat(out)
end
-- ========= cover renderer =========
-- Map of cover roles -> {label, cover-category-suffix}
local COVER_ROLE_MAP = {
designer = { "Design", " (cover designer)" },
sketcher = { "Sketch", " (cover sketcher)" },
photographer = { "Photograph", " (cover photographer)" },
illustrator = { "Illustration", " (cover illustrator)" },
artist = { "Art", " (cover artist)" },
penciller = { "Pencils", " (cover penciller)" },
inker = { "Inks", " (cover inker)" },
colorist = { "Colors", " (cover colorist)" },
color_separator = { "Color separations", " (cover color separator)" },
color_separations = { "Color separations", " (cover color separator)" }, -- alias
}
-- Reuse your existing collectRoleArgs(), splitBaseAndSuffix(), stripQuotesForCategory(),
-- displayFromBase(), makeCategoryLink(), and getArgs() utilities from the main module.
-- Build normalized entry objects for a role:
-- { baseForCategory, displayBase, suffix } with de-dup by (base+suffix)
local function buildRoleEntries(args, key)
local entries, seen = {}, {}
for _, raw in ipairs(collectRoleArgs(args, key)) do
local baseForCategory, suffix = splitBaseAndSuffix(stripQuotesForCategory(raw))
local displayBase = select(1, splitBaseAndSuffix(raw)) -- keep quotes, drop (...)
if baseForCategory ~= "" then
local dkey = baseForCategory .. (suffix or "")
if not seen[dkey] then
seen[dkey] = true
table.insert(entries, {
base = baseForCategory,
display = displayBase,
suffix = suffix or "",
})
end
end
end
return entries
end
-- Convert entries -> lookup by base
local function mapByBase(entries)
local m = {}
for _, e in ipairs(entries) do m[e.base] = e end
return m
end
-- Build one inline "Label: link, link …" line with cover categories
local function buildCoverLine(args, key, label, roleCatSuffix)
local vals = collectRoleArgs(args, key)
if #vals == 0 then return "", {}, {} end
local pieces, seen, cats = {}, {}, {}
for _, raw in ipairs(vals) do
local baseForCategory, suffix = splitBaseAndSuffix(stripQuotesForCategory(raw))
local displayBase = select(1, splitBaseAndSuffix(raw))
if baseForCategory ~= "" then
local dedupKey = baseForCategory .. (suffix or "")
if not seen[dedupKey] then
seen[dedupKey] = true
local link = makeCategoryLink(baseForCategory, displayFromBase(displayBase))
if suffix and suffix ~= "" then link = link .. " " .. suffix end
table.insert(pieces, link)
cats[string.format("[[Category:%s%s]]", baseForCategory, roleCatSuffix)] = true
end
end
end
if #pieces == 0 then return "", {}, {} end
return string.format("'''%s:''' %s", label, table.concat(pieces, ", ")), cats, {}
end
-- Turn entries -> "'''Label:''' link, link" and collect categories
local function lineFromEntries(entries, label, coverSuffix)
if #entries == 0 then return "", {} end
local pieces, cats = {}, {}
for _, e in ipairs(entries) do
local link = makeCategoryLink(e.base, displayFromBase(e.display))
if e.suffix ~= "" then link = link .. " " .. e.suffix end
table.insert(pieces, link)
cats[string.format("[[Category:%s%s]]", e.base, coverSuffix)] = true
end
return string.format("'''%s:''' %s", label, table.concat(pieces, ", ")), cats
end
function p.renderCover(frame)
local args = getArgs(frame)
local any =
(args.designer or args.sketcher or args.photographer or args.illustrator or
args.artist or args.penciller or args.inker or args.colorist or
args.color_separator or args.color_separations)
if not any then return "" end
local lines, catsAll = {}, {}
local function push(s)
if s and mw.text.trim(s) ~= "" then
table.insert(lines, s)
end
end
-- 1) Simple roles FIRST (pre-artist)
local simpleKeys = { "designer","sketcher","photographer","illustrator" }
for _, key in ipairs(simpleKeys) do
local cfg = COVER_ROLE_MAP[key]
local line, cats = buildCoverLine(args, key, cfg[1], cfg[2])
push(line)
for c,_ in pairs(cats) do catsAll[c] = true end
end
-- 2) Artist + Colorist logic (build once; print in the order you want)
local artistEntries = buildRoleEntries(args, "artist")
local coloristEntries = buildRoleEntries(args, "colorist")
local byBaseArtist = mapByBase(artistEntries)
local byBaseColor = mapByBase(coloristEntries)
local merged, onlyArtist, onlyColor = {}, {}, {}
-- Intersection: Art and color
for base, a in pairs(byBaseArtist) do
local c = byBaseColor[base]
if c then
table.insert(merged, {
base = base,
display = a.display,
suffix = (a.suffix ~= "" and a.suffix or c.suffix)
})
end
end
-- Unique artists
for _, a in ipairs(artistEntries) do
if not byBaseColor[a.base] then table.insert(onlyArtist, a) end
end
-- Unique colorists (we will print this AFTER Inks)
for _, c in ipairs(coloristEntries) do
if not byBaseArtist[c.base] then table.insert(onlyColor, c) end
end
-- (Art and color)
if #merged > 0 then
local pieces = {}
for _, e in ipairs(merged) do
local link = makeCategoryLink(e.base, displayFromBase(e.display))
if e.suffix ~= "" then link = link .. " " .. e.suffix end
table.insert(pieces, link)
catsAll[string.format("[[Category:%s%s]]", e.base, COVER_ROLE_MAP.artist[2])] = true
catsAll[string.format("[[Category:%s%s]]", e.base, COVER_ROLE_MAP.colorist[2])] = true
end
push("'''Art and color:''' " .. table.concat(pieces, ", "))
end
-- Art (only-artist)
do
local artLine, artCats = lineFromEntries(onlyArtist, COVER_ROLE_MAP.artist[1], COVER_ROLE_MAP.artist[2])
push(artLine)
for c,_ in pairs(artCats) do catsAll[c] = true end
end
-- 3) Pencils, Inks
for _, key in ipairs({"penciller","inker"}) do
local cfg = COVER_ROLE_MAP[key]
local line, cats = buildCoverLine(args, key, cfg[1], cfg[2])
push(line)
for c,_ in pairs(cats) do catsAll[c] = true end
end
-- 4) Colors (only-color) — printed here, after Inks
do
local colLine, colCats = lineFromEntries(onlyColor, COVER_ROLE_MAP.colorist[1], COVER_ROLE_MAP.colorist[2])
push(colLine)
for c,_ in pairs(colCats) do catsAll[c] = true end
end
-- 5) Color separations (canonical, then alias)
for _, key in ipairs({"color_separator","color_separations"}) do
local cfg = COVER_ROLE_MAP[key]
local line, cats = buildCoverLine(args, key, cfg[1], cfg[2])
push(line)
for c,_ in pairs(cats) do catsAll[c] = true end
end
if #lines == 0 then return "" end
local catList = {}
for c,_ in pairs(catsAll) do table.insert(catList, c) end
table.sort(catList)
return table.concat(lines, "<br>") .. table.concat(catList)
end
return p