File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 112 204 54.9
condition 39 62 62.9
subroutine 37 60 61.6
pod 8 27 29.6
total 480 791 60.6


line stmt bran cond sub pod time code
1 46     46   557 use strict; use warnings;
  46     46   85  
  46         1165  
  46         204  
  46         65  
  46         1972  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 46     46   219 use Carp;
  46         79  
  46         14029  
6             require Exporter;
7             our @EXPORT = ();
8             our @EXPORT_BASE = qw(field const stub super);
9             our @EXPORT_OK = (@EXPORT_BASE, qw(id WWW XXX YYY ZZZ));
10             our %EXPORT_TAGS = (XXX => [qw(WWW XXX YYY ZZZ)]);
11              
12             my $stack_frame = 0;
13             my $dump = 'yaml';
14             my $bases_map = {};
15              
16             sub WWW; sub XXX; sub YYY; sub ZZZ;
17              
18             # This line is here to convince "autouse" into believing we are autousable.
19             sub can {
20 407 50 33 407 0 2165 ($_[1] eq 'import' and caller()->isa('autouse'))
21             ? \&Exporter::import # pacify autouse's equality test
22             : $_[0]->SUPER::can($_[1]) # normal case
23             }
24              
25             # TODO
26             #
27             # Exported functions like field and super should be hidden so as not to
28             # be confused with methods that can be inherited.
29             #
30              
31             sub new {
32 266     266 0 408 my $class = shift;
33 266   33     808 $class = ref($class) || $class;
34 266         498 my $self = bless {}, $class;
35 266         583 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 266         672 return $self;
40             }
41              
42             my $filtered_files = {};
43             my $filter_dump = 0;
44             my $filter_save = 0;
45             our $filter_result = '';
46             sub import {
47 46     46   368 no strict 'refs';
  46         98  
  46         1722  
48 46     46   242 no warnings;
  46         74  
  46         53153  
49 298     298   818 my $self_package = shift;
50              
51             # XXX Using parse_arguments here might cause confusion, because the
52             # subclass's boolean_arguments and paired_arguments can conflict, causing
53             # difficult debugging. Consider using something truly local.
54 298         411 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 298     298   917 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 298         2102 };
62 298     298   1130 local *paired_arguments = sub { qw(-package) };
  298         529  
