Jump to content

Module:Progress box: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
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
use a setter for category names to better preserve encapsulation, and use a better fallback for DatedCategory's "for" argument
Line 34: Line 34:
local self = setmetatable({}, Category)
local self = setmetatable({}, Category)
self._cfg = data.cfg
self._cfg = data.cfg
self._category = data.category
self:setCategory(data.category)
return self
return self
end

function Category:setCategory(category)
self._category = category
end
end


Line 77: Line 81:
self._dateFormat = data.dateFormat or self:message('date-format')
self._dateFormat = data.dateFormat or self:message('date-format')
self._formattedDate = self:formatDate(self._date)
self._formattedDate = self:formatDate(self._date)
do
self._category = self:message(
'dated-category-format',
local category = self:message(
'dated-category-format',
data.undatedCategory,
data.undatedCategory,
self._formattedDate,
self._formattedDate,
data.from or '',
data.suffix or ''
data.from or self:message('dated-category-format-from'),
data.suffix or ''
)
)
self._category = self._category:match('^%s*(.-)%s*$') -- trim whitespace
category = category:match('^%s*(.-)%s*$') -- trim whitespace
self:setCategory(category)
end
return self
return self
end
end

Revision as of 02:06, 17 May 2015

-- 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:setCategory(data.category)
	return self
end

function Category:setCategory(category)
	self._category = category
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)
	do
		local category = self:message(
			'dated-category-format',
			data.undatedCategory,
			self._formattedDate,
			data.from or self:message('dated-category-format-from'),
			data.suffix or ''
		)
		category = category:match('^%s*(.-)%s*$') -- trim whitespace
		self:setCategory(category)
	end
	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