File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 282 435 64.8
branch 106 200 53.0
condition 38 62 61.2
subroutine 38 61 62.3
pod 8 27 29.6
total 472 785 60.1


line stmt bran cond sub pod time code
1             #line 1
2 16     16   73 package Spiffy;
  16         18  
  16         443  
3 16     16   263 use strict;
  16         43  
4 16     16   74 use 5.006001;
  16         19  
  16         508  
5 16     16   67 use warnings;
  16         18  
  16         5843  
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 638 50 33 638 0 3658 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 353     353 0 322 sub new {
34 353   33     988 my $class = shift;
35 353         721 $class = ref($class) || $class;
36 353         636 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 353         543 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 16     16   96 sub import {
  16         27  
  16         613  
49 16     16   72 no strict 'refs';
  16         66  
  16         18539  
50 78     78   135 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 78         108 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 78     78   250 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 78         551 )
63 78     78   290 };
  78         121  
64 78         325 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66             };
67 78 50       294 return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 78 50       185  
70 78 50       194 $filter_dump = 1 if $args->{-filter_dump};
71 78 50       177 $filter_save = 1 if $args->{-filter_save};
72 78 50       199 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 78         240  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 78 50       221  
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             spiffy_filter()
83 78 100 66     746 if ($args->{-selfless} or $args->{-Base}) and
      66        
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 78   33     807  
86 31         382 my $caller_package = $args->{-package} || caller($stack_frame);
87 78 100 66     402 push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 78         100  
  78         211  
