Compare commits

..

4 commits

9 changed files with 266 additions and 869 deletions

2
.gitignore vendored
View file

@ -1,2 +0,0 @@
__pycache__/
*~

View file

@ -1,28 +0,0 @@
#!/bin/bash
# Dernière révision : 2021/10/31
# Auteur : Zatalyz. Merci à YannK, Tycho, Pulkomandy, Madi, Link Mauve et Glorf pour leurs nombreux conseils, les explications, l'aide et la patience !
# Licence CC0
# Ce script permet de ne garder que l'alentours et les emotes.
# Système adapté au nouveau système de log (à la fois plus simple puisque pas besoin du syslog,
# et un peu moins régulier dans les noms...)
# Fonctionnement idéal : /usr/bin/bash ./1analyse_new_logryzom.sh fichier_entrée fichier_sortie
# Par exemple :
# ./1analyse_new_logryzom.sh log_zatalyz.txt log_zatalye_alentours.txt
# On peut aussi renseigner les noms et chemins ici sur les variables :
logfile1="$1"
logsortie="$2"
# Canaux à garder
# Alentours + Endroit où on est
grep 'SAY\|SHOUT\|SYSTEM\/ZON' "$logfile1" > templog.txt
# vire le timestamp de début des lignes. Garde le * si ça peut servir à nettoyer le sys.info..
cut -b 21- templog.txt > templog2.txt
# Enlève les couleurs des canaux
sed 's/@{[A-F0-9]\{4\}}//g' templog2.txt > templog3.txt
# enlever le nom des canaux :
sed -re 's:^\([A-Z/]+\) +\* +(.*)$:\1:' templog3.txt > templog4.txt
# laisser uniquement les phrases traduites (attention, ça peut être étrange)
sed -re 's#\{:[a-zA-Z]{2}:[^}]+\}@\{[[:space:]]##' templog4.txt > templog5.txt
# Si quelqu'un joue avec des caractères bizarres, ça remet tout d'aplomb
iconv -t utf-8 -c templog5.txt > "$logsortie"
# Enlever les fichiers temporaires
rm templog*.txt

View file

