File Coverage

blib/lib/Module/Overview.pm
Criterion Covered Total %
statement 126 162 77.7
branch 25 48 52.0
condition 9 35 25.7
subroutine 17 18 94.4
pod 4 4 100.0
total 181 267 67.7


line stmt bran cond sub pod time code
1             package Module::Overview;
2              
3             =head1 NAME
4              
5             Module::Overview - print/graph module(s) information
6              
7             =head1 SYNOPSIS
8              
9             use Module::Overview;
10            
11             my $mo = Module::Overview->new({
12             'module_name' => 'Module::Overview',
13             });
14            
15             print $mo->text_simpletable;
16            
17             my $graph = $mo->graph; # Graph::Easy
18             open my $DOT, '|dot -Tpng -o graph.png' or die ("Cannot open pipe to dot: $!");
19             print $DOT $graph->as_graphviz;
20             close $DOT;
21              
22             =cut
23              
24 1     1   86940 use warnings;
  1         3  
  1         36  
25 1     1   5 use strict;
  1         2  
  1         32  
26              
27             our $VERSION = '0.02';
28              
29 1     1   586 use Class::Sniff;
  1         145300  
  1         32  
30 1     1   8 use Text::SimpleTable;
  1         2  
  1         17  
31 1     1   477 use Module::ExtractUse;
  1         135343  
  1         38  
32 1     1   10 use Graph::Easy;
  1         2  
  1         29  
33 1     1   6 use Carp 'confess';
  1         2  
  1         52  
34 1     1   6 use List::MoreUtils qw(none);
  1         3  
  1         15  
35              
36 1     1   1093 use base 'Class::Accessor::Fast';
  1         2  
  1         570  
