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 0 27 0.0
total 474 794 59.7


line stmt bran cond sub pod time code
1             #line 1
2 15     15   73 package Spiffy;
  15         27  
  15         8636  
3 15     15   277 use strict;
  15         56  
  15         751  
4 15     15   72 use 5.006001;
  15         28  
  15         568  
5 15     15   79 use warnings;
  15         18  
  15         6964  
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 723 50 33 723 0 6633 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 414     414 0 577 sub new {
34 414   33     1472 my $class = shift;
35 414         1048 $class = ref($class) || $class;
36 414         1126 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 414         1041 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 15     15   83 sub import {
  15         27  
  15         428  
49 15     15   95 no strict 'refs';
  15         30  
  15         20163  
50 88     88   216 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 88         147 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 88     88   416 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 88         588 )
63 88     88   348 };
  88         184  
64 88         835 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 88 50       386 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 88 50       270  
70 88 50       240 $filter_dump = 1 if $args->{-filter_dump};
71 88 50       231 $filter_save = 1 if $args->{-filter_save};
72 88 50       234 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 88         345  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 88 50       358  
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 88 100 66     980 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 88   33     1119  
86 88 100 66     517 my $caller_package = $args->{-package} || caller($stack_frame);
  43         620  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 88         187  
  88         268  
