This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 61c84da8fcce CAMEL-23226: Add init --list and export --dry-run to 
camel-jbang
61c84da8fcce is described below

commit 61c84da8fcced831502a262626fd823c648a51f5
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Mar 23 11:20:12 2026 +0100

    CAMEL-23226: Add init --list and export --dry-run to camel-jbang
    
    - Add `camel init --list` to show available templates grouped by category
    - Add `camel export --dry-run` to preview export without writing files
    - Shows file listing with human-readable sizes from a temp directory
    - Properly restores state (exportDir, quiet, cleanExportDir, dryRun) in 
finally block
---
 .../camel/dsl/jbang/core/commands/Export.java      |  1 +
 .../dsl/jbang/core/commands/ExportBaseCommand.java | 82 ++++++++++++++++++++++
 .../apache/camel/dsl/jbang/core/commands/Init.java | 57 ++++++++++++++-
 .../camel/dsl/jbang/core/commands/InitTest.java    | 34 +++++++++
 4 files changed, 173 insertions(+), 1 deletion(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
index 3f3c7064ea5d..02e77c2e5b6b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
@@ -256,6 +256,7 @@ public class Export extends ExportBaseCommand {
         cmd.groovyPrecompiled = this.groovyPrecompiled;
         cmd.hawtio = this.hawtio;
         cmd.hawtioVersion = this.hawtioVersion;
+        cmd.dryRun = this.dryRun;
         // run export
         return cmd.export();
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index ac866cf3ddc4..ffc0742a097e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -27,6 +27,7 @@ import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -266,6 +267,10 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
                         description = "Verbose output of startup activity 
(dependency resolution and downloading")
     protected boolean verbose;
 
+    @CommandLine.Option(names = { "--dry-run" }, defaultValue = "false",
+                        description = "Preview export without writing files")
+    protected boolean dryRun;
+
     @CommandLine.Option(names = { "--ignore-loading-error" }, defaultValue = 
"false",
                         description = "Whether to ignore route loading and 
compilation errors (use this with care!)")
     protected boolean ignoreLoadingError;
@@ -311,10 +316,87 @@ public abstract class ExportBaseCommand extends 
CamelCommand {
         if (!quiet) {
             printConfigurationValues("Exporting integration with the following 
configuration:");
         }
+
+        if (dryRun) {
+            return doDryRunExport();
+        }
+
         // export
         return export();
     }
 
+    /**
+     * Performs a dry-run export: runs the export to a temporary directory, 
lists the files that would be generated, and
+     * cleans up.
+     */
+    private Integer doDryRunExport() throws Exception {
+        // Save original state
+        String originalExportDir = this.exportDir;
+        boolean originalCleanExportDir = this.cleanExportDir;
+        boolean originalQuiet = this.quiet;
+        Path tempDir = Files.createTempDirectory("camel-export-dry-run-");
+        try {
+            // Redirect export to temp directory
+            this.exportDir = tempDir.toString();
+            this.cleanExportDir = false;
+            this.dryRun = false; // avoid recursion in subclasses
+            this.quiet = true; // suppress normal output
+
+            Integer result = export();
+
+            // Restore quiet so output works correctly
+            this.quiet = originalQuiet;
+
+            printer().println("Dry-run export preview:");
+            printer().println();
+            printer().println("Target directory: " + (originalExportDir != 
null ? originalExportDir : "."));
+            printer().println();
+            printer().println("Files that would be created:");
+            try (Stream<Path> walk = Files.walk(tempDir)) {
+                walk.filter(Files::isRegularFile)
+                        .sorted()
+                        .forEach(p -> {
+                            Path rel = tempDir.relativize(p);
+                            try {
+                                long size = Files.size(p);
+                                printer().printf("  %s (%s)%n", rel, 
humanReadableSize(size));
+                            } catch (IOException e) {
+                                printer().printf("  %s%n", rel);
+                            }
+                        });
+            }
+
+            return result;
+        } finally {
+            // Restore original state
+            this.exportDir = originalExportDir;
+            this.cleanExportDir = originalCleanExportDir;
+            this.quiet = originalQuiet;
+            this.dryRun = true;
+            // Clean up temp directory
+            try (Stream<Path> walk = Files.walk(tempDir)) {
+                walk.sorted(Comparator.reverseOrder())
+                        .forEach(p -> {
+                            try {
+                                Files.deleteIfExists(p);
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        });
+            }
+        }
+    }
+
+    private static String humanReadableSize(long bytes) {
+        if (bytes < 1024) {
+            return bytes + " bytes";
+        } else if (bytes < 1024 * 1024) {
+            return String.format("%.1f KB", bytes / 1024.0);
+        } else {
+            return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
+        }
+    }
+
     protected static String mavenRepositoriesAsPomXml(String repos) {
         StringBuilder sb = new StringBuilder();
         int i = 1;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
index 4100a11e70a6..1a0f967b8bff 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java
@@ -22,6 +22,8 @@ import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Stack;
 import java.util.StringJoiner;
 
@@ -49,11 +51,14 @@ import static 
org.apache.camel.dsl.jbang.core.common.GitHubHelper.fetchGithubUrl
          sortOptions = false, showDefaultValues = true)
 public class Init extends CamelCommand {
 
-    @Parameters(description = "Name of integration file (or a github link)", 
arity = "1",
+    @Parameters(description = "Name of integration file (or a github link)", 
arity = "0..1",
                 paramLabel = "<file>", parameterConsumer = FileConsumer.class)
     private Path filePath; // Defined only for file path completion; the field 
never used
     private String file;
 
+    @Option(names = { "--list" }, description = "List available templates")
+    private boolean list;
+
     @Option(names = {
             "--dir",
             "--directory" }, description = "Directory relative path where the 
new Camel integration will be saved",
@@ -86,6 +91,13 @@ public class Init extends CamelCommand {
 
     @Override
     public Integer doCall() throws Exception {
+        if (list) {
+            return listTemplates();
+        }
+        if (file == null) {
+            printer().printErr("Missing required parameter: <file>");
+            return 1;
+        }
         int code = execute();
         if (code == 0) {
             // In case of successful execution, we create the working 
directory if it does not exist to help the tooling
@@ -216,6 +228,49 @@ public class Init extends CamelCommand {
         return packageDeclaration;
     }
 
+    private int listTemplates() {
+        // Templates grouped by category with descriptions
+        // Only include user-facing templates (not POM/Dockerfile/internal 
templates)
+        Map<String, Map<String, String>> categories = new LinkedHashMap<>();
+
+        Map<String, String> routes = new LinkedHashMap<>();
+        routes.put("java", "Java DSL route (MyRoute.java)");
+        routes.put("xml", "XML DSL route (my-route.xml)");
+        routes.put("yaml", "YAML DSL route (my-route.yaml)");
+        categories.put("Routes", routes);
+
+        Map<String, String> kamelets = new LinkedHashMap<>();
+        kamelets.put("kamelet-source.yaml", "Kamelet source connector 
(my-source.kamelet.yaml)");
+        kamelets.put("kamelet-sink.yaml", "Kamelet sink connector 
(my-sink.kamelet.yaml)");
+        kamelets.put("kamelet-action.yaml", "Kamelet action processor 
(my-action.kamelet.yaml)");
+        categories.put("Kamelets", kamelets);
+
+        Map<String, String> pipes = new LinkedHashMap<>();
+        pipes.put("init-pipe.yaml", "Pipe CR connecting source and sink 
(my-pipe.yaml --pipe)");
+        pipes.put("pipe.yaml", "Pipe resource (my-pipe.pipe.yaml)");
+        pipes.put("integration.yaml", "Integration CR 
(my-integration.integration.yaml)");
+        categories.put("Pipes and CRs", pipes);
+
+        Map<String, String> restDsl = new LinkedHashMap<>();
+        restDsl.put("rest-dsl.yaml", "REST DSL with OpenAPI 
(my-api.rest-dsl.yaml)");
+        categories.put("REST", restDsl);
+
+        printer().println("Available templates for 'camel init':");
+        printer().println();
+        for (Map.Entry<String, Map<String, String>> category : 
categories.entrySet()) {
+            printer().println(category.getKey() + ":");
+            for (Map.Entry<String, String> template : 
category.getValue().entrySet()) {
+                printer().printf("  %-25s %s%n", template.getKey(), 
template.getValue());
+            }
+            printer().println();
+        }
+        printer().println("Usage: camel init <filename>.<ext>");
+        printer().println("The template is selected based on the file 
extension.");
+        printer().println("Example: camel init MyRoute.java");
+
+        return 0;
+    }
+
     private void createWorkingDirectoryIfAbsent() {
         Path work = CommandLineHelper.getWorkDir();
         if (!Files.exists(work)) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
index aed11b75f115..50b4ff4ca86c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java
@@ -23,12 +23,14 @@ import java.nio.file.Paths;
 import java.util.List;
 
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import picocli.CommandLine;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class InitTest {
@@ -122,4 +124,36 @@ class InitTest {
         assertEquals("import org.apache.camel.builder.RouteBuilder;", 
lines.get(0));
         Files.delete(f);
     }
+
+    @Test
+    void initListTemplates() throws Exception {
+        StringPrinter printer = new StringPrinter();
+        Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+        CommandLine.populateCommand(initCommand, "--list");
+
+        int exit = initCommand.doCall();
+
+        assertEquals(0, exit);
+        String output = printer.getOutput();
+        assertTrue(output.contains("Available templates"), "Should contain 
header");
+        assertTrue(output.contains("Routes:"), "Should contain Routes 
category");
+        assertTrue(output.contains("Kamelets:"), "Should contain Kamelets 
category");
+        assertTrue(output.contains("java"), "Should list java template");
+        assertTrue(output.contains("yaml"), "Should list yaml template");
+        assertTrue(output.contains("xml"), "Should list xml template");
+        // Should not list internal templates
+        assertFalse(output.contains("Dockerfile"), "Should not list Dockerfile 
templates");
+        assertFalse(output.contains("pom"), "Should not list POM templates");
+    }
+
+    @Test
+    void initWithoutFileAndWithoutList() throws Exception {
+        StringPrinter printer = new StringPrinter();
+        Init initCommand = new Init(new CamelJBangMain().withPrinter(printer));
+        CommandLine.populateCommand(initCommand);
+
+        int exit = initCommand.doCall();
+
+        assertEquals(1, exit);
+    }
 }

Reply via email to