Import soldier lead module
Import soldier lead module
Line 99: Line 99:
end
end
return tidyDisplay(raw)
return tidyDisplay(raw)
end
local ACRONYMS = {
US = true, USA = true, POW = true, IPW = true, MII = true, OSS = true,
MITC = true, ASN = true, WWII = true, NARA = true, DC = true, DEML = true,
CSU = true, ASTP = true, CAC = true, KIA = true, MIA = true, NG = true,
MRBC = true, PACMIRS = true, GED = true, MITU = true, RDC = true,
}
local SMALL_WORDS = {
and = true, of = true, the = true, for = true, to = true, in = true,
a = true, an = true, at = true, by = true, or = true,
}
local STATE_NAME = {
alabama = 'Alabama', alaska = 'Alaska', arizona = 'Arizona', arkansas = 'Arkansas',
california = 'California', colorado = 'Colorado', connecticut = 'Connecticut',
delaware = 'Delaware', florida = 'Florida', georgia = 'Georgia', hawaii = 'Hawaii',
idaho = 'Idaho', illinois = 'Illinois', indiana = 'Indiana', iowa = 'Iowa',
kansas = 'Kansas', kentucky = 'Kentucky', louisiana = 'Louisiana', maine = 'Maine',
maryland = 'Maryland', massachusetts = 'Massachusetts', michigan = 'Michigan',
minnesota = 'Minnesota', mississippi = 'Mississippi', missouri = 'Missouri',
montana = 'Montana', nebraska = 'Nebraska', nevada = 'Nevada', ohio = 'Ohio',
oklahoma = 'Oklahoma', oregon = 'Oregon', pennsylvania = 'Pennsylvania',
tennessee = 'Tennessee', texas = 'Texas', utah = 'Utah', vermont = 'Vermont',
virginia = 'Virginia', washington = 'Washington', wisconsin = 'Wisconsin',
wyoming = 'Wyoming',
}
local STATE_ABBR = {
al = 'Alabama', ak = 'Alaska', az = 'Arizona', ar = 'Arkansas', ca = 'California',
co = 'Colorado', ct = 'Connecticut', de = 'Delaware', fl = 'Florida', ga = 'Georgia',
hi = 'Hawaii', id = 'Idaho', il = 'Illinois', ['in'] = 'Indiana', ia = 'Iowa',
ks = 'Kansas', ky = 'Kentucky', la = 'Louisiana', me = 'Maine', md = 'Maryland',
ma = 'Massachusetts', mi = 'Michigan', mn = 'Minnesota', ms = 'Mississippi',
mo = 'Missouri', mt = 'Montana', ne = 'Nebraska', nv = 'Nevada', nh = 'New Hampshire',
nj = 'New Jersey', nm = 'New Mexico', ny = 'New York', nc = 'North Carolina',
nd = 'North Dakota', oh = 'Ohio', ok = 'Oklahoma', ['or'] = 'Oregon', pa = 'Pennsylvania',
ri = 'Rhode Island', sc = 'South Carolina', sd = 'South Dakota', tn = 'Tennessee',
tx = 'Texas', ut = 'Utah', vt = 'Vermont', va = 'Virginia', wa = 'Washington',
wv = 'West Virginia', wi = 'Wisconsin', wy = 'Wyoming', dc = 'Washington, D.C.',
}
local TWO_WORD_STATE = {
['new hampshire'] = 'New Hampshire',
['new jersey'] = 'New Jersey',
['new mexico'] = 'New Mexico',
['new york'] = 'New York',
['north carolina'] = 'North Carolina',
['north dakota'] = 'North Dakota',
['rhode island'] = 'Rhode Island',
['south carolina'] = 'South Carolina',
['south dakota'] = 'South Dakota',
['west virginia'] = 'West Virginia',
}
local NEGATIVE_PHRASES = {
'unsatisfactory', 'failed class', 'failed as', 'failure', 'flunk',
'washed out', 'wash out', 'washout', 'kicked out', 'kick out',
'dismissed', 'expelled', 'poor performance', 'poor work', 'poor grades',
'awol', 'court-martial', 'court martial', 'disciplinary',
'discharged for cause', 'dropped out', 'dropped from', 'also dropped',
'physically unfit', 'unfit for', 'relieved prior', 'relieved of',
'kicked', 'eliminated from', 'not graduated', 'did not graduate',
}
local function splitWords(s)
local words = {}
for w in mw.text.gsplit(s, ' ', true) do
if w ~= '' then
table.insert(words, w)
end
end
return words
end
local function isAllCapsPhrase(s)
local letters = mw.ustring.gsub(s, '[^%a]', '')
if mw.ustring.len(letters) < 2 then
return false
end
return letters == mw.ustring.upper(letters)
end
local function prettyTitle(s, keepSmall)
s = trim(s)
if s == '' then
return s
end
local words = splitWords(s)
local out = {}
for i, w in ipairs(words) do
local bare = mw.ustring.gsub(w, '[^%a]', '')
local up = mw.ustring.upper(bare)
if ACRONYMS[up] then
table.insert(out, mw.ustring.gsub(w, bare, up, 1))
else
local low = mw.ustring.lower(w)
local lowBare = mw.ustring.lower(bare)
if keepSmall and i > 1 and SMALL_WORDS[lowBare] then
table.insert(out, mw.ustring.lower(w))
else
local first = mw.ustring.upper(mw.ustring.sub(low, 1, 1))
table.insert(out, first .. mw.ustring.sub(low, 2))
end
end
end
return table.concat(out, ' ')
end
end


