File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 287 440 65.2
branch 111 204 54.4
condition 38 62 61.2
subroutine 38 61 62.3
pod 8 27 29.6
total 482 794 60.7


line stmt bran cond sub pod time code
1             #line 1
2 14     14   75 package Spiffy;
  14         23  
  14         406  
3 14     14   218 use strict;
  14         43  
  14         504  
4 14     14   116 use 5.006001;
  14         31  
  14         342  
5 14     14   69 use warnings;
  14         34  
  14         5682  
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 1195 50 33 1195 0 10785 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 662     662 0 917 sub new {
34 662   33     2460 my $class = shift;
35 662         1568 $class = ref($class) || $class;
36 662         1745 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 662         1588 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 14     14   90 sub import {
  14         30  
  14         487  
49 14     14   72 no strict 'refs';
  14         23  
  14         24500  
50 70     70   162 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 70         124 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 70     70   296 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 70         393 )
63 70     70   271 };
  70         136  
64 70         425 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 70 50       287 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 70 50       211  
70 70 50       194 $filter_dump = 1 if $args->{-filter_dump};
71 70 50       190 $filter_save = 1 if $args->{-filter_save};
72 70 50       191 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 70         264  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 70 50       314  
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 70 100 66     825 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 70   33     1861  
86 70 100 66     429 my $caller_package = $args->{-package} || caller($stack_frame);
  28         428  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 70         132  
  70         214  
