File Coverage

blib/lib/Carp/Object.pm
Criterion Covered Total %
statement 137 141 97.1
branch 42 52 80.7
condition 19 28 67.8
subroutine 22 23 95.6
pod 6 7 85.7
total 226 251 90.0


line stmt bran cond sub pod time code
1             package Carp::Object;
2 5     5   587243 use 5.10.0;
  5         48  
3 5     5   40 use utf8;
  5         20  
  5         54  
4 5     5   154 use strict;
  5         42  
  5         144  
5 5     5   35 use warnings;
  5         39  
  5         350  
6 5     5   2791 use Devel::StackTrace;
  5         36111  
  5         239  
7 5     5   2787 use Module::Load qw/load/;
  5         8387  
  5         35  
8 5     5   2849 use Clone qw/clone/;
  5         2751  
  5         3091  
9              
10             our $VERSION = 1.02;
11              
12             my %export_groups = (carp => [qw/carp croak confess/],
13             all => [qw/carp croak confess cluck/], );
14              
15             # ======================================================================
16             # METHODS
17             # ======================================================================
18              
19             sub new {
20 33     33 1 176237 my ($class, %args) = @_;
21            
22             # create $self, consume the 'verbose' arg
23 33         134 my $self = {verbose => delete $args{verbose}};
24              
25             # class for stack traces
26 33   50     193 $self->{stacktrace_class} = delete $args{stacktrace_class} // 'Devel::StackTrace';
27              
28             # if there is a 'clan' argument, compute a frame filter -- see L
29 33 100       87 if (my $clan = delete $args{clan}) {
30 6 50       21 not $args{frame_filter} or $class->new->croak("can't have arg 'clan' if arg 'frame_filter' is present");
31 36     36   2229 $args{frame_filter} = sub {my $raw_frame_ref = shift;
32 36         78 my $pkg = $raw_frame_ref->{caller}[0];
33 6         44 return $pkg !~ /$clan/};
  36         202  
34             }
35              
36             # handler for displaying stack frames
37 33   100     146 $self->{display_frame} = delete $args{display_frame} // \&_default_display_frame;
38 33         70 $self->{display_frame_param} = delete $args{display_frame_param};
39              
40             # classes to be ignored by Devel::StackTrace : list supplied by caller + current class
41 33   50     178 my $ignore_class = delete $args{ignore_class} // [];
42 33 50       88 $ignore_class = [$ignore_class] if not ref $ignore_class;
43 33         95 push @$ignore_class, $class;
44 33         66 $args{ignore_class} = $ignore_class;
45              
46             # remaining args will be passed to Devel::StackTrace->new
47 33   50     188 $args{message} //= ''; # to avoid the 'Trace begun' string from StackTrace::Frame::as_string
48 33   100     170 $args{indent} //= 1;
49 33         65 $self->{stacktrace_args} = \%args;
50              
51             # return the carper object
52 33         162 bless $self, $class;
53             }
54              
55 28     28 1 71 sub croak {my $self = shift; die $self->msg(join("", @_), 1)} # 1 means "just one frame"
  28         133  
56 1     1 1 6 sub carp {my $self = shift; warn $self->msg(join("", @_), 1)} # idem
  1         7  
57 4     4 1 9 sub confess {my $self = shift; die $self->msg(join("", @_) )} # no second arg means "the whole stack"
  4         21  
58 0     0 1 0 sub cluck {my $self = shift; warn $self->msg(join("", @_) )} # idem
  0         0  
