Module:ImportPerformances

From Angelina Jordan Wiki
Revision as of 08:35, 3 October 2025 by Most2dot0 (talk | contribs) (Rewrite, to replace certain characters with their HTML encoding, so we get a litereal output e.g. of headings)

Documentation for this module may be created at Module:ImportPerformances/doc

-- Module:ImportPerformances
-- Reads a wiki page containing JSON (an array of objects) and emits literal transclusion calls
-- of Performance-devel and Video-devel for each object.
-- Usage: {{#invoke:ImportPerformances|import|source=PageWithJSON}}

local p = {}

-- Helper: safe tostring
local function s(v)
  if v == nil then return nil end
  return tostring(v)
end

-- Escape text for safe literal display in HTML output.
-- Converts <, >, & to entities and converts { and } to numeric entities
-- so that {{...}} is not treated as a template.
local function escapeForOutput(str)
  if str == nil then return nil end
  local t = s(str)
  t = t:gsub('&', '&amp;')
  t = t:gsub('<', '&lt;')
  t = t:gsub('>', '&gt;')
  t = t:gsub('{', '&#123;')
  t = t:gsub('}', '&#125;')
  return t
end

-- Build a template call as literal text with predictable argument order when provided.
local function makeTemplate(name, args, order)
  local parts = {}
  if order and type(order) == 'table' then
    for _, k in ipairs(order) do
      local v = args[k]
      if v ~= nil and v ~= '' then
        table.insert(parts, '|' .. k .. '=' .. escapeForOutput(v))
      end
    end
    -- include any remaining keys not in the order list
    for k, v in pairs(args) do
      local found = false
      for _, ok in ipairs(order) do if ok == k then found = true break end end
      if not found and v ~= nil and v ~= '' then
        table.insert(parts, '|' .. k .. '=' .. escapeForOutput(v))
      end
    end
  else
    for k, v in pairs(args) do
      if v ~= nil and v ~= '' then
        table.insert(parts, '|' .. k .. '=' .. escapeForOutput(v))
      end
    end
  end
  return '&#123;&#123;' .. name .. table.concat(parts, '') .. '&#125;&#125;'
end

function p.import(frame)
  local args = frame.args or {}
  local source = args.source or args[1]
  if not source or source == "" then
    return "Error: supply a 'source' page name containing JSON, e.g. |source=Module:MyData"
  end

  local titleObj = mw.title.new(source)
  if not titleObj then
    return "Error: invalid page name: " .. source
  end

  local raw = titleObj:getContent()
  if not raw then
    return "Error: could not read page content: " .. source
  end

  -- JSON decode function: prefer mw.text.jsonDecode if available, else fall back to Module:JSON
  local decode
  if mw.text and mw.text.jsonDecode then
    decode = mw.text.jsonDecode
  else
    local ok, json = pcall(require, 'Module:JSON')
    if ok and json and type(json.decode) == 'function' then
      decode = json.decode
    else
      return "Error: No JSON decoder available (need mw.text.jsonDecode or Module:JSON)."
    end
  end

  local ok2, data = pcall(decode, raw)
  if not ok2 then
    return "Error decoding JSON: " .. tostring(data)
  end

  if type(data) ~= 'table' then
    return "Error: JSON must be an array of objects"
  end

  local out = {}
  local id = 0
  local prevSong = nil

  for _, obj in ipairs(data) do
    id = id + 1

    -- Insert heading when song changes. Escape '=' so it is shown literally.
    local songText = obj.song or ''
    if songText ~= prevSong then
      prevSong = songText
      local escSong = escapeForOutput(songText)
      table.insert(out, '&#61;&#61;&#61; ' .. escSong .. ' &#61;&#61;&#61;')
    end

    -- Build args for Performance-devel. Only include keys that exist in JSON.
    local perfArgs = {}
    if obj.song ~= nil then perfArgs.song = s(obj.song) end
    if obj.event ~= nil then perfArgs.event = s(obj.event) end

    if obj.context ~= nil then
      if type(obj.context) == 'table' then
        local ctx = {}
        for i, e in ipairs(obj.context) do ctx[i] = (e == nil and '' or s(e)) end
        perfArgs.context = table.concat(ctx, '#')
      else
        perfArgs.context = s(obj.context)
      end
    end

    if obj.date ~= nil then perfArgs.date = s(obj.date) end
    if obj.type ~= nil then perfArgs["type"] = s(obj.type) end
    if obj.pos ~= nil then perfArgs.pos = s(obj.pos) end
    if obj["with"] ~= nil then perfArgs["with"] = s(obj["with"]) end
    if obj.comment ~= nil then perfArgs.comment = s(obj.comment) end

    perfArgs.id = tostring(id)

    -- Use a fixed argument order for readability
    local perfOrder = { 'song', 'event', 'context', 'date', 'type', 'pos', 'with', 'comment', 'id' }
    table.insert(out, makeTemplate('Performance-devel', perfArgs, perfOrder))

    -- Videos: collect and emit one line per video
    local function addVideo(url, duration, quality)
      if not url or url == '' then return end
      local vArgs = { id = tostring(id), url = s(url) }
      if duration ~= nil and duration ~= '' then vArgs.duration = s(duration) end
      if quality ~= nil and quality ~= '' then vArgs.quality = s(quality) end
      local videoOrder = { 'id', 'url', 'duration', 'quality' }
      table.insert(out, makeTemplate('Video-devel', vArgs, videoOrder))
    end

    if obj.url ~= nil and obj.url ~= '' then
      addVideo(obj.url, obj.duration, obj.quality)
    end

    if obj.videos ~= nil and type(obj.videos) == 'table' then
      for _, v in ipairs(obj.videos) do
        if v ~= nil then
          local vurl = v.url or v["url"]
          local vdur = v.duration or obj.duration
          local vqual = v.quality or obj.quality
          addVideo(vurl, vdur, vqual)
        end
      end
    end
  end

  -- Join with single newlines and wrap in <pre> so layout is preserved.
  return '<pre>' .. table.concat(out, '\n') .. '</pre>'
end

return p