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

davsclaus pushed a commit to branch camel-23485-ascii-art-renderer
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 3363f7c54f91154a7d7584aade64dc24f0d87cba
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue May 12 14:29:20 2026 +0200

    CAMEL-23485: camel-diagram - Add scope boxes to ASCII art renderer
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../camel-diagram/src/main/docs/diagram.adoc       |  3 +-
 .../camel/diagram/RouteDiagramAsciiRenderer.java   | 52 +++++++++++++++++++++-
 .../org/apache/camel/diagram/RouteDiagramTest.java | 22 +++++++++
 3 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/components/camel-diagram/src/main/docs/diagram.adoc 
b/components/camel-diagram/src/main/docs/diagram.adoc
index e8e6ca9e417a..c9525206ede9 100644
--- a/components/camel-diagram/src/main/docs/diagram.adoc
+++ b/components/camel-diagram/src/main/docs/diagram.adoc
@@ -181,4 +181,5 @@ route1
   +----------------------+        +----------------------+
 ----
 
-NOTE: Scope boxes (dotted boundaries for filter, split, etc.) are not included 
in ASCII art rendering.
+Scope boxes (for filter, split, doTry, etc.) are rendered with dashed borders 
using `:` for vertical
+and `- - -` for horizontal lines.
diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
index d79cf001bf54..86c4c6ba1226 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java
@@ -20,10 +20,13 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.Bounds;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutNode;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutRoute;
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.TreeNode;
 
 import static org.apache.camel.diagram.RouteDiagramLayoutEngine.PADDING;
+import static org.apache.camel.diagram.RouteDiagramLayoutEngine.SCOPE_BOX_PAD;
 
 /**
  * Renders route diagrams as plain ASCII art text.
@@ -73,6 +76,12 @@ public class RouteDiagramAsciiRenderer {
         }
         drawText(grid, labelRow, toCol(PADDING), label);
 
+        for (LayoutNode ln : lr.nodes) {
+            if (ln.treeNode != null && 
RouteDiagramLayoutEngine.hasScope(ln.treeNode)) {
+                drawScopeBox(grid, ln);
+            }
+        }
+
         for (LayoutNode ln : lr.nodes) {
             if (ln.parentNode != null) {
                 if (ln.connectFromMerge) {
@@ -132,7 +141,7 @@ public class RouteDiagramAsciiRenderer {
         int fromCx = centerCol(from);
         int fromBottom = toRow(from.y) + boxHeight(from);
         int toCx = centerCol(to);
-        int toTop = toRow(to.y);
+        int toTop = getTopRow(to);
 
         drawArrowPath(grid, fromCx, fromBottom, toCx, toTop);
     }
@@ -141,7 +150,7 @@ public class RouteDiagramAsciiRenderer {
         int fromCx = toCol(to.mergeCx);
         int fromRow = toRow(to.mergeY);
         int toCx = centerCol(to);
-        int toTop = toRow(to.y);
+        int toTop = getTopRow(to);
 
         drawArrowPath(grid, fromCx, fromRow, toCx, toTop);
     }
@@ -178,6 +187,45 @@ public class RouteDiagramAsciiRenderer {
         }
     }
 
+    private void drawScopeBox(char[][] grid, LayoutNode scopeNode) {
+        TreeNode tn = scopeNode.treeNode;
+        Bounds bounds = new Bounds(
+                scopeNode.x, scopeNode.y,
+                scopeNode.x + nodeWidth, scopeNode.y + scopeNode.height);
+        for (TreeNode child : tn.children) {
+            RouteDiagramLayoutEngine.expandBoundsForBox(child, bounds, 
nodeWidth);
+        }
+
+        int col1 = toCol(bounds.minX - SCOPE_BOX_PAD);
+        int row1 = toRow(bounds.minY - SCOPE_BOX_PAD);
+        int col2 = toCol(bounds.maxX + SCOPE_BOX_PAD);
+        int row2 = toRow(bounds.maxY + SCOPE_BOX_PAD);
+
+        drawDashedHLine(grid, row1, col1, col2);
+        drawDashedHLine(grid, row2, col1, col2);
+        for (int r = row1 + 1; r < row2; r++) {
+            setChar(grid, r, col1, ':');
+            setChar(grid, r, col2, ':');
+        }
+    }
+
+    private void drawDashedHLine(char[][] grid, int row, int col1, int col2) {
+        for (int c = col1; c <= col2; c++) {
+            if (c == col1 || c == col2) {
+                setChar(grid, row, c, '+');
+            } else if ((c - col1) % 2 == 0) {
+                setChar(grid, row, c, '-');
+            }
+        }
+    }
+
+    private int getTopRow(LayoutNode node) {
+        if (node.treeNode != null && 
RouteDiagramLayoutEngine.hasScope(node.treeNode)) {
+            return toRow(node.y - SCOPE_BOX_PAD);
+        }
+        return toRow(node.y);
+    }
+
     private int centerCol(LayoutNode node) {
         return toCol(node.x + nodeWidth / 2);
     }
diff --git 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
index 40d662a0b330..7024198eef7d 100644
--- 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
+++ 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
@@ -949,6 +949,28 @@ class RouteDiagramTest {
         assertTrue(result.contains("log:a"));
     }
 
+    @Test
+    void testAsciiDiagramWithScopeBox() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(node("from", "direct:start", 0));
+        route.nodes.add(node("filter", "filter[header(x)]", 1));
+        route.nodes.add(node("log", "log[filtered]", 2));
+        route.nodes.add(node("to", "to[mock:end]", 1));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        String result = renderer.renderDiagram(List.of(lr), lr.maxY + 
RouteDiagramLayoutEngine.V_GAP);
+
+        assertTrue(result.contains("filter[header(x)]"));
+        assertTrue(result.contains("log[filtered]"));
+        assertTrue(result.contains("to[mock:end]"));
+        assertTrue(result.contains(":"), "Scope box should have dashed 
vertical borders");
+        assertTrue(result.contains("+ -"), "Scope box should have dashed 
horizontal borders");
+    }
+
     @Test
     void testAsciiWrapTextShort() {
         List<String> lines = RouteDiagramAsciiRenderer.wrapText("timer:tick", 
20);

Reply via email to