diff --git a/pom.xml b/pom.xml index 34c71edb..d846758b 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ 5 true - 3.5 + 3.6.1 1.4.4.0 diff --git a/src/main/java/io/hyperfoil/tools/h5m/cli/ListNode.java b/src/main/java/io/hyperfoil/tools/h5m/cli/ListNode.java index 29b9f254..d6243c83 100644 --- a/src/main/java/io/hyperfoil/tools/h5m/cli/ListNode.java +++ b/src/main/java/io/hyperfoil/tools/h5m/cli/ListNode.java @@ -3,16 +3,14 @@ import io.hyperfoil.tools.h5m.api.Node; import io.hyperfoil.tools.h5m.api.NodeGroup; import io.hyperfoil.tools.h5m.api.svc.NodeGroupServiceInterface; -import io.hyperfoil.tools.h5m.entity.NodeEntity; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.aesh.util.graph.GraphStyle; import picocli.CommandLine; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.Callable; +import java.util.stream.Collectors; import org.aesh.util.graph.Graph; import org.aesh.util.graph.GraphNode; @@ -32,6 +30,16 @@ public static enum Render {Table, Graph}; @CommandLine.Option(names = {"as"}, description = "Valid values: ${COMPLETION-CANDIDATES}\")", defaultValue = "Table") Render render; + @CommandLine.Option(names = {"--hide"}, description = "hide nodes matching suffix pattern (comma-separated, e.g. _primary,_alt)", arity = "0..1") String hidePattern; + + @CommandLine.Option(names = {"--type"}, description = "filter by node type (comma-separated, e.g. sql,ecma,fp,rd)", arity = "0..1") String typeFilter; + + @CommandLine.Option(names = {"--depth"}, description = "max depth from root", arity = "0..1", defaultValue = "0") int maxDepth; + + @CommandLine.Option(names = {"--show-type"}, description = "prefix node labels with [type]", defaultValue = "true") boolean showType; + + @CommandLine.Option(names = {"--max-label-width"}, description = "wrap labels at word boundaries when wider than N chars (0 = no wrap)", defaultValue = "30") int maxLabelWidth; + @Override @Transactional public Integer call() throws Exception { @@ -47,13 +55,31 @@ public Integer call() throws Exception { return 1; } if(render.equals(Render.Graph)){ - GraphNode rootNode = GraphNode.of("root"); + Set skipNames = new HashSet<>(); + if (hidePattern != null) { + List suffixes = Arrays.stream(hidePattern.split(",")).map(String::trim).toList(); + for (Node source : nodeGroup.sources()) { + for (String suffix : suffixes) { + if (source.name().endsWith(suffix)) { + skipNames.add(source.name()); + break; + } + } + } + } + + Set types = typeFilter != null + ? Arrays.stream(typeFilter.split(",")).map(String::trim).collect(Collectors.toSet()) + : Collections.emptySet(); + + String rootLabel = showType ? "[root] root" : "root"; + GraphNode rootNode = GraphNode.of(rootLabel); Map nodes = new HashMap<>(); nodes.put(nodeGroup.root(), rootNode); - for(Node source: nodeGroup.sources()){ - walk(source,nodes); - } - System.out.println(Graph.render(rootNode, GraphStyle.ROUNDED)); + for(Node source: nodeGroup.sources()){ + walk(source, nodes, skipNames, types, 1, maxDepth, showType); + } + System.out.println(Graph.render(rootNode, GraphStyle.ROUNDED, 0, maxLabelWidth)); }else { System.out.println( ListCmd.table(80,nodeGroup.sources(),List.of("name","type","fqdn","operation"), @@ -69,18 +95,34 @@ public Integer call() throws Exception { return 0; } - public GraphNode walk(Node node, Map nodes){ + public GraphNode walk(Node node, Map nodes, Set skipNames, + Set typeFilter, int depth, int maxDepth, boolean showType){ if(nodes.containsKey(node)){ return nodes.get(node); - }else{ - GraphNode rtrn = GraphNode.of(node.name()); + } + if(maxDepth > 0 && depth > maxDepth){ + return null; + } + + boolean skip = skipNames.contains(node.name()) + || (!typeFilter.isEmpty() && !typeFilter.contains(node.type().display())); + + GraphNode rtrn = null; + if (!skip) { + String label = showType ? "[" + node.type().display() + "] " + node.name() : node.name(); + rtrn = GraphNode.of(label); nodes.put(node, rtrn); - for(Node source: node.sources()){ - GraphNode fromSource = walk(source,nodes); - fromSource.child(rtrn); - } - return rtrn; } + + for(Node source: node.sources()){ + GraphNode fromSource = walk(source, nodes, skipNames, typeFilter, depth + 1, maxDepth, showType); + if(rtrn != null && fromSource != null){ + fromSource.child(rtrn); + } else if(rtrn != null && nodes.containsKey(source)){ + nodes.get(source).child(rtrn); + } + } + return rtrn; } }