Commit Initial

Le script n'est pas fonctionnel, je segmente chaque fonctionnalité
et vérifie au fur et a mesure.
This commit is contained in:
David Castex 2025-05-19 10:32:45 +02:00
commit fc70c9f910
24 changed files with 620 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Je ne commit pas l'environnement de dev, il faudra installer venv
# et les modules supp.
venv/
__pycahe__/
# Je commit pas les ZIPs des jeux de données.
*.zip

31
README.md Normal file
View File

@ -0,0 +1,31 @@
# NeoBanbou
Script Python d'assistance dans le controle des dossiers de mise en place de fibre optiques.
## Lancer les Tests
Pour lancer les tests, il faudra préalablement installer un environnement de travail isolé
```bash
python -m venv venv
```
J'ai utiliser Pytest pour les tests et son module pytest-mock
```bash
pip install pytest
pip install pytest-mock
```
Les tests sont situés dans le dossier `test/` pour les lancer tous, depuis la racine du projet :
```
pytest test/
```
## Auteurs
- [@david.castex](https://gitea.digitanie.org/david.castex)

312
banbou.py Normal file
View File

@ -0,0 +1,312 @@
## Script Banbou pour le prétraitement des données des dossiers d'installation
## de fibre optique.
import os, shutil
# DEFINITIONS DE FONCTIONS
def formatter(chaine):
"""Formate selon nomenclature.
Formate la chaine de caractères passée en paramètre :
Enlève tous les accents français. Enlève la cédille du C.
Remplace les espaces ' ' et les traits d'union '-' par
des tirets bas '_'.
Ne traite pas pour le moment le AE et OE ligaturé.
Si plusieurs '_' se suivent, les réduire à un seul.
"""
#TODO : gérer les accents sur majuscules
#TODO : amélioration ou exercice, utiliser la méthode str.translate() et maketrans
resultat = ""
precedent = None
for c in chaine:
match c:
case "à" | "â" | "ä":
resultat+= "a"
case "é" | "è" | "ê" | "ë":
resultat+= "e"
case "î" | "ï":
resultat+= "i"
case "ô" | "ö":
resultat+= "o"
case "ù" | "û" | "ü":
resultat+= "u"
case "ÿ":
resultat+= "y"
case "ç":
resultat+= "c"
case " " | "-" | "_":
if('_' not in precedent ):
resultat+= "_"
c = "_"
case _:
resultat+= c
precedent = c
return resultat
def creer_liste(dossier):
"""
Construit une liste avec les fichiers de 'dossier'
Parcourt le dossier et ses sous-dossier, ajoute les fichiers
dans une liste de _Fichier.
Chaque _Fichier ajouté a ses attributs mis a jour.
Retourne la liste.
:param user_input: nom complet du dossier
:return: liste d'élements de type _Fichier
"""
liste = []
for dossier_courant, list_sousdossiers, list_fichiers in os.walk(dossier):
for fichier_courant in list_fichiers:
ce_Fichier = _Fichier.lire(dossier_courant, fichier_courant)
liste.append(ce_Fichier)
return liste
# REPRÉSENTATION DES DONNÉES
class _Fichier:
def __init__(self,
nom_original="Pas de nom original",
chemin="Pas de chemin",
extension="Pas d'extension",
nom= "Pas de nom",
implication="Non-conforme",
taille=0):
self.nom_original = nom_original # - son nom original
self.chemin = chemin # - son chemin
self.extension = extension # - son extension
self.nom = nom # - son nom formaté
self.implication = implication # - son implication dans le projet
self.taille = taille
def afficher(self):
"""
Affiche dans la sortie standard les éléments du fichier
"""
print("nom :".ljust(16) + self.nom)
print("nom orig :".ljust(16) + self.nom_original)
print("ext :".ljust(16) + self.extension)
print("chemin :".ljust(16) + self.chemin)
print("implication :".ljust(16) + self.implication)
print("taille :".ljust(16) + str(self.taille) )
def lire(dossier, fichier):
"""
Lit le nom du fichier et du dossier
Construit un élement _Fichier et met à jour tous ses attributs
:param user_input: chemin absolue du dossier, nom du fichier
:return: un element _Fichier
"""
# initialiser un _Fichier
ce_Fichier = _Fichier()
# lire son chemin
ce_Fichier.chemin = dossier + "\\" + fichier
# déterminer son nom original et son extension
ce_Fichier.nom_original, ce_Fichier.extension = fichier.split(".", maxsplit=1) # maxsplit permet de spliter qu'une fois au cas ou on a plusieurs . dans le nom de fichier)
# formatter et écrire le nom
ce_Fichier.nom = formatter(ce_Fichier.nom_original)
# déterminer son implication
ce_Fichier.impliquer()
# calculer sa taille
ce_Fichier.taille = os.path.getsize(ce_Fichier.chemin)
return ce_Fichier
def impliquer(self):
"""Définir l'implication d'un fichier
Les fichiers nécessaires seront copiés dans le répertoire "Travail"
L'implication est définie en fonction de l'extension du fichier.
Les fichiers nécessaires sont les DWGs, les CSVs pour les datas.
Pour les shémas et relevés de topo :
- PDFs, DOCs, ODTs (Doc LibreOffice),
"""
match self.extension:
case "pdf" | "dwg" | "csv" | "doc" | "odt" | "PDF" | "DWG" | "CSV" | "DOC" | "ODT":
self.implication = "Necessaire"
case _:
self.implication = "A-ignorer"
class _Projet:
def __init__(self,
nom="Pas de nom",
date="Pas de date",
racine="Pas de chemin",
liste=[],
nb_fichiers=0,
taille=0,
rapport="Pas de fichier",
nb_shemas=0,
nb_releves=0,
nb_csv=0,
nb_dwgs=0):
self.nom = nom # nom du projet
self.date = date # date du traitement
self.racine = racine # chemin racine du projet
self.liste = liste # liste de _Fichier
self.nb_fichiers = nb_fichiers # nb de fichiers dans "Travail"
self.taille = taille # taille des fichiers dans "Travail"
self.rapport = rapport # chemin vers le visa
self.nb_shemas = nb_shemas # nb de plans de la mise en place
self.nb_releves = nb_releves # nb de rapports de relevés topo
self.nb_csv = nb_csv # nb de fichiers CSV
self.nb_dwgs = nb_dwgs # nb de fichiers DWG
def enraciner(self):
"""
récupère le repertoire de travail (working directory) courant
met à jour l'attribut 'racine'
"""
self.racine = os.getcwd()
print("Racine : ".ljust(16), f"{self.racine}\n")
def calculer_taille(self):
"""
calcule la taille des fichiers necessaires d'un liste
d'élements _Fichier.
Met à jour l'attribut 'taille' dans le projet
"""
taille = 0
for courant in self.liste:
if courant.implication in "Necessaire":
taille += courant.taille
self.taille = taille
print(f"Taille totale : {self.taille / 1024**2:.2} Mo.\n")
def dater_projet(self):
"""
recupère la date du jour
met à jour l'attribut 'date' du projet
"""
self.date = datetime.datetime.today().strftime('%Y%m%d')
def nommer_projet(self):
"""
met à jour l'attribut 'nom' en composant un nom.
Le nom est constitué du nom du dossier racine et de la date
courante formatté.
"""
self.nom = f"{os.path.basename(os.getcwd())}_{self.date}"
def preparer_dossier_travail(self):
"""
Créer un dossier "Travail" dans la racine du working directory et
le peuple des fichiers nécessaires
"""
travail = "Travail"
# création du dossier "Travail et dossier"
chemin = f"{self.racine}\\{travail}"
print(f"CHEMIN FABRIQUE : {chemin}")
try:
os.mkdir(chemin)
print (f'Dossier "{travail}" créé.')
except FileExistsError as erreur:
print(f'Avertissement: Le dossier "{travail}" existe déja.')
except OSError as err:
print(f"Fichier non trouvé. Surement un pb de chemin en amont.")
# peuplement du dossier Travail avec les fichiers necessaires
for fichier in self.liste:
print("TEST001")
if fichier.implication in "Necessaire":
print("TEST002")
shutil.copyfile(fichier.chemin + "\\" + fichier.nom_original, self.racine + "\\" + travail + "\\" + fichier.nom)
# TODO surcharger la fonction print native pour cet affichage
def afficher(liste):
"""
Affiche le nom des fichiers de la liste des _Fichiers
"""
for courant in liste:
print(courant.nom + "." + courant.extension)
# MAIN
racine = os.getcwd()
print("Répertoire courant : ".center(18), racine)
# --recupérer le chemin du dossier à traiter
pas_de_dossier = True
for a in os.scandir():
print("courant scandir() = ", a, " ", a.is_dir())
if a.is_dir():
#il y a un dossier
pas_de_dossier = False
print(f"Dossier à Traiter trouvé : {a.name}")
racine = a
if pas_de_dossier:
print("Pas de dossier trouvé...\nFin de programme\n")
os.system("pause")
# fin du programme
exit()

Binary file not shown.

View File

@ -0,0 +1,9 @@
1007;TACE;1604957,07;2169690,34;715,80
1008;TEIC;1604959,44;2169692,70;716,01
1009;TEIC;1604960,25;2169693,85;716,07
1010;TEIC;1604959,70;2169694,24;715,99
1011;TEIC;1604958,87;2169693,10;715,94
1012;TACC;1604959,57;2169693,47;715,94
1014;RCIC;1604957,15;2169690,71;715,39
1016;RCCC;1604958,09;2169691,76;715,58
1018;RCIC;1604959,06;2169692,91;715,58
1 1007 TACE 1604957,07 2169690,34 715,80
2 1008 TEIC 1604959,44 2169692,70 716,01
3 1009 TEIC 1604960,25 2169693,85 716,07
4 1010 TEIC 1604959,70 2169694,24 715,99
5 1011 TEIC 1604958,87 2169693,10 715,94
6 1012 TACC 1604959,57 2169693,47 715,94
7 1014 RCIC 1604957,15 2169690,71 715,39
8 1016 RCCC 1604958,09 2169691,76 715,58
9 1018 RCIC 1604959,06 2169692,91 715,58

View File

View File

View File

View File

View File

View File

@ -0,0 +1,10 @@
id_point;TYPE;X;Y;Z
1007;TACE;1604957,07;2169690,34;715,80
1008;TEIC;1604959,44;2169692,70;716,01
1009;TEIC;1604960,25;2169693,85;716,07
1010;TEIC;1604959,70;2169694,24;715,99
1011;TEIC;1604958,87;2169693,10;715,94
1012;TACC;1604959,57;2169693,47;715,94
1014;RCIC;1604957,15;2169690,71;715,39
1016;RCCC;1604958,09;2169691,76;715,58
1018;RCIC;1604959,06;2169692,91;715,58
1 id_point TYPE X Y Z
2 1007 TACE 1604957,07 2169690,34 715,80
3 1008 TEIC 1604959,44 2169692,70 716,01
4 1009 TEIC 1604960,25 2169693,85 716,07
5 1010 TEIC 1604959,70 2169694,24 715,99
6 1011 TEIC 1604958,87 2169693,10 715,94
7 1012 TACC 1604959,57 2169693,47 715,94
8 1014 RCIC 1604957,15 2169690,71 715,39
9 1016 RCCC 1604958,09 2169691,76 715,58
10 1018 RCIC 1604959,06 2169692,91 715,58

0
test/__init__.py Normal file
View File

42
test/test_creer_liste.py Normal file
View File

@ -0,0 +1,42 @@
from banbou import creer_liste
from banbou import afficher
from banbou import _Fichier
def test_doit_creer_la_liste(mocker):
dossier_courant = r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\dossier00"
list_sousdossiers = []
list_fichiers = ["fichier01.csv", "fichier02.pdf"]
value = [(dossier_courant, list_sousdossiers, list_fichiers)] # os.walk renvoie bien une LISTE de tuples (chaque tuple representant le contenu du dossier courant qu'il parcourt), même si ya qu'un seul tuple
print(f"value = {value}")
mock = mocker.patch("os.walk", return_value = value)
#mock02 = mocker.patch("banbou.lire", return_value = )
sut = creer_liste(dossier_courant)
afficher(sut)
assert sut[0].nom == "fichier01"
assert sut[1].nom == "fichier02"
def test_doit_creer_liste_depuis_un_dossier_complexe(mocker, capsys):
# j'ai pas mocker le dossier, c'est un vrai dossier utilisé pour faire le test
dossier = r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\DIS_AXR08_PT802161"
sut = creer_liste(dossier)
afficher(sut)
out, err = capsys.readouterr()
expected_out = "DIS.pdf\n" + "DIS_AXR08_PT802161.csv\n" + "DIS_AXR08_PT802161.dwg\n" + "DIS_AXR08_PT802161.pdf\n" + "fichier_a_ignorer_lors_de_la_copie.txt\n"
assert out == expected_out

View File

@ -0,0 +1,36 @@
from banbou import _Fichier
__nom = "nom :".ljust(16)
__nom_orig = "nom orig :".ljust(16)
__ext = "ext :".ljust(16)
__chemin = "chemin :".ljust(16)
__implication = "implication :".ljust(16)
__taille = "taille :".ljust(16)
def test_doit_afficher_un_fichier(mocker, capsys):
sut = _Fichier("mon fichier", "/home/david/projet01", "csv", "mon_fichier", "Necessaire", 300)
# declenche l'affichage dans stdout
sut.afficher()
#capture l'affichage de stdout
out, err = capsys.readouterr()
expected_out = __nom + "mon_fichier\n" + __nom_orig + "mon fichier\n" + __ext + "csv\n" + __chemin + "/home/david/projet01\n" + __implication + "Necessaire\n" + __taille + "300\n"
assert out == expected_out
def test_doit_afficher_un_fichier_avec_des_champs_vides(mocker, capsys):
sut = _Fichier()
sut.afficher()
out, err = capsys.readouterr()
expected_out = __nom + "Pas de nom\n" + __nom_orig + "Pas de nom original\n" + __ext + "Pas d'extension\n" + __chemin + "Pas de chemin\n" + __implication + "Non-conforme\n" + __taille + "0\n"
assert out == expected_out

View File

@ -0,0 +1,34 @@
from banbou import _Fichier
def test_doit_impliquer_necessaire(mocker):
sut = _Fichier()
sut.extension = "pdf"
sut.impliquer()
expected_value = "Necessaire"
assert sut.implication == expected_value
def test_doit_impliquer_majuscule(mocker):
sut = _Fichier()
sut.extension = "DWG"
sut.impliquer()
expected_value = "Necessaire"
assert sut.implication == expected_value
def test_doit_impliquer_a_ignorer(mocker):
sut = _Fichier()
sut.extension = "txt"
sut.impliquer()
expected_value = "A-ignorer"
assert sut.implication == expected_value

20
test/test_fichier_lire.py Normal file
View File

@ -0,0 +1,20 @@
from banbou import _Fichier
def test_doit_lire_un_fichier(mocker):
param01, param02 = (r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\dossier00", "mon fichier.csv")
value = "mon_fichier"
mock = mocker.patch("banbou.formatter", return_value = value )
sut = _Fichier.lire(param01, param02)
assert isinstance(sut, _Fichier) == True
assert sut.nom == "mon_fichier"
assert sut.nom_original == "mon fichier"
assert sut.extension == "csv"
assert sut.chemin == r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\dossier00\mon fichier.csv"

28
test/test_formatter.py Normal file
View File

@ -0,0 +1,28 @@
from banbou import formatter
def test_enleve_les_accents(mocker):
original = "äàâ"+ "ëéèê" + "îï" + "ôö" + "ùûü" + "ÿ" + "ç"
expected_value = "aaaeeeeiioouuuyc"
out = formatter(original)
assert out == expected_value
def test_remplace_les_tirets_et_espaces(mocker):
original = "A-Z-E-R T Y UIOP "
expected_value = "A_Z_E_R_T_Y_UIOP_"
out = formatter(original)
assert out == expected_value
def test_fusionne_les_espaces_et_les_tirets_successifs(mocker):
original = "A Z--E R____TY-----UIOP "
expected_value = "A_Z_E_R_TY_UIOP_"
out = formatter(original)
assert out == expected_value

View File

@ -0,0 +1,60 @@
from banbou import _Projet
import banbou
def test_doit_calculer_la_taille_du_projet(mocker):
class MockResponse:
def __init__(self):
self.taille = 40
self.implication = "Necessaire"
mocker.patch("banbou._Fichier", return_value = MockResponse())
liste = [banbou._Fichier()]
sut = _Projet()
sut.liste = liste
expected_value = 40
sut.calculer_taille()
assert sut.taille == expected_value
def test_doit_calculer_une_liste_vide(mocker):
liste = []
sut = _Projet()
sut.liste = liste
expected_value = 0
sut.calculer_taille()
assert sut.taille == expected_value
def test_ne_doit_pas_calculer_fichier_non_necessaire(mocker):
class MockResponse:
def __init__(self):
self.taille = 40
self.implication = "A-ignorer"
mocker.patch("banbou._Fichier", return_value = MockResponse())
liste = [banbou._Fichier()]
sut = _Projet()
sut.liste = liste
expected_value = 0
sut.calculer_taille()
assert sut.taille == expected_value

View File

@ -0,0 +1,13 @@
from banbou import _Projet
def test_doit_enregistrer_le_chemin_racine(mocker):
dossier = r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\DIS_AXR08_PT802161"
mock = mocker.patch("os.getcwd", return_value = dossier )
sut = _Projet()
sut.enraciner()
assert sut.racine == dossier

View File

@ -0,0 +1,18 @@
from banbou import _Projet
import banbou
# Je n'ai pas mocker l'environnement donc prévoir d'effacer
# les anciens fichiers et dossier créés par les tests précédents
def test_doit_copier_les_fichiers_necessaires(mocker):
dossier = r"C:\Users\David_Castex\Documents\DATAS\GITDAV\NEOBANBOU\test\JEUXDONNEES\DIS_AXR08_PT802161"
sut = _Projet()
sut.racine = dossier
sut.liste = banbou.creer_liste(dossier)
sut.preparer_dossier_travail()
###ENCOURS