File Coverage

blib/lib/Devel/Isa/Explainer.pm
Criterion Covered Total %
statement 149 176 84.6
branch 40 54 74.0
condition 3 3 100.0
subroutine 27 29 93.1
pod 1 1 100.0
total 220 263 83.6


line stmt bran cond sub pod time code
1 11     11   106008 use 5.006; # our
  11         80  
2 11     11   34 use strict;
  11         10  
  11         175  
3 11     11   32 use warnings;
  11         9  
  11         542  
4              
5             package Devel::Isa::Explainer;
6              
7             our $VERSION = '0.002900'; # TRIAL
8              
9             # ABSTRACT: Pretty Print Hierarchies of Subs in Packages
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 11     11   35 use Exporter ();
  11         13  
  11         275  
14 11     11   4609 use Term::ANSIColor 3.00 ('colored'); # bright_
  11         48046  
  11         2383  
15 11     11   54 use Carp ('croak');
  11         7  
  11         331  
16 11     11   4263 use MRO::Compat ();
  11         21756  
  11         197  
17 11     11   48 use B ('svref_2object');
  11         11  
  11         499  
18 11     11   3862 use Devel::Isa::Explainer::_MRO qw( get_linear_class_shadows get_parents );
  11         17  
  11         944  
19              
20              
21             # Perl critic is broken. This is not a void context.
22             ## no critic (BuiltinFunctions::ProhibitVoidMap)
23 11     11   43 use constant 1.03 ( { map { ( ( sprintf '_E%x', $_ ), ( sprintf ' (id: %s#%d)', __PACKAGE__, $_ ), ) } 1 .. 5 } );
  11         149  
  11         31  
  55         1010  
24              
25 11     11   44 use constant _HAS_CONST => B::CV->can('CONST');
  11         13  
  11         585  
26              
27 11     11   60 use namespace::clean;
  11         14  
  11         34  
