2.4 Projet : Le Parser de Scan
Il est temps de transformer pyCycloneFlow. Nous allons parser le fichier scanner.txt pour créer notre premier inventaire structuré.
1. Regex Chirurgicales
Nous utilisons le module re pour extraire proprement les données.
import re
ligne = "requests 2.28.1"
# (\S+) => Capture le premier bloc de caractères (tout ce qui n'est pas un espace, comme un nom ou une clé)
# \s+ => Ignore l'espace (ou les espaces/tabulations) entre les deux.
# ([\d.]+) => Capture le deuxième bloc composé de chiffres ou de points (typiquement un nombre entier ou décimal).
match = re.search(r"(\S+)\s+([\d.]+)", ligne)
2. Logging vs Print
Le module logging permet de tracer les événements sans polluer la sortie utilisateur.
Projet Fil Rouge : Mise à jour de main.py
Nous respectons la Réutilisation Incrémentale : on garde notre load_scan_file de la Phase 1.
import pathlib
import re
import logging
# Config Logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
def load_scan_file(file_path: str) -> str:
"""[Safe Opener de la Phase 1]"""
try:
path = pathlib.Path(file_path)
return path.read_text(encoding="utf-8")
except Exception as e:
logging.error(f"Erreur lecture : {e}")
return ""
def parse_scan_data(raw_data: str) -> list[dict]:
"""Transforme le texte brut en liste de dictionnaires."""
components = []
if not raw_data: return components
lines = raw_data.strip().split('\n')
for line in lines:
if "Package" in line or "---" in line: continue
# Regex : capture (Nom) (Espaces) (Version)
match = re.search(r"(\S+)\s+([\d\.]+)", line)
if match:
components.append({
"name": match.group(1),
"version": match.group(2)
})
return components
def main():
raw = load_scan_file("scanner.txt")
inventory = parse_scan_data(raw)
print(f"--- Inventaire pyCycloneFlow ({len(inventory)} items) ---")
for item in inventory:
print(f" [ ] {item['name']} == {item['version']}")
if __name__ == "__main__":
main()
# Résultat attendu :
$ python main.py
--- Inventaire pyCycloneFlow (3 items) ---
[ ] requests == 2.28.1
[ ] flask == 2.2.2
[ ] cryptography == 38.0.1
3. Suite de Tests Cumulative
On enrichit tests/test_opener.py avec la logique du parser.
import unittest
from main import load_scan_file, parse_scan_data
class TestCycloneFlow(unittest.TestCase):
# --- Tests de la Phase 1 (Safe Opener) ---
def test_file_not_found(self):
result = load_scan_file("fichier_imaginaire.txt")
self.assertEqual(result, "")
def test_load_success(self):
result = load_scan_file("scanner.txt")
self.assertIn("Package Version", result)
# --- Nouveaux Tests Phase 2 (Parser) ---
def test_parsing_logic(self):
sample = "my-lib 1.0.0"
result = parse_scan_data(sample)
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['name'], "my-lib")
self.assertEqual(result[0]['version'], "1.0.0")
if __name__ == '__main__':
unittest.main()
Résultat attendu :
$ python -m unittest tests.test_opener
ERROR: Fichier introuvable : fichier_imaginaire.txt
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK