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 12     12   66 package Spiffy;
  12         25  
  12         424  
3 12     12   182 use strict;
  12         40  
  12         437  
4 12     12   63 use 5.006001;
  12         19  
  12         321  
5 12     12   61 use warnings;
  12         19  
  12         4703  
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 1452 50 33 1452 0 13792 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 776     776 0 4628 sub new {
34 776   33     2815 my $class = shift;
35 776         1885 $class = ref($class) || $class;
36 776         1979 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 776         1940 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 12     12   65 sub import {
  12         22  
  12         381  
49 12     12   64 no strict 'refs';
  12         26  
  12         15948  
50 60     60   132 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 60         102 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 60     60   229 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 60         343 )
63 60     60   240 };
  60         126  
64 60         318 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 60 50       249 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 60 50       175  
70 60 50       183 $filter_dump = 1 if $args->{-filter_dump};
71 60 50       161 $filter_save = 1 if $args->{-filter_save};
72 60 50       161 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 60         239  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 60 50       213  
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 60 100 66     669 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 60   33     906  
86 60 100 66     406 my $caller_package = $args->{-package} || caller($stack_frame);
  24         334  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 60         118  
  60         187  
90 72 50       659 for my $class (@{all_my_bases($self_package)}) {
91 708         3119 next unless $class->isa('Spiffy');
92 708         668 my @export = grep {
  72         573  
93 24         83 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 72 100 66     195 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 540         2229 );
98 540         539 my @export_ok = grep {
  72         254  
99 72         109 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 72         147 # if there is nothing to do (optimization)
  1104         1948  
104 72 50       312 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 72         101  
  72         289  
107 72         95 my @export_save = @{"$class\::EXPORT"};
  72         260  
108 72         107 my @export_ok_save = @{"$class\::EXPORT_OK"};
  72         336  
109 72         130 @{"$class\::EXPORT"} = @export;
  72         285  
110 24         128 @{"$class\::EXPORT_OK"} = @export_ok;
111 72         185 my @list = grep {
112 24 50       127 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 72         8989 } @export_list;
115 72         148 Exporter::export($class, $caller_package, @list);
  72         379  
116 72         147 @{"$class\::EXPORT"} = @export_save;
  72         14406  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 24     24 0 13677 sub spiffy_filter {
122 24         19283 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 24 50   24   84 sub {
126 24         52 return 0 if $done;
127 24         442 my ($data, $end) = ('', '');
128 12084 50       24097 while (my $status = Filter::Util::Call::filter_read()) {
129 12084 100       22819 return $status if $status < 0;
130 24         61 if (/^__(?:END|DATA)__\r?$/) {
131 24         85 $end = $_;
132             last;
133 12060         16177 }
134 12060         35996 $data .= $_;
135             $_ = '';
136 24         96 }
137 24         39 $_ = $data;
138 24         3721 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 24         2664 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 24         1386 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 24         63 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 24 50       101 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 24         655 }
150 24 50       146 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 24 50       77 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 24         1439 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 24         250 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 72     72 0 135 sub all_my_bases {
164             my $class = shift;
165 72 100       351  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 24         60  
169 12     12   123 my @bases = ($class);
  12         21  
  12         2919  
170 24         41 no strict 'refs';
  24         138  
171 12         28 for my $base_class (@{"${class}::ISA"}) {
  12         70  
172             push @bases, @{all_my_bases($base_class)};
173 24         63 }
174 24         66 my $used = {};
  36         222  
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 216     216 1 1735 sub field {
203 216         273 my $package = caller;
204 12     12   64 my ($args, @values) = do {
  12         20  
  12         4631  
205 216     216   823 no warnings;
  216         409  
206 216     216   2214 local *boolean_arguments = sub { (qw(-weak)) };
  216         455  
207 216         799 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 216         457 };
210 216 50       616 my ($field, $default) = @values;
211 216 50 66     2726 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 216 50       290 if defined $default && $args->{-weak};
  216         1397  
214 216 50       518 return if defined &{"${package}::$field"};
215 216 100 100     1323 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 216         394  
223 216 100       616 my $code = $code{sub_start};
224 48 50       153 if ($args->{-init}) {
225 48         248 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 216 100       708 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 216         670 if defined $default;
230 216         442 $code .= sprintf $code{return_if_get}, $field;
231 216 50       717 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 216         444 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 216 100 100 73   26512  
  73 100 100     377  
  34 100 100     150  
  17 100 100     79  
  151 100       1555  
  62 100       250  
  42 100       207  
  5 100       29  
  6 100       35  
  38 100       172  
  146 100       1467  
  130 100       1026  
  57 100       249  
  606 100       16138  
  108 100       900  
  72 100       276  
  327 100       935  
  305 100       909  
  442 100       1833  
  66 100       307  
  46 100       148  
  33 100       145  
  13 100       43  
  74 100       458  
  43 100       168  
  106 100       335  
  202 100       685  
  196 100       873  
  5 100       26  
  83 100       486  
  95         314  
  78         211  
  43         202  
  54         253  
  31         114  
  26         142  
  79         948  
  9         24  
  36         129  
  53         330  
  58         244  
  43         197  
  21         156  
  37         180  
  15         82  
  14         82  
  136         405  
  153         738  
  125         1216  
  40         152  
  33         144  
  31         124  
  40         246  
  13         55  
  132         636  
  192         1010  
  149         1075  
  77         193  
  102         865  
  111         472  
  61         183  
  58         236  
  44         240  
  9         27  
  19         112  
  37         214  
  18         91  
  3         40  
  29         82  
  69         196  
  59         140  
  72         291  
  180         667  
  138         341  
  138         292  
236 216 50       589 my $sub = eval $code;
237 12     12   65 die $@ if $@;
  12         24  
  12         2481  
238 216         249 no strict 'refs';
  216         1139  
239 216 50       1021 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 180     180 0 16463 sub default_as_code {
244 180         129139 require Data::Dumper;
245 180         609 local $Data::Dumper::Sortkeys = 1;
246 180         9803 my $code = Data::Dumper::Dumper(shift);
247 180         510 $code =~ s/^\$VAR1 = //;
248 180         489 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 12     12   61 my ($args, @values) = do {
  12         19  
  12         1266  
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 12     12   59 $package = $args->{-package} if defined $args->{-package};
  12         25  
  12         1545  
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 12     12   65 my ($args, @values) = do {
  12         36  
  12         1190  
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 12     12   61 $package = $args->{-package} if defined $args->{-package};
  12         58  
  12         5938  
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 276     276 1 419 sub parse_arguments {
286 276         587 my $class = shift;
287 276         747 my ($args, @values) = ({}, ());
  756         2018  
288 276         870 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  492         2567  
289 276         804 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 408         538 while (@_) {
291 408 100 66     3330 my $elem = shift;
    100 66        
      66        
292 24 50 33     168 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 48         176 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 336         993 else {
301             push @values, $elem;
302             }
303 276 50       3074 }
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 12     12   70 {
  12         20  
  12         3140  
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 12     12   62 for my $super_class (@super_classes) {
  12         22  
  12         2376  
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 12 50   12   84 BEGIN {
370 12   50     220 require base unless defined $INC{'base.pm'};
371 12         30 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 12         47 $real_base_import = \&base::import;
373 12     12   74 $real_mixin_import = \&mixin::import;
  12         21  
  12         738  
374 12         104 no warnings;
375 12         639 *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 101     101 0 113112 sub spiffy_base_import {
389 101         185 my @base_classes = @_;
390 12     12   60 shift @base_classes;
  12         29  
  12         4166  
391 101         2035 no strict 'refs';
392             goto &$real_base_import
393 101 100       240 unless grep {
  101 50       149  
394 101         67059 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 12     12   63 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  12         22  
  12         341  
425 12     12   58 no strict 'refs';
  12         22  
  12         1704  
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 12     12   57 my $mixin_class = shift;
  12         23  
  12         11915  
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 12     12   73 sub spiffy_all_methods {
  12         27  
  12         2649  
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 12     12   67 sub spiffy_dump {
  12         25  
  12         4726  
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__