28              
29 11     11   19742 BEGIN { *import = \&Exporter::import } ## no critic (ProhibitCallsToUnexportedSubs)
30              
31             our @EXPORT_OK = qw( explain_isa );
32              
33             # These exist for twiddling, but are presently undocumented as their interface
34             # is not deemed even remotely stable. Use at own risk.
35              
36             our @TYPE_UTIL = qw( cyan );
37             our @TYPE = qw( yellow );
38             our @PRIVATE = qw( reset );
39             our @PUBLIC = qw( bold bright_green );
40             our @SHADOWED_PRIVATE = qw( magenta );
41             our @SHADOWED_PUBLIC = qw( red );
42              
43             our $MAX_WIDTH = 80;
44             our $SHOW_SHADOWED = 1;
45             our $INDENT = q[ ] x 4;
46             our $SHADOW_SUFFIX = q{(^)};
47             our $SHADOWED_SUFFIX = q{}; # TBD
48             our $CLUSTERING = 'type_clustered';
49              
50              
51              
52              
53              
54              
55              
56              
57              
58             sub explain_isa {
59 6     6 1 1272 my $nargs = scalar( my ( $target, ) = @_ );
60 6 50       15 1 == $nargs or croak "Passed $nargs arguments, Expected 1" . _E1;
61 6 50       12 defined $target or croak 'Expected defined target' . _E2;
62 6 50       18 length $target or croak 'Expected target with non-zero length' . _E3;
63 6 50       11 ref $target and croak 'Expected scalar target' . _E4;
64 6         15 return _pp_key() . _pp_class($target);
65             }
66              
67             # -- no user servicable parts --
68             sub _sub_type {
69 364     364   286 my ($sub) = @_;
70 364 100       676 return 'PRIVATE' if $sub =~ /\A_/sx;
71 240 50       359 return 'TYPE_UTIL' if $sub =~ /\A(is_|assert_|to_)[[:upper:]]/sx;
72 240 100       359 return 'PRIVATE' if $sub =~ /\A[[:upper:]][[:upper:]]/sx;
73 216 50       249 return 'TYPE' if $sub =~ /\A[[:upper:]]/sx;
74 216         454 return 'PUBLIC';
75             }
76              
77             sub _hl_TYPE_UTIL {
78 6 50   6   38 if ( $_[0] =~ /\A([^_]+_)(.*\z)/sx ) {
79 6         16 return colored( \@TYPE_UTIL, $1 ) . colored( \@TYPE, $2 );
80             }
81 0         0 return $_[0];
82             }
83              
84             sub _hl_suffix {
85 230 100   230   4174 return colored( $_[0], $SHADOW_SUFFIX ) if $_[1]->{shadowing};
86 201 100       283 return colored( $_[0], $SHADOWED_SUFFIX ) if $_[1]->{shadowed};
87 185         474 return q[];
88             }
89              
90 6     6   16 sub _hl_TYPE { return colored( \@TYPE, $_[0] ) }
91              
92             sub _hl_PUBLIC {
93 132 100   132   294 return ( $_[1]->{shadowed} ? colored( \@SHADOWED_PUBLIC, $_[0] ) : colored( \@PUBLIC, $_[0] ) )
94             . _hl_suffix( \@SHADOWED_PUBLIC, $_[1] );
95             }
96              
97             sub _hl_PRIVATE {
98 98 100   98   206 return ( $_[1]->{shadowed} ? colored( \@SHADOWED_PRIVATE, $_[0] ) : colored( \@PRIVATE, $_[0] ) )
99             . _hl_suffix( \@SHADOWED_PRIVATE, $_[1] );
100             }
101              
102             sub _pp_sub {
103 182     182   197 return __PACKAGE__->can( '_hl_' . _sub_type( $_[0] ) )->(@_);
104             }
105              
106             sub _pp_key {
107 6     6   7 my @tokens;
108 6         13 push @tokens, 'Public Sub: ' . _hl_PUBLIC('foo_example');
109 6         15 push @tokens, 'Type Constraint: ' . _hl_TYPE('TypeName');
110 6         110 push @tokens, 'Type Constraint Utility: ' . _hl_TYPE_UTIL('typeop_TypeName');
111 6         196 push @tokens, 'Private/Boring Sub: ' . _hl_PRIVATE('foo_example');
112 6 50       15 if ($SHOW_SHADOWED) {
113 6         15 push @tokens, 'Public Sub shadowing another: ' . _hl_PUBLIC( 'shadowing_example', { shadowing => 1 } );
114 6         110 push @tokens, 'Public Sub shadowed by higher scope: ' . _hl_PUBLIC( 'shadowed_example', { shadowed => 1 } );
115 6         103 push @tokens, 'Public Sub shadowing another and shadowed itself: '
116             . _hl_PUBLIC( 'shadowed_shadowing_example', { shadowing => 1, shadowed => 1 } );
117              
118 6         100 push @tokens, 'Private/Boring Sub shadowing another: ' . _hl_PRIVATE( 'shadowing_example', { shadowing => 1 } );
119 6         122 push @tokens, 'Private/Boring Sub shadowed by higher scope: ' . _hl_PRIVATE( 'shadowed_example', { shadowed => 1 } );
120 6         101 push @tokens, 'Private/Boring Sub another and shadowed itself: '
121             . _hl_PRIVATE( 'shadowing_shadowed_example', { shadowed => 1, shadowing => 1 } );
122             }
123 6         94 push @tokens, 'No Subs: ()';
124 6         48 return sprintf "Key:\n$INDENT%s\n\n", join qq[\n$INDENT], @tokens;
125             }
126              
127             sub _mg_sorted {
128 20     20   50 my (%subs) = @_;
129 20 50       31 if ($SHOW_SHADOWED) {
130 20         49 return ( [ sort { lc $a cmp lc $b } keys %subs ] );
  593         464  
131             }
132 0         0 return ( [ grep { !$subs{$_} } sort { lc $a cmp lc $b } keys %subs ] );
  0         0  
  0         0  
133             }
134              
135             sub _mg_type_shadow_clustered {
136 0     0   0 my (%subs) = @_;
137 0         0 my %clusters;
138 0         0 for my $sub ( keys %subs ) {
139 0         0 my $shadow = '.shadowed' x !!$subs{$sub};
140 0         0 $clusters{ _sub_type($sub) . $shadow }{$sub} = $subs{$sub};
141             }
142 0         0 my @out;
143 0         0 for my $type ( map { ( $_, "$_.shadowed" ) } qw( PUBLIC PRIVATE TYPE TYPE_UTIL ) ) {
  0         0  
144 0 0       0 next unless exists $clusters{$type};
145 0         0 push @out, _mg_sorted( %{ $clusters{$type} } );
  0         0  
146             }
147 0         0 return @out;
148             }
149              
150             sub _mg_type_clustered {
151 12     12   39 my (%subs) = @_;
152 12         12 my %clusters;
153 12         27 for my $sub ( keys %subs ) {
154 182         176 $clusters{ _sub_type($sub) }{$sub} = $subs{$sub};
155             }
156 12         13 my @out;
157 12         12 for my $type (qw( PUBLIC PRIVATE TYPE TYPE_UTIL )) {
158 48 100       78 next unless exists $clusters{$type};
159 20         11 push @out, _mg_sorted( %{ $clusters{$type} } );
  20         54  
160             }
161 12         46 return @out;
162             }
163              
164             sub _mg_aleph {
165 0     0   0 my (%subs) = @_;
166 0         0 my %clusters;
167 0         0 for my $sub ( keys %subs ) {
168 0         0 $clusters{ lc( substr $sub, 0, 1 ) }{$sub} = $subs{$sub};
169             }
170 0         0 my @out;
171 0         0 for my $key ( sort keys %clusters ) {
172 0         0 push @out, _mg_sorted( %{ $clusters{$key} } );
  0         0  
173             }
174 0         0 return @out;
175              
176             }
177              
178             sub _pp_subs {
179 12     12   37 my (%subs) = @_;
180 12         90 my (@clusters) = __PACKAGE__->can( '_mg_' . $CLUSTERING )->(%subs);
181 12         17 my (@out_clusters);
182 12         13 for my $cluster (@clusters) {
183 20         19 my $cluster_out = q[];
184              
185 20         15 my @subs = @{$cluster};
  20         33  
186 20         33 while (@subs) {
187 51         37 my $line = $INDENT;
188             flowcontrol: {
189 51         34 my $sub = shift @subs;
  182         124  
190 182         130 $line .= $sub . q[, ];
191 182 100 100     424 redo flowcontrol if @subs and length $line < $MAX_WIDTH;
192             }
193 51         86 $cluster_out .= "$line\n";
194             }
195              
196             # Suck up trailing ,
197 20         57 $cluster_out =~ s/,[ ]\n\z/\n/sx;
198 20         57 $cluster_out =~ s{(\w+)}{ _pp_sub($1, $subs{$1} ) }gsex;
  182         338  
199 20         143 push @out_clusters, $cluster_out;
200             }
201 12         74 return join qq[\n], @out_clusters;
202             }
203              
204             sub _pp_class {
205 6     6   10 my ($class) = @_;
206 6         8 my $out = q[];
207 6         13 my $mro_order = _extract_mro($class);
208 4         5 for my $mro_entry ( @{$mro_order} ) {
  4         7  
209 12         31 $out .= colored( ['green'], $mro_entry->{class} ) . q[:];
210 12 50       208 my (%subs) = %{ $mro_entry->{subs} || {} };
  12         94  
211 12 50       26 if ( not keys %subs ) {
212 0         0 $out .= " ()\n";
213 0         0 next;
214             }
215 12         14 else { $out .= "\n" }
216 12         31 $out .= _pp_subs(%subs) . "\n";
217              
218 12         29 next;
219             }
220 4         76 return $out;
221             }
222              
223             sub _extract_mro {
224 14     14   4279 my ($class) = @_;
225              
226 14         18 my (@mro_order) = @{ get_linear_class_shadows($class) };
  14         56  
227              
228 14         23 my $found_interesting = 0;
229 14         25 for my $isa_entry (@mro_order) {
230              
231             # Universal will always be present, but parents/children
232             # of UNIVERSAL are "interesting"
233 16 100       38 next if 'UNIVERSAL' eq $isa_entry->{class};
234 14 100       12 next unless keys %{ $isa_entry->{subs} };
  14         43  
235 12         14 $found_interesting++;
236 12         17 last;
237             }
238 14         22 for my $isa_entry (@mro_order) {
239 39         91 $isa_entry->{parents} = get_parents( $isa_entry->{class} );
240             ## no critic (Subroutines::ProhibitCallsToUnexportedSubs)
241 39         95 $isa_entry->{mro} = mro::get_mro( $isa_entry->{class} );
242 39         36 for my $sub ( keys %{ $isa_entry->{subs} } ) {
  39         132  
243 675         530 my $sub_data = $isa_entry->{subs}->{$sub};
244 675         459 @{$sub_data}{ 'xsub', 'constant', 'stub' } = ( 0, 0, 0 );
  675         795  
245 675         575 my $ref = delete $sub_data->{ref};
246 675         896 my $cv = svref_2object($ref);
247 675 100       1260 if ( _HAS_CONST ? $cv->CONST : ref $cv->XSUBANY ) {
    100          
    100          
248 349         426 $sub_data->{constant} = 1;
249             }
250             elsif ( $cv->XSUB ) {
251 93         123 $sub_data->{xsub} = 1;
252             }
253 233         407 elsif ( not defined &{$ref} ) {
254 1         2 $sub_data->{stub} = 1;
255             }
256             }
257             }
258 14 100       32 if ( not $found_interesting ) {
259              
260             # Huh, No inheritance, and no subs. K.
261 2         2 my $module_path = $class;
262 2         9 $module_path =~ s{ (::|') }{/}sgx;
263              
264             # TODO: Maybe don't make this fatal and return data context to user instead?
265             # Undecided, and will have to come after more complex concerns.
266             # - kentnl, Apr 2016
267 2 50       7 if ( not $INC{ $module_path . '.pm' } ) {
268 2         219 croak "No module called $class successfully loaded" . _E5;
269             }
270             }
271 12         28 return \@mro_order;
272             }
273              
274             1;
275              
276             __END__