90 84 50       658 for my $class (@{all_my_bases($self_package)}) {
91 826         3957 next unless $class->isa('Spiffy');
92 826         806 my @export = grep {
  84         1707  
93 28         94 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 84 100 66     126 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 630         2730 );
98 630         603 my @export_ok = grep {
  84         297  
99 84         222 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 84         167 # if there is nothing to do (optimization)
  1288         2484  
104 84 50       372 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 84         129  
  84         341  
107 84         113 my @export_save = @{"$class\::EXPORT"};
  84         295  
108 84         161 my @export_ok_save = @{"$class\::EXPORT_OK"};
  84         384  
109 84         145 @{"$class\::EXPORT"} = @export;
  84         336  
110 28         139 @{"$class\::EXPORT_OK"} = @export_ok;
111 84         362 my @list = grep {
112 28 50       265 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 84         11505 } @export_list;
115 84         179 Exporter::export($class, $caller_package, @list);
  84         392  
116 84         151 @{"$class\::EXPORT"} = @export_save;
  84         14531  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 28     28 0 17803 sub spiffy_filter {
122 28         18318 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 28 50   28   97 sub {
126 28         60 return 0 if $done;
127 28         434 my ($data, $end) = ('', '');
128 14098 50       25198 while (my $status = Filter::Util::Call::filter_read()) {
129 14098 100       24578 return $status if $status < 0;
130 28         70 if (/^__(?:END|DATA)__\r?$/) {
131 28         77 $end = $_;
132             last;
133 14070         15662 }
134 14070         42031 $data .= $_;
135             $_ = '';
136 28         108 }
137 28         51 $_ = $data;
138 28         4097 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 28         3021 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 28         1454 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 28         70 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 28 50       113 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 28         745 }
150 28 50       155 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 28 50       470 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 28         1644 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 28         276 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 84     84 0 145 sub all_my_bases {
164             my $class = shift;
165 84 100       448  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 28         85  
169 14     14   92 my @bases = ($class);
  14         36  
  14         4279  
170 28         57 no strict 'refs';
  28         171  
171 14         33 for my $base_class (@{"${class}::ISA"}) {
  14         83  
172             push @bases, @{all_my_bases($base_class)};
173 28         64 }
174 28         63 my $used = {};
  42         261  
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 252     252 1 527 sub field {
203 252         320 my $package = caller;
204 14     14   78 my ($args, @values) = do {
  14         26  
  14         6121  
205 252     252   979 no warnings;
  252         522  
206 252     252   2826 local *boolean_arguments = sub { (qw(-weak)) };
  252         548  
207 252         15654 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 252         603 };
210 252 50       930 my ($field, $default) = @values;
211 252 50 66     1253 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 252 50       301 if defined $default && $args->{-weak};
  252         1599  
214 252 50       636 return if defined &{"${package}::$field"};
215 252 100 100     1710 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 252         691  
223 252 100       690 my $code = $code{sub_start};
224 56 50       180 if ($args->{-init}) {
225 56         307 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 252 100       1003 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 252         750 if defined $default;
230 252         543 $code .= sprintf $code{return_if_get}, $field;
231 252 50       714 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 252         582 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 252 100 100 244   34004  
  244 100 100     2517  
  39 100 100     112  
  37 100 100     122  
  31 100       156  
  106 100       396  
  86 100       405  
  75 100       263  
  28 100       91  
  103 100       1115  
  39 100       184  
  110 100       343  
  98 100       332  
  74 100       225  
  15 100       287  
  27 100       169  
  64 100       312  
  29 100       119  
  222 100       2260  
  72 100       286  
  46 100       455  
  26 100       107  
  25 100       118  
  16 100       77  
  81 100       285  
  84 100       318  
  70 100       198  
  32 100       151  
  36 100       134  
  108 100       1043  
  33         346  
  13         97  
  49         261  
  58         315  
  4         19  
  36         172  
  111         574  
  110         393  
  89         271  
  61         237  
  22         83  
  19         134  
  34         155  
  150         1167  
  88         477  
  52         295  
  126         1569  
  70         388  
  53         211  
  58         341  
  110         349  
  94         271  
  127         425  
  154         1417  
  61         258  
  20         88  
  151         8765  
  35         105  
  40         146  
  69         265  
  61         277  
  54         423  
  53         216  
  81         313  
  48         195  
  142         583  
  101         406  
  66         182  
  58         258  
  144         988  
  75         186  
  100         312  
  268         1654  
  155         423  
  155         341  
236 252 50       835 my $sub = eval $code;
237 14     14   82 die $@ if $@;
  14         22  
  14         3990  
238 252         376 no strict 'refs';
  252         1611  
239 252 50       1368 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 210     210 0 21458 sub default_as_code {
244 210         204051 require Data::Dumper;
245 210         586 local $Data::Dumper::Sortkeys = 1;
246 210         13278 my $code = Data::Dumper::Dumper(shift);
247 210         662 $code =~ s/^\$VAR1 = //;
248 210         545 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 14     14   78 my ($args, @values) = do {
  14         19  
  14         2159  
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 14     14   64 $package = $args->{-package} if defined $args->{-package};
  14         25  
  14         7021  
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 1 0 sub stub {
267 0         0 my $package = caller;
268 14     14   298 my ($args, @values) = do {
  14         44  
  14         5547  
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 14     14   71 $package = $args->{-package} if defined $args->{-package};
  14         22  
  14         10001  
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 322     322 1 500 sub parse_arguments {
286 322         764 my $class = shift;
287 322         955 my ($args, @values) = ({}, ());
  882         4514  
288 322         1042 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  574         1482  
289 322         1142 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 476         625 while (@_) {
291 476 100 66     4119 my $elem = shift;
    100 66        
      66        
292 28 50 33     195 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 56         221 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 392         1163 else {
301             push @values, $elem;
302             }
303 322 50       4410 }
304             return wantarray ? ($args, @values) : $args;
305             }
306 0     0 1 0  
307 0     0 1 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 14     14   98 {
  14         31  
  14         4283  
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 1 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 14     14   87 for my $super_class (@super_classes) {
  14         29  
  14         8166  
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 14 50   14   99 BEGIN {
370 14   50     366 require base unless defined $INC{'base.pm'};
371 14         36 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 14         50 $real_base_import = \&base::import;
373 14     14   334 $real_mixin_import = \&mixin::import;
  14         47  
  14         1221  
374 14         186 no warnings;
375 14         742 *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 129     129 0 40572 sub spiffy_base_import {
389 129         302 my @base_classes = @_;
390 14     14   76 shift @base_classes;
  14         21  
  14         4839  
391 142         4083 no strict 'refs';
392             goto &$real_base_import
393 129 100       341 unless grep {
  142 50       208  
394 142         98054 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 1   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 14     14   84 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  14         27  
  14         370  
425 14     14   64 no strict 'refs';
  14         24  
  14         2131  
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 14     14   78 my $mixin_class = shift;
  14         33  
  14         7648  
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 14     14   88 sub spiffy_all_methods {
  14         147  
  14         3369  
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 14     14   77 sub spiffy_dump {
  14         34  
  14         9586  
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__