File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 269 435 61.8
branch 96 202 47.5
condition 34 62 54.8
subroutine 37 60 61.6
pod 0 27 0.0
total 436 786 55.4


line stmt bran cond sub pod time code
1 5     5   59 #line 1
  5     5   8  
  5         129  
  5         21  
  5         6  
  5         213  
2             use strict; use warnings;
3             our $VERSION = '0.46';
4              
5 5     5   29 use Carp;
  5         8  
  5         2171  
6             require Exporter;
7             our @EXPORT = ();
8             our @EXPORT_BASE = qw(field const stub super);
9             our @EXPORT_OK = (@EXPORT_BASE, qw(id WWW XXX YYY ZZZ));
10             our %EXPORT_TAGS = (XXX => [qw(WWW XXX YYY ZZZ)]);
11              
12             my $stack_frame = 0;
13             my $dump = 'yaml';
14             my $bases_map = {};
15              
16             sub WWW; sub XXX; sub YYY; sub ZZZ;
17              
18             # This line is here to convince "autouse" into believing we are autousable.
19             ($_[1] eq 'import' and caller()->isa('autouse'))
20 59 50 33 59 0 325 ? \&Exporter::import # pacify autouse's equality test
21             : $_[0]->SUPER::can($_[1]) # normal case
22             }
23              
24             # TODO
25             #
26             # Exported functions like field and super should be hidden so as not to
27             # be confused with methods that can be inherited.
28             #
29              
30             my $class = shift;
31             $class = ref($class) || $class;
32 33     33 0 59 my $self = bless {}, $class;
33 33   33     87 while (@_) {
34 33         49 my $method = shift;
35 33         65 $self->$method(shift);
36 0         0 }
37 0         0 return $self;
38             }
39 33         60  
40             my $filtered_files = {};
41             my $filter_dump = 0;
42             my $filter_save = 0;
43             our $filter_result = '';
44             no strict 'refs';
45             no warnings;
46             my $self_package = shift;
47 5     5   96  
  5         10  
  5         264  
48 5     5   26 # XXX Using parse_arguments here might cause confusion, because the
  5         7  
  5         6599  
49 19     19   68 # subclass's boolean_arguments and paired_arguments can conflict, causing
50             # difficult debugging. Consider using something truly local.
51             my ($args, @export_list) = do {
52             local *boolean_arguments = sub {
53             qw(
54 19         38 -base -Base -mixin -selfless
55             -XXX -dumper -yaml
56 19     19   85 -filter_dump -filter_save
57             )
58             };
59             local *paired_arguments = sub { qw(-package) };
60             $self_package->parse_arguments(@_);
61 19         172 };
62 19     19   420 return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
  19         40  
63 19         93 if $args->{-mixin};
64              
65             $filter_dump = 1 if $args->{-filter_dump};
66 19 50       103 $filter_save = 1 if $args->{-filter_save};
67             $dump = 'yaml' if $args->{-yaml};
68 19 50       56 $dump = 'dumper' if $args->{-dumper};
69 19 50       47  
70 19 50       57 local @EXPORT_BASE = @EXPORT_BASE;
71 19 50       74  
72             if ($args->{-XXX}) {
73 19         68 push @EXPORT_BASE, @{$EXPORT_TAGS{XXX}}
74             unless grep /^XXX$/, @EXPORT_BASE;
75 19 50       1418 }
76 0 0       0  
  0         0  
77             spiffy_filter()
78             if ($args->{-selfless} or $args->{-Base}) and
79             not $filtered_files->{(caller($stack_frame))[1]}++;
80              
81             my $caller_package = $args->{-package} || caller($stack_frame);
82 19 100 66     4500 push @{"$caller_package\::ISA"}, $self_package
      66        
