Module:T-CountryHub
Documentation for this module may be created at Module:T-CountryHub/doc
-- Modules/T-CountryHub.lua
-- Blueprint-based full-page template for country hubs with discrete content blocks
-- SAFE REQUIRE HELPERS
local function safeRequire(name)
local ok, mod = pcall(require, name)
if ok and mod then
return mod
end
return setmetatable({}, {
__index = function()
return function() return '' end
end
})
end
local p = {}
local Blueprint = safeRequire('Module:LuaTemplateBlueprint')
local ErrorHandling = safeRequire('Module:ErrorHandling')
local CountryData = safeRequire('Module:CountryData')
local mw = mw
local html = mw.html
local smw = (mw.smw and mw.smw.ask) and mw.smw or nil
-- Cache for Semantic queries
local _askCache = {}
local function askCached(key, params)
if not smw then return {} end
local serial = table.concat(params, '¦')
if _askCache[key or serial] then return _askCache[key or serial] end
local res = smw.ask(params) or {}
_askCache[key or serial] = res
return res
end
local function safeField(row, key)
if not row then return '' end
if row[key] ~= nil and row[key] ~= '' then return row[key] end
if row[1] ~= nil and row[1] ~= '' then return row[1] end
return ''
end
local function renderTable(result, columns)
local t = html.create('table'):addClass('wikitable')
-- Header row
local header = t:tag('tr')
for _, col in ipairs(columns) do
header:tag('th'):wikitext(col):done()
end
-- Data rows
for _, row in ipairs(result) do
local tr = t:tag('tr')
for _, col in ipairs(columns) do
tr:tag('td'):wikitext(safeField(row, col)):done()
end
end
return tostring(t)
end
local errorContext = ErrorHandling.createContext("T-CountryHub")
-- Register template with fullPage enabled
local template = Blueprint.registerTemplate('CountryHub', {
features = {
fullPage = true,
countryFlag = true,
countryWrapper = true,
infoBox = true,
intro = true,
overview = true,
organizations = true,
people = true,
laws = true,
documents = true,
geoTlds = true,
meetings = true,
nra = true,
relatedCategories = true,
externalResources = true,
semanticProperties = true,
categories = true,
errorReporting = true
},
constants = {
tableClass = ""
}
})
Blueprint.initializeConfig(template)
template.config.blocks = template.config.blocks or {}
-- Wrapper open block
template.config.blocks.wrapperOpen = {
feature = 'countryWrapper',
render = function() return '<div class="country-hub-wrapper">' end
}
-- Flag block
template.config.blocks.countryFlag = {
feature = 'countryFlag',
render = function(template, args)
return Blueprint.protectedExecute(
template,
'CustomBlock_countryFlag',
function()
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_', ' ')
local flagFile = CountryData.getFlagFileName(country)
if not flagFile then return "" end
local ok, fileUrl = pcall(function()
return mw.title.new('File:' .. flagFile):fullUrl()
end)
if not ok or not fileUrl then fileUrl = "" end
return string.format(
'<div class="country-hub-flag" style="background-image:url(\'%s\');"></div>',
fileUrl
)
end, '', args
)
end
}
-- Info box block
template.config.blocks.infoBox = {
feature = 'infoBox',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
-- ccTLD query (one line changed, two lines added)
local ccTLDParams = {
string.format('[[Has TLD type::Country code top-level domain]] [[Has country::%s]]', country),
mainlabel = 'ccTLD', -- give the title a predictable key
limit = 1 -- you only need one hit
}
local ccTLDData = askCached('infoBox:ccTLD:' .. country, ccTLDParams)
local ccTLDText = ccTLDData[1] and ccTLDData[1]['ccTLD'] or
string.format('No ccTLD found for %s.', country)
-- ICANN region query
local regionParams = {
string.format('[[Has country::%s]]', country),
'?Has ICANN region',
format = 'plain',
limit = 1
}
local regionData = askCached('infoBox:region:' .. country, regionParams)
local regionText = regionData[1] and regionData[1]['Has ICANN region'] or ''
-- ISOC chapter query
local ISOCParams = {
string.format('[[Category:ISOC Chapter]] [[Has country::%s]]', country),
limit = 1
}
local ISOCData = askCached('infoBox:ISOC:' .. country, ISOCParams)
local ISOCText = ISOCData[1] and ISOCData[1]['result'] or ''
-- Youth IGF query
local youthParams = {
string.format('[[Has country::%s]] [[Category:Youth IGF]]', country),
'?Has website',
mainlabel = 'Youth IGF',
format = 'plain',
sort = 'Has date',
order = 'desc'
}
local youthData = askCached('infoBox:youth:' .. country, youthParams)
local youthText = youthData[1] and youthData[1]['result'] or string.format('No Youth IGF found for %s.', country)
-- Build the infobox table
local infoBox = html.create('table')
:addClass('wikitable')
:css('float', 'right')
:css('margin-left', '1em')
:css('width', '300px')
-- Header row
infoBox:tag('tr')
:tag('th')
:attr('colspan', '2')
:css('text-align', 'center')
:css('background-color', 'green')
:wikitext(string.format('%s', country))
:done()
:done()
-- ccTLD row
infoBox:tag('tr')
:tag('td'):wikitext('ccTLD'):done()
:tag('td'):wikitext(ccTLDText):done()
:done()
-- ccNSO query
local ccNSOParams = {
string.format('([[Category:ccNSO]] OR [[Has membership::ccNSO]]) [[Has country::%s]]', country),
format = 'plain'
}
local ccNSOData = askCached('infoBox:ccNSO:' .. country, ccNSOParams)
local ccNSOText = ccNSOData[1] and ccNSOData[1]['result'] or string.format('No ccNSO membership for %s.', country)
-- ccNSO row
infoBox:tag('tr')
:tag('td'):wikitext('ccNSO'):done()
:tag('td'):wikitext(ccNSOText):done()
:done()
-- ICANN region row
infoBox:tag('tr')
:tag('td'):wikitext('ICANN region'):done()
:tag('td'):wikitext(regionText):done()
:done()
-- ISOC chapter row
infoBox:tag('tr')
:tag('td'):wikitext('ISOC chapter'):done()
:tag('td'):wikitext(ISOCText):done()
:done()
-- Youth IGF row
infoBox:tag('tr')
:tag('td'):wikitext('Youth IGF'):done()
:tag('td'):wikitext(youthText):done()
:done()
return tostring(infoBox)
end
}
-- Intro paragraph block
template.config.blocks.intro = {
feature = 'intro',
render = function()
return '<p>Welcome to ICANNWiki\'s hub for {{PAGENAME}}. With the use of semantics, this page aggregates all Internet Governance content for this territory that is currently indexed in our database.</p>'
end
}
-- Overview section block
template.config.blocks.overview = {
feature = 'overview',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Country]]',
'?Has description',
format = 'plain',
limit = 1
}
local data = askCached('overview:' .. country, params)
local desc = (data[1] and data[1]['Has description']) or ''
if desc == '' then
desc = 'No overview description found for ' .. country .. '.'
end
return '== Overview ==\n' .. desc
end
}
-- Organizations section block
template.config.blocks.organizations = {
feature = 'organizations',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Organization]]',
mainlabel = 'Organization',
limit = 50
}
local data = askCached('organizations:' .. country, params)
local htmlTable = renderTable(data, {'Organization'})
local browseLink = string.format(
'[{{fullurl:Special:BrowseData/Organization|Has_country=%s}} Browse all organizations for %s →]',
country, country
)
return '<div class="icannwiki-hubs-container"><div class="icannwiki-hub-column">\n'
.. '=== Organizations ===\n'
.. htmlTable .. '\n' .. browseLink .. '\n'
.. '</div>'
end
}
-- People section block
template.config.blocks.people = {
feature = 'people',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Person]]',
mainlabel = 'Person',
limit = 20
}
local data = askCached('people:' .. country, params)
local tableHtml = renderTable(data, {'Person'})
local link = string.format(
'[{{fullurl:Special:BrowseData/Person|Has_country=%s}} Browse all people for %s →]',
country, country
)
local html = '<div class="icannwiki-hubs-container"><div class="icannwiki-hub-column">\n' ..
'=== People ===\n' ..
tableHtml .. '\n' ..
link .. '\n' ..
'</div></div>'
return html
end,
}
-- Laws and Regulations block
template.config.blocks.laws = {
feature = 'laws',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Laws]]',
'?Has title', '?Has date', '?Has description',
mainlabel = 'Law', sort = 'Has date', order = 'desc',
limit = 50
}
local data = askCached('laws:' .. country, params)
local tableHtml = renderTable(data, {'Law','Has title','Has date','Has description'})
local link = string.format(
'[{{fullurl:Special:BrowseData/Laws|Has_country=%s}} Browse all laws and regulations for %s →]',
country, country
)
return '=== Laws and Regulations ===\n' .. tableHtml .. '\n' .. link
end,
}
-- Key Documents block
template.config.blocks.documents = {
feature = 'documents',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Document]]',
'?Has title', '?Has date', '?Has author',
mainlabel = 'Document', sort = 'Has date', order = 'desc',
limit = 50
}
local data = askCached('documents:' .. country, params)
local tableHtml = renderTable(data, {'Document','Has title','Has date','Has author'})
local link = string.format(
'[{{fullurl:Special:BrowseData/Document|Has_country=%s}} Browse all documents for %s →]',
country, country
)
return '=== Key Documents ===\n' .. tableHtml .. '\n' .. link
end,
}
-- GeoTLDs block
template.config.blocks.geoTlds = {
feature = 'geoTlds',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
'[[Category:gTLD]]',
string.format('[[Has geographic focus::%s]]', country),
'?Has registry operator', '?Has registration date',
mainlabel = 'GeoTLD',
limit = 50
}
local data = askCached('geoTlds:' .. country, params)
local tableHtml = renderTable(data, {'GeoTLD','Has registry operator','Has registration date'})
local link = string.format(
'[{{fullurl:Special:BrowseData/TLD|Has_country=%s}} Browse all TLDs for %s →]',
country, country
)
return '=== GeoTLDs ===\n' .. tableHtml .. '\n' .. link
end,
}
-- Meetings block
template.config.blocks.meetings = {
feature = 'meetings',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Meeting]]',
'?Has date', '?Has location', '?Has organizer',
mainlabel = 'Meeting', sort = 'Has date', order = 'desc',
limit = 50
}
local data = askCached('meetings:' .. country, params)
local tableHtml = renderTable(data, {'Meeting','Has date','Has location','Has organizer'})
local link = string.format(
'[{{fullurl:Special:BrowseData/Meeting|Has_country=%s}} Browse all meetings for %s →]',
country, country
)
return '=== Internet Governance Meetings ===\n' .. tableHtml .. '\n' .. link
end,
}
-- National Regulatory Authority block
template.config.blocks.nra = {
feature = 'nra',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:Regulatory Authority]]',
'?Has website', '?Has description',
mainlabel = 'Authority',
limit = 50
}
local data = askCached('nra:' .. country, params)
local tableHtml = renderTable(data, {'Authority','Has website','Has description'})
return '=== National Regulatory Authority ===\n' .. tableHtml
end,
}
-- Related Categories block
template.config.blocks.relatedCategories = {
feature = 'relatedCategories',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
return '== Related Categories ==\n' ..
'* [[:Category:' .. country .. '|All pages in Category:' .. country .. ']]\n' ..
'* {{#ask: [[Has country::' .. country .. ']] | format=category | limit=10 | sort=Has date | order=desc }}'
end,
}
-- External Resources block
template.config.blocks.externalResources = {
feature = 'externalResources',
render = function(template, args)
local country = args.country or mw.title.getCurrentTitle().text or ""
country = country:gsub('_',' ')
local params = {
string.format('[[Has country::%s]]', country),
'[[Category:External Resource]]',
'?Has URL', '?Has description',
mainlabel = 'Resource',
limit = 50
}
local data = askCached('externalResources:' .. country, params)
local tableHtml = renderTable(data, {'Resource','Has URL','Has description'})
return '== External Resources ==\n' .. tableHtml
end,
}
-- Wrapper close block
template.config.blocks.wrapperClose = {
feature = 'countryWrapper',
render = function() return '</div>' end
}
-- Define block sequence
template.config.blockSequence = {
'countryFlag',
'wrapperOpen',
'infoBox',
'intro',
'overview',
'organizations',
'people',
'laws',
'documents',
'geoTlds',
'meetings',
'nra',
'relatedCategories',
'externalResources',
'wrapperClose',
'categories',
'errors'
}
-- Preprocessor: seed country/region
Blueprint.addPreprocessor(template, function(_, args)
args.country = args.country or mw.title.getCurrentTitle().text or ""
args.has_country = CountryData.normalizeCountryName(args.country)
args.region = CountryData.getRegionByCountry(args.country)
return args
end)
-- Semantic property provider
Blueprint.registerPropertyProvider(template, function(_, args)
local props = {}
if args.has_country and args.has_country ~= "(Unrecognized)" then
props["Has country"] = args.has_country
props["Has ICANN region"] = args.region
end
return props
end)
-- Category provider
Blueprint.registerCategoryProvider(template, function(_, args)
local cats = {"Country Hub"}
if args.has_country and args.has_country ~= "(Unrecognized)" then
table.insert(cats, args.has_country)
table.insert(cats, args.region)
end
return cats
end)
-- Render entry point
function p.render(frame)
return ErrorHandling.protect(
errorContext, "render",
function() return template.render(frame) end,
ErrorHandling.getMessage("TEMPLATE_RENDER_ERROR"),
frame
)
end
return p