Actions

Module

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