File Coverage

blib/lib/Devel/TRay.pm
Criterion Covered Total %
statement 97 115 84.3
branch 29 48 60.4
condition 0 3 0.0
subroutine 19 21 90.4
pod 0 1 0.0
total 145 188 77.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::TRay - See what your code's doing
4              
5             =head1 AUTHOR
6              
7             Pavel P. Serikov
8              
9             =head1 LICENCE
10              
11             Perl5
12              
13             =head1 SYNOPSIS
14              
15             #!/usr/bin/perl -d:TRay
16              
17             or
18              
19             perl -d:TRay script.pl
20            
21             =head1 DESCRIPTION
22              
23             Fork of L with following additions
24              
25             =over
26              
27             =item *
28              
29             Filter output as easy as L
30            
31             =item *
32            
33             Ability to not show public and CORE module calls
34              
35             =back
36              
37             See module tests for more details.
38            
39             =head1 FILTERS USAGE
40              
41             You can use multiple filters with syntax like
42              
43             -d:TRay=subs_matching=X:hide_core=1:hide_cpan=1:hide_eval=1:show_lines=0
44              
45             ( import options are separated with ':' symbol )
46              
47             =cut
48              
49             package Devel::TRay;
50 1     1   100689 use warnings;
  1         2  
  1         77  
51 1     1   7 use strict;
  1         2  
  1         25  
52 1     1   5 no strict 'refs';
  1         1  
  1         25  
53              
54 1     1   5 use vars qw($SUBS_MATCHING);
  1         2  
  1         283  
55             our $VERSION = '0.03';
56             our $calls = [];
57             our $ARGS;
58             our $SUBS_MATCHING = qr/.*/;
59              
60             sub _get_args {
61 5     5   11 my ( $arg_str ) = @_;
62 5 100       18 return if !$arg_str;
63 4         5 my $res;
64 4         18 my @x = split( ':', $arg_str);
65 4         13 for my $i (@x) {
66 13         25 my @y = split( '=', $i );
67 13         40 $res->{ $y[0] } = $y[1];
68             }
69 4         17 return $res;
70             }
71              
72             sub import {
73 5     5   7137 my ( $self, $import_tags ) = @_;
74 5         18 $ARGS = _get_args($import_tags);
75            
76 5 100       20 my $re = $ARGS->{subs_matching} if $ARGS;
77              
78 5 100       51 if ($re) {
79 1         26 $Devel::TRay::SUBS_MATCHING = qr/$re/;
80             }
81             }
82              
83             package DB;
84 1     1   8 use Data::Dumper;
  1         2  
  1         61  
85 1     1   13 use List::Util qw(uniq);
  1         2  
  1         87  
86 1     1   519 use MetaCPAN::Client;
  1         340102  
  1         35  
87 1     1   2866 use Module::CoreList;
  1         104655  
  1         11  
