Module:ImportPerformances
From Angelina Jordan Wiki
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 and Video for each object.
-- Usage:
-- {{#invoke:ImportPerformances|import|source=PageWithJSON|headings=no|pre=no|filter_song=SongTitle}}
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, but leaves '&' alone
-- so URLs like ?a=1&b=2 are not double-escaped.
local function escapeForOutput(str)
if str == nil then return nil end
local t = s(str)
-- Do NOT encode '&' to avoid double escaping
t = t:gsub('<', '<')
t = t:gsub('>', '>')
t = t:gsub('{', '{')
t = t:gsub('}', '}')
return t
end
-- Escape URLs so they display literally (no auto-linking, pipes preserved)
local function escapeURL(url)
if not url or url == '' then return '' end
local safe = s(url)
safe = safe .. " " -- prevent autolinking by trailing space
return escapeForOutput(safe)
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
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 '{{' .. name .. table.concat(parts, '') .. '}}'
end
-- Sorting helper: compare by song, then by date
local function sortPerformances(a, b)
local sa, sb = (a.song or ''), (b.song or '')
if sa == sb then
local da, db = (a.date or ''), (b.date or '')
return da < db
else
return sa < sb
end
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
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
-- Optional filtering
local filter_song = args.filter_song and mw.text.trim(args.filter_song) or nil
local filterPage = (args.filterPage and args.filterPage:lower() == 'yes')
if filterPage then
filter_song = mw.title.getCurrentTitle().text
end
-- Sort by song, then by date
table.sort(data, sortPerformances)
local out = {}
local id = 0
local prevSong = nil
local enableHeadings = not (args.headings and args.headings:lower() == 'no')
local enablePre = not (args.pre and args.pre:lower() == 'no')
for _, obj in ipairs(data) do
local songText = obj.song or ''
-- Skip entries that do not match filter
if not filter_song or songText == filter_song then
-- Increment id only for included items
id = id + 1
-- Add heading when song changes
if enableHeadings and songText ~= prevSong then
prevSong = songText
local escSong = escapeForOutput(songText)
table.insert(out, '=== ' .. escSong .. ' ===')
end
-- Build args for Performance
local perfArgs = {}
if obj.song then perfArgs.song = s(obj.song) end
if obj.event then perfArgs.event = s(obj.event) end
if obj.context then
if type(obj.context) == 'table' then
perfArgs.context = table.concat(obj.context, '#')
else
perfArgs.context = s(obj.context)
end
end
if obj.date then perfArgs.date = s(obj.date) end
if obj.type then perfArgs["type"] = s(obj.type) end
if obj.pos then perfArgs.pos = s(obj.pos) end
if obj["partners"] then perfArgs["partners"] = s(obj["partners"]) end
if obj.comment then perfArgs.comment = s(obj.comment) end
perfArgs.id = tostring(id)
local perfOrder = { 'song', 'event', 'context', 'date', 'type', 'pos', 'with', 'comment', 'id' }
table.insert(out, '* ' .. makeTemplate('Performance', perfArgs, perfOrder))
-- Video entries
local function addVideo(url, duration, quality)
if not url or url == '' then return end
local vArgs = {
pid = tostring(id),
url = escapeURL(url), -- avoid autolinking and preserve pipes
}
if duration and duration ~= '' then vArgs.duration = s(duration) end
if quality and quality ~= '' then vArgs.quality = s(quality) end
local videoOrder = { 'pid', 'url', 'duration', 'quality' }
table.insert(out, '*: ' .. makeTemplate('Video', vArgs, videoOrder))
end
if obj.url and obj.url ~= '' then
addVideo(obj.url, obj.duration, obj.quality)
end
if obj.videos 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
end
-- Output all lines within <pre> for safe copying
if enablePre then
return '<pre>' .. table.concat(out, '\n') .. '</pre>'
else
return table.concat(out, '\n')
end
end
function p.importPartners(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
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
-- Optional filtering
local filter_song = args.filter_song and mw.text.trim(args.filter_song) or nil
local filterPage = (args.filterPage and args.filterPage:lower() == 'yes')
if filterPage then
filter_song = mw.title.getCurrentTitle().text
end
-- Sort by song, then by date
table.sort(data, sortPerformances)
local out = {}
local id = 0
local prevSong = nil
local enableHeadings = not (args.headings and args.headings:lower() == 'no')
local enablePre = not (args.pre and args.pre:lower() == 'no')
for _, obj in ipairs(data) do
local songText = obj.song or ''
-- Skip entries that do not match filter
if not filter_song or songText == filter_song then
-- Increment id only for included items
id = id + 1
-- Add heading when song changes
if enableHeadings and songText ~= prevSong then
prevSong = songText
local escSong = escapeForOutput(songText)
table.insert(out, '=== ' .. escSong .. ' ===')
end
-- Build args for Performance
local perfArgs = {}
if obj.song then perfArgs.song = s(obj.song) end
if obj.event then perfArgs.event = s(obj.event) end
if obj.context then
if type(obj.context) == 'table' then
perfArgs.context = table.concat(obj.context, '#')
else
perfArgs.context = s(obj.context)
end
end
if obj.date then perfArgs.date = s(obj.date) end
if obj.type then perfArgs["type"] = s(obj.type) end
if obj.pos then perfArgs.pos = s(obj.pos) end
if obj["partners"] then perfArgs["partners"] = s(obj["partners"]) end
if obj.comment then perfArgs.comment = s(obj.comment) end
perfArgs.id = tostring(id)
if obj["partners"] then -- only export if partner entries exist
local perfOrder = { 'song', 'event', 'context', 'date', 'type', 'pos', 'with', 'comment', 'id' }
table.insert(out, makeTemplate('no_Performance', perfArgs, perfOrder))
end
end
end
-- Output all lines within <pre> for safe copying
if enablePre then
return '<pre>' .. table.concat(out, '\n') .. '</pre>'
else
return table.concat(out, '\n')
end
end
function p.preloadSongPage(frame)
local title = mw.title.getCurrentTitle().text
return "This page is about: " .. title
end
return p