Documentation[créer] [purger]
--[[
	Petites fonctions utiles.
	]]
local p = {}
--[[
	Arguments passés au #invoke.
	
	Un argument sera cherché en priorité dans cette table. S'il n'est pas
	trouvé, on le cherchera dans les arguments passés au modèle.
	]]
local args0		= {}
--[[
	Arguments passés au modèle.
	
	Un argument sera cherché dans cette table, s'il n'est pas passé au #invoke.
	]]
local args1		= {}
local Romains	= require('Module:Chiffres romains')
local Date		= require('Module:Date')

--[[
	Traitement d'une erreur.
	Argument:	message d'erreur en texte simple
	Résultat:	la fonction ne retourne pas à l'appelant, mais après le pcall()
	
	Lorsqu'une erreur est détectée, par exemple un argument invalide dans
	l'appel du modèle, cette fonction est appelée. Elle termine tous les appels
	de fonction, jusqu'à atteindre un appel à la fonction pcall(), qui retourne
	false et le message (s'il n'y avait pas eu d'erreur dans le traitement,
	pcall retournait true et les valeurs retournées par la fonction qu'on lui
	passe en premier argument).
	
	Cette méthode a l'intérêt de rendre le code nettement plus lisible en ne
	traînant pas partout des codes et des messages d'erreur, et accessoirement
	de gagner un brin de performance.
	
	Dans ce module, la fonction erreur() peut être appelée à peu près n'importe
	quand.
	]]
local function erreur		(msg)				
	if string.sub(msg, 1, 5) ~= '<span' then
		-- Romains.conversion() inclut déjà le <span>
		msg = '<span class="error">' .. msg .. '</span>'
	end
	error(msg, 0)
end

--[[
	Extraction d'un argument.
	Arguments:	nom		nom de l'argument
				defaut	valeur par défaut de l'argument
	Résultat:	la valeur de l'argument
	
	Comme mediawiki trimme les valeurs des arguments, un argument peut être
	inclus entre "double apostrophes" qui sont éliminées ici. Cela permet de
	spécifier un argument qui commence ou se termine par une espace. Une autre
	solution pour les textes qui apparaissent comme liens, est d'utiliser un
	souligné _ qui n'est pas éliminé par mediawiki.
	]]
local function arg			(nom, defaut)		
	local x = nil
	if not x then x = args0[nom] end
	if not x then x = args1[nom] end
	if not x then x = defaut     end

	if not x							then return x
	elseif string.sub(x, 1, 1) ~= '"'	then return x
	elseif string.sub(x, -1) ~= '"'		then return x
	else									 return string.sub(x, 2, -2)
	end
end

--[[
	Conversion de chiffres romains en nombre
	Arguments:	chaine	avec des chiffres romains
	Résultat:	la chaine convertie en nombre
	]]
local function nombre		(chaine)			
	local test, msg, resultat = Romains.conversion(chaine)
	if not test then erreur(msg) end
	return resultat
end

--[[
	Détection du type et de la valeur de la date.
	Arguments:	chaine avec une date éventuelle
	Résultat:	type, valeur
		type:	0 = pas de correspondance,
				1 = millénaire avant J-C,
				2 = millénaire après J-C,
				3 = siècle avant J-C,
				4 = siècle après J-C,
				5 = décennie avant J-C,
				6 = décennie après J-C,
				7 = année avant J-C,
				8 = année après J-C
		valeur:	millénaire, siècle, décennie ou année exacte
	
	Les patterns utilisés dans cette fonction ne contiennent pas de classes de
	caractères unicode, il n'est donc pas nécessaire d'utiliser mw.ustring.
	
	Il n'y a pas lieu de s'effrayer du grand nombre de patterns testés: les
	tests de pattern sont très performants en Lua tant qu'on n'utilise pas des
	patterns unicode, et ce module sera probablement appelé une seule fois dans
	un article Wikipédia donné.
	]]