88             sub DB{};
89             our $CALL_DEPTH = 0;
90             our $traced_modules = [];
91             my $indent = $Devel::TRay::ARGS->{indent} || " ";
92             my $mcpan = MetaCPAN::Client->new( version => 'v1' );
93              
94             sub _get_enabled_module_filters {
95 5 100   5   816 return [ grep { $_ =~ 'hide_' && $Devel::TRay::ARGS->{$_} } sort keys %{$Devel::TRay::ARGS} ];
  16         110  
  5         35  
96             }
97              
98             sub sub {
99             local $DB::CALL_DEPTH = $DB::CALL_DEPTH+1;
100             Devel::TRay::called($DB::CALL_DEPTH, \@_)
101             if ($DB::sub =~ $Devel::TRay::SUBS_MATCHING);
102             &{$DB::sub};
103             }
104              
105              
106             sub Devel::TRay::called {
107 0     0 0 0 my ( $depth, $routine_params ) = @_;
108 0         0 my $frame = { 'sub' => "$DB::sub", 'depth' => $depth };
109 0 0       0 if (exists $DB::sub{$DB::sub}) {
110 0         0 $frame->{'line'} = $DB::sub{$DB::sub};
111             }
112 0         0 push @$calls, $frame;
113             }
114              
115             # return Data::Dumper from Data::Dumper::Dumper()
116             sub _extract_module_name {
117 7     7   2678 my ($sub) = @_;
118 7         25 my @x = split( '::', $sub );
119 7 100       26 return $x[0] if ( scalar @x == 1 );
120 6         9 pop @x;
121 6         36 return join( '::', @x );
122             }
123              
124             sub _is_cpan_published {
125 7     7   2244 my ($pkg, $severity) = @_;
126 7 50       27 return 0 if !defined $pkg;
127 7 50       25 $severity = 2 if !defined $severity;
128            
129 7 50       40 if ( $severity == 0 ) {
    50          
    50          
130             eval {
131 0         0 return $mcpan->module($pkg)->distribution;
132 0 0       0 } or do {
133 0         0 return 0;
134             }
135             }
136            
137             elsif ( $severity == 1 ) {
138 0         0 my $expected_distro = $pkg;
139 0         0 $expected_distro =~ s/::/-/g;
140             eval {
141 0         0 return $mcpan->distribution($expected_distro)->name;
142 0 0       0 } or do {
143 0         0 return 0;
144             }
145             }
146            
147             elsif ( $severity == 2 ) {
148 7         14 my $expected_distro = $pkg;
149 7         27 $expected_distro =~ s/::/-/g;
150            
151 7         13 my $success = eval {
152 7         37 $mcpan->distribution($expected_distro)->name;
153             };
154 7 100       358755 return $success if $success;
155            
156 5         13 $success = eval {
157 5         27 $mcpan->module($pkg)->distribution;
158             };
159            
160 5 100       481485 if ( $success ) {
161             # exceptions
162 1 50       6 return $success if ( $success eq 'Moo' );
163 1 50       4 return $success if ( $success eq 'Moose' );
164            
165             # $pkg can be Sub::Defer and $success is Sub-Quote
166 1         7 my $root_namespace = (split( '-', $success))[0];
167 1 50       19 return $success if ( $pkg =~ qr/$root_namespace/ );
168             }
169            
170 5         36 return 0;
171             }
172            
173             else {
174 0         0 die "Wrong or non implemented severity value";
175             }
176             }
177              
178             sub _is_core {
179 10     10   7226 my ($pkg) = @_;
180 10 50       26 return 0 if !defined $pkg;
181 10         39 return Module::CoreList::is_core(@_);
182             }
183              
184             sub _is_eval {
185 7     7   2581 my ($pkg) = @_;
186 7 50       26 return 0 if !defined $pkg;
187 7 100       26 return 1 if ( $pkg eq '(eval)' );
188 4         14 return 0;
189             }
190              
191             sub _check_filter {
192 18     18   2311 my ($option, $pkg) = @_;
193             # dispatch table
194             # all functions must return true when value need to be removed
195 18         74 my %actions = (
196             'hide_cpan' => \&_is_cpan_published,
197             'hide_core' => \&_is_core,
198             'hide_eval' => \&_is_eval
199             );
200 18         56 my $res = $actions{$option}->($pkg);
201             # print STDERR "$option\t$pkg\t$res\n";
202 18         18186 return $res;
203             }
204              
205             # return 1 if module must be leaved in stacktrace
206             sub _leave_in_trace {
207 6     6   22 my ( $module, $filters ) = @_;
208            
209 6 50       19 die "No filters specified" if !defined $filters;
210            
211 6         15 for my $f (@$filters) {
212 12 100       29 return 0 if ( _check_filter( $f, $module ) );
213             }
214 2         13 return 1;
215             }
216              
217             sub _filter_calls {
218 2     2   385 my ( $calls ) = @_;
219            
220 2         8 @$calls = grep { $_->{'sub'} !~ /CODE/ } @$calls;
  3         13  
221            
222 2         7 my $subs = [ map { $_->{'sub'} } @$calls ];
  2         6  
223 2         18 $traced_modules = [ uniq map { _extract_module_name($_) } @$subs ];
  2         5  
224              
225 2         8 @$traced_modules = grep { $_ ne 'Devel::TRay' } @$traced_modules;
  2         6  
226            
227 2         10 my $filters = _get_enabled_module_filters();
228 2         8 @$traced_modules = grep { _leave_in_trace($_, $filters) } @$traced_modules;
  2         5  
229            
230 2         7 my %modules_left = map { $_ => 1 } @$traced_modules;
  1         5  
231 2         6 @$calls = grep { $modules_left{_extract_module_name($_->{'sub'})} } @$calls;
  2         7  
232            
233 2         22 return { 'calls' => $calls, 'traced' => $traced_modules };
234             }
235              
236             sub _print {
237 0     0     my ( $frame ) = @_;
238 0           my $str = $indent x $frame->{'depth'} . $frame->{'sub'};
239 0 0 0       $str.= " (".$frame->{'line'}.")" if ( $frame->{'line'} && $Devel::TRay::show_lines );
240 0           print STDERR "$str\n";
241             }
242              
243             END {
244 1     1   1631550 _filter_calls($calls);
245 1         15 _print($_) for @$calls;
246             }
247              
248             1;