Modulo:Progetti interessati

Modulo che implementa il template {{Progetti interessati}}.

Ha due sottopagine di configurazione:


--[[
* Modulo che implementa il template Progetti interessati.
]]--

require('Modulo:No globals')

local getArgs = require('Modulo:Arguments').getArgs
local cfg = mw.loadData('Modulo:Progetti interessati/Configurazione')
local catPrefix = mw.loadData('Modulo:Progetti interessati/Categorie')
-- Nomi e valori consentiti dei parametri di monitoraggio/importanza richiesti da più funzioni
local gradeParams = { 'accuratezza', 'scrittura', 'fonti', 'immagini' }
local validGrades = { a = true, b = true, c = true, d = true, e = true }
local validImportances = { massima = true, alta = true }

-- =============================================================================
--                            Funzioni di utilità
-- =============================================================================

-- Wrapper di mw.title.exists, verifica sia che name sia valido, sia che esista.
--
-- @param {string} name
-- @return {boolean}
local function titleExists(name)
	local title = mw.title.new(name)
	return title and title.exists
end

-- Restituisce il valore della valutazione ("a", "b", "c" o "d") più bassa inserita
-- nei quattro parametri "accuratezza", "scrittura", "fonti" e "immagini".
--
-- @param {table} args
-- @return {string}
local function lowestGrade(args)
	local t = { args.accuratezza, args.scrittura, args.fonti, args.immagini }
	table.sort(t)
	return t[4]
end

-- Restituisce l'icona da visualizzare per il progetto specificato,
-- utilizzando il template Icona argomento e verificando se è un sottoprogetto.
--
-- @param {string} name
-- @return {string}
local function getIcon(name)
	local icon = mw.getCurrentFrame():expandTemplate {
		title = 'Icona argomento',
		args = { name }
	}
	if icon == '' then
		local subpage = mw.title.new('Progetto:' .. name).subpageText
		if subpage ~= name then
			icon = mw.getCurrentFrame():expandTemplate {
				title = 'Icona argomento',
				args = { subpage }
			}
		end
	end
	icon = icon == '' and 'Crystal Clear app ksirtet.png' or icon

	return string.format('[[File:%s|20x20px]]', icon)
end

-- Restituisce il valore del parametro importanzaN relativo al progettoN specificato,
-- o nil se non usato dall'utente.
--
-- @param {string} projParam
-- @param {table} args
-- @return {string}
local function getImportance(projParam, args)
	local num = mw.ustring.match(projParam, '^progetto(%d)$')
	local param = num and ('importanza' .. num) or
				  (projParam == 'progetto' and 'importanza' or nil)
	return args[param]
end

-- Controlla gli argomenti importanzaN e quelli per la valutazione del monitoraggio.
-- Per ogni parametro progettoN specificato crea una table con i dati del progetto.
--
-- @param {table} args
-- @return {table} gli argomenti passati alla funzione
-- @return {table} una sequence di table che descrivono i singoli progetti
-- @return {table} eventuali categorie di errore
-- @return {string} un eventuale messaggio di errore per i progetti non esistenti
local function parseArgs(args)
	local errcat = {}
	local projects = {}
	local nonexistingProjects = {}
	local errmsg
	local importanceParams = { 'importanza' }
	local projectParams = { 'progetto' }

	-- controllo importanza
	for i = 2, cfg.max_progetti do
		table.insert(importanceParams, 'importanza' .. i)
	end
	for _, param in ipairs(importanceParams) do
		if args[param] and not validImportances[args[param]] then
			args[param] = nil
			table.insert(errcat, '[[Categoria:Pagine con template Progetti interessati con importanza invalida]]')
		end
	end

	-- controllo valutazioni
	for _, param in ipairs(gradeParams) do
		if args[param] and not validGrades[args[param]] then
			if args[param] ~= 'nc' then
				table.insert(errcat, '[[Categoria:Pagine con template Progetti interessati con valutazione invalida]]')
			end
			args[param] = nil
		end
	end
	args.data = args.data and args.data:lower() or nil

	-- popola la tabella projects
	if args.progetto then
		for i = 2, cfg.max_progetti do
			table.insert(projectParams, 'progetto' .. i)
		end
		for _, param in ipairs(projectParams) do
			if args[param] then
				local name = cfg.alias[args[param] and args[param]:lower()] or
							 mw.language.getContentLanguage():ucfirst(args[param])
				if titleExists('Progetto:' .. name) then
					table.insert(projects, {
						name = name,
						subpage = mw.title.new('Progetto:' .. name).subpageText,
						monitoraggio = titleExists(string.format('Progetto:%s/Monitoraggio voci', name)),
						importance = getImportance(param, args),
						icon = getIcon(name),
						catPrefix = catPrefix[name:lower()]
					})
				else
					table.insert(errcat, '[[Categoria:Pagine con template Progetti interessati con progetto non esistente]]')
					table.insert(nonexistingProjects, name)
				end
			end
		end
	else
		-- almeno un progetto deve essere specificato
		table.insert(errcat, '[[Categoria:Errori di compilazione del template Progetti interessati]]')
	end

	-- messaggio d'errore con progetti non esistenti
	if #nonexistingProjects > 0 then
		local descr = #nonexistingProjects > 1 and 'non esistono i progetti' or 'non esiste il progetto'
		errmsg = string.format('<span class="error" style="margin:5px 10%%">Errore: %s %s</span>',
							   descr, mw.text.listToText(nonexistingProjects))
	end

	return args, projects, errcat, errmsg
