Module:Engineers
Documentation for this module may be created at Module:Engineers/doc
-- Module:Engineers
-- Reads canonical Engineer facts from Module:Data/Engineers/<Name> (JSON
-- content-model pages) and formats them for three consumers: an Engineer's
-- own infobox (via Template:Engineer infobox), List of Engineers, and
-- Engineer Capability Comparison. One record, one module, three consumers
-- -- see Elite Dangerous Field Manual:Adding Structured Data.
--
-- Adding a new engineer: create Module:Data/Engineers/<Name> in the same
-- shape as the existing four, then append the name to
-- Module:Data/Engineers/index. Don't edit this module unless you're
-- changing the schema itself.
local p = {}
-- Field order/labels shared by both the JSON-backed path and the manual
-- fallback path, so {{Engineer infobox|system=...|facility=...}} produces
-- the same shape of infobox whether or not a JSON record exists yet.
local FIELD_ORDER = {
{ key = 'engineer_type', label = 'Engineer type', group = 'ENGINEER' },
{ key = 'system', label = 'System' },
{ key = 'body', label = 'Body' },
{ key = 'facility', label = 'Facility' },
{ key = 'region', label = 'Region' },
{ key = 'allegiance', label = 'Allegiance' },
{ key = 'specialties', label = 'Specialties' },
{ key = 'max_grade', label = 'Maximum grade' },
{ key = 'prerequisite_engineer', label = 'Prerequisite', group = 'ACCESS' },
{ key = 'discovery_requirement', label = 'Discovery' },
{ key = 'invitation_requirement', label = 'Invitation' },
{ key = 'unlock_requirement', label = 'Unlock' },
}
local function loadRecord(name)
local title = mw.title.new('Data/Engineers/' .. name, 'Module')
if not title then
return nil
end
local content = title:getContent()
if not content then
return nil
end
local ok, data = pcall(mw.text.jsonDecode, content)
if not ok then
return nil
end
return data
end
local function loadIndex()
local title = mw.title.new('Data/Engineers/index', 'Module')
local content = title and title:getContent()
if not content then
return {}
end
local ok, data = pcall(mw.text.jsonDecode, content)
if not ok then
return {}
end
return data
end
local function joinSpecialties(d)
if d.specialties and type(d.specialties) == 'table' then
return table.concat(d.specialties, ', ')
end
return d.specialties
end
-- Builds a plain { key = displayValue } table from either a JSON record
-- or manually-supplied template args, using the same FIELD_ORDER either
-- way.
local function buildFields(d, pargs)
local fields = {}
if d then
for _, f in ipairs(FIELD_ORDER) do
fields[f.key] = d[f.key]
end
fields.specialties = joinSpecialties(d)
if d.prerequisite_engineer then
local note = d.prerequisite_engineer_note and (' ' .. d.prerequisite_engineer_note) or ''
fields.prerequisite_engineer = '[[' .. d.prerequisite_engineer .. ']]' .. note
else
fields.prerequisite_engineer = d.prerequisite_engineer_display or 'None — available from the start'
end
else
for _, f in ipairs(FIELD_ORDER) do
fields[f.key] = pargs[f.key]
end
end
-- Infobox pages may override the structured data directly in the article
-- template call. This keeps the sidebar editable from page edit/source
-- while still letting JSON records provide defaults and power comparison
-- tables. Empty parameters do not erase structured defaults.
for _, f in ipairs(FIELD_ORDER) do
if pargs[f.key] ~= nil and pargs[f.key] ~= '' then
fields[f.key] = pargs[f.key]
end
end
return fields
end
-- {{#invoke:Engineers|infobox}}, called from Template:Engineer infobox.
-- Falls back to whatever manual named params were passed if no JSON
-- record exists yet, so the wrapper template is safe to adopt one page at
-- a time rather than needing every engineer's data backfilled first.
function p.infobox(frame)
local parent = frame:getParent()
local pargs = parent and parent.args or {}
local name = pargs.name or mw.title.getCurrentTitle().text
local d = loadRecord(name)
local fields = buildFields(d, pargs)
local image = pargs.image
if ( image == nil or image == '' ) and d and d.image then
image = d.image
end
local args = { name = name, image = image }
local n = 0
for _, f in ipairs(FIELD_ORDER) do
local value = fields[f.key]
if value ~= nil and value ~= '' then
n = n + 1
if f.group then
args['group' .. n] = f.group
end
args['label' .. n] = f.label
args['row' .. n] = value
end
end
return frame:expandTemplate{ title = 'Infobox', args = args }
end
-- {{#invoke:Engineers|list}}, used by List of Engineers.
function p.list()
local rows = {}
for _, name in ipairs(loadIndex()) do
local d = loadRecord(name)
if d then
table.insert(rows, string.format(
'|-\n| [[%s]] || %s || %s || %s',
d.name,
d.system or '—',
joinSpecialties(d) or '—',
d.max_grade or 'Not yet confirmed'
))
end
end
return '{| class="wikitable sortable"\n! Engineer !! System !! Specialties !! Maximum grade\n'
.. table.concat(rows, '\n') .. '\n|}'
end
-- {{#invoke:Engineers|comparison}}, used by Engineer Capability Comparison.
function p.comparison()
local rows = {}
for _, name in ipairs(loadIndex()) do
local d = loadRecord(name)
if d then
local prereq = d.prerequisite_engineer and ('[[' .. d.prerequisite_engineer .. ']]') or ( d.prerequisite_engineer_display or 'None' )
table.insert(rows, string.format(
'|-\n| [[%s]] || %s || %s || %s',
d.name,
joinSpecialties(d) or '—',
prereq,
d.unlock_requirement or 'Not yet confirmed'
))
end
end
return '{| class="wikitable sortable"\n! Engineer !! Specialties !! Prerequisite Engineer !! Unlock requirement\n'
.. table.concat(rows, '\n') .. '\n|}'
end
-- {{#invoke:Engineers|count}}, returns how many Engineers currently have a
-- canonical JSON record in Module:Data/Engineers/index -- used so pages that
-- cite this number (e.g. List of Engineers' {{Article status}} uncertainty
-- text) stay correct automatically as records are added, instead of a
-- hand-typed number silently going stale.
function p.count()
return tostring(#loadIndex())
end
return p