Utilisateur:Botomatik/mise à jour de la liste des langues au format JSON/code

Code python utilisant le framework Pywikibot (version core) pour mettre à jour MediaWiki:Gadget-translation editor.js/langues.json, qui est la liste utilisée par l’outil d’ajout de traductions. La liste des langues écrite en Lua est utilisée comme base pour la mise à jour. Le résultat de la mise à jour est écrit dans la page Utilisateur:Botomatik/mise à jour de la liste des langues au format JSON.

Actuellement, ce code est exécuté chaque mois.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import codecs
import re
import json
import datetime
import pywikibot

test = True # Pour tester le script (ne sauvegarde rien)
site = pywikibot.Site('fr', 'wiktionary')

page_lua = u'Module:langues/data'
page_sortie = u'Utilisateur:Botomatik/mise à jour de la liste des langues au format JSON'
page_json = u'MediaWiki:Gadget-translation editor.js/langues.json'

def sortkey(str):
	key = str.lower()
	key = re.sub( u'[àáâãäå]', u'a', key )
	key = re.sub( u'[æ]', u'ae', key )
	key = re.sub( u'[çćċč]', u'c', key )
	key = re.sub( u'[ĉ]', u'cx', key )
	key = re.sub( u'[èéêë]', u'e', key )
	key = re.sub( u'[ĝ]', u'gx', key )
	key = re.sub( u'[ĥ]', u'hx', key )
	key = re.sub( u'[ìíîï]', u'i', key )
	key = re.sub( u'[ĵ]', u'jx', key )
	key = re.sub( u'[ñ]', u'n', key )
	key = re.sub( u'[òóôõö]', u'o', key )
	key = re.sub( u'[œ]', u'oe', key )
	key = re.sub( u'[òóôõö]', u'o', key )
	key = re.sub( u'[ŝ]', u'sx', key )
	key = re.sub( u'[ùúûü]', u'u', key )
	key = re.sub( u'[ŭ]', u'ux', key )
	key = re.sub( u'[ýÿ]', u'y', key )
	key = re.sub( u'[\'’)(]', u'', key )
	key = re.sub( u'[-\/]', u' ', key )
	return key

def tuple_to_json(my_list):
	'''
	Prend une liste de tuples et retourne un string au format json

	* Utilise la fonction sortkey()
	* Les strings de la liste ne doivent pas contenir de caractère "

	'''
	my_list = sorted(my_list, key=lambda elt: sortkey(elt[1]))
	res = u'{'
	for elt in my_list:
		res += u'"' + elt[0] + u'":"' + elt[1] + u'",'
	res = res[:len(res)-1] # on enlève la dernière virgule
	res += u'}'
	return res