end

-- Formatta i valori assegnati ai parametri "accuratezza", "scrittura", "fonti" e "immagini".
--
-- @param {string} value
-- @return {table}
local function getLabelGrade(value)
	local codeStyle = {
		['font-weight'] = 'bold',
		['font-size'] = '155%',
		['padding-left'] = '.2em',
		['padding-right'] = '.2em',
		border = '1px solid lightsteelblue',
		background = cfg.colors[value] or 'white'
	}
	return mw.html.create('code')
		:css(codeStyle)
		:wikitext(value and value:upper() or '<small>nc</small>')
end

-- Restituisce true se almeno un progetto tra quelli specificati
-- ha attivato il monitoraggio.
--
-- @param {table} projects
-- @return {boolean}
local function hasMonitoraggio(projects)
	local ret = false
	for _, project in ipairs(projects) do
		if project.monitoraggio then
			ret = true
			break
		end
	end
	return ret
end

-- =============================================================================
--                       Funzioni base del Monitoraggio
-- =============================================================================

-- Restituisce il risultato del monitoraggio in base alle valutazioni inserite
-- nei quattro parametri "accuratezza", "scrittura", "fonti" e "immagini".
-- Il valore restituito, dal basso verso l'alto, può essere:
-- "0.2", "0.3", "0.4", "0.5", "IMMAGINI", "F", "W", "BOZZA", "1", "2", "3", "4".
--
-- @param {table} args
-- @return {string}
local function getLivello(args)
	local ret

	-- è presente accuratezza
	if args.accuratezza then
		if not args.scrittura then
			ret = '0.4'
		elseif not args.fonti then
			ret = '0.3'
		elseif not args.immagini then
			ret = '0.2'
		else
			-- sono presenti tutti e quattro i parametri di valutazione
			if args.accuratezza == 'e' then
				ret = 'BOZZA'
			elseif args.scrittura == 'e' then
				ret = 'W'
			elseif args.fonti == 'e' then
				ret = 'F'
			elseif args.immagini == 'e' then
				ret = 'IMMAGINI'
			else
				local values = { a = 4, b = 3, c = 2, d = 1 }
				ret = tostring(values[lowestGrade(args)])
			end
		end
	-- manca accuratezza
	else
		if args.scrittura or args.fonti or args.immagini then
			ret = '0.5'
		else
			ret = nil
		end
	end

	return ret
end