83             if $args->{-Base} or $args->{-base};
84 19   33     199  
85 7         84 for my $class (@{all_my_bases($self_package)}) {
86 19 100 66     93 next unless $class->isa('Spiffy');
87             my @export = grep {
88 19         38 not defined &{"$caller_package\::$_"};
  19         58  
89 24 50       158 } ( @{"$class\::EXPORT"},
90             ($args->{-Base} or $args->{-base})
91 278         300 ? @{"$class\::EXPORT_BASE"} : (),
  278         914  
92 24         204 );
93             my @export_ok = grep {
94 24 100 66     39 not defined &{"$caller_package\::$_"};
  7         23  
95             } @{"$class\::EXPORT_OK"};
96              
97 171         200 # Avoid calling the expensive Exporter::export
  171         490  
98 24         43 # if there is nothing to do (optimization)
  24         97  
99             my %exportable = map { ($_, 1) } @export, @export_ok;
100             next unless keys %exportable;
101              
102 24         54 my @export_save = @{"$class\::EXPORT"};
  401         637  
103 24 50       93 my @export_ok_save = @{"$class\::EXPORT_OK"};
104             @{"$class\::EXPORT"} = @export;
105 24         39 @{"$class\::EXPORT_OK"} = @export_ok;
  24         162  
106 24         35 my @list = grep {
  24         68  
107 24         30 (my $v = $_) =~ s/^[\!\:]//;
  24         107  
108 24         47 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  24         75  
109             } @export_list;
110 24         46 Exporter::export($class, $caller_package, @list);
  7         33  
111 7 50       30 @{"$class\::EXPORT"} = @export_save;
  0         0  
112             @{"$class\::EXPORT_OK"} = @export_ok_save;
113 24         3217 }
114 24         111 }
  24         86  
115 24         51  
  24         2211  
116             require Filter::Util::Call;
117             my $done = 0;
118             Filter::Util::Call::filter_add(
119             sub {
120 7     7 0 4769 return 0 if $done;
121 7         4647 my ($data, $end) = ('', '');
122             while (my $status = Filter::Util::Call::filter_read()) {
123             return $status if $status < 0;
124 14 100   14   433 if (/^__(?:END|DATA)__\r?$/) {
125 7         17 $end = $_;
126 7         103 last;
127 4121 50       5419 }
128 4121 50       5548 $data .= $_;
129 0         0 $_ = '';
130 0         0 }
131             $_ = $data;
132 4121         4705 my @my_subs;
133 4121         7538 s[^(sub\s+\w+\s+\{)(.*\n)]
134             [${1}my \$self = shift;$2]gm;
135 7         189 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
136 7         25 [${1}${2}]gm;
137 7         816 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
138 7         583 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
139 7         204 my $preclare = '';
  0         0  
  0         0  
140 7         20 if (@my_subs) {
141 7 50       56 $preclare = join ',', map "\$$_", @my_subs;
142 0         0 $preclare = "my($preclare);";
143 0         0 }
144             $_ = "use strict;use warnings;$preclare${_};1;\n$end";
145 7         270 if ($filter_dump) { print; exit }
146 7 50       31 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
147 7 50       21 $done = 1;
  0         0  
  0         0  
148 7         554 }
149             );
150 7         53 }
151              
152             push @_, -base;
153             goto &import;
154 0     0 0 0 }
155 0         0  
156             my $class = shift;
157              
158             return $bases_map->{$class}
159 24     24 0 39 if defined $bases_map->{$class};
160              
161             my @bases = ($class);
162 24 100       102 no strict 'refs';
163             for my $base_class (@{"${class}::ISA"}) {
164 10         25 push @bases, @{all_my_bases($base_class)};
165 5     5   41 }
  5         10  
  5         1685  
166 10         17 my $used = {};
  10         59  
167 5         12 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  5         26  
168             }
169 10         23  
170 10         25 my %code = (
  15         79  
171             sub_start =>
172             "sub {\n",
173             set_default =>
174             " \$_[0]->{%s} = %s\n unless exists \$_[0]->{%s};\n",
175             init =>
176             " return \$_[0]->{%s} = do { my \$self = \$_[0]; %s }\n" .
177             " unless \$#_ > 0 or defined \$_[0]->{%s};\n",
178             weak_init =>
179             " return do {\n" .
180             " \$_[0]->{%s} = do { my \$self = \$_[0]; %s };\n" .
181             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n" .
182             " \$_[0]->{%s};\n" .
183             " } unless \$#_ > 0 or defined \$_[0]->{%s};\n",
184             return_if_get =>
185             " return \$_[0]->{%s} unless \$#_ > 0;\n",
186             set =>
187             " \$_[0]->{%s} = \$_[1];\n",
188             weaken =>
189             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n",
190             sub_end =>
191             " return \$_[0]->{%s};\n}\n",
192             );
193              
194             my $package = caller;
195             my ($args, @values) = do {
196             no warnings;
197             local *boolean_arguments = sub { (qw(-weak)) };
198 87     87 0 167 local *paired_arguments = sub { (qw(-package -init)) };
199 87         108 Spiffy->parse_arguments(@_);
200 5     5   33 };
  5         15  
  5         1753  
201 87     87   387 my ($field, $default) = @values;
  87         170  
