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);