63 298         1299 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 298 50       861 if $args->{-mixin};
67              
68 298 50       534 $filter_dump = 1 if $args->{-filter_dump};
69 298 50       505 $filter_save = 1 if $args->{-filter_save};
70 298 50       516 $dump = 'yaml' if $args->{-yaml};
71 298 50       560 $dump = 'dumper' if $args->{-dumper};
72              
73 298         828 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 298 50       556 if ($args->{-XXX}) {
76 0 0       0 push @EXPORT_BASE, @{$EXPORT_TAGS{XXX}}
  0         0  
77             unless grep /^XXX$/, @EXPORT_BASE;
78             }
79              
80             spiffy_filter()
81             if ($args->{-selfless} or $args->{-Base}) and
82 298 100 66     2632 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 298   33     2729 my $caller_package = $args->{-package} || caller($stack_frame);
85 168         1725 push @{"$caller_package\::ISA"}, $self_package
86 298 100 66     986 if $args->{-Base} or $args->{-base};
87              
88 298         413 for my $class (@{all_my_bases($self_package)}) {
  298         583  
89 474 50       2204 next unless $class->isa('Spiffy');
90             my @export = grep {
91 5584         5464 not defined &{"$caller_package\::$_"};
  5584         14820  
92 474         2252 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 474 100 66     560 ? @{"$class\::EXPORT_BASE"} : (),
  252         775  
95             );
96             my @export_ok = grep {
97 2682         2808 not defined &{"$caller_package\::$_"};
  2682         6702  
98 474         692 } @{"$class\::EXPORT_OK"};
  474         1225  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 474         764 my %exportable = map { ($_, 1) } @export, @export_ok;
  7562         10112  
103 474 100       1562 next unless keys %exportable;
104              
105 436         495 my @export_save = @{"$class\::EXPORT"};
  436         2989  
106 436         514 my @export_ok_save = @{"$class\::EXPORT_OK"};
  436         1102  
107 436         2098 @{"$class\::EXPORT"} = @export;
  436         1515  
108 436         2172 @{"$class\::EXPORT_OK"} = @export_ok;
  436         4056  
109             my @list = grep {
110 436         652 (my $v = $_) =~ s/^[\!\:]//;
  318         4229  
111 318 100       2500 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  234         902  
112             } @export_list;
113 436         38807 Exporter::export($class, $caller_package, @list);
114 436         1129 @{"$class\::EXPORT"} = @export_save;
  436         1473  
115 436         605 @{"$class\::EXPORT_OK"} = @export_ok_save;
  436         49769  
116             }
117             }
118              
119             sub spiffy_filter {
120 130     130 0 24361 require Filter::Util::Call;
121 130         39726 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 260 100   260   12338 return 0 if $done;
125 130         1623 my ($data, $end) = ('', '');
126 130         4078 while (my $status = Filter::Util::Call::filter_read()) {
127 49818 50       59767 return $status if $status < 0;
128 49818 50       65022 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 49818         48367 $data .= $_;
133 49818         81683 $_ = '';
134             }
135 130         2996 $_ = $data;
136 130         1585 my @my_subs;
137 130         10822 s[^(sub\s+\w+\s+\{)(.*\n)]
138 130         7720 [${1}my \$self = shift;$2]gm;
139 130         2469 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 130         1736 [${1}${2}]gm;
141 130 50       535 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
142 0         0 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
143 0         0 my $preclare = '';
144             if (@my_subs) {
145 130         2511 $preclare = join ',', map "\$$_", @my_subs;
146 130 50       439 $preclare = "my($preclare);";
  0         0  
  0         0  
147 130 50       270 }
  0         0  
  0         0  
148 130         5516 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 130         832 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
151             $done = 1;
152             }
153             );
154 0     0 0 0 }
155 0         0  
156             sub base {
157             push @_, -base;
158             goto &import;
159 428     428 0 569 }
160              
161             sub all_my_bases {
162 428 100       1329 my $class = shift;
163              
164 176         334 return $bases_map->{$class}
165 46     46   369 if defined $bases_map->{$class};
  46         96  
  46         10564  
166 176         212  
  176         657  
167 130         200 my @bases = ($class);
  130         336  
168             no strict 'refs';
169 176         295 for my $base_class (@{"${class}::ISA"}) {
170 176         354 push @bases, @{all_my_bases($base_class)};
  352         1299  
171             }
172             my $used = {};
173             $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
174             }
175              
176             my %code = (
177             sub_start =>
178             "sub {\n",
179             set_default =>
180             " \$_[0]->{%s} = %s\n unless exists \$_[0]->{%s};\n",
181             init =>
182             " return \$_[0]->{%s} = do { my \$self = \$_[0]; %s }\n" .
183             " unless \$#_ > 0 or defined \$_[0]->{%s};\n",
184             weak_init =>
185             " return do {\n" .
186             " \$_[0]->{%s} = do { my \$self = \$_[0]; %s };\n" .
187             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n" .
188             " \$_[0]->{%s};\n" .
189             " } unless \$#_ > 0 or defined \$_[0]->{%s};\n",
190             return_if_get =>
191             " return \$_[0]->{%s} unless \$#_ > 0;\n",
192             set =>
193             " \$_[0]->{%s} = \$_[1];\n",
194             weaken =>
195             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n",
196             sub_end =>
197             " return \$_[0]->{%s};\n}\n",
198 820     820 1 1521 );
199 820         942  
200 46     46   324 sub field {
  46         103  
  46         16316  
201 820     820   3692 my $package = caller;
  820         1391  
202 820     820   2510 my ($args, @values) = do {
  820         1401  
203 820         2423 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 820         1773 local *paired_arguments = sub { (qw(-package -init)) };
206 820 50       1549 Spiffy->parse_arguments(@_);
207             };
208 820 50 66     1921 my ($field, $default) = @values;
209 820 50       806 $package = $args->{-package} if defined $args->{-package};
  820         3942  
210 820 50       1458 die "Cannot have a default for a weakened field ($field)"
211 820 100 100     3480 if defined $default && $args->{-weak};
    100 66        
212             return if defined &{"${package}::$field"};
213             require Scalar::Util if $args->{-weak};
214             my $default_string =
215             ( ref($default) eq 'ARRAY' and not @$default )
216             ? '[]'
217             : (ref($default) eq 'HASH' and not keys %$default )
218 820         1369 ? '{}'
219 820 100       1617 : default_as_code($default);
220 184 50       489  
221 184         968 my $code = $code{sub_start};
222 184         1095 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 820 100       2448 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 820         2134 }
227 820         1441 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 820 50       1321 $code .= sprintf $code{return_if_get}, $field;
230 820         1464 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 820 100 100 72   89060 if $args->{-weak};
  72 100 100     236  
  58 100 100     144  
  44 100 100     84  
  60 100       313  
  45 100       188  
  30 100       121  
  69 100       228  
  56 100       102  
  64 100       137  
  32 100       91  
  28 100       91  
  28 100       85  
  16 100       40  
  8 100       28  
  14 100       83  
  4 100       12  
  13 100       40  
  124 100       675  
  30 100       94  
  14 100       26  
  32 100       142  
  32 100       104  
  34 100       78  
  27 100       70  
  16 100       67  
  14 100       37  
  13 100       65  
  39 100       170  
  47 100       149  
  46         135  
  93         369  
  39         110  
  38         72  
  6         27  
  10         48  
  22         83  
  16         77  
  9         42  
  18         51  
  32         116  
  25         89  
  27         110  
  30         110  
  18         59  
  33         142  
  61         366  
  25         75  
  21         78  
  13         53  
  29         87  
  44         160  
  25         94  
  16         63  
  11         39  
  16         62  
  11         44  
  13         40  
  14         67  
  9         52  
  107         695  
  49         310  
  24         82  
  45         146  
  42         138  
  37         97  
  17         67  
  4         8  
  79         384  
  21         92  
  12         21  
  25         83  
  46         164  
  23         65  
  23         46  