202 87     87   297 $package = $args->{-package} if defined $args->{-package};
  87         196  
203 87         258 die "Cannot have a default for a weakened field ($field)"
204             if defined $default && $args->{-weak};
205 87         192 return if defined &{"${package}::$field"};
206 87 50       163 require Scalar::Util if $args->{-weak};
207             my $default_string =
208 87 50 66     214 ( ref($default) eq 'ARRAY' and not @$default )
209 87 50       96 ? '[]'
  87         415  
210 87 50       161 : (ref($default) eq 'HASH' and not keys %$default )
211 87 100 100     368 ? '{}'
    100 66        
212             : default_as_code($default);
213              
214             my $code = $code{sub_start};
215             if ($args->{-init}) {
216             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
217             my @count = ($fragment =~ /(%s)/g);
218 87         140 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
219 87 100       170 }
220 20 50       53 $code .= sprintf $code{set_default}, $field, $default_string, $field
221 20         94 if defined $default;
222 20         105 $code .= sprintf $code{return_if_get}, $field;
223             $code .= sprintf $code{set}, $field;
224 87 100       257 $code .= sprintf $code{weaken}, $field, $field
225             if $args->{-weak};
226 87         237 $code .= sprintf $code{sub_end}, $field;
227 87         151  
228             my $sub = eval $code;
229 87 50       139 die $@ if $@;
230 87         152 no strict 'refs';
231             *{"${package}::$field"} = $sub;
232 87 100 33 5   9352 return $code if defined wantarray;
  5 50 66     21  
  5 100 100     17  
  1 50 66     6  
  0 100       0  
  1 100       7  
  1 0       2  
  3 0       17  
  1 100       3  
  1 50       17  
  1 100       6  
  1 100       4  
  1 50       6  
  3 100       39  
  1 100       2  
  1 100       8  
  2 50       10  
  1 100       4  
  1 100       3  
  4 50       16  
  4 100       18  
  0 50       0  
  0 100       0  
  22 50       61  
  20 100       43  
  20 50       43  
  3 100       14  
  1 100       7  
  0 50       0  
  1         5  
  8         27  
  7         33  
  0         0  
  3         16  
  4         31  
  2         8  
  3         12  
  35         300  
  2         7  
  10         21  
  9         70  
  9         19  
  1         8  
  0         0  
  8         28  
  10         47  
  3         8  
  3         8  
  2         18  
  1         2  
  1         19  
  2         11  
  1         2  
  1         15  
  2         13  
  0         0  
  0         0  
  7         27  
  7         41  
  0         0  
  0         0  
  5         17  
  5         21  
  1         12  
  0         0  
  19         157  
  2         4  
  2         12  
  7         36  
  2         4  
  2         3  
  1         6  
233 87 50       237 }
234 5     5   33  
  5         10  
  5         1152  
235 87         103 require Data::Dumper;
  87         396  
236 87 50       329 local $Data::Dumper::Sortkeys = 1;
237             my $code = Data::Dumper::Dumper(shift);
238             $code =~ s/^\$VAR1 = //;
239             $code =~ s/;$//;
240 73     72 0 3642 return $code;
241 72         31032 }
242 72         188  
243 72         3239 my $package = caller;
244 72         215 my ($args, @values) = do {
245 72         142 no warnings;
246             local *paired_arguments = sub { (qw(-package)) };
247             Spiffy->parse_arguments(@_);
248             };
249 0     0 0 0 my ($field, $default) = @values;
250 0         0 $package = $args->{-package} if defined $args->{-package};
251 5     5   32 no strict 'refs';
  5         10  
  5         507  
252 0     0   0 return if defined &{"${package}::$field"};
  0         0  
253 0         0 *{"${package}::$field"} = sub { $default }
254             }
255 0         0  
256 0 50       0 my $package = caller;
257 5     5   31 my ($args, @values) = do {
  5         8  
  5         584  
258 0 0       0 no warnings;
  0         0  
259 0     0   0 local *paired_arguments = sub { (qw(-package)) };
  0         0  
260 0         0 Spiffy->parse_arguments(@_);
261             };
262             my ($field, $default) = @values;
263 0     0 0 0 $package = $args->{-package} if defined $args->{-package};
264 0         0 no strict 'refs';
265 5     5   37 return if defined &{"${package}::$field"};
  5         9  
  5         487  
266 0     0   0 *{"${package}::$field"} =
  0         0  
267 0         0 sub {
268             require Carp;
269 0         0 Carp::confess
270 0 0       0 "Method $field in package $package must be subclassed";
271 5     5   29 }
  5         9  
  5         2134  
