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:Featured

From Ritchie Boys Wiki
Revision as of 19:58, 2 September 2026 by Admin (talk | contribs) (Only parse bullets under == Pool ==)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Featured/doc

-- Weekly rotating featured soldiers for the Main Page (level-2 curated pool).
-- Pool page: [[Featured pool]] — one bullet per soldier; sysop-edit only.
--
-- Line formats (pipe-separated, optional fields):
--   * Page title
--   * Page title | Caption
--   * Page title | Caption | Filename.jpg
-- If caption or photo is omitted, the module derives them from the soldier page.

local p = {}

local POOL_TITLE = 'Featured pool'
local DEFAULT_COUNT = 3

local function trim(s)
	return mw.text.trim(tostring(s or ''))
end

local function displayFromTitle(title)
	title = trim(title)
	-- "Surname, Given (ASN)" → "Given Surname"
	local surname, given = mw.ustring.match(title, '^([^,]+),%s*(.-)%s*%(%d')
	if surname and given then
		given = mw.ustring.gsub(given, '%s+$', '')
		return trim(given .. ' ' .. surname)
	end
	surname, given = mw.ustring.match(title, '^([^,]+),%s*(.+)$')
	if surname and given then
		return trim(given .. ' ' .. surname)
	end
	return title
end

local function extractPhoto(wikitext)
	if not wikitext or wikitext == '' then
		return ''
	end
	local photo = mw.ustring.match(wikitext, '|%s*photo%s*=%s*([^\n|}]+)')
	if not photo then
		return ''
	end
	photo = trim(photo)
	photo = mw.ustring.gsub(photo, '^%[%[File:', '')
	photo = mw.ustring.gsub(photo, '%]%]$', '')
	photo = mw.ustring.gsub(photo, '|.*$', '')
	photo = mw.ustring.gsub(photo, '^File:', '')
	return trim(photo)
end

local function parseLine(line)
	line = trim(line)
	if line == '' then
		return nil
	end
	-- Strip list markers; skip headings and HTML comments
	line = mw.ustring.gsub(line, '^[*#:;]+%s*', '')
	if line == '' or mw.ustring.sub(line, 1, 1) == '=' then
		return nil
	end
	if mw.ustring.find(line, '^%<%!%-%-', 1, true) then
		return nil
	end

	-- [[Title|Label]] or [[Title]] at the start → plain fields
	line = mw.ustring.gsub(line, '^%[%[([^%]|]+)|([^%]]+)%]%]', '%1|%2')
	line = mw.ustring.gsub(line, '^%[%[([^%]|]+)%]%]', '%1')

	local parts = mw.text.split(line, '|')
	local title = trim(parts[1] or '')
	local caption = trim(parts[2] or '')
	local photo = trim(parts[3] or '')

	if title == '' then
		return nil
	end
	if photo ~= '' then
		photo = mw.ustring.gsub(photo, '^File:', '')
	end

	return {
		title = title,
		caption = caption,
		photo = photo,
	}
end

local function loadPool()
	local titleObj = mw.title.new(POOL_TITLE)
	if not titleObj or not titleObj.exists then
		return {}
	end
	local content = titleObj:getContent()
	if not content or content == '' then
		return {}
	end

	-- Only read bullets under == Pool == (ignore examples elsewhere on the page)
	local section = mw.ustring.match(content, '\n==%s*Pool%s*==\n(.-)\n==%s*')
	if not section then
		section = mw.ustring.match(content, '^==%s*Pool%s*==\n(.-)\n==%s*')
	end
	if not section then
		section = mw.ustring.match(content, '\n==%s*Pool%s*==\n(.*)$')
			or mw.ustring.match(content, '^==%s*Pool%s*==\n(.*)$')
	end
	if not section then
		return {}
	end

	local entries = {}
	for line in mw.text.gsplit(section, '\n', true) do
		local entry = parseLine(line)
		if entry then
			table.insert(entries, entry)
		end
	end
	return entries
end

local function enrich(entry)
	local title = entry.title
	local caption = entry.caption
	local photo = entry.photo

	if caption == '' then
		caption = displayFromTitle(title)
	end

	if photo == '' then
		local page = mw.title.new(title)
		if page and page.exists then
			photo = extractPhoto(page:getContent() or '')
		end
	end

	return {
		title = title,
		caption = caption,
		photo = photo,
	}
end

local function weekOffset(n)
	if n < 1 then
		return 0
	end
	local lang = mw.getContentLanguage()
	-- ISO week-of-year + ISO week-year so Dec/Jan boundary stays stable
	local week = tonumber(lang:formatDate('W')) or 1
	local year = tonumber(lang:formatDate('o')) or tonumber(lang:formatDate('Y')) or 2026
	local key = year * 53 + week
	return key % n
end

local function renderThumb(entry)
	local title = entry.title
	local caption = entry.caption
	local photo = entry.photo
	local link = string.format('[[%s|%s]]', title, caption)

	if photo ~= '' then
		return string.format(
			'<div class="home-feature-thumb">[[File:%s|150x220px|link=%s|alt=%s]]'
				.. '<div class="home-feature-caption">%s</div></div>',
			photo,
			title,
			caption,
			link
		)
	end
	-- No photo yet: caption-only card so the pool entry still appears
	return string.format(
		'<div class="home-feature-thumb home-feature-thumb-nophoto">'
			.. '<div class="home-feature-caption">%s</div></div>',
		link
	)
end

function p.main(frame)
	local args = frame.args
	local parent = frame:getParent()
	if parent and parent.args then
		for k, v in pairs(parent.args) do
			if args[k] == nil or args[k] == '' then
				args[k] = v
			end
		end
	end

	local count = tonumber(args.count or args[1]) or DEFAULT_COUNT
	if count < 1 then
		count = DEFAULT_COUNT
	end
	if count > 12 then
		count = 12
	end

	local pool = loadPool()
	if #pool == 0 then
		return '<div class="home-feature-thumbs"><!-- Featured pool is empty — edit [[Featured pool]] --></div>'
	end

	local offset = weekOffset(#pool)
	local parts = { '<div class="home-feature-thumbs">' }
	for i = 0, count - 1 do
		local idx = ((offset + i) % #pool) + 1
		table.insert(parts, renderThumb(enrich(pool[idx])))
	end
	table.insert(parts, '</div>')
	return table.concat(parts)
end

-- Debug helper: {{#invoke:Featured|preview|offset=0|count=3}}
function p.preview(frame)
	local args = frame.args
	local count = tonumber(args.count) or DEFAULT_COUNT
	local pool = loadPool()
	if #pool == 0 then
		return 'Pool empty'
	end
	local offset = tonumber(args.offset) or weekOffset(#pool)
	offset = offset % #pool
	local lines = {
		string.format('Pool size: %d; offset: %d; showing %d', #pool, offset, count),
	}
	for i = 0, count - 1 do
		local idx = ((offset + i) % #pool) + 1
		local e = enrich(pool[idx])
		table.insert(
			lines,
			string.format('* %s — %s — photo=%s', e.title, e.caption, e.photo ~= '' and e.photo or '(none)')
		)
	end
	return table.concat(lines, '\n')
end

return p