local function titleCase(s)
local function titleCase(s)
return prettyTitle(s, false)
end
local function looksLikeCode(s)
s = trim(s)
if s == '' then
return true
end
if mw.ustring.find(s, '%d') and mw.ustring.len(s) <= 8 and not mw.ustring.find(s, ' ') then
return true
end
if mw.ustring.len(s) <= 3 and not mw.ustring.find(s, ' ') then
return true
end
return false
end
local function prettyPlace(s)
s = trim(s)
if s == '' then
return s
end
local compact = mw.ustring.lower(s)
compact = mw.ustring.gsub(compact, '[.,]', '')
compact = mw.ustring.gsub(compact, '%s+', ' ')
compact = trim(compact)
if compact == 'washington dc' or compact == 'washington d c'
or compact == 'district of columbia' or compact == 'washington dc' then
return 'Washington, D.C.'
end
if mw.ustring.find(compact, ' washington dc$') or mw.ustring.find(compact, ' washington d c$') then
local city = mw.ustring.gsub(s, '[Ww][Aa][Ss][Hh][Ii][Nn][Gg][Tt][Oo][Nn]%s+[Dd]%.?%s*[Cc]%.?$', '')
city = prettyTitle(trim(city), false)
if city ~= '' then
return city .. ', Washington, D.C.'
end
return 'Washington, D.C.'
end
if mw.ustring.find(s, ',', 1, true) then
local bits = {}
for part in mw.text.gsplit(s, ',', true) do
part = trim(part)
if part ~= '' then
table.insert(bits, prettyTitle(part, false))
end
end
return table.concat(bits, ', ')
end
local words = splitWords(s)
if #words >= 2 then
local last2 = mw.ustring.lower(words[#words - 1] .. ' ' .. words[#words])
last2 = mw.ustring.gsub(last2, '%.', '')
if TWO_WORD_STATE[last2] then
local city = {}
for i = 1, #words - 2 do
table.insert(city, words[i])
end
if #city > 0 then
return prettyTitle(table.concat(city, ' '), false) .. ', ' .. TWO_WORD_STATE[last2]
end
return TWO_WORD_STATE[last2]
end
end
if #words >= 1 then
local last = mw.ustring.lower(words[#words])
last = mw.ustring.gsub(last, '%.', '')
local state = STATE_NAME[last] or STATE_ABBR[last]
if state then
if #words >= 2 then
local city = {}
for i = 1, #words - 1 do
table.insert(city, words[i])
end
return prettyTitle(table.concat(city, ' '), false) .. ', ' .. state
end
return state
end
end
if isAllCapsPhrase(s) then
return prettyTitle(s, false)
end
return s
end
local function prettyProse(s)
s = trim(s)
if s == '' or looksLikeCode(s) then
return s
end
if isAllCapsPhrase(s) or prettyPlace(s) ~= s then
local placed = prettyPlace(s)
if placed ~= s then
return placed
end
end
if isAllCapsPhrase(s) then
return prettyTitle(s, true)
end
return s
end
local function prettyNamePart(s)
s = trim(s)
s = trim(s)
if s == '' then
if s == '' then
return s
return s
end
end
s = mw.ustring.lower(s)
if isAllCapsPhrase(s) then
s = mw.ustring.gsub(s, "(%a)([%w']*)", function(first, rest)
return prettyTitle(s, false)
return mw.ustring.upper(first) .. rest
end
end)
return s
return s
end
local function isNegativeClause(s)
local lower = mw.ustring.lower(s)
if lower == 'failed' or mw.ustring.find(lower, '^failed%s') or mw.ustring.find(lower, '%sfailed%s') or mw.ustring.find(lower, '%sfailed$') then
return true
end
for _, phrase in ipairs(NEGATIVE_PHRASES) do
if mw.ustring.find(lower, phrase, 1, true) then
return true
end
end
return false
end
local function sanitizeNote(s)
s = tidyDisplay(s)
if s == '' then
return ''
end
local lower = mw.ustring.lower(s)
if mw.ustring.find(lower, 'no notes were in the source', 1, true) then
return ''
end
local kept = {}
for clause in mw.text.gsplit(s, ';', true) do
clause = trim(clause)
if clause ~= '' then
for sent in mw.text.gsplit(clause, '. ', true) do
sent = trim(sent)
sent = mw.ustring.gsub(sent, '%.$', '')
if sent ~= '' and not isNegativeClause(sent) then
table.insert(kept, prettyProse(sent))
end
end
end
end
return table.concat(kept, '; ')
end
end


Line 118: Line 365:


local function displayName(frame)
local function displayName(frame)
local first = parentArg(frame, 'first')
local first = prettyNamePart(parentArg(frame, 'first'))
local middle = parentArg(frame, 'middle')
local middle = prettyNamePart(parentArg(frame, 'middle'))
local surname = parentArg(frame, 'surname')
local surname = prettyNamePart(parentArg(frame, 'surname'))
local suffix = parentArg(frame, 'suffix')
local suffix = prettyNamePart(parentArg(frame, 'suffix'))
local parts = {}
local parts = {}
if first ~= '' then
if first ~= '' then
Line 159: Line 406:
end
end
return out
return out
end
local function teamLinkList(raw, pagePrefix, label)
local out = {}
for part in mw.text.gsplit(raw, ';', true) do
part = trim(part)
part = mw.ustring.gsub(part, '[#<>%[%]|{}]', '')
part = trim(part)
if part ~= '' then
table.insert(out, string.format('[[Team %s%s|%s %s]]', pagePrefix, part, label, part))
end
end
return table.concat(out, ', ')
end
end


Line 357: Line 617:
end
end


local alias = parentArg(frame, 'alias')
local alias = prettyNamePart(parentArg(frame, 'alias'))
local birth = yearOnly(parentArg(frame, 'birth_year'))
local birth = yearOnly(parentArg(frame, 'birth_year'))
local death = yearOnly(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')
local marital = parentArg(frame, 'marital')
local marital = prettyProse(parentArg(frame, 'marital'))
local education = parentArg(frame, 'education')
local education = prettyProse(parentArg(frame, 'education'))
local occupation = parentArg(frame, 'occupation')
local occupation = prettyProse(parentArg(frame, 'occupation'))
local state = parentArg(frame, 'state')
local state = prettyPlace(parentArg(frame, 'state'))


if alias ~= '' or birth ~= '' or death ~= '' or nativity ~= ''
if alias ~= '' or birth ~= '' or death ~= '' or nativity ~= ''
Line 382: Line 642:
addRow(tbl, 'Occupation', occupation)
addRow(tbl, 'Occupation', occupation)
if state ~= '' then
if state ~= '' then
addRow(tbl, 'State', titleCase(state))
addRow(tbl, 'State', state)
end
end


Line 415: Line 675:
addRow(tbl, 'Other class', parentArg(frame, 'other_class'))
addRow(tbl, 'Other class', parentArg(frame, 'other_class'))


local enlist = parentArg(frame, 'enlistment_place')
local enlist = prettyPlace(parentArg(frame, 'enlistment_place'))
if enlist ~= '' then
if enlist ~= '' then
addRow(tbl, 'Enlisted', titleCase(enlist))
addRow(tbl, 'Enlisted', enlist)
end
end
addRow(tbl, 'Assignment', assignmentLink(parentArg(frame, 'assignment')))
addRow(tbl, 'Assignment', assignmentLink(parentArg(frame, 'assignment')))
addRow(tbl, 'Unit / division', parentArg(frame, 'division'))
addRow(tbl, 'Unit / division', prettyProse(parentArg(frame, 'division')))
addRow(tbl, 'IPW (German)', parentArg(frame, 'ipw_ge'))
addRow(tbl, 'IPW (German)', teamLinkList(parentArg(frame, 'ipw_ge'), 'IPW ', 'IPW'))
addRow(tbl, 'IPW (Italian)', parentArg(frame, 'ipw_it'))
addRow(tbl, 'IPW (Italian)', teamLinkList(parentArg(frame, 'ipw_it'), 'IPW Italian ', 'IPW Italian'))
addRow(tbl, 'Special', parentArg(frame, 'special'))
addRow(tbl, 'Special', prettyProse(parentArg(frame, 'special')))
addRow(tbl, 'LOC', parentArg(frame, 'loc'))
addRow(tbl, 'LOC', parentArg(frame, 'loc'))
addRow(tbl, 'Arrived', parentArg(frame, 'date_arrive'))
addRow(tbl, 'Arrived', parentArg(frame, 'date_arrive'))
Line 454: Line 714:
end
end


return string.format(
local lead = string.format(
"'''%s'''%s was a %s%s.",
"'''%s'''%s was a %s%s.",
name,
name,
Line 461: Line 721:
trained
trained
)
)
local who = prettyNamePart(parentArg(frame, 'first'))
if who == '' then
who = prettyNamePart(parentArg(frame, 'surname'))
end
if who == '' then
who = 'He'
end
local enlist = prettyPlace(parentArg(frame, 'enlistment_place'))
if enlist ~= '' then
lead = lead .. ' ' .. who .. ' enlisted at ' .. enlist .. '.'
end
return lead
end
function p.researchNotes(frame)
local title = mw.title.getCurrentTitle()
if not title or not title.getContent then
return ''
end
local ok, content = pcall(function()
return title:getContent()
end)
if not ok or not content or content == '' then
return ''
end
local startPos = mw.ustring.find(content, '== Research notes ==', 1, true)
if not startPos then
return ''
end
local after = mw.ustring.sub(content, startPos + 20)
local nextHead = mw.ustring.find(after, '\n== ', 1, true)
local section = after
if nextHead then
section = mw.ustring.sub(after, 1, nextHead - 1)
end
local items = {}
for line in mw.text.gsplit(section, '\n', true) do
line = trim(line)
if mw.ustring.sub(line, 1, 2) == '* ' then
local note = sanitizeNote(mw.ustring.sub(line, 3))
if note ~= '' then
table.insert(items, note)
end
end
end
if #items == 0 then
return ''
end
local wrap = mw.html.create('div')
wrap:addClass('soldier-research-notes')
wrap:tag('div'):addClass('soldier-research-notes-heading'):wikitext('Research notes')
local ul = wrap:tag('ul')
for _, note in ipairs(items) do
ul:tag('li'):wikitext(note)
end
return tostring(wrap)
end
end


return p
return p