File Coverage

blib/lib/Devel/PerlySense/CallTree/Graph.pm
Criterion Covered Total %
statement 77 80 96.2
branch 1 2 50.0
condition n/a
subroutine 21 22 95.4
pod 0 6 0.0
total 99 110 90.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::PerlySense::CallTree::Graph - A GraphViz graph of the CallTree
4              
5             =head1 DESCRIPTION
6              
7              
8             =cut
9              
10             package Devel::PerlySense::CallTree::Graph;
11             $Devel::PerlySense::CallTree::Graph::VERSION = '0.0217';
12 69     69   33860 use strict;
  69         93  
  69         1756  
13 69     69   235 use warnings;
  69         102  
  69         1519  
14 69     69   680 use utf8;
  69         104  
  69         391  
15              
16              
17              
18 69     69   1604 use Moo;
  69         9386  
  69         317  
19 69     69   16464 use Path::Tiny;
  69         8149  
  69         3525  
20 69     69   31546 use Digest::SHA qw/ sha256_hex /;
  69         139547  
  69         4535  
21              
22 69     69   786 use Devel::PerlySense::CallTree;
  69         115  
  69         1232  
23 69     69   260 use Devel::PerlySense::CallTree::Caller;
  69         90  
  69         54180  
24              
25              
26              
27             has call_tree => ( is => "ro", required => 1 );
28              
29             has output_format => ( is => "lazy" );
30 1     1   436 sub _build_output_format { "png" }
31              
32             has output_dir => ( is => "lazy" );
33 1     1   11 sub _build_output_dir { "." }
34             around output_dir => sub {
35             my $orig = shift;
36             my $self = shift;
37             my $dir = $self->$orig(@_);
38              
39             (-e $dir) or $dir->mkpath();
40             (-d $dir) or die("Can not create directory ($dir)\n");
41              
42             return $dir;
43             };
44              
45             has base_file => ( is => "lazy" );
46             sub _build_base_file {
47 1     1   448 my $self = shift;
48 1         5 return sha256_hex( $self->edge_declarations_text );
49             }
50              
51             has dot_file => ( is => "lazy" );
52             sub _build_dot_file {
53 1     1   388 my $self = shift;
54 1         27 my $file = path($self->output_dir, $self->base_file . ".dot")->absolute;
55 1         197 return $file;
56             }
57              
58             has output_file => ( is => "lazy" );
59             sub _build_output_file {
60 1     1   467 my $self = shift;
61 1         27 path($self->output_dir, $self->base_file . "." . $self->output_format)->absolute;
62             }
63              
64             has edge_declarations_text => ( is => "lazy" );
65             sub _build_edge_declarations_text {
66 1     1   384 my $self = shift;
67 1         24 my $called_by_caller = $self->call_tree->method_called_by_caller;
68             my $edge_declarations = join(
69             "\n",
70             map {
71 1         19 my $callers = $called_by_caller->{ $_ };
  10         57  
72 10         43 my @callers = sort keys %$callers;
73 10         198 my $target = Devel::PerlySense::CallTree::Caller->new({
74             caller => $_,
75             });
76             join(
77             "\n",
78             map {
79 10         208 my $method_caller = $_;
  27         86  
80 27         527 my $caller = Devel::PerlySense::CallTree::Caller->new({
81             caller => $method_caller,
82             });
83 27         978 " " . $caller->id . " -> " . $target->id;
84             }
85             @callers
86             );
87             }
88             sort keys %$called_by_caller
89             );
90 1         97 return $edge_declarations;
91             }
92              
93             has cluster_declarations_text => ( is => "lazy" );
94             sub _build_cluster_declarations_text {
95 1     1   403 my $self = shift;
96 1         9 my $package_callers = $self->call_tree->package_callers;
97             my $cluster_declarations = join(
98             "\n",
99             map {
100 1         10 my $package = $_;
  5         5  
101 5         7 my $callers = $package_callers->{ $package };
102             my $node_declarations = join(
103             "\n",
104 5         7 map { " " . $self->node_declaration($_) }
  19         30  
105             @$callers
106             );
107              
108 5         60 my $package_id = $self->to_id(package => $package);
109 5         27 qq|
110             subgraph cluster_${package_id} {
111             label = "$package";
112             $node_declarations
113             }
114             |;
115             }
116             sort keys %$package_callers
117             );
118 1         28 return $cluster_declarations;
119             }
120              
121             has dot_source_template => ( is => "lazy" );
122             sub _build_dot_source_template {
123 1     1   508 my $self = shift;
124 1         7 my $source = qq|
125             digraph d {
126             overlap = false
127             ranksep = 0.3; nodesep = 0.1;
128             rankdir = TB;
129             fontname = "Verdana";
130             labelloc = "t";
131              
132             // splines=ortho
133              
134             graph [
135             fontname = "Verdana",
136             fontsize = 10,
137             fontcolor = "#2980b9",
138             style = "rounded",
139             color="#cccccc",
140             ];
141              
142             node [
143             width = 0.1,
144             height = 0.2,
145             fontname = "Verdana",
146             fontsize = 8,
147             shape = "none",
148             ];
149             edge [
150             arrowsize = 0.4,
151             fontname = "Helvetica",
152             fontsize = 9,
153             ];
154              
155              
156             %s
157              
158              
159             %s
160              
161             }
162              
163             |;
164             }
165              
166             sub create_graph {
167 1     1 0 1810 my $self = shift;
168 1         5 my $dot_file = $self->dot_file;
169 1         6 $self->write_dot_file( $dot_file );
170 1         754 $self->run_dot( $dot_file, $self->output_file );
171             }
172              
173             sub node_declaration {
174 19     19 0 19 my $self = shift;
175 19         16 my ($caller) = @_;
176 19         341 return sprintf(
177             qq{ %s [ label="%s" ];},
178             $caller->id,
179             $caller->method,
180             );
181             }
182              
183             sub to_id {
184 5     5 0 7 my $self = shift;
185 5         7 my ($prefix, $name) = @_;
186 5         24 $name =~ s/\W+/_/gsm;
187 5         15 return join("_", $prefix, lc($name));
188             }
189              
190             sub to_caller {
191 0     0 0 0 my $self = shift;
192 0         0 my ($caller) = @_;
193 0         0 return Devel::PerlySense::CallTree::Caller->new({ caller => $caller });
194             }
195              
196             sub write_dot_file {
197 1     1 0 2 my $self = shift;
198 1         2 my ($filename) = @_;
199 1         6 my $source = sprintf(
200             $self->dot_source_template,
201             $self->cluster_declarations_text(),
202             $self->edge_declarations_text(),
203             );
204 1         23 path($filename)->spew($source);
205             }
206              
207             sub run_dot {
208 1     1 0 105 my $self = shift;
209 1         2 my ($dot_file, $output_file) = @_;
210 1         23 my $format = $self->output_format;
211 1         10 my $command = qq|dot -T$format -o"$output_file" "$dot_file" 2>&1|;
212 1         2968 my $error = qx($command);
213 1 50       66 $error and die("Could not run 'dot':\n$!\n$error \n");
214             }
215              
216             1;
217              
218              
219              
220              
221             __END__
222              
223             =encoding utf8
224              
225             =head1 AUTHOR
226              
227             Johan Lindstrom, C<< <johanl@cpan.org> >>
228              
229             =head1 BUGS
230              
231             Please report any bugs or feature requests to
232             C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
233             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
234             I will be notified, and then you'll automatically be notified of progress on
235             your bug as I make changes.
236              
237             =head1 ACKNOWLEDGEMENTS
238              
239             =head1 COPYRIGHT & LICENSE
240              
241             Copyright 2005 Johan Lindstrom, All Rights Reserved.
242              
243             This program is free software; you can redistribute it and/or modify it
244             under the same terms as Perl itself.
245              
246             =cut