Module:FamilyTree: Difference between revisions
From KB Lexicon
(Undo revision 1885 by Wylder Merrow (talk)) Tag: Undo |
No edit summary |
||
| Line 2: | Line 2: | ||
local cargo = mw.ext.cargo | local cargo = mw.ext.cargo | ||
local html = mw.html | |||
-- ========================================= | |||
-- Helpers | |||
-- ========================================= | |||
local function trim(s) | local function trim(s) | ||
if s | if not s then return nil end | ||
s = tostring(s) | s = tostring(s) | ||
s = mw.text.trim(s) | s = mw.text.trim(s) | ||
if s == '' then | if s == '' then return nil end | ||
return s | return s | ||
end | end | ||
local function | local function isRealValue(v) | ||
v = trim(v) | |||
if not | if not v then return false end | ||
return | |||
local lowered = mw.ustring.lower(v) | |||
if lowered == 'unknown' or lowered == 'none' or lowered == 'n/a' then | |||
return false | |||
end | end | ||
return true | |||
end | end | ||
local function | local function safeArray(v) | ||
if type(v) == 'table' then | |||
return v | |||
end | end | ||
return {} | |||
end | end | ||
local function | local function uniq(list) | ||
local seen = {} | |||
local out = {} | |||
for _, v in ipairs(list or {}) do | |||
if isRealValue(v) and not seen[v] then | |||
seen[v] = true | |||
table.insert(out, v) | |||
end | |||
end | |||
end | end | ||
return out | |||
return | |||
end | end | ||
local function | local function makeLink(name) | ||
if not isRealValue(name) then return '' end | |||
return string.format('[[%s|%s]]', name, name) | |||
return | |||
end | end | ||
local function | local function addUnique(list, value) | ||
if not isRealValue(value) then return end | |||
for _, existing in ipairs(list) do | |||
if existing == value then | |||
return | |||
end | |||
for _, | |||
end | end | ||
table.insert(list, value) | |||
end | end | ||
local function | local function getArg(frame, key) | ||
local v = frame.args[key] | |||
if | if isRealValue(v) then | ||
return | return trim(v) | ||
end | end | ||
if frame:getParent() then | |||
v = frame:getParent().args[key] | |||
if isRealValue(v) then | |||
return trim(v) | |||
return | |||
end | end | ||
end | end | ||
return | return nil | ||
end | end | ||
-- ========================================= | |||
-- Cargo loaders | |||
-- ========================================= | |||
local | local function queryCharacters() | ||
' | local results = cargo.query( | ||
' | 'Characters2', | ||
'Name,DisplayName,Gender,BirthDate,DeathDate,BirthFamily,CurrentFamily,Status', | |||
{ | { | ||
where = ' | where = 'Name IS NOT NULL', | ||
limit = | limit = 5000 | ||
} | } | ||
) | ) | ||
local | local people = {} | ||
for _, row in ipairs( | for _, row in ipairs(results) do | ||
local | local name = trim(row.Name) | ||
if isRealValue(name) then | |||
people[name] = { | |||
name = name, | |||
displayName = trim(row.DisplayName) or name, | |||
gender = trim(row.Gender), | |||
birthDate = trim(row.BirthDate), | |||
deathDate = trim(row.DeathDate), | |||
birthFamily = trim(row.BirthFamily), | |||
currentFamily = trim(row.CurrentFamily), | |||
status = trim(row.Status), | |||
parents = {}, | |||
children = {}, | |||
partners = {}, | |||
unions = {} | |||
} | |||
end | end | ||
end | end | ||
return | return people | ||
end | end | ||
local function | local function ensurePerson(people, name) | ||
if not isRealValue(name) then return nil end | |||
name = trim(name) | |||
if not people[name] then | |||
people[name] = { | |||
name = name, | |||
displayName = name, | |||
gender = nil, | |||
birthDate = nil, | |||
deathDate = nil, | |||
birthFamily = nil, | |||
currentFamily = nil, | |||
status = nil, | |||
parents = {}, | |||
children = {}, | |||
partners = {}, | |||
unions = {} | |||
} | } | ||
end | end | ||
return people[name] | |||
end | |||
local | local function loadParentChild(people) | ||
local results = cargo.query( | |||
'ParentChild', | 'ParentChild', | ||
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder', | 'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder', | ||
{ | { | ||
where = | where = 'Child IS NOT NULL', | ||
limit = | limit = 5000 | ||
} | } | ||
) | ) | ||
for _, row in ipairs(results) do | |||
local | local child = trim(row.Child) | ||
local | local p1 = trim(row.Parent1) | ||
local p2 = trim(row.Parent2) | |||
if isRealValue(child) then | |||
ensurePerson(people, child) | |||
if isRealValue(p1) then | |||
ensurePerson(people, p1) | |||
addUnique(people[child].parents, p1) | |||
addUnique(people[p1].children, child) | |||
end | end | ||
if | if isRealValue(p2) then | ||
ensurePerson(people, p2) | |||
addUnique(people[child].parents, p2) | |||
addUnique(people[p2].children, child) | |||
end | end | ||
end | end | ||
end | end | ||
end | end | ||
local function | local function loadUnions(people) | ||
local results = cargo.query( | |||
'Unions2', | |||
'UnionID,Partner1,Partner2,UnionType,Status,EngagementDate,MarriageDate,DivorceDate', | |||
local | |||
' | |||
'UnionID,Partner1,Partner2,UnionType,Status,MarriageDate, | |||
{ | { | ||
where = 'Partner1 | where = '(Partner1 IS NOT NULL OR Partner2 IS NOT NULL)', | ||
limit = | limit = 5000 | ||
} | } | ||
) | ) | ||
for _, | for _, row in ipairs(results) do | ||
local p1 = trim( | local unionID = trim(row.UnionID) | ||
local p2 = trim( | local p1 = trim(row.Partner1) | ||
local p2 = trim(row.Partner2) | |||
if p1 | if isRealValue(p1) then ensurePerson(people, p1) end | ||
if isRealValue(p2) then ensurePerson(people, p2) end | |||
end | |||
if isRealValue(p1) and isRealValue(p2) then | |||
addUnique(people[p1].partners, p2) | |||
addUnique(people[p2].partners, p1) | |||
table.insert(people[p1].unions, { | |||
unionID = unionID, | |||
partner = p2, | |||
unionType = trim(row.UnionType), | |||
status = trim(row.Status), | |||
engagementDate = trim(row.EngagementDate), | |||
marriageDate = trim(row.MarriageDate), | |||
divorceDate = trim(row.DivorceDate) | |||
}) | |||
table.insert(people[p2].unions, { | |||
unionID = unionID, | |||
partner = p1, | |||
unionType = trim(row.UnionType), | |||
status = trim(row.Status), | |||
engagementDate = trim(row.EngagementDate), | |||
marriageDate = trim(row.MarriageDate), | |||
divorceDate = trim(row.DivorceDate) | |||
}) | |||
end | end | ||
end | |||
end | |||
local function loadData() | |||
local people = queryCharacters() | |||
loadParentChild(people) | |||
loadUnions(people) | |||
for _, person in pairs(people) do | |||
person.parents = uniq(person.parents) | |||
person.children = uniq(person.children) | |||
person.partners = uniq(person.partners) | |||
end | end | ||
return people | |||
end | |||
-- ========================================= | |||
-- Legacy crash stopper | |||
-- ========================================= | |||
-- Old code was still calling this. Keep it defined so the module doesn't explode. | |||
local function makeFocusBranches() | |||
return '' | |||
end | |||
-- ========================================= | |||
-- Rendering helpers | |||
-- ========================================= | |||
return | local function renderPersonBox(name, extraClass) | ||
end | if not isRealValue(name) then return nil end | ||
local | local box = html.create('div') | ||
box:addClass('familytree-person') | |||
if isRealValue(extraClass) then | |||
box:addClass(extraClass) | |||
end | |||
end | |||
box:wikitext(makeLink(name)) | |||
return | return box | ||
end | end | ||
local function | local function renderRow(title, peopleList, rowClass) | ||
peopleList = safeArray(peopleList) | |||
if | if #peopleList == 0 then | ||
return | return nil | ||
end | end | ||
local | local row = html.create('div') | ||
row:addClass('familytree-row') | |||
if isRealValue(rowClass) then | |||
row:addClass(rowClass) | |||
end | |||
if isRealValue(title) then | |||
if | row:tag('div') | ||
:addClass('familytree-row-label') | |||
:wikitext(title) | |||
end | end | ||
local items = row:tag('div'):addClass('familytree-row-items') | |||
for _, name in ipairs(peopleList) do | |||
local box = renderPersonBox(name) | |||
if box then | |||
items:node(box) | |||
if | |||
end | end | ||
end | end | ||
return | return row | ||
end | end | ||
local function | local function sortNamesByDisplay(people, names) | ||
table.sort(names, function(a, b) | |||
local ad = (people[a] and people[a].displayName) or a | |||
local bd = (people[b] and people[b].displayName) or b | |||
return mw.ustring.lower(ad) < mw.ustring.lower(bd) | |||
end) | |||
end | |||
end | end | ||
local function | local function getGrandparents(people, personName) | ||
if not | local grandparents = {} | ||
local person = people[personName] | |||
if not person then return grandparents end | |||
for _, parentName in ipairs(person.parents) do | |||
local parent = people[parentName] | |||
for _, | if parent then | ||
for _, gp in ipairs(parent.parents) do | |||
addUnique(grandparents, gp) | |||
end | end | ||
end | end | ||
end | end | ||
return | return uniq(grandparents) | ||
end | end | ||
local function | local function getSiblings(people, personName) | ||
local siblings = {} | |||
local person = people[personName] | |||
if not person then return siblings end | |||
local | |||
local siblingSet = {} | |||
for _, parentName in ipairs(person.parents) do | |||
local parent = people[parentName] | |||
if parent then | |||
for _, childName in ipairs(parent.children) do | |||
if childName ~= personName then | |||
siblingSet[childName] = true | |||
if | |||
end | end | ||
end | end | ||
end | end | ||
end | |||
table.insert( | for name, _ in pairs(siblingSet) do | ||
table.insert(siblings, name) | |||
end | end | ||
return uniq(siblings) | |||
return | |||
end | end | ||
function | local function renderTreeForPerson(people, personName) | ||
local | local person = people[personName] | ||
if not person then | |||
return string.format( | |||
'<strong>FamilyTree error:</strong> No character found for "%s".', | |||
if not | personName or '(blank)' | ||
return ' | ) | ||
end | end | ||
local | local grandparents = getGrandparents(people, personName) | ||
local | local parents = uniq(person.parents) | ||
local | local partners = uniq(person.partners) | ||
local children = uniq(person.children) | |||
local siblings = getSiblings(people, personName) | |||
sortNamesByDisplay(people, grandparents) | |||
sortNamesByDisplay(people, parents) | |||
sortNamesByDisplay(people, siblings) | |||
sortNamesByDisplay(people, partners) | |||
sortNamesByDisplay(people, children) | |||
local root = html.create('div') | |||
root:addClass('familytree-wrapper') | |||
local gpRow = renderRow('Grandparents', grandparents, 'familytree-grandparents') | |||
if gpRow then root:node(gpRow) end | |||
local pRow = renderRow('Parents', parents, 'familytree-parents') | |||
if pRow then root:node(pRow) end | |||
local selfRow = html.create('div') | |||
selfRow:addClass('familytree-row') | |||
selfRow:addClass('familytree-focus-row') | |||
selfRow:tag('div') | |||
:addClass('familytree-row-label') | |||
:wikitext('Focus') | |||
local | local selfItems = selfRow:tag('div'):addClass('familytree-row-items') | ||
selfItems:node(renderPersonBox(personName, 'familytree-focus-person')) | |||
root:node(selfRow) | |||
local sibRow = renderRow('Siblings', siblings, 'familytree-siblings') | |||
end | if sibRow then root:node(sibRow) end | ||
local partnerRow = renderRow('Partners', partners, 'familytree-partners') | |||
if partnerRow then root:node(partnerRow) end | |||
local | |||
local childRow = renderRow('Children', children, 'familytree-children') | |||
if childRow then root:node(childRow) end | |||
end | |||
return tostring(root) | |||
end | |||
local | local function renderProfileForPerson(people, personName) | ||
local person = people[personName] | |||
if not person then | |||
return string.format( | |||
'<strong>FamilyTree error:</strong> No character found for "%s".', | |||
personName or '(blank)' | |||
) | |||
end | end | ||
local | local root = html.create('div') | ||
root:addClass('familytree-profile') | |||
root:tag('div') | |||
:addClass('familytree-profile-name') | |||
:wikitext(makeLink(person.name)) | |||
local dl = root:tag('dl') | |||
local | |||
if | local function addField(label, value) | ||
if isRealValue(value) then | |||
dl:tag('dt'):wikitext(label) | |||
dl:tag('dd'):wikitext(value) | |||
end | |||
end | end | ||
addField('Display Name', person.displayName ~= person.name and person.displayName or nil) | |||
addField('Gender', person.gender) | |||
addField('Birth Date', person.birthDate) | |||
addField('Death Date', person.deathDate) | |||
addField('Birth Family', person.birthFamily and makeLink(person.birthFamily) or nil) | |||
addField('Current Family', person.currentFamily and makeLink(person.currentFamily) or nil) | |||
addField('Status', person.status) | |||
if #person.parents > 0 then | |||
local | local parentLinks = {} | ||
for _, | for _, name in ipairs(person.parents) do | ||
table.insert(parentLinks, makeLink(name)) | |||
end | end | ||
addField('Parents', table.concat(parentLinks, ', ')) | |||
end | end | ||
local | if #person.partners > 0 then | ||
local partnerLinks = {} | |||
for _, name in ipairs(person.partners) do | |||
table.insert(partnerLinks, makeLink(name)) | |||
end | |||
addField('Partners', table.concat(partnerLinks, ', ')) | |||
end | |||
local | if #person.children > 0 then | ||
local childLinks = {} | |||
for _, name in ipairs(person.children) do | |||
table.insert(childLinks, makeLink(name)) | |||
end | |||
addField('Children', table.concat(childLinks, ', ')) | |||
end | end | ||
return tostring(root) | |||
end | |||
-- ========================================= | |||
-- Public functions | |||
-- ========================================= | |||
function p.tree(frame) | |||
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text | |||
local people = loadData() | |||
-- harmless legacy call so any older code path won't die | |||
makeFocusBranches() | |||
return renderTreeForPerson(people, personName) | |||
end | |||
function p.profile(frame) | |||
return | local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text | ||
local people = loadData() | |||
return renderProfileForPerson(people, personName) | |||
end | end | ||
return p | return p | ||
Revision as of 21:26, 29 March 2026
Documentation for this module may be created at Module:FamilyTree/doc
local p = {}
local cargo = mw.ext.cargo
local html = mw.html
-- =========================================
-- Helpers
-- =========================================
local function trim(s)
if not s then return nil end
s = tostring(s)
s = mw.text.trim(s)
if s == '' then return nil end
return s
end
local function isRealValue(v)
v = trim(v)
if not v then return false end
local lowered = mw.ustring.lower(v)
if lowered == 'unknown' or lowered == 'none' or lowered == 'n/a' then
return false
end
return true
end
local function safeArray(v)
if type(v) == 'table' then
return v
end
return {}
end
local function uniq(list)
local seen = {}
local out = {}
for _, v in ipairs(list or {}) do
if isRealValue(v) and not seen[v] then
seen[v] = true
table.insert(out, v)
end
end
return out
end
local function makeLink(name)
if not isRealValue(name) then return '' end
return string.format('[[%s|%s]]', name, name)
end
local function addUnique(list, value)
if not isRealValue(value) then return end
for _, existing in ipairs(list) do
if existing == value then
return
end
end
table.insert(list, value)
end
local function getArg(frame, key)
local v = frame.args[key]
if isRealValue(v) then
return trim(v)
end
if frame:getParent() then
v = frame:getParent().args[key]
if isRealValue(v) then
return trim(v)
end
end
return nil
end
-- =========================================
-- Cargo loaders
-- =========================================
local function queryCharacters()
local results = cargo.query(
'Characters2',
'Name,DisplayName,Gender,BirthDate,DeathDate,BirthFamily,CurrentFamily,Status',
{
where = 'Name IS NOT NULL',
limit = 5000
}
)
local people = {}
for _, row in ipairs(results) do
local name = trim(row.Name)
if isRealValue(name) then
people[name] = {
name = name,
displayName = trim(row.DisplayName) or name,
gender = trim(row.Gender),
birthDate = trim(row.BirthDate),
deathDate = trim(row.DeathDate),
birthFamily = trim(row.BirthFamily),
currentFamily = trim(row.CurrentFamily),
status = trim(row.Status),
parents = {},
children = {},
partners = {},
unions = {}
}
end
end
return people
end
local function ensurePerson(people, name)
if not isRealValue(name) then return nil end
name = trim(name)
if not people[name] then
people[name] = {
name = name,
displayName = name,
gender = nil,
birthDate = nil,
deathDate = nil,
birthFamily = nil,
currentFamily = nil,
status = nil,
parents = {},
children = {},
partners = {},
unions = {}
}
end
return people[name]
end
local function loadParentChild(people)
local results = cargo.query(
'ParentChild',
'Child,Parent1,Parent2,UnionID,RelationshipType,BirthOrder',
{
where = 'Child IS NOT NULL',
limit = 5000
}
)
for _, row in ipairs(results) do
local child = trim(row.Child)
local p1 = trim(row.Parent1)
local p2 = trim(row.Parent2)
if isRealValue(child) then
ensurePerson(people, child)
if isRealValue(p1) then
ensurePerson(people, p1)
addUnique(people[child].parents, p1)
addUnique(people[p1].children, child)
end
if isRealValue(p2) then
ensurePerson(people, p2)
addUnique(people[child].parents, p2)
addUnique(people[p2].children, child)
end
end
end
end
local function loadUnions(people)
local results = cargo.query(
'Unions2',
'UnionID,Partner1,Partner2,UnionType,Status,EngagementDate,MarriageDate,DivorceDate',
{
where = '(Partner1 IS NOT NULL OR Partner2 IS NOT NULL)',
limit = 5000
}
)
for _, row in ipairs(results) do
local unionID = trim(row.UnionID)
local p1 = trim(row.Partner1)
local p2 = trim(row.Partner2)
if isRealValue(p1) then ensurePerson(people, p1) end
if isRealValue(p2) then ensurePerson(people, p2) end
if isRealValue(p1) and isRealValue(p2) then
addUnique(people[p1].partners, p2)
addUnique(people[p2].partners, p1)
table.insert(people[p1].unions, {
unionID = unionID,
partner = p2,
unionType = trim(row.UnionType),
status = trim(row.Status),
engagementDate = trim(row.EngagementDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate)
})
table.insert(people[p2].unions, {
unionID = unionID,
partner = p1,
unionType = trim(row.UnionType),
status = trim(row.Status),
engagementDate = trim(row.EngagementDate),
marriageDate = trim(row.MarriageDate),
divorceDate = trim(row.DivorceDate)
})
end
end
end
local function loadData()
local people = queryCharacters()
loadParentChild(people)
loadUnions(people)
for _, person in pairs(people) do
person.parents = uniq(person.parents)
person.children = uniq(person.children)
person.partners = uniq(person.partners)
end
return people
end
-- =========================================
-- Legacy crash stopper
-- =========================================
-- Old code was still calling this. Keep it defined so the module doesn't explode.
local function makeFocusBranches()
return ''
end
-- =========================================
-- Rendering helpers
-- =========================================
local function renderPersonBox(name, extraClass)
if not isRealValue(name) then return nil end
local box = html.create('div')
box:addClass('familytree-person')
if isRealValue(extraClass) then
box:addClass(extraClass)
end
box:wikitext(makeLink(name))
return box
end
local function renderRow(title, peopleList, rowClass)
peopleList = safeArray(peopleList)
if #peopleList == 0 then
return nil
end
local row = html.create('div')
row:addClass('familytree-row')
if isRealValue(rowClass) then
row:addClass(rowClass)
end
if isRealValue(title) then
row:tag('div')
:addClass('familytree-row-label')
:wikitext(title)
end
local items = row:tag('div'):addClass('familytree-row-items')
for _, name in ipairs(peopleList) do
local box = renderPersonBox(name)
if box then
items:node(box)
end
end
return row
end
local function sortNamesByDisplay(people, names)
table.sort(names, function(a, b)
local ad = (people[a] and people[a].displayName) or a
local bd = (people[b] and people[b].displayName) or b
return mw.ustring.lower(ad) < mw.ustring.lower(bd)
end)
end
local function getGrandparents(people, personName)
local grandparents = {}
local person = people[personName]
if not person then return grandparents end
for _, parentName in ipairs(person.parents) do
local parent = people[parentName]
if parent then
for _, gp in ipairs(parent.parents) do
addUnique(grandparents, gp)
end
end
end
return uniq(grandparents)
end
local function getSiblings(people, personName)
local siblings = {}
local person = people[personName]
if not person then return siblings end
local siblingSet = {}
for _, parentName in ipairs(person.parents) do
local parent = people[parentName]
if parent then
for _, childName in ipairs(parent.children) do
if childName ~= personName then
siblingSet[childName] = true
end
end
end
end
for name, _ in pairs(siblingSet) do
table.insert(siblings, name)
end
return uniq(siblings)
end
local function renderTreeForPerson(people, personName)
local person = people[personName]
if not person then
return string.format(
'<strong>FamilyTree error:</strong> No character found for "%s".',
personName or '(blank)'
)
end
local grandparents = getGrandparents(people, personName)
local parents = uniq(person.parents)
local partners = uniq(person.partners)
local children = uniq(person.children)
local siblings = getSiblings(people, personName)
sortNamesByDisplay(people, grandparents)
sortNamesByDisplay(people, parents)
sortNamesByDisplay(people, siblings)
sortNamesByDisplay(people, partners)
sortNamesByDisplay(people, children)
local root = html.create('div')
root:addClass('familytree-wrapper')
local gpRow = renderRow('Grandparents', grandparents, 'familytree-grandparents')
if gpRow then root:node(gpRow) end
local pRow = renderRow('Parents', parents, 'familytree-parents')
if pRow then root:node(pRow) end
local selfRow = html.create('div')
selfRow:addClass('familytree-row')
selfRow:addClass('familytree-focus-row')
selfRow:tag('div')
:addClass('familytree-row-label')
:wikitext('Focus')
local selfItems = selfRow:tag('div'):addClass('familytree-row-items')
selfItems:node(renderPersonBox(personName, 'familytree-focus-person'))
root:node(selfRow)
local sibRow = renderRow('Siblings', siblings, 'familytree-siblings')
if sibRow then root:node(sibRow) end
local partnerRow = renderRow('Partners', partners, 'familytree-partners')
if partnerRow then root:node(partnerRow) end
local childRow = renderRow('Children', children, 'familytree-children')
if childRow then root:node(childRow) end
return tostring(root)
end
local function renderProfileForPerson(people, personName)
local person = people[personName]
if not person then
return string.format(
'<strong>FamilyTree error:</strong> No character found for "%s".',
personName or '(blank)'
)
end
local root = html.create('div')
root:addClass('familytree-profile')
root:tag('div')
:addClass('familytree-profile-name')
:wikitext(makeLink(person.name))
local dl = root:tag('dl')
local function addField(label, value)
if isRealValue(value) then
dl:tag('dt'):wikitext(label)
dl:tag('dd'):wikitext(value)
end
end
addField('Display Name', person.displayName ~= person.name and person.displayName or nil)
addField('Gender', person.gender)
addField('Birth Date', person.birthDate)
addField('Death Date', person.deathDate)
addField('Birth Family', person.birthFamily and makeLink(person.birthFamily) or nil)
addField('Current Family', person.currentFamily and makeLink(person.currentFamily) or nil)
addField('Status', person.status)
if #person.parents > 0 then
local parentLinks = {}
for _, name in ipairs(person.parents) do
table.insert(parentLinks, makeLink(name))
end
addField('Parents', table.concat(parentLinks, ', '))
end
if #person.partners > 0 then
local partnerLinks = {}
for _, name in ipairs(person.partners) do
table.insert(partnerLinks, makeLink(name))
end
addField('Partners', table.concat(partnerLinks, ', '))
end
if #person.children > 0 then
local childLinks = {}
for _, name in ipairs(person.children) do
table.insert(childLinks, makeLink(name))
end
addField('Children', table.concat(childLinks, ', '))
end
return tostring(root)
end
-- =========================================
-- Public functions
-- =========================================
function p.tree(frame)
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text
local people = loadData()
-- harmless legacy call so any older code path won't die
makeFocusBranches()
return renderTreeForPerson(people, personName)
end
function p.profile(frame)
local personName = getArg(frame, 'person') or mw.title.getCurrentTitle().text
local people = loadData()
return renderProfileForPerson(people, personName)
end
return p