Module:CategoryAudit
Documentation for this module may be created at Module:CategoryAudit/doc
-- Module:CategoryAudit
-- Identifies brands and types that are used on pages but don't have
-- corresponding category pages registered for the Main Page listing.
local p = {}
local function trim(s)
if s == nil then return nil end
s = tostring(s)
s = s:gsub('^%s+', ''):gsub('%s+$', '')
if s == '' then return nil end
return s
end
-- Helper to build a "create category" URL
local function createCategoryUrl(categoryTitle, preloadTemplate, brandOrType)
return tostring(mw.uri.fullUrl(categoryTitle, {
action = 'edit',
preload = preloadTemplate,
['preloadparams[]'] = brandOrType
}))
end
-- Find CRT brands that are used but don't have a category page
function p.missingCRTBrands(frame)
-- Get all brands used on CRT pages (with pagination)
local usedBrandsSet = {}
local batchSize = 500
for batch = 0, 10 do
local offset = batch * batchSize
local queryWikitext =
'{{#ask:\n' ..
' [[Category:CRT models]]\n' ..
' [[Has brand::+]]\n' ..
' |?Has brand\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=' .. batchSize .. '\n' ..
' |offset=' .. offset .. '\n' ..
' |searchlabel=\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
if result then
for cellContent in result:gmatch('<td[^>]*>(.-)</td>') do
local brand = trim(cellContent:gsub('<[^>]+>', ''))
if brand and brand ~= '' then
usedBrandsSet[brand] = true
end
end
end
-- Check if we got a full batch
local cellCount = 0
if result then
for _ in result:gmatch('<td[^>]*>') do
cellCount = cellCount + 1
end
end
if cellCount < batchSize then break end
end
-- Get registered category pages
local registeredQuery =
'{{#ask:\n' ..
' [[Is CRT brand category::Yes]]\n' ..
' |?Brand name\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=500\n' ..
' |searchlabel=\n' ..
'}}'
local registeredResult = frame:preprocess(registeredQuery)
local registeredSet = {}
if registeredResult then
for cellContent in registeredResult:gmatch('<td[^>]*>(.-)</td>') do
local brand = trim(cellContent:gsub('<[^>]+>', ''))
if brand and brand ~= '' then
registeredSet[brand] = true
end
end
end
-- Find missing
local missing = {}
for brand, _ in pairs(usedBrandsSet) do
if not registeredSet[brand] then
table.insert(missing, brand)
end
end
table.sort(missing)
if #missing == 0 then
return "✓ ''All CRT brands have category pages.''"
end
local output = {}
for _, brand in ipairs(missing) do
local count = frame:preprocess('{{#ask:[[Category:CRT models]][[Has brand::' .. brand .. ']]|format=count}}')
local categoryTitle = 'Category:' .. brand .. ' CRTs'
local createUrl = createCategoryUrl(categoryTitle, 'Template:CRT brand category/preload', brand)
-- Use [[:Category:...]] with colon to make it a LINK not a category assignment
table.insert(output, '* <strong>' .. brand .. '</strong> — ' .. count .. ' CRTs need [[:' .. categoryTitle .. ']] — [' .. createUrl .. ' create it]')
end
return table.concat(output, '\n')
end
-- Find CRT types that are used but don't have a category page
function p.missingCRTTypes(frame)
local usedTypesSet = {}
local batchSize = 500
for batch = 0, 10 do
local offset = batch * batchSize
local queryWikitext =
'{{#ask:\n' ..
' [[Category:CRT models]]\n' ..
' [[Has CRT type::+]]\n' ..
' |?Has CRT type\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=' .. batchSize .. '\n' ..
' |offset=' .. offset .. '\n' ..
' |searchlabel=\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
if result then
for cellContent in result:gmatch('<td[^>]*>(.-)</td>') do
local crtType = trim(cellContent:gsub('<[^>]+>', ''))
if crtType and crtType ~= '' then
usedTypesSet[crtType] = true
end
end
end
local cellCount = 0
if result then
for _ in result:gmatch('<td[^>]*>') do
cellCount = cellCount + 1
end
end
if cellCount < batchSize then break end
end
-- Get registered
local registeredQuery =
'{{#ask:\n' ..
' [[Is CRT type category::Yes]]\n' ..
' |?CRT type name\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=500\n' ..
' |searchlabel=\n' ..
'}}'
local registeredResult = frame:preprocess(registeredQuery)
local registeredSet = {}
if registeredResult then
for cellContent in registeredResult:gmatch('<td[^>]*>(.-)</td>') do
local crtType = trim(cellContent:gsub('<[^>]+>', ''))
if crtType and crtType ~= '' then
registeredSet[crtType] = true
end
end
end
-- Find missing
local missing = {}
for crtType, _ in pairs(usedTypesSet) do
if not registeredSet[crtType] then
table.insert(missing, crtType)
end
end
table.sort(missing)
if #missing == 0 then
return "✓ ''All CRT types have category pages.''"
end
local output = {}
for _, crtType in ipairs(missing) do
local count = frame:preprocess('{{#ask:[[Category:CRT models]][[Has CRT type::' .. crtType .. ']]|format=count}}')
local categoryTitle = 'Category:' .. crtType
local createUrl = createCategoryUrl(categoryTitle, 'Template:CRT type category/preload', crtType)
table.insert(output, '* <strong>' .. crtType .. '</strong> — ' .. count .. ' CRTs need [[:' .. categoryTitle .. ']] — [' .. createUrl .. ' create it]')
end
return table.concat(output, '\n')
end
-- Find AV brands that are used but don't have a category page
function p.missingAVBrands(frame)
local usedBrandsSet = {}
local batchSize = 500
for batch = 0, 10 do
local offset = batch * batchSize
local queryWikitext =
'{{#ask:\n' ..
' [[Category:AV devices]]\n' ..
' [[Has brand::+]]\n' ..
' |?Has brand\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=' .. batchSize .. '\n' ..
' |offset=' .. offset .. '\n' ..
' |searchlabel=\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
if result then
for cellContent in result:gmatch('<td[^>]*>(.-)</td>') do
local brand = trim(cellContent:gsub('<[^>]+>', ''))
if brand and brand ~= '' then
usedBrandsSet[brand] = true
end
end
end
local cellCount = 0
if result then
for _ in result:gmatch('<td[^>]*>') do
cellCount = cellCount + 1
end
end
if cellCount < batchSize then break end
end
-- Get registered
local registeredQuery =
'{{#ask:\n' ..
' [[Is AV brand category::Yes]]\n' ..
' |?Brand name\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=500\n' ..
' |searchlabel=\n' ..
'}}'
local registeredResult = frame:preprocess(registeredQuery)
local registeredSet = {}
if registeredResult then
for cellContent in registeredResult:gmatch('<td[^>]*>(.-)</td>') do
local brand = trim(cellContent:gsub('<[^>]+>', ''))
if brand and brand ~= '' then
registeredSet[brand] = true
end
end
end
-- Find missing
local missing = {}
for brand, _ in pairs(usedBrandsSet) do
if not registeredSet[brand] then
table.insert(missing, brand)
end
end
table.sort(missing)
if #missing == 0 then
return "✓ ''All AV brands have category pages.''"
end
local output = {}
for _, brand in ipairs(missing) do
local count = frame:preprocess('{{#ask:[[Category:AV devices]][[Has brand::' .. brand .. ']]|format=count}}')
local categoryTitle = 'Category:' .. brand .. ' AV devices'
local createUrl = createCategoryUrl(categoryTitle, 'Template:AV brand category/preload', brand)
table.insert(output, '* <strong>' .. brand .. '</strong> — ' .. count .. ' devices need [[:' .. categoryTitle .. ']] — [' .. createUrl .. ' create it]')
end
return table.concat(output, '\n')
end
-- Find AV device types that are used but don't have a category page
function p.missingAVTypes(frame)
local usedTypesSet = {}
local batchSize = 500
for batch = 0, 10 do
local offset = batch * batchSize
local queryWikitext =
'{{#ask:\n' ..
' [[Category:AV devices]]\n' ..
' [[Has device type::+]]\n' ..
' |?Has device type\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=' .. batchSize .. '\n' ..
' |offset=' .. offset .. '\n' ..
' |searchlabel=\n' ..
'}}'
local result = frame:preprocess(queryWikitext)
if result then
for cellContent in result:gmatch('<td[^>]*>(.-)</td>') do
local deviceType = trim(cellContent:gsub('<[^>]+>', ''))
if deviceType and deviceType ~= '' then
usedTypesSet[deviceType] = true
end
end
end
local cellCount = 0
if result then
for _ in result:gmatch('<td[^>]*>') do
cellCount = cellCount + 1
end
end
if cellCount < batchSize then break end
end
-- Get registered
local registeredQuery =
'{{#ask:\n' ..
' [[Is AV device type category::Yes]]\n' ..
' |?Device type name\n' ..
' |format=broadtable\n' ..
' |headers=hide\n' ..
' |link=none\n' ..
' |mainlabel=-\n' ..
' |limit=500\n' ..
' |searchlabel=\n' ..
'}}'
local registeredResult = frame:preprocess(registeredQuery)
local registeredSet = {}
if registeredResult then
for cellContent in registeredResult:gmatch('<td[^>]*>(.-)</td>') do
local deviceType = trim(cellContent:gsub('<[^>]+>', ''))
if deviceType and deviceType ~= '' then
registeredSet[deviceType] = true
end
end
end
-- Find missing
local missing = {}
for deviceType, _ in pairs(usedTypesSet) do
if not registeredSet[deviceType] then
table.insert(missing, deviceType)
end
end
table.sort(missing)
if #missing == 0 then
return "✓ ''All AV device types have category pages.''"
end
local output = {}
for _, deviceType in ipairs(missing) do
local count = frame:preprocess('{{#ask:[[Category:AV devices]][[Has device type::' .. deviceType .. ']]|format=count}}')
local categoryTitle = 'Category:' .. deviceType .. 's'
local createUrl = createCategoryUrl(categoryTitle, 'Template:AV device type category/preload', deviceType)
table.insert(output, '* <strong>' .. deviceType .. '</strong> — ' .. count .. ' devices need [[:' .. categoryTitle .. ']] — [' .. createUrl .. ' create it]')
end
return table.concat(output, '\n')
end
return p