Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 22/03/2010, à 21:30

Totor

[JEU] challenge scripting #7

1269554555.png

[informations sur le jeu]

<<    challenge bash #7    >>

nouveauté ! : désormais le challenge est ouvert aux autres langages de scripting ! scripteurs bash, python, perl, ruby… à vos claviers !

Lecteur de flux RSS
Toujours en mode console, l'objectif est de réaliser un lecteur de flux RSS.
Ce lecteur devra respecter le standard 2.0.
Il devra gérer plusieurs flux RSS. Ces flux pourront être ajouté ou supprimé d'un fichier de configuration.

Libre à vous d'afficher périodiquement ou en continu les informations que vous souhaitez mais le lecteur doit être discret et ne pas bloquer l'utilisation de la console.
L'aspect estéthique n'est toutefois pas à négliger.

Il devra être également possible de demander au lecteur d'afficher la totalité des propriétés d'un flux. 

bonne réflexion...


-- Lucid Lynx --

Hors ligne

#2 Le 22/03/2010, à 21:31

Totor

Re : [JEU] challenge scripting #7

Le lecteur de flux n'est pas difficile en soit. La difficulté réside dans le traitement du fichier XML. C'est un sujet récurrent sur le forum.
J'ai donc écris un parser XML (Il doit en exister mais je n'ai pas cherché. Sputnick en connait il me semble)

Etant un sujet récurrent, je m'attarde un peu sur le fonctionnement de ce parser.
Le parser que je propose n'est pas propre aux flux RSS. Je n'ai pas effectué une recette complète à son sujet mais il me semble assez abouti.
Pour l'utiliser, il faut le sourcer puis l'initialiser via la méthode xmlInit (avec le nom du fichier XML en paramètre).
Je propose ensuite 2 procédures :
- xmlAffiche : Affiche un arbre des tags et des valeurs)
- xmlRecherche : Cette procédure permet de rechercher une propriété suivant d'un nom de tag. Le nom est soit une valeur correspondant à une arborescence complète. Pour cela il faut utiliser l'option -F en paramètre  n°1 (cf. le lecteur rss). Ou bien, le nom correspond au nom d'un tag. Si le tag n'est pas trouvé, la méthode retourne 1. Sinon la variable xmlValeurTrouvee est valorisée par la valeur du tag et xmlCheminTrouve correspond à l'arborescence du tag.

Script parseXml.sh :

#!/bin/bash 

xmlBoolInit=false
xmlFichier=""

xmlRechercheProperties()
{
	for xmlPropriete
	do
		((idx++))
		
		if [[  "${xmlPropriete}" = *[=:]* ]]; then			
			xmlIsValue[idx]=true
			[[ "${xmlPropriete}" = *=* ]] && {
			xmlTags[idx]="${xmlPropriete%%=*}" 
			#read xmlValues[idx] < <(printf "${xmlPropriete#*=}") # !?! erreur sous crunchbang et pas sous redhat :=> malloc : ../bash/subst.c:4198 : assertion manquée\nrealloc : les tailles de fragment au début et à la fin sont différentes
			xmlValues[idx]="$(awk -F= '{print $2}' <<< "${xmlPropriete}")"
			} || {
				xmlTags[idx]="${xmlPropriete%:*}" 
				xmlValues[idx]="$(awk -F: '{print $NF}' <<< "${xmlPropriete}")"
			}
		else
			((idx--))
			xmlIsValue[idx]=true
			xmlValues[idx]="${xmlPropriete}"
		fi
	done
}


xmlNomTag()
{
	unTag="${1#<}"
	unTag="${unTag%>}"
	unTag="${unTag#[\?/]}"
    unTag="${unTag%[\?/]}"
	echo "${unTag%% *}"
}