@ -1,164 +0,0 @@
#!/bin/bash
# Dernière révision : 2021/10/31
# Auteur : Zatalyz. Merci à YannK, Tycho et Glorf pour leurs nombreux conseils, les explications, l'aide et la patience !
# Licence CC0
# Ce script sépare les logs en 1 fichier de log par jour.
# Il est prévu pour les logs du jeu Ryzom mais peut s'adapter à d'autres types de log (virer "pseudo !")
# Mettre tous les logs en vrac dans le même dossier que le script, et lancer la moulinette.
# La bonne commande à passer est
# cmd ./dossiersource ./dossier final [nomperso]
# Sans argument des variables par défaut seront utilisées. Le nom du perso peut être déduit des logs
# Exemple :
# ./split_log.sh ./logbrut/zatalyz/ ./final john
# => les fichiers seront traités depuis ./logbrut/zatalyz/, et mis dans le dossier ./final sous le nom de log_john_année_mois_jour.txt
# sans préciser "john", le nom des logs sera probablement celui de zatalyz, si les fichiers d'origine sont bien formatés comme log_zatalyz.txt ou log_zatalyz_*.txt
# Le script range aussi les logs.
# Attention ! Il laisse quelques fichiers dans le dossier courant.
# Attention ! Gardez les logs originaux, dans quelques cas les lignes
# ne sont pas analysées correctement (cas des retours à la ligne comme
# dans les poèmes : la ligne ne commence plus par une date).
#############
# Variables #
#############
# Variables par défaut dans les checks : pouvoir les changer ?
# Vérification si le dossier source des logs existe et est renseigné
if [ -d "$1" ]
then originalfolderlog="$1"
else originalfolderlog="./logsource"
fi
# Vérification si le dossier final des logs existe et est renseigné
if [ -d "$2" ]
then finalfolder="$2"
else finalfolder="./final_logs"
fi
# Vérification si un nom de perso est donné et sinon, extraction depuis les noms de fichier
if [ -z "$3" ]
then
myfunc() {
basename -a "$originalfolderlog"/* | cut -d_ -f2 | uniq
}
perso="$(myfunc)"
else perso="$3"
fi
echo "Résultat final : nous partons de $originalfolderlog pour mettre les logs de $perso dans $finalfolder"
# Variables des dossiers de travail
tmpfolder="./tmplogfolder"
listrawlog="$tmpfolder/listrawlog.txt"
rawfolder="$tmpfolder/raw_logs"
yearfolder="$tmpfolder/year_logs"
monthfolder="$tmpfolder/month_logs"
dayfolder="$tmpfolder/day_logs"
#############
# Script #
#############
# Vérifier si on a les dossiers temporaires de travail et sinon, les créer
if [ -d "$rawfolder" ];then
echo "Le dossier $rawfolder existe, passons à la suite";
else
mkdir -p "$rawfolder"
echo "Le dossier $rawfolder a été créé, passons à la suite";
fi
if [ -d "$yearfolder" ];then
echo "Le dossier $yearfolder existe, passons à la suite";
else
mkdir -p "$yearfolder"
echo "Le dossier $yearfolder a été créé, passons à la suite";
fi
if [ -d "$monthfolder" ];then
echo "Le dossier $monthfolder existe, passons à la suite";
else
mkdir -p "$monthfolder"
echo "Le dossier $monthfolder a été créé, passons à la suite";
fi
if [ -d "$dayfolder" ];then
echo "Le dossier $dayfolder existe, passons à la suite";
else
mkdir -p "$dayfolder"
echo "Le dossier $dayfolder a été créé, passons à la suite";
fi
if [ -d "$finalfolder" ];then
echo "Le dossier $finalfolder existe, passons à la suite";
else
mkdir -p "$finalfolder"
echo "Le dossier $finalfolder a été créé, passons à la suite";
fi
# On ne travaille pas sur les fichiers sources. Jamais.
# Parce qu'il y a parfois des blagues dans les logs, on nettoie l'encodage.
for file in "$originalfolderlog"/*
do
originallog="$(basename $file)"
cat "$file" | tr -d "\000-\010" | tr -d "\016-\037" > "$rawfolder"/"$originallog"
done
# on va faire un très gros fichier avec tous les logs. Évitez de l'ouvrir avec un éditeur de texte basique.
cat "$rawfolder"/* > "$tmpfolder"/logcomplet_"$perso".txt
#Et on va faire un fichier de controle donnant toutes les dates avec logs, et les lignes foireuses (genre chants) (à vérifier manuellement en cas de doute)
cut -c 1-11 "$tmpfolder"/logcomplet_"$perso".txt | sort | uniq > controlline_"$perso".txt
echo "$tmpfolder"/logcomplet_"$perso".txt et controlline_"$perso".txt créés
for logname in "$tmpfolder"/logcomplet_"$perso".txt
do
# Vérification que les fichiers sont bien des fichiers, ce serait bête de planter le script pour un manque de vérification
if [ -f "$logname" ] ; then
echo "$logname est un fichier et va être traité"
# On va faire un seul gros fichier par année
for year in {2009..2025} ; do
#echo "${year}"
grep "^${year}/" "$logname" >> "$yearfolder"/"${year}".log
# Effacer les fichiers vides
[ -s "$yearfolder"/"${year}".log ] || rm -f "$yearfolder"/"${year}".log
#echo "${year} traitée"
# On vérifie si le fichier d'année existe et on ne traite que celles qui existent
if [ -f "$yearfolder"/"${year}".log ] ; then
# Puis on va faire un fichier par mois
for month in {01..12} ; do
# echo "${year}/${month} en cours"
grep "^${year}/${month}" "$yearfolder"/"${year}".log > "$monthfolder"/"${year}_${month}".log
[ -s "$monthfolder"/"${year}_${month}".log ] || rm -f "$monthfolder"/"${year}_${month}".log
# On vérifie si le fichier année/mois existe et on ne traite que ceux qui existent
if [ -f "$monthfolder"/"${year}_${month}".log ] ; then
#echo "$monthfolder"/"${year}_${month}".log
# Puis un fichier par jour
for day in {01..31} ; do
#echo "${year}/${month}/${day} en cours"
grep "^${year}/${month}/${day}" "$monthfolder"/"${year}_${month}".log > "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".log
[ -s "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".log ] || rm -f "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".log
#echo "${year}/${month}/${day} traité"
done
fi
done
fi
done
else
echo "$logname n'est pas un fichier, il n'a pas été traité"
fi
done
#Tri des fichiers dans des dossiers du type Dossier_trié/perso/année/mois/log.log
# On récupère la date, en formattant comme pour les logs
for f in $dayfolder/*.log
do
year=${f: -14:4}
month=${f: -9:2}
day=${f: -6:2}
# On se fait une jolie variable qui imite la partie datée du nom des fichiers de log
log=log_"$perso"_"${year}_${month}_${day}.log"
# on créé le dossier de chaque mois dans les archives s'il n'existe pas
mkdir -p "$finalfolder"/"$perso"/"$year"/"$month/"
# on déplace ces logs dans leur archive
mv "$dayfolder"/"$log" "$finalfolder"/"$perso"/"$year"/"$month"/"$log"
done
echo "Les fichiers ont été découpés et rangés"
# Nettoyage
#rm -r "$tmpfolder"
echo "faites un rm -r $tmpfolder si le dossier tmp était bien JUSTE pour le script..."
echo "Tout est bon. Contrôlez le nombre de fichiers entre $finalfolder et controlline_$perso.txt"

View file

@ -1,38 +1,38 @@
# Python version
The script uses basic python3 with no additional 3rd part libraries. The main file is main.py and the only other file it uses is tk_tooltip.py.
To just run the GUI, run:
`python3 main.py`
## Mac executable
Executable for Mac can be downloaded from https://ryzom.siela1915.com/download/ryzom_log_cleaner_mac.zip (Unzip it and then right-click -> Open the executable to open)
##
If you want to create a binary for distribution, it seems that pyinstaller is the easiest way.
Just run:
`pip3 install pyinstaller`
`pyinstaller --onefile --noconsole --clean --log-level=WARN --strip main.py tk_tooltip.py`
# Gestion des logs
Ensemble de scripts bash pour nettoyer les logs clients de Ryzom
Cet ensemble de script sert à traiter les logs des personnages de Ryzom (action à activer en jeu avec /chatlog). Grâce à ça, on peut ensuite partager et relire les bêtises qu'on a raconté avec les amies.
## Analyse New log
Pour analyser les logs d'après 2012/2013, par là.
## Fonctionnement
Pour analyser les logs d'après 2012/2013 (au moment où le format a changé).
Fonctionnement idéal : `/bin/bash ./1analyse_new_logryzom.sh fichier_entrée fichier_sortie`
Lancez
./clean _log.sh
Et regarder ce qui est dit.
Par exemple :
Mettez les logs d'un seul perso dans le dossier "sources_brutes".
Cela ne prends pas les persos mélangés (pas encore...).
`./1analyse_new_logryzom.sh log_zatalyz.txt log_zatalye_alentours.txt`
On peut aussi passer des arguments pour préciser les dossiers, mais quel intérêt ? Voir options dans le script.
Le script va découper les logs de base pour avoir un fichier par jour, puis analyser chacun de ces fichiers afin d'en avoir une version nettoyée avec uniquement les infos qu'on souhaite garder (généralement le rp en alentours).
Attention ! Dans quelques cas les lignes ne sont pas analysées correctement (cas des retours à la ligne comme dans les poèmes : la ligne ne commence plus par une date). Gardez les logs originaux et controllez manuellement selon les indications du script.
### Vieille version des logs et snippet
Pour les logs d'avant le changement de système (donc avant 2013, je crois) : afin de pouvoir continuer à nettoyer ce genre de log, le script "analyse_old_logryzom.sh" est là. À prendre tel quel. Il FAUT un fichier sysinfo.ini, qui contient toutes les expressions régulières à filtrer (tout ce qui est dans le sys.infos). C'était plus lourd. Les canaux sont gardés selon un code couleur. À documenter, un jour, peut-être, ou pas : ça se retrouve en regardant les logs.
Le script "erase_all.sh" (qui est un snippet plus qu'un script) permet de remettre son dossier à neuf avant de relancer une analyse. Évitez de le lancer si vous ne voulez pas tout effacer, après c'est perdu pour de vrai.
## Liste des canaux
Si on veut filtrer autrement. Par défaut, le script est réglé pour l'alentours (emotes et cris compris) + une indication sur les zones traversées, afin de suivre quand les persos se déplacent.
Liste des canaux, si on veut filtrer autrement :
- SAY : alentours
- SAY/EMT : emotes
- SAY/BBL : messages de PNJ
- SAY/SHOUT : cris
- UNIVERSE : Univers
- REGION : comme indiqué
- SYSTEM : messages systèmes
- SYSTEM/BC : Broadcast (annonces des administrateurs)
- SYSTEM/AROUND : messages en alentours qui ne sont pas dit par des homins (genre "bienvenue sur ryzom)
@ -49,39 +49,16 @@ Liste des canaux, si on veut filtrer autrement :
- SYSTEM/TSK : informations à propos des missions
- SYSTEM/XP : expérience gagnée
- SYSTEM/THM : encyclopédie
- GUILD/MTD : Mot du jour de la Guilde
- GUILD : Messages en guilde
- DYN0, DYN1, DYN3, etc : canaux dynamiques. Le canal de langue est souvent sur DYN0
- TELL : messages privés
- TEAM : messages en équipe
## Analyse Old Log
Pour les logs d'avant le changement de système (donc avant 2013, je crois). Il FAUT un fichier sysinfo.ini, qui contient toutes les expressions régulières à filtrer (tout ce qui est dans le sys.infos). C'était plus lourd. Les canaux sont gardés selon un code couleur. À documenter, un jour, peut-être, ou pas : ça se retrouve en regardant les logs.
## Splitlog
Ce script sépare les logs en 1 fichier de log par jour.
Il est prévu pour les logs du jeu Ryzom mais peut s'adapter à d'autres types de log (virer "pseudo !")
Mettre tous les logs en vrac dans le même dossier que le script, et lancer la moulinette.
La bonne commande à passer est
`cmd ./dossiersource ./dossier final [nomperso]`
Sans argument des variables par défaut seront utilisées. Le nom du perso peut être déduit des logs
Exemple :
`./split_log.sh ./logbrut/zatalyz/ ./final john`
=> les fichiers seront traités depuis ./logbrut/zatalyz/, et mis dans le dossier ./final sous le nom de log_john_année_mois_jour.txt sans préciser "john", le nom des logs sera probablement celui de zatalyz, si les fichiers d'origine sont bien formatés comme log_zatalyz.txt ou log_zatalyz_*.txt
Le script range aussi les logs.
Attention ! Il laisse quelques fichiers dans le dossier courant. Gardez les logs originaux, dans quelques cas les lignes ne sont pas analysées correctement (cas des retours à la ligne comme dans les poèmes : la ligne ne commence plus par une date).
## Crédits et licence
Auteur : Zatalyz. Tout est sous licence CC0, c'est de l'assemblage de bons conseils et de tests, rien de transcendant. Plus de détail dans chaque script.

235
clean_log.sh Executable file
View file

@ -0,0 +1,235 @@
#!/bin/bash
# Dernière révision : 2022/02/14
# Auteur : Zatalyz. Merci à YannK, Tycho et Glorf pour leurs nombreux conseils, les explications, l'aide et la patience !
# Licence CC0
# Ce script fait tout en un coup !
# 1) Splitter les logs de Ryzom qu'on lui file et les ranger dans les bons dossiers
# 2) Nettoyer et ne garder que les logs alentours
# Et ceci avec des lots de fichiers.
# ! Il faut que ce soit un seul perso par contre, ça ne veut pas marcher sinon !
# Attention, le script cherche en "dur" des années 2009 à 2025. Si les logs sont en dehors de ces dates, corrigez.
# Syntaxe : par défaut il va tout trouver et dire s'il y a un souci. Lancez simplement
# ./clean _log.sh
# Mais si besoin de préciser :
# ./clean _log.sh dossier_source dossier_tri dossier_alentours perso
# dossier_source = dossier des logs bruts au format ryzom
# dossier_tri = dossier de tri des fichiers bruts en un fichier par jour
# dossier_alentours = dossiers avec les fchiers de logs nettoyés pour ne garder que "alentours".
# perso = nom du perso
# Attention ! Gardez les logs originaux, dans quelques cas les lignes
# ne sont pas analysées correctement (cas des retours à la ligne comme
# dans les poèmes : la ligne ne commence plus par une date). Cela se controle avec le fichier
#############
# Variables #
#############
# Canaux à analyser
channels="SAY\|SHOUT\|SYSTEM\/ZON"
# Vérification si le dossier source des logs existe et est renseigné
if [ -d "$1" ]
then foldersource="$1"
echo "Le dossier $foldersource existe, passons à la suite";
else foldersource="./sources_brutes"
if [ -d "$foldersource" ]
then echo "Le dossier $foldersource existe, passons à la suite";
else
mkdir -p "$foldersource"
echo "Le dossier $foldersource n'existait pas et été créé, déplacez les logs sources dedans";
exit
fi
fi
# Vérification si le dossier des logs bruts triés existe et sinon, le créer
if [ -d "$2" ]
then folderrawsorted="$2"
echo "Le dossier $folderrawsorted existe, passons à la suite";
else folderrawsorted="./sources_tri"
if [ -d "$folderrawsorted" ]
then echo "Le dossier $folderrawsorted existe, passons à la suite";
else mkdir -p "$folderrawsorted"
echo "Le dossier $folderrawsorted a été créé, passons à la suite";
fi
fi
# Vérifier si on a le dossier pour les logs alentours et sinon, le créer
if [ -d "$3" ]
then folderaround="$3"
echo "Le dossier $folderaround existe, passons à la suite";
else folderaround="./alentours"
if [ -d "$folderaround" ]
then echo "Le dossier $folderaround existe, passons à la suite";
else mkdir -p "$folderaround"
echo "Le dossier $folderaround a été créé, passons à la suite";
fi
fi
# Extraire le nom du persos depuis les noms de fichier
# TODO truc améliorable : arriver à gérer plusieurs persos
if [ -z "$4" ]
then
for file1 in "$foldersource"/*
do
myfunc() {
basename "$file1" .txt | cut -d_ -f2 | uniq
}
perso="$(myfunc)"
done
else perso="$4"
fi
# Avoir une variable avec la majuscule au pseudo
pseudo=${perso^}
# Dossiers nécessaires à faire le travail
tmpfolder="./tmplogfolder"
listrawlog="$tmpfolder/listrawlog.txt"
rawfolder="$tmpfolder/raw_logs"
yearfolder="$tmpfolder/year_logs"
monthfolder="$tmpfolder/month_logs"
dayfolder="$tmpfolder/day_logs"
tmparoundfolder="$tmpfolder/tmparoundfolder"
tmparoundfolder2="$tmpfolder/tmparoundfolder2"
# Comme ils sont détruits à la fin, on les recrée à chaque fois
mkdir -p "$tmpfolder" "$rawfolder" "$yearfolder" "$monthfolder" "$dayfolder" "$tmparoundfolder" "$tmparoundfolder2"
echo "Fichiers du personnage $pseudo prêts à être traités" ;
#############
# Split #
#############
# Cette partie sépare les logs bruts en 1 fichier de log par jour.
# On ne travaille pas sur les fichiers sources. Jamais.
# Parce qu'il y a parfois des blagues dans les logs, on nettoie l'encodage.
for file in "$foldersource"/*
do
originallog="$(basename $file)"
cat "$file" | tr -d "\000-\010" | tr -d "\016-\037" > "$rawfolder"/"$originallog"
done
# on va faire un très gros fichier avec tous les logs. Évitez de l'ouvrir avec un éditeur de texte basique.
cat "$rawfolder"/* > "$tmpfolder"/logcomplet_"$perso".txt
#Et on va faire un fichier de controle donnant toutes les dates avec logs, et les lignes foireuses (genre chants) (à vérifier manuellement en cas de doute)
cut -c 1-11 "$tmpfolder"/logcomplet_"$perso".txt | sort | uniq > controlline_"$perso".txt
for logname in "$tmpfolder"/logcomplet_"$perso".txt
do
# Vérification que les fichiers sont bien des fichiers, ce serait bête de planter le script pour un manque de vérification
if [ -f "$logname" ] ; then
#echo "$logname est un fichier et va être traité"
# On va faire un seul gros fichier par année
for year in {2009..2025} ; do
#echo "${year}"
grep "^${year}/" "$logname" >> "$yearfolder"/"${year}".txt
# Effacer les fichiers vides
[ -s "$yearfolder"/"${year}".txt ] || rm -f "$yearfolder"/"${year}".txt
#echo "${year} traitée"
# On vérifie si le fichier d'année existe et on ne traite que celles qui existent
if [ -f "$yearfolder"/"${year}".txt ] ; then
# Puis on va faire un fichier par mois
for month in {01..12} ; do
# echo "${year}/${month} en cours"
grep "^${year}/${month}" "$yearfolder"/"${year}".txt > "$monthfolder"/"${year}_${month}".txt
[ -s "$monthfolder"/"${year}_${month}".txt ] || rm -f "$monthfolder"/"${year}_${month}".txt
# On vérifie si le fichier année/mois existe et on ne traite que ceux qui existent
if [ -f "$monthfolder"/"${year}_${month}".txt ] ; then
#echo "$monthfolder"/"${year}_${month}".txt
# Puis un fichier par jour
for day in {01..31} ; do
#echo "${year}/${month}/${day} en cours"
grep "^${year}/${month}/${day}" "$monthfolder"/"${year}_${month}".txt > "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".txt
[ -s "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".txt ] || rm -f "$dayfolder"/log_"$perso"_"${year}_${month}_${day}".txt
#echo "${year}/${month}/${day} traité"
done
fi
done
fi
done
else
echo "$logname n'est pas un fichier, il n'a pas été traité"
fi
done
#Tri des fichiers dans des dossiers du type Dossier_trié/année/mois/log.txt
# On récupère la date, en formattant comme pour les logs
for f in $dayfolder/*.txt
do
year=${f: -14:4}
month=${f: -9:2}
day=${f: -6:2}
# On se fait une jolie variable qui imite la partie datée du nom des fichiers de log
log=log_"$perso"_"${year}_${month}_${day}.txt"
# on créé le dossier de chaque mois dans les archives s'il n'existe pas
mkdir -p "$folderrawsorted"/"$year"/"$month/"
# on déplace ces logs dans leur archive
mv "$dayfolder"/"$log" "$folderrawsorted"/"$year"/"$month"/"$log"
done
echo "Les fichiers ont été découpés et rangés"
#############
# Alentours #
#############
# Alors là, la misère... car on a des fichiers dans des sous-dossiers.
# Le plus simple : tout remettre dans un dossier en vrac et traiter à partir de là.
find "$folderrawsorted" -maxdepth 4 -name '*.txt' -exec cp {} "$tmparoundfolder"/ \;
for mylog in "$tmparoundfolder/log"*.txt
do
justnamelog() {
basename -s .txt "$mylog"
}
namelog="$(justnamelog)"
sourcelog="$tmparoundfolder/${namelog}.txt"
finallog="$tmparoundfolder2/${namelog}_alentours.txt"
#./1analyse_new_logryzom.sh $sourcelog $finallog
# Analyse des logs proprement dites
# Canaux à garder
# Alentours + Endroit où on est
grep "$channels" "$sourcelog" > templog.txt
# vire le timestamp de début des lignes. Garde le * si ça peut servir à nettoyer le sys.info..
cut -b 21- templog.txt > templog2.txt
# Enlève les couleurs des canaux
sed 's/@{[A-F0-9]\{4\}}//g' templog2.txt > templog3.txt
# enlever le nom des canaux :
sed -re 's:^\([A-Z/]+\) +\* +(.*)$:\1:' templog3.txt > templog4.txt
# laisser uniquement les phrases traduites (attention, ça peut être étrange)
sed -re 's#\{:[a-zA-Z]{2}:[^}]+\}@\{[[:space:]]##' templog4.txt > templog5.txt
# Remplacer "vous dites" par "Pseudo dit"
sed s/"Vous dites :"/"$pseudo dit :"/g templog5.txt > templog6.txt
sed s/"Vous criez"/"$pseudo crie :"/g templog6.txt > templog7.txt
# Si quelqu'un joue avec des caractères bizarres, ça remet tout d'aplomb
iconv -t utf-8 -c templog7.txt > "$finallog"
# Enlever les fichiers temporaires
rm templog*.txt
#echo "$sourcelog a été traité en tant que $finallog" #très bavard
done
# Retrier par année les fichiers nettoyés
for faround in $tmparoundfolder2/*.txt
do
# penser à compter les 10 caractères "alentours" pour que la variable marche
year=${faround: -24:4}
month=${faround: -19:2}
day=${faround: -16:2}
# On se fait une jolie variable qui imite la partie datée du nom des fichiers de log
finallogsorted=log_"$perso"_"${year}_${month}_${day}_alentours.txt"
# on créé le dossier de chaque mois s'il n'existe pas
mkdir -p "$folderaround"/"$year"/"$month/"
# on déplace ces logs dans leur archive
mv "$tmparoundfolder2"/"$finallogsorted" "$folderaround"/"$year"/"$month"/"$finallogsorted"
done
#############
# Dernier ménage #
#############
# Nettoyage
rm -r "$tmpfolder"
# On compte et vérifie les fichiers
count=$(find $folderrawsorted -maxdepth 4 -name '*.txt' | wc -l)
echo "Les sources sont triées en $count fichiers dans $folderrawsorted. Contrôlez ce nombre sur controlline_$perso.txt"
countaround=$(find $folderaround -maxdepth 4 -name '*.txt' | wc -l)
echo "$countaround fichiers ont été nettoyés et rangés dans $folderaround. "
if [ "$count" = "$countaround" ]
then echo "Il semble y avoir le bon nombre de fichiers traités entre les bruts triés et les log nettoyés" ;
else echo "Attention, il y a une incohérence dans le nombre de fichiers traités entre les bruts triés et les log nettoyés" ;
fi

4
erase_all.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
# Dernière révision : 2022/02/14
# Erase_all remet le dossier "à vide". Attention ça efface comme un bourrin.
rm -r ./alentours ./sources_tri ./sources_brutes/* controlline_*.txt

539
main.py
View file

@ -1,539 +0,0 @@
#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tk_tooltip import CreateToolTip
import re
import os
import platform
channel_names = [
"SAY", # 0
"SHOUT", # 1
"TEAM", # 2
"GUILD", # 3
# "CIVILIZATION", # Unused
# "TERRITORY", # Unused
"UNIVERSE", # 4
"TELL", # 5
# "PLAYER", # Unused
# "ARROUND", # Unused
"REGION", # 6
"DYN0", # 7
"DYN1", # 8
"DYN2", # 9
"DYN3", # 10
"DYN4", # 11
"EMOTES", # 12
"SYSTEM", # 13
]
system_info_categories = [
("SYS", "Default system messages"),
("BC", "Broadcast messages"),
("TAGBC", "Tagged Broadcast messages"),
("XP", "XP Gain"),
("SP", "SP Gain"),
("TTL", "Title"),
("TSK", "Task"),
("ZON", "Zone"),
("DG", "Damage to me"),
("DMG", "Damage to me"),
("DGP", "Damage to me from player"),
("DGM", "Damage from me"),
("MIS", "Opponent misses"),
("MISM", "I miss"),
("ITM", "Item"),
("ITMO", "Item other in group"),
("ITMF", "Item failed"),
("SPL", "Spell to me"),
("SPLM", "Spell from me"),
("EMT", "Emote"),
("MTD", "Message of the day"),
("FORLD", "Forage locate deposit"),
("CHK", "Failed check"),
("CHKCB", "Failed check in combat"),
("PVPTM", "PVP Timer"),
("THM", "Thema finished (encyclopedia)"),
("AMB", "Ambiance (Occupation)"),
("ISE", "Item special effect"),
("ISE2", "Item special effect centered text"),
("OSM", "Outpost state message"),
("AROUND", "Around channel system message"),
("R2_INVITE", "Ring invitation"),
]
say_1st_to_3rd_person = {
"Vous": " dit ",
"You": " says",
"Du": " sagt",
}
class GUI:
color_regex = re.compile('@\{[A-F0-9]{4}\}')
def __init__(self):
self.originalfolderlog = ''
self.keep_color = False
self.keep_channel = False
self.keep_lang_flag = False
self.keep_original_part = False
self.keep_translated_part = False
self.keep_timestamp = False
self.replace_charname = False
self.window = tk.Tk()
self.window.title("Ryzom Log Cleaner")
self.window.columnconfigure(0, weight=1)
#self.window.rowconfigure([0,1], minsize=50)
self.ntb_file_selection = ttk.Notebook(self.window)
### Single file tab
self.frm_single_file = tk.Frame(self.window)
self.frm_single_file.columnconfigure(0, weight=1)
btn_input = tk.Button(self.frm_single_file, text="Input file", command=self.get_input_filepath)
self.ent_input = tk.Entry(self.frm_single_file, text="Input path")
# ent_input.insert(0, "~/log.txt")
btn_output = tk.Button(self.frm_single_file, text="Output file", command=self.get_output_filepath)
self.ent_output = tk.Entry(self.frm_single_file, text="Output path")
# ent_output.insert(0, "~/cleaned/")
self.frm_charname = tk.Frame(self.frm_single_file)
lbl_charname = tk.Label(self.frm_charname, text="Char name:")
self.ent_charname = tk.Entry(self.frm_charname, text="Charname")
self.ent_charname.insert(0, "Select input file to auto-fill")
btn_input.grid(row=0, column=1, sticky='ew')
self.ent_input.grid(row=0, column=0, sticky='ew')
btn_output.grid(row=1, column=1, sticky='ew')
self.ent_output.grid(row=1, column=0, sticky='ew')
self.frm_charname.grid(row=2, column=0, columnspan=2, sticky='ew')
self.frm_charname.columnconfigure(1, weight=1)
lbl_charname.grid(row=0, column=0)
self.ent_charname.grid(row=0, column=1, sticky='ew')
### Multi file tab
self.frm_multi_file = tk.Frame(self.window)
self.frm_multi_file.columnconfigure(0, weight=1)
btn_input_multi = tk.Button(self.frm_multi_file, text="Input files", command=self.get_input_filepaths)
self.ent_input_multi = tk.Entry(self.frm_multi_file, text="Input paths")
# ent_input.insert(0, "~/log.txt")
btn_output_multi = tk.Button(self.frm_multi_file, text="Output directory", command=self.get_output_directory)
self.ent_output_multi = tk.Entry(self.frm_multi_file, text="Output paths")
# ent_output.insert(0, "~/cleaned/")
self.frm_charname_multi = tk.Frame(self.frm_multi_file)
lbl_charname_multi = tk.Label(self.frm_charname_multi, text="Char name (detected from file name of each file if empty):")
self.ent_charname_multi = tk.Entry(self.frm_charname_multi, text="Charname Multi")
btn_input_multi.grid(row=0, column=1, sticky='ew')
self.ent_input_multi.grid(row=0, column=0, sticky='ew')
btn_output_multi.grid(row=1, column=1, sticky='ew')
self.ent_output_multi.grid(row=1, column=0, sticky='ew')
self.frm_charname_multi.grid(row=2, column=0, columnspan=2, sticky='ew')
self.frm_charname_multi.columnconfigure(1, weight=1)
lbl_charname_multi.grid(row=0, column=0)
self.ent_charname_multi.grid(row=0, column=1, sticky='ew')
### Organise logs tab
self.frm_organise_logs = tk.Frame(self.window)
self.frm_organise_logs.columnconfigure(0, weight=1)
btn_input_orgalogs = tk.Button(self.frm_organise_logs, text="Input directory", command=self.get_input_dir_orgalogs)
self.ent_input_orgalogs = tk.Entry(self.frm_organise_logs, text="Input dir")
# ent_input.insert(0, "~/log.txt")
btn_output_orgalogs = tk.Button(self.frm_organise_logs, text="(Empty) Output directory", command=self.get_output_dir_orgalogs)
self.ent_output_orgalogs = tk.Entry(self.frm_organise_logs, text="Output dir")
# ent_output.insert(0, "~/cleaned/")
self.frm_charname_orgalogs = tk.Frame(self.frm_organise_logs)
lbl_charname_orgalogs = tk.Label(self.frm_charname_orgalogs, text="Char name (detected from file name of each file if empty):")
self.ent_charname_orgalogs = tk.Entry(self.frm_charname_orgalogs, text="Charname Orgalogs")
btn_input_orgalogs.grid(row=0, column=1, sticky='ew')
self.ent_input_orgalogs.grid(row=0, column=0, sticky='ew')
btn_output_orgalogs.grid(row=1, column=1, sticky='ew')
self.ent_output_orgalogs.grid(row=1, column=0, sticky='ew')
self.frm_charname_orgalogs.grid(row=2, column=0, columnspan=2, sticky='ew')
self.frm_charname_orgalogs.columnconfigure(1, weight=1)
lbl_charname_orgalogs.grid(row=0, column=0)
self.ent_charname_orgalogs.grid(row=0, column=1, sticky='ew')
self.btn_organise = tk.Button(self.frm_organise_logs, text="Organise!", command=self.organise_logs)
self.btn_organise.grid(row=10, column=0, columnspan=2, sticky='ew')
### Setup tab notebook
self.ntb_file_selection.add(self.frm_single_file, text="Single file input/output")
self.ntb_file_selection.add(self.frm_multi_file, text="Multi file input/output")
self.ntb_file_selection.add(self.frm_organise_logs, text="Organise logs")
self.ntb_file_selection.grid(row=0, column=0, columnspan=2, sticky='ew', ipady='2.5')
def on_tab_change(event):
tab = event.widget.tab('current')['text']
if tab == "Single file input/output":
self.btn_process.config(command=self.process_file)
self.frm_process_settings.grid(in_=self.frm_single_file)
elif tab == "Multi file input/output":
self.btn_process.config(command=self.process_files)
self.frm_process_settings.grid(in_=self.frm_multi_file)
self.ntb_file_selection.bind('<<NotebookTabChanged>>', on_tab_change)
self.frm_process_settings = tk.Frame(self.window)
self.frm_process_settings.grid(row=5, column=0, columnspan=2, sticky='ew', in_=self.frm_single_file)
self.frm_process_settings.columnconfigure([0,1], weight=1)
sep_general_settings = ttk.Separator(self.frm_process_settings, orient='horizontal')
sep_general_settings.grid(row=5, column=0, columnspan=2, sticky='ew', pady='5')
### General settings
self.frm_general_settings = tk.Frame(self.frm_process_settings)
self.frm_general_settings.grid(row=6, column=0, columnspan=2, sticky='ew', pady='5')
self.frm_general_settings.columnconfigure([0,1,2], weight=1)
self.btn_keep_color = self.create_toggle_button(self.frm_general_settings, "Keep color", self.toggle_setting("keep_color"))
self.btn_keep_channel = self.create_toggle_button(self.frm_general_settings, "Keep channel name", self.toggle_setting("keep_channel"))
self.btn_keep_lang_flag = self.create_toggle_button(self.frm_general_settings, "Keep language flag", self.toggle_setting("keep_lang_flag"))
self.btn_keep_original_part = self.create_toggle_button(self.frm_general_settings, "Keep original text", self.toggle_setting("keep_original_part"))
self.btn_keep_translated_part = self.create_toggle_button(self.frm_general_settings, "Keep translated text", self.toggle_setting("keep_translated_part"))
self.btn_keep_timestamp = self.create_toggle_button(self.frm_general_settings, "Keep timestamp", self.toggle_setting("keep_timestamp"))
self.btn_replace_charname = self.create_toggle_button(self.frm_general_settings, "Replace charname", self.toggle_setting("replace_charname"))
self.btn_keep_color.grid(row=0, column=0, sticky='ew')
self.btn_keep_channel.grid(row=0, column=1, sticky='ew')
self.btn_keep_lang_flag.grid(row=0, column=2, sticky='ew')
self.btn_keep_original_part.grid(row=1, column=0, sticky='ew')
self.btn_keep_translated_part.grid(row=1, column=1, sticky='ew')
self.btn_keep_timestamp.grid(row=1, column=2, sticky='ew')
self.btn_replace_charname.grid(row=2, column=0, sticky='ew')
sep_channels = ttk.Separator(self.frm_process_settings, orient='horizontal')
sep_channels.grid(row=10, column=0, columnspan=2, sticky='ew')
lbl_channel_select = tk.Label(self.frm_process_settings, text="Select channels to keep:")
lbl_channel_select.grid(row=11, column=0, sticky='w')
self.btn_chan_toggle = []
self.frm_btn_channel = tk.Frame(self.frm_process_settings)
self.frm_btn_channel.grid(row=12, column=0, columnspan=2, sticky='ew')
self.frm_btn_channel.columnconfigure([0,1,2,3], weight=1)
self.frm_btn_channel.rowconfigure([0,1,2], weight=1)
for i,name in enumerate(channel_names):
self.btn_chan_toggle.append(self.create_toggle_button(self.frm_btn_channel, name, self.toggle_channel(i)))
self.btn_chan_toggle[i].grid(row=int(i/4), column=i%4, sticky='ew')
self.btn_sys_toggle = []
self.frm_btn_sys = tk.Frame(self.frm_process_settings, bg="#808080", borderwidth=5)
self.frm_btn_sys.grid(row=13, column=0, columnspan=2, sticky='e')
self.frm_btn_sys.columnconfigure([0,1,2,3,4,5,6], weight=1)
self.frm_btn_sys.rowconfigure([0,1,2,3,4], weight=1)
for i,(name,tooltip) in enumerate(system_info_categories):
self.btn_sys_toggle.append(self.create_toggle_button(self.frm_btn_sys, name, self.toggle_system(i)))
self.btn_sys_toggle[i].grid(row=int(i/7), column=i%7, sticky='ew')
ttp_sys = CreateToolTip(self.btn_sys_toggle[i], tooltip)
self.btn_process = tk.Button(self.frm_process_settings, text="Process!", command=self.process_file)
self.btn_process.grid(row=20, column=0, columnspan=2, sticky='ew')
# Default selection
self.chan_toggle = [False] * len(channel_names)
self.sys_toggle = [False] * len(system_info_categories)
self.toggle_channel(0)()
self.toggle_channel(1)()
self.toggle_channel(12)()
self.toggle_channel(13)()
self.toggle_system(7)()
self.toggle_setting("keep_translated_part")()
def create_toggle_button(self, parent, text, command):
btn = None
if platform.system() == "Darwin":
btn = tk.Label(parent, text=text, bg="#ffcccb", relief='raised')
btn.bind("<Button-1>",lambda e:command())
btn.grid(padx=1, pady=1, ipady=3)
else:
btn = tk.Button(parent, text=text, command=command, bg="#ffcccb")
return btn
def toggle_setting(self, setting):
def tgl_pref():
if getattr(self, setting):
getattr(self, f"btn_{setting}").config(relief="raised", bg="#ffcccb")
setattr(self, setting, False)
else:
getattr(self, f"btn_{setting}").config(relief="sunken", bg="#99e599")
setattr(self, setting, True)
return tgl_pref
def get_input_filepath(self):
filepath = filedialog.askopenfilename(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
initialdir=os.path.dirname(self.ent_input.get())
)
if not filepath:
return
self.ent_input.delete(0, tk.END)
self.ent_input.insert(0, filepath)
filename = os.path.basename(filepath)
name_start = filename.find("log_")
name_end = filename.find("_", name_start+4)
if name_end == -1:
name_end = filename.find(".", name_start+4)
if name_start != -1:
self.ent_charname.delete(0, tk.END)
self.ent_charname.insert(0, os.path.basename(filepath)[name_start+4:name_end])
def get_output_filepath(self):
filepath = filedialog.asksaveasfilename(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
initialdir=os.path.dirname(self.ent_output.get())
)
if not filepath:
return
self.ent_output.delete(0, tk.END)
self.ent_output.insert(0, filepath)
def get_input_filepaths(self):
filepaths = filedialog.askopenfilenames(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
initialdir=os.path.dirname(self.ent_input_multi.get().split(';')[0])
)
if not filepaths:
return
self.ent_input_multi.delete(0, tk.END)
self.ent_input_multi.insert(0, ';'.join(filepaths))
def get_output_directory(self):
directory = filedialog.askdirectory(
initialdir=self.ent_output_multi.get()
)
if not directory:
return
self.ent_output_multi.delete(0, tk.END)
self.ent_output_multi.insert(0, directory)
def get_input_dir_orgalogs(self):
directory = filedialog.askdirectory(
initialdir=self.ent_input_orgalogs.get()
)
if not directory:
return
self.ent_input_orgalogs.delete(0, tk.END)
self.ent_input_orgalogs.insert(0, directory)
def get_output_dir_orgalogs(self):
directory = filedialog.askdirectory(
initialdir=self.ent_output_orgalogs.get()
)
if not directory:
return
self.ent_output_orgalogs.delete(0, tk.END)
self.ent_output_orgalogs.insert(0, directory)
def toggle_channel(self, index):
def tgl_chan():
if self.chan_toggle[index]:
self.btn_chan_toggle[index].config(relief="raised", bg="#ffcccb")
self.chan_toggle[index] = False
if channel_names[index] == "SYSTEM":
self.frm_btn_sys.grid_remove()
else:
self.btn_chan_toggle[index].config(relief="sunken", bg="#99e599")
self.chan_toggle[index] = True
if channel_names[index] == "SYSTEM":
self.frm_btn_sys.grid()
return tgl_chan
def toggle_system(self,index):
def tgl_sys():
if self.sys_toggle[index]:
self.btn_sys_toggle[index].config(relief="raised", bg="#ffcccb")
self.sys_toggle[index] = False
else:
self.btn_sys_toggle[index].config(relief="sunken", bg="#99e599")
self.sys_toggle[index] = True
return tgl_sys
def process_file(self):
if not self.check_path_exists("ent_input"):
return
chan_pattern = '|'.join(['\(' + name + '\)' for i,name in enumerate(channel_names[:-2]) if self.chan_toggle[i]])
if self.chan_toggle[-2]:
chan_pattern += '|\(SAY/EMT\)'
if self.chan_toggle[-1]:
if self.sys_toggle[0]:
chan_pattern += '|\(SYSTEM\)'
for i,(name,tooltip) in enumerate(system_info_categories[1:]):
if self.sys_toggle[i+1]:
chan_pattern += '|\(SYSTEM/' + name + '\)'
chan_regex = re.compile(chan_pattern)
with open(self.ent_input.get(), 'r', errors='surrogateescape') as in_file, open(self.ent_output.get(), 'w', errors='surrogateescape') as out_file:
orig_lines = 0
filtered_lines = 0
self.btn_process["text"] = "Started Processing..."
for line in in_file:
orig_lines += 1
if chan_regex.search(line) == None:
continue
if not self.keep_color:
line = self.color_regex.sub('',line)
if not self.keep_channel:
line = line[:20] + line[line.find(') * ')+1:]
if not self.keep_timestamp:
line = line[20:]
original_start = line.find('{:')
if original_start != -1 and not self.keep_lang_flag:
line = line[:original_start+1] + line[original_start+5:]
original_end = line.find('}@{')
if original_end != -1 and not self.keep_translated_part:
line = line[:original_end+4] + '\n'
if original_end != -1 and not self.keep_original_part:
original_text_start = original_start + (5 if self.keep_lang_flag else 1)
line = line[:original_text_start] + line[original_end:]
if original_end != -1:
original_end = line.find('}@{')
line = line[:original_start] + line[original_start+1:original_end] + line[original_end+4:]
if self.replace_charname:
char_name_start = line.find(' * ') + 3 if not self.keep_color or line.find('}') == -1 else line.find('}') + 1
if line[char_name_start] == '[' and line[char_name_start+2] == ']':
char_name_start += 3
char_name_end = line.find(':', char_name_start)
char_talks_end = char_name_end
char_name_end -= len(line[:char_name_end].rstrip().split(' ')[-1]) + 1 + (len(line[:char_name_end])-len(line[:char_name_end].rstrip()))
char_name = line[char_name_start:char_name_end]
if char_name in say_1st_to_3rd_person:
line = line[:char_name_start] + ' '.join([s.capitalize() for s in self.ent_charname.get().split(' ')]) + say_1st_to_3rd_person[char_name] + line[char_talks_end:]
line = line[:line.find(' * ')] + line[line.find(' * ')+3:]
# channel_name = line[21:line.find(')')]
out_file.write(line.lstrip())
filtered_lines += 1
self.btn_process["text"]="Processing done! (" + str(filtered_lines) + " lines kept out of " + str(orig_lines) + " original lines)"
def process_files(self):
input_filepaths = self.ent_input_multi.get().split(';')
for filepath in input_filepaths:
filename = os.path.basename(filepath)
self.ent_input.delete(0, tk.END)
self.ent_input.insert(0, filepath)
self.ent_output.delete(0, tk.END)
self.ent_output.insert(0, os.path.join(self.ent_output_multi.get(), filename))
if len(self.ent_charname_multi.get()) == 0:
name_start = filename.find("log_")
name_end = filename.find("_", name_start+4)
if name_end == -1:
name_end = filename.find(".", name_start+4)
if name_start != -1:
self.ent_charname.delete(0, tk.END)
self.ent_charname.insert(0, os.path.basename(filepath)[name_start+4:name_end])
else:
self.ent_charname.delete(0, tk.END)
self.ent_charname.insert(0, self.ent_charname_multi.get())
self.process_file()
def organise_logs(self):
if not self.check_path_exists("ent_output_orgalogs") or not self.check_path_exists("ent_input_orgalogs"):
return
self.my_pathes = {}
for dirpath, _, filenames in os.walk(self.ent_input_orgalogs.get()):
for f in filenames:
with open(os.path.join(dirpath, f), 'r', errors='surrogateescape') as in_f:
last_year = -1
last_month = -1
last_day = -1
out_f = None
if len(self.ent_charname_orgalogs.get()) == 0:
name_start = f.find("log_")
name_end = f.find("_", name_start+4)
if name_end == -1:
name_end = f.find(".", name_start+4)
if name_start != -1:
self.ent_charname_orgalogs.delete(0, tk.END)
self.ent_charname_orgalogs.insert(0, f[name_start+4:name_end])
charname = self.ent_charname_orgalogs.get().lower()
manual_check_p = self.make_path_safe(os.path.join(self.ent_output_orgalogs.get(), f"log_{charname}_manual_check.log"))
manual_check_f = open(manual_check_p, 'a', errors='surrogateescape')
for line in in_f:
year_end = line.find('/')
unclassified = False
year = month = day = 0
if year_end == -1 or year_end+3 >= len(line) or line[year_end+3] != '/':
if last_year != -1:
year, month, day = last_year, last_month, last_day
else:
unclassified = True
else:
try:
year = int(line[:year_end])
month = int(line[year_end+1:year_end+3])
day = int(line[year_end+4:year_end+6])
except ValueError:
unclassified = True
if not unclassified and (year != last_year or month != last_month or day != last_day):
if out_f != None:
out_f.close()
out_p = self.make_path_safe(os.path.join(self.ent_output_orgalogs.get(), charname, f"{year:04d}", f"{month:02d}", f"log_{charname}_{year:04d}_{month:02d}_{day:02d}.log"))
out_f = open(out_p, 'a', errors='surrogateescape')
if unclassified:
manual_check_f.write(line)
else:
out_f.write(line)
last_year = year
last_month = month
last_day = day
if out_f != None:
out_f.close()
manual_check_f.close()
def make_path_safe(self, path):
if path in self.my_pathes:
return self.my_pathes[path]
(dirpath, filename) = os.path.split(path)
(basefile, ext) = os.path.splitext(filename)
if ext == '':
os.makedirs(path, exist_ok=True)
self.my_pathes[path] = path
return path
else:
os.makedirs(dirpath, exist_ok=True)
counter = 1
new_path = path
while os.path.exists(new_path):
new_path = os.path.join(dirpath, f"{basefile}_{counter}{ext}")
counter += 1
self.my_pathes[path] = new_path
return new_path
def check_path_exists(self, attribute):
if not hasattr(self, attribute) or getattr(self, attribute) == None or not os.path.exists(getattr(self, attribute).get()):
tk.messagebox.showerror(title="Error in files selection", message=f"Error with input/output files. Make sur the input/output files or directories are set and exist")
return False
return True
if __name__ == '__main__':
gui = GUI()
gui.window.mainloop()

View file

@ -1,86 +0,0 @@
""" tk_ToolTip_class101.py
gives a Tkinter widget a tooltip as the mouse is above the widget
tested with Python27 and Python34 by vegaseat 09sep2014
www.daniweb.com/programming/software-development/code/484591/a-tooltip-class-for-tkinter
Modified to include a delay time by Victor Zaccardo, 25mar16
"""
try:
# for Python2
import Tkinter as tk
except ImportError:
# for Python3
import tkinter as tk
class CreateToolTip(object):
"""
create a tooltip for a given widget
"""
def __init__(self, widget, text='widget info'):
self.waittime = 200 #miliseconds
self.wraplength = 180 #pixels
self.widget = widget
self.text = text
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
self.id = None
self.tw = None
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.waittime, self.showtip)
def unschedule(self):
id = self.id
self.id = None
if id:
self.widget.after_cancel(id)
def showtip(self, event=None):
x = y = 0
x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() + self.widget.winfo_width() - 10
y += self.widget.winfo_rooty() + self.widget.winfo_height() - 10
# creates a toplevel window
self.tw = tk.Toplevel(self.widget)
# Leaves only the label and removes the app window
self.tw.wm_overrideredirect(True)
self.tw.wm_geometry("+%d+%d" % (x, y))
label = tk.Label(self.tw, text=self.text, justify='left',
background="#ffffff", relief='solid', borderwidth=1,
wraplength = self.wraplength)
label.pack(ipadx=1)
def hidetip(self):
tw = self.tw
self.tw= None
if tw:
tw.destroy()
# testing ...
if __name__ == '__main__':
root = tk.Tk()
btn1 = tk.Button(root, text="button 1")
btn1.pack(padx=10, pady=5)
button1_ttp = CreateToolTip(btn1, \
'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, '
'consectetur, adipisci velit. Neque porro quisquam est qui dolorem ipsum '
'quia dolor sit amet, consectetur, adipisci velit. Neque porro quisquam '
'est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.')
btn2 = tk.Button(root, text="button 2")
btn2.pack(padx=10, pady=5)
button2_ttp = CreateToolTip(btn2, \
"First thing's first, I'm the realest. Drop this and let the whole world "
"feel it. And I'm still in the Murda Bizness. I could hold you down, like "
"I'm givin' lessons in physics. You should want a bad Vic like this.")
root.mainloop()