-- Restituisce le categorie di servizio in base al livello del monitoraggio ottenuto,
-- agli eventuali parametri "importanza" e alla presenza di errori utente di compilazione.
--
-- @param {string} livello
-- @param {table} args
-- @param {table} projects
-- @return {table}
local function getCategories(livello, args, projects)
	local ret = {}
	local cat

	local suffix = livello and cfg.livello[livello].cat or 'non compilate'

	-- per la "Situazione generale" di [[Progetto:Qualità/Monitoraggio voci/Tabella]]
	cat = string.format('[[Categoria:Voci monitorate - %s]]', suffix)
	table.insert(ret, cat)

	for _, project in ipairs(projects) do
		if project.monitoraggio then
			cat = string.format('[[Categoria:Voci monitorate Progetto %s]]', project.name)
			table.insert(ret, cat)
		end
	end

	for _, gradeParam in ipairs(gradeParams) do
		-- per la "Situazione monitoraggio per parametro" di [[Progetto:Qualità/Monitoraggio voci/Tabella]]
		cat = string.format('[[Categoria:Voci monitorate - %s %s]]',
							gradeParam, (args[gradeParam] or 'nc'):upper())
		table.insert(ret, cat)

		-- categorie per il [[template:Tabella monitoraggio]]
		for _, project in ipairs(projects) do
			if project.monitoraggio then
				cat = string.format('Categoria:Progetto:%s/Tabella monitoraggio automatico - %s %s',
									project.name, gradeParam, args[gradeParam] or 'nc')
				if titleExists(cat) then
					table.insert(ret, '[[' .. cat .. ']]')
				end
			end
		end
	end

	-- categorie per il [[template:Tabella monitoraggio]]
	for _, project in ipairs(projects) do
		if project.monitoraggio then
			cat = string.format('Categoria:Voci monitorate Progetto %s - %s',
								project.name, suffix)
			if titleExists(cat) then
				table.insert(ret, '[[' .. cat .. ']]')
			end
		end
		if project.importance and project.catPrefix then
			cat = string.format('Categoria:%s - importanza %s', project.catPrefix, project.importance)
			table.insert(ret, '[[' .. cat .. ']]')
		end
	end

	-- per la "Situazione monitoraggio per mese" di [[Progetto:Qualità/Monitoraggio voci/Tabella]]
	if livello then
		if args.data then
			cat = string.format('Categoria:Voci monitorate - %s', args.data)
			if titleExists(cat) then
				table.insert(ret, '[[' .. cat .. ']]')
			else
				table.insert(ret, '[[Categoria:Errori di compilazione del template Progetti interessati]]')
			end
		else
			table.insert(ret, '[[Categoria:Voci monitorate - non datate]]')
		end
	elseif (args.data or args.utente) and not args.note then
		table.insert(ret, '[[Categoria:Pagine con template Progetti interessati con utente o data e senza monitoraggio]]')
	end

	return ret
end

-- =============================================================================
--                            Classe ProjectsTable
-- =============================================================================

-- La classe ProjectsTable rappresenta la prima delle due tabelle HTML
-- prodotte dal modulo, quella per elencare i wikiprogetti.

local ProjectsTable = {}

-- Costruttore della classe ProjectsTable.
--
-- @param {table} projects
-- @return {table} un nuovo oggetto ProjectsTable
function ProjectsTable:new(projects)
	local self = {}
	setmetatable(self, { __index = ProjectsTable })
	self.projects = projects
	return self
end

-- Restituisce la tabella HTML con una riga di introduzione e una per la sottotabella progetti.
--
-- @return {string}
function ProjectsTable:getHTML()
	local tableStyle = {
		margin = '5px 10%',
		width = '80%',
		border = '1px solid #a7d7f9',
		['background-color'] = cfg.bgcolor,
	}
	local text = #self.projects > 1 and cfg.progetti.text2 or cfg.progetti.text
	local tableNode = mw.html.create('table'):css(tableStyle)
	tableNode
		:tag('tr')
			:tag('td')
				:css('width', '52px')
				:css('text-align', 'center')
				:wikitext(cfg.progetti.icon)
				:done()
			:tag('td')
				:wikitext(mw.getCurrentFrame():preprocess(text))
				:done()
	tableNode
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:node(self:_getNodeProjects())
				:done()

	return tostring(tableNode)
end

-- Restituisce la sottotabella HTML con un progetto per riga.
--
-- @return {table}
function ProjectsTable:_getNodeProjects()
	local tableStyle = {
		width = '100%',
		['border-collapse'] = 'collapse'
	}
	local tableNode = mw.html.create('table'):css(tableStyle)

	-- progetti
	for _, project in ipairs(self.projects) do
		tableNode:tag('tr')
			:css('background-color', 'white')
			:css('border', 'thin solid #D8D8D8')
			:tag('td')
				:css('width', '28px')
				:css('text-align', 'center')
				:wikitext(project.icon)
				:done()
			:tag('td')
				:css('width', '1px')
				:css('white-space', 'nowrap')
				:wikitext(string.format("'''[[Progetto:%s|%s]]'''", project.name, project.subpage))
				:done()
			:tag('td')
				:addClass('plainlinks')
				:css('padding-left', '5px')
				:wikitext(self:_getWlinkProject(project))
				:done()
			:tag('td')
				:css('width', '11em')
				:wikitext((project.importance and project.catPrefix) and
						  string.format('[[:Categoria:%s - importanza %s|importanza %s]]',
						  project.catPrefix, project.importance, project.importance) or '')
				:done()
	end

	return tableNode