local function detection	(chaine)			
	if not chaine or chaine == "" then return 0, 0 end
	local capture
	-- Ier millénaire av. J.-C.
	capture = string.match(chaine, "([IVXLCDM]+)er? millénaire av[ant]*[ %.%-]-J[ %.%-]-C[%.]?")
	if capture then return 1, nombre(capture) end
	capture = string.match(chaine, "([IVXLCDM]+)er? millénaire av[ant]*[ %.%-]-Jésus[ %.%-]-Christ[%.]?")
	if capture then return 1, nombre(capture) end
	capture = string.match(chaine, "%-([IVXLCDM]+)er? millénaire")
	if capture then return 1, nombre(capture) end
	-- IIe millénaire
	capture = string.match(chaine, "([IVXLCDM]+)er? millénaire")
	if capture then return 2, nombre(capture) end
	-- Xe siècle av. J.-C.
	capture = string.match(chaine, "([IVXLCDM]+)er? siècle av[ant]*[ %.%-]-J[ %.%-]-C[%.]?")
	if capture then return 3, nombre(capture) end
	capture = string.match(chaine, "([IVXLCDM]+)er? siècle av[ant]*[ %.%-]-Jésus[ %.%-]-Christ[%.]?")
	if capture then return 3, nombre(capture) end
	capture = string.match(chaine, "%-([IVXLCDM]+)er? siècle")
	if capture then return 3, nombre(capture) end
	-- XIVe siècle
	capture = string.match(chaine, "([IVXLCDM]+)er? siècle")
	if capture then return 4, nombre(capture) end
	-- Années 970 av. J.-C.
	capture = string.match(chaine, "Années (%d+)0 av[ant]*[ %.%-]-J[ %.%-]-C[%.]?")
	if capture then return 5, tonumber(capture) end
	capture = string.match(chaine, "Années (%d+)0 av[ant]*[ %.%-]-Jésus[ %.%-]-Christ[%.]?")
	if capture then return 5, tonumber(capture) end
	capture = string.match(chaine, "Années %-(%d+)0")
	if capture then return 5, tonumber(capture) end
	capture = string.match(chaine, "Années 0 av[ant]*[ %.%-]-J[ %.%-]-C[%.]?")
	if capture then return 5, 0 end
	capture = string.match(chaine, "Années 0 av[ant]*[ %.%-]-Jésus[ %.%-]-Christ[%.]?")
	if capture then return 5, 0 end
	capture = string.match(chaine, "Années %-0")
	if capture then return 5, 0 end
	-- Années 1360
	capture = string.match(chaine, "Années (%d+)0")
	if capture then return 6, tonumber(capture) end
	capture = string.match(chaine, "Années 0")
	if capture then return 6, 0 end
	-- 970 av. J.-C.
	capture = string.match(chaine, "(%d+) av[ant]*[ %.%-]-J[ %.%-]-C[%.]?")
	if capture then
		if tonumber(capture) == 0 then erreur("L'année 0 n'existe pas") end
		return 7, tonumber(capture)
	end
	capture = string.match(chaine, "(%d+) av[ant]*[ %.%-]-Jésus[ %.%-]-Christ[%.]?")
	if capture then
		if tonumber(capture) == 0 then erreur("L'année 0 n'existe pas") end
		return 7, tonumber(capture)
	end
	-- Pour éviter de détecter par exemple "test-1" dans le titre de la page,
	-- on utilise un pattern plus précis, avec caractères unicode.
	capture = mw.ustring.match(" " .. chaine .. " ", "[^%w%-_]%-(%d+)[^%w%-_]")
	if capture then
		if tonumber(capture) == 0 then erreur("L'année 0 n'existe pas") end
		return 7, tonumber(capture)
	end
	-- 1363
	-- Pour éviter de détecter par exemple "test1" dans le titre de la page,
	-- on utilise un pattern plus précis, avec caractères unicode.
	capture = mw.ustring.match(" " .. chaine .. " ", "[^%w%-_](%d+)[^%w%-_]")
	if capture then
		if tonumber(capture) == 0 then erreur("L'année 0 n'existe pas") end
		return 8, tonumber(capture)
	end
	-- on abandonne	
	return 0, 0
end

--[[
	Extraction du type et de la valeur de la date.
	Arguments:	aucun
	Résultat:	type, valeur
	
	La date est prise de l'argument date, ou sinon de l'argument 1, ou sinon
	du titre de la page, ou sinon de l'année courante.
	]]
local function date			()					
	local typ, val = 0, 0
	typ, val = detection(arg("date",  nil))
	if typ > 0 then return typ, val end
	typ, val = detection(arg("année", nil))
	if typ > 0 then return typ, val end
	typ, val = detection(arg("1",     nil))
	if typ > 0 then return typ, val end
	typ, val = detection(mw.title.getCurrentTitle().text)
	if typ > 0 then return typ, val end
	typ, val = 8, tonumber(os.date("%Y"))
	if typ > 0 then return typ, val end
	erreur("Impossible de déterminer la date")
end

--[[
	Retourne true si l'année est bissextile.
	Argument:	numéro de l'année
	Résultat:	true si l'année est bissextile
	]]
