davsclaus commented on code in PR #26604:
URL: https://github.com/apache/camel/pull/26604#discussion_r4050444446


##########
dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java:
##########
@@ -135,15 +152,155 @@ private static boolean matches(JsonObject entry, String 
filter) {
         return false;
     }
 
+    /**
+     * The groups of the example ladder in reading order: level, title, and 
the one-line introduction the README of
+     * camel-jbang-examples uses. Quick start comes first, showcase last; a 
level not listed here sorts after them.
+     */
+    private static final String[][] GROUPS = {
+            {
+                    "quick-start", "Quick start",
+                    "The first ten minutes: generic examples with no story and 
no service, each running in seconds." },
+            { "run", "Run", "Running Camel: timers and cron schedules, a bean 
in a route, properties and profiles." },
+            { "transform", "Transform and map", "JSON, XML and CSV in and out, 
field-by-field mapping, Groovy and XSLT." },
+            {
+                    "route", "Route",
+                    "The routing patterns: content-based router, splitter, 
aggregator, filter and multicast." },
+            {
+                    "fail-well", "Fail well",
+                    "Retries, a dead letter channel, and a circuit breaker in 
front of a flaky service." },
+            {
+                    "connect", "Connect without a service",
+                    "Files, an HTTP client and a REST server; everything runs 
inside the example." },
+            {
+                    "connect-service", "Connect to one service",
+                    "SQL, JMS, MQTT, Kafka and FTP against a service the Camel 
CLI starts with camel infra run." },
+            {
+                    "contracts", "Contracts and security",
+                    "An OpenAPI contract served and called, and an API 
protected by Keycloak." },
+            { "ai", "AI", "A local model writing text, routes exposed as MCP 
tools, RAG over documents, PII redaction." },
+            {
+                    "cloud", "Cloud",
+                    "A cloud service, run locally through LocalStack and 
switched to the real thing by properties." },
+            {
+                    "showcase", "Showcase",
+                    "Tooling demos outside the ladder: the TUI, a memory leak, 
message sizes, log analysis." },
+    };
+
+    /**
+     * The levels of the ladder in reading order.
+     */
+    public static List<String> getGroupOrder() {
+        List<String> order = new ArrayList<>();
+        for (String[] g : GROUPS) {
+            order.add(g[0]);
+        }
+        return order;
+    }
+
+    /**
+     * The title of a group (level), for example "Quick start" for 
quick-start; an unknown level is capitalized.
+     */
+    public static String getGroupTitle(String level) {
+        for (String[] g : GROUPS) {
+            if (g[0].equals(level)) {
+                return g[1];
+            }
+        }
+        return formatCategory(level);
+    }
+
+    /**
+     * The one-line introduction of a group (level), or an empty string for an 
unknown level.
+     */
+    public static String getGroupIntro(String level) {
+        for (String[] g : GROUPS) {
+            if (g[0].equals(level)) {
+                return g[2];
+            }
+        }
+        return "";
+    }
+
+    /**
+     * Groups the examples by level in ladder order, each group sorted by 
name; empty groups are left out and levels not
+     * on the ladder come last in the order they appear.
+     */
+    public static Map<String, List<JsonObject>> groupByLevel(List<JsonObject> 
catalog) {
+        Map<String, List<JsonObject>> groups = new LinkedHashMap<>();
+        for (String level : getGroupOrder()) {
+            groups.put(level, new ArrayList<>());
+        }
+        for (JsonObject entry : catalog) {
+            String level = entry.getStringOrDefault("level", "other");
+            groups.computeIfAbsent(level, k -> new ArrayList<>()).add(entry);
+        }
+        groups.values().removeIf(List::isEmpty);
+        for (List<JsonObject> entries : groups.values()) {
+            entries.sort(Comparator.comparingInt(ExampleHelper::getOrder)
+                    .thenComparing(e -> e.getStringOrDefault("name", "")));
+        }
+        return groups;
+    }
+
+    /**
+     * The reading order of the example within its group from the metadata, or 
a large number when it has none, so
+     * examples with an order come first and the rest sort by name.
+     */
+    public static int getOrder(JsonObject entry) {
+        Object order = entry.get("order");
+        if (order instanceof Number n) {
+            return n.intValue();
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * What the example teaches, as one line: the components and the EIPs from 
its metadata, or an empty string.
+     */
+    @SuppressWarnings("unchecked")
+    public static String getTeachesSummary(JsonObject entry) {
+        JsonObject teaches = entry.getMap("teaches");
+        if (teaches == null || teaches.isEmpty()) {
+            return "";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String key : new String[] { "components", "eips", "languages", 
"dataformats" }) {
+            Collection<String> values = (Collection<String>) teaches.get(key);

Review Comment:
   Applied: the summary now checks for a Collection and keeps only the String 
entries, with a comment; a test with a number in the components array and a 
string where a list belongs covers it.



##########
dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ExampleHelperTest.java:
##########
@@ -125,31 +125,67 @@ void shouldGetFiles() {
     @Test
     void shouldExtractBundledExample() throws Exception {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, 
"eip/circuit-breaker");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"fail-well/circuit-breaker");
         Path tempDir = ExampleHelper.extractBundledExample(entry);
 
-        assertTrue(Files.exists(tempDir.resolve("route.camel.yaml")));
-        String content = Files.readString(tempDir.resolve("route.camel.yaml"));
+        
assertTrue(Files.exists(tempDir.resolve("circuit-breaker.camel.yaml")));
+        String content = 
Files.readString(tempDir.resolve("circuit-breaker.camel.yaml"));
         assertFalse(content.isEmpty());
     }
 
     @Test
-    void shouldExtractBundledExampleWithSubdirectory() throws Exception {
+    void shouldExtractBundledExampleWithJavaAndBeans() throws Exception {
         List<JsonObject> catalog = ExampleHelper.loadCatalog();
-        JsonObject entry = ExampleHelper.findExample(catalog, 
"transformation/xslt");
+        JsonObject entry = ExampleHelper.findExample(catalog, 
"quick-start/routes");
         Path tempDir = ExampleHelper.extractBundledExample(entry);
 
-        assertTrue(Files.exists(tempDir.resolve("consumer.camel.yaml")));
-        assertTrue(Files.exists(tempDir.resolve("stylesheet.xsl")));
-        assertTrue(Files.exists(tempDir.resolve("input/account.xml")));
+        assertTrue(Files.exists(tempDir.resolve("routes.camel.yaml")));
+        assertTrue(Files.exists(tempDir.resolve("Greeter.java")));
+        assertTrue(Files.exists(tempDir.resolve("beans.yaml")));
+    }
+
+    @Test
+    void shouldGroupByLevelInLadderOrder() {
+        List<JsonObject> catalog = ExampleHelper.loadCatalog();
+        java.util.Map<String, List<JsonObject>> groups = 
ExampleHelper.groupByLevel(catalog);
+        List<String> levels = new java.util.ArrayList<>(groups.keySet());
+        assertEquals("quick-start", levels.get(0));
+        assertTrue(levels.indexOf("route") < levels.indexOf("fail-well"));
+        assertTrue(levels.indexOf("connect") < 
levels.indexOf("connect-service"));
+        assertEquals("showcase", levels.get(levels.size() - 1));
+        assertEquals("Quick start", 
ExampleHelper.getGroupTitle("quick-start"));
+        assertEquals("Connect to one service", 
ExampleHelper.getGroupTitle("connect-service"));
+        assertFalse(ExampleHelper.getGroupIntro("fail-well").isEmpty());
+        for (List<JsonObject> entries : groups.values()) {
+            assertFalse(entries.isEmpty());
+        }
+    }
+
+    @Test
+    void shouldSummarizeTeachesAndCiSkip() {
+        List<JsonObject> catalog = ExampleHelper.loadCatalog();
+        JsonObject aggregator = ExampleHelper.findExample(catalog, 
"route/aggregator");
+        String teaches = ExampleHelper.getTeachesSummary(aggregator);
+        assertTrue(teaches.contains("eips: "), teaches);
+        assertTrue(teaches.contains("aggregate"), teaches);
+        assertFalse(ExampleHelper.isCiSkip(aggregator));
+        JsonObject chat = ExampleHelper.findExample(catalog, 
"ai/langchain4j-chat");
+        assertTrue(ExampleHelper.isCiSkip(chat));
+    }
+
+    @Test
+    void shouldFindAmbiguousShortNames() {
+        List<JsonObject> catalog = ExampleHelper.loadCatalog();
+        assertEquals(1, ExampleHelper.findExamplesByShortName(catalog, 
"aggregator").size());
+        assertTrue(ExampleHelper.findExamplesByShortName(catalog, 
"no-such-example").isEmpty());

Review Comment:
   Applied: the test now also builds two synthetic entries, group-a/foo and 
group-b/foo, and asserts the 2-match case.



##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ExampleBrowserPopup.java:
##########
@@ -139,9 +138,22 @@ boolean handleKeyEvent(KeyEvent ke) {
             return true;
         }
         if (ke.isChar('d')) {
-            loadDocFromExample();
+            loadDocForSelected();
             return true;
         }
+        if (currentFolder == null) {
+            // 1 to 9 open the first nine groups, 0 the tenth
+            for (char c = '0'; c <= '9'; c++) {
+                if (ke.isChar(c)) {
+                    int n = c == '0' ? 10 : c - '0';
+                    List<String> levels = new 
ArrayList<>(ExampleHelper.groupByLevel(catalog).keySet());

Review Comment:
   Applied: the grouping is computed once when the catalog is loaded and the 
five call sites use the cached map.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to