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 multiple consumers:
--
-- * Engineer infoboxes via Template:Engineer infobox
-- * List of Engineers
-- * Engineer Capability Comparison
--
-- This module adds appropriate internal wiki links when rendering content.
--
-- Adding a new Engineer:
-- 1. Create Module:Data/Engineers/<Name>
-- 2. Use the same JSON schema as the existing Engineer records
-- 3. Append the Engineer name to Module:Data/Engineers/index
--
-- See Elite Dangerous Field Manual:Adding Structured Data.
local p = {}
-- Field order and labels shared by both the structured-data path and the
-- manual fallback path.
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' },
}
------------------------------------------------------------------------
-- Utility functions
------------------------------------------------------------------------
-- Converts a plain structured-data value into an internal wiki link.
--
-- Examples:
--
-- wikilink('Deciat')
-- -> [[Deciat]]
--
-- wikilink('Farseer Inc')
-- -> [[Farseer Inc]]
--
-- Existing wiki links are left untouched so explicit/manual overrides
-- remain safe.
local function wikilink(value, target)
if value == nil or value == '' then
return value
end
if type(value) ~= 'string' then
return value
end
-- Already contains a wiki link.
if value:find('%[%[') then
return value
end
target = target or value
if target == value then
return '[[' .. value .. ']]'
end
return '[[' .. target .. '|' .. value .. ']]'
end
------------------------------------------------------------------------
-- Data loading
------------------------------------------------------------------------
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
------------------------------------------------------------------------
-- Specialty formatting
------------------------------------------------------------------------
-- Structured Engineer records should preferably store specialties as an
-- array:
--
-- "specialties": [
-- "Frame Shift Drive",
-- "Power Plant",
-- "Thrusters"
-- ]
--
-- This allows each module name to become its own internal link.
local function joinSpecialties(d, linked)
if not d or not d.specialties then
return nil
end
if type(d.specialties) == 'table' then
local values = {}
for _, specialty in ipairs(d.specialties) do
if linked then
table.insert(values, wikilink(specialty))
else
table.insert(values, specialty)
end
end
return table.concat(values, ', ')
end
-- Preserve legacy/string values unchanged. These may contain descriptive
-- prose rather than a single module name, so automatically linking the
-- entire string would not always be appropriate.
return d.specialties
end
------------------------------------------------------------------------
-- Unlock formatting
------------------------------------------------------------------------
-- Supports a newer structured unlock format while retaining compatibility
-- with the existing unlock_requirement prose field.
--
-- Example JSON:
--
-- "unlock": {
-- "action": "Deliver",
-- "quantity": 1,
-- "item": "Meta-Alloys"
-- }
--
-- Produces:
--
-- Deliver 1 unit of [[Meta-Alloys]]
--
-- Optional fields:
--
-- "unit": "unit"
-- "unit_plural": "units"
-- "item_target": "Meta-Alloys"
--
-- For unlock requirements that do not fit this structure cleanly, continue
-- using unlock_requirement as a display string.
local function formatUnlock(d)
if not d then
return nil
end
if type(d.unlock) == 'table' and d.unlock.item then
local action = d.unlock.action or 'Deliver'
local quantity = d.unlock.quantity or 1
local quantityNumber = tonumber(quantity)
local singularUnit = d.unlock.unit or 'unit'
local pluralUnit = d.unlock.unit_plural or (singularUnit .. 's')
local unit
if quantityNumber == 1 then
unit = singularUnit
else
unit = pluralUnit
end
local item = wikilink(
d.unlock.item,
d.unlock.item_target or d.unlock.item
)
return string.format(
'%s %s %s of %s',
action,
tostring(quantity),
unit,
item
)
end
-- Backward compatibility with existing records.
return d.unlock_requirement
end
------------------------------------------------------------------------
-- Field construction
------------------------------------------------------------------------
-- Builds the display values used by an Engineer infobox.
--
-- Structured data supplies the defaults. Explicit parameters supplied by
-- an article override those defaults.
local function buildFields(d, pargs)
local fields = {}
if d then
-- Start with the canonical values.
for _, f in ipairs(FIELD_ORDER) do
fields[f.key] = d[f.key]
end
--------------------------------------------------------------------
-- Automatically linked structured fields
--------------------------------------------------------------------
fields.system = wikilink(d.system)
fields.body = wikilink(d.body)
fields.facility = wikilink(d.facility)
fields.region = wikilink(d.region)
fields.allegiance = wikilink(d.allegiance)
-- Each specialty/module gets its own internal link.
fields.specialties = joinSpecialties(d, true)
--------------------------------------------------------------------
-- Prerequisite Engineer
--------------------------------------------------------------------
if d.prerequisite_engineer then
local note = ''
if d.prerequisite_engineer_note
and d.prerequisite_engineer_note ~= '' then
note = ' ' .. d.prerequisite_engineer_note
end
fields.prerequisite_engineer =
wikilink(d.prerequisite_engineer) .. note
else
fields.prerequisite_engineer =
d.prerequisite_engineer_display
or 'None — available from the start'
end
--------------------------------------------------------------------
-- Unlock
--------------------------------------------------------------------
fields.unlock_requirement = formatUnlock(d)
else
-- No structured record exists yet. Fall back entirely to manually
-- supplied template parameters.
for _, f in ipairs(FIELD_ORDER) do
fields[f.key] = pargs[f.key]
end
end
------------------------------------------------------------------------
-- Explicit article overrides
------------------------------------------------------------------------
--
-- This preserves:
--
-- {{Engineer infobox
-- |image = DifferentPortrait.png
-- }}
--
-- as a valid way to override one structured value without duplicating
-- the rest of the Engineer record.
--
-- 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
------------------------------------------------------------------------
-- Engineer infobox
------------------------------------------------------------------------
-- {{#invoke:Engineers|infobox}}
--
-- Called by Template:Engineer infobox.
--
-- If a canonical JSON record exists, it supplies the defaults.
-- Explicit template parameters override structured values.
-- If no JSON record exists, manual parameters are used instead.
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)
------------------------------------------------------------------------
-- Image
------------------------------------------------------------------------
local image = pargs.image
if (image == nil or image == '')
and d
and d.image
and d.image ~= '' then
image = d.image
end
------------------------------------------------------------------------
-- Build Template:Infobox parameters
------------------------------------------------------------------------
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
------------------------------------------------------------------------
-- Engineer list
------------------------------------------------------------------------
-- {{#invoke:Engineers|list}}
--
-- Generates a sortable Engineer table from the canonical index.
--
-- Engineer names, systems, and specialties are linked automatically.
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',
wikilink(d.name or name),
wikilink(d.system) or '—',
joinSpecialties(d, true) 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
------------------------------------------------------------------------
-- Engineer capability comparison
------------------------------------------------------------------------
-- {{#invoke:Engineers|comparison}}
--
-- Generates a comparison table from canonical Engineer data.
--
-- Engineer names, specialties, prerequisite Engineers, and structured
-- unlock items are linked automatically.
function p.comparison()
local rows = {}
for _, name in ipairs(loadIndex()) do
local d = loadRecord(name)
if d then
local prereq
if d.prerequisite_engineer then
prereq = wikilink(d.prerequisite_engineer)
else
prereq =
d.prerequisite_engineer_display
or 'None'
end
table.insert(
rows,
string.format(
'|-\n| %s || %s || %s || %s',
wikilink(d.name or name),
joinSpecialties(d, true) or '—',
prereq,
formatUnlock(d) or 'Not yet confirmed'
)
)
end
end
return
'{| class="wikitable sortable"\n'
.. '! Engineer !! Specialties !! Prerequisite Engineer !! Unlock requirement\n'
.. table.concat(rows, '\n')
.. '\n|}'
end
------------------------------------------------------------------------
-- Engineer count
------------------------------------------------------------------------
-- {{#invoke:Engineers|count}}
--
-- Returns the number of canonical Engineer records currently present in
-- Module:Data/Engineers/index.
function p.count()
return tostring(#loadIndex())
end
return p