xmlInit()
{
	xmlBoolInit=false
	xmlFichier="$1"
	xmlString=
	xmlTags=()
	xmlValues=()
	xmlIsValue=()
	
	[ $# -ne 1 ] && {
		cat >&2 <<EOF
Utilisation :
	${FUNCNAME[0]} <fichier.xml>
EOF
		return 1
	}
	
	[ ! -f "${xmlFichier}" ] && {
		echo >&2 "${FUNCNAME[0]} : ${xmlFichier} non valide."
		return 2
	}

	xmlString="$(tr -d '\n' < "${xmlFichier}")"
	wString="${xmlString}"
	idx=0
	xmlBoolInit=true
	shopt -s nocasematch
	while read
	do
		case "${REPLY}" in
		
		\<\?* | \<*/\> | \<*=*\> )
			xmlTag="$(xmlNomTag "${REPLY}")"
			xmlProperties="${REPLY#*${xmlTag}}"
			xmlProperties="${xmlProperties%>}"
			xmlProperties="${xmlProperties%[\?/]}"
			xmlTags[idx]="${xmlTag}"
			xmlIsValue[idx]=false

			# gestion des propriétés
			eval "xmlRechercheProperties ${xmlProperties}"
			
			# doit-on fermer le tag
			[[ "${REPLY}" = *[\?/]\> ]] && {
				((idx++))
				xmlTags[idx]=""
			}
			;;

		\<[^\?/]*[^/]\>*)
			xmlTags[idx]="$(xmlNomTag "${REPLY}")"
			xmlIsValue[idx]=false
			;;

		\</*) 
			xmlTag="${REPLY#</}"
			xmlTag="${xmlTag%>}"
			xmlTag=${xmlTag%% *}
			xmlIsValue[idx]=false
			[ "${xmlTags[$((idx-1))]}" = "${xmlTag}" ] && {
				((idx--))
				#read xmlValues[$((idx-1))] < <( printf "${wString%%${REPLY}*}") # !?! erreur sous crunchbang et pas sous redhat :=> malloc : ../bash/subst.c:4198 : assertion manquée\nrealloc : les tailles de fragment au début et à la fin sont différentes
				xmlValues[idx]="$(awk -F"${REPLY}" '{print $1}' <<< "${wString}")"				
				xmlIsValue[idx]=true
			} || xmlTags[idx]=""
			;;
		esac
		
		wString="${wString#*${REPLY}}"
		((idx++))
		
	done < <( egrep -o '<[/\?]?[^\?/>]*([^\?/>]+=("[^"]*"|[^ >]*))*[^\?/>][/\?]?>' <<< "${xmlString}")
	shopt -u nocasematch
	
	xmlDebutRecherche
}

xmlAffiche()
{
	${xmlBoolInit} || {
		echo "${FUNCNAME[0]} : Le parser n'a pas été initialisé."
		return 1
	}

	echo "${xmlFichier}"
	decalage=""
	indent="|"$'\t'
	
	[ ${#xmlTags[@]} -eq 0 ] && echo "<Rien à  afficher>"
	for((idx=0;idx < ${#xmlTags[@]}; idx++))
	do
	[ "${xmlTags[idx]}" ] && {
		 ${xmlIsValue[idx]}  && echo -e "${decalage}${xmlTags[idx]}=${xmlValues[idx]}" || {
			echo "${decalage}${xmlTags[idx]}:"
			decalage="${decalage}${indent}"
		}
	} || decalage="${decalage%${indent}}"
	done
}

xmlDebutRecherche()
{
    xmlIdxSearch=-1
	unset xmlPath[@]
}

xmlRecherche()
{
	xmlFullSearch=false
	xmlLastIdx=${xmlIdxSearch}
	xmlValeurTrouvee=""
	xmlCheminTrouve=""
	

	
	case $# in 
		1) xmlSearch="$1"
		   ;;

		2) xmlSearch="$2"
		   [ "$1" = "-F" ] || {
		   	echo "${FUNCNAME[0]} : Option '$1' non valide."
			return 1
		   }
		   xmlFullSearch=true
		   xmlDebutRecherche
		   ;;

		*) cat <<EOF
${FUNCNAME[0]} : Nombre de parametre non valide ! 1 ou 2 attendu.
[-F full-path]|[tag]
EOF
	;;
	esac

	[ ! "${xmlSearch}" ] && {
		echo "${FUNCNAME[0]} : Pattern à rechercher non valide..."
		return 1
	}

	xmlPatternFound=false
	xmlEndReached=false
	shopt -s nocasematch
	until ${xmlPatternFound} || ${xmlEndReached}
	do
		((xmlIdxSearch++))
		(( xmlIdxSearch >= ${#xmlTags[@]} )) && { 
			# on a atteint la fin, on stope la recherche et on repositionne l'index
			xmlEndReached=true
			xmlIdxSearch=${xmlLastIdx}
		} || {
			if [ "${xmlTags[xmlIdxSearch]}" ] ; then
				xmlPath+=(  ${xmlTags[xmlIdxSearch]} ) 

				if ${xmlFullSearch}; then
					allPath=${xmlPath[@]}
					[ "${allPath}" = "${xmlSearch}" ] && { 
						xmlPatternFound=true
						xmlValeurTrouvee=${xmlValues[xmlIdxSearch]}
						xmlCheminTrouve=${xmlPath[@]}
					}
				else
					[ "${xmlTags[xmlIdxSearch]}" = "${xmlSearch}" ] && {
						xmlPatternFound=true
						xmlValeurTrouvee=${xmlValues[xmlIdxSearch]}
						xmlCheminTrouve=${xmlPath[@]}
					}
				fi
				
				 ${xmlIsValue[xmlIdxSearch]} && unset xmlPath[$((${#xmlPath[@]}-1))]
			else
				unset xmlPath[$((${#xmlPath[@]}-1))]
			fi
		}
	done
	shopt -u nocasematch
	${xmlEndReached} && { echo "'${xmlSearch}' not found !" >&2 ; return 1; }
	return 0
}

Lecteur de flux XML :

#!/bin/bash


[ $# -ne 1 ] && {
	echo "${0##*/} : Url manquante !" >&2
	exit 1
}

