This is an automated email from the ASF dual-hosted git repository.
davsclaus 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 c72d1497f968 CAMEL-23654: camel-jbang - group commands in --help
output by category
c72d1497f968 is described below
commit c72d1497f968d65ba060e569233bfa1e70b3dc90
Author: Shashi Kumar Reddy <[email protected]>
AuthorDate: Sun Jun 28 12:18:33 2026 +0530
CAMEL-23654: camel-jbang - group commands in --help output by category
Group the 40+ camel CLI subcommands into functional categories
(Running, Monitoring, Actions, Development, Configuration, Catalog, AI)
using a picocli IHelpSectionRenderer, replacing the flat alphabetical
list so commands are easier to find. Ungrouped commands fall into "Other".
Closes #24276
---
.../dsl/jbang/core/commands/CamelJBangMain.java | 4 +
.../core/commands/GroupedCommandHelpRenderer.java | 100 +++++++++++++++++++++
.../commands/GroupedCommandHelpRendererTest.java | 98 ++++++++++++++++++++
3 files changed, 202 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index f0e26bd88823..1b3f9de7dc29 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -228,6 +228,10 @@ public class CamelJBangMain implements Callable<Integer> {
.addSubcommand("wrapper", new CommandLine(new
WrapperCommand(this)))
.setParameterExceptionHandler(new
MissingPluginParameterExceptionHandler());
+ commandLine.getHelpSectionMap().put(
+ CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST,
+ new GroupedCommandHelpRenderer());
+
postAddCommands(commandLine, args);
if (discoverPlugins && PluginHelper.shouldDiscoverPlugins(commandLine,
args)) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRenderer.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRenderer.java
new file mode 100644
index 000000000000..72c7a2e6081a
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRenderer.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import picocli.CommandLine;
+
+/**
+ * Renders the camel CLI subcommand list grouped by functional category,
replacing the default flat alphabetical list.
+ */
+public class GroupedCommandHelpRenderer implements
CommandLine.IHelpSectionRenderer {
+
+ private static final Map<String, List<String>> GROUPS = new
LinkedHashMap<>();
+
+ static {
+ GROUPS.put("Running", List.of("run", "dev", "stop", "restart", "ps",
"log", "shell", "script"));
+ GROUPS.put("Monitoring", List.of("get", "top", "trace", "hawtio",
"jolokia"));
+ GROUPS.put("Actions", List.of("cmd", "bind"));
+ GROUPS.put("Development",
+ List.of("init", "export", "debug", "eval", "explain",
"transform", "dirty", "doctor", "sbom", "nano"));
+ GROUPS.put("Configuration", List.of("config", "dependency", "version",
"update", "wrapper", "completion", "plugin"));
+ GROUPS.put("Catalog", List.of("catalog", "doc", "infra"));
+ GROUPS.put("AI", List.of("ask", "harden"));
+ }
+
+ @Override
+ public String render(CommandLine.Help help) {
+ Map<String, CommandLine> subcommands =
help.commandSpec().subcommands();
+ if (subcommands.isEmpty()) {
+ return "";
+ }
+
+ int nameWidth =
subcommands.keySet().stream().mapToInt(String::length).max().orElse(10);
+
+ List<String> assigned = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+
+ for (Map.Entry<String, List<String>> entry : GROUPS.entrySet()) {
+ List<CommandLine> present = new ArrayList<>();
+ for (String name : entry.getValue()) {
+ CommandLine sub = subcommands.get(name);
+ if (sub != null) {
+ // don't print hidden commands (picocli hides them too),
but still
+ // count them as assigned so they don't pop up under
"Other"
+ if (!sub.getCommandSpec().usageMessage().hidden()) {
+ present.add(sub);
+ }
+ assigned.add(name);
+ }
+ }
+ if (!present.isEmpty()) {
+ sb.append(String.format("%n %s:%n", entry.getKey()));
+ for (CommandLine sub : present) {
+ appendCommand(sb, sub, nameWidth);
+ }
+ }
+ }
+
+ List<CommandLine> ungrouped = new ArrayList<>();
+ for (Map.Entry<String, CommandLine> entry : subcommands.entrySet()) {
+ if (!assigned.contains(entry.getKey())
+ &&
!entry.getValue().getCommandSpec().usageMessage().hidden()) {
+ ungrouped.add(entry.getValue());
+ }
+ }
+ if (!ungrouped.isEmpty()) {
+ sb.append(String.format("%n Other:%n"));
+ for (CommandLine sub : ungrouped) {
+ appendCommand(sb, sub, nameWidth);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static void appendCommand(StringBuilder sb, CommandLine sub, int
nameWidth) {
+ String name = sub.getCommandSpec().name();
+ String[] desc = sub.getCommandSpec().usageMessage().description();
+ String description = desc != null && desc.length > 0 ? desc[0] : "";
+ sb.append(String.format(" %-" + nameWidth + "s %s%n", name,
description));
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRendererTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRendererTest.java
new file mode 100644
index 000000000000..03368439c479
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/GroupedCommandHelpRendererTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class GroupedCommandHelpRendererTest extends
CamelCommandBaseTestSupport {
+
+ private CommandLine capturedCommandLine;
+
+ private String captureHelp() {
+ StringWriter sw = new StringWriter();
+ CamelJBangMain main = new CamelJBangMain() {
+ @Override
+ public void quit(int exitCode) {
+ }
+
+ @Override
+ public void postAddCommands(CommandLine commandLine, String[]
args) {
+ commandLine.setOut(new PrintWriter(sw));
+ capturedCommandLine = commandLine;
+ }
+ }.withPrinter(printer);
+ main.setDiscoverPlugins(false);
+ main.execute("--help");
+ return sw.toString();
+ }
+
+ @Test
+ public void helpOutputContainsGroups() throws Exception {
+ String output = captureHelp();
+
+ assertTrue(output.contains("Running:"), "Missing 'Running' group");
+ assertTrue(output.contains("Monitoring:"), "Missing 'Monitoring'
group");
+ assertTrue(output.contains("Actions:"), "Missing 'Actions' group");
+ assertTrue(output.contains("Development:"), "Missing 'Development'
group");
+ assertTrue(output.contains("Configuration:"), "Missing 'Configuration'
group");
+ assertTrue(output.contains("Catalog:"), "Missing 'Catalog' group");
+ assertTrue(output.contains("AI:"), "Missing 'AI' group");
+ }
+
+ @Test
+ public void helpOutputContainsKeyCommands() throws Exception {
+ String output = captureHelp();
+
+ assertTrue(output.contains("run"), "Missing 'run' command");
+ assertTrue(output.contains("get"), "Missing 'get' command");
+ assertTrue(output.contains("config"), "Missing 'config' command");
+ assertTrue(output.contains("catalog"), "Missing 'catalog' command");
+ }
+
+ @Test
+ public void noBuiltInCommandFallsIntoOther() throws Exception {
+ String output = captureHelp();
+
+ // Every command should belong to a category, so we never expect to
see "Other".
+ // If this fails, someone added a command but forgot to put it in a
group.
+ assertFalse(output.contains("Other:"),
+ "An unmapped command fell into the 'Other' group — add it to a
category in GroupedCommandHelpRenderer");
+ }
+
+ @Test
+ public void allRegisteredCommandsAppearInHelp() throws Exception {
+ String output = captureHelp();
+
+ // Make sure no command quietly goes missing: every registered,
non-hidden
+ // command should appear somewhere in the grouped output.
+ for (Map.Entry<String, CommandLine> entry :
capturedCommandLine.getSubcommands().entrySet()) {
+ if (entry.getValue().getCommandSpec().usageMessage().hidden()) {
+ continue;
+ }
+ String name = entry.getValue().getCommandSpec().name();
+ assertTrue(output.contains(name), "Command '" + name + "' is
missing from the grouped help output");
+ }
+ }
+}