File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 286 440 65.0
branch 105 204 51.4
condition 36 62 58.0
subroutine 38 61 62.3
pod 0 27 0.0
total 465 794 58.5


line stmt bran cond sub pod time code
1             #line 1
2 4     4   20 package Spiffy;
  4         8  
  4         157  
3 4     4   63 use strict;
  4         13  
  4         148  
4 4     4   29 use 5.006001;
  4         6  
  4         232  
5 4     4   39 use warnings;
  4         147  
  4         2163  
6             use Carp;
7             require Exporter;
8             our $VERSION = '0.30';
9             our @EXPORT = ();
10             our @EXPORT_BASE = qw(field const stub super);
11             our @EXPORT_OK = (@EXPORT_BASE, qw(id WWW XXX YYY ZZZ));
12             our %EXPORT_TAGS = (XXX => [qw(WWW XXX YYY ZZZ)]);
13              
14             my $stack_frame = 0;
15             my $dump = 'yaml';
16             my $bases_map = {};
17              
18             sub WWW; sub XXX; sub YYY; sub ZZZ;
19              
20             # This line is here to convince "autouse" into believing we are autousable.
21 671 50 33 671 0 5887 sub can {
22             ($_[1] eq 'import' and caller()->isa('autouse'))
23             ? \&Exporter::import # pacify autouse's equality test
24             : $_[0]->SUPER::can($_[1]) # normal case
25             }
26              
27             # TODO
28             #
29             # Exported functions like field and super should be hidden so as not to
30             # be confused with methods that can be inherited.
31             #
32              
33 369     369 0 511 sub new {
34 369   33     1287 my $class = shift;
35 369         842 $class = ref($class) || $class;
36 369         897 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 369         866 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 4     4   26 sub import {
  4         7  
  4         121  
49 4     4   17 no strict 'refs';
  4         7  
  4         7159  
50 32     32   5015 no warnings;
51             my $self_package = shift;
52              
53             # XXX Using parse_arguments here might cause confusion, because the
54             # subclass's boolean_arguments and paired_arguments can conflict, causing
55 32         61 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 32     32   132 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 32         240 )
63 32     32   134 };
  32         76  
64 32         198 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 32 50       146 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 32 50       98  
70 32 50       89 $filter_dump = 1 if $args->{-filter_dump};
71 32 50       90 $filter_save = 1 if $args->{-filter_save};
72 32 50       87 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 32         196  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 32 50       248  
77 0 0       0 if ($args->{-XXX}) {
  0         0  
78             push @EXPORT_BASE, @{$EXPORT_TAGS{XXX}}
79             unless grep /^XXX$/, @EXPORT_BASE;
80             }
81              
82 32 100 66     375 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 32   33     445  
86 32 100 66     221 my $caller_package = $args->{-package} || caller($stack_frame);
  16         265  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 32         64  
  32         99  
