File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 287 440 65.2
branch 107 204 52.4
condition 37 62 59.6
subroutine 38 61 62.3
pod 8 27 29.6
total 477 794 60.0


line stmt bran cond sub pod time code
1             #line 1
2 9     9   45 package Spiffy;
  9         17  
  9         258  
3 9     9   132 use strict;
  9         27  
  9         279  
4 9     9   39 use 5.006001;
  9         15  
  9         259  
5 9     9   58 use warnings;
  9         21  
  9         3428  
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 585 50 33 585 0 5379 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 324     324 0 453 sub new {
34 324   33     1178 my $class = shift;
35 324         788 $class = ref($class) || $class;
36 324         834 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 324         990 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 9     9   45 sub import {
  9         13  
  9         307  
49 9     9   39 no strict 'refs';
  9         13  
  9         12064  
50 45     45   102 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 45         76 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 45     45   178 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 45         256 )
63 45     45   200 };
  45         98  
64 45         346 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 45 50       188 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 45 50       176  
70 45 50       132 $filter_dump = 1 if $args->{-filter_dump};
71 45 50       117 $filter_save = 1 if $args->{-filter_save};
72 45 50       112 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 45         173  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 45 50       145  
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 45 100 66     541 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 45   33     551  
86 45 100 66     269 my $caller_package = $args->{-package} || caller($stack_frame);
  18         253  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 45         84  
  45         175  