90 94 50       552 for my $class (@{all_my_bases($self_package)}) {
91             next unless $class->isa('Spiffy');
92 876         577 my @export = grep {
  876         2599  
93 94         619 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 94 100 66     102 ($args->{-Base} or $args->{-base})
  31         84  
96             ? @{"$class\::EXPORT_BASE"} : (),
97             );
98 702         489 my @export_ok = grep {
  702         2005  
99 94         108 not defined &{"$caller_package\::$_"};
  94         299  
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 94         139 # if there is nothing to do (optimization)
  1390         1526  
104 94 50       323 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 94         89  
  94         276  
107 94         88 my @export_save = @{"$class\::EXPORT"};
  94         244  
108 94         112 my @export_ok_save = @{"$class\::EXPORT_OK"};
  94         264  
109 94         95 @{"$class\::EXPORT"} = @export;
  94         236  
110             @{"$class\::EXPORT_OK"} = @export_ok;
111 94         139 my @list = grep {
  31         151  
112 31 50       136 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 94         8137 } @export_list;
115 94         124 Exporter::export($class, $caller_package, @list);
  94         299  
116 94         114 @{"$class\::EXPORT"} = @export_save;
  94         12826  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 31     31 0 10130 sub spiffy_filter {
122 31         14859 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 31 50   31   87 sub {
126 31         74 return 0 if $done;
127 31         351 my ($data, $end) = ('', '');
128 15076 50       17733 while (my $status = Filter::Util::Call::filter_read()) {
129 15076 100       19674 return $status if $status < 0;
130 31         65 if (/^__(?:END|DATA)__\r?$/) {
131 31         66 $end = $_;
132             last;
133 15045         11831 }
134 15045         29331 $data .= $_;
135             $_ = '';
136 31         244 }
137 31         55 $_ = $data;
138 31         2886 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 31         2081 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 31         696 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 31         63 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 31 50       114 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 31         520 }
150 31 50       135 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 31 50       104 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 31         1306 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 31         232 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 94     94 0 122 sub all_my_bases {
164             my $class = shift;
165              
166 94 100       347 return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 32         125  
169 16     16   91 my @bases = ($class);
  16         26  
  16         5406  
170 32         37 no strict 'refs';
  32         143  
171 16         54 for my $base_class (@{"${class}::ISA"}) {
  16         71  
172             push @bases, @{all_my_bases($base_class)};
173 32         47 }
174 32         64 my $used = {};
  48         233  
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 271     271 1 440 sub field {
203 271         267 my $package = caller;
204 16     16   91 my ($args, @values) = do {
  16         17  
  16         5899  
205 271     271   1257 no warnings;
  271         408  
206 271     271   876 local *boolean_arguments = sub { (qw(-weak)) };
  271         408  
207 271         741 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 271         457 };
210 271 50       716 my ($field, $default) = @values;
211             $package = $args->{-package} if defined $args->{-package};
212 271 50 66     780 die "Cannot have a default for a weakened field ($field)"
213 271 50       215 if defined $default && $args->{-weak};
  271         1490  
214 271 50       518 return if defined &{"${package}::$field"};
215 271 100 100     1529 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 271         424  
223 271 100       659 my $code = $code{sub_start};
224 64 50       157 if ($args->{-init}) {
225 64         5058 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 271 100       856 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 271         730 if defined $default;
230 271         469 $code .= sprintf $code{return_if_get}, $field;
231             $code .= sprintf $code{set}, $field;
232 271 50       565 $code .= sprintf $code{weaken}, $field, $field
233 271         437 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 271 100 100 149   28994  
  149 100 100     976  
  29 100 100     90  
  27 100 100     148  
  37 100       121  
  28 100       110  
  8 100       45  
  30 100       120  
  33 100       136  
  24 100       186  
  10 100       53  
  92 100       719  
  10 100       40  
  6 100       31  
  75 100       190  
  82 100       206  
  72 100       133  
  40 100       251  
  39 100       154  
  34 100       143  
  62 100       179  
  70 100       175  
  62 100       145  
  30 100       250  
  24 100       78  
  29 100       209  
  7 100       33  
  68 100       358  
  40         103  
  44         107  
  21         113  
  47         319  
  18         44  
  17         70  
  32         106  
  26         94  
  48         370  
  66         153  
  87         355  
  63         152  
  28         86  
  24         97  
  35         113  
  22         95  
  14         104  
  30         77  
  30         106  
  48         114  
  52         440  
  14         52  
  44         131  
  56         193  
  21         93  
  11         25  
  127         751  
  87         244  
  57         217  
  21         96  
  22         90  
  101         855  
  27         101  
  44         178  
  18         75  
  14         62  
  66         402  
  46         186  
  6         12  
  9         34  
  120         312  
  88         128  
  88         113  
236 271 50       649 my $sub = eval $code;
237 16     16   90 die $@ if $@;
  16         23  
  16         3432  
238 271         252 no strict 'refs';
  271         1288  
239 271 50       1056 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 223     223 0 12803 sub default_as_code {
244 223         128948 require Data::Dumper;
245 223         576 local $Data::Dumper::Sortkeys = 1;
246 223         10983 my $code = Data::Dumper::Dumper(shift);
247 223         498 $code =~ s/^\$VAR1 = //;
248 223         484 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 16     16   84 my ($args, @values) = do {
  16         21  
  16         1509  
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 16     16   88 $package = $args->{-package} if defined $args->{-package};
  16         25  
  16         2258  
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 16     16   81 my ($args, @values) = do {
  16         20  
  16         1417  
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 16     16   66 $package = $args->{-package} if defined $args->{-package};
  16         19  
  16         7272  
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 349     349 1 409 sub parse_arguments {
286 349         600 my $class = shift;
287 349         660 my ($args, @values) = ({}, ());
  973         2063  
288 349         739 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  620         1144  
289 349         859 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 509         522 while (@_) {
291 509 100 66     3469 my $elem = shift;
    100 66        
      66        
292 31 50 33     213 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 64         201 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 414         912 else {
301             push @values, $elem;
302             }
303 349 50       2939 }
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 16     16   88 {
  16         21  
  16         3939  
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             my $seen = 0;
346 0 0 0     0 my @super_classes = reverse grep {
347 0         0 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  0         0  
348 0         0 } reverse @{all_my_bases($class)};
349 16     16   84 for my $super_class (@super_classes) {
  16         26  
  16         3068  
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 16 50   16   97 BEGIN {
370 16   50     108 require base unless defined $INC{'base.pm'};
371 16         66 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 16         58 $real_base_import = \&base::import;
373 16     16   80 $real_mixin_import = \&mixin::import;
  16         20  
  16         984  
374 16         105 no warnings;
375 16         754 *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 82     82 0 801299 sub spiffy_base_import {
389 82         150 my @base_classes = @_;
390 16     16   86 shift @base_classes;
  16         47  
  16         5014  
391             no strict 'refs';
392             goto &$real_base_import
393 82 50       172 unless grep {
  82 50       122  
  82         427  
394 82         45864 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 16     16   83 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  16         23  
  16         514  
425 16     16   70 no strict 'refs';
  16         21  
  16         2033  
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 16     16   76 my $mixin_class = shift;
  16         19  
  16         6768  
436 0           no strict 'refs';
437             my %methods = spiffy_all_methods($mixin_class);
438 0 0         map {
439 0           $methods{$_}
440 0 0         ? ($_, \ &{"$methods{$_}\::$_"})
  0            
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             while (grep /^!*:/, @roles) {
451 0           @roles = map {
  0            
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 16     16   78 sub spiffy_all_methods {
  16         20  
  16         3011  
479 0     0 0   no strict 'refs';
480 0 0         my $class = shift;
481             return if $class eq 'Spiffy';
482 0           my %methods = map {
483             ($_, $class)
484 0 0         } grep {
  0            
485 0           defined &{"$class\::$_"} and not /^_/
  0            
486 0           } keys %{"$class\::"};
487 0           my %super_methods;
488 0 0         %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
  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 16     16   102 sub spiffy_dump {
  16         21  
  16         5138  
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__