272 0 0       0 }
  0         0  
273 0         0  
274             my $class = shift;
275 0     0   0 my ($args, @values) = ({}, ());
276 0         0 my %booleans = map { ($_, 1) } $class->boolean_arguments;
277             my %pairs = map { ($_, 1) } $class->paired_arguments;
278             while (@_) {
279 0         0 my $elem = shift;
280             if (defined $elem and defined $booleans{$elem}) {
281             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
282 106     106 0 153 ? shift
283 106         217 : 1;
284 106         233 }
  258         580  
285 106         255 elsif (defined $elem and defined $pairs{$elem} and @_) {
  193         336  
286 106         289 $args->{$elem} = shift;
287 161         209 }
288 161 100 66     879 else {
    100 66        
      66        
289 7 50 33     41 push @values, $elem;
290             }
291             }
292             return wantarray ? ($args, @values) : $args;
293             }
294 20         51  
295              
296             # get a unique id for any node
297 134         290 if (not ref $_[0]) {
298             return 'undef' if not defined $_[0];
299             \$_[0] =~ /\((\w+)\)$/o or die;
300 106 50       730 return "$1-S";
301             }
302             require overload;
303 0     0 0 0 overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
304 0     0 0 0 return $1;
305             }
306              
307             #===============================================================================
308 0 0   0 0 0 # It's super, man.
309 0 0       0 #===============================================================================
310 0 0       0 {
311 0         0 no warnings 'redefine';
312             my @dummy = caller(@_ ? $_[0] : 2);
313 0         0 return @DB::args;
314 0 0       0 }
315 0         0 }
316              
317             my $method;
318             my $frame = 1;
319             while ($method = (caller($frame++))[3]) {
320             $method =~ s/.*::// and last;
321             }
322             my @args = DB::super_args($frame);
323 5     5   32 @_ = @_ ? ($args[0], @_) : @args;
  5         8  
  5         1044  
324             my $class = ref $_[0] ? ref $_[0] : $_[0];
325 0 0   0 0 0 my $caller_class = caller;
326 0         0 my $seen = 0;
327             my @super_classes = reverse grep {
328             ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
329             } reverse @{all_my_bases($class)};
330             for my $super_class (@super_classes) {
331             no strict 'refs';
332 0     0 0 0 next if $super_class eq $class;
333 0         0 if (defined &{"${super_class}::$method"}) {
334 0         0 ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
335 0 0       0 if $method eq 'AUTOLOAD';
336             return &{"${super_class}::$method"};
337 0         0 }
338 0 0       0 }
339 0 0       0 return;
340 0         0 }
341 0         0  
342             #===============================================================================
343 0 0 0     0 # This code deserves a spanking, because it is being very naughty.
344 0         0 # It is exchanging base.pm's import() for its own, so that people
  0         0  
345 0         0 # can use base.pm with Spiffy modules, without being the wiser.
346 5     5   32 #===============================================================================
  5         7  
  5         822  
347 0 0       0 my $real_base_import;
348 0 0       0 my $real_mixin_import;
  0         0  
349 0 0       0  
  0         0  
  0         0  
350             BEGIN {
351 0         0 require base unless defined $INC{'base.pm'};
  0         0  
352             $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
353             $real_base_import = \&base::import;
354 0         0 $real_mixin_import = \&mixin::import;
355             no warnings;
356             *base::import = \&spiffy_base_import;
357             *mixin::import = \&spiffy_mixin_import;
358             }
359              
360             # my $i = 0;
361             # while (my $caller = caller($i++)) {
362             # next unless $caller eq 'base' or $caller eq 'mixin';
363             # croak <<END;
364             # Spiffy.pm must be loaded before calling 'use base' or 'use mixin' with a
365             # Spiffy module. See the documentation of Spiffy.pm for details.
366 5 50   5   69 # END
367 5   50     44 # }
368 5         12  
369 5         14 my @base_classes = @_;
370 5     5   33 shift @base_classes;
  5         7  
  5         347  
371 5         46 no strict 'refs';
372 5         241 goto &$real_base_import
373             unless grep {
374             eval "require $_" unless %{"$_\::"};
375             $_->isa('Spiffy');
376             } @base_classes;
377             my $inheritor = caller(0);
378             for my $base_class (@base_classes) {
379             next if $inheritor->isa($base_class);
380             croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
381             "See the documentation of Spiffy.pm for details\n "
382             unless $base_class->isa('Spiffy');
383             $stack_frame = 1; # tell import to use different caller
384             import($base_class, '-base');
385 95     95 0 2609391 $stack_frame = 0;
386 95         188 }
387 5     5   54 }
  5         8  
  5         1365  