59              
60             sub msg {
61 33     33 1 75 my ($self, $errstr, $n_frames) = @_;
62 33         63 my $class = ref $self;
63 33   50     70 $errstr //= "Died";
64              
65             # is this call a croak (single stackframe) or a confess (full stack) ?
66             my $want_full_stack = ! defined $n_frames
67 5   100 5   54 || $self->{verbose} || do {no warnings 'once'; $Carp::Verbose || $Carp::Clan::Verbose};
  5         8  
  5         4714  
  33         222  
68              
69              
70             # if not doing a "confess", tell Devel::Stacktrace to skip frames from the first outside caller
71 33         532 my $stacktrace_args = clone $self->{stacktrace_args};
72 33 100       106 if (!$want_full_stack) {
73 22         58 my $outside_caller;
74 22         36 my $i = 0;
75 22   50     33 do {$outside_caller = caller($i++) // ""} while $outside_caller->isa($class);
  58         403  
76 22 100       66 push @{$stacktrace_args->{ignore_package}}, $outside_caller unless $outside_caller eq 'main';
  19         65  
77             }
78              
79             # get stack frames from Devel::StackTrace and truncate the list to the requested number
80 33         139 load $self->{stacktrace_class};
81 33         2577 my $trace = $self->{stacktrace_class}->new(%{$stacktrace_args});
  33         211  
82 33         15930 my @frames = $trace->frames;
83 33 100 66     11746 splice @frames, $n_frames if @frames && !$want_full_stack;
84              
85             # complete the original $errstr with frame descriptions
86 33 50       96 if (my $first_frame = shift @frames) {
87 33         65 my $p = $self->{display_frame_param}; # see L
88 33         89 $errstr .= $self->{display_frame}->($first_frame, 1, $p); # 1 means "is first"
89 33         106 $errstr .= $self->{display_frame}->($_, undef, $p) foreach @frames;
90             }
91              
92 33         856 return $errstr;
93             }
94              
95             # ======================================================================
96             # SUBROUTINES (NOT METHODS) -- used as callback
97             # ======================================================================
98              
99             sub _default_display_frame {
100 69     69   139 my ($frame, $is_first, $p) = @_;
101              
102             # let Devel::StackTrace::Frame compute a string representation
103 69         159 my $str = $frame->as_string($is_first, $p);
104              
105             # if this seems to be a method call, make it look like so
106 69         3012 $str =~ s{^ (\t)? # optional tab -- capture in $1
107             ([\w:]+) # class name -- capture in $2
108             ::
109             (\w+) # method name -- capture in $3
110             \(' # beginning arg list
111             ( \2 # first arg: again the class name
112             (?: = [^']+)? # .. possibly followed by the ref addr
113             )
114             ' # end of fist arg -- capture in $4
115             (?: ,\h* )? # possibly followed by a comma
116             }
117             {$1$4->$3(}x; # rewrite as a method call
118              
119 69 100       177 $str .= "." if $is_first; # because Carp does add this colon to the first line
120              
121 69         221 return "$str\n";
122             }
123            
124              
125             # ======================================================================
126             # IMPORT API (CLASS METHOD)
127             # ======================================================================
128              
129             sub import {
130 5     5   83 my ($class, @import_list) = @_;
131 5         16 my $calling_pkg = caller(0);
132              
133             # find out what the importer wants
134 5         24 my ($exports, $options) = $class->parse_import_list(@import_list);
135              
136             # default exports : carp, croak and confess
137             keys %$exports
138 5 100       24 or $exports = { map {$_ => {name => $_}} @{$export_groups{carp}} };
  6         22  
  2         5  
139              
140             # if required, apply prefix and suffix
141 5 100       18 if (my $prefix = $options->{prefix}) {
142 1         9 substr $exports->{$_}{name}, 0, 0, $prefix foreach keys %$exports;
143             }
144 5 100       17 if (my $suffix = $options->{suffix}) {
145 1         7 $exports->{$_}{name} .= $suffix foreach keys %$exports;
146             }
147              
148             # export the requested symbols into the caller
149 5         24 while (my ($method, $hash) = each %$exports) {
150 5     5   47 no strict "refs";
  5         11  
  5         1284  
151 14   66     64 my $export_as = $hash->{as} // $hash->{name};
152 14         101 *{"$calling_pkg\::$export_as"} = sub (@) {
153              
154             # if present, the current value of %CARP_OBJECT_CONSTRUCTOR within the calling package
155             # will be passed to the constructor
156 19   100 19   972860 my $constructor_args = *{"$calling_pkg\::CARP_OBJECT_CONSTRUCTOR"}{HASH} // {};
  19         173  
157              
158             # if present, the current value of @CARP_NOT within the calling package
159             # will be passed as 'ignore_package' to the Devel::StackTrace constructor
160 19 100       43 if (my $carp_not = *{"$calling_pkg\::CARP_NOT"}{ARRAY}) {
  19         110  
161 3         10 $constructor_args->{ignore_package} = $carp_not;
162             }
163              
164             # build a one-shot instance and call the requested method
165 19         109 $class->new(%$constructor_args)->$method(@_);
166 14         63 };
167             }
168              
169             # install an import function into the caller if -reexport is requested
170 5 100       17 if ($options->{reexport}) {
171 5     5   37 no strict "refs";
  5         10  
  5         1527  
172 1         7 not *{"$calling_pkg\::import"}{CODE}
173 1 50       2 or $class->new->croak("use $class -reexport => ... : $calling_pkg already has an import function");
174 1         4 *{"$calling_pkg\::import"} = sub {
175 2     2   30 my $further_calling_pkg = caller(0);
176 2         10 foreach my $symbol (keys %$exports) {
177 6         10 *{"$further_calling_pkg\::$symbol"} = *{"$calling_pkg\::$symbol"}{CODE};
  6         716  
  6         16  
178             }
179 1         6 };
180             }
181              
182             # populate %CARP_OBJECT_CONSTRUCTOR within the caller from the 'constructor_args' option
183 5 100       2693 if (my $args = $options->{constructor_args}) {
184 2 50       7 ref $args eq 'HASH'
185             or $class->new->croak("use $class {-constructor_args => ...} : must be a hashref");
186 5     5   45 no strict 'refs';
  5         13  
  5         4747  
187 2         4 *{"$calling_pkg\::CARP_OBJECT_CONSTRUCTOR"} = $args;
  2         4509  
188             }
189             }
190              
191              
192             sub parse_import_list {
193 5     5 0 16 my ($class, @import_list) = @_;
194              
195 5         16 my %exports;
196             my %options;
197 5         0 my $last_export;
198              
199             # loop on import args
200 5         51 while (my $arg = shift @import_list) {
201              
202             # hashref : options to the exporter
203 9 100 33     68 if (my $ref = ref $arg) {
    100          
    100          
    100          
    50          
204 3 50       8 $ref eq 'HASH' or $class->new->croak("$class->import() cannot handle $ref references");
205 3         17 while (my ($k, $v) = each %$arg) {
206 5 100       16 if ($k =~ /^-(prefix|suffix|constructor_args|reexport)$/) {
    50          
207 3         17 $options{$1} = $v;
208             }
209             elsif ($k eq '-as') {
210 2 50       6 $last_export or $class->new->croak("use $class ... : {-as => ...} must follow the name of a symbol to import");
211 2         39 $exports{$last_export}{as} = $v;
212             }
213             else {
214 0         0 $class->new->croak("$class->import(): unknown option: '$k'");
215             }
216             }
217             }
218              
219             # the 'reexport' option -- different syntax for better readability, for ex: use C:O -reexport => qw/carp croak/;
220             elsif ($arg eq '-reexport') {
221 1         6 $options{reexport} = 1;
222             }
223              
224             # groups of symbols (:carp, :all)
225             elsif ($arg =~ /^[:-](\w+)/) {
226 2         6 undef $last_export;
227 2 50       13 my $group = $export_groups{$1} or $class->new->croak("use $class qw/:$1/ : group '$1' is not exported");
228 2         21 $exports{$_}{name} = $_ foreach @$group;
229             }
230              
231             # individual symbols
232             elsif ($arg =~ /^(croak|carp|confess|cluck)$/) {
233 2         10 $exports{$arg}{name} = $arg;
234 2         8 $last_export = $arg;
235             }
236              
237             # something that looks like a regexp -- probably intended for Carp::Clan-like behaviour
238             elsif ($arg =~ /^\^/ or $arg =~ /[|(]/ ) {
239 1         4 $options{constructor_args}{clan} = $arg;
240             }
241              
242             else {
243 0         0 $class->new->croak("use $class '$arg' : this symbol is not exported");
244             }
245              
246             }
247 5         32 return (\%exports, \%options);
248             }
249              
250             1;
251              
252              
253             __END__