233 820 50       2244 $code .= sprintf $code{sub_end}, $field;
234 46     46   336  
  46         81  
  46         10919  
235 820         923 my $sub = eval $code;
  820         4046  
236 820 50       3225 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 682     682 0 32203 }
241 682         301055  
242 682         1725 sub default_as_code {
243 682         32990 require Data::Dumper;
244 682         1960 local $Data::Dumper::Sortkeys = 1;
245 682         1468 my $code = Data::Dumper::Dumper(shift);
246             $code =~ s/^\$VAR1 = //;
247             $code =~ s/;$//;
248             return $code;
249 0     0 1 0 }
250 0         0  
251 46     46   345 sub const {
  46         97  
  46         4979  
252 0     0   0 my $package = caller;
  0         0  
253 0         0 my ($args, @values) = do {
254             no warnings;
255 0         0 local *paired_arguments = sub { (qw(-package)) };
256 0 0       0 Spiffy->parse_arguments(@_);
257 46     46   294 };
  46         85  
  46         6227  
258 0 0       0 my ($field, $default) = @values;
  0         0  
259 0     0   0 $package = $args->{-package} if defined $args->{-package};
  0         0  
260 0         0 no strict 'refs';
261             return if defined &{"${package}::$field"};
262             *{"${package}::$field"} = sub { $default }
263 0     0 1 0 }
264 0         0  
265 46     46   624 sub stub {
  46         90  
  46         4888  
266 0     0   0 my $package = caller;
  0         0  
267 0         0 my ($args, @values) = do {
268             no warnings;
269 0         0 local *paired_arguments = sub { (qw(-package)) };
270 0 0       0 Spiffy->parse_arguments(@_);
271 46     46   302 };
  46         94  
  46         22385  
272 0 0       0 my ($field, $default) = @values;
  0         0  
273 0         0 $package = $args->{-package} if defined $args->{-package};
274             no strict 'refs';
275 0     0   0 return if defined &{"${package}::$field"};
276 0         0 *{"${package}::$field"} =
277             sub {
278             require Carp;
279 0         0 Carp::confess
280             "Method $field in package $package must be subclassed";
281             }
282 1118     1118 1 1599 }
283 1118         1963  
284 1118         2246 sub parse_arguments {
  3502         6335  
285 1118         2572 my $class = shift;
  1938         3414  
286 1118         2586 my ($args, @values) = ({}, ());
287 1702         2068 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1702 100 66     14255 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 168 50 33     806 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 184         533 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1350         3035 $args->{$elem} = shift;
298             }
299             else {
300 1118 50       8038 push @values, $elem;
301             }
302             }
303 0     0 1 0 return wantarray ? ($args, @values) : $args;
304 0     0 1 0 }
305              
306             sub boolean_arguments { () }
307             sub paired_arguments { () }
308 0 0   0 0 0  
309 0 0       0 # get a unique id for any node
310 0 0       0 sub id {
311 0         0 if (not ref $_[0]) {
312             return 'undef' if not defined $_[0];
313 0         0 \$_[0] =~ /\((\w+)\)$/o or die;
314 0 0       0 return "$1-S";
315 0         0 }
316             require overload;
317             overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
318             return $1;
319             }
320              
321             #===============================================================================
322             # It's super, man.
323 46     46   355 #===============================================================================
  46         117  
  46         10999  