388              
389             my $self = shift;
390 95 100       205 my $target_class = ref($self);
  95 50       153  
  95         758  
391 95         21150 spiffy_mixin_import($target_class, @_)
392             }
393 0            
394 0           my $target_class = shift;
395 0 0         $target_class = caller(0)
396 0 0         if $target_class eq 'mixin';
397             my $mixin_class = shift
398             or die "Nothing to mixin";
399 0           eval "require $mixin_class";
400 0           my @roles = @_;
401 0           my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
402             my %methods = spiffy_mixin_methods($mixin_class, @roles);
403             no strict 'refs';
404             no warnings;
405             @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
406 0     0 0   @{"$target_class\::ISA"} = ($pseudo_class);
407 0           for (keys %methods) {
408 0           *{"$pseudo_class\::$_"} = $methods{$_};
409             }
410             }
411              
412 0     0 0   my $mixin_class = shift;
413 0 0         no strict 'refs';
414             my %methods = spiffy_all_methods($mixin_class);
415 0 0         map {
416             $methods{$_}
417 0           ? ($_, \ &{"$methods{$_}\::$_"})
418 0           : ($_, \ &{"$mixin_class\::$_"})
419 0           } @_
420 0           ? (get_roles($mixin_class, @_))
421 5     5   31 : (keys %methods);
  5         10  
  5         156  
422 5     5   23 }
  5         9  
  5         773  
423 0            
  0            
  0            
424 0           my $mixin_class = shift;
  0            
425 0           my @roles = @_;
426 0           while (grep /^!*:/, @roles) {
  0            
427             @roles = map {
428             s/!!//g;
429             /^!:(.*)/ ? do {
430             my $m = "_role_$1";
431 0     0 0   map("!$_", $mixin_class->$m);
432 5     5   30 } :
  5         11  
  5         2229  
433 0           /^:(.*)/ ? do {
434             my $m = "_role_$1";
435 0 0         ($mixin_class->$m);
436 0           } :
437 0 0         ($_)
  0            
438             } @roles;
439             }
440             if (@roles and $roles[0] =~ /^!/) {
441             my %methods = spiffy_all_methods($mixin_class);
442             unshift @roles, keys(%methods);
443             }
444 0     0 0   my %roles;
445 0           for (@roles) {
446 0           s/!!//g;
447             delete $roles{$1}, next
448 0           if /^!(.*)/;
  0            
449             $roles{$_} = 1;
450 0           }
451 0           keys %roles;
452             }
453 0 0          
    0          
454 0           no strict 'refs';
455 0           my $class = shift;
456             return if $class eq 'Spiffy';
457             my %methods = map {
458             ($_, $class)
459             } grep {
460 0 0 0       defined &{"$class\::$_"} and not /^_/
461 0           } keys %{"$class\::"};
462 0           my %super_methods;
463             %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
464 0           if @{"$class\::ISA"};
465 0           %{{%super_methods, %methods}};
466 0           }
467 0 0          
468              
469 0           # END of naughty code.
470             #===============================================================================
471 0           # Debugging support
472             #===============================================================================
473             no warnings;
474             if ($dump eq 'dumper') {
475 5     5   30 require Data::Dumper;
  5         124  
  5         937  
476 0     0 0   $Data::Dumper::Sortkeys = 1;
477 0 0         $Data::Dumper::Indent = 1;
478             return Data::Dumper::Dumper(@_);
479 0           }
480             require YAML;
481 0 0         $YAML::UseVersion = 0;
  0            
482 0           return YAML::Dump(@_) . "...\n";
  0            
483 0           }
484 0            
485 0 0         my ($file_path, $line_number) = (caller(1))[1,2];
  0            
486 0           " at $file_path line $line_number\n";
  0            
487             }
488              
489             warn spiffy_dump(@_) . at_line_number;
490             return wantarray ? @_ : $_[0];
491             }
492              
493             die spiffy_dump(@_) . at_line_number;
494             }
495 5     5   29  
  5         9  
  5         1815  
496 0 0   0 0   print spiffy_dump(@_) . at_line_number;
497 0           return wantarray ? @_ : $_[0];
498 0           }
499 0            
500 0           require Carp;
501             Carp::confess spiffy_dump(@_);
502 0           }
503 0            
504 0           1;