90 48 50       380 for my $class (@{all_my_bases($self_package)}) {
91 459         2173 next unless $class->isa('Spiffy');
92 459         461 my @export = grep {
  48         431  
93 20         68 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 48 100 66     69 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 288         1206 );
98 288         316 my @export_ok = grep {
  48         462  
99 48         70 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 48         92 # if there is nothing to do (optimization)
  651         1454  
104 48 50       220 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 48         68  
  48         201  
107 48         57 my @export_save = @{"$class\::EXPORT"};
  48         168  
108 48         67 my @export_ok_save = @{"$class\::EXPORT_OK"};
  48         263  
109 48         94 @{"$class\::EXPORT"} = @export;
  48         188  
110 20         68 @{"$class\::EXPORT_OK"} = @export_ok;
111 48         108 my @list = grep {
112 20 100       74 (my $v = $_) =~ s/^[\!\:]//;
  12         64  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 48         16161 } @export_list;
115 48         100 Exporter::export($class, $caller_package, @list);
  48         312  
116 48         90 @{"$class\::EXPORT"} = @export_save;
  48         6783  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 16     16 0 5791 sub spiffy_filter {
122 16         6841 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 24 100   24   6022 sub {
126 16         41 return 0 if $done;
127 16         163 my ($data, $end) = ('', '');
128 4513 50       8260 while (my $status = Filter::Util::Call::filter_read()) {
129 4513 100       8603 return $status if $status < 0;
130 8         37 if (/^__(?:END|DATA)__\r?$/) {
131 8         22 $end = $_;
132             last;
133 4505         5749 }
134 4505         14231 $data .= $_;
135             $_ = '';
136 16         144 }
137 16         26 $_ = $data;
138 16         1225 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 16         1040 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 16         718 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 16         40 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 16 50       56 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 16         299 }
150 16 50       71 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 16 50       49 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 16         735 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 16         155 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 44     44 0 72 sub all_my_bases {
164             my $class = shift;
165 44 100       226  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 16         81  
169 4     4   30 my @bases = ($class);
  4         8  
  4         1253  
170 16         28 no strict 'refs';
  16         86  
171 12         24 for my $base_class (@{"${class}::ISA"}) {
  12         43  
172             push @bases, @{all_my_bases($base_class)};
173 16         114 }
174 16         33 my $used = {};
  32         163  
175             $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
176             }
177              
178             my %code = (
179             sub_start =>
180             "sub {\n",
181             set_default =>
182             " \$_[0]->{%s} = %s\n unless exists \$_[0]->{%s};\n",
183             init =>
184             " return \$_[0]->{%s} = do { my \$self = \$_[0]; %s }\n" .
185             " unless \$#_ > 0 or defined \$_[0]->{%s};\n",
186             weak_init =>
187             " return do {\n" .
188             " \$_[0]->{%s} = do { my \$self = \$_[0]; %s };\n" .
189             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n" .
190             " \$_[0]->{%s};\n" .
191             " } unless \$#_ > 0 or defined \$_[0]->{%s};\n",
192             return_if_get =>
193             " return \$_[0]->{%s} unless \$#_ > 0;\n",
194             set =>
195             " \$_[0]->{%s} = \$_[1];\n",
196             weaken =>
197             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n",
198             sub_end =>
199             " return \$_[0]->{%s};\n}\n",
200             );
201              
202 72     72 0 164 sub field {
203 72         103 my $package = caller;
204 4     4   20 my ($args, @values) = do {
  4         6  
  4         3642  
205 72     72   338 no warnings;
  72         154  
206 72     72   246 local *boolean_arguments = sub { (qw(-weak)) };
  72         162  
207 72         325 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 72         169 };
210 72 50       1266 my ($field, $default) = @values;
211 72 50 66     275 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 72 50       87 if defined $default && $args->{-weak};
  72         538  
214 72 50       211 return if defined &{"${package}::$field"};
215 72 100 100     477 require Scalar::Util if $args->{-weak};
    100 66        
216             my $default_string =
217             ( ref($default) eq 'ARRAY' and not @$default )
218             ? '[]'
219             : (ref($default) eq 'HASH' and not keys %$default )
220             ? '{}'
221             : default_as_code($default);
222 72         143  
223 72 100       1723 my $code = $code{sub_start};
224 16 50       46 if ($args->{-init}) {
225 16         86 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 72 100       320 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 72         238 if defined $default;
230 72         167 $code .= sprintf $code{return_if_get}, $field;
231 72 50       183 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 72         161 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 72 100 100 118   9625  
  118 100 33     932  
  61 100 100     578  
  5 50 100     52  
  18 100       55  
  14 50       64  
  1 100       4  
  84 50       222  
  85 100       398  
  0 50       0  
  0 100       0  
  88 50       247  
  88 100       531  
  57 100       208  
  56 50       277  
  2 100       14  
  2 100       16  
  38 100       184  
  11 100       58  
  5 100       61  
  29 100       158  
  6 100       45  
  7 50       26  
  56 100       143  
  52 100       128  
  52 100       153  
  2 100       18  
  2 100       12  
  2 100       18  
  27 100       66  
  82         235  
  82         430  
  2         15  
  1         9  
  84         394  
  28         68  
  52         218  
  113         280  
  113         292  
  192         1089  
  19         113  
  5         11  
  69         297  
  25         70  
  25         95  
  319         4556  
  29         110  
  28         67  
  79         260  
  82         360  
  1         8  
  2         19  
  22         64  
  21         61  
  21         58  
  2         12  
  1         8  
  1         9  
  2         16  
  25         128  
  33         202  
  4         39  
  26         135  
  30         125  
  1         4  
  2         10  
  13         74  
  4         10  
  14         58  
  17         117  
  2         7  
  2         29  
  418         4044  
  181         390  
  181         393  
236 72 50       207 my $sub = eval $code;
237 4     4   33 die $@ if $@;
  4         8  
  4         1239  
238 72         84 no strict 'refs';
  72         492  
239 72 50       378 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 60     60 0 8152 sub default_as_code {
244 60         79909 require Data::Dumper;
245 60         3600 local $Data::Dumper::Sortkeys = 1;
246 60         5448 my $code = Data::Dumper::Dumper(shift);
247 60         195 $code =~ s/^\$VAR1 = //;
248 60         157 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 0 0 sub const {
253 0         0 my $package = caller;
254 4     4   21 my ($args, @values) = do {
  4         8  
  4         461  
255 0     0   0 no warnings;
  0         0  
256 0         0 local *paired_arguments = sub { (qw(-package)) };
257             Spiffy->parse_arguments(@_);
258 0         0 };
259 0 0       0 my ($field, $default) = @values;
260 4     4   20 $package = $args->{-package} if defined $args->{-package};
  4         11  
  4         528  
261 0 0       0 no strict 'refs';
  0         0  
262 0     0   0 return if defined &{"${package}::$field"};
  0         0  
263 0         0 *{"${package}::$field"} = sub { $default }
264             }
265              
266 0     0 0 0 sub stub {
267 0         0 my $package = caller;
268 4     4   22 my ($args, @values) = do {
  4         8  
  4         438  
269 0     0   0 no warnings;
  0         0  
270 0         0 local *paired_arguments = sub { (qw(-package)) };
271             Spiffy->parse_arguments(@_);
272 0         0 };
273 0 0       0 my ($field, $default) = @values;
274 4     4   27 $package = $args->{-package} if defined $args->{-package};
  4         8  
  4         2720  
275 0 0       0 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 104     104 0 167 sub parse_arguments {
286 104         275 my $class = shift;
287 104         363 my ($args, @values) = ({}, ());
  360         2744  
288 104         420 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  176         499  
289 104         317 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 148         252 while (@_) {
291 148 100 66     1276 my $elem = shift;
    100 66        
      66        
292 16 50 33     107 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 16         66 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 116         364 else {
301             push @values, $elem;
302             }
303 104 50       1406 }
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 0       0 if (not ref $_[0]) {
313 0 0       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 4     4   35 {
  4         8  
  4         1748  
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 4     4   27 for my $super_class (@super_classes) {
  4         9  
  4         1033  
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 4 50   4   27 BEGIN {
370 4   50     30 require base unless defined $INC{'base.pm'};
371 4         11 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 4         15 $real_base_import = \&base::import;
373 4     4   24 $real_mixin_import = \&mixin::import;
  4         8  
  4         337  
374 4         56 no warnings;
375 4         220 *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 12     12 0 529 sub spiffy_base_import {
389 12         27 my @base_classes = @_;
390 4     4   24 shift @base_classes;
  4         6  
  4         1569  
391 12         85 no strict 'refs';
392             goto &$real_base_import
393 12 50       32 unless grep {
  12 50       20  
394 12         8108 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 4     4   23 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  4         7  
  4         101  
425 4     4   29 no strict 'refs';
  4         6  
  4         687  
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 4     4   21 my $mixin_class = shift;
  4         7  
  4         2174  
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 4     4   40 sub spiffy_all_methods {
  4         7  
  4         1138  
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 4     4   23 sub spiffy_dump {
  4         6  
  4         1941  
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;
536              
537             __END__