File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 305 438 69.6
branch 127 204 62.2
condition 39 62 62.9
subroutine 38 61 62.3
pod 0 27 0.0
total 509 792 64.2


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