package io.trygvis.rules.engine.cli; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.file.Path; import java.util.Objects; import java.util.concurrent.Callable; import java.util.stream.Collectors; import static io.trygvis.rules.engine.cli.EngineFile.*; import static picocli.CommandLine.Command; @Command(name = "ninja") public class NinjaCommand implements Callable { public File basedir = null; public Path basepath; @Override public Integer call() throws Exception { basepath = Objects.requireNonNullElseGet(basedir, () -> new File("").getAbsoluteFile()).toPath(); var factory = new YAMLFactory(); factory.enable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); factory.enable(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID); var mapper = new ObjectMapper(factory); mapper.enable(MapperFeature.AUTO_DETECT_FIELDS); var f = mapper.readValue(new File(basedir, "engine.yaml"), EngineFile.class); Path dbDir; if (StringUtils.trimToNull(f.dbDir) == null) { System.err.println("Missing required field: dbDir"); return 1; } else { dbDir = Path.of(f.dbDir); } for (var job : f.jobs) { if (job.outputs.isEmpty()) { var output = new Output(); output.name = job.name; output.includes = job.outputIncludes; job.outputIncludes = null; job.outputs.add(output); } } if (false) { System.out.println("----------------------------------------------------"); System.out.println(f); System.out.println("----------------------------------------------------"); } var buf = new StringWriter(); var out = new PrintWriter(buf); out.println("# Generated"); out.println("#"); out.println("### engine.ninja"); out.println(""); out.println("rule engine-yaml-to-ninja"); out.println(" command = engine ninja"); out.println(""); out.println("build engine.ninja: engine-yaml-to-ninja engine.yaml"); out.println(""); out.println("### engine.png"); out.println(""); out.println("rule ninja-to-dot"); out.println(" command = ninja -t graph > $out"); out.println(""); out.println("rule dot-to-png"); out.println(" command = dot -Tpng < $in > $out"); out.println(""); out.println("build engine.dot: ninja-to-dot build.ninja engine.ninja"); out.println(""); out.println("build engine.png: dot-to-png engine.dot"); out.println(""); out.println("rule engine-run"); out.println(" command=engine run $name $inputs $output_state $output_includes $generated_output $agenda_groups $modules"); out.println(); out.println("### Jobs"); for (var job : f.jobs) { out.println(""); var dependencies = job.inputs.stream() .map(s -> dbDir.resolve(s + ".yaml").toString()) .collect(Collectors.joining(" ")); var generated = job.outputs.stream() .map(output -> dbDir.resolve(output.name + ".yaml").toString()) .collect(Collectors.joining(" ")); out.println("build %s: engine-run %s".formatted(generated, dependencies)); out.println(" name=--name %s".formatted(job.name)); if (!job.inputs.isEmpty()) { var is = job.inputs.stream() .map(s -> "--input=" + dbDir.resolve(s + ".yaml")) .collect(Collectors.joining(" $\n ", "\n ", "")); out.println(" inputs=$%s".formatted(is)); } if (job.outputs.size() != 1) { System.err.println("Only one output supported for now."); return 1; } var output = job.outputs.get(0); out.println(" output_state=--output-state %s".formatted(dbDir.resolve(output.name + ".yaml"))); if (!output.includes.isEmpty()) { var str = output.includes.stream() .map(s -> "--output-include='" + s + "'") .collect(Collectors.joining(" $\n ", "\n ", "")); out.println(" output_includes=$%s".formatted(str)); } if (job.generatedOutput != null) { out.println(" generated_output=--generated-output %s".formatted(fixPath(job.generatedOutput))); } if (!job.agendaGroups.isEmpty()) { var ag = job.agendaGroups.stream() .map(s -> "--agenda-group=" + s) .collect(Collectors.joining(" $\n ", "\n ", "")); out.println(" agenda_groups=%s".formatted(ag)); } if (!job.modules.isEmpty()) { var ms = job.modules.stream() .map(this::fixPath) .map(s -> "--module=" + s) .collect(Collectors.joining(" $\n ", "\n ", "")); out.println(" modules=$%s".formatted(ms)); } } var ninjaFile = new File(basedir, "engine.ninja"); try (var writer = new FileWriter(ninjaFile)) { writer.write(buf.toString()); } return 0; } private String fixPath(String s) { if (s.startsWith("$MODULE_HOME/")) { s = "$" + s; } var p = Path.of(s); if (p.isAbsolute()) { s = basepath.relativize(Path.of(s)).toString(); } if (s.contains("*")) { s = "$$(echo " + s + ")"; } return s; } }