local function bissextile	(annee)				
	if annee > 1582 then return Date.isLeapYear(annee) end
	if annee > 0    then return annee % 4 == 0         end
	erreur("Année non supportée")
end

--[[
	Retourne "bissextile" ou "commune" selon que l'année est bissextile ou non.
	Argument:	numéro de l'année
	Résultat:	"bissextile" ou "commune"
	]]
local function bissextile2	(annee)				
	if bissextile(annee)
	then return "bissextile"
	else return "commune"
	end
end

--[[
	Retourne le jour julien du 1er janvier de l'année indiquée.
	Argument:	numéro de l'année
	Résultat:	jour julien du 1er janvier de l'année
	
	Voir https://fr.wikipedia.org/wiki/Jour_julien.
	
	Le module Date utilise le jour julien standard, commençant à 12:00.
	Pour faciliter les calculs, nous utilisons le Jour julien à 0 h.
	]]
local function jj			(annee)				
	if annee > 1582 then return Date.julianDay      (annee, 1, 1, 12, 0, 0) end
	if annee > 0    then return Date.julianDayJulian(annee, 1, 1, 12, 0, 0) end
	erreur("Année non supportée")
end

--[[
	Retourne le numéro du jour de la semaine au 1er janvier de l'année indiquée.
	Argument:	numéro de l'année
	Résultat:	numéro du jour du 1er janvier, 1 = lundi, 7 = dimanche
	]]
local function jour			(annee)				
	if annee > 1582 then return jj(annee) % 7 + 1 end
	if annee > 0    then return jj(annee) % 7 + 1 end
	erreur("Année non supportée")
end

--[[
	Retourne le nom du jour du 1er janvier de l'année indiquée.
	Argument:	numéro de l'année
	Résultat:	nom du jour du 1er janvier, "lundi" à "dimanche"
	]]
local function jour2		(annee)				
	local jours = {"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"}
	return jours[jour(annee)]
end

--[[
	Génération d'une phrase d'introduction pour une année AD donnée.
	]]
local function riAD			(annee)				
	return "Cet article présente les faits marquants de l'année '''[[".. annee .. "]]''' concernant le jeu vidéo."
--	return "Cette page concerne des événements d'actualité qui se sont produits durant l'année '''[[".. annee .. "]]''' du [[calendrier grégorien]] aux '''[[États-Unis]]'''."
--	return "Cet article présente la liste des évènements de la '''[[The Walt Disney Company|Walt Disney Company]]''' ayant eu lieu en [[".. annee .. "]]."
--	local txt = ""
--	local b = bissextile2(annee)
--	local j = jour2		 (annee)
--	txt = txt
--		.. "L'année '''" .. annee .. "''' est une "
--	if annee % 100 == 0 then
--	txt = txt
--		.. "[[année séculaire]] et une "
--	end
--	txt = txt
--		.. "[[année " .. b .. "]] qui "
--		.. "[[année " .. b .. " commençant un " .. j .. "|commence un " .. j .. "]]."
--	return txt
end

--[[
	Génération d'une phrase d'introduction pour une année BC donnée.
	]]
local function riBC			(annee)				
	local txt = ""
	txt = txt
		.. "''Cette page concerne l'année '''"
		.. annee
		.. " av. J.-C.''' du [[calendrier julien"
	if annee > 45 then txt = txt .. " proleptique" end
	txt = txt
		.. "]].''"
	return txt
end

--[[
	Génération d'une phrase d'introduction pour une décennie AD donnée.
	]]
local function ridAD		(annee)				
	local txt = ""
	txt = txt
		.. "Les '''années " .. annee .. "0''' couvrent la période de " .. annee .. "0 à " .. annee .. "9."
	return txt
end

--[[
	Génération d'une phrase d'introduction pour une décennie BC donnée.
	]]
local function ridBC		(annee)				
	local txt = ""
	txt = txt
		.. "Les '''années " .. annee .. "0 av. J.-C.''' couvrent les années de " .. annee .. "9 av. J.-C. à " .. annee .. "0 av. J.-C."
	return txt
end

--[[
	Génération d'une phrase d'introduction.
	]]
local function p_ri			(frame)				
	args0 = frame.args
	args1 = frame:getParent().args
	local typ, val = date()
	if typ == 8 then return riAD(val) end
	if typ == 7 then return riBC(val) end
	if typ == 6 then return ridAD(val) end
	if typ == 5 then return ridBC(val) end
	erreur("Année non supportée")
end

--[[
	Génération d'une phrase d'introduction.
	]]
function p.ri				(frame)				
	local ok, texte = pcall(p_ri, frame)
	return texte
end

return p