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   622 use strict; use warnings;
  46     46   96  
  46         1221  
  46         226  
  46         79  
  46         2073  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 46     46   248 use Carp;
  46         102  
  46         14884  
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 2248 ($_[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 521 my $class = shift;
33 266   33     820 $class = ref($class) || $class;
34 266         483 my $self = bless {}, $class;
35 266         613 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 266         749 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   377 no strict 'refs';
  46         88  
  46         1731  
48 46     46   255 no warnings;
  46         74  
  46         56014  
49 298     298   867 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         401 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 298     298   1026 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 298         2321 };
62 298     298   1184 local *paired_arguments = sub { qw(-package) };
  298         506  
63 298         1436 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 298 50       978 if $args->{-mixin};
67              
68 298 50       624 $filter_dump = 1 if $args->{-filter_dump};
69 298 50       540 $filter_save = 1 if $args->{-filter_save};
70 298 50       507 $dump = 'yaml' if $args->{-yaml};
71 298 50       643 $dump = 'dumper' if $args->{-dumper};
72              
73 298         894 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 298 50       661 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     2824 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 298   33     3109 my $caller_package = $args->{-package} || caller($stack_frame);
85 168         1812 push @{"$caller_package\::ISA"}, $self_package
86 298 100 66     1068 if $args->{-Base} or $args->{-base};
87              
88 298         455 for my $class (@{all_my_bases($self_package)}) {
  298         617  
89 474 50       2423 next unless $class->isa('Spiffy');
90             my @export = grep {
91 5584         7566 not defined &{"$caller_package\::$_"};
  5584         15889  
92 474         2377 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 474 100 66     622 ? @{"$class\::EXPORT_BASE"} : (),
  252         787  
95             );
96             my @export_ok = grep {
97 2682         2713 not defined &{"$caller_package\::$_"};
  2682         10560  
98 474         689 } @{"$class\::EXPORT_OK"};
  474         3119  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 474         2483 my %exportable = map { ($_, 1) } @export, @export_ok;
  7562         10633  
103 474 100       1544 next unless keys %exportable;
104              
105 436         555 my @export_save = @{"$class\::EXPORT"};
  436         2998  
106 436         544 my @export_ok_save = @{"$class\::EXPORT_OK"};
  436         4516  
107 436         558 @{"$class\::EXPORT"} = @export;
  436         3738  
108 436         634 @{"$class\::EXPORT_OK"} = @export_ok;
  436         2770  
109             my @list = grep {
110 436         4109 (my $v = $_) =~ s/^[\!\:]//;
  318         2965  
111 318 100       3466 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  234         1014  
112             } @export_list;
113 436         45695 Exporter::export($class, $caller_package, @list);
114 436         1144 @{"$class\::EXPORT"} = @export_save;
  436         3374  
115 436         2493 @{"$class\::EXPORT_OK"} = @export_ok_save;
  436         46431  
116             }
117             }
118              
119             sub spiffy_filter {
120 130     130 0 25981 require Filter::Util::Call;
121 130         42149 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 260 100   260   8843 return 0 if $done;
125 130         3640 my ($data, $end) = ('', '');
126 130         3207 while (my $status = Filter::Util::Call::filter_read()) {
127 49818 50       62494 return $status if $status < 0;
128 49818 50       66963 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 49818         51421 $data .= $_;
133 49818         88576 $_ = '';
134             }
135 130         1656 $_ = $data;
136 130         251 my @my_subs;
137 130         10956 s[^(sub\s+\w+\s+\{)(.*\n)]
138 130         8302 [${1}my \$self = shift;$2]gm;
139 130         2608 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 130         304 [${1}${2}]gm;
141 130 50       514 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         4310 $preclare = join ',', map "\$$_", @my_subs;
146 130 50       4021 $preclare = "my($preclare);";
  0         0  
  0         0  
147 130 50       5256 }
  0         0  
  0         0  
148 130         7259 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 130         953 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 632 }
160              
161             sub all_my_bases {
162 428 100       1375 my $class = shift;
163              
164 176         348 return $bases_map->{$class}
165 46     46   408 if defined $bases_map->{$class};
  46         108  
  46         11721  
166 176         240  
  176         923  
167 130         285 my @bases = ($class);
  130         448  
168             no strict 'refs';
169 176         369 for my $base_class (@{"${class}::ISA"}) {
170 176         334 push @bases, @{all_my_bases($base_class)};
  352         1464  
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 1548 );
199 820         946  
200 46     46   356 sub field {
  46         105  
  46         18298  
201 820     820   3746 my $package = caller;
  820         1420  
202 820     820   2882 my ($args, @values) = do {
  820         1427  
203 820         2542 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 820         1855 local *paired_arguments = sub { (qw(-package -init)) };
206 820 50       1661 Spiffy->parse_arguments(@_);
207             };
208 820 50 66     2035 my ($field, $default) = @values;
209 820 50       858 $package = $args->{-package} if defined $args->{-package};
  820         4280  
210 820 50       1551 die "Cannot have a default for a weakened field ($field)"
211 820 100 100     3671 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         1401 ? '{}'
219 820 100       1653 : default_as_code($default);
220 184 50       544  
221 184         1387 my $code = $code{sub_start};
222 184         1146 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 820 100       2673 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 820         2304 }
227 820         1500 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 820 50       1424 $code .= sprintf $code{return_if_get}, $field;
230 820         1481 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 820 100 100 116   96776 if $args->{-weak};
  116 100 100     685  
  36 100 100     125  
  16 100 100     33  
  15 100       72  
  37 100       120  
  36 100       113  
  66 100       183  
  56 100       155  
  46 100       103  
  14 100       59  
  43 100       150  
  40 100       141  
  56 100       177  
  59 100       205  
  50 100       226  
  10 100       30  
  31 100       105  
  64 100       311  
  38 100       135  
  23 100       53  
  12 100       57  
  11 100       61  
  8 100       29  
  59 100       407  
  35 100       124  
  26 100       93  
  24 100       115  
  19 100       73  
  56 100       157  
  64         163  
  75         212  
  26         79  
  23         82  
  29         86  
  21         73  
  10         46  
  20         63  
  32         138  
  20         91  
  19         71  
  24         112  
  14         52  
  22         104  
  12         52  
  13         62  
  24         84  
  7         22  
  11         67  
  12         54  
  10         30  
  9         35  
  16         88  
  8         28  
  33         225  
  14         70  
  45         300  
  28         449  
  38         99  
  52         197  
  20         113  
  3         7  
  24         89  
  23         81  
  10         22  
  50         322  
  181         1220  
  42         83  
  53         107  
  49         233  
  26         48  
  7         34  
  50         326  
  8         40  
  8         20  
233 820 50       2286 $code .= sprintf $code{sub_end}, $field;
234 46     46   337  
  46         85  
  46         11288  
235 820         1056 my $sub = eval $code;
  820         4258  
236 820 50       3399 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 682     682 0 34170 }
241 682         321392  
242 682         1834 sub default_as_code {
243 682         34702 require Data::Dumper;
244 682         2064 local $Data::Dumper::Sortkeys = 1;
245 682         1462 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   324 sub const {
  46         67  
  46         5375  
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   335 };
  46         148  
  46         6169  
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   342 sub stub {
  46         106  
  46         5146  
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   305 };
  46         119  
  46         23377  
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 1824 }
283 1118         2163  
284 1118         2391 sub parse_arguments {
  3502         7205  
285 1118         2787 my $class = shift;
  1938         3548  
286 1118         2619 my ($args, @values) = ({}, ());
287 1702         2121 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1702 100 66     13014 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 168 50 33     813 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 184         588 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1350         6813 $args->{$elem} = shift;
298             }
299             else {
300 1118 50       8654 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   398 #===============================================================================
  46         123  
  46         12205  
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   312 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  46         89  
  46         8946  
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   310 my $real_mixin_import;
367 46   50     361  
368 46         97 BEGIN {
369 46         138 require base unless defined $INC{'base.pm'};
370 46     46   294 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  46         75  
  46         3604  
371 46         533 $real_base_import = \&base::import;
372 46         2675 $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 6373 # }
386 139         285  
387 46     46   333 sub spiffy_base_import {
  46         103  
  46         15005  
388             my @base_classes = @_;
389             shift @base_classes;
390 139 50       345 no strict 'refs';
  139 50       240  
  139         1010  
391 139         33781 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   310 my @roles = @_;
  46         98  
  46         1882  
422 46     46   247 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  46         72  
  46         6501  
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   332  
  46         114  
  46         20587  
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   398 }
  46         929  
  46         8954  
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   318 # Debugging support
  46         89  
  46         17003  
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;