require('Module:No globals')
--case sensitive conf/map table
local conf = {
--[[============================================================================
Any category group (e.g. Bacteria/Plants/etc.) NOT explicitly outlined here
in conf{} will follow the 'Default' tree for that group when the template is
used on that cateogry.
The default-style category tree is: year -> century -> Animals.
Decadal categories are discouraged.
To create a new tree, only if different from the default, copy an existing
tree and paste it at the end of the conf{} table, following the same general
table format, and updating any group-specific variables.
============================================================================]]--
['tocmin'] = 401, --integer; minimum category size to add {{Category TOC}}
['Default'] = { --"group" (e.g. Amphibians/Birds/Crustaceans/Fish/Mammals/Molluscs/Reptiles/Spiders)
['yearmin'] = 1758, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Birds described in 1901]]
['parent1'] = 'century', --[[Category:Birds described in the 20th century]] --decade, century, or formal
['parent2'] = 'Animals', --[[Category:Animals described in 1901]]
['description'] = 'This category should only contain articles on [[species]] and notable [[subspecies]].', --adapted from [[Category:Species described in the 18th century]] & [[Category:Species described in the 21st century]]
},
['century'] = { --[[Category:Birds described in the 20th century]]
['parent1'] = 'formal', --[[Category:Birds by year of formal description]]
['parent2'] = 'Animals', --[[Category:Animals described in the 20th century]]
['description'] = '', --Century description tbd; container category, etc...
},
},
['Animals'] = { --"group"
['yearmin'] = 1757, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Animals described in 1901]]
['parent1'] = 'century', --[[Category:Animals described in the 20th century]]
['parent2'] = 'Species', --[[Category:Species described in 1901]]
['description'] = '', --Year description tbd; *not* a container cat
},
['century'] = { --[[Category:Animals described in the 20th century]]
['parent1'] = 'formal', --[[Category:Animals by year of formal description]]
['parent2'] = 'Species', --[[Category:Species described in the 20th century]]
['description'] = '', --Century description tbd; container category, etc...
},
},
--[[============================ Insects tree ============================]]
['Butterflies'] = { --"subgroup" to Insects
['yearmin'] = 1758, --integer; lowest possible year (and thus decade) displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Butterflies described in 1901]]
['parent1'] = 'century', --[[Category:Butterflies described in the 20th century]]
['parent2'] = 'Insects', --[[Category:Insects described in 1901]]
['description'] = '', --Year description tbd; 'This category should only contain species articles', etc...
},
['century'] = { --[[Category:Butterflies described in the 20th century]]
['parent1'] = 'formal', --[[Category:Butterflies by year of formal description]]
['parent2'] = 'Insects', --[[Category:Insects described in the 20th century]]
['sortkey2'] = '*',
['description'] = '', --Century description tbd; container category, etc...
},
},
['Moths'] = { --"subgroup" to Insects
['yearmin'] = 1758, --integer; lowest possible year (and thus decade) displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Moths described in 1901]]
['parent1'] = 'century', --[[Category:Moths described in the 20th century]]
['parent2'] = 'Insects', --[[Category:Insects described in 1901]]
['description'] = '', --Year description tbd; 'This category should only contain species articles', etc...
},
['century'] = { --[[Category:Moths described in the 20th century]]
['parent1'] = 'formal', --[[Category:Moths by year of formal description]]
['parent2'] = 'Insects', --[[Category:Insects described in the 20th century]]
['sortkey2'] = '*',
['description'] = '', --Century description tbd; container category, etc...
},
},
--[[============================= Non-animals ============================]]
['Bacteria'] = { --"group"
['yearmin'] = 1758, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Plants described in 1901]]
['parent1'] = 'century', --[[Category:Plants described in the 20th century]]
['parent2'] = 'Species', --[[Category:Species described in 1901]]
['description'] = '', --Year description tbd; 'This category should only contain species articles', etc...
},
['century'] = { --[[Category:Plants described in the 20th century]]
['parent1'] = 'formal', --[[Category:Plants by year of formal description]]
['parent2'] = 'Species', --[[Category:Species described in the 20th century]]
['description'] = '', --Century description tbd; container category, etc...
},
},
['Fungi'] = { --"group"
['yearmin'] = 1753, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Fungi described in 1901]]
['parent1'] = 'century', --[[Category:Fungi described in the 20th century]]
['parent2'] = 'Species', --[[Category:Species described in 1901]]
['description'] = '', --Year description tbd; 'This category should only contain species articles', etc...
},
['century'] = { --[[Category:Fungi described in the 20th century]]
['parent1'] = 'formal', --[[Category:Fungi by year of formal description]]
['parent2'] = 'Species', --[[Category:Species described in the 20th century]]
['description'] = '', --Century description tbd; container category, etc...
},
},
['Plants'] = { --"group"
['yearmin'] = 1753, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
['year'] = { --[[Category:Plants described in 1901]]
['parent1'] = 'century', --[[Category:Plants described in the 20th century]]
['parent2'] = 'Species', --[[Category:Species described in 1901]]
['description'] = "This category includes plants that were ''first formally and validly described'' in %year% according to the rules of the [[International Code of Botanical Nomenclature]]. Use [[WP:RS|reliable sources]] like the [[International Plant Names Index]] to figure out the proper category. For examples see the [[Wikipedia:WikiProject Plants/Description in year categories|WikiProject Plants essay]] on this topic.", --taken from [[Category:Plants described in 1928]]
},
['century'] = { --[[Category:Plants described in the 20th century]]
['parent1'] = 'formal', --[[Category:Plants by year of formal description]]
['parent2'] = 'Species', --[[Category:Species described in the 20th century]]
['description'] = 'For advice on using the subcategories of this category, see [[Wikipedia:WikiProject Plants/Description in year categories]].', --taken from [[Category:Plants described in the 21st century]]
},
},
--[=[ Finalize & implement only after discussions at
[[WT:PALEO#'Fossil taxa described in' categories categories]] &
[[WT:TREE#Taxa described in]] are concluded
--[[==================== "Species described in"-level ====================]]
['Species'] = {
['year'] = { --[[Category:Species described in 1901]]
['parent1'] = 'century', --[[Category:Species described in the 20th century]]
['sortkey1'] = ' ',
['parent2'] = 'biology', --[[Category:1901 in biology]] ?
['parent3'] = 'Taxa', --[[Category:Taxa described in 1901]] ??
['description'] = '', --Year description tbd
},
['century'] = { --[[Category:Species described in the 20th century]]
['parent1'] = 'formal', --[[Category:Species by year of formal description]]
['parent2'] = 'Taxa', --[[Category:Taxa described in the 20th century]] ??
['description'] = "*'''[[Species]]''' of [[Biota (taxonomy)|biota]] described in the '''[[%%century%% century]]'''.", --taken from [[Category:Species described in the 19th century]] & [[Category:Species described in the 20th century]]
},
},
]=]
}
local function addOrd( i ) --20 -> 20th, etc.
if tonumber(i) then
local s = tostring(i)
local lastd = mw.ustring.match(s, '%d$')
if lastd == '1' then return s..'st'
elseif lastd == '2' then return s..'nd'
elseif lastd == '3' then return s..'rd'
elseif lastd ~= nil then return s..'th'
end
end
return ''
end
local p = {}
function p.autodetect( frame )
local currentTitle = mw.title.getCurrentTitle()
local header = ' ' --header template(s), nav bar, and category description text; whitespace-initialized for convenience
local nav = nil
local commons = nil
local description = nil
local toc = nil
local categories = {}
local trackingCategories = {
[1] = '', --placeholder for [[Category:Described in year unknown category]]
[2] = '', --placeholder for [[Category:Described in year error]]
}
local outString = nil
local bConfError = false
--prelim namespace/title determination
local currCat = nil
local currQID = nil
if currentTitle.namespace == 14 then --Category:
currCat = currentTitle.text --without namespace nor interwiki prefixes
currQID = mw.wikibase.getEntityIdForCurrentPage()
else
--accept 1 unnamed category parameter if not in Category: space; required for testing/doc/etc purposes
local parentArg = frame:getParent().args[1]
if parentArg then
currCat = mw.ustring.gsub(parentArg, 'Category:', '')
currQID = mw.wikibase.getEntityIdForTitle('Category:' .. currCat)
else --currQID & currCat both nil
if currentTitle.fullText ~= 'Template:Category described in year' then --ignore self...
trackingCategories[2] = '[[Category:Described in year error|P]]' --missing a category parameter outside category-space
end
end
end
--find commons link(s) & produce {{Commons}} template(s)
if currQID then
local commonsLinks = {}
local currEntity = mw.wikibase.getEntity(currQID)
if currEntity then
--check Commons category property (P373)
local ccPropState = currEntity:getBestStatements('P373')[1]
if ccPropState then
local ccPropVal = ccPropState.mainsnak.datavalue.value
if ccPropVal then
commonsLinks[#commonsLinks + 1] = 'Category:' .. mw.ustring.gsub(ccPropVal, 'Category:', '')
end
end
--check Commons gallery property (P935)
local cgPropState = currEntity:getBestStatements('P935')[1]
if cgPropState then
local cgPropVal = cgPropState.mainsnak.datavalue.value
if cgPropVal then
commonsLinks[#commonsLinks + 1] = cgPropVal
end
end
--check "Other sites" sitelink
local currSiteLinks = currEntity.sitelinks
if currSiteLinks then
local currCommonsWiki = currEntity.sitelinks.commonswiki
if currCommonsWiki then
local currCommonsWikiTitle = currEntity.sitelinks.commonswiki.title
if currCommonsWikiTitle then
commonsLinks[#commonsLinks + 1] = currCommonsWikiTitle
end
end
end
end
--produce {{Commons}} template(s) (ignore duplicates)
if commonsLinks[1] then --turn these into a loop if # of commons sources >= 4
commons = frame:expandTemplate{ title = 'Commons', args = { commonsLinks[1] } }
if commonsLinks[2] and
commonsLinks[2] ~= commonsLinks[1] then
commons = commons .. frame:expandTemplate{ title = 'Commons', args = { commonsLinks[2] } }
end
if commonsLinks[3] and
commonsLinks[3] ~= commonsLinks[1] and
commonsLinks[3] ~= commonsLinks[2] then
commons = commons .. frame:expandTemplate{ title = 'Commons', args = { commonsLinks[3] } }
end
end
end --if currQID then
--[[======================================================================]]
--[[ Main ]]
--[[======================================================================]]
if currCat then
--determine current/related/adjacent cats' properties/vars/etc.
local currGroup = mw.ustring.match(currCat, '^%w+') --Bacteria/Plants/etc.
if conf[currGroup] == nil then conf[currGroup] = conf['Default'] end --default to Default
local currYDC = nil --possible future values: year/decade/century
local currYear = mw.ustring.match(currCat, 'described in (%d%d%d%d)$')
local currDeca = mw.ustring.match(currCat, 'described in the (%d%d%d%d)s$')
local currCent = mw.ustring.match(currCat, 'described in the (%d+)[snrt][tdh] century$')
local parentDeca = nil --used with currYear & currDeca
local parentCent = nil --used with currYear & currDeca
local lastCent, nextCent = nil, nil --used with currYear & currCent
local minYear = tonumber(conf[currGroup].yearmin)
if (minYear == nil or (minYear and minYear <= 1700)) then
minYear = 1758 --default to 1758 per ICZN Art. 5
end
if currYear then
currYDC = 'year'
parentDeca = mw.ustring.match(currYear, '^(%d%d%d)%d$') .. '0'
if mw.ustring.match(currYear, '^%d%d00') then --1900 in 19th century
parentCent = mw.ustring.match(currYear, '^%d%d')
else --1901 in 20th century
parentCent = 1 + mw.ustring.match(currYear, '^%d%d')
end
lastCent = parentCent - 1
nextCent = parentCent + 1
elseif currDeca then
currYDC = 'decade'
parentDeca = mw.ustring.match(currDeca, '^(%d%d%d)%d$') .. '0'
parentCent = mw.ustring.match(parentDeca, '^%d%d') + 1
elseif currCent then
currYDC = 'century'
lastCent = currCent - 1
nextCent = currCent + 1
else
bConfError = true
trackingCategories[2] = '[[Category:Described in year error|N]]' --invalid category name
end
--conf error checkng (missing keys)
--Numeric sortkeys are unfortunately grouped together under "0-9".
--Check phab T203355 (Magic word to force category number headings instead of 0-9).
if bConfError == false then
if conf[currGroup] == nil then
bConfError = true
trackingCategories[2] = '[[Category:Described in year error|1]]' --group (Bacteria/Plants/etc.) key missing from conf
elseif conf[currGroup][currYDC] == nil then
bConfError = true
trackingCategories[2] = '[[Category:Described in year error|2]]' --year/decade/century key missing
else
if conf[currGroup][currYDC].description == nil then
bConfError = true
trackingCategories[2] = '[[Category:Described in year error|3]]' --description key missing
end
if conf[currGroup][currYDC].parent1 == nil then
bConfError = true
trackingCategories[2] = '[[Category:Described in year error|4]]' --parent key missing
end
end
end
--produce description & toc
if bConfError == false then
description = conf[currGroup][currYDC].description
if mw.ustring.match(description, '%%year%%') then
if currYear then description = mw.ustring.gsub(description, '%%year%%', currYear) --"2011"
else description = mw.ustring.gsub(description, '%%year%%', 'this year') end
end
if mw.ustring.match(description, '%%decade%%') then
if currDeca then description = mw.ustring.gsub(description, '%%decade%%', currDeca .. 's') --"2010s"
else description = mw.ustring.gsub(description, '%%decade%%', 'this decade') end
end
if mw.ustring.match(description, '%%century%%') then
if currCent then description = mw.ustring.gsub(description, '%%century%%', addOrd(currCent)) --"21st"
else description = mw.ustring.gsub(description, '%%century%%', 'this century') end
end
if mw.site.stats.pagesInCategory(currCat, 'pages') >= conf['tocmin'] then --expensive
local args = { numerals = 'no' }
toc = frame:expandTemplate{ title = 'Category TOC', args = args }
end
end
--produce cats & navs
if bConfError == false then
local iparent = 1
local parenti = 'parent' .. iparent
local sortkeyi = 'sortkey' .. iparent
while conf[currGroup][currYDC][parenti] do
local parent = conf[currGroup][currYDC][parenti]
local sortkey = conf[currGroup][currYDC][sortkeyi]
if currYDC == 'year' then
if nav == nil then
local args = { linkcatifexists2 = 'yes' }
nav = frame:expandTemplate{ title = 'Navseasoncats', args = args }
end
if parent == 'decade' then
if sortkey == nil then sortkey = currYear end --default to currYear
categories[iparent] = '[[Category:'..currGroup..' described in the '..parentDeca..'s|'..sortkey..']]'
elseif parent == 'century' then
if sortkey == nil then sortkey = currYear end --default to currYear
categories[iparent] = '[[Category:'..currGroup..' described in the '..addOrd(parentCent)..' century|'..sortkey..']]'
elseif mw.ustring.match(parent, '^%u%l') then --e.g. Animals/Insects
if sortkey == nil then sortkey = '' --default to none
else sortkey = '|'..sortkey end
categories[iparent] = '[[Category:'..parent..' described in '..currYear..sortkey..']]'
else
trackingCategories[2] = '[[Category:Described in year error|Y]]' --invalid year-parent
end
elseif currYDC == 'decade' then
if nav == nil then
nav = frame:expandTemplate{ title = 'Container category' }
local args = { decade = currDeca, min = minYear, cat = currGroup .. ' described in the' }
nav = frame:expandTemplate{ title = 'Category by decade', args = args }
end
if parent == 'century' then
if sortkey == nil then sortkey = currDeca end --default to currDeca
categories[iparent] = '[[Category:'..currGroup..' described in the '..addOrd(parentCent)..' century|'..sortkey..']]'
elseif mw.ustring.match(parent, '^%u%l') then --e.g. Animals/Insects
if sortkey == nil then sortkey = '' --default to none
else sortkey = '|'..sortkey end
categories[iparent] = '[[Category:'..parent..' described in the '..currDeca..'s'..sortkey..']]'
else
trackingCategories[2] = '[[Category:Described in year error|D]]' --invalid decade-parent
end
elseif currYDC == 'century' then
if nav == nil then
nav = frame:expandTemplate{ title = 'Container category' }
local args = { currGroup .. ' described in the ' .. addOrd(lastCent) .. ' century',
currGroup .. ' described in the ' .. addOrd(nextCent) .. ' century' }
nav = nav .. frame:expandTemplate{ title = 'Category pair', args = args }
end
if parent == 'formal' then
if sortkey == nil then sortkey = addOrd(currCent) end --default to currCent
categories[iparent] = '[[Category:'..currGroup..' by year of formal description|'..sortkey..']]'
elseif mw.ustring.match(parent, '^%u%l') then --e.g. Animals/Insects
if sortkey == nil then sortkey = '' --default to none
else sortkey = '|'..sortkey end
categories[iparent] = '[[Category:'..parent..' described in the '..addOrd(currCent)..' century'..sortkey..']]'
else
trackingCategories[2] = '[[Category:Described in year error|C]]' --invalid century-parent
end
else
trackingCategories[2] = '[[Category:Described in year error|U]]' --unknown configuration
end
iparent = iparent + 1
parenti = 'parent' .. iparent
sortkeyi = 'sortkey' .. iparent
end --while conf[currGroup][currYDC][parenti] do
end --if bConfError == false then
--check for non-existent cats
for _, category in pairs(categories) do
local cat = mw.ustring.match(category, '%[%[Category:([%w%s]+)')
if mw.title.new(cat, 14).exists == false then
trackingCategories[1] = '[[Category:Described in year unknown category]]'
break
end
end
end --if currCat then
--build header & rem surrounding whitespace
if nav then header = nav end
if commons then header = header .. commons end
if description and description ~= '' then header = header .. description end
if toc then header = header .. '<br />' .. toc end
header = mw.text.trim(header)
header = mw.ustring.gsub(header, '^<br />', '')
header = mw.ustring.gsub(header, '<br />$', '')
--append header to outString
if outString then outString = outString .. header
else outString = header end
--append cats to outString
if currentTitle.namespace == 14 then --Category:
if table.maxn(categories) > 0 then outString = outString .. table.concat(categories) end
outString = outString .. table.concat(trackingCategories)
else
if table.maxn(categories) > 0 then --might be 0 if there's an error before setting cats
outString = outString .. '<br />' .. mw.ustring.gsub(table.concat(categories, '<br />'), '%[%[', '[[:')
end
outString = outString .. '<br />' .. mw.ustring.gsub(table.concat(trackingCategories, '<br />'), '%[%[', '[[:')
outString = mw.ustring.gsub(outString, '<br /><br />', '<br />') --produced by empty ('') first/consecutive tracking cat/s
outString = mw.ustring.gsub(outString, '<br /><br />', '<br />') --jic (use while loop if #trackingCategories >= 3 or 4)
end
return outString
end
return p