File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 301 438 68.7
branch 123 204 60.2
condition 38 62 61.2
subroutine 38 61 62.3
pod 0 27 0.0
total 500 792 63.1


line stmt bran cond sub pod time code
1 15     15   61 #line 1
  15     15   18  
  15         491  
  15         60  
  15         23  
  15         714  
2             use strict; use warnings;
3             package Spiffy;
4             our $VERSION = '0.46';
5 15     15   63  
  15         16  
  15         4389  
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 4610 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 625 sub new {
33 414   33     1303 my $class = shift;
34 414         718 $class = ref($class) || $class;
35 414         860 my $self = bless {}, $class;
36 0         0 while (@_) {
37 0         0 my $method = shift;
38             $self->$method(shift);
39 414         698 }
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   71 sub import {
  15         23  
  15         433  
48 15     15   67 no strict 'refs';
  15         17  
  15         15879  
49 88     88   213 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         130 # difficult debugging. Consider using something truly local.
55             my ($args, @export_list) = do {
56 88     88   358 local *boolean_arguments = sub {
57             qw(
58             -base -Base -mixin -selfless
59             -XXX -dumper -yaml
60             -filter_dump -filter_save
61 88         679 )
62 88     88   326 };
  88         161  
63 88         412 local *paired_arguments = sub { qw(-package) };
64             $self_package->parse_arguments(@_);
65 88 50       383 };
66             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
67             if $args->{-mixin};
68 88 50       268  
69 88 50       233 $filter_dump = 1 if $args->{-filter_dump};
70 88 50       238 $filter_save = 1 if $args->{-filter_save};
71 88 50       220 $dump = 'yaml' if $args->{-yaml};
72             $dump = 'dumper' if $args->{-dumper};
73 88         328  
74             local @EXPORT_BASE = @EXPORT_BASE;
75 88 50       274  
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     970 spiffy_filter()
      66        
82             if ($args->{-selfless} or $args->{-Base}) and
83             not $filtered_files->{(caller($stack_frame))[1]}++;
84 88   33     1725  
85 88 100 66     559 my $caller_package = $args->{-package} || caller($stack_frame);
  43         745  
86             push @{"$caller_package\::ISA"}, $self_package
87             if $args->{-Base} or $args->{-base};
88 88         182  
  88         257  
89 129 50       1425 for my $class (@{all_my_bases($self_package)}) {
90 1613         4957 next unless $class->isa('Spiffy');
91 1613         1077 my @export = grep {
  129         1357  
92 56         196 not defined &{"$caller_package\::$_"};
93             } ( @{"$class\::EXPORT"},
94 129 100 66     167 ($args->{-Base} or $args->{-base})
95             ? @{"$class\::EXPORT_BASE"} : (),
96 792         2226 );
97 792         551 my @export_ok = grep {
  129         405  
98 129         168 not defined &{"$caller_package\::$_"};
99             } @{"$class\::EXPORT_OK"};
100              
101             # Avoid calling the expensive Exporter::export
102 129         224 # if there is nothing to do (optimization)
  2173         2812  
103 129 50       493 my %exportable = map { ($_, 1) } @export, @export_ok;
104             next unless keys %exportable;
105 129         150  
  129         610  
106 129         157 my @export_save = @{"$class\::EXPORT"};
  129         357  
107 129         156 my @export_ok_save = @{"$class\::EXPORT_OK"};
  129         481  
108 129         191 @{"$class\::EXPORT"} = @export;
  129         348  
109 30         160 @{"$class\::EXPORT_OK"} = @export_ok;
110 129         385 my @list = grep {
111 30 50       141 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
112             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
113 129         13524 } @export_list;
114 129         245 Exporter::export($class, $caller_package, @list);
  129         592  
115 129         206 @{"$class\::EXPORT"} = @export_save;
  129         13337  
116             @{"$class\::EXPORT_OK"} = @export_ok_save;
117             }
118             }
119              
120 43     43 0 10135 sub spiffy_filter {
121 43         13907 require Filter::Util::Call;
122             my $done = 0;
123             Filter::Util::Call::filter_add(
124 86 100   86   19188 sub {
125 43         98 return 0 if $done;
126 43         400 my ($data, $end) = ('', '');
127 15859 50       19314 while (my $status = Filter::Util::Call::filter_read()) {
128 15859 50       21736 return $status if $status < 0;
129 0         0 if (/^__(?:END|DATA)__\r?$/) {
130 0         0 $end = $_;
131             last;
132 15859         13122 }
133 15859         32954 $data .= $_;
134             $_ = '';
135 43         418 }
136 43         69 $_ = $data;
137 43         3205 my @my_subs;
138             s[^(sub\s+\w+\s+\{)(.*\n)]
139 43         2797 [${1}my \$self = shift;$2]gm;
140             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
141 43         1333 [${1}${2}]gm;
142 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
143 43         91 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
144 43 50       150 my $preclare = '';
145 0         0 if (@my_subs) {
146 0         0 $preclare = join ',', map "\$$_", @my_subs;
147             $preclare = "my($preclare);";
148 43         596 }
149 43 50       180 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
150 43 50       113 if ($filter_dump) { print; exit }
  0         0  
  0         0  
151 43         1361 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
152             $done = 1;
153 43         390 }
154             );
155             }
156              
157 0     0 0 0 sub base {
158 0         0 push @_, -base;
159             goto &import;
160             }
161              
162 116     116 0 229 sub all_my_bases {
163             my $class = shift;
164 116 100       603  
165             return $bases_map->{$class}
166             if defined $bases_map->{$class};
167 43         112  
168 15     15   209 my @bases = ($class);
  15         20  
  15         3442  
169 43         69 no strict 'refs';
  43         248  
170 28         46 for my $base_class (@{"${class}::ISA"}) {
  28         137  
171             push @bases, @{all_my_bases($base_class)};
172 43         110 }
173 43         108 my $used = {};
  84         430  
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 534 sub field {
202 270         276 my $package = caller;
203 15     15   77 my ($args, @values) = do {
  15         19  
  15         5305  
204 270     270   907 no warnings;
  270         422  
205 270     270   700 local *boolean_arguments = sub { (qw(-weak)) };
  270         503  
206 270         801 local *paired_arguments = sub { (qw(-package -init)) };
207             Spiffy->parse_arguments(@_);
208 270         501 };
209 270 50       748 my ($field, $default) = @values;
210 270 50 66     907 $package = $args->{-package} if defined $args->{-package};
211             die "Cannot have a default for a weakened field ($field)"
212 270 50       1068 if defined $default && $args->{-weak};
  270         1458  
213 270 50       592 return if defined &{"${package}::$field"};
214 270 100 100     1406 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         422  
222 270 100       727 my $code = $code{sub_start};
223 60 50       162 if ($args->{-init}) {
224 60         345 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
225 60         373 my @count = ($fragment =~ /(%s)/g);
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
227 270 100       1074 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 270         1342 if defined $default;
230 270         509 $code .= sprintf $code{return_if_get}, $field;
231 270 50       530 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 270         464 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 270 100 100 16   28478  
  16 100 100 111   129  
  8 100 100     37  
  5 100 100     47  
  6 100       22  
  33 100       155  
  26 100       110  
  37 100       318  
  20 100       87  
  44 100       193  
  48 100       155  
  11 100       83  
  16 100       81  
  11 100       69  
  33 100       347  
  37 100       112  
  38 100       114  
  255 100       1530  
  70 100       225  
  83 100       215  
  40 100       176  
  26 100       135  
  29 100       70  
  104 100       553  
  92 100       462  
  22 100       40  
  33 100       112  
  53 100       213  
  45 100       159  
  17 100       81  
  30         129  
  55         204  
  62         249  
  51         195  
  12         86  
  3         16  
  45         97  
  56         258  
  26         43  
  8         35  
  60         356  
  23         99  
  10         27  
  10         44  
  53         174  
  26         66  
  41         119  
  161         838  
  104         338  
  56         171  
  167         910  
  187         386  
  181         369  
  185         370  
  79         349  
  20         85  
  28         132  
  33         126  
  39         170  
  76         227  
  64         145  
  89         185  
  58         201  
  44         234  
  56         103  
  92         418  
  46         180  
  43         78  
  31         88  
  49         277  
  40         123  
  13         78  
  5         70  
  14         40  
  4         32  
236 270 50       684 my $sub = eval $code;
237 15     15   88 die $@ if $@;
  15         21  
  15         3024  
238 270         269 no strict 'refs';
  270         1463  
239 270 50       1163 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 228     225 0 12242 sub default_as_code {
244 229         138706 require Data::Dumper;
245 238         659 local $Data::Dumper::Sortkeys = 1;
246 238         11141 my $code = Data::Dumper::Dumper(shift);
247 237         568 $code =~ s/^\$VAR1 = //;
248 236         569 $code =~ s/;$//;
249             return $code;
250             }
251              
252 3     0 0 22 sub const {
253 2         18 my $package = caller;
254 15     15   78 my ($args, @values) = do {
  15         16  
  15         1723  
255 7     0   26 no warnings;
  6         21  
256 5         12 local *paired_arguments = sub { (qw(-package)) };
257             Spiffy->parse_arguments(@_);
258 3         15 };
259 8 50       28 my ($field, $default) = @values;
260 15     15   80 $package = $args->{-package} if defined $args->{-package};
  15         19  
  15         1613  
261 8 100       52 no strict 'refs';
  1         7  
262 3     0   24 return if defined &{"${package}::$field"};
  15         66  
263 0         0 *{"${package}::$field"} = sub { $default }
264             }
265              
266 2     0 0 11 sub stub {
267 3         24 my $package = caller;
268 15     15   75 my ($args, @values) = do {
  15         22  
  15         1379  
269 14     0   42 no warnings;
  1         3  
270 0         0 local *paired_arguments = sub { (qw(-package)) };
271             Spiffy->parse_arguments(@_);
272 117         721 };
273 13 100       21 my ($field, $default) = @values;
274 15     15   75 $package = $args->{-package} if defined $args->{-package};
  15         20  
  15         6474  
275 13 100       14 no strict 'refs';
  0         0  
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 0         0 }
283             }
284              
285 358     358 0 477 sub parse_arguments {
286 358         631 my $class = shift;
287 358         921 my ($args, @values) = ({}, ());
  1062         2190  
288 358         1029 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  628         1172  
289 358         949 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 523         582 while (@_) {
291 523 100 66     3848 my $elem = shift;
    100 66        
      66        
292 43 100 33     248 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 60         179 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 420         1053 else {
301             push @values, $elem;
302             }
303 358 100       3728 }
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 0   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 0       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   82 {
  15         24  
  15         3631  
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   76 for my $super_class (@super_classes) {
  15         21  
  15         2655  
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   111 BEGIN {
370 15   50     112 require base unless defined $INC{'base.pm'};
371 15         29 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 15         74 $real_base_import = \&base::import;
373 15     15   121 $real_mixin_import = \&mixin::import;
  15         19  
  15         969  
374 15         115 no warnings;
375 15         694 *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 2140 sub spiffy_base_import {
389 285         351 my @base_classes = @_;
390 15     15   89 shift @base_classes;
  15         23  
  15         4270  
391 285         1274 no strict 'refs';
392             goto &$real_base_import
393 285 50       482 unless grep {
  285 50       269  
394 285         26054 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   74 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  15         20  
  15         417  
425 15     15   60 no strict 'refs';
  15         18  
  15         2058  
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   77 my $mixin_class = shift;
  15         19  
  15         6367  
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   93 sub spiffy_all_methods {
  15         23  
  15         2915  
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   86 sub spiffy_dump {
  15         20  
  15         5109  
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;