Jump to content

Module:Progress box

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Mr. Stradivarius (talk | contribs) at 05:28, 16 May 2015 (don't use the "all" category to find the total count, as counting the subcategories should be more accurate and requires less expensive function calls). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

-- This module implements [[Template:Progress box]]

local makePurgeLink = require('Module:Purge')._main
local lang = mw.language.getContentLanguage()
local CONFIG_MODULE = 'Module:Progress box/config'

-------------------------------------------------------------------------------
-- Message mixin
-- 
-- This function is mixed into all of the other classes
-------------------------------------------------------------------------------

local function message(self, key, ...)
	local msg = self._cfg[key]
	if not msg then
		error(string.format("no message found with key '%s'", tostring(key)), 2)
	end
	if select('#', ...) > 0 then
		return mw.message.newRawMessage(msg, ...):plain()
	else
		return msg
	end
end

-------------------------------------------------------------------------------
-- Category class
-------------------------------------------------------------------------------

local Category = {}
Category.__index = Category
Category.message = message

function Category.new(data)
	local self = setmetatable({}, Category)
	self._cfg = data.cfg
	self._category = data.category
	return self
end

function Category:getCategory()
	return self._category
end

function Category:makeCategoryLink(display)
	local cat = self:getCategory()
	display = display or cat
	return string.format('[[:Category:%s|%s]]', cat, display)
end

function Category:getCount()
	if not self._count then
		self._count = mw.site.stats.pagesInCategory(self:getCategory(), 'pages')
	end
	return self._count
end

function Category:getFormattedCount()
	return lang:formatNum(self:getCount())
end

function Category:exists()
	return mw.title.makeTitle(14, self:getCategory()).exists
end

-------------------------------------------------------------------------------
-- DatedCategory class
-- Inherits from Category
-------------------------------------------------------------------------------

local DatedCategory = {}
DatedCategory.__index = DatedCategory
setmetatable(DatedCategory, Category)

function DatedCategory.new(data)
	local self = setmetatable(Category.new(data), {__index = DatedCategory})
	self._date = data.date
	self._dateFormat = data.dateFormat or self:message('date-format')
	self._formattedDate = self:formatDate(self._date)
	self._category = self:message(
		'dated-category-format',
		data.undatedCategory,
		self._formattedDate,
		data.from or '',
		data.suffix or ''
	)
	self._category = self._category:match('^%s*(.-)%s*$') -- trim whitespace
	return self
end

function DatedCategory:formatDate(date)
	return lang:formatDate(self._dateFormat, date)
end

function DatedCategory:getDate()
	return self._date
end

function DatedCategory:getFormattedDate()
	return self._formattedDate
end

-------------------------------------------------------------------------------
-- ProgressBox class
-------------------------------------------------------------------------------

local ProgressBox = {}
ProgressBox.__index = ProgressBox
ProgressBox.message = message

function ProgressBox.new(args, cfg, title)
	local self = setmetatable({}, ProgressBox)

	-- Argument defaults
	args = args or {}
	self._cfg = cfg or mw.loadData(CONFIG_MODULE)
	self._title = title or mw.title.getCurrentTitle()

	-- Set data
	self._float = args.float or 'left'
	self._margin = args.float == 'none' and 'auto' or nil
	self._header = args[1]
	self._frame = mw.getCurrentFrame()

	-- Make the undated category object
	do
		local undatedCategory = args[2] or args[1]
		if not undatedCategory then
			error('no category specified', 3)
		end
		self._undatedCategoryObj = Category.new{
			cfg = self._cfg,
			category = undatedCategory,
		}
	end

	-- Make datedCategory objects
	self._datedCategories = {}
	do
		local cfg = self._cfg
		local undatedCategory = self._undatedCategoryObj:getCategory()
		local from = args.from or self:message('dated-category-format-from')
		local suffix = args.suffix
		local currentDate = lang:formatDate('Y-m')
		local date = self:findEarliestCategoryDate()
		local dateFormat = self:message('date-format')
		while date <= currentDate do
			local datedCategoryObj = DatedCategory.new{
				cfg = cfg,
				undatedCategory = undatedCategory,
				from = from,
				suffix = suffix,
				date = date,
				dateFormat = dateFormat,
			}
			if datedCategoryObj:getCount() > 0 then
				table.insert(self._datedCategories, datedCategoryObj)
			end
			date = ProgressBox.incrementDate(date)
		end
	end

	-- Make all-article category object
	do
		local allCategory
		if args[3] then
			allCategory = args[3]
		else
			allCategory = self:message(
				'all-articles-category-format',
				self._undatedCategoryObj:getCategory()
			)
			allCategory = self._frame:preprocess(allCategory)
		end
		self._allCategoryObj = Category.new{
			cfg = self._cfg,
			category = allCategory,
		}
	end

	return self
end

-- Increments a date in the format YYYY-MM
function ProgressBox.incrementDate(date)
	local year, month = date:match('^(%d%d%d%d)%-(%d%d)$')
	year = tonumber(year)
	month = tonumber(month)
	if not year or not month then
		error(string.format("error parsing date '%s'", tostring(date)), 2)
	end
	month = month + 1
	if month > 12 then
		month = 1
		year = year + 1
	end
	return string.format('%04d-%02d', year, month)
end

function ProgressBox:findEarliestCategoryDate()
	return self:message('start-date')
end

function ProgressBox:isCollapsed()
	return self._title.namespace ~= 10 -- is not in template namespace
end

function ProgressBox:makeTotalLabel()
	local display = self:message('all-articles-heading')
	if self._allCategoryObj:exists() then
		return self._allCategoryObj:makeCategoryLink(display)
	else
		return display
	end
end

function ProgressBox:getTotalCount()
	local count = 0
	for i, obj in ipairs(self._datedCategories) do
		count = count + obj:getCount()
	end
	count = count + self._undatedCategoryObj:getCount()
	return count
end

function ProgressBox:getFormattedTotalCount()
	return lang:formatNum(self:getTotalCount())
end

function ProgressBox:__tostring()
	data = data or {}
	local root = mw.html.create('table')
	
	-- Base classes and styles
	root
		:addClass('infobox')
		:css('float', self._float)
		:css('clear', self._float)
		:css('margin', self._margin)
		:css('width', '22em')

	-- Header row
	root:tag('tr'):tag('th')
		:attr('colspan', 2)
		:addClass('navbox-title')
		:css('padding', '0.2em')
		:css('font-size', '125%')
		:wikitext(self._header)

	-- Refresh row
	root:tag('tr'):tag('td')
		:attr('colspan', 2)
		:css('text-align', 'center')
		:wikitext(makePurgeLink{self:message('purge-link-display')})

	-- Subtotals
	local subtotalTable = root
		:tag('tr')
			:tag('td')
				:attr('colspan', 2)
				:css('padding', 0)
				:tag('table')
					:addClass('collapsible')
					:addClass(self:isCollapsed() and 'collapsed' or nil)
					:css('width', '100%')
					:css('margin', 0)
	subtotalTable
		:tag('tr')
			:tag('th')
				:attr('colspan', 2)
				:wikitext(self:message('subtotal-heading'))
	for i, datedCategoryObj in ipairs(self._datedCategories) do
		subtotalTable
			:tag('tr')
				:tag('td')
					:wikitext(datedCategoryObj:makeCategoryLink(
						datedCategoryObj:getFormattedDate()
					))
					:done()
				:tag('td')
					:css('text-align', 'right')
					:wikitext(datedCategoryObj:getFormattedCount())
	end

	-- Undated articles
	subtotalTable
		:tag('tr')
			:tag('td')
				:wikitext(self._undatedCategoryObj:makeCategoryLink(
					self:message('undated-category-link-display')
				))
				:done()
			:tag('td')
				:css('text-align', 'right')
				:wikitext(self._undatedCategoryObj:getFormattedCount())

	-- Total
	root
		:tag('tr')
			:css('font-size', '110%')
			:tag('td')
				:wikitext(string.format("'''%s'''", self:makeTotalLabel()))
				:done()
			:tag('td')
				:css('text-align', 'right')
				:wikitext(string.format(
					"'''%s'''",
					self:getFormattedTotalCount()
				))

	return tostring(root)
end

-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------

local p = {}

function p._exportClasses()
	return {
		Category = Category,
		DatedCategory = DatedCategory,
		ProgressBox = ProgressBox,
	}
end

function p._main(args, cfg, title)
	return tostring(ProgressBox.new(args, cfg, title))
end

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Progress box'
	})
	return p._main(args)
end

return p