# markdown_parser.nim # Programme principal qui définit l'interface en ligne de commande et orchestre le processus import std/[strutils, os, times, strformat,parseopt, options], modules/[fileutils, metadata, config, report], version # Initialiser le mode debug global à partir du fichier de configuration let appConfigGlobal = loadConfig("config.json") setGlobalDebugMode(appConfigGlobal.debugModeActive) const HELP_TEXT = """ markdown_parser - Analyseur et restructureur de fichiers Markdown Usage: markdown_parser [options] Options: -h, --help Affiche ce message d'aide -v, --version Affiche la version du programme -V, --verbose Mode verbeux pour afficher plus d'informations -s, --source=PATH Chemin vers le répertoire source -f, --files=FILE1,FILE2,... Liste de fichiers Markdown (séparés par des virgules) -a, --analyze Active l'analyse IA des fichiers sans métadonnées """ type AppConfig* = object sourceDir*: string files*: seq[string] verbose*: bool showHelp*: bool showVersion*: bool analyze*: bool proc parseCommandLine(): AppConfig = var config = AppConfig() for kind, key, val in getopt(): case kind of cmdLongOption, cmdShortOption: case key of "h", "help": config.showHelp = true of "v", "version": config.showVersion = true of "V", "verbose": config.verbose = true of "s", "source": config.sourceDir = val of "f", "files": config.files = val.split(',') of "a", "analyze": config.analyze = true else: discard return config proc backupAndRenameSourceDir(sourceDir: string, verbose: bool = false): string = ## Crée une sauvegarde du répertoire source, renomme le répertoire source ## avec un timestamp, et crée un nouveau répertoire avec le nom original. ## ## Paramètres: ## sourceDir: Chemin du répertoire source ## verbose: Active les messages de débogage détaillés ## ## Retourne: ## Le nom du répertoire source renommé en cas de succès, ou une chaîne vide en cas d'erreur # Format timestamp for filenames - manually create timestamp string # Get current time components let now = now() let year = $now.year let month = align($(ord(now.month)), 2, '0') # Pad with leading zeros let day = align($now.monthday, 2, '0') # Pad with leading zeros let hour = align($now.hour, 2, '0') # Pad with leading zeros let minute = align($now.minute, 2, '0') # Pad with leading zeros let second = align($now.second, 2, '0') # Pad with leading zeros # Concatenate timestamp components manually let timestamp = year & month & day & hour & minute & second # Create filenames let backupFileName = fmt"{sourceDir}_{timestamp}.zip" let newSourceDirName = fmt"{sourceDir}_{timestamp}" # Create backup using zipDir function from fileutils module let backupSuccess = zipDir(sourceDir, backupFileName, verbose) if not backupSuccess: echo fmt"Erreur: Échec de la création du backup {backupFileName}" return "" # Rename source directory if verbose: echo fmt"Renommage du répertoire source: {sourceDir} -> {newSourceDirName}" try: moveDir(sourceDir, newSourceDirName) except OSError: echo fmt"Erreur lors du renommage du répertoire source: {getCurrentExceptionMsg()}" return "" # Create new source directory with original name try: createDir(sourceDir) if verbose: echo fmt"Création du nouveau répertoire source: {sourceDir}" except OSError: echo fmt"Erreur lors de la création du nouveau répertoire: {getCurrentExceptionMsg()}" return "" return newSourceDirName proc validateConfig(config: AppConfig): bool = if config.showHelp or config.showVersion: return true if config.sourceDir == "" and config.files.len == 0: echo "Erreur: Ni répertoire source ni fichiers spécifiés" return false if config.sourceDir != "" and not dirExists(config.sourceDir): echo fmt"Erreur: Le répertoire source '{config.sourceDir}' n'existe pas" return false for file in config.files: if not fileExists(file): echo fmt"Erreur: Le fichier '{file}' n'existe pas" return false # Vérifier la disponibilité du modèle AI si l'analyse est activée if config.analyze: let appConfig = loadConfig("config.json") if not isModelAvailable(appConfig): echo fmt"Erreur: Le modèle AI '{appConfig.activeModelName}' n'est pas disponible. Analyse désactivée." echo "Aucun traitement ne sera effectué." return false return true proc processMarkdownFile(sourcePath, targetPath: string, verbose: bool, analyze: bool): FileReport = ## Traite un fichier Markdown en extrayant/générant les métadonnées var fileReport = FileReport( sourcePath: sourcePath, targetPath: targetPath, status: "", success: false ) try: # Lire le contenu du fichier let content = readFile(sourcePath) var processedContent = content let appConfig = loadConfig("config.json") # Extraire les métadonnées existantes if verbose: echo fmt"Analyse du fichier: {sourcePath}" var metadata = extractMetadataFromYaml(content) # Si pas de métadonnées et analyse activée, utiliser l'IA if metadata.isNone() and analyze: if verbose: echo "Pas de métadonnées trouvées, tentative d'analyse IA..." let analysisResult = analyzeWithAI(getContentWithoutYaml(content), "config.json") metadata = some(Metadata( title: analysisResult.title, description: analysisResult.description, tags: analysisResult.tags, creationDate: now().format("yyyy-MM-dd"), category: extractCategoryFromPath(sourcePath) )) processedContent = addMetadataToContent(metadata.get(), content) if verbose: let tagsJoined = analysisResult.tags.join(", ") echo fmt"Métadonnées générées par l'IA: titre='{analysisResult.title}', description='{analysisResult.description}', tags=[{tagsJoined}]" fileReport.status = "Métadonnées générées par IA" fileReport.success = true elif metadata.isSome(): if verbose: echo "Métadonnées existantes trouvées" fileReport.status = "Métadonnées existantes" fileReport.success = true else: if verbose: echo "Aucune métadonnée trouvée et analyse IA désactivée" fileReport.status = "Aucune métadonnée" fileReport.success = true # Écrire le contenu traité dans le fichier cible writeFile(targetPath, processedContent) except Exception as e: echo fmt"Erreur lors du traitement du fichier {sourcePath}: {e.msg}" fileReport.status = fmt"Erreur: {e.msg}" fileReport.success = false return fileReport proc processMarkdownFiles(config: AppConfig): Report = var report = newReport() var newSourceDir = "" # Vérifier la disponibilité du modèle AI si l'analyse est activée if config.analyze: let appConfig = loadConfig("config.json") if not isModelAvailable(appConfig): echo fmt"Erreur: Le modèle AI '{appConfig.activeModelName}' n'est pas disponible. Analyse désactivée." echo "Aucun traitement ne sera effectué." return report # Si un répertoire source est spécifié, traiter tous les fichiers if config.sourceDir != "": if config.verbose: echo fmt"Analyse du répertoire source: {config.sourceDir}" newSourceDir = backupAndRenameSourceDir(config.sourceDir, config.verbose) if newSourceDir == "": echo "Erreur: Impossible de préparer les répertoires" return report # Récupérer récursivement tous les fichiers du répertoire source let allFiles = walkDirRecFiles(newSourceDir, "") if config.verbose: echo fmt"Nombre total de fichiers trouvés: {allFiles.len}" for file in allFiles: let relPath = relativePath(file, newSourceDir) targetFile = config.sourceDir / relPath parentDir = parentDir(targetFile) if not dirExists(parentDir): createDir(parentDir) # Vérifier si c'est un fichier markdown if file.endsWith(".md"): # Traiter les fichiers markdown avec processMarkdownFile let fileReport = processMarkdownFile(file, targetFile, config.verbose, config.analyze) report.fileReports.add(fileReport) else: # Copier simplement les autres fichiers if config.verbose: echo fmt"Copie du fichier non-markdown: {file} -> {targetFile}" discard copyFileWithDirectories(file, targetFile, config.verbose) # Après le traitement, supprimer le répertoire temporaire try: if config.verbose: echo fmt"Suppression du répertoire temporaire: {newSourceDir}" removeDir(newSourceDir) except Exception as e: echo fmt"Erreur lors de la suppression du répertoire temporaire: {e.msg}" # Traiter les fichiers spécifiés individuellement avec l'option --files elif config.files.len > 0: # Créer un répertoire temporaire pour les fichiers d'origine let now = now() year = $now.year month = align($(ord(now.month)), 2, '0') day = align($now.monthday, 2, '0') hour = align($now.hour, 2, '0') minute = align($now.minute, 2, '0') second = align($now.second, 2, '0') timestamp = year & month & day & hour & minute & second tempDir = "markdown_files_" & timestamp # Créer le répertoire temporaire createDir(tempDir) if config.verbose: echo fmt"Création du répertoire temporaire: {tempDir}" # Déterminer le répertoire cible (utiliser sourceDir s'il est spécifié, sinon "processed_files") var targetBaseDir = if config.sourceDir != "": config.sourceDir else: "processed_files" # Normaliser le chemin cible targetBaseDir = normalizedPath(targetBaseDir) # Créer le répertoire cible s'il n'existe pas if not dirExists(targetBaseDir): createDir(targetBaseDir) if config.verbose: echo fmt"Création du répertoire cible: {targetBaseDir}" # Créer une sauvegarde du répertoire cible s'il contient déjà des fichiers if dirExists(targetBaseDir) and walkDirRecFiles(targetBaseDir, "").len > 0: let backupSuccess = zipDir(targetBaseDir, "", config.verbose) if not backupSuccess and config.verbose: echo fmt"Avertissement: Échec de la création du backup pour {targetBaseDir}" var processedPaths: seq[string] = @[] # Traiter chaque fichier spécifié for file in config.files: let absFilePath = absolutePath(file) fileDir = parentDir(absFilePath) # Déterminer le chemin relatif en préservant la structure du dossier original var relPath: string # Si un répertoire source est spécifié et que le fichier est à l'intérieur if config.sourceDir != "" and absFilePath.startsWith(absolutePath(config.sourceDir)): relPath = relativePath(absFilePath, absolutePath(config.sourceDir)) else: # Si aucun répertoire source n'est spécifié, préserver la structure à partir du dossier parent du fichier let filename = extractFilename(absFilePath) # Utiliser seulement le dernier dossier parent comme sous-répertoire let parentDirName = extractFilename(fileDir) if parentDirName != "": relPath = parentDirName / filename else: relPath = filename # Chemins pour fichiers temporaires et cibles let tempFilePath = tempDir / relPath targetFile = targetBaseDir / relPath targetDir = parentDir(targetFile) # Ajouter à la liste des chemins traités processedPaths.add(targetFile) # Créer les répertoires parents nécessaires pour le fichier temporaire let tempFileDir = parentDir(tempFilePath) if not dirExists(tempFileDir): createDir(tempFileDir) # Créer le répertoire parent cible s'il n'existe pas if not dirExists(targetDir): createDir(targetDir) if config.verbose: echo fmt"Création du répertoire parent cible: {targetDir}" # Copier le fichier dans le répertoire temporaire if config.verbose: echo fmt"Copie du fichier original vers le répertoire temporaire: {absFilePath} -> {tempFilePath}" discard copyFileWithDirectories(absFilePath, tempFilePath, config.verbose) # Traiter le fichier markdown if file.endsWith(".md"): if config.verbose: echo fmt"Traitement du fichier markdown: {tempFilePath} -> {targetFile}" let fileReport = processMarkdownFile(tempFilePath, targetFile, config.verbose, config.analyze) report.fileReports.add(fileReport) else: # Copier simplement les fichiers non-markdown if config.verbose: echo fmt"Copie du fichier non-markdown: {tempFilePath} -> {targetFile}" discard copyFileWithDirectories(tempFilePath, targetFile, config.verbose) # Créer une sauvegarde du répertoire temporaire let backupDir = "backup" if not dirExists(backupDir): createDir(backupDir) let tempBackupSuccess = zipDir(tempDir, backupDir / (tempDir & ".zip"), config.verbose) if tempBackupSuccess: if config.verbose: echo fmt"Sauvegarde du répertoire temporaire créée: {backupDir}/{tempDir}.zip" else: echo fmt"Erreur lors de la création de la sauvegarde du répertoire temporaire: {tempDir}" # Supprimer le répertoire temporaire après la sauvegarde try: if config.verbose: echo fmt"Suppression du répertoire temporaire: {tempDir}" removeDir(tempDir) except Exception as e: echo fmt"Erreur lors de la suppression du répertoire temporaire: {e.msg}" return report proc main() = let config = parseCommandLine() if config.showHelp: echo HELP_TEXT quit(0) if config.showVersion: echo fmt"markdown_parser version {VERSION_STRING}" quit(0) if not validateConfig(config): echo HELP_TEXT quit(1) if config.verbose: echo "Configuration validée, début du traitement..." let report = processMarkdownFiles(config) echo "\nRapport de traitement:" let reportStr = report.toString() echo reportStr.replace("\\n", "\n") if config.verbose: echo "Traitement terminé." when isMainModule: main()