markdown_parser/modules/fileutils.nim
Bruno Charest 18ee8a1cfd feat: Add report generation module for tracking metadata changes
- Implemented `report.nim` to create structured reports on metadata modifications.
- Added functionality to merge reports and convert them to formatted strings.

docs: Create prompt documentation for Markdown parser project

- Added `prompt.md` detailing the requirements and functionalities for the Markdown parser.
- Included specifications, usage examples, and testing guidelines.

docs: Generate code review report for Markdown parser

- Created `rapport_revue_code.md` outlining security vulnerabilities, code quality issues, and suggested improvements.
- Provided a detailed analysis of the codebase with actionable recommendations.

test: Add test data for Markdown parser

- Included various Markdown files and a JPG image in `test_data` to simulate different scenarios.
- Ensured that the parser can handle both valid and invalid metadata.

chore: Add version management file

- Created `version.nim` for automatic versioning of the Markdown parser.
- Established constants for major, minor, patch, and build versions.
2026-04-19 12:56:55 -04:00

210 lines
7.5 KiB
Nim

# fileutils.nim
# Module responsable des opérations sur les fichiers et répertoires
import std/[os, strutils, strformat, options, tables]
import zippy/ziparchives
import metadata, report, config
proc printOutput*(output: string, debug: bool = false, appConfig: Option[AppConfig] = none(AppConfig)) =
## Affiche un message en sortie standard
## Si debug est true, n'affiche le message que si debugModeActive est activé dans la configuration
## Si debug est false, affiche toujours le message
##
## Paramètres:
## - output: Le message à afficher
## - debug: Si true, il s'agit d'un message de débogage
## - appConfig: Configuration de l'application (optionnel)
if not debug:
# Message normal, toujours affiché
echo output
else:
# Message de débogage, affiché uniquement si debugModeActive est true
if appConfig.isSome and appConfig.get().debugModeActive:
echo fmt"[DEBUG] {output}"
proc debugLog*(message: string, appConfig: AppConfig) =
## Affiche un message de débogage si le mode debug est activé
if appConfig.debugModeActive:
echo fmt"[DEBUG] {message}"
proc walkDirRecFiles*(dir: string, ext: string): seq[string] =
## Parcourt récursivement un répertoire et retourne tous les fichiers avec l'extension spécifiée
result = @[]
for kind, path in walkDir(dir):
if kind == pcDir:
result.add(walkDirRecFiles(path, ext))
elif kind == pcFile and path.endsWith(ext):
result.add(path)
proc readMarkdownFile*(filePath: string): string =
## Lit le contenu d'un fichier Markdown
try:
return readFile(filePath)
except:
echo fmt"Erreur lors de la lecture du fichier: {filePath}"
return ""
proc writeMarkdownFile*(filePath: string, content: string): bool =
## Écrit le contenu dans un fichier Markdown
try:
echo fmt"DEBUG: Tentative d'écriture dans le fichier: {filePath}"
echo fmt"DEBUG: Contenu à écrire (premiers 100 caractères): {content[0..min(99, content.len-1)]}"
let parentDir = parentDir(filePath)
if not dirExists(parentDir):
echo fmt"DEBUG: Création du répertoire parent: {parentDir}"
createDir(parentDir)
# Normalisation des retours à la ligne pour éviter les problèmes de \n littéraux
let normalizedContent = content.replace("\n", "\r\n")
echo fmt"DEBUG: Écriture de {normalizedContent.len} caractères"
writeFile(filePath, normalizedContent)
echo fmt"DEBUG: Fichier écrit avec succès"
return true
except Exception as e:
echo fmt"Erreur lors de l'écriture dans le fichier: {filePath}"
echo fmt"DEBUG: Exception: {e.msg}"
return false
proc copyFileWithDirectories*(sourcePath, targetPath: string, verbose: bool = false): bool =
## Copie un fichier de sourcePath vers targetPath en créant les répertoires
## intermédiaires si nécessaire
##
## Paramètres:
## sourcePath: Chemin source du fichier
## targetPath: Chemin destination du fichier
## verbose: Affiche des informations supplémentaires
##
## Retourne:
## true si la copie a réussi, false sinon
try:
if verbose:
echo fmt"Copie du fichier: {sourcePath} -> {targetPath}"
let parentDir = parentDir(targetPath)
if not dirExists(parentDir):
createDir(parentDir)
copyFile(sourcePath, targetPath)
return true
except Exception as e:
echo fmt"Erreur lors de la copie du fichier: {e.msg}"
return false
proc zipDir*(sourceDir: string, zipFilePath: string = "", verbose: bool = false): bool =
## Crée une archive zip du répertoire source dans un répertoire "backup"
## Si zipFilePath est vide, utilise le nom du répertoire source pour le zip
try:
let
sourceDirName = sourceDir.splitPath().tail
parentDir = sourceDir.splitPath().head
backupDir = parentDir / "backup"
# Créer le répertoire backup s'il n'existe pas
if not dirExists(backupDir):
if verbose:
echo fmt"Création du répertoire backup: {backupDir}"
createDir(backupDir)
# Déterminer le nom du fichier zip
var actualZipPath: string
if zipFilePath == "":
# Si aucun nom n'est fourni, utiliser le nom du répertoire source
actualZipPath = backupDir / fmt"{sourceDirName}.zip"
else:
# Si un nom est fourni, l'utiliser mais le placer dans le répertoire backup
actualZipPath = backupDir / zipFilePath.extractFilename()
if verbose:
echo fmt"Création d'une archive zip: {actualZipPath} à partir de {sourceDir}"
# Utilisons une approche plus simple basée sur la documentation de zippy
# Extrayons d'abord tous les fichiers avec leurs chemins relatifs
var fileEntries = initTable[string, string]()
# Fonction récursive pour ajouter les fichiers
proc addFilesRecursively(dir: string, relativePath = "") =
for kind, path in walkDir(dir):
let relPath = if relativePath.len > 0: relativePath / path.extractFilename else: path.extractFilename
if kind == pcFile:
# Ajouter le fichier à l'archive
if verbose:
echo fmt"Ajout du fichier: {path}"
fileEntries[relPath] = readFile(path)
elif kind == pcDir:
# Récursivement parcourir les sous-répertoires
addFilesRecursively(path, relPath)
# Commencer la compression depuis le répertoire source
addFilesRecursively(sourceDir)
# Créer l'archive avec toutes les entrées
let zipData = ziparchives.createZipArchive(fileEntries)
# Sauvegarder l'archive zip
writeFile(actualZipPath, zipData)
if verbose:
echo fmt"Archive zip créée avec succès: {actualZipPath}"
return true
except Exception as e:
echo fmt"Exception lors de la création du zip: {e.msg}"
return false
proc processMarkdownFileBasic*(sourcePath, targetPath: string, verbose: bool = false): Report =
## Traite un fichier Markdown : lit, analyse les métadonnées, complète si nécessaire, et écrit
var report = newReport()
if verbose:
echo fmt"Traitement du fichier: {sourcePath}"
let content = readMarkdownFile(sourcePath)
if content == "":
report.errors.add(fmt"Impossible de lire le fichier: {sourcePath}")
return report
# Extraire les métadonnées existantes ou créer de nouvelles métadonnées
var metadata: Metadata
let metadataOpt = extractMetadataFromYaml(content)
# Utiliser correctement l'API Option
if metadataOpt.isSome():
metadata = metadataOpt.get()
else:
metadata = newMetadata()
# Compléter les métadonnées manquantes
echo ">>> Avant updateMetadata: catégorie = " & metadata.category
# Utiliser le targetPath pour déterminer la catégorie au lieu du sourcePath
let modified = updateMetadata(metadata, content, targetPath, true, true)
echo ">>> Après updateMetadata: catégorie = " & metadata.category
if verbose:
if metadata.isNew:
echo fmt"Métadonnées ajoutées pour: {sourcePath}"
elif modified:
echo fmt"Métadonnées mises à jour pour: {sourcePath}"
else:
echo fmt"Métadonnées déjà complètes pour: {sourcePath}"
# Ajouter les métadonnées au contenu
let newContent = addMetadataToContent(metadata, content)
# Écrire le nouveau contenu dans le fichier cible
if writeMarkdownFile(targetPath, newContent):
if metadata.isNew:
report.newMetadata.add(targetPath)
elif modified:
report.updatedMetadata.add(targetPath)
else:
report.unchanged.add(targetPath)
else:
report.errors.add(fmt"Impossible d'écrire dans le fichier: {targetPath}")
return report