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 dc73cb46757c CAMEL-23236: Add doc --example flag and camel-kit shell
banner hint (#23347)
dc73cb46757c is described below
commit dc73cb46757cee7a5e0da2357e78332d9484520d
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed May 20 08:17:19 2026 +0200
CAMEL-23236: Add doc --example flag and camel-kit shell banner hint (#23347)
- Add --example flag to camel doc that generates a minimal working
YAML route snippet for any component (consumer or producer)
- Add camel-kit discoverability hint in shell banner when no routes
are found in the current directory
- Add unit tests for the new --example flag
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../ROOT/pages/jbang-commands/camel-jbang-doc.adoc | 1 +
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../camel/dsl/jbang/core/commands/Shell.java | 1 +
.../jbang/core/commands/catalog/CatalogDoc.java | 87 +++++++++++++++++++++-
.../core/commands/catalog/CatalogDocTest.java | 82 ++++++++++++++++++++
5 files changed, 170 insertions(+), 3 deletions(-)
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
index d2049ffdf83d..9171fdbb2c4e 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-doc.adoc
@@ -21,6 +21,7 @@ camel doc [options]
| Option | Description | Default | Type
| `--camel-version` | To use a different Camel version than the default
version | | String
| `--download` | Whether to allow automatic downloading JAR dependencies (over
the internet) | true | boolean
+| `--example` | Prints a minimal working YAML route snippet for the component
| false | boolean
| `--filter` | Filter option listed in tables by name, description, or group |
| String
| `--header` | Whether to display component message headers | false | boolean
| `--kamelets-version` | Apache Camel Kamelets version |
RuntimeType.KAMELETS_VERSION | String
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 24545f91b2f4..055c7288eedb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -8,7 +8,7 @@
{ "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
{ "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
- { "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
+ { "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
{ "name": "doctor", "fullName": "doctor", "description": "Checks the
environment and reports potential issues", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "eval", "fullName": "eval", "description": "Evaluate Camel
expressions and scripts", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression",
"fullName": "eval expression", "description": "Evaluates Camel expression",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
{ "name": "explain", "fullName": "explain", "description": "Explain what a
Camel route does using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", "javaTyp [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
index 01e919e74a98..da0427070077 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Shell.java
@@ -159,6 +159,7 @@ public class Shell extends CamelCommand {
writer.println(" Quick start: init MyRoute.yaml && run *");
writer.println(" Templates: init --list");
writer.println(" Docs: doc <component>");
+ writer.println(" AI scaffold: plugin add kit");
writer.println(" Need help? help");
} else {
writer.printf("Found %d route file(s) in current directory.%n",
routeCount);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
index eb487dbd275f..70cbb7ea651b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.catalog;
import java.awt.*;
import java.net.URI;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -58,7 +59,8 @@ import static
org.apache.camel.dsl.jbang.core.commands.catalog.CatalogBaseComman
" camel doc kafka",
" camel doc timer",
" camel doc aws-s3-source",
- " camel doc jsonpath" })
+ " camel doc jsonpath",
+ " camel doc kafka --example" })
public class CatalogDoc extends CamelCommand {
@CommandLine.Parameters(description = "Name of kamelet, component,
dataformat, or other Camel resource",
@@ -109,6 +111,10 @@ public class CatalogDoc extends CamelCommand {
description = "Whether to display component message
headers", defaultValue = "false")
boolean headers;
+ @CommandLine.Option(names = { "--example" },
+ description = "Prints a minimal working YAML route
snippet for the component", defaultValue = "false")
+ boolean example;
+
@CommandLine.Option(names = {
"--kamelets-version" }, description = "Apache Camel Kamelets
version",
defaultValue = RuntimeType.KAMELETS_VERSION)
@@ -161,7 +167,11 @@ public class CatalogDoc extends CamelCommand {
if (prefix == null || "component".equals(prefix)) {
ComponentModel cm = catalog.componentModel(name);
if (cm != null) {
- docComponent(cm);
+ if (example) {
+ printExample(cm);
+ } else {
+ docComponent(cm);
+ }
return 0;
}
}
@@ -226,6 +236,79 @@ public class CatalogDoc extends CamelCommand {
return 1;
}
+ private void printExample(ComponentModel cm) {
+ String scheme = cm.getScheme();
+ String uri = buildExampleUri(cm);
+
+ printer().println("# Example: " + scheme + " component");
+
+ if (cm.isProducerOnly()) {
+ printer().println("- route:");
+ printer().println(" from:");
+ printer().println(" uri: \"timer:tick\"");
+ printer().println(" parameters:");
+ printer().println(" period: 1000");
+ printer().println(" steps:");
+ printer().println(" - to:");
+ printer().println(" uri: \"" + uri + "\"");
+ } else {
+ printer().println("- route:");
+ printer().println(" from:");
+ printer().println(" uri: \"" + uri + "\"");
+ printer().println(" steps:");
+ printer().println(" - to:");
+ printer().println(" uri: \"log:" + scheme + "\"");
+ }
+
+ printer().println();
+ printer().println("# Save to a file and run with:");
+ printer().println("# camel run my-route.yaml");
+ }
+
+ private String buildExampleUri(ComponentModel cm) {
+ String scheme = cm.getScheme();
+ List<ComponentModel.EndpointOptionModel> pathOptions =
cm.getEndpointPathOptions();
+ if (pathOptions.isEmpty()) {
+ return scheme;
+ }
+ List<String> pathValues = new ArrayList<>();
+ for (ComponentModel.EndpointOptionModel opt : pathOptions) {
+ Object dv = opt.getDefaultValue();
+ if (dv != null && !dv.toString().isEmpty()) {
+ pathValues.add(dv.toString());
+ } else if (opt.getEnums() != null && !opt.getEnums().isEmpty()) {
+ pathValues.add(opt.getEnums().get(0));
+ } else {
+ pathValues.add("my" + capitalize(opt.getName()));
+ }
+ }
+ String syntax = cm.getSyntax();
+ if (syntax != null) {
+ String result = syntax;
+ for (ComponentModel.EndpointOptionModel opt : pathOptions) {
+ Object dv = opt.getDefaultValue();
+ String value;
+ if (dv != null && !dv.toString().isEmpty()) {
+ value = dv.toString();
+ } else if (opt.getEnums() != null &&
!opt.getEnums().isEmpty()) {
+ value = opt.getEnums().get(0);
+ } else {
+ value = "my" + capitalize(opt.getName());
+ }
+ result = result.replace(opt.getName(), value);
+ }
+ return result;
+ }
+ return scheme + ":" + String.join("/", pathValues);
+ }
+
+ private static String capitalize(String s) {
+ if (s == null || s.isEmpty()) {
+ return s;
+ }
+ return Character.toUpperCase(s.charAt(0)) + s.substring(1);
+ }
+
private void docKamelet(KameletModel km) throws Exception {
String link = websiteLink("kamelet", name, kameletsVersion);
if (openUrl) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
new file mode 100644
index 000000000000..04adf28cb320
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.catalog;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class CatalogDocTest extends CamelCommandBaseTestSupport {
+
+ @Test
+ public void shouldPrintExampleForConsumerComponent() throws Exception {
+ CatalogDoc command = new CatalogDoc(new
CamelJBangMain().withPrinter(printer));
+ command.name = "timer";
+ command.example = true;
+
+ int exit = command.doCall();
+
+ Assertions.assertEquals(0, exit);
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.contains("from:"), "Should have a from
clause");
+ Assertions.assertTrue(output.contains("timer"), "Should reference the
timer component");
+ Assertions.assertTrue(output.contains("log:"), "Consumer components
should route to log");
+ }
+
+ @Test
+ public void shouldPrintExampleForProducerOnlyComponent() throws Exception {
+ CatalogDoc command = new CatalogDoc(new
CamelJBangMain().withPrinter(printer));
+ command.name = "log";
+ command.example = true;
+
+ int exit = command.doCall();
+
+ Assertions.assertEquals(0, exit);
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.contains("timer:tick"), "Producer-only
should use timer as source");
+ Assertions.assertTrue(output.contains("log"), "Should reference the
log component");
+ }
+
+ @Test
+ public void shouldPrintExampleForKafka() throws Exception {
+ CatalogDoc command = new CatalogDoc(new
CamelJBangMain().withPrinter(printer));
+ command.name = "kafka";
+ command.example = true;
+
+ int exit = command.doCall();
+
+ Assertions.assertEquals(0, exit);
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.contains("kafka:"), "Should contain kafka
URI");
+ Assertions.assertTrue(output.contains("route:"), "Should have route
structure");
+ Assertions.assertTrue(output.contains("camel run"), "Should show run
instructions");
+ }
+
+ @Test
+ public void shouldPrintStandardDocWithoutExampleFlag() throws Exception {
+ CatalogDoc command = new CatalogDoc(new
CamelJBangMain().withPrinter(printer));
+ command.name = "timer";
+
+ int exit = command.doCall();
+
+ Assertions.assertEquals(0, exit);
+ String output = printer.getOutput();
+ Assertions.assertTrue(output.contains("Component Name: timer"),
"Should show standard doc");
+ Assertions.assertFalse(output.contains("camel run my-route.yaml"),
"Should not show example run hint");
+ }
+}