From eceff13b9a56f0734ff57d491adfbe1c958162a7 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Fri, 12 Dec 2025 13:42:54 +0100 Subject: [PATCH 01/10] Extrapolate basic functionality for workflow files generation into `SolutionCreationUtils` and also replace dependent padding (`if (stepNumber < 10)`) by String format flags --- .../SolutionCreationUtils.java | 80 +++++++++++++++++++ .../cwl/AbstractCWLCreator.java | 15 ++-- .../solutionStructure/cwl/CWLToolBase.java | 59 +------------- .../cwl/CWLWorkflowBase.java | 59 +------------- .../cwl/DefaultCWLCreator.java | 7 +- .../snakemake/SnakemakeCreator.java | 46 ++--------- 6 files changed, 104 insertions(+), 162 deletions(-) create mode 100644 src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java new file mode 100644 index 00000000..a3612535 --- /dev/null +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java @@ -0,0 +1,80 @@ +package nl.uu.cs.ape.solver.solutionStructure; + +/** + * The {@code SolutionCreationUtils} class provides general functionality + * for creating workflow definition files as indentations and name generation. + * + * @author Mario Frank + */ +public class SolutionCreationUtils { + + + /** + * Generate the name for a step in the workflow. + * + * @param moduleNode The {@link ModuleNode} that is the workflow step. + * @return The name of the workflow step. + */ + public static String stepName(ModuleNode moduleNode) { + int stepNumber = moduleNode.getAutomatonState().getLocalStateNumber(); + + // The step number is automatically padded with 0 if the number is smaller than 10 (< 2 chars). + return String.format("%s_%02d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); + } + + + /** + * Generate the name of the input or output of a step's run input or output. + * I.e. "moduleName_indicator_n". + * + * @param moduleNode The {@link ModuleNode} that is the workflow step. + * @param indicator Indicator whether it is an input or an output. + * @param n The n-th input or output this is. + * @return The name of the input or output. + */ + public static String generateInputOrOutputName(ModuleNode moduleNode, String indicator, int n) { + return String.format("%s_%s_%d", + moduleNode.getNodeLabel(), + indicator, + n); + } + + + /** + * Generate the indentation at the start of a line. + * + * @param level The level of indentation. + * @param indentStyle The indentation style. + * @return The indentation of the given level. + */ + public static String ind(int level, IndentStyle indentStyle) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < level; i++) { + builder.append(indentStyle); + } + return builder.toString(); + } + + + /** + * The available indentation styles. + */ + public enum IndentStyle { + /** Two spaces for indentation. */ + SPACES2(" "), + /** Four spaces for indentation. */ + SPACES4(" "); + + private final String text; + + IndentStyle(String s) { + this.text = s; + } + + @Override + public String toString() { + return this.text; + } + } + +} diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/AbstractCWLCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/AbstractCWLCreator.java index 6a2b2fd6..d1b598ee 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/AbstractCWLCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/AbstractCWLCreator.java @@ -2,6 +2,7 @@ import nl.uu.cs.ape.models.logic.constructs.TaxonomyPredicate; import nl.uu.cs.ape.solver.solutionStructure.ModuleNode; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; import nl.uu.cs.ape.solver.solutionStructure.TypeNode; @@ -93,14 +94,14 @@ private void generateWorkflowOutputs() { // outputSource .append(ind(2)) .append("outputSource: ") - .append(stepName(typeNode.getCreatedByModule())) + .append(SolutionCreationUtils.stepName(typeNode.getCreatedByModule())) .append("/"); // Get the id of the step run's output bound to this workflow output // (step_name/output_name_ID) int outId = typeNode.getCreatedByModule().getOutputTypes().get(i - 1).getAutomatonState() .getLocalStateNumber(); cwlRepresentation - .append(generateInputOrOutputName(typeNode.getCreatedByModule(), "out", outId + 1)) + .append(SolutionCreationUtils.generateInputOrOutputName(typeNode.getCreatedByModule(), "out", outId + 1)) .append("\n"); i++; } @@ -126,7 +127,7 @@ private void generateStep(ModuleNode moduleNode) { // Name cwlRepresentation .append(ind(baseInd)) - .append(stepName(moduleNode)) + .append(SolutionCreationUtils.stepName(moduleNode)) .append(":\n"); generateStepIn(moduleNode); generateStepOut(moduleNode); @@ -157,7 +158,7 @@ private void generateStepIn(ModuleNode moduleNode) { for (TypeNode typeNode : moduleNode.getInputTypes()) { cwlRepresentation .append(ind(baseInd + 1)) - .append(generateInputOrOutputName(moduleNode, "in", i + 1)) + .append(SolutionCreationUtils.generateInputOrOutputName(moduleNode, "in", i + 1)) .append(": ") .append(workflowParameters.get(typeNode.getNodeID())) .append("\n"); @@ -180,8 +181,8 @@ private void generateStepOut(ModuleNode moduleNode) { .append("["); int i = 1; for (TypeNode typeNode : moduleNode.getOutputTypes()) { - String name = generateInputOrOutputName(moduleNode, "out", i); - addParameter(typeNode, String.format("%s/%s", stepName(moduleNode), name)); + String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", i); + addParameter(typeNode, String.format("%s/%s", SolutionCreationUtils.stepName(moduleNode), name)); cwlRepresentation .append(name) .append(", "); @@ -310,7 +311,7 @@ private void generateTypeNodes(ModuleNode moduleNode, List typeNodeLis cwlRepresentation // Name .append(ind(baseInd)) - .append(generateInputOrOutputName(moduleNode, input ? "in" : "out", i)) + .append(SolutionCreationUtils.generateInputOrOutputName(moduleNode, input ? "in" : "out", i)) .append(":\n") // Data type .append(ind(baseInd + 1)) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLToolBase.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLToolBase.java index 237bf4d6..9cac0ee9 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLToolBase.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLToolBase.java @@ -11,6 +11,8 @@ import nl.uu.cs.ape.models.Module; import nl.uu.cs.ape.models.Type; import nl.uu.cs.ape.models.logic.constructs.TaxonomyPredicate; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils.IndentStyle; /** * Base class with shared behavior for CWL export classes. @@ -172,37 +174,6 @@ protected void generateTopComment() { "# The template for this tool description is generated by APE (https://github.com/workflomics/ape).\n"); } - /** - * Generate the name of the input or output of a step's run input or output. - * I.e. "moduleName_indicator_n". - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @param indicator Indicator whether it is an input or an output. - * @param n The n-th input or output this is. - * @return The name of the input or output. - */ - protected String generateInputOrOutputName(ModuleNode moduleNode, String indicator, int n) { - return String.format("%s_%s_%d", - moduleNode.getNodeLabel(), - indicator, - n); - } - - /** - * Generate the name for a step in the workflow. - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @return The name of the workflow step. - */ - protected String stepName(ModuleNode moduleNode) { - int stepNumber = moduleNode.getAutomatonState().getLocalStateNumber(); - if (stepNumber < 10) { - return String.format("%s_0%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } else { - return String.format("%s_%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } - } - /** * Generate the main part of the CWL representation. */ @@ -233,31 +204,7 @@ protected void deleteLastNCharactersFromCWL(int numberOfCharToDel) { * @return The indentation of the given level. */ protected String ind(int level) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < level; i++) { - builder.append(this.indentStyle); - } - return builder.toString(); + return SolutionCreationUtils.ind(level, this.indentStyle); } - /** - * The available indentation styles. - */ - public enum IndentStyle { - /** Two spaces for indentation. */ - SPACES2(" "), - /** Four spaces for indentation. */ - SPACES4(" "); - - private final String text; - - IndentStyle(String s) { - this.text = s; - } - - @Override - public String toString() { - return this.text; - } - } } diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java index 7e453088..c0d9b906 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java @@ -1,6 +1,8 @@ package nl.uu.cs.ape.solver.solutionStructure.cwl; import nl.uu.cs.ape.solver.solutionStructure.ModuleNode; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils.IndentStyle; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; /** @@ -105,37 +107,6 @@ private void generateDoc() { cwlRepresentation.append(".").append("\n").append("\n"); } - /** - * Generate the name of the input or output of a step's run input or output. - * I.e. "moduleName_indicator_n". - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @param indicator Indicator whether it is an input or an output. - * @param n The n-th input or output this is. - * @return The name of the input or output. - */ - protected String generateInputOrOutputName(ModuleNode moduleNode, String indicator, int n) { - return String.format("%s_%s_%d", - moduleNode.getNodeLabel(), - indicator, - n); - } - - /** - * Generate the name for a step in the workflow. - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @return The name of the workflow step. - */ - protected String stepName(ModuleNode moduleNode) { - int stepNumber = moduleNode.getAutomatonState().getLocalStateNumber(); - if (stepNumber < 10) { - return String.format("%s_0%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } else { - return String.format("%s_%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } - } - /** * Generate the main part of the CWL representation. */ @@ -166,31 +137,7 @@ protected void deleteLastNCharactersFromCWL(int numberOfCharToDel) { * @return The indentation of the given level. */ protected String ind(int level) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < level; i++) { - builder.append(this.indentStyle); - } - return builder.toString(); + return SolutionCreationUtils.ind(level, this.indentStyle); } - /** - * The available indentation styles. - */ - public enum IndentStyle { - /** Two spaces for indentation. */ - SPACES2(" "), - /** Four spaces for indentation. */ - SPACES4(" "); - - private final String text; - - IndentStyle(String s) { - this.text = s; - } - - @Override - public String toString() { - return this.text; - } - } } diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java index bf899307..9e9b145f 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/DefaultCWLCreator.java @@ -2,6 +2,7 @@ import nl.uu.cs.ape.models.Type; import nl.uu.cs.ape.solver.solutionStructure.ModuleNode; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; import nl.uu.cs.ape.solver.solutionStructure.TypeNode; import nl.uu.cs.ape.utils.APEResources; @@ -149,7 +150,7 @@ private void generateWorkflowOutputs() { // outputSource .append(ind(2)) .append("outputSource: ") - .append(stepName(typeNode.getCreatedByModule())) + .append(SolutionCreationUtils.stepName(typeNode.getCreatedByModule())) .append("/"); // Get the id of the step run's output bound to this workflow output // (step_name/output_name_ID) @@ -182,7 +183,7 @@ private void generateStep(ModuleNode moduleNode) { // Name cwlRepresentation .append(ind(baseInd)) - .append(stepName(moduleNode)) + .append(SolutionCreationUtils.stepName(moduleNode)) .append(":\n"); generateDefaultStepRun(moduleNode); generateStepIn(moduleNode); @@ -236,7 +237,7 @@ private void generateStepOut(ModuleNode moduleNode) { IntStream.range(0, outputs.size()).filter(i -> !outputs.get(i).isEmpty()) .forEach(i -> { String name = moduleNode.getOutputCWLKeys().get(i); - addNewParameterToMap(outputs.get(i), String.format("%s/%s", stepName(moduleNode), name)); + addNewParameterToMap(outputs.get(i), String.format("%s/%s", SolutionCreationUtils.stepName(moduleNode), name)); cwlRepresentation .append(name) .append(", "); diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index 7f09d141..be5a8ac6 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -1,7 +1,8 @@ package nl.uu.cs.ape.solver.solutionStructure.snakemake; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils; +import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils.IndentStyle; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; -import nl.uu.cs.ape.solver.solutionStructure.cwl.CWLWorkflowBase.IndentStyle; import java.util.HashMap; import java.util.List; @@ -46,7 +47,7 @@ public String generateSnakemakeRepresentation() { for (ModuleNode moduleNode : solution.getModuleNodes()) { snakemakeRepresentation .append("rule ") - .append(stepName(moduleNode)) + .append(SolutionCreationUtils.stepName(moduleNode)) .append(":\n"); generateRuleInput(moduleNode); generateRuleOutput(moduleNode); @@ -73,7 +74,7 @@ private void generateRuleAll() { int outId = typeNode.getCreatedByModule().getOutputTypes().get(i - 1).getAutomatonState() .getLocalStateNumber(); snakemakeRepresentation - .append(generateInputOrOutputName(typeNode.getCreatedByModule(), "out", outId + 1)) + .append(SolutionCreationUtils.generateInputOrOutputName(typeNode.getCreatedByModule(), "out", outId + 1)) .append("'\n"); i++; } @@ -128,7 +129,7 @@ private void generateRuleOutput(ModuleNode moduleNode) { List outputs = moduleNode.getOutputTypes(); IntStream.range(0, outputs.size()).filter(i -> !outputs.get(i).isEmpty()) .forEach(i -> { - String name = generateInputOrOutputName(moduleNode, "out", i + 1); + String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", i + 1); addNewParameterToMap(outputs.get(i), String.format("%s", name)); snakemakeRepresentation .append(ind(2)) @@ -188,21 +189,6 @@ private String getWorkflowName() { return String.format("WorkflowNo_%d", solution.getIndex()); } - /** - * Generate the name for a step in the workflow. - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @return The name of the workflow step. - */ - protected String stepName(ModuleNode moduleNode) { - int stepNumber = moduleNode.getAutomatonState().getLocalStateNumber(); - if (stepNumber < 10) { - return String.format("%s_0%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } else { - return String.format("%s_%d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); - } - } - /** * Generate the indentation at the start of a line. * @@ -210,27 +196,7 @@ protected String stepName(ModuleNode moduleNode) { * @return The indentation of the given level. */ protected String ind(int level) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < level; i++) { - builder.append(this.indentStyle); - } - return builder.toString(); - } - - /** - * Generate the name of the input or output of a step's run input or output. - * I.e. "moduleName_indicator_n". - * - * @param moduleNode The {@link ModuleNode} that is the workflow step. - * @param indicator Indicator whether it is an input or an output. - * @param n The n-th input or output this is. - * @return The name of the input or output. - */ - protected String generateInputOrOutputName(ModuleNode moduleNode, String indicator, int n) { - return String.format("%s_%s_%d", - moduleNode.getNodeLabel(), - indicator, - n); + return SolutionCreationUtils.ind(level, this.indentStyle); } } From c92d0897532e5d6049fb6976fc01160e17667d86 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 8 Jan 2026 14:23:25 +0100 Subject: [PATCH 02/10] [Fix/Refactor] Replace `getOutputTypes()` in `generateRuleInput()` with `getInputTypes()` and refactor the functions `generateRuleInput()` and `generateRuleOutput()` such that String join is used making removal of superfluous trailing newline and colon obsolete. --- .../snakemake/SnakemakeCreator.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index be5a8ac6..eff314bd 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -4,6 +4,7 @@ import nl.uu.cs.ape.solver.solutionStructure.SolutionCreationUtils.IndentStyle; import nl.uu.cs.ape.solver.solutionStructure.SolutionWorkflow; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.stream.IntStream; @@ -101,19 +102,19 @@ private void generateRuleInput(ModuleNode moduleNode) { snakemakeRepresentation .append(ind(1)) .append("input:\n"); - if (moduleNode.hasInputTypes()){ - List inputs = moduleNode.getInputTypes(); - IntStream.range(0, inputs.size()).filter(i -> !inputs.get(i).isEmpty()) - .forEach(i -> snakemakeRepresentation - .append(ind(2)) - .append(String.format("'add-path/%s'", workflowParameters.get(inputs.get(i).getNodeID()))) - .append(",\n")); - if (moduleNode.hasOutputTypes()) { - // Remove the last comma - deleteLastNCharactersFromSnakefile(2); + + List valid_inputs = new ArrayList<>(); + // Create a path representation for each valid, i.e. non-empty, input type + for (TypeNode node : moduleNode.getInputTypes()) + { + if (!node.isEmpty()) + { + valid_inputs.add(String.format("%s'add-path/%s'", ind(2), workflowParameters.get(node.getNodeID()))); } - snakemakeRepresentation.append("\n"); } + + // Join all valid input representations with a colon followed by a newline + snakemakeRepresentation.append(String.join(",\n", valid_inputs)).append("\n"); } /** @@ -125,22 +126,25 @@ private void generateRuleOutput(ModuleNode moduleNode) { snakemakeRepresentation .append(ind(1)) .append("output:\n"); - - List outputs = moduleNode.getOutputTypes(); - IntStream.range(0, outputs.size()).filter(i -> !outputs.get(i).isEmpty()) - .forEach(i -> { - String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", i + 1); - addNewParameterToMap(outputs.get(i), String.format("%s", name)); - snakemakeRepresentation - .append(ind(2)) - .append(String.format("'add-path/%s'", name)) - .append(",\n"); - }); - if (moduleNode.hasOutputTypes()) { - // Remove the last comma - deleteLastNCharactersFromSnakefile(2); + + List valid_output = new ArrayList<>(); + int valid_output_id = 0; + // Create a path representation for each valid, i.e. non-empty, output type + for (TypeNode node : moduleNode.getOutputTypes()) + { + if (!node.isEmpty()) + { + // Increment the output id and generate the output name + valid_output_id += 1; + String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", valid_output_id); + addNewParameterToMap(node, String.format("%s", name)); + + valid_output.add(String.format("%s'add-path/%s'", ind(2), name)); + } } - snakemakeRepresentation.append("\n"); + + // Join all valid output representations with a colon followed by a newline + snakemakeRepresentation.append(String.join(",\n", valid_output)).append("\n"); } /** From 0d583f0633639619b12784a354b4b1b95e4f313a Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 8 Jan 2026 14:35:49 +0100 Subject: [PATCH 03/10] [Refactor] Replace another append chain by string format. --- .../solver/solutionStructure/snakemake/SnakemakeCreator.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index eff314bd..a7ed46b4 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -46,10 +46,7 @@ public String generateSnakemakeRepresentation() { addWorkflowInputs(); generateRuleAll(); for (ModuleNode moduleNode : solution.getModuleNodes()) { - snakemakeRepresentation - .append("rule ") - .append(SolutionCreationUtils.stepName(moduleNode)) - .append(":\n"); + snakemakeRepresentation.append(String.format("rule %s:\n'", SolutionCreationUtils.stepName(moduleNode))); generateRuleInput(moduleNode); generateRuleOutput(moduleNode); generateRuleShell(moduleNode); From 6acff82fdc6a21e462da00c6a84a14ae2f81a8fb Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 8 Jan 2026 14:39:54 +0100 Subject: [PATCH 04/10] [Refactor] Remove unused function --- .../solver/solutionStructure/snakemake/SnakemakeCreator.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index a7ed46b4..e896efbc 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -169,9 +169,6 @@ private String addNewParameterToMap(TypeNode typeNode, String name) { return typeNode.getNodeID(); } - public void deleteLastNCharactersFromSnakefile(int numberOfCharToDel) { - snakemakeRepresentation.delete(snakemakeRepresentation.length() - numberOfCharToDel, snakemakeRepresentation.length()); - } /** * Adds the comment at the top of the file. From 84c898c3a99da229c45b6c65f968070d6bffb223 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 12 Jan 2026 14:26:10 +0100 Subject: [PATCH 05/10] [FIX] Add missing Builder functionality for Snakemake --- .../java/nl/uu/cs/ape/configuration/APERunConfig.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java b/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java index f3d6c438..2b0f3d3e 100644 --- a/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java +++ b/src/main/java/nl/uu/cs/ape/configuration/APERunConfig.java @@ -193,6 +193,7 @@ private APERunConfig(Builder builder) { setNoExecutions(builder.noExecutions); setNoGraphs(builder.noGraphs); setNoCWL(builder.noCWL); + setNoSnakemake(builder.noSnakemake); setUseWorkflowInput(builder.useWorkflowInput); setUseAllGeneratedData(builder.useAllGeneratedData); setDebugMode(builder.debugMode); @@ -710,6 +711,8 @@ public interface IBuildStage { IBuildStage withNoCWL(int noCWL); + IBuildStage withNoSnakemake(int noSnakemake); + IBuildStage withProgramInputs(List programInputs); IBuildStage withProgramOutputs(List programOutputs); @@ -741,6 +744,7 @@ public static final class Builder implements ISolutionMinLengthStage, ISolutionM private int noExecutions; private int noGraphs; private int noCWL; + private int noSnakemake; private List programInputs = Collections.emptyList(); private List programOutputs = Collections.emptyList(); private ConfigEnum useWorkflowInput; @@ -817,6 +821,12 @@ public IBuildStage withNoCWL(int noCWL) { return this; } + @Override + public IBuildStage withNoSnakemake(int noSnakemake) { + this.noSnakemake = noSnakemake; + return this; + } + @Override public IBuildStage withProgramInputs(List programInputs) { this.programInputs = programInputs; From 6da0e66734105e29f7d2d44517b513794a050436 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 12 Jan 2026 14:30:22 +0100 Subject: [PATCH 06/10] [FIX] Change variable names to CamelCase --- .../snakemake/SnakemakeCreator.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index e896efbc..f1566530 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -100,18 +100,18 @@ private void generateRuleInput(ModuleNode moduleNode) { .append(ind(1)) .append("input:\n"); - List valid_inputs = new ArrayList<>(); + List validInputs = new ArrayList<>(); // Create a path representation for each valid, i.e. non-empty, input type for (TypeNode node : moduleNode.getInputTypes()) { if (!node.isEmpty()) { - valid_inputs.add(String.format("%s'add-path/%s'", ind(2), workflowParameters.get(node.getNodeID()))); + validInputs.add(String.format("%s'add-path/%s'", ind(2), workflowParameters.get(node.getNodeID()))); } } // Join all valid input representations with a colon followed by a newline - snakemakeRepresentation.append(String.join(",\n", valid_inputs)).append("\n"); + snakemakeRepresentation.append(String.join(",\n", validInputs)).append("\n"); } /** @@ -124,24 +124,24 @@ private void generateRuleOutput(ModuleNode moduleNode) { .append(ind(1)) .append("output:\n"); - List valid_output = new ArrayList<>(); - int valid_output_id = 0; + List validOutput = new ArrayList<>(); + int validOutputId = 0; // Create a path representation for each valid, i.e. non-empty, output type for (TypeNode node : moduleNode.getOutputTypes()) { if (!node.isEmpty()) { // Increment the output id and generate the output name - valid_output_id += 1; - String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", valid_output_id); + validOutputId += 1; + String name = SolutionCreationUtils.generateInputOrOutputName(moduleNode, "out", validOutputId); addNewParameterToMap(node, String.format("%s", name)); - valid_output.add(String.format("%s'add-path/%s'", ind(2), name)); + validOutput.add(String.format("%s'add-path/%s'", ind(2), name)); } } // Join all valid output representations with a colon followed by a newline - snakemakeRepresentation.append(String.join(",\n", valid_output)).append("\n"); + snakemakeRepresentation.append(String.join(",\n", validOutput)).append("\n"); } /** From 8f149dd2281c6d49ac709ac2389d8971d526409f Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 15 Jan 2026 10:31:21 +0100 Subject: [PATCH 07/10] [Refactor] Extrapolate preparation of output directory as separate method and additionally make configuration checking for outputs consistent in all methods. --- src/main/java/nl/uu/cs/ape/APE.java | 170 +++++++++++++++------------- 1 file changed, 93 insertions(+), 77 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/APE.java b/src/main/java/nl/uu/cs/ape/APE.java index 2af3034e..818834dd 100644 --- a/src/main/java/nl/uu/cs/ape/APE.java +++ b/src/main/java/nl/uu/cs/ape/APE.java @@ -4,8 +4,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; - +import java.util.Arrays; +import java.util.Collection; +import java.util.SortedSet; import org.json.JSONException; import org.json.JSONObject; @@ -394,21 +395,23 @@ public static boolean writeSolutionToFile(SolutionsList allSolutions) throws IOE * @return true if the execution was successfully performed, false otherwise. */ public static boolean writeExecutableWorkflows(SolutionsList allSolutions, boolean createPartialImplementations) { - Path executionsFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Executables(); - Integer noExecutions = allSolutions.getRunConfiguration().getNoExecutions(); - if (executionsFolder == null || noExecutions == null || noExecutions == 0 || allSolutions.isEmpty()) { + if (allSolutions.isEmpty()) { + return false; + } + // Check the configuration before continuing. + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path executionsFolder = runConfig.getSolutionDirPath2Executables(); + int noExecutions = runConfig.getNoExecutions(); + if (executionsFolder == null || noExecutions == 0) { return false; } + APEUtils.printHeader(null, "Executing first " + noExecutions + " solution"); APEUtils.timerStart("executingWorkflows", true); - final File executeDir = executionsFolder.toFile(); - if (executeDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(executeDir, SolutionWorkflow.getFileNamePrefix(), "sh"); - } else { - executeDir.mkdir(); - } + /* Removing the existing files from the file system. */ + prepareOutputDirectory(executionsFolder, "sh"); + log.debug("Generating executable scripts."); /* Creating the requested scripts in parallel. */ @@ -465,31 +468,33 @@ public static boolean writeDataFlowGraphs(SolutionsList allSolutions) { * @return true if the generating was successfully performed, false otherwise. */ public static boolean writeDataFlowGraphs(SolutionsList allSolutions, RankDir orientation) { - Path graphsFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Figures(); - Integer noGraphs = allSolutions.getRunConfiguration().getNoGraphs(); - if (graphsFolder == null || noGraphs == null || noGraphs == 0 || allSolutions.isEmpty()) { - return false; - } + if (allSolutions.isEmpty()) { + return false; + } + // Check the configuration before continuing. + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path graphsFolder = runConfig.getSolutionDirPath2Figures(); + int noGraphs = runConfig.getNoGraphs(); + if (graphsFolder == null || noGraphs == 0) { + return false; + } + // Store whether we are in debug mode. + boolean debugMode = runConfig.getDebugMode(); + APEUtils.printHeader(null, "Generating graphical representation", "of the first " + noGraphs + " workflows"); APEUtils.timerStart("drawingGraphs", true); - /* Removing the existing files from the file system. */ - File graphDir = graphsFolder.toFile(); - if (graphDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(graphDir, SolutionWorkflow.getFileNamePrefix(), "png"); - } else { - graphDir.mkdir(); - } + /* Removing the existing files from the file system. */ + prepareOutputDirectory(graphsFolder, "png"); + log.debug("Generating data flow graphs (png)."); /* Creating the requested graphs in parallel. */ allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noGraphs).forEach(solution -> { try { String title = solution.getFileName(); Path path = graphsFolder.resolve(title); - solution.getDataflowGraph(title, orientation).write2File(path.toFile(), Format.PNG, - allSolutions.getRunConfiguration().getDebugMode()); + solution.getDataflowGraph(title, orientation).write2File(path.toFile(), Format.PNG, debugMode); } catch (IOException e) { log.error("Error occurred while data flow graphs (png) to the file system."); @@ -524,31 +529,32 @@ public static boolean writeTavernaDesignGraphs(SolutionsList allSolutions) { * @return true if the generating was successfully performed, false otherwise. */ public static boolean writeTavernaDesignGraphs(SolutionsList allSolutions, Format format) { - Path graphsFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Figures(); - Integer noGraphs = allSolutions.getRunConfiguration().getNoGraphs(); - if (graphsFolder == null || noGraphs == null || noGraphs == 0 || allSolutions.isEmpty()) { - return false; - } + if (allSolutions.isEmpty()) { + return false; + } + // Check the configuration before continuing. + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path graphsFolder = runConfig.getSolutionDirPath2Figures(); + int noGraphs = runConfig.getNoGraphs(); + if (graphsFolder == null || noGraphs == 0) { + return false; + } + // Store whether we are in debug mode. + boolean debugMode = runConfig.getDebugMode(); + APEUtils.printHeader(null, "Generating graphical representation", "of the first " + noGraphs + " workflows"); APEUtils.timerStart("drawingGraphs", true); - /* Removing the existing files from the file system. */ - File graphDir = graphsFolder.toFile(); - if (graphDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(graphDir, SolutionWorkflow.getFileNamePrefix(), format.fileExtension); - } else { - graphDir.mkdir(); - } + /* Removing the existing files from the file system. */ + prepareOutputDirectory(graphsFolder, format.fileExtension); log.debug("Generating data flow graphs (png)."); /* Creating the requested graphs in parallel. */ allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noGraphs).forEach(solution -> { try { String title = solution.getFileName(); Path path = graphsFolder.resolve(title); - solution.getTavernaStyleGraph(title).write2File(path.toFile(), format, - allSolutions.getRunConfiguration().getDebugMode()); + solution.getTavernaStyleGraph(title).write2File(path.toFile(), format, debugMode); } catch (IOException e) { log.error("Error occurred while data flow graphs (png) to the file system."); @@ -584,31 +590,32 @@ public static boolean writeControlFlowGraphs(SolutionsList allSolutions) { * @return true if the generating was successfully performed, false otherwise. */ public static boolean writeControlFlowGraphs(SolutionsList allSolutions, RankDir orientation) { - Path graphsFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Figures(); - Integer noGraphs = allSolutions.getRunConfiguration().getNoGraphs(); - if (graphsFolder == null || noGraphs == null || noGraphs == 0 || allSolutions.isEmpty()) { + if (allSolutions.isEmpty()) { + return false; + } + // Check the configuration before continuing. + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path graphsFolder = runConfig.getSolutionDirPath2Figures(); + int noGraphs = runConfig.getNoGraphs(); + if (graphsFolder == null || noGraphs == 0) { return false; } + // Store whether we are in debug mode. + boolean debugMode = runConfig.getDebugMode(); + APEUtils.printHeader(null, "Generating graphical representation", "of the first " + noGraphs + " workflows"); APEUtils.timerStart("drawingGraphs", true); /* Removing the existing files from the file system. */ - File graphDir = graphsFolder.toFile(); - if (graphDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(graphDir, SolutionWorkflow.getFileNamePrefix(), "png"); - } else { - graphDir.mkdir(); - } + prepareOutputDirectory(graphsFolder, "png"); log.debug("Generating control flow graphs (png)."); /* Creating the requested graphs in parallel. */ allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noGraphs).forEach(solution -> { try { String title = solution.getFileName(); Path path = graphsFolder.resolve(title); - solution.getControlflowGraph(title, orientation).write2File(path.toFile(), Format.PNG, - allSolutions.getRunConfiguration().getDebugMode()); + solution.getControlflowGraph(title, orientation).write2File(path.toFile(), Format.PNG, debugMode); } catch (IOException e) { log.error("Error occurred while writing control flow graphs (png) to the file system."); @@ -630,28 +637,23 @@ public static boolean writeControlFlowGraphs(SolutionsList allSolutions, RankDir * @return true if the execution was successfully performed, false otherwise. */ public static boolean writeCWLWorkflows(SolutionsList allSolutions, boolean createPartialImplementations) { - if (allSolutions.isEmpty()) { return false; } // Check the configuration before continuing. - Path cwlFolder = allSolutions.getRunConfiguration().getSolutionDirPath2CWL(); - int noCWLFiles = allSolutions.getRunConfiguration().getNoCWL(); - if (cwlFolder == null || noCWLFiles == 0 || allSolutions.isEmpty()) { + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path cwlFolder = runConfig.getSolutionDirPath2CWL(); + int noCWLFiles = runConfig.getNoCWL(); + if (cwlFolder == null || noCWLFiles == 0) { return false; } + final String timerID = "writingCWL"; APEUtils.printHeader(null, String.format("Writing the first %d solution(s) to CWL files", noCWLFiles)); APEUtils.timerStart(timerID, true); - final File cwlDir = cwlFolder.toFile(); - if (cwlDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(cwlDir, SolutionWorkflow.getFileNamePrefix(), "cwl"); - } else { - // Create the CWL directory if it does not already exist - cwlDir.mkdir(); - } + /* Removing the existing files from the file system. */ + prepareOutputDirectory(cwlFolder, "cwl"); log.debug("Generating CWL files."); // Write the CWL files @@ -715,8 +717,10 @@ public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions, boolea if (allSolutions.isEmpty()) { return false; } - Path snakemakeFolder = allSolutions.getRunConfiguration().getSolutionDirPath2Snakemake(); - int noSnakemakeFiles = allSolutions.getRunConfiguration().getNoSnakemake(); + // Check the configuration before continuing. + APERunConfig runConfig = allSolutions.getRunConfiguration(); + Path snakemakeFolder = runConfig.getSolutionDirPath2Snakemake(); + int noSnakemakeFiles = runConfig.getNoSnakemake(); if (snakemakeFolder == null || noSnakemakeFiles == 0) { return false; } @@ -725,14 +729,8 @@ public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions, boolea APEUtils.printHeader(null, String.format("Writing the first %d solution(s) to Snakemake files", noSnakemakeFiles)); APEUtils.timerStart(timerID, true); - final File snakemakeDir = snakemakeFolder.toFile(); - if (snakemakeDir.isDirectory()) { - // If the directory already exists, empty it first - deleteExistingFiles(snakemakeDir, SolutionWorkflow.getFileNamePrefix(), "snakefile"); - } else { - // Create the Snakemake directory if it does not already exist - snakemakeDir.mkdir(); - } + /* Removing the existing files from the file system. */ + prepareOutputDirectory(snakemakeFolder, "snakefile"); log.debug("Generating Snakemake files."); allSolutions.getParallelStream().filter(solution -> solution.getIndex() < noSnakemakeFiles).forEach(solution -> { @@ -769,7 +767,25 @@ public static boolean writeSnakemakeWorkflows(SolutionsList allSolutions, boolea return true; } - /** + /** + * Prepares the output directory by removing all files with the given extension + * and creating the output directory if it does not exist. + * + * @param basePath The base path to create or clean up. + * @param fileExtension The extension of existing files to delete. + */ + public static void prepareOutputDirectory(Path basePath, String fileExtension) { + final File baseDir = basePath.toFile(); + if (baseDir.isDirectory()) { + // If the directory already exists, empty it first + deleteExistingFiles(baseDir, SolutionWorkflow.getFileNamePrefix(), fileExtension); + } else { + baseDir.mkdir(); + } + } + + + /** * Delete all files in the given directory that start with the given prefix and * have the given extension. * From 5b3e3ac36db3ac56d90c3e085da22b77570ace22 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Thu, 15 Jan 2026 16:04:55 +0100 Subject: [PATCH 08/10] [Update] Extrapolate `getWorkflowName` and remove trailing prime symbol. --- .../solutionStructure/SolutionCreationUtils.java | 10 ++++++++++ .../solutionStructure/cwl/CWLWorkflowBase.java | 13 ++----------- .../snakemake/SnakemakeCreator.java | 13 +++---------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java index a3612535..cb13c9a9 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/SolutionCreationUtils.java @@ -22,6 +22,16 @@ public static String stepName(ModuleNode moduleNode) { return String.format("%s_%02d", moduleNode.getUsedModule().getPredicateLabel(), stepNumber); } + /** + * Get the name of the workflow. + * + * @param solution The solution workflow. + * @return The name of the workflow. + */ + public static String getWorkflowName(SolutionWorkflow solution) { + return String.format("WorkflowNo_%02d", solution.getIndex()+1); + } + /** * Generate the name of the input or output of a step's run input or output. diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java index c0d9b906..b2d97133 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/cwl/CWLWorkflowBase.java @@ -67,7 +67,7 @@ public String generate() { // Label and doc cwlRepresentation.append("\n"); - cwlRepresentation.append("label: ").append(getWorkflowName()).append("\n"); + cwlRepresentation.append("label: ").append(SolutionCreationUtils.getWorkflowName(solution)).append("\n"); generateDoc(); generateCWLRepresentation(); @@ -78,19 +78,10 @@ public String generate() { * Adds the comment at the top of the file. */ protected void generateTopComment() { - cwlRepresentation.append(String.format("# %s%n", getWorkflowName())); + cwlRepresentation.append(String.format("# %s%n", SolutionCreationUtils.getWorkflowName(solution))); cwlRepresentation.append("# This workflow is generated by APE (https://github.com/workflomics/ape).\n"); } - /** - * Get the name of the workflow. - * - * @return The name of the workflow. - */ - private String getWorkflowName() { - return String.format("WorkflowNo_%d", solution.getIndex()); - } - /** * Generate the workflow description. */ diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index f1566530..cc446ce5 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -46,7 +46,7 @@ public String generateSnakemakeRepresentation() { addWorkflowInputs(); generateRuleAll(); for (ModuleNode moduleNode : solution.getModuleNodes()) { - snakemakeRepresentation.append(String.format("rule %s:\n'", SolutionCreationUtils.stepName(moduleNode))); + snakemakeRepresentation.append(String.format("rule %s:\n", SolutionCreationUtils.stepName(moduleNode))); generateRuleInput(moduleNode); generateRuleOutput(moduleNode); generateRuleShell(moduleNode); @@ -174,18 +174,11 @@ private String addNewParameterToMap(TypeNode typeNode, String name) { * Adds the comment at the top of the file. */ protected void generateTopComment() { - snakemakeRepresentation.append(String.format("# %s%n", getWorkflowName())); + snakemakeRepresentation.append(String.format("# %s%n", SolutionCreationUtils.getWorkflowName(solution))); snakemakeRepresentation.append("# This workflow is generated by APE (https://github.com/workflomics/ape).\n"); } - /** - * Get the name of the workflow. - * - * @return The name of the workflow. - */ - private String getWorkflowName() { - return String.format("WorkflowNo_%d", solution.getIndex()); - } + /** * Generate the indentation at the start of a line. From 15fa60f1de177369013c1fecca76415572f5720e Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 9 Feb 2026 10:43:34 +0100 Subject: [PATCH 09/10] [FIX] Resolving import issues. --- src/main/java/nl/uu/cs/ape/APE.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/nl/uu/cs/ape/APE.java b/src/main/java/nl/uu/cs/ape/APE.java index 818834dd..8108d72f 100644 --- a/src/main/java/nl/uu/cs/ape/APE.java +++ b/src/main/java/nl/uu/cs/ape/APE.java @@ -4,9 +4,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; -import java.util.SortedSet; +import java.util.*; import org.json.JSONException; import org.json.JSONObject; From f31c83f0853632c8327775c9f49291581b794d51 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 9 Feb 2026 10:58:48 +0100 Subject: [PATCH 10/10] [FIX] Removing stale placeholder from logging info. --- .../solver/solutionStructure/snakemake/SnakemakeCreator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java index cc446ce5..94ab34fb 100644 --- a/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java +++ b/src/main/java/nl/uu/cs/ape/solver/solutionStructure/snakemake/SnakemakeCreator.java @@ -153,7 +153,7 @@ private void generateRuleShell(ModuleNode moduleNode) { String moduleName = moduleNode.getUsedModule().getPredicateLabel(); String command = moduleNode.getUsedModule().getExecutionCommand(); if (command == null || command.equals("")) { - log.info("No command for {} specified, using this name as fallback command \"{}\"", moduleName); + log.info("No command for {} specified, using this name as fallback command.", moduleName); command = moduleName; } snakemakeRepresentation