90 54 50       427 for my $class (@{all_my_bases($self_package)}) {
91 531         2181 next unless $class->isa('Spiffy');
92 531         500 my @export = grep {
  54         421  
93 18         65 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 54 100 66     92 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 405         1689 );
98 405         436 my @export_ok = grep {
  54         188  
99 54         87 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 54         116 # if there is nothing to do (optimization)
  828         1502  
104 54 50       234 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 54         84  
  54         256  
107 54         84 my @export_save = @{"$class\::EXPORT"};
  54         685  
108 54         100 my @export_ok_save = @{"$class\::EXPORT_OK"};
  54         229  
109 54         94 @{"$class\::EXPORT"} = @export;
  54         196  
110 18         93 @{"$class\::EXPORT_OK"} = @export_ok;
111 54         148 my @list = grep {
112 18 50       97 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 54         6656 } @export_list;
115 54         113 Exporter::export($class, $caller_package, @list);
  54         247  
116 54         95 @{"$class\::EXPORT"} = @export_save;
  54         9104  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 18     18 0 10292 sub spiffy_filter {
122 18         11965 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 18 50   18   60 sub {
126 18         39 return 0 if $done;
127 18         294 my ($data, $end) = ('', '');
128 9063 50       15330 while (my $status = Filter::Util::Call::filter_read()) {
129 9063 100       15962 return $status if $status < 0;
130 18         52 if (/^__(?:END|DATA)__\r?$/) {
131 18         44 $end = $_;
132             last;
133 9045         11926 }
134 9045         26877 $data .= $_;
135             $_ = '';
136 18         79 }
137 18         33 $_ = $data;
138 18         2564 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 18         2312 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 18         1111 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 18         77 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 18 50       76 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 18         451 }
150 18 50       105 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 18 50       58 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 18         1190 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 18         172 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 54     54 0 90 sub all_my_bases {
164             my $class = shift;
165 54 100       326  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 18         45  
169 9     9   59 my @bases = ($class);
  9         13  
  9         2130  
170 18         29 no strict 'refs';
  18         95  
171 9         18 for my $base_class (@{"${class}::ISA"}) {
  9         52  
172             push @bases, @{all_my_bases($base_class)};
173 18         41 }
174 18         39 my $used = {};
  27         167  
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 162     162 1 510 sub field {
203 162         198 my $package = caller;
204 9     9   68 my ($args, @values) = do {
  9         20  
  9         3775  
205 162     162   643 no warnings;
  162         322  
206 162     162   512 local *boolean_arguments = sub { (qw(-weak)) };
  162         454  
207 162         578 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 162         374 };
210 162 50       473 my ($field, $default) = @values;
211 162 50 66     656 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 162 50       181 if defined $default && $args->{-weak};
  162         1083  
214 162 50       401 return if defined &{"${package}::$field"};
215 162 100 100     1012 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 162         328  
223 162 100       508 my $code = $code{sub_start};
224 36 50       141 if ($args->{-init}) {
225 36         191 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 162 100       505 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 162         460 if defined $default;
230 162         321 $code .= sprintf $code{return_if_get}, $field;
231 162 50       374 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 162         356 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 162 100 66 82   20224  
  82 50 100     564  
  49 100 100     276  
  7 100 100     46  
  4 100       19  
  23 100       111  
  14 100       65  
  61 100       785  
  86 100       838  
  27 100       137  
  13 100       92  
  24 100       108  
  26 50       152  
  10 100       43  
  33 100       144  
  27 100       97  
  28 100       145  
  10 100       53  
  55 100       621  
  29 100       154  
  32 100       122  
  28 100       113  
  14 100       69  
  21 100       116  
  37 50       245  
  19 100       73  
  31 100       173  
  117 100       1346  
  45 100       218  
  32 100       187  
  7         42  
  3         15  
  18         110  
  58         663  
  22         84  
  28         97  
  44         118  
  39         129  
  38         183  
  2         9  
  3         26  
  93         928  
  24         70  
  23         86  
  28         126  
  13         51  
  16         69  
  5         34  
  12         55  
  13         55  
  10         37  
  53         163  
  100         694  
  55         126  
  13         89  
  11         63  
  12         45  
  15         107  
  98         988  
  15         77  
  36         228  
  23         129  
  12         118  
  12         50  
  30         121  
  30         196  
  18         100  
  7         26  
  15         49  
  29         206  
  6         16  
  22         123  
  206         573  
  186         463  
  186         424  
236 162 50       474 my $sub = eval $code;
237 9     9   59 die $@ if $@;
  9         18  
  9         2057  
238 162         183 no strict 'refs';
  162         992  
239 162 50       744 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 135     135 0 14844 sub default_as_code {
244 135         133077 require Data::Dumper;
245 135         425 local $Data::Dumper::Sortkeys = 1;
246 135         7722 my $code = Data::Dumper::Dumper(shift);
247 135         403 $code =~ s/^\$VAR1 = //;
248 135         342 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 9     9   52 my ($args, @values) = do {
  9         15  
  9         1062  
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 9     9   48 $package = $args->{-package} if defined $args->{-package};
  9         17  
  9         1133  
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 9     9   45 my ($args, @values) = do {
  9         14  
  9         910  
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 9     9   43 $package = $args->{-package} if defined $args->{-package};
  9         15  
  9         4743  
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 207     207 1 322 sub parse_arguments {
286 207         474 my $class = shift;
287 207         650 my ($args, @values) = ({}, ());
  567         1530  
288 207         677 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  369         946  
289 207         673 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 306         443 while (@_) {
291 306 100 66     2730 my $elem = shift;
    100 66        
      66        
292 18 50 33     153 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 36         137 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 252         742 else {
301             push @values, $elem;
302             }
303 207 50       2400 }
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 9     9   56 {
  9         17  
  9         2408  
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 9     9   52 for my $super_class (@super_classes) {
  9         16  
  9         2057  
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 9 50   9   63 BEGIN {
370 9   50     298 require base unless defined $INC{'base.pm'};
371 9         21 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 9         32 $real_base_import = \&base::import;
373 9     9   49 $real_mixin_import = \&mixin::import;
  9         16  
  9         637  
374 9         126 no warnings;
375 9         460 *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 9     9 0 1094 sub spiffy_base_import {
389 9         26 my @base_classes = @_;
390 9     9   49 shift @base_classes;
  9         14  
  9         3037  
391 9         64 no strict 'refs';
392             goto &$real_base_import
393 9 50       28 unless grep {
  9 50       23  
394 9         916 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 9     9   49 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  9         22  
  9         275  
425 9     9   44 no strict 'refs';
  9         15  
  9         1352  
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 9     9   46 my $mixin_class = shift;
  9         18  
  9         4954  
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 9     9   156 sub spiffy_all_methods {
  9         18  
  9         2016  
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 9     9   66 sub spiffy_dump {
  9         19  
  9         3728  
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__