{ 
	speed=8 # 0->10
	((delay=10-speed))

# Chargement du parser XML
. ./parseXml.sh

# URL du fichier RSS
rssURL="$1"
#rssURL="http://www.prixdunet.com/rss_new.xml"

rssFile="${rssURL##*/}"

while true;
do
	# récupération du fichier
	wget -q  -O "${rssFile}" "${rssURL}"

	[ $? -ne 0 ] && {
		echo "${0##*/} : Erreur lors du download de '${rssURL}'" >&2
		exit 1
	}

	xmlInit "${rssFile}"
	
#xmlAffiche > traceAffiche.log
#exit

	# Recherche du Titre du fil, de l'url et de la description
	xmlRecherche -F "rss channel title" 
	infos="${xmlValeurTrouvee}"
	xmlRecherche -F "rss channel link"
	infos="${infos} (${xmlValeurTrouvee}) - "
	xmlRecherche -F "rss channel description"
	infos="${infos} ${xmlValeurTrouvee}"

	rssNumber=0
	# recherche du 1er item puis de l'ensemble des titre et link
	titre=""
	url=""
	
	xmlRecherche -F "rss channel item"  && for((idx=${xmlIdxSearch};idx<${#xmlTags[@]};idx++))
	do
		[ "${xmlTags[idx]}" = title ] && titre="${xmlValues[idx]}"
		[ "${xmlTags[idx]}" = link ] && url="${xmlValues[idx]}"
		
		[ "${titre}" ] && [ "${url}" ] && {
			((rssNumber++))
			infos="${infos} - info #${rssNumber} : ${titre} (${url})"
			titre=""
			url=""
		}
	done

	while [ "${infos}" ]
	do
		texte="${infos}"
		maxCols=$(tput cols) 
		((${#texte} > maxCols)) && texte="${texte:0:maxCols}"
		
		# récupération de la position du curseur
		echo -en "\e[s"
	
		# on se positionne en haut
		echo -en "\e[H"
		
		# affichage
		echo -en "\r\e[2K${texte}\r"
		
		# restauration du curseur
		echo -en "\e[u"
		
		((delay<10)) && sleep 0.${delay}s || sleep 1
		infos="${infos#?}"
	done
done
}  &

EDIT : Retour sur la fonction xmlRecherche. Lorsqu'elle est utilisée sans l'option -F (full path), la recherche s'effectue à partir du précédant élément trouvé

Dernière modification par Totor (Le 26/04/2010, à 15:00)


-- Lucid Lynx --

Hors ligne

#3 Le 22/03/2010, à 21:37

nesthib

Re : [JEU] challenge scripting #7

Le titre du challenge n'est pas encore changé mais vous pouvez donc utiliser un autre langage de scripting que le bash si vous le désirez smile

Et n'oubliez pas de commenter votre code !

ps. le challenge est issu d'une proposition de Gage, n'hésitez pas à faire les vôtres. Topic de discussion a propos du challenge et salon jabber : challenge.ubuntu-fr@chat.jabberfr.org


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#4 Le 23/03/2010, à 08:01

Gage

Re : [JEU] challenge scripting #7

Je m'abonne !


Ça, ce sont les sources. Le mouton que tu veux est dedans.
Merci, c'est tout à fait comme ça que je le voulais ! Crois-tu qu'il faille beaucoup de ressources à ce mouton ? Parce que ma config est toute petite...
Ça devrait aller. Tu peux te compiler un petit mouton.
Pas si petit que ça. Tiens ! il s'est mis en veille...

Hors ligne

#5 Le 23/03/2010, à 09:11

nesthib

Re : [JEU] challenge scripting #7

(@Gage : on triche pas pour avoir la première place hein ? big_smile d'toute façon on t'a vu lol)
allez bon courage à tous !


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#6 Le 23/03/2010, à 12:28

Gage

Re : [JEU] challenge scripting #7

En même temps, ça serait pas super malin de participer la semaine où le challenge est une de mes propositions... Question intégrité, tout ça, on a vu mieux.


Ça, ce sont les sources. Le mouton que tu veux est dedans.
Merci, c'est tout à fait comme ça que je le voulais ! Crois-tu qu'il faille beaucoup de ressources à ce mouton ? Parce que ma config est toute petite...
Ça devrait aller. Tu peux te compiler un petit mouton.
Pas si petit que ça. Tiens ! il s'est mis en veille...

Hors ligne

#7 Le 23/03/2010, à 12:50

nesthib

Re : [JEU] challenge scripting #7

c'est comme tu veux si tu ne souhaites pas participer, mais je compte quand même sur toi pour nous envoyer ta solution wink


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#8 Le 25/03/2010, à 21:50

Totor

Re : [JEU] challenge scripting #7

Bonjour,

Ce challenge ne semble pas bousculer les foules...
Il y a t'il une quelconque difficulté ?
Vous parait-il inintéressant ?
Manquez-vous de temps ?

Avez-vous besoin d'une piste, de temps ?

@+


-- Lucid Lynx --

Hors ligne

#9 Le 25/03/2010, à 22:30

Kanor

Re : [JEU] challenge scripting #7

C'est quand la date de fin ?

Hors ligne

#10 Le 25/03/2010, à 22:31

Gage

Re : [JEU] challenge scripting #7

Je pense à plusieurs pistes :
- Ça existe déjà, pourquoi réinventer la roue
- Bash me paraît particulièrement inadapté à ce challenge, il faudrait se tourner vers Python ou autre
- Les gens n'ont pas envie de se coltiner la norme RSS...

Et j'avoue que personnellement, je n'ai pas le temps (et ne l'aurai pas avant un bon mois). Dommage, ç'aurait été intéressant sad.

Dernière modification par Gage (Le 25/03/2010, à 22:32)


Ça, ce sont les sources. Le mouton que tu veux est dedans.
Merci, c'est tout à fait comme ça que je le voulais ! Crois-tu qu'il faille beaucoup de ressources à ce mouton ? Parce que ma config est toute petite...
Ça devrait aller. Tu peux te compiler un petit mouton.
Pas si petit que ça. Tiens ! il s'est mis en veille...

Hors ligne

#11 Le 25/03/2010, à 22:43

nesthib

Re : [JEU] challenge scripting #7

Kanor a écrit :

C'est quand la date de fin ?

quand il y aura suffisamment de participation (on espère) sinon encore quelques jours je pense.

Gage a écrit :

Je pense à plusieurs pistes :
- Ça existe déjà, pourquoi réinventer la roue

le but c'est de faire un script sympa et de comparer les codes wink

Gage a écrit :

- Bash me paraît particulièrement inadapté à ce challenge, il faudrait se tourner vers Python ou autre

nesthib a écrit :

Le titre du challenge n'est pas encore changé mais vous pouvez donc utiliser un autre langage de scripting que le bash si vous le désirez smile

NB. j'ai changé le titre smile


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#12 Le 25/03/2010, à 22:47

frafa

Re : [JEU] challenge scripting #7

Bonjour,
Moi pas de temps en ce moment,
+ des soucis de santé (en voie de résolution...)
qui limitent mon temps devant un ecran.

Hors ligne

#13 Le 25/03/2010, à 22:55

Gage

Re : [JEU] challenge scripting #7

nesthib, tu prêches un convaincu wink. Je cherchais juste à imaginer ce qui pouvait bloquer, alors que les précédents challenges ont marché du feu de dieu.


Ça, ce sont les sources. Le mouton que tu veux est dedans.
Merci, c'est tout à fait comme ça que je le voulais ! Crois-tu qu'il faille beaucoup de ressources à ce mouton ? Parce que ma config est toute petite...
Ça devrait aller. Tu peux te compiler un petit mouton.
Pas si petit que ça. Tiens ! il s'est mis en veille...

Hors ligne

#14 Le 25/03/2010, à 23:37

toto2006

Re : [JEU] challenge scripting #7

bonsoir

Kanor a écrit:
    C'est quand la date de fin ?

+1  c'est vrai que connaitre la date de fin des challenges serait appréciable.


Ce challenge ne semble pas bousculer les foules...
Il y a t'il une quelconque difficulté ?
Vous parait-il inintéressant ?
Manquez-vous de temps ?

Avez-vous besoin d'une piste, de temps ?

@totor  c'est vrai que ce challenge ne m'interpelle pas plus que ca , et que j'ai pas forcement trop de tps a consacrer a un challenge en général , mais bon c'est vrai que ma 2eme idées en lisant l'intitulé (la première étant BOF, on verra le prochain ) c'était que je voyais ca plus pour un challenge pour des langages genre C/C++ (ou python ruby pour les adeptes ) . mon idée finale et que ca pourrait être marrant de voir si possible de faire ca en awk+bash par exemple , mais je pense pas avoir vraiment le temps  de me pencher sur ca en ce moment .

- Les gens n'ont pas envie de se coltiner la norme RSS...

@Gage c'est vrai y-a de ca aussi smile


Ps : bon rétablissement frafa , j'utilise ton multibootV3 tout les jours ,il est vraiment excellent !

Hors ligne

#15 Le 26/03/2010, à 12:11

AnsuzPeorth

Re : [JEU] challenge scripting #7

Bjr,
Ce challenge ne m'a pas motivé plus que ça non plus ...!
Il faut dire que je n'utilise pas les flux rss.
Donc pour ce challenge, a part récupérer le flux, le découper, je vois pas quoi rajouter d'autre...
Il aurait peut être fallu fournir un cahier des charges précis, pour savoir quelle direction prendre !

ps: Courage Frafa, c'est juste un mauvais moment à passer, je connait..malheureusement !
Au faite, tu n'es pas du signe, ou ascendant de la vierge ?


Interface graphique pour bash, python ou autre: glade2script
Support Tchat: http://chat.jabberfr.org/muckl_int/inde … ade2script  (Hors ligne)

Hors ligne

#16 Le 26/03/2010, à 13:47

Kanor

Re : [JEU] challenge scripting #7

Idée de fonctionnalité
- gestion d'un démon pour obtenir les notification en temps "réel"
- création d'une interface en curse(ou autre)
- gestion d'un service web (différent technique pour ne pas recharger x fois le flux )
- mise en place d'un cache
- import /export en opml

voila quelque idée ... wink

Hors ligne

#17 Le 26/03/2010, à 14:16

Totor

Re : [JEU] challenge scripting #7

Bon alors, effectivement, la difficulté ne résidait pas dans la gestion du flux et l'intérêt n'était pas là
(Je n'utilise pas à titre personnel de flux RSS)
Je n'ai pas été assez précis sur un point : "et ne pas bloquer l'utilisation de la console".
- mon idée était d'incorporer un prompt qui affiche un flux dynamique en permanence (et pas seulement à chaque nouvel invite de ligne)


@Kanor : à plusieurs reprises, il nous a été remonté que les utilisateurs manquaient de temps. Il ne faut donc pas que ce soit un réel projet. Nous avons déjà essayé de le faire en le décomposant en plusieurs étapes (démon de gestion des utilisateurs/mot de passe) mais l'investissement semblait trop conséquent et finalement, nous n'avons pas été jusqu'au bout.


-- Lucid Lynx --

Hors ligne

#18 Le 26/03/2010, à 15:34

Alambique

Re : [JEU] challenge scripting #7

N'empêche que tout ca me donne raison quant à mes reflexions sur le post du challenge précédent... héhéhé roll


Mal-pensant, mais résolument linuxien.

Hors ligne

#19 Le 26/03/2010, à 15:55

nesthib

Re : [JEU] challenge scripting #7

Alambique a écrit :

N'empêche que tout ca me donne raison quant à mes reflexions sur le post du challenge précédent... héhéhé roll

note dans l'intitulé du challenge :
nouveauté ! : désormais le challenge est ouvert aux autres langages de scripting ! scripteurs bash, python, perl, ruby… à vos claviers !

roll on attend ta participation tongue


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#20 Le 02/04/2010, à 00:43

sputnick

Re : [JEU] challenge scripting #7

Désolé, moi non plus j'ai moins de temps en ce moment pour animer le thread. Mais l'idée me semble intéressante.


On ne peut pas mettre d'array dans un string!
https://sputnick.fr/

Hors ligne

#21 Le 04/04/2010, à 19:47

sputnick

Re : [JEU] challenge scripting #7

Bon, moi je propose plusieurs solutions ^^
wnews rss2html newsbeuter rss2html-hg raggle snownews


On ne peut pas mettre d'array dans un string!
https://sputnick.fr/

Hors ligne

#22 Le 18/04/2010, à 10:13

toto2006

Re : [JEU] challenge scripting #7

Sinon pour relancer le topic je trouve le challenge 7 un peu long smile

Hors ligne

#23 Le 18/04/2010, à 12:42

nesthib

Re : [JEU] challenge scripting #7

petit manque de temps en ce moment… mais sputnick devrait nous en sortir un nouveau très bientôt smile

d'ailleurs si certains veulent participer à l'animation ce ne sera pas de trop wink


GUL Bordeaux : GirollServices libres : TdCT.org
Hide in your shell, scripts & astuces :  applications dans un tunnelsmart wgettrouver des pdfinstall. auto de paquetssauvegarde auto♥ awk
  ⃛ɹǝsn xnuᴉꞁ uʍop-ǝpᴉsdn

Hors ligne

#24 Le 18/04/2010, à 12:50

Totor

Re : [JEU] challenge scripting #7

Désolé, je manque de temps également et je rentre de congés .... Je m'engage à fournir une réponse sous peu.


-- Lucid Lynx --

Hors ligne

#25 Le 24/04/2010, à 23:36

Totor

Re : [JEU] challenge scripting #7

Bonsoir,

Bon, j'ai finalement pu prendre le temps d'écrire une solution.
Désolé pour cet énorme retard.


-- Lucid Lynx --

Hors ligne