324             package DB;
325 0 0   0 0 0 {
326 0         0 no warnings 'redefine';
327             sub super_args {
328             my @dummy = caller(@_ ? $_[0] : 2);
329             return @DB::args;
330             }
331             }
332 0     0 1 0  
333 0         0 package Spiffy;
334 0         0 sub super {
335 0 0       0 my $method;
336             my $frame = 1;
337 0         0 while ($method = (caller($frame++))[3]) {
338 0 0       0 $method =~ s/.*::// and last;
339 0 0       0 }
340 0         0 my @args = DB::super_args($frame);
341 0         0 @_ = @_ ? ($args[0], @_) : @args;
342             my $class = ref $_[0] ? ref $_[0] : $_[0];
343 0 0 0     0 my $caller_class = caller;
344 0         0 my $seen = 0;
  0         0  
345 0         0 my @super_classes = reverse grep {
346 46     46   309 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  46         84  
  46         8185  
347 0 0       0 } reverse @{all_my_bases($class)};
348 0 0       0 for my $super_class (@super_classes) {
  0         0  
349 0 0       0 no strict 'refs';
  0         0  
  0         0  
350             next if $super_class eq $class;
351 0         0 if (defined &{"${super_class}::$method"}) {
  0         0  
352             ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
353             if $method eq 'AUTOLOAD';
354 0         0 return &{"${super_class}::$method"};
355             }
356             }
357             return;
358             }
359              
360             #===============================================================================
361             # This code deserves a spanking, because it is being very naughty.
362             # It is exchanging base.pm's import() for its own, so that people
363             # can use base.pm with Spiffy modules, without being the wiser.
364             #===============================================================================
365             my $real_base_import;
366 46 50   46   289 my $real_mixin_import;
367 46   50     344  
368 46         101 BEGIN {
369 46         167 require base unless defined $INC{'base.pm'};
370 46     46   293 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  46         73  
  46         3441  
371 46         395 $real_base_import = \&base::import;
372 46         2559 $real_mixin_import = \&mixin::import;
373             no warnings;
374             *base::import = \&spiffy_base_import;
375             *mixin::import = \&spiffy_mixin_import;
376             }
377              
378             # my $i = 0;
379             # while (my $caller = caller($i++)) {
380             # next unless $caller eq 'base' or $caller eq 'mixin';
381             # croak <
382             # Spiffy.pm must be loaded before calling 'use base' or 'use mixin' with a
383             # Spiffy module. See the documentation of Spiffy.pm for details.
384             # END
385 139     139 0 5380 # }
386 139         266  
387 46     46   299 sub spiffy_base_import {
  46         92  
  46         13724  
388             my @base_classes = @_;
389             shift @base_classes;
390 139 50       367 no strict 'refs';
  139 50       202  
  139         813  
391 139         30854 goto &$real_base_import
392             unless grep {
393 0           eval "require $_" unless %{"$_\::"};
394 0           $_->isa('Spiffy');
395 0 0         } @base_classes;
396 0 0         my $inheritor = caller(0);
397             for my $base_class (@base_classes) {
398             next if $inheritor->isa($base_class);
399 0           croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
400 0           "See the documentation of Spiffy.pm for details\n "
401 0           unless $base_class->isa('Spiffy');
402             $stack_frame = 1; # tell import to use different caller
403             import($base_class, '-base');
404             $stack_frame = 0;
405             }
406 0     0 1   }
407 0            
408 0           sub mixin {
409             my $self = shift;
410             my $target_class = ref($self);
411             spiffy_mixin_import($target_class, @_)
412 0     0 0   }
413 0 0          
414             sub spiffy_mixin_import {
415 0 0         my $target_class = shift;
416             $target_class = caller(0)
417 0           if $target_class eq 'mixin';
418 0           my $mixin_class = shift
419 0           or die "Nothing to mixin";
420 0           eval "require $mixin_class";
421 46     46   328 my @roles = @_;
  46         70  
  46         1489  
422 46     46   229 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  46         88  
  46         6179  
423 0           my %methods = spiffy_mixin_methods($mixin_class, @roles);
  0            
  0            
424 0           no strict 'refs';
  0            
425 0           no warnings;
426 0           @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
  0            
427             @{"$target_class\::ISA"} = ($pseudo_class);
428             for (keys %methods) {
429             *{"$pseudo_class\::$_"} = $methods{$_};
430             }
431 0     0 0   }
432 46     46   306  
  46         78  
  46         20347  
