Module:Category described in year

This is an old revision of this page, as edited by Tom.Reding (talk | contribs) at 20:30, 27 May 2018 (+[Category:Described in year error|U] for unknown configuration; --ce). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

require('Module:No globals')

--case sensitive conf/map table
local conf = {
	--[[========================================================================
		
	'Fish' is the default category tree: year -> century -> Species, no decades.
	'Spiders' & 'Plants' use year -> decade -> century -> Species.
	
	Any category group (i.e. Birds/Crustaceans/Molluscs/etc.) NOT explicitly 
	outlined here in conf{} will follow the 'Fish' tree for that group.
	
	To create a customized tree, like 'Spiders' below, add it to the bottom of 
	the conf{} table, following the same general table format.
		
	========================================================================]]-- 
	
	['tocmin'] = 401, --integer; minimum category size to add {{Category TOC}}
	
	['Fish'] = { --group
		['yearmin'] = 1758, --integer; lowest possible year displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
		['year'] = {    --[[Category:Fish described in 1901]]
			['description'] = 'This category should only contain species-level articles.',
			['parent1'] = 'century', --year/decade/century/formal, i.e. 'century' for [[Category:Fish described in the 20th century]]
			['parent2'] = 'Animals', --[[Category:Animals described in the 20th century]]
		},
		['century'] = {              --[[Category:Fish described in the 20th century]]
			['description'] = '',    --'Description tbd; century; container category, etc....',
			['parent1'] = 'formal',  --[[Category:Fish by year of formal description]]
			['parent2'] = 'Animals', --[[Category:Animals described in the 20th century]]
		},
	},
	
	['Spiders'] = { --group
		['yearmin'] = 1757, --integer; lowest possible year (and thus decade) displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
		['year'] = {    --[[Category:Spiders described in 1901]]
			['description'] = '',    --'Description tbd; year; This category should only contain species articles, etc....',
			['parent1'] = 'decade',  --[[Category:Spiders described in the 1900s]]
			['parent2'] = 'Animals', --[[Category:Animals described in 1900]]
		},
		['decade'] = { --[[Category:Spiders described in the 1900s]]
			['description'] = '',    --'Description tbd; decade; container category, etc....',
			['parent1'] = 'century', --[[Category:Spiders described in the 20th century]]
			['parent2'] = 'Animals', --[[Category:Animals described in the 1900s]]
		},
		['century'] = { --[[Category:Spiders described in the 20th century]]
			['description'] = '',    --'Description tbd; century; container category, etc....',
			['parent1'] = 'formal',  --[[Category:Spiders by year of formal description]]
			['parent2'] = 'Animals', --[[Category:Animals described in the 20th century]]
		},
	},

	['Plants'] = { --group
		['yearmin'] = 1753, --integer; lowest possible year (and thus decade) displayed in nav bars; 0/nil defaults to 1758 per ICZN Art. 5
		['year'] = {    --[[Category:Plants 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]]
			['parent1'] = 'decade',  --[[Category:Plants described in the 1900s]]
			['parent2'] = 'Species', --[[Category:Species described in 1900]]
		},
		['decade'] = { --[[Category:Plants described in the 1900s]]
			['description'] = '',    --'Description tbd; decade; container category, etc....',
			['parent1'] = 'century', --[[Category:Plants described in the 20th century]]
			['parent2'] = 'Species', --[[Category:Species described in the 1900s]]
		},
		['century'] = { --[[Category:Plants 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]]
			['parent1'] = 'formal',  --[[Category:Plants by year of formal description]]
			['parent2'] = 'Species', --[[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
	
	--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
	
	if currCat then
		
		--determine current/related/adjacent cats' properties/vars/etc
		local currGroup = mw.ustring.match(currCat, '^%w+') --Fish/Spiders/default/etc.
		if conf[currGroup] == nil then conf[currGroup] = conf['Fish'] end --'Fish' == default
		local currYDC = nil --possible 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 (i.e. Spiders) & currDeca (i.e. Spiders)
		local parentCent = nil --used with currYear (i.e. Fish) & currDeca (i.e. Spiders)
		local lastCent, nextCent = nil, nil --used with currYear (i.e. Fish) & currCent (i.e. Fish)
		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
			trackingCategories[2] = '[[Category:Described in year error|N]]' --invalid category name
		end
		
		--produce description & toc
		if currYDC then
			description = conf[currGroup][currYDC].description
			if mw.ustring.match(description, '%%year%%') then
				if currYear then description = mw.ustring.gsub(description, '%%year%%', currYear) --"2001"
				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') --"2000s"
				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 currYDC then
			local iparent = 1
			local parenti = 'parent' .. iparent
			while conf[currGroup][currYDC][parenti] do
				local parent = conf[currGroup][currYDC][parenti]
				
				if currYDC == 'year' then
					if nav == nil then
						local args = { year = currYear, min = minYear, cat = currGroup .. ' described in' }
						nav = frame:expandTemplate{ title = 'Category in year', args = args }
					end
					if parent == 'decade' then
						categories[iparent] = '[[Category:' .. currGroup .. ' described in the ' .. parentDeca .. 's]]'
					elseif parent == 'century' then
						categories[iparent] = '[[Category:' .. currGroup .. ' described in the ' .. addOrd(parentCent) .. ' century]]'
					elseif mw.ustring.match(parent, '^%u%l') then --i.e. Animals
						categories[iparent] = '[[Category:' .. parent .. ' described in ' .. currYear .. ']]'
					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
						categories[iparent] = '[[Category:' .. currGroup .. ' described in the ' .. addOrd(parentCent) .. ' century]]'
					elseif mw.ustring.match(parent, '^%u%l') then --i.e. Animals
						categories[iparent] = '[[Category:' .. parent .. ' described in the ' .. currDeca .. 's]]'
					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
						categories[iparent] = '[[Category:' .. currGroup .. ' by year of formal description|' .. addOrd(currCent) .. ']]'
					elseif mw.ustring.match(parent, '^%u%l') then --i.e. Animals
						categories[iparent] = '[[Category:' .. parent .. ' described in the ' .. addOrd(currCent) .. ' century]]'
					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
			end --while conf[currGroup][currYDC][parenti] do
		end --if currYDC 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 .. '<br />' .. 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