37              
38             __PACKAGE__->mk_accessors(qw{
39             module_name
40             recursive
41             recursion_filter
42             hide_methods
43             });
44              
45             sub new {
46 2     2 1 7766 my $class = shift;
47 2         23 my $self = $class->SUPER::new(@_);
48            
49 2 50       78 confess('module_name is mandatory property')
50             if not $self->module_name;
51            
52 2         25 return $self;
53             }
54              
55             sub get {
56 2     2 1 4 my $self = shift;
57 2   33     7 my $module_name = shift || $self->{'module_name'};
58            
59 2         5 my $recursion_filter = $self->{'recursion_filter'};
60              
61 2         3 my %overview;
62            
63 1     1   25 eval qq{ use $module_name };
  1     1   7  
  1         8  
  1         10  
  1         2  
  1         10  
  2         168  
64 2 50       47 warn 'error loading "'.$module_name.'" - '.$@ if $@;
65              
66 2         18 my $sniff = Class::Sniff->new({class => $module_name});
67 2         1605917 my $euse = Module::ExtractUse->new;
68              
69             #my $graph = $sniff->graph; # Graph::Easy
70             #print $sniff->report;
71             #print join("\n", $sniff->methods), "\n";
72 2         22 $overview{'class'} = $module_name;
73             $overview{'parents'} = [
74 2         8 grep { $_ ne 'Exporter' } # skip uninteresting
75 2         10 grep { $_ !~ m{^[0-9._]+$} } # skip perl versions
  2         38  
76             $sniff->parents
77             ];
78             delete $overview{'parents'}
79 2 50       6 if not @{$overview{'parents'}};
  2         9  
80             $overview{'classes'} = [
81 5     5   11 grep { my $s = $_; none { $_ eq $s } @{$overview{'parents'}} } # skip parents
  5         19  
  5         22  
  5         18  
82 5         11 grep { $_ ne 'Exporter' } # skip uninteresting
83 5         14 grep { $_ !~ m{^[0-9._]+$} } # skip perl versions
84 2         8 grep { $_ ne $module_name } # skip self
  7         23  
85             $sniff->classes
86             ];
87             delete $overview{'classes'}
88 2 50       5 if not @{$overview{'classes'}};
  2         8  
89            
90 2         6 my $module_name_path = $module_name.'.pm';
91 2         10 $module_name_path =~ s{::}{/}g;
92 2 50 33     114 if (exists $INC{$module_name_path} and (-r $INC{$module_name_path})) {
93 2         20 $euse->extract_use($INC{$module_name_path});
94 2         62597 $DB::single=1;
95 2         9 my %skip_kw = map {$_ => 1} qw(strict warnings constant vars Exporter);
  10         28  
96             $overview{'uses'} = [
97 6 50       22 grep { (not $recursion_filter) or ($_ =~ m/$recursion_filter/) } # filter modules
98 8     8   13 grep { my $s = $_; none { $_ eq $s } @{$overview{'parents'}} } # skip parents
  8         22  
  8         28  
  8         22  
99 10         19 grep { !$skip_kw{$_} } # skip uninteresting
100 2         13 grep { $_ !~ m{^[0-9._]+$} } # skip perl versions
  10         44  
101             sort
102             $euse->array
103             ];
104             delete $overview{'uses'}
105 2 100       7 if not @{$overview{'uses'}};
  2         9  
106             }
107              
108 2         5 my (@methods, @methods_imported);
109 2         4 while (my ($method, $classes) = each %{$sniff->{methods}}) {
  64         183  
110 62         79 my $class = ${$classes}[0];
  62         114  
111 62 100       169 my $method_desc = $method.'()'.($class ne $module_name ? ' ['.$class.']' : '');
112              
113             # source - Pod::Coverage _get_syms()
114             # see if said method wasn't just imported from elsewhere
115 1     1   3176 my $glob = do { no strict 'refs'; \*{$class.'::'.$method} };
  1         2  
  1         827  
  62         78  
  62         73  
  62         190  
116 62         140 my $o = B::svref_2object($glob);
117             # in 5.005 this flag is not exposed via B, though it exists
118 62   50     93 my $imported_cv = eval { B::GVf_IMPORTED_CV() } || 0x80;
119 62         119 my $imported = $o->GvFLAGS & $imported_cv;
120              
121 62 100       112 if ($imported) {
122 22         28 push @methods_imported, $method_desc;
123 22         48 next;
124             }
125            
126 40         93 push @methods, $method_desc;
127             }
128             $overview{'methods'} = [ sort @methods ]
129 2 100 66     43 if @methods and (not $self->{'hide_methods'});
130             $overview{'methods_imported'} = [ sort @methods_imported ]
131 2 100 66     15 if @methods_imported and (not $self->{'hide_methods'});
132            
133 2         73 return \%overview;
134             }
135              
136             sub text_simpletable {
137 2     2 1 2066 my $self = shift;
138 2   33     12 my $module_name = shift || $self->{'module_name'};
139            
140 2         7 my $module_overview = $self->get($module_name);
141 2         287 my $table = Text::SimpleTable->new(16, 60);
142              
143 2         124 $table->row('class', $module_overview->{'class'});
144 2 50 33     242 if ($module_overview->{'parents'} || $module_overview->{'classes'}) {
145 2         9 $table->hr;
146             }
147 2 50       33 if ($module_overview->{'parents'}) {
148 2         4 $table->row('parents', join("\n", @{$module_overview->{'parents'}}));
  2         10  
149             }
150 2 50       172 if ($module_overview->{'classes'}) {
151 2         5 $table->row('classes', join("\n", @{$module_overview->{'classes'}}));
  2         9  
152             }
153 2 100       178 if ($module_overview->{'uses'}) {
154 1         4 $table->hr;
155 1         14 $table->row('uses', join("\n", @{$module_overview->{'uses'}}));
  1         5  
156             }
157 2 100       168 if ($module_overview->{'methods'}) {
158 1         5 $table->hr;
159 1         14 $table->row('methods', join("\n", @{$module_overview->{'methods'}}));
  1         7  
160             }
161 2 100       240 if ($module_overview->{'methods_imported'}) {
162 1         3 $table->hr;
163 1         15 $table->row('methods_imported', join("\n", @{$module_overview->{'methods_imported'}}));
  1         6  
164             }
165 2         171 return $table->draw;
166             }
167              
168             sub graph {
169 0     0 1   my $self = shift;
170 0   0       my $module_name = shift || $self->{'module_name'};
171 0   0       my $graph = shift || Graph::Easy->new();
172            
173 0           my $recursion_filter = $self->{'recursion_filter'};
174 0 0 0       return $graph
175             if ($recursion_filter and ($module_name !~ m/$recursion_filter/));
176            
177 0           my $module_overview = $self->get($module_name);
178            
179 0           $graph->add_node($module_name)->set_attributes({'font-size' => '150%', 'textstyle' => 'bold', 'fill' => 'lightgrey'});
180 0 0         if ($module_overview->{'parents'}) {
181 0           my $module_name_parent = $module_name.' parent';
182 0           $graph->add_node($module_name_parent)->set_attributes({
183             'label' => 'parent',
184             'shape' => 'ellipse',
185             'font-size' => '75%',
186             });
187 0           $graph->add_edge_once($module_name => $module_name_parent);
188              
189 0           foreach my $parent (@{$module_overview->{'parents'}}) {
  0            
190 0           $graph->add_node($parent);
191            
192 0           my $e = $graph->add_edge_once($module_name_parent, $parent);
193            
194             #my $e = $graph->add_edge_once($module_name, $parent, 'parent');
195            
196             $self->graph($parent, $graph)
197 0 0 0       if ($e and $self->{'recursive'});
198             }
199             }
200 0 0         if ($module_overview->{'uses'}) {
201 0           my $module_name_use = $module_name.' use';
202 0           $graph->add_node($module_name_use)->set_attributes({
203             'label' => 'use',
204             'shape' => 'ellipse',
205             'font-size' => '75%',
206             });
207 0           $graph->add_edge_once($module_name => $module_name_use);
208              
209 0           foreach my $use (@{$module_overview->{'uses'}}) {
  0            
210 0           $graph->add_node($use);
211            
212 0           my $e = $graph->add_edge_once($module_name_use, $use);
213            
214             #my $e = $graph->add_edge_once($module_name, $use, 'use');
215            
216             $self->graph($use, $graph)
217 0 0 0       if ($e and $self->{'recursive'});
218             }
219             }
220 0 0         if ($module_overview->{'methods'}) {
221 0           my $module_name_methods = $module_name.' methods';
222             $graph->add_node($module_name_methods)->set_attributes({
223 0           'label' => join('\n', @{$module_overview->{'methods'}}),
  0            
224             'font-size' => '75%',
225             'align' => 'left',
226             'borderstyle' => 'dashed',
227             });
228 0           $graph->add_edge_once($module_name => $module_name_methods, 'methods');
229             }
230 0 0         if ($module_overview->{'methods_imported'}) {
231 0           my $module_name_methods = $module_name.' methods_imported';
232             $graph->add_node($module_name_methods)->set_attributes({
233 0           'label' => join('\n', @{$module_overview->{'methods_imported'}}),
  0            
234             'font-size' => '75%',
235             'align' => 'left',
236             'borderstyle' => 'dashed',
237             });
238 0           $graph->add_edge_once($module_name => $module_name_methods, 'methods imported');
239             }
240              
241 0           return $graph;
242             }
243              
244             'OV?';
245              
246             __END__