| 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 |  | 61915 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 27 |  | 
| 25 | 1 |  |  | 1 |  | 5 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 33 |  | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | our $VERSION = '0.01'; | 
| 28 |  |  |  |  |  |  |  | 
| 29 | 1 |  |  | 1 |  | 21 | use 5.010; | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 1 |  |  | 1 |  | 887 | use Class::Sniff; | 
|  | 1 |  |  |  |  | 262625 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 32 | 1 |  |  | 1 |  | 13 | use Text::SimpleTable; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 33 | 1 |  |  | 1 |  | 1724 | use Module::ExtractUse; | 
|  | 1 |  |  |  |  | 173058 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 34 | 1 |  |  | 1 |  | 10 | use Graph::Easy; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 25 |  | 
| 35 | 1 |  |  | 1 |  | 5 | use Carp 'confess'; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 1 |  |  | 1 |  | 5 | use base 'Class::Accessor::Fast'; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 776 |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | __PACKAGE__->mk_accessors(qw{ | 
| 40 |  |  |  |  |  |  | module_name | 
| 41 |  |  |  |  |  |  | recursive | 
| 42 |  |  |  |  |  |  | recursion_filter | 
| 43 |  |  |  |  |  |  | hide_methods | 
| 44 |  |  |  |  |  |  | }); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | sub new { | 
| 47 | 2 |  |  | 2 | 1 | 47483 | my $class = shift; | 
| 48 | 2 |  |  |  |  | 36 | my $self = $class->SUPER::new(@_); | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 2 | 50 |  |  |  | 35 | confess('module_name is mandatory property') | 
| 51 |  |  |  |  |  |  | if not $self->module_name; | 
| 52 |  |  |  |  |  |  |  | 
| 53 | 2 |  |  |  |  | 29 | return $self; | 
| 54 |  |  |  |  |  |  | } | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | sub get { | 
| 57 | 2 |  |  | 2 | 1 | 4 | my $self        = shift; | 
| 58 | 2 |  | 33 |  |  | 8 | my $module_name = shift || $self->{'module_name'}; | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 2 |  |  |  |  | 4 | my $recursion_filter = $self->{'recursion_filter'}; | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 2 |  |  |  |  | 5 | my %overview; | 
| 63 |  |  |  |  |  |  |  | 
| 64 | 1 |  |  | 1 |  | 11 | eval qq{ use $module_name }; | 
|  | 1 |  |  | 1 |  | 2 |  | 
|  | 1 |  |  |  |  | 12 |  | 
|  | 1 |  |  |  |  | 10 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 12 |  | 
|  | 2 |  |  |  |  | 231 |  | 
| 65 | 2 | 50 |  |  |  | 40 | warn 'error loading "'.$module_name.'" - '.$@ if $@; | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 2 |  |  |  |  | 24 | my $sniff = Class::Sniff->new({class => $module_name}); | 
| 68 | 2 |  |  |  |  | 1639034 | my $euse  = Module::ExtractUse->new; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | #my $graph    = $sniff->graph;   # Graph::Easy | 
| 71 |  |  |  |  |  |  | #print $sniff->report; | 
| 72 |  |  |  |  |  |  | #print join("\n", $sniff->methods), "\n"; | 
| 73 | 2 |  |  |  |  | 25 | $overview{'class'} = $module_name; | 
| 74 | 2 |  |  |  |  | 618 | $overview{'parents'} = [ | 
| 75 | 2 |  |  |  |  | 39 | grep { not ($_ ~~ [qw(Exporter)]) }     # skip uninteresting | 
| 76 | 2 |  |  |  |  | 10 | grep { $_ !~ m{^[0-9._]+$} }            # skip perl versions | 
| 77 |  |  |  |  |  |  | $sniff->parents | 
| 78 |  |  |  |  |  |  | ]; | 
| 79 | 2 |  |  |  |  | 13 | delete $overview{'parents'} | 
| 80 | 2 | 50 |  |  |  | 6 | if not @{$overview{'parents'}}; | 
| 81 | 5 |  |  |  |  | 18 | $overview{'classes'} = [ | 
| 82 | 5 |  |  |  |  | 20 | grep { not ($_ ~~ $overview{'parents'}) }  # skip parents | 
| 83 | 5 |  |  |  |  | 16 | grep { not ($_ ~~ [qw(Exporter)]) }        # skip uninteresting | 
| 84 | 7 |  |  |  |  | 25 | grep { $_ !~ m{^[0-9._]+$} }               # skip perl versions | 
| 85 | 2 |  |  |  |  | 12 | grep { $_ ne $module_name }                # skip self | 
| 86 |  |  |  |  |  |  | $sniff->classes | 
| 87 |  |  |  |  |  |  | ]; | 
| 88 | 2 |  |  |  |  | 10 | delete $overview{'classes'} | 
| 89 | 2 | 50 |  |  |  | 6 | if not @{$overview{'classes'}}; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 2 |  |  |  |  | 8 | my $module_name_path = $module_name.'.pm'; | 
| 92 | 2 |  |  |  |  | 11 | $module_name_path =~ s{::}{/}g; | 
| 93 | 2 | 50 | 33 |  |  | 151 | if (exists $INC{$module_name_path} and (-r $INC{$module_name_path})) { | 
| 94 | 2 |  |  |  |  | 14 | $euse->extract_use($INC{$module_name_path}); | 
| 95 | 2 |  |  |  |  | 75086 | $DB::single=1; | 
| 96 | 5 | 50 |  |  |  | 19 | $overview{'uses'} = [ | 
| 97 | 7 |  |  |  |  | 17 | grep { (not $recursion_filter) or ($_ =~ m/$recursion_filter/) }   # filter modules | 
| 98 | 9 |  |  |  |  | 34 | grep { not ($_ ~~ $overview{'parents'}) }                          # skip parents | 
| 99 | 10 |  |  |  |  | 80 | grep { not ($_ ~~ [qw(strict warnings constant vars Exporter)]) }  # skip uninteresting | 
| 100 | 2 |  |  |  |  | 13 | grep { $_ !~ m{^[0-9._]+$} }                                       # skip perl versions | 
| 101 |  |  |  |  |  |  | sort | 
| 102 |  |  |  |  |  |  | $euse->array | 
| 103 |  |  |  |  |  |  | ]; | 
| 104 | 2 |  |  |  |  | 11 | delete $overview{'uses'} | 
| 105 | 2 | 100 |  |  |  | 4 | if not @{$overview{'uses'}}; | 
| 106 |  |  |  |  |  |  | } | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 2 |  |  |  |  | 3 | my (@methods, @methods_imported); | 
| 109 | 2 |  |  |  |  | 4 | while (my ($method, $classes) = each %{$sniff->{methods}}) { | 
|  | 60 |  |  |  |  | 191 |  | 
| 110 | 58 |  |  |  |  | 59 | my $class = ${$classes}[0]; | 
|  | 58 |  |  |  |  | 87 |  | 
| 111 | 58 | 100 |  |  |  | 165 | 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 |  | 5720 | my $glob = do { no strict 'refs'; \*{$class.'::'.$method} }; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 816 |  | 
|  | 58 |  |  |  |  | 60 |  | 
|  | 58 |  |  |  |  | 59 |  | 
|  | 58 |  |  |  |  | 172 |  | 
| 116 | 58 |  |  |  |  | 168 | my $o = B::svref_2object($glob); | 
| 117 |  |  |  |  |  |  | # in 5.005 this flag is not exposed via B, though it exists | 
| 118 | 58 |  | 50 |  |  | 65 | my $imported_cv = eval { B::GVf_IMPORTED_CV() } || 0x80; | 
| 119 | 58 |  |  |  |  | 126 | my $imported = $o->GvFLAGS & $imported_cv; | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 58 | 100 |  |  |  | 108 | if ($imported) { | 
| 122 | 18 |  |  |  |  | 27 | push @methods_imported, $method_desc; | 
| 123 | 18 |  |  |  |  | 38 | next; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 40 |  |  |  |  | 103 | push @methods, $method_desc; | 
| 127 |  |  |  |  |  |  | } | 
| 128 | 2 | 100 | 66 |  |  | 47 | $overview{'methods'}          = [ sort @methods ] | 
| 129 |  |  |  |  |  |  | if @methods and (not $self->{'hide_methods'}); | 
| 130 | 2 | 100 | 66 |  |  | 21 | $overview{'methods_imported'} = [ sort @methods_imported ] | 
| 131 |  |  |  |  |  |  | if @methods_imported and (not $self->{'hide_methods'}); | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 2 |  |  |  |  | 77 | return \%overview; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | sub text_simpletable { | 
| 137 | 2 |  |  | 2 | 1 | 2148 | my $self = shift; | 
| 138 | 2 |  | 33 |  |  | 17 | my $module_name = shift || $self->{'module_name'}; | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 2 |  |  |  |  | 11 | my $module_overview = $self->get($module_name); | 
| 141 | 2 |  |  |  |  | 358 | my $table = Text::SimpleTable->new(16, 60); | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 2 |  |  |  |  | 93 | $table->row('class', $module_overview->{'class'}); | 
| 144 | 2 | 50 | 33 |  |  | 174 | if ($module_overview->{'parents'} || $module_overview->{'classes'}) { | 
| 145 | 2 |  |  |  |  | 10 | $table->hr; | 
| 146 |  |  |  |  |  |  | } | 
| 147 | 2 | 50 |  |  |  | 37 | if ($module_overview->{'parents'}) { | 
| 148 | 2 |  |  |  |  | 5 | $table->row('parents', join("\n", @{$module_overview->{'parents'}})); | 
|  | 2 |  |  |  |  | 11 |  | 
| 149 |  |  |  |  |  |  | } | 
| 150 | 2 | 50 |  |  |  | 136 | if ($module_overview->{'classes'}) { | 
| 151 | 2 |  |  |  |  | 4 | $table->row('classes', join("\n", @{$module_overview->{'classes'}})); | 
|  | 2 |  |  |  |  | 11 |  | 
| 152 |  |  |  |  |  |  | } | 
| 153 | 2 | 100 |  |  |  | 124 | if ($module_overview->{'uses'}) { | 
| 154 | 1 |  |  |  |  | 4 | $table->hr; | 
| 155 | 1 |  |  |  |  | 11 | $table->row('uses', join("\n", @{$module_overview->{'uses'}})); | 
|  | 1 |  |  |  |  | 5 |  | 
| 156 |  |  |  |  |  |  | } | 
| 157 | 2 | 100 |  |  |  | 82 | if ($module_overview->{'methods'}) { | 
| 158 | 1 |  |  |  |  | 5 | $table->hr; | 
| 159 | 1 |  |  |  |  | 14 | $table->row('methods', join("\n", @{$module_overview->{'methods'}})); | 
|  | 1 |  |  |  |  | 10 |  | 
| 160 |  |  |  |  |  |  | } | 
| 161 | 2 | 100 |  |  |  | 161 | if ($module_overview->{'methods_imported'}) { | 
| 162 | 1 |  |  |  |  | 3 | $table->hr; | 
| 163 | 1 |  |  |  |  | 11 | $table->row('methods_imported', join("\n", @{$module_overview->{'methods_imported'}})); | 
|  | 1 |  |  |  |  | 5 |  | 
| 164 |  |  |  |  |  |  | } | 
| 165 | 2 |  |  |  |  | 98 | 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 | 0 | 0 | 0 |  |  |  | $self->graph($parent, $graph) | 
| 197 |  |  |  |  |  |  | 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 | 0 | 0 | 0 |  |  |  | $self->graph($use, $graph) | 
| 217 |  |  |  |  |  |  | if ($e and $self->{'recursive'}); | 
| 218 |  |  |  |  |  |  | } | 
| 219 |  |  |  |  |  |  | } | 
| 220 | 0 | 0 |  |  |  |  | if ($module_overview->{'methods'}) { | 
| 221 | 0 |  |  |  |  |  | my $module_name_methods = $module_name.' methods'; | 
| 222 | 0 |  |  |  |  |  | $graph->add_node($module_name_methods)->set_attributes({ | 
| 223 | 0 |  |  |  |  |  | 'label'       => join('\n', @{$module_overview->{'methods'}}), | 
| 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 | 0 |  |  |  |  |  | $graph->add_node($module_name_methods)->set_attributes({ | 
| 233 | 0 |  |  |  |  |  | 'label'       => join('\n', @{$module_overview->{'methods_imported'}}), | 
| 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__ |