end

-- Restituisce il link al progetto, al bar di progetto e all'eventuale monitoraggio delle voci.
--
-- @param {table} project
-- @return {string}
function ProjectsTable:_getWlinkProject(project)
	local links = {}
	local fmtIcon = '[[File:Rpb dialog icon.svg|23x15px|bar di progetto|link=Discussioni progetto:%s]]'
	local fmtIcon2 = '[[File:VisualEditor - Icon - Search-big.svg|20x20px|monitoraggio voci|link=Progetto:%s/Monitoraggio voci]]'

	table.insert(links, string.format(fmtIcon, project.name))
	table.insert(links, string.format('[[Discussioni progetto:%s|bar di progetto]]', project.name))
	-- link monitoraggio opzionale
	if project.monitoraggio then
		table.insert(links, string.format(fmtIcon2, project.name))
		table.insert(links, string.format('[[Progetto:%s/Monitoraggio voci|monitoraggio voci]]', project.name))
	end

	return table.concat(links, ' ')
end

-- =============================================================================
--                            Classe MonitoraggioTable
-- =============================================================================

-- La classe ProjectsTable rappresenta la seconda delle due tabelle HTML
-- prodotte dal modulo, quella per visualizzare la valutazione del monitoraggio.

local MonitoraggioTable = {}

-- Costruttore della classe MonitoraggioTable.
--
-- @param {string} livello
-- @param {table} args
-- @return {table} un nuovo oggetto MonitoraggioTable
function MonitoraggioTable:new(livello, args)
	local self = {}
	setmetatable(self, { __index = MonitoraggioTable })
	self.livello = livello
	self.args = args
	return self
end

-- Restituisce la tabella HTML con una riga di introduzione e una per la sottotabella
-- collassabile per "accuratezza", "scrittura", "fonti" e "immagini".
-- Termina con due righe opzionali in base alla presenza delle note e della data.
--
-- @return {string}
function MonitoraggioTable:getHTML()
	local tableStyle = {
		margin = '5px 10% 5px 10%',
		width = '80%',
		border = '1px solid #a7d7f9',
		['background-color'] = cfg.bgcolor
	}
	local tableNode = mw.html.create('table')
	tableNode
		:addClass('mw-collapsible mw-collapsed')
		:css(tableStyle)

	-- livello
	tableNode
		:node(self:_getNodeLivello())
	-- valutazioni
	tableNode
		:tag('tr')
			:tag('td')
				:attr('colspan', '3')
				:css('background-color', 'none')
				:css('font-size', '95%')
				:tag('table')
					:css('width', '100%')
					:css('border-collapse', 'collapse')
					:node(self:_getNodeGrade('accuratezza'))
					:node(self:_getNodeGrade('scrittura'))
					:node(self:_getNodeGrade('fonti'))
					:node(self:_getNodeGrade('immagini'))
	-- note
	if self.args.note then
		tableNode
			:tag('tr')
				:tag('td')
					:css('padding-left', '0.2em')
					:attr('colspan', '3')
					:wikitext(string.format("'''Note:''' %s%s",
							  self.args.note:match('^[#*]') and '\n' or '',
							  self.args.note))
	end
	-- data
	if self.args.data then
		local data = self:_getTextData()
		tableNode
			:tag('tr')
				:tag('td')
					:css('text-align', 'right')
					:css('font-size', '80%')
					:css('line-height', '1')
					:attr('colspan', '3')
					:wikitext(string.format('Monitoraggio effettuato %s%s',
							  data:match('^[ao]') and 'nell\'' or 'nel ', data))
	end

	return tostring(tableNode)
end

