Soft launch: Soldier biographies and research notes vary in completeness. Prefer class-roster, NARA, newspapers, and other cited sources for contested facts — treat Research notes as leads, not settled history. Anyone can correct a page; contact us with questions.
Module:Soldier: Difference between revisions
Jump to navigation
Jump to search
Import soldier lead module |
Import soldier lead module |
||
| Line 159: | Line 159: | ||
end | end | ||
return out | return out | ||
end | |||
local function assignmentLink(raw) | |||
raw = trim(raw) | |||
if raw == '' then | |||
return '' | |||
end | |||
raw = mw.ustring.gsub(raw, '[#<>%[%]|{}]', '') | |||
raw = trim(raw) | |||
if raw == '' then | |||
return '' | |||
end | |||
return string.format('[[Assignment %s|%s]]', raw, raw) | |||
end | end | ||
| Line 179: | Line 192: | ||
end | end | ||
td:wikitext(value) | td:wikitext(value) | ||
end | |||
local function yearOnly(s) | |||
s = trim(s) | |||
if s == '' or s == '?' then | |||
return '' | |||
end | |||
local year = mw.ustring.match(s, '(%d%d%d%d)') | |||
if year then | |||
return year | |||
end | |||
return '' | |||
end | |||
local RANK_MAP = { | |||
['PVT'] = 'Private', | |||
['PVTE'] = 'Private', | |||
['PRIVATE'] = 'Private', | |||
['PFC'] = 'Private First Class', | |||
['P F C'] = 'Private First Class', | |||
['PRIVATE FIRST CLASS'] = 'Private First Class', | |||
['CPL'] = 'Corporal', | |||
['CORPORAL'] = 'Corporal', | |||
['T/5'] = 'Technician Fifth Grade', | |||
['TEC5'] = 'Technician Fifth Grade', | |||
['TEC 5'] = 'Technician Fifth Grade', | |||
['T5'] = 'Technician Fifth Grade', | |||
['TECH 5'] = 'Technician Fifth Grade', | |||
['SGT'] = 'Sergeant', | |||
['SERGEANT'] = 'Sergeant', | |||
['T/4'] = 'Technician Fourth Grade', | |||
['TEC4'] = 'Technician Fourth Grade', | |||
['TEC 4'] = 'Technician Fourth Grade', | |||
['T4'] = 'Technician Fourth Grade', | |||
['TECH 4'] = 'Technician Fourth Grade', | |||
['S/SGT'] = 'Staff Sergeant', | |||
['SSGT'] = 'Staff Sergeant', | |||
['S SGT'] = 'Staff Sergeant', | |||
['STAFF SERGEANT'] = 'Staff Sergeant', | |||
['T/3'] = 'Technician Third Grade', | |||
['TEC3'] = 'Technician Third Grade', | |||
['TEC 3'] = 'Technician Third Grade', | |||
['T3'] = 'Technician Third Grade', | |||
['TECH 3'] = 'Technician Third Grade', | |||
['T/SGT'] = 'Technical Sergeant', | |||
['TSGT'] = 'Technical Sergeant', | |||
['T SGT'] = 'Technical Sergeant', | |||
['TECHNICAL SERGEANT'] = 'Technical Sergeant', | |||
['1ST SGT'] = 'First Sergeant', | |||
['1SGT'] = 'First Sergeant', | |||
['FIRST SERGEANT'] = 'First Sergeant', | |||
['M/SGT'] = 'Master Sergeant', | |||
['MSGT'] = 'Master Sergeant', | |||
['MASTER SERGEANT'] = 'Master Sergeant', | |||
['WO'] = 'Warrant Officer', | |||
['WOJG'] = 'Warrant Officer', | |||
['WARRANT OFFICER'] = 'Warrant Officer', | |||
['CWO'] = 'Chief Warrant Officer', | |||
['2ND LT'] = 'Second Lieutenant', | |||
['2D LT'] = 'Second Lieutenant', | |||
['2LT'] = 'Second Lieutenant', | |||
['SECOND LIEUTENANT'] = 'Second Lieutenant', | |||
['1ST LT'] = 'First Lieutenant', | |||
['1LT'] = 'First Lieutenant', | |||
['FIRST LIEUTENANT'] = 'First Lieutenant', | |||
['LT'] = 'Lieutenant', | |||
['LIEUTENANT'] = 'Lieutenant', | |||
['CAPT'] = 'Captain', | |||
['CPT'] = 'Captain', | |||
['CAPTAIN'] = 'Captain', | |||
['MAJ'] = 'Major', | |||
['MAJOR'] = 'Major', | |||
['LT COL'] = 'Lieutenant Colonel', | |||
['LTCOL'] = 'Lieutenant Colonel', | |||
['LTC'] = 'Lieutenant Colonel', | |||
['LIEUTENANT COLONEL'] = 'Lieutenant Colonel', | |||
['COL'] = 'Colonel', | |||
['COLONEL'] = 'Colonel', | |||
['BRIG GEN'] = 'Brigadier General', | |||
['BG'] = 'Brigadier General', | |||
['BRIGADIER GENERAL'] = 'Brigadier General', | |||
['MAJ GEN'] = 'Major General', | |||
['MG'] = 'Major General', | |||
['MAJOR GENERAL'] = 'Major General', | |||
['LT GEN'] = 'Lieutenant General', | |||
['LIEUTENANT GENERAL'] = 'Lieutenant General', | |||
['GEN'] = 'General', | |||
['GENERAL'] = 'General', | |||
['TECH'] = 'Technician', | |||
['TEC'] = 'Technician', | |||
} | |||
local function expandRank(raw) | |||
raw = trim(raw) | |||
if raw == '' then | |||
return '' | |||
end | |||
local key = mw.ustring.upper(raw) | |||
key = mw.ustring.gsub(key, '%.', '') | |||
key = mw.ustring.gsub(key, '%s+', ' ') | |||
key = trim(key) | |||
return RANK_MAP[key] or raw | |||
end | |||
local function lifespan(birth, death) | |||
birth = yearOnly(birth) | |||
death = yearOnly(death) | |||
if birth ~= '' and death ~= '' then | |||
return ' (' .. birth .. '–' .. death .. ')' | |||
end | |||
if birth ~= '' then | |||
return ' (born ' .. birth .. ')' | |||
end | |||
if death ~= '' then | |||
return ' (died ' .. death .. ')' | |||
end | |||
return '' | |||
end | |||
local function preferredRank(frame) | |||
local rank = expandRank(parentArg(frame, 'rank_final')) | |||
if rank == '' then | |||
rank = expandRank(parentArg(frame, 'class_rank')) | |||
end | |||
return rank | |||
end | end | ||
| Line 220: | Line 358: | ||
local alias = parentArg(frame, 'alias') | local alias = parentArg(frame, 'alias') | ||
local birth = parentArg(frame, 'birth_year') | local birth = yearOnly(parentArg(frame, 'birth_year')) | ||
local death = parentArg(frame, 'death_year') | local death = yearOnly(parentArg(frame, 'death_year')) | ||
local nativity = parentArg(frame, 'nativity') | local nativity = parentArg(frame, 'nativity') | ||
local religion = parentArg(frame, 'religion') | local religion = parentArg(frame, 'religion') | ||
| Line 256: | Line 394: | ||
addRow(tbl, 'ASN', parentArg(frame, 'asn'), 'serial') | addRow(tbl, 'ASN', parentArg(frame, 'asn'), 'serial') | ||
local rankFinal = parentArg(frame, 'rank_final') | local rankFinal = expandRank(parentArg(frame, 'rank_final')) | ||
local classRank = parentArg(frame, 'class_rank') | local classRank = expandRank(parentArg(frame, 'class_rank')) | ||
if rankFinal ~= '' and classRank ~= '' and classRank ~= rankFinal then | if rankFinal ~= '' and classRank ~= '' and classRank ~= rankFinal then | ||
addRow(tbl, 'Rank', rankFinal) | addRow(tbl, 'Rank', rankFinal) | ||
| Line 281: | Line 419: | ||
addRow(tbl, 'Enlisted', titleCase(enlist)) | addRow(tbl, 'Enlisted', titleCase(enlist)) | ||
end | end | ||
addRow(tbl, 'Assignment', parentArg(frame, 'assignment')) | addRow(tbl, 'Assignment', assignmentLink(parentArg(frame, 'assignment'))) | ||
addRow(tbl, 'Unit / division', parentArg(frame, 'division')) | addRow(tbl, 'Unit / division', parentArg(frame, 'division')) | ||
addRow(tbl, 'IPW (German)', parentArg(frame, 'ipw_ge')) | addRow(tbl, 'IPW (German)', parentArg(frame, 'ipw_ge')) | ||
addRow(tbl, 'IPW (Italian)', parentArg(frame, 'ipw_it')) | addRow(tbl, 'IPW (Italian)', parentArg(frame, 'ipw_it')) | ||
addRow(tbl, 'Special', parentArg(frame, 'special')) | addRow(tbl, 'Special', parentArg(frame, 'special')) | ||
addRow(tbl, 'LOC', parentArg(frame, 'loc')) | addRow(tbl, 'LOC', parentArg(frame, 'loc')) | ||
| Line 297: | Line 434: | ||
function p.lead(frame) | function p.lead(frame) | ||
local name = displayName(frame) | local name = displayName(frame) | ||
local | local rank = preferredRank(frame) | ||
local role = 'United States Army soldier' | |||
if rank ~= '' then | |||
role = 'United States Army ' .. rank | |||
local | |||
if | |||
end | end | ||
| Line 316: | Line 444: | ||
if #classLinks == 1 then | if #classLinks == 1 then | ||
trained = ' who trained at [[Camp Ritchie]], Maryland, in ' .. classLinks[1] | trained = ' who trained at [[Camp Ritchie]], Maryland, in ' .. classLinks[1] | ||
elseif #classLinks > | elseif #classLinks == 2 then | ||
trained = ' who trained at [[Camp Ritchie]], Maryland, in | trained = ' who trained at [[Camp Ritchie]], Maryland, in ' .. classLinks[1] .. ' and ' .. classLinks[2] | ||
elseif #classLinks > 2 then | |||
trained = ' who trained at [[Camp Ritchie]], Maryland, in ' | |||
.. table.concat(classLinks, ', ', 1, #classLinks - 1) | |||
.. ', and ' .. classLinks[#classLinks] | |||
else | else | ||
trained = ' associated with Camp Ritchie, Maryland' | trained = ' associated with Camp Ritchie, Maryland' | ||
end | end | ||
return string.format( | |||
"'''%s'''%s was a | "'''%s'''%s was a %s%s.", | ||
name, | name, | ||
lifespan(parentArg(frame, 'birth_year'), parentArg(frame, 'death_year')), | |||
role, | |||
trained | trained | ||
) | ) | ||
end | end | ||
return p | return p | ||
Revision as of 02:00, 18 August 2026
Documentation for this module may be created at Module:Soldier/doc
local p = {}
local function trim(s)
return mw.text.trim(tostring(s or ''))
end
local function countChar(s, ch)
local _, n = mw.ustring.gsub(s, ch, '')
return n
end
local function tidyDisplay(s)
s = trim(s)
if s == '' then
return ''
end
s = mw.text.decode(s, true)
s = mw.ustring.gsub(s, mw.ustring.char(160), ' ')
s = mw.ustring.gsub(s, mw.ustring.char(0xFFFD), '')
s = mw.ustring.gsub(s, mw.ustring.char(0x200B), '')
s = trim(s)
local lower = mw.ustring.lower(s)
if lower == '#n/a' or lower == 'n/a' or lower == 'none' or lower == 'null' or lower == '-' then
return ''
end
if s == '.' or s == '?' then
return ''
end
local prev
repeat
prev = s
s = mw.ustring.gsub(s, '""', '"')
until s == prev
s = trim(s)
local changed = true
while changed and mw.ustring.len(s) >= 2 do
changed = false
local first = mw.ustring.sub(s, 1, 1)
local last = mw.ustring.sub(s, -1)
if first == '"' and last == '"' then
local inner = mw.ustring.sub(s, 2, -2)
if countChar(inner, '"') == 0 then
s = trim(inner)
changed = true
elseif countChar(s, '"') % 2 == 1 then
local second = mw.ustring.find(s, '"', 2, true)
local slen = mw.ustring.len(s)
if second and second + 1 < slen then
local nxt = mw.ustring.sub(s, second + 1, second + 1)
if mw.ustring.find(nxt, '%w') then
s = mw.ustring.sub(s, 2)
else
s = mw.ustring.sub(s, 1, -2)
end
else
s = mw.ustring.sub(s, 1, -2)
end
s = trim(s)
changed = true
end
elseif first == "'" and last == "'" then
local inner = mw.ustring.sub(s, 2, -2)
if not mw.ustring.find(inner, "'", 1, true) then
s = trim(inner)
changed = true
end
end
end
local q = countChar(s, '"')
local first = mw.ustring.sub(s, 1, 1)
local last = mw.ustring.sub(s, -1)
if first == '"' and q == 1 and last ~= '"' then
s = trim(mw.ustring.sub(s, 2))
end
last = mw.ustring.sub(s, -1)
first = mw.ustring.sub(s, 1, 1)
q = countChar(s, '"')
if last == '"' and q == 1 and first ~= '"' then
s = trim(mw.ustring.sub(s, 1, -2))
end
s = mw.ustring.gsub(s, '^\\+', '')
s = trim(s)
if s == '"' or s == "'" or s == '.' or s == '?' or s == '\\' then
return ''
end
s = mw.ustring.gsub(s, '%s+', ' ')
return trim(s)
end
local function parentArg(frame, name)
local parent = frame:getParent()
local raw
if parent and parent.args[name] then
raw = parent.args[name]
else
raw = frame.args[name]
end
return tidyDisplay(raw)
end
local function titleCase(s)
s = trim(s)
if s == '' then
return s
end
s = mw.ustring.lower(s)
s = mw.ustring.gsub(s, "(%a)([%w']*)", function(first, rest)
return mw.ustring.upper(first) .. rest
end)
return s
end
local function containsIgnoreCase(haystack, needle)
return mw.ustring.find(mw.ustring.lower(haystack), mw.ustring.lower(needle), 1, true) ~= nil
end
local function displayName(frame)
local first = parentArg(frame, 'first')
local middle = parentArg(frame, 'middle')
local surname = parentArg(frame, 'surname')
local suffix = parentArg(frame, 'suffix')
local parts = {}
if first ~= '' then
table.insert(parts, first)
end
if middle ~= '' then
table.insert(parts, middle)
end
if surname ~= '' then
table.insert(parts, surname)
end
local name = table.concat(parts, ' ')
if suffix ~= '' then
if name ~= '' then
name = name .. ', ' .. suffix
else
name = suffix
end
end
if name == '' then
name = 'This soldier'
end
return name
end
local function classLinkList(raw, withPrefix)
local out = {}
for part in mw.text.gsplit(raw, ';', true) do
part = trim(part)
if part ~= '' then
if withPrefix then
table.insert(out, string.format('[[Class %s]]', part))
else
table.insert(out, string.format('[[Class %s|%s]]', part, part))
end
end
end
return out
end
local function assignmentLink(raw)
raw = trim(raw)
if raw == '' then
return ''
end
raw = mw.ustring.gsub(raw, '[#<>%[%]|{}]', '')
raw = trim(raw)
if raw == '' then
return ''
end
return string.format('[[Assignment %s|%s]]', raw, raw)
end
local function addHeader(tbl, text)
tbl:tag('tr'):tag('th')
:attr('colspan', '2')
:addClass('infobox-header')
:wikitext(text)
end
local function addRow(tbl, label, value, dataClass)
if trim(value) == '' then
return
end
local tr = tbl:tag('tr')
tr:tag('th'):addClass('infobox-label'):wikitext(label)
local td = tr:tag('td'):addClass('infobox-data')
if dataClass and dataClass ~= '' then
td:addClass(dataClass)
end
td:wikitext(value)
end
local function yearOnly(s)
s = trim(s)
if s == '' or s == '?' then
return ''
end
local year = mw.ustring.match(s, '(%d%d%d%d)')
if year then
return year
end
return ''
end
local RANK_MAP = {
['PVT'] = 'Private',
['PVTE'] = 'Private',
['PRIVATE'] = 'Private',
['PFC'] = 'Private First Class',
['P F C'] = 'Private First Class',
['PRIVATE FIRST CLASS'] = 'Private First Class',
['CPL'] = 'Corporal',
['CORPORAL'] = 'Corporal',
['T/5'] = 'Technician Fifth Grade',
['TEC5'] = 'Technician Fifth Grade',
['TEC 5'] = 'Technician Fifth Grade',
['T5'] = 'Technician Fifth Grade',
['TECH 5'] = 'Technician Fifth Grade',
['SGT'] = 'Sergeant',
['SERGEANT'] = 'Sergeant',
['T/4'] = 'Technician Fourth Grade',
['TEC4'] = 'Technician Fourth Grade',
['TEC 4'] = 'Technician Fourth Grade',
['T4'] = 'Technician Fourth Grade',
['TECH 4'] = 'Technician Fourth Grade',
['S/SGT'] = 'Staff Sergeant',
['SSGT'] = 'Staff Sergeant',
['S SGT'] = 'Staff Sergeant',
['STAFF SERGEANT'] = 'Staff Sergeant',
['T/3'] = 'Technician Third Grade',
['TEC3'] = 'Technician Third Grade',
['TEC 3'] = 'Technician Third Grade',
['T3'] = 'Technician Third Grade',
['TECH 3'] = 'Technician Third Grade',
['T/SGT'] = 'Technical Sergeant',
['TSGT'] = 'Technical Sergeant',
['T SGT'] = 'Technical Sergeant',
['TECHNICAL SERGEANT'] = 'Technical Sergeant',
['1ST SGT'] = 'First Sergeant',
['1SGT'] = 'First Sergeant',
['FIRST SERGEANT'] = 'First Sergeant',
['M/SGT'] = 'Master Sergeant',
['MSGT'] = 'Master Sergeant',
['MASTER SERGEANT'] = 'Master Sergeant',
['WO'] = 'Warrant Officer',
['WOJG'] = 'Warrant Officer',
['WARRANT OFFICER'] = 'Warrant Officer',
['CWO'] = 'Chief Warrant Officer',
['2ND LT'] = 'Second Lieutenant',
['2D LT'] = 'Second Lieutenant',
['2LT'] = 'Second Lieutenant',
['SECOND LIEUTENANT'] = 'Second Lieutenant',
['1ST LT'] = 'First Lieutenant',
['1LT'] = 'First Lieutenant',
['FIRST LIEUTENANT'] = 'First Lieutenant',
['LT'] = 'Lieutenant',
['LIEUTENANT'] = 'Lieutenant',
['CAPT'] = 'Captain',
['CPT'] = 'Captain',
['CAPTAIN'] = 'Captain',
['MAJ'] = 'Major',
['MAJOR'] = 'Major',
['LT COL'] = 'Lieutenant Colonel',
['LTCOL'] = 'Lieutenant Colonel',
['LTC'] = 'Lieutenant Colonel',
['LIEUTENANT COLONEL'] = 'Lieutenant Colonel',
['COL'] = 'Colonel',
['COLONEL'] = 'Colonel',
['BRIG GEN'] = 'Brigadier General',
['BG'] = 'Brigadier General',
['BRIGADIER GENERAL'] = 'Brigadier General',
['MAJ GEN'] = 'Major General',
['MG'] = 'Major General',
['MAJOR GENERAL'] = 'Major General',
['LT GEN'] = 'Lieutenant General',
['LIEUTENANT GENERAL'] = 'Lieutenant General',
['GEN'] = 'General',
['GENERAL'] = 'General',
['TECH'] = 'Technician',
['TEC'] = 'Technician',
}
local function expandRank(raw)
raw = trim(raw)
if raw == '' then
return ''
end
local key = mw.ustring.upper(raw)
key = mw.ustring.gsub(key, '%.', '')
key = mw.ustring.gsub(key, '%s+', ' ')
key = trim(key)
return RANK_MAP[key] or raw
end
local function lifespan(birth, death)
birth = yearOnly(birth)
death = yearOnly(death)
if birth ~= '' and death ~= '' then
return ' (' .. birth .. '–' .. death .. ')'
end
if birth ~= '' then
return ' (born ' .. birth .. ')'
end
if death ~= '' then
return ' (died ' .. death .. ')'
end
return ''
end
local function preferredRank(frame)
local rank = expandRank(parentArg(frame, 'rank_final'))
if rank == '' then
rank = expandRank(parentArg(frame, 'class_rank'))
end
return rank
end
function p.displayName(frame)
return displayName(frame)
end
function p.classLinks(frame)
return table.concat(classLinkList(parentArg(frame, 'class_number'), false), ', ')
end
function p.infobox(frame)
local name = displayName(frame)
local tbl = mw.html.create('table')
tbl:addClass('infobox'):addClass('vcard'):addClass('soldier-infobox')
tbl:tag('tr'):tag('th')
:attr('colspan', '2')
:addClass('infobox-above fn')
:wikitext(name)
local photo = parentArg(frame, 'photo')
if photo ~= '' then
local size = parentArg(frame, 'image_size')
if size == '' then
size = '220px'
end
local alt = parentArg(frame, 'alt')
if alt == '' then
alt = name
end
local td = tbl:tag('tr'):tag('td')
:attr('colspan', '2')
:addClass('infobox-image')
td:wikitext(string.format('[[File:%s|%s|alt=%s]]', photo, size, alt))
local caption = parentArg(frame, 'caption')
if caption ~= '' then
td:tag('div'):addClass('infobox-caption'):wikitext(caption)
end
end
local alias = parentArg(frame, 'alias')
local birth = yearOnly(parentArg(frame, 'birth_year'))
local death = yearOnly(parentArg(frame, 'death_year'))
local nativity = parentArg(frame, 'nativity')
local religion = parentArg(frame, 'religion')
local marital = parentArg(frame, 'marital')
local education = parentArg(frame, 'education')
local occupation = parentArg(frame, 'occupation')
local state = parentArg(frame, 'state')
if alias ~= '' or birth ~= '' or death ~= '' or nativity ~= ''
or religion ~= '' or marital ~= '' or education ~= ''
or occupation ~= '' or state ~= '' then
addHeader(tbl, 'Personal details')
end
addRow(tbl, 'Nickname', alias, 'nickname')
addRow(tbl, 'Born', birth)
addRow(tbl, 'Nativity', nativity)
addRow(tbl, 'Died', death)
addRow(tbl, 'Religion', religion)
addRow(tbl, 'Spouse / marital', marital)
addRow(tbl, 'Education', education)
addRow(tbl, 'Occupation', occupation)
if state ~= '' then
addRow(tbl, 'State', titleCase(state))
end
addHeader(tbl, 'Military service')
addRow(tbl, 'Allegiance', 'United States')
local branch = parentArg(frame, 'class_branch')
if branch == '' then
branch = 'United States Army'
end
addRow(tbl, 'Branch', branch)
addRow(tbl, 'ASN', parentArg(frame, 'asn'), 'serial')
local rankFinal = expandRank(parentArg(frame, 'rank_final'))
local classRank = expandRank(parentArg(frame, 'class_rank'))
if rankFinal ~= '' and classRank ~= '' and classRank ~= rankFinal then
addRow(tbl, 'Rank', rankFinal)
addRow(tbl, 'Rank at Ritchie', classRank)
else
local rank = rankFinal
if rank == '' then
rank = classRank
end
addRow(tbl, 'Rank', rank)
end
local classRaw = parentArg(frame, 'class_number')
local classBits = classLinkList(classRaw, false)
if #classBits > 0 then
addRow(tbl, 'Camp Ritchie', 'Class ' .. table.concat(classBits, ', '))
end
addRow(tbl, 'Section', parentArg(frame, 'class_section'))
addRow(tbl, 'Other class', parentArg(frame, 'other_class'))
local enlist = parentArg(frame, 'enlistment_place')
if enlist ~= '' then
addRow(tbl, 'Enlisted', titleCase(enlist))
end
addRow(tbl, 'Assignment', assignmentLink(parentArg(frame, 'assignment')))
addRow(tbl, 'Unit / division', parentArg(frame, 'division'))
addRow(tbl, 'IPW (German)', parentArg(frame, 'ipw_ge'))
addRow(tbl, 'IPW (Italian)', parentArg(frame, 'ipw_it'))
addRow(tbl, 'Special', parentArg(frame, 'special'))
addRow(tbl, 'LOC', parentArg(frame, 'loc'))
addRow(tbl, 'Arrived', parentArg(frame, 'date_arrive'))
addRow(tbl, 'Departed', parentArg(frame, 'date_depart'))
addRow(tbl, 'Certificate', parentArg(frame, 'certificate'))
return tostring(tbl)
end
function p.lead(frame)
local name = displayName(frame)
local rank = preferredRank(frame)
local role = 'United States Army soldier'
if rank ~= '' then
role = 'United States Army ' .. rank
end
local classLinks = classLinkList(parentArg(frame, 'class_number'), true)
local trained
if #classLinks == 1 then
trained = ' who trained at [[Camp Ritchie]], Maryland, in ' .. classLinks[1]
elseif #classLinks == 2 then
trained = ' who trained at [[Camp Ritchie]], Maryland, in ' .. classLinks[1] .. ' and ' .. classLinks[2]
elseif #classLinks > 2 then
trained = ' who trained at [[Camp Ritchie]], Maryland, in '
.. table.concat(classLinks, ', ', 1, #classLinks - 1)
.. ', and ' .. classLinks[#classLinks]
else
trained = ' associated with Camp Ritchie, Maryland'
end
return string.format(
"'''%s'''%s was a %s%s.",
name,
lifespan(parentArg(frame, 'birth_year'), parentArg(frame, 'death_year')),
role,
trained
)
end
return p