def diff_listes_json(nouvelle_liste):
	'''Prend deux listes de langues au format JSON et les compare

	Le résultat de la comparaison est retourné sous forme de chaine
	'''
	liste_actuelle = pywikibot.Page(site, page_json).text
	liste_actuelle = json.loads(liste_actuelle)
	nouvelle_liste = json.loads(nouvelle_liste)
	langues_retirees = []
	langues_ajoutees = []
	langues_modifiees = []
	redirections_retirees = []
	redirections_ajoutees = []
	redirections_modifiees = []

	# Ajouts/retraits dans le champs "redirections" ?
	redirects_actu = None
	redirects_new = None
	if u'redirects' in liste_actuelle:
		redirects_actu = liste_actuelle[u'redirects']
	del liste_actuelle[u'redirects']
	if u'redirects' in nouvelle_liste:
		redirects_new = nouvelle_liste[u'redirects']
	del nouvelle_liste[u'redirects']

	for code in liste_actuelle:
		if code not in nouvelle_liste:
			langues_retirees.append(code + u' : ' + liste_actuelle[code])
		if code in nouvelle_liste and liste_actuelle[code] != nouvelle_liste[code]:
			langues_modifiees.append(code + u' : ' + liste_actuelle[code] + u' → ' + nouvelle_liste[code])
	for code in nouvelle_liste:
		if code not in liste_actuelle:
			langues_ajoutees.append(code + u' : ' + nouvelle_liste[code])

	for code in redirects_actu:
		if code not in redirects_new:
			redirections_retirees.append(code + u' : ' + redirects_actu[code])
		if code in redirects_new and redirects_actu[code] != redirects_new[code]:
			redirections_modifiees.append(code + u' : ' + redirects_actu[code] + u' → ' + redirects_new[code])
	for code in redirects_new:
		if code not in redirects_actu:
			redirections_ajoutees.append(code + u' : ' + redirects_new[code])

	if len(langues_ajoutees) == 0 and len(langues_retirees) == 0 and \
		len(redirections_ajoutees) == 0 and len(redirections_retirees) == 0 \
		and len(langues_modifiees) == 0 and len(redirections_modifiees):
		return u''
	res = u'%s langue(s) retirée(s) :\n* %s' \
			% (len(langues_retirees), u'\n* '.join(langues_retirees))
	res += u'\n\n%s langue(s) ajoutée(s) :\n* %s' \
			% (len(langues_ajoutees), u'\n* '.join(langues_ajoutees))
	res += u'\n\n%s langue(s) modifiée(s) :\n* %s' \
			% (len(langues_modifiees), u'\n* '.join(langues_modifiees))
	res += u'\n\n%s redirection(s) retirée(s) :\n* %s' \
			% (len(redirections_retirees), u'\n* '.join(redirections_retirees))
	res += u'\n\n%s redirection(s) ajoutée(s) :\n* %s' \
			% (len(redirections_ajoutees), u'\n* '.join(redirections_ajoutees))
	res += u'\n\n%s redirection(s) modifiée(s) :\n* %s' \
			% (len(redirections_modifiees), u'\n* '.join(redirections_modifiees))
	return res

def erreur(msg):
	'''
	écrit un message d'erreur sur la page de sortie
	'''
	Page_sortie = pywikibot.Page(site, page_sortie)
	if test:
		pywikibot.output(msg)
	else:
		sauvegarde(Page_sortie, msg, summary=u'Mise à jour non réalisée suite à une erreur')