433 0           sub spiffy_mixin_methods {
434             my $mixin_class = shift;
435 0 0         no strict 'refs';
436 0           my %methods = spiffy_all_methods($mixin_class);
437 0 0         map {
  0            
438             $methods{$_}
439             ? ($_, \ &{"$methods{$_}\::$_"})
440             : ($_, \ &{"$mixin_class\::$_"})
441             } @_
442             ? (get_roles($mixin_class, @_))
443             : (keys %methods);
444 0     0 0   }
445 0            
446 0           sub get_roles {
447             my $mixin_class = shift;
448 0           my @roles = @_;
  0            
449             while (grep /^!*:/, @roles) {
450 0           @roles = map {
451 0           s/!!//g;
452             /^!:(.*)/ ? do {
453 0 0         my $m = "_role_$1";
    0          
454 0           map("!$_", $mixin_class->$m);
455 0           } :
456             /^:(.*)/ ? do {
457             my $m = "_role_$1";
458             ($mixin_class->$m);
459             } :
460 0 0 0       ($_)
461 0           } @roles;
462 0           }
463             if (@roles and $roles[0] =~ /^!/) {
464 0           my %methods = spiffy_all_methods($mixin_class);
465 0           unshift @roles, keys(%methods);
466 0           }
467 0 0         my %roles;
468             for (@roles) {
469 0           s/!!//g;
470             delete $roles{$1}, next
471 0           if /^!(.*)/;
472             $roles{$_} = 1;
473             }
474             keys %roles;
475 46     46   315 }
  46         824  
  46         9064  
476 0     0 0    
477 0 0         sub spiffy_all_methods {
478             no strict 'refs';
479 0           my $class = shift;
480             return if $class eq 'Spiffy';
481 0 0         my %methods = map {
  0            
482 0           ($_, $class)
  0            
483 0           } grep {
484 0           defined &{"$class\::$_"} and not /^_/
485 0 0         } keys %{"$class\::"};
  0            
486 0           my %super_methods;
  0            
487             %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
488             if @{"$class\::ISA"};
489             %{{%super_methods, %methods}};
490             }
491              
492              
493             # END of naughty code.
494             #===============================================================================
495 46     46   307 # Debugging support
  46         124  
  46         15665  
496 0 0   0 0   #===============================================================================
497 0           sub spiffy_dump {
498 0           no warnings;
499 0           if ($dump eq 'dumper') {
500 0           require Data::Dumper;
501             $Data::Dumper::Sortkeys = 1;
502 0           $Data::Dumper::Indent = 1;
503 0           return Data::Dumper::Dumper(@_);
504 0           }
505             require YAML;
506             $YAML::UseVersion = 0;
507             return YAML::Dump(@_) . "...\n";
508 0     0 0   }
509 0            
510             sub at_line_number {
511             my ($file_path, $line_number) = (caller(1))[1,2];
512             " at $file_path line $line_number\n";
513 0     0 0   }
514 0 0          
515             sub WWW {
516             warn spiffy_dump(@_) . at_line_number;
517             return wantarray ? @_ : $_[0];
518 0     0 0   }
519              
520             sub XXX {
521             die spiffy_dump(@_) . at_line_number;
522 0     0 0   }
523 0 0          
524             sub YYY {
525             print spiffy_dump(@_) . at_line_number;
526             return wantarray ? @_ : $_[0];
527 0     0 0   }
528 0            
529             sub ZZZ {
530             require Carp;
531             Carp::confess spiffy_dump(@_);
532             }
533              
534             1;