7 indent2graph - Generate graph out of whitespace-indented hierarchical text
11 indent2graph < tree.txt > tree.dot
15 Take line-based input, and output a directed graph in a given format, eg. dot(1) (see graphviz(1)).
16 Each input line is a node.
17 How much the line is indented (by leading spaces or TABs) determines its relation to the nodes of the surrounding lines.
18 Lines which are indented to the same level, go to the same rank on the tree-like graph in the output.
19 The graph may contain loops:
20 lines with the same text (apart from the leading whitespace) are considered the same node
21 (except when B<--tree> option is set).
43 indent2graph -f clojure | vijual draw-tree -
52 +------------------------+----+---------+----------+--------+
54 +-----+------+ +-----+------+ +-----+-----+ +--+---+ +--+---+
55 | libselinux | | libgssapi_ | | libcrypto | | libz | | libc |
56 +-----+------+ | krb5 | +-----------+ +------+ +------+
59 | +----------+-+--------------+--------------+
60 +-----+------+ | | | |
61 | libpcre2-8 | +----+----+ +-----+------+ +-----+------+ +-----+------+
62 +------------+ | libkrb5 | | libk5crypt | | libcom_err | | libkrb5sup |
63 +----+----+ | o | +------------+ | port |
64 | +------------+ +------------+
67 +-----+------+ +-----+-----+
68 | libkeyutil | | libresolv |
76 =item -f, --format I<FORMAT>
82 =item B<dot> (default)
84 The graphviz(1) (dot(1)) format.
88 Simple B<TAB>-separated node name pairs, each describes a graph edge, 1 per line.
92 Clojure-style nested vectors (represented as string).
97 Graph::Easy(3pl)'s own "txt" format.
98 With graph-easy(1) you can transform further into other formats, like GDL, VCG, ...
106 =item -a, --ascendent
108 Indentation in the input represents ascendents, not descendents.
109 Default is descendent chart.
110 This influences to where arrows point.
114 Interpret input strictly as a tree with no cycles.
115 By default, without B<--tree>, lines with the same text represent the same node,
116 so you can build arbitrary graph.
117 With B<--tree>, you can build a tree-like graph in which different nodes may have the same text (label).
119 =item -d, --rankdir I<DIR>
121 This is the dot(1) graph's B<rankdir> parameter.
122 This option is although specific to dot(1) format,
123 but translated to B<grapheasy> if it is the chosen output format.
124 I<DIR> is one of B<TB>, B<BT>, B<LR>, B<RL>.
125 Default is B<LR> ie. left-to-right.
126 See graphviz(1) documentation for details.
132 indent2tree(1), graphviz(1), dot(1), vijual(1), Graph::Easy(3pl)
139 use Getopt
::Long qw
/:config no_ignore_case no_bundling no_getopt_compat no_auto_abbrev require_order/;
145 $_[0] =~ s/[\\\Q$_[1]\E]/\\$&/gr;
156 my $node_id = $p{id
};
157 my $node_text = $NodeText{$node_id};
164 print node_repr
(text
=>$node_text);
165 for my $child_node_id (@
{$SubTree{$node_id}->{'children'}})
168 output_node_rec
(id
=> $child_node_id);
172 case
(['dot', 'pairs', 'grapheasy'])
174 for my $child_node_id (@
{$SubTree{$node_id}->{'children'}})
176 print edge_repr
({id
=>$node_id, text
=>$node_text}, {id
=>$child_node_id, text
=>$NodeText{$child_node_id}});
178 for my $child_node_id (@
{$SubTree{$node_id}->{'children'}})
180 output_node_rec
(id
=>$child_node_id);
193 if(defined $p{id
}) { return $p{id
}; }
194 else { return '"'.esc_dquo
($p{text
}).'"'; }
196 case
('pairs') { return $p{text
} =~ s/\t/\\t/gr; }
199 if(defined $p{id
}) { return ':'.$p{id
}; }
200 else { return '"'.esc_dquo
($p{text
}).'"'; }
204 if(defined $p{id
}) { return '[ '.esc
($p{id
}, ']|').' ]'; }
205 else { return '[ '.esc
($p{text
}, ']|').' ]'; }
215 case
('dot') { return sprintf '%s -> %s;', node_repr
(%$n1), node_repr
(%$n2); }
216 case
('pairs') { return sprintf '%s\t%s', node_repr
(%$n1), node_repr
(%$n2); }
217 case
('clojure') { return sprintf '[%s %s]', node_repr
(%$n1), node_repr
(%$n2); }
218 case
('grapheasy') { return sprintf '%s --> %s', node_repr
(%$n1), node_repr
(%$n2); }
229 'a|ascendent!' => \
$OptAscendent,
230 'd|rankdir=s' => \
$OptRankdir,
231 'f|format=s' => \
$OptFormat,
232 't|tree!' => \
$OptTree,
233 'help' => sub { pod2usage
(-exitval
=>0, -verbose
=>99); },
234 ) or pod2usage
(-exitval
=>2, -verbose
=>99);
237 die "Output format 'pairs' and strict tree input is not supported.\n" if $OptFormat eq 'pairs' and $OptTree;
239 $GraphEasyGraphFlow = {qw
/TB south LR east RL west BT north/}->{$OptRankdir};
244 my $indent_level = length $1;
247 $NodeText{$node_id} = $node;
250 if($indent_level > $prev_indent_level)
252 $parent_of_level{$indent_level} = $prev_node;
253 $parent_id_of_level{$indent_level} = $prev_node_id;
256 $related_node = $parent_of_level{$indent_level};
257 $related_node_id = $parent_id_of_level{$indent_level};
259 if(defined $related_node)
263 push @
{$SubTree{$related_node_id}->{'children'}}, $node_id;
267 $Relation{$node}->{$related_node} = 1;
268 push @Relation, [$node, $related_node];
272 $prev_indent_level = $indent_level;
274 $prev_node_id = $node_id;
285 print "rankdir=$OptRankdir;";
286 print "node [shape=box];";
295 printf 'graph { flow: %s; }'.$\
, $GraphEasyGraphFlow;
301 for my $id (keys %NodeText)
303 my $text = $NodeText{$id};
306 case
('dot') { print node_repr
(id
=>$id) . ' [label=' . node_repr
(text
=>$text) . '];'; }
307 case
('grapheasy') { print node_repr
(id
=>$id) . ' { label: ' . esc
($text, ';}') . ' }'; }
311 output_node_rec
(id
=> 1);
315 if($OptFormat eq 'clojure')
317 for my $relation (@Relation)
319 my ($node, $related_node) = @
$relation;
320 ($node, $related_node) = ($related_node, $node) if not $OptAscendent;
321 print edge_repr
({text
=>$node}, {text
=>$related_node});
326 for my $node (sort keys %Relation)
328 for my $related_node (sort keys %{$Relation{$node}})
330 ($node, $related_node) = ($related_node, $node) if not $OptAscendent;
331 print edge_repr
({text
=>$node}, {text
=>$related_node});
349 # for my $id (keys %NodeText)
351 # print node_repr(id=>$id).' '.node_repr(text=>$NodeText{$id}).' ';