commit fc70c9f9107521fbdae9ae7d1f4c539e28256916 Author: David Castex Date: Mon May 19 10:32:45 2025 +0200 Commit Initial Le script n'est pas fonctionnel, je segmente chaque fonctionnalité et vérifie au fur et a mesure. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3dbc867 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..6101da5 --- /dev/null +++ b/README.md @@ -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) \ No newline at end of file diff --git a/banbou.py b/banbou.py new file mode 100644 index 0000000..16d7bd7 --- /dev/null +++ b/banbou.py @@ -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() \ No newline at end of file diff --git a/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS.pdf b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS.pdf new file mode 100644 index 0000000..2e837f8 Binary files /dev/null and b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS.pdf differ diff --git a/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.csv b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.csv new file mode 100644 index 0000000..c482114 --- /dev/null +++ b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.csv @@ -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 diff --git a/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.dwg b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.dwg new file mode 100644 index 0000000..296c56f Binary files /dev/null and b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.dwg differ diff --git a/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.pdf b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.pdf new file mode 100644 index 0000000..0989c4d Binary files /dev/null and b/test/JEUXDONNEES/DIS_AXR08_PT802161/DIS_AXR08_PT802161.pdf differ diff --git a/test/JEUXDONNEES/DIS_AXR08_PT802161/fichier a ignorer lors de la copie.txt b/test/JEUXDONNEES/DIS_AXR08_PT802161/fichier a ignorer lors de la copie.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/document01.txt b/test/JEUXDONNEES/document01.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/dossier00/docu02.txt b/test/JEUXDONNEES/dossier00/docu02.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/dossier00/fichier01.csv b/test/JEUXDONNEES/dossier00/fichier01.csv new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/dossier00/fichier02.pdf b/test/JEUXDONNEES/dossier00/fichier02.pdf new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/dossier00/fichier03.odt b/test/JEUXDONNEES/dossier00/fichier03.odt new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/dossier00/mon fichier.csv b/test/JEUXDONNEES/dossier00/mon fichier.csv new file mode 100644 index 0000000..e69de29 diff --git a/test/JEUXDONNEES/jeu_simple.csv b/test/JEUXDONNEES/jeu_simple.csv new file mode 100644 index 0000000..bc927e1 --- /dev/null +++ b/test/JEUXDONNEES/jeu_simple.csv @@ -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 diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_creer_liste.py b/test/test_creer_liste.py new file mode 100644 index 0000000..b751e26 --- /dev/null +++ b/test/test_creer_liste.py @@ -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 + + + + + diff --git a/test/test_fichier_afficher.py b/test/test_fichier_afficher.py new file mode 100644 index 0000000..6abf33c --- /dev/null +++ b/test/test_fichier_afficher.py @@ -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 + diff --git a/test/test_fichier_impliquer.py b/test/test_fichier_impliquer.py new file mode 100644 index 0000000..c99b21a --- /dev/null +++ b/test/test_fichier_impliquer.py @@ -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 diff --git a/test/test_fichier_lire.py b/test/test_fichier_lire.py new file mode 100644 index 0000000..4b57016 --- /dev/null +++ b/test/test_fichier_lire.py @@ -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" + + + diff --git a/test/test_formatter.py b/test/test_formatter.py new file mode 100644 index 0000000..ccd2102 --- /dev/null +++ b/test/test_formatter.py @@ -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 \ No newline at end of file diff --git a/test/test_projet_calculer_taille.py b/test/test_projet_calculer_taille.py new file mode 100644 index 0000000..b5dfe17 --- /dev/null +++ b/test/test_projet_calculer_taille.py @@ -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 + + + + + + + diff --git a/test/test_projet_enraciner.py b/test/test_projet_enraciner.py new file mode 100644 index 0000000..6cae1c8 --- /dev/null +++ b/test/test_projet_enraciner.py @@ -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 \ No newline at end of file diff --git a/test/test_projet_preparer_dossier_travail.py b/test/test_projet_preparer_dossier_travail.py new file mode 100644 index 0000000..d705761 --- /dev/null +++ b/test/test_projet_preparer_dossier_travail.py @@ -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 \ No newline at end of file