-- Restituisce la prima riga della tabella HTML, contenente il risultato del monitoraggio
-- oppure l'indicazione che il monitoraggio non è ancora stato effettuato.
--
-- @return {table}
function MonitoraggioTable:_getNodeLivello()
	local text, icon
	if self.livello then
		if self.livello:sub(1, 1) == '0' then
			icon, text = cfg.monitoraggio.icon_pc, cfg.monitoraggio.text_pc
		else
			icon, text = cfg.monitoraggio.icon, cfg.monitoraggio.text
			text = string.format('%s<br />Ha ottenuto una valutazione di livello %s <small>(%s)</small>.',
								 text, self:_getWlinkLivello(), self:_getTextData())
		end
	else
		icon, text = cfg.monitoraggio.icon_nc, cfg.monitoraggio.text_nc
	end
	return mw.html.create('tr')
		:tag('td')
			:css('width', '52px')
			:css('text-align', 'center')
			:wikitext(icon)
			:done()
		:tag('td')
			:wikitext(mw.getCurrentFrame():preprocess(text))
			:done()
end

-- Restituisce il wikilink del risultato del monitoraggio, formattato opportunamente.
--
-- @return {string}
function MonitoraggioTable:_getWlinkLivello()
	local codeStyle = {
		['font-weight'] = 'bold',
		['padding-left'] = '.4em',
		['padding-right'] = '.4em',
		border = '1px solid lightsteelblue',
		background = cfg.livello[self.livello].color,
		color  = 'blue'
	}
	local text = string.format('[[:Categoria:Voci monitorate - %s|%s]]',
							   cfg.livello[self.livello].cat, cfg.livello[self.livello].label)
	local codeNode = mw.html.create('code')
	codeNode:css(codeStyle)
	codeNode:wikitext(text)
	return tostring(codeNode)
end

-- Restituisce la data del monitoraggio se esiste la relativa categoria
-- di monitoraggio per mese oppure un messaggio di errore.
--
-- @return {string}
function MonitoraggioTable:_getTextData()
	local ret
	if self.args.data then
		local cat = string.format('Categoria:Voci monitorate - %s', self.args.data)
		if titleExists(cat) then
			ret = self.args.data
		end
	end
	return ret or "<span style=\"color:red;\">'''''mese e anno'''''</span>"
end

-- Restituisce la riga della tabella HTML per ciascuna delle quattro valutazioni di
-- "accuratezza", "scrittura", "fonti" e "immagini", formattata opportunamente.
--
-- @param {string} param
-- @return {table}
function MonitoraggioTable:_getNodeGrade(param)
	return mw.html.create('tr')
		:css('background-color', 'white')
		:css('border', 'thin solid #D8D8D8')
		:tag('td')
			:css('width', '52px')
			:css('text-align', 'center')
			:node(getLabelGrade(self.args[param]))
			:done()
		:tag('td')
			:wikitext(cfg[param][self.args[param] or 'nc'] .. ' ' .. cfg[param].help)
			:done()
end

-- =============================================================================
--                            Funzioni esportate
-- =============================================================================

local p = {}

-- Funzione per l'utilizzo da un altro modulo.
function p._livello(args)
	return getLivello(parseArgs(args))
end

-- Funzione per {{#invoke:Progetti interessati|livello}}.
function p.livello(frame)
	return p._livello(getArgs(frame))
end

-- Funzione per {{#invoke:Progetti interessati|categorie}}.
function p.categorie(frame)
	local args, projects = parseArgs(getArgs(frame))
	local livello = getLivello(args)
	local categories = getCategories(livello, args, projects)
	return args.debug and ( table.concat(categories, '<br />'):gsub('%[%[', '[[:') ) .. '<br />' or
		   table.concat(categories)
end

-- Funzione per il sottotemplate {{Progetti interessati/classe}}
-- per retrocompatibilità con template che lo usavano.
function p.classe(frame)
	local value = frame:getParent().args[1]
	return tostring(getLabelGrade(validGrades[value] and value or nil))
end

-- Funzione per il template {{Progetti interessati}}.
function p.main(frame)
	local args, projects, errcat, errmsg = parseArgs(getArgs(frame, { parentOnly = true }))
	if hasMonitoraggio(projects) then
		local livello = getLivello(args)
		local categories = mw.title.getCurrentTitle().namespace == 1 and
					   	(table.concat(getCategories(livello, args, projects)) .. table.concat(errcat)) or ''
		return (errmsg or '') ..
			   ProjectsTable:new(projects):getHTML() ..
			   MonitoraggioTable:new(livello, args):getHTML() ..
			   categories
	else
		return (errmsg or '') ..
			   ProjectsTable:new(projects):getHTML() ..
			   table.concat(errcat)
	end
end

return p