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");
+        }
+    }
+}

Reply via email to