diff --git a/src/fr/monlouyan/bakefile/BakefileParser.java b/src/fr/monlouyan/bakefile/BakefileParser.java index 9cee36c..c0a496b 100644 --- a/src/fr/monlouyan/bakefile/BakefileParser.java +++ b/src/fr/monlouyan/bakefile/BakefileParser.java @@ -48,6 +48,12 @@ public class BakefileParser { */ private static final Pattern PHONY_PATTERN = Pattern.compile("^\\.PHONY:\\s*([^#]*?)\\s*(?:#.*)?$"); + /** + * Regex pour détecter les lignes de continuation. + * Format : " gcc -o program program.c \" + */ + private static final Pattern CONTINUATION_PATTERN = Pattern.compile("^(.*)\\\\\\s*$"); + /** * Regex pour détecter les références de variables. * Format : "${VAR}" ou "$(VAR)" @@ -73,6 +79,59 @@ public class BakefileParser { firstTarget = null; } + /** + * Gérer les lignes de continuation. + * @param lines Liste des lignes du fichier Bakefile + * @param startIndex Index de la première ligne de continuation + * @return Tableau contenant la commande combinée, les lignes brutes et le nombre de lignes traitées + */ + private Object[] handleContinuationLines(List<String> lines, int startIndex) { + StringBuilder combinedLine = new StringBuilder(); + List<String> rawLines = new ArrayList<>(); + int i = startIndex; + + // Ajouter la première ligne avec son backslash + String firstLine = lines.get(i); + rawLines.add(firstLine); // Garder la ligne telle quelle + + Matcher contMatcher = CONTINUATION_PATTERN.matcher(firstLine); + if (contMatcher.matches()) { + combinedLine.append(contMatcher.group(1).trim()).append(" "); + i++; + } + + // Traiter les lignes suivantes + while (i < lines.size()) { + String line = lines.get(i); + rawLines.add(line); // Garder la ligne telle quelle + + contMatcher = CONTINUATION_PATTERN.matcher(line); + if (contMatcher.matches()) { + // Ajouter sans le backslash à la commande combinée + combinedLine.append(contMatcher.group(1).trim()).append(" "); + i++; + } else { + // Dernière ligne de la séquence (sans backslash) + combinedLine.append(line.trim()); + i++; + break; + } + } + + if (BakeCLI.isDebug()) { + System.out.println("Debug: Combined " + (i - startIndex) + " lines into: [" + + combinedLine.toString().trim() + "]"); + System.out.println("Debug: Raw lines preserved: " + rawLines.size()); + } + + // Retourner la commande combinée, les lignes brutes et le nombre de lignes + return new Object[] { + combinedLine.toString().trim(), // Commande combinée pour exécution + rawLines, // Lignes brutes pour affichage + i - startIndex // Nombre de lignes traitées + }; + } + /** * Remplacer les variables dans une chaîne. * @param input Chaîne à traiter @@ -165,150 +224,155 @@ public class BakefileParser { * @return Liste des règles extraites */ public List<Rule> parse() { - List<Rule> rules = new ArrayList<>(); - Set<String> phonyTargets = new HashSet<>(); - - if (!Files.exists(Paths.get(filename))) { - System.out.println("*** No targets specified and no makefile found. Stop."); - System.exit(2); - } - - try { - List<String> lines = Files.readAllLines(Paths.get(filename)); - List<String> currentTargets = null; - List<String> dependencies = new ArrayList<>(); - List<String> commands = new ArrayList<>(); - boolean inContinuedCommand = false; - StringBuilder continuedCommand = new StringBuilder(); - - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i).replace("\r", ""); - - // Ignorer les lignes vides - if (line.trim().isEmpty()) { - continue; - } - - // Gérer les erreurs de format (espaces au lieu de tabulations) - if (line.matches("^ +.*$") && !inContinuedCommand) { - System.err.println(filename + ":" + (i+1) + ": *** missing separator. Stop."); - System.exit(2); - } - - // Si nous sommes en train de traiter une ligne continuée - if (inContinuedCommand) { - if (line.endsWith("\\")) { - // Encore une continuation - continuedCommand.append(" ").append(line.substring(0, line.length() - 1).trim()); - } else { - // Fin de la continuation - continuedCommand.append(" ").append(line.trim()); - commands.add(continuedCommand.toString().trim()); - inContinuedCommand = false; - continuedCommand = new StringBuilder(); - } - continue; - } - - // Matcher pour les déclarations .PHONY - Matcher phonyMatcher = PHONY_PATTERN.matcher(line); - if (phonyMatcher.matches()) { - String[] phonies = phonyMatcher.group(1).trim().split("\\s+"); - Collections.addAll(phonyTargets, phonies); - continue; - } - - // Matcher pour les déclarations de variables - Matcher varMatcher = VARIABLE_PATTERN.matcher(line); - if (varMatcher.matches()) { - String varName = varMatcher.group(1); - String varValue = varMatcher.group(2).trim(); - // Évaluer les variables référencées dans la valeur - varValue = replaceVariables(varValue); - variables.put(varName, varValue); - continue; - } - - // Matcher pour les cibles et dépendances - Matcher targetMatcher = TARGET_PATTERN.matcher(line); - if (targetMatcher.matches()) { - // Si nous avions des cibles précédentes, créons les règles correspondantes - if (currentTargets != null) { - // Créer une règle pour chaque cible avec les mêmes dépendances et commandes - for (String target : currentTargets) { - String resolvedTarget = replaceVariables(target.trim()); - rules.add(new Rule( - resolvedTarget, - replaceVariablesInList(dependencies), - replaceVariablesInList(commands), - phonyTargets.contains(resolvedTarget) - )); - - if (firstTarget == null) { - firstTarget = resolvedTarget; - } - } - } - - // Configuration pour les nouvelles cibles - String targetStr = targetMatcher.group(1); - currentTargets = splitTargets(targetStr); - - String depStr = targetMatcher.group(2); - dependencies = splitDependencies(depStr); - commands = new ArrayList<>(); - continue; - } - - // Matcher pour les lignes de commande - Matcher commandMatcher = COMMAND_PATTERN.matcher(line); - if (commandMatcher.matches()) { - String command = commandMatcher.group(1); - - // Gérer la continuation de ligne - if (command.endsWith("\\")) { - inContinuedCommand = true; - continuedCommand = new StringBuilder(command.substring(0, command.length() - 1).trim()); - } else { - commands.add(command); - } - } - } - - // Traiter les dernières cibles - if (currentTargets != null) { - // Créer une règle pour chaque cible avec les mêmes dépendances et commandes - for (String target : currentTargets) { - String resolvedTarget = replaceVariables(target.trim()); - rules.add(new Rule( - resolvedTarget, - replaceVariablesInList(dependencies), - replaceVariablesInList(commands), - phonyTargets.contains(resolvedTarget) - )); - - if (firstTarget == null) { - firstTarget = resolvedTarget; - } - } - } - - if (BakeCLI.isDebug()) { - System.out.println("Debug: Parsed " + rules.size() + " rules."); - for (Rule rule : rules) { - System.out.println("Debug: Rule: " + rule.getName()); - System.out.println("Debug: Commands: " + rule.getCommands().size()); - for (String cmd : rule.getCommands()) { - System.out.println("Debug: [" + cmd + "]"); - } - } - } - - } catch (IOException e) { - e.printStackTrace(); - } - return rules; - } + List<Rule> rules = new ArrayList<>(); + Set<String> phonyTargets = new HashSet<>(); + + if (!Files.exists(Paths.get(filename))) { + System.out.println("*** No targets specified and no makefile found. Stop."); + System.exit(2); + } + + try { + List<String> lines = Files.readAllLines(Paths.get(filename)); + List<String> currentTargets = null; + List<String> dependencies = new ArrayList<>(); + List<String> commands = new ArrayList<>(); + List<List<String>> displayCommands = new ArrayList<>(); + + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i).replace("\r", ""); + + // Ignorer les lignes vides + if (line.trim().isEmpty()) { + continue; + } + + // Gérer les erreurs de format (espaces au lieu de tabulations) + if (line.matches("^ +.*$")) { + System.err.println(filename + ":" + (i+1) + ": *** missing separator. Stop."); + System.exit(2); + } + + // Matcher pour les déclarations .PHONY + Matcher phonyMatcher = PHONY_PATTERN.matcher(line); + if (phonyMatcher.matches()) { + String[] phonies = phonyMatcher.group(1).trim().split("\\s+"); + Collections.addAll(phonyTargets, phonies); + continue; + } + + // Matcher pour les déclarations de variables + Matcher varMatcher = VARIABLE_PATTERN.matcher(line); + if (varMatcher.matches()) { + String varName = varMatcher.group(1); + String varValue = varMatcher.group(2).trim(); + // Évaluer les variables référencées dans la valeur + varValue = replaceVariables(varValue); + variables.put(varName, varValue); + continue; + } + + // Matcher pour les cibles et dépendances + Matcher targetMatcher = TARGET_PATTERN.matcher(line); + if (targetMatcher.matches()) { + // Si nous avions des cibles précédentes, créons les règles correspondantes + if (currentTargets != null) { + // Créer une règle pour chaque cible avec les mêmes dépendances et commandes + for (String target : currentTargets) { + String resolvedTarget = replaceVariables(target.trim()); + rules.add(new Rule( + resolvedTarget, + replaceVariablesInList(dependencies), + replaceVariablesInList(commands), + displayCommands, + phonyTargets.contains(resolvedTarget) + )); + + if (firstTarget == null) { + firstTarget = resolvedTarget; + } + } + } + + // Configuration pour les nouvelles cibles + String targetStr = targetMatcher.group(1); + currentTargets = splitTargets(targetStr); + + String depStr = targetMatcher.group(2); + dependencies = splitDependencies(depStr); + commands = new ArrayList<>(); + displayCommands = new ArrayList<>(); + continue; + } + + // Matcher pour les lignes de commande + Matcher commandMatcher = COMMAND_PATTERN.matcher(line); + if (commandMatcher.matches()) { + String command = commandMatcher.group(1); + + // Gérer la continuation de ligne + if (command.endsWith("\\")) { + // Traiter la séquence complète de continuation + Object[] result = handleContinuationLines(lines, i); + String fullCommand = (String)result[0]; + @SuppressWarnings("unchecked") + List<String> rawLines = (List<String>)result[1]; + int linesUsed = (Integer)result[2]; + + // Ajouter la commande complète pour l'exécution + commands.add(fullCommand); + + // Ajouter les lignes brutes pour l'affichage + displayCommands.add(rawLines); + + // Ajuster i pour sauter les lignes traitées (moins 1 car la boucle for incrémente i) + i += linesUsed - 1; + } else { + commands.add(command); + // Pour les commandes simples, créer une liste avec une seule ligne + List<String> singleLineDisplay = new ArrayList<>(); + singleLineDisplay.add(line); + displayCommands.add(singleLineDisplay); + } + continue; + } + } + + // Traiter les dernières cibles + if (currentTargets != null) { + // Créer une règle pour chaque cible avec les mêmes dépendances et commandes + for (String target : currentTargets) { + String resolvedTarget = replaceVariables(target.trim()); + rules.add(new Rule( + resolvedTarget, + replaceVariablesInList(dependencies), + replaceVariablesInList(commands), + displayCommands, + phonyTargets.contains(resolvedTarget) + )); + + if (firstTarget == null) { + firstTarget = resolvedTarget; + } + } + } + + if (BakeCLI.isDebug()) { + System.out.println("Debug: Parsed " + rules.size() + " rules."); + for (Rule rule : rules) { + System.out.println("Debug: Rule: " + rule.getName()); + System.out.println("Debug: Commands: " + rule.getCommands().size()); + for (String cmd : rule.getCommands()) { + System.out.println("Debug: [" + cmd + "]"); + } + } + } + + } catch (IOException e) { + e.printStackTrace(); + } + return rules; + } /** * Récupérer la première cible diff --git a/src/fr/monlouyan/bakefile/CommandExecutor.java b/src/fr/monlouyan/bakefile/CommandExecutor.java index 60664dc..8fe286c 100644 --- a/src/fr/monlouyan/bakefile/CommandExecutor.java +++ b/src/fr/monlouyan/bakefile/CommandExecutor.java @@ -2,6 +2,7 @@ package fr.monlouyan.bakefile; import java.io.File; import java.io.IOException; +import java.util.List; /** * Exécuteur des commandes définies dans les règles. @@ -70,7 +71,13 @@ public class CommandExecutor { return; // On ne fait rien mais on ne montre pas de message } - for (String command : rule.getCommands()) { + List<String> executableCommands = rule.getCommands(); + List<List<String>> displayCommands = rule.getDisplayCommands(); + + for (int i = 0; i < executableCommands.size(); i++) { + String command = executableCommands.get(i); + List<String> displayLines = (i < displayCommands.size()) ? displayCommands.get(i) : null; + // Vérifier si la commande commence par @ (ne pas afficher la commande) boolean silent = command.startsWith("@"); // Enlever le @ si présent pour exécuter la commande correctement @@ -83,8 +90,11 @@ public class CommandExecutor { } if (isCircular){ - if (!silent) { - System.out.println(actualCommand); + if (!silent && displayLines != null && !displayLines.isEmpty()) { + // Afficher les lignes formatées + for (String line : displayLines) { + System.out.println(line); + } } } @@ -92,7 +102,14 @@ public class CommandExecutor { if (ruleNeedsUpdate) { try { if(!isCircular && !silent){ - System.out.println(actualCommand); + // Afficher les lignes formatées + if (displayLines != null && !displayLines.isEmpty()) { + for (String line : displayLines) { + System.out.println(line); + } + } else { + System.out.println(command); + } } if (debug) System.out.println("Debug: Executing " + actualCommand); ProcessBuilder pb = new ProcessBuilder("sh", "-c", actualCommand); diff --git a/src/fr/monlouyan/bakefile/Rule.java b/src/fr/monlouyan/bakefile/Rule.java index f2f3648..d22c015 100644 --- a/src/fr/monlouyan/bakefile/Rule.java +++ b/src/fr/monlouyan/bakefile/Rule.java @@ -21,6 +21,9 @@ public class Rule { /*** Liste des commandes */ private List<String> commands; + + /*** Liste des commandes d'affichage (pour préserver la mise en page originale) */ + private List<List<String>> displayCommands; /*** true si la règle est phony, false sinon */ private boolean isPhony; @@ -30,12 +33,15 @@ public class Rule { * @param name Nom de la règle * @param dependencies Liste des dépendances * @param commands Liste des commandes + * @param displayCommands Liste des commandes formatées pour affichage * @param isPhony true si la règle est phony, false sinon */ - public Rule(String name, List<String> dependencies, List<String> commands, boolean isPhony) { + public Rule(String name, List<String> dependencies, List<String> commands, + List<List<String>> displayCommands, boolean isPhony) { this.name = name; this.dependencies = dependencies; this.commands = commands; + this.displayCommands = displayCommands; this.isPhony = isPhony; } @@ -56,6 +62,12 @@ public class Rule { * @return La liste des commandes */ public List<String> getCommands() { return commands; } + + /** + * Récupère la liste des commandes formatées pour affichage. + * @return La liste des commandes formatées + */ + public List<List<String>> getDisplayCommands() { return displayCommands; } /** * Vérifie si la règle est phony. @@ -76,6 +88,16 @@ public class Rule { public boolean needsUpdate() { if (BakeCLI.isDebug()){ System.out.println("Debug : Checking if rule " + name + " needs update"); + System.out.println("Debug : Rule has " + commands.size() + " commands:"); + for (int i = 0; i < commands.size(); i++) { + System.out.println("Debug : Command " + (i+1) + ": [" + commands.get(i) + "]"); + } + System.out.println("Debug : Rule has " + dependencies.size() + " dependencies:"); + for (String dep : dependencies) { + System.out.println("Debug : Dependency: [" + dep + "]"); + } + File targetFile = new File(name); + System.out.println("Debug : Target file '" + name + "' exists: " + targetFile.exists()); } // Les règles phony sont toujours mises à jour