90 129 50       1064 for my $class (@{all_my_bases($self_package)}) {
91 1613         6560 next unless $class->isa('Spiffy');
92 1613         1546 my @export = grep {
  129         986  
93 56         183 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 129 100 66     185 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 792         3039 );
98 792         860 my @export_ok = grep {
  129         508  
99 129         206 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 129         235 # if there is nothing to do (optimization)
  2173         3925  
104 129 50       609 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 129         167  
  129         523  
107 129         160 my @export_save = @{"$class\::EXPORT"};
  129         408  
108 129         182 my @export_ok_save = @{"$class\::EXPORT_OK"};
  129         610  
109 129         229 @{"$class\::EXPORT"} = @export;
  129         517  
110 30         153 @{"$class\::EXPORT_OK"} = @export_ok;
111 129         268 my @list = grep {
112 30 50       153 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 129         16347 } @export_list;
115 129         294 Exporter::export($class, $caller_package, @list);
  129         669  
116 129         251 @{"$class\::EXPORT"} = @export_save;
  129         17146  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 43     43 0 17326 sub spiffy_filter {
122 43         18566 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 56 100   56   39613 sub {
126 43         87 return 0 if $done;
127 43         654 my ($data, $end) = ('', '');
128 15664 50       26263 while (my $status = Filter::Util::Call::filter_read()) {
129 15664 100       29283 return $status if $status < 0;
130 30         84 if (/^__(?:END|DATA)__\r?$/) {
131 30         71 $end = $_;
132             last;
133 15634         23748 }
134 15634         51327 $data .= $_;
135             $_ = '';
136 43         246 }
137 43         100 $_ = $data;
138 43         6861 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 43         4519 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 43         1765 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 43         98 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 43 50       170 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 43         929 }
150 43 50       204 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 43 50       131 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 43         2513 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 43         390 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 116     116 0 184 sub all_my_bases {
164             my $class = shift;
165 116 100       542  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 43         109  
169 15     15   93 my @bases = ($class);
  15         42  
  15         4116  
170 43         93 no strict 'refs';
  43         223  
171 28         56 for my $base_class (@{"${class}::ISA"}) {
  28         115  
172             push @bases, @{all_my_bases($base_class)};
173 43         105 }
174 43         89 my $used = {};
  84         672  
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 270     270 0 548 sub field {
203 270         320 my $package = caller;
204 15     15   78 my ($args, @values) = do {
  15         25  
  15         6236  
205 270     270   1025 no warnings;
  270         575  
206 270     270   845 local *boolean_arguments = sub { (qw(-weak)) };
  270         558  
207 270         869 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 270         636 };
210 270 50       772 my ($field, $default) = @values;
211 270 50 66     932 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 270 50       299 if defined $default && $args->{-weak};
  270         1757  
214 270 50       645 return if defined &{"${package}::$field"};
215 270 100 100     1516 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 270         486  
223 270 100       864 my $code = $code{sub_start};
224 60 50       179 if ($args->{-init}) {
225 60         302 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 270 100       984 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 270         778 if defined $default;
230 270         549 $code .= sprintf $code{return_if_get}, $field;
231 270 50       612 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 270         550 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 270 100 100 87   30819  
  87 100 100     464  
  66 100 100     282  
  8 100 100     74  
  31 100       147  
  135 100       639  
  93 100       367  
  61 100       170  
  125 100       1409  
  35 100       161  
  18 100       68  
  81 100       664  
  58 100       282  
  14 100       67  
  29 100       112  
  124 100       453  
  111 100       411  
  62 100       168  
  22 100       146  
  55 100       239  
  29 100       133  
  47 100       164  
  70 100       246  
  70 100       281  
  34 100       115  
  16 100       118  
  34 100       187  
  23 100       123  
  24 100       204  
  137 100       1398  
  26         108  
  32         115  
  29         166  
  76         603  
  42         205  
  32         142  
  30         168  
  38         212  
  47         229  
  24         146  
  57         390  
  13         104  
  21         97  
  27         130  
  137         1289  
  61         187  
  61         281  
  67         232  
  50         362  
  84         646  
  79         807  
  73         244  
  84         310  
  102         323  
  48         325  
  25         148  
  22         90  
  19         86  
  36         228  
  34         176  
  36         364  
  42         183  
  43         218  
  28         156  
  16         102  
  127         1079  
  20         94  
  31         95  
  116         756  
  66         417  
  32         92  
  60         181  
  143         635  
  69         374  
  69         211  
236 270 50       724 my $sub = eval $code;
237 15     15   102 die $@ if $@;
  15         34  
  15         3488  
238 270         306 no strict 'refs';
  270         1314  
239 270 50       1193 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 225     225 0 20642 sub default_as_code {
244 225         200298 require Data::Dumper;
245 225         643 local $Data::Dumper::Sortkeys = 1;
246 225         11955 my $code = Data::Dumper::Dumper(shift);
247 225         636 $code =~ s/^\$VAR1 = //;
248 225         543 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 0 0 sub const {
253 0         0 my $package = caller;
254 15     15   80 my ($args, @values) = do {
  15         26  
  15         1600  
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 15     15   72 $package = $args->{-package} if defined $args->{-package};
  15         27  
  15         2016  
261 0 0       0 no strict 'refs';
  0         0  
262 0     0   0 return if defined &{"${package}::$field"};
  0         0  
263 0         0 *{"${package}::$field"} = sub { $default }
264             }
265              
266 0     0 0 0 sub stub {
267 0         0 my $package = caller;
268 15     15   80 my ($args, @values) = do {
  15         27  
  15         1862  
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 15     15   92 $package = $args->{-package} if defined $args->{-package};
  15         30  
  15         7902  
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 358     358 0 605 sub parse_arguments {
286 358         773 my $class = shift;
287 358         1047 my ($args, @values) = ({}, ());
  1062         2680  
288 358         5401 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  628         2956  
289 358         1100 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 523         703 while (@_) {
291 523 100 66     4226 my $elem = shift;
    100 66        
      66        
292 43 50 33     302 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 60         219 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 420         1175 else {
301             push @values, $elem;
302             }
303 358 50       4065 }
304             return wantarray ? ($args, @values) : $args;
305             }
306 0     0 0 0  
307 0     0 0 0 sub boolean_arguments { () }
308             sub paired_arguments { () }
309              
310             # get a unique id for any node
311 0 0   0 0 0 sub id {
312 0 0       0 if (not ref $_[0]) {
313 0 0       0 return 'undef' if not defined $_[0];
314 0         0 \$_[0] =~ /\((\w+)\)$/o or die;
315             return "$1-S";
316 0         0 }
317 0 0       0 require overload;
318 0         0 overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
319             return $1;
320             }
321              
322             #===============================================================================
323             # It's super, man.
324             #===============================================================================
325             package DB;
326 15     15   85 {
  15         28  
  15         4478  
327             no warnings 'redefine';
328 0 0   0 0 0 sub super_args {
329 0         0 my @dummy = caller(@_ ? $_[0] : 2);
330             return @DB::args;
331             }
332             }
333              
334             package Spiffy;
335 0     0 0 0 sub super {
336 0         0 my $method;
337 0         0 my $frame = 1;
338 0 0       0 while ($method = (caller($frame++))[3]) {
339             $method =~ s/.*::// and last;
340 0         0 }
341 0 0       0 my @args = DB::super_args($frame);
342 0 0       0 @_ = @_ ? ($args[0], @_) : @args;
343 0         0 my $class = ref $_[0] ? ref $_[0] : $_[0];
344 0         0 my $caller_class = caller;
345 0 0 0     0 my $seen = 0;
346 0         0 my @super_classes = reverse grep {
347 0         0 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
348 0         0 } reverse @{all_my_bases($class)};
349 15     15   91 for my $super_class (@super_classes) {
  15         28  
  15         3197  
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 15 50   15   302 BEGIN {
370 15   50     1237 require base unless defined $INC{'base.pm'};
371 15         33 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 15         47 $real_base_import = \&base::import;
373 15     15   78 $real_mixin_import = \&mixin::import;
  15         33  
  15         5633  
374 15         357 no warnings;
375 15         741 *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 285     285 0 2357 sub spiffy_base_import {
389 285         406 my @base_classes = @_;
390 15     15   116 shift @base_classes;
  15         25  
  15         5041  
391 285         1210 no strict 'refs';
392             goto &$real_base_import
393 285 50       630 unless grep {
  285 50       298  
394 285         49459 eval "require $_" unless %{"$_\::"};
395             $_->isa('Spiffy');
396 0           } @base_classes;
397 0           my $inheritor = caller(0);
398 0 0         for my $base_class (@base_classes) {
399 0 0         next if $inheritor->isa($base_class);
400             croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
401             "See the documentation of Spiffy.pm for details\n "
402 0           unless $base_class->isa('Spiffy');
403 0           $stack_frame = 1; # tell import to use different caller
404 0           import($base_class, '-base');
405             $stack_frame = 0;
406             }
407             }
408              
409 0     0 0   sub mixin {
410 0           my $self = shift;
411 0           my $target_class = ref($self);
412             spiffy_mixin_import($target_class, @_)
413             }
414              
415 0     0 0   sub spiffy_mixin_import {
416 0 0         my $target_class = shift;
417             $target_class = caller(0)
418 0 0         if $target_class eq 'mixin';
419             my $mixin_class = shift
420 0           or die "Nothing to mixin";
421 0           eval "require $mixin_class";
422 0           my @roles = @_;
423 0           my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
424 15     15   78 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  15         30  
  15         523  
425 15     15   72 no strict 'refs';
  15         24  
  15         2113  
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 15     15   73 my $mixin_class = shift;
  15         24  
  15         7576  
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 15     15   100 sub spiffy_all_methods {
  15         134  
  15         3118  
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 15     15   89 sub spiffy_dump {
  15         41  
  15         5941  
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__