Module:Project statistics

Module documentation
local p = {}
local dataByWiki = require("Module:Project portal/wikis")
local contentLang = mw.getContentLanguage()
local datePattern = "Last update: ... (%d%d ... %d%d%d%d)"

-- Returns a title object for the page that contains statistics about the given
-- project.
function p.statsTitleForProject(project)
	if project == "wikipedia" then
		return mw.title.new("List of Wikipedias/Table", 0)
	end
	return mw.title.new(project .. "/Table", 0)
end

function p.getStatistics(project)
	project = project or "wikipedia"
	local statsTable = mw.ext.data.get("Wikipedia statistics/data.tab")
	
	-- Metadata
	local timestamp = mw.ustring.match(statsTable.description, datePattern)
	local date = mw.text.split(contentLang:formatDate("Y-m-d", timestamp), "-")
	
	local wikis = {}
	local totals = {}
	for i, row in ipairs(statsTable.data) do
		local wiki = mw.ustring.match(row[1], "(.+)%." .. project)
		if wiki ~= nil then
			local stats = {
				wiki = wiki,
				articles = row[4],
				pages = row[7],
				edits = row[5],
				admins = row[3],
				users = row[8],
				activeUsers = row[2],
				files = row[6],
			}
			local data = dataByWiki[wiki] and dataByWiki[wiki][project]
			if data and data.closed then
				stats.closed = true
			end
			-- [[Wikipedia article depth]]
			stats.depth = stats.edits / stats.pages * ((stats.pages - stats.articles) / stats.articles) ^ 2
			if wiki == "total" or wiki == "totalactive" or wiki == "totalclosed" then
				totals[wiki] = stats
			else
				wikis[wiki] = stats
			end
		end
	end
	
	-- Sometimes the statistics pages don’t get updated right after a wiki is
	-- created. The workaround is to temporarily set [project].numArticles in
	-- /wikis to the number of articles.
	for wiki, data in pairs(dataByWiki) do
		local data = dataByWiki[wiki] and dataByWiki[wiki][project]
		if not wikis[wiki] and data and not data.closed then
			wikis[wiki] = {
				manual = true,
				wiki = wiki,
				articles = data.numArticles,
				pages = data.numPages,
				edits = data.numEdits,
				admins = data.numAdmins,
				users = data.numUsers,
				activeUsers = data.numActiveUsers,
				files = data.numFiles,
			}
		end
	end
	
	return {
		project = project,
		source = title,
		date = date,
		wikis = wikis,
		totals = totals,
	}
end

function round(x)
	if x > 0 then
		return math.floor(x + 0.5)
	else
		return math.ceil(x - 0.5)
	end
end

function p.wikitable(frame)
	local language = mw.getLanguage(frame.args.language or "en")
	local project = frame.args.project
	local stats = p.getStatistics(project)
	local lines = {}
	
	local headerCells = {
		frame.args.rank or "№",
		frame.args.linguonym or "Language",
		frame.args.autonym or "Language (local)",
		frame.args.articles or "Articles",
		frame.args.code or "Wiki",
		frame.args.pages or "All pages",
		frame.args.edits or "Edits",
		frame.args.admins or "Admins",
		frame.args.users or "Users",
		frame.args.activeUsers or "Active users",
		frame.args.files or "Files",
	}
	if project == "wikipedia" then
		table.insert(headerCells, mw.ustring.format("[[Wikipedia article depth|%s]]", frame.args.depth or "Depth"))
	end
	local header = "|-\n! " .. table.concat(headerCells, " !! ")
	table.insert(lines, header)
	
	local sortedStats = {}
	for wiki, row in pairs(stats.wikis) do
		table.insert(sortedStats, row)
	end
	table.sort(sortedStats, function(a, b)
		return a.articles > b.articles
	end)
	
	-- Repeat the rank in case of a tie.
	for i, row in ipairs(sortedStats) do
		row.rank = i
		if i > 1 and row.articles == sortedStats[i - 1].articles then
			row.rank = sortedStats[i - 1].rank
		end
	end
	
	local languageNames = mw.language.fetchLanguageNames(frame.args.language)
	local autonyms = mw.language.fetchLanguageNames()
	local articleFormat = frame.args.articleFormat or "%s language"
	for rank, row in ipairs(sortedStats) do
		local wiki = row.wiki
		if not row.closed then
			local languageName
			if languageNames[wiki] then
				languageName = mw.ustring.format("[[w:%s:%s|%s]]",
					frame.args.language or "en",
					mw.ustring.format(articleFormat, languageNames[wiki]),
					languageNames[wiki])
			else
				-- Akan is not a language and thus has no language name anymore,
				-- but ak.wikipedia.org still exists (same for the autonym);
				-- cf. [[Proposals for closing projects/Closure of Akan Wikipedia 2]]
				languageName = '?'
			end
			local cells = {
				"align='right' | " .. language:formatNum(rank),
				languageName,
				autonyms[wiki] or '?',
				mw.ustring.format("[[%s:%s:|%s]]", project, wiki, wiki),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:Statistics|%s]]", project, wiki, language:formatNum(row.articles)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:AllPages|%s]]", project, wiki, language:formatNum(row.pages)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:RecentChanges|%s]]", project, wiki, language:formatNum(row.edits)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:ListUsers/sysop|%s]]", project, wiki, language:formatNum(row.admins)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:ListUsers|%s]]", project, wiki, language:formatNum(row.users)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:ActiveUsers|%s]]", project, wiki, language:formatNum(row.activeUsers)),
				"align='right' | " .. mw.ustring.format("[[%s:%s:Special:NewFiles|%s]]", project, wiki, language:formatNum(row.files)),
			}
			if project == "wikipedia" then
				-- row.depth will be the positive infinity if there are no articles (division by zero)
				table.insert(cells, "align='right' | " .. (row.depth == math.huge and '–' or language:formatNum(round(row.depth))))
			end
			local line = "|-\n| " .. table.concat(cells, " || ")
			table.insert(lines, line)
		end
	end
	
	return "{| class='wikitable sortable'\n" .. table.concat(lines, "\n") .. "\n|}"
end

return p