def maj_liste():
	Page_sortie = pywikibot.Page(site, page_sortie)
	Page_lua = pywikibot.Page(site, page_lua)

	# La liste des langues se trouve entre ces deux marqueurs
	marqueur_debut_langues = u'-- Langues\n'
	marqueur_fin_langues = u'-- Fin langues\n'
	# La liste des redirections de langues se trouve entre ces deux marqueurs
	marqueur_debut_redirections = u'-- Redirections de langues\n'
	marqueur_fin_redirections = u'-- Fin redirections de langues\n'

	try:
		contenu = Page_lua.text
	except pywikibot.NoPage:
		erreur(u'Erreur lors de la \'\'\'mise à jour du %s\'\'\' : la page'
				u'%s n\'existe pas.' % (date_actuelle, page_lua))
		return
	except pywikibot.IsRedirectPage:
		erreur(u'Erreur lors de la \'\'\'mise à jour du %s\'\'\' : la page'
				u'%s n\'existe pas.' % (date_actuelle, page_lua))
		return

	date_actuelle = datetime.datetime.utcnow()
	date_actuelle = date_actuelle.strftime('%d/%m/%Y')

	######################################
	############## LANGUES ###############
	######################################
	# On extrait de la page la liste des langues en se basant sur
	# les marqueurs précédemment déclarés
	pos_marqueur_debut = contenu.find(marqueur_debut_langues)
	pos_marqueur_fin = contenu.find(marqueur_fin_langues)
	# On génère une erreur si ces marqueurs n'ont pas été trouvé
	if pos_marqueur_debut == -1 or pos_marqueur_fin == -1:
		erreur(u'\'\'\'Mise à jour du %s\'\'\' abandonnée : le format de la liste des langues en Lua'
							u' n\'a pas été reconnu (au moins un des marqueurs %s ou %s manque)'
							% (date_actuelle, marqueur_debut_langues, marqueur_fin_langues))
		return
	contenu_langues = contenu[pos_marqueur_debut:pos_marqueur_fin]
	# On récupère toutes les lignes de la liste principale dans un tableau python
	liste_langues = []
	R = re.compile(ur'l\[\'([^\']+?)\'\] = \{ nom = \'([^\']+?)\'[, ][ }]')
	for langue in R.findall(contenu_langues):
		liste_langues.append(langue)
	######################################

	######################################
	###### REDIRECTIONS DE LANGUES #######
	######################################
	# On extrait de la page la liste des redirections de langues
	# en se basant sur les marqueurs précédemment déclarés
	pos_marqueur_debut = contenu.find(marqueur_debut_redirections)
	pos_marqueur_fin = contenu.find(marqueur_fin_redirections)
	# On génère une erreur si ces marqueurs n'ont pas été trouvé
	if pos_marqueur_debut == -1 or pos_marqueur_fin == -1:
		erreur(u'\'\'\'Mise à jour du %s\'\'\' abandonnée : le format de la liste des redirections'
				u' de langues en Lua n\'a pas été reconnu (au moins un des marqueurs %s ou %s manque)'
				% (date_actuelle, marqueur_debut_redirections, marqueur_fin_redirections))
		return
	contenu_redirections = contenu[pos_marqueur_debut:pos_marqueur_fin]
	liste_redirections_langues = []
	R = re.compile(ur'l\[\'([^\']+?)\'\] = l\[\'([^\']+?)\'\]')
	for langue in R.findall(contenu_redirections):
		liste_redirections_langues.append(langue)
	######################################

	######################################
	## MISE EN FORME JSON ET SAUVEGARDE ##
	######################################
	redirections_json = tuple_to_json(liste_redirections_langues)
	langues_json = tuple_to_json(liste_langues)
	contenu = langues_json[:len(langues_json)-1] + u',"redirects":' + redirections_json + u'}'

	diff = diff_listes_json(contenu)
	contenu = u'\'\'\'Mise à jour du %s\'\'\'\n\n' % date_actuelle + \
			diff + u'\n\n=== Code à mettre dans la page [[%s]] ===\n\n' \
			% page_json + contenu

	if not Page_sortie.exists() or contenu != Page_sortie.text:
		sauvegarde(Page_sortie, contenu, summary=u'Mise à jour automatique de la liste')
	else:
		sauvegarde(Page_sortie, u'\'\'\'Mise à jour du %s\'\'\' : aucun changement de la'
						u'liste des langues depuis la dernière mise à jour' % date_actuelle,
						summary=u'Aucune mise à jour nécessaire')

# adapté d'un script de JackPotte
def sauvegarde(PageCourante, Contenu, summary=None):
	pagename = PageCourante.title()
	modif = "o"
	if test:
		if PageCourante.exists() and len(Contenu) <= 5000:
			pywikibot.showDiff(PageCourante.text, Contenu)
		elif len(Contenu) > 5000:
				pywikibot.output(u'Début du contenu :')
				pywikibot.output(Contenu[:2000])
				pywikibot.output(u'\nFin du contenu :')
				pywikibot.output(Contenu[-1000:])
		else:
			pywikibot.output(Contenu)
		modif = pywikibot.inputChoice(u"Sauvegarder ?",
		                    ["oui", "non"], ["o", "n"], default="n")
	if modif == "o":
		try:
			PageCourante.put(Contenu, summary, minorEdit=False)
		except pywikibot.IsRedirectPage:
			pywikibot.output(u"IsRedirectPage en sauvegarde")
			return
		except pywikibot.LockedPage:
			pywikibot.output(u"LockedPage en sauvegarde")
			return
		except pywikibot.EditConflict:
			pywikibot.output(u"EditConflict en sauvegarde")
			return
		except pywikibot.ServerError:
			pywikibot.output(u"ServerError en sauvegarde")
			return
		except pywikibot.BadTitle:
			pywikibot.output(u"BadTitle en sauvegarde")
			return
		except AttributeError:
			pywikibot.output(u"AttributeError en sauvegarde")
			return
	else:
		pywikibot.output(u"Non modifié (pas de changement ou clic de l'utilisateur)")

if __name__ == '__main__':
	try:
		maj_liste()
	finally:
		pywikibot.stopme()