File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 112 206 54.3
condition 39 62 62.9
subroutine 37 60 61.6
pod 8 27 29.6
total 480 793 60.5


line stmt bran cond sub pod time code
1 46     46   255 use strict; use warnings;
  46     46   70  
  46         1551  
  46         381  
  46         177  
  46         3549  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 46     46   297 use Carp;
  46         97  
  46         17756  
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 2189 ($_[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 371 my $class = shift;
33 266   33     791 $class = ref($class) || $class;
34 266         470 my $self = bless {}, $class;
35 266         540 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 266         737 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   373 no strict 'refs';
  46         78  
  46         2117  
48 46     46   239 no warnings;
  46         84  
  46         78045  
49 298     298   977 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         463 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 298     298   1049 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 298         2549 };
62 298     298   1142 local *paired_arguments = sub { qw(-package) };
  298         561  
63 298         1499 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 298 50       1107 if $args->{-mixin};
67              
68 298 50       659 $filter_dump = 1 if $args->{-filter_dump};
69 298 50       604 $filter_save = 1 if $args->{-filter_save};
70 298 50       663 $dump = 'yaml' if $args->{-yaml};
71 298 50       653 $dump = 'dumper' if $args->{-dumper};
72              
73 298         1056 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 298 50       754 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     2834 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 298   33     3305 my $caller_package = $args->{-package} || caller($stack_frame);
85 168         2092 push @{"$caller_package\::ISA"}, $self_package
86 298 100 66     1111 if $args->{-Base} or $args->{-base};
87              
88 298         448 for my $class (@{all_my_bases($self_package)}) {
  298         712  
89 474 50       2741 next unless $class->isa('Spiffy');
90             my @export = grep {
91 5584         5998 not defined &{"$caller_package\::$_"};
  5584         17626  
92 474         2597 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 474 100 66     661 ? @{"$class\::EXPORT_BASE"} : (),
  252         786  
95             );
96             my @export_ok = grep {
97 2682         2764 not defined &{"$caller_package\::$_"};
  2682         7231  
98 474         650 } @{"$class\::EXPORT_OK"};
  474         1461  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 474         817 my %exportable = map { ($_, 1) } @export, @export_ok;
  7562         13438  
103 474 100       1651 next unless keys %exportable;
104              
105 436         563 my @export_save = @{"$class\::EXPORT"};
  436         1736  
106 436         577 my @export_ok_save = @{"$class\::EXPORT_OK"};
  436         1296  
107 436         628 @{"$class\::EXPORT"} = @export;
  436         2269  
108 436         624 @{"$class\::EXPORT_OK"} = @export_ok;
  436         1328  
109             my @list = grep {
110 436         781 (my $v = $_) =~ s/^[\!\:]//;
  318         1138  
111 318 100       955 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  234         1082  
112             } @export_list;
113 436         47101 Exporter::export($class, $caller_package, @list);
114 436         1161 @{"$class\::EXPORT"} = @export_save;
  436         1964  
115 436         608 @{"$class\::EXPORT_OK"} = @export_ok_save;
  436         55274  
116             }
117             }
118              
119             sub spiffy_filter {
120 130     130 0 28399 require Filter::Util::Call;
121 130         44786 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 260 100   260   11929 return 0 if $done;
125 130         347 my ($data, $end) = ('', '');
126 130         1412 while (my $status = Filter::Util::Call::filter_read()) {
127 49818 50       72824 return $status if $status < 0;
128 49818 50       75940 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 49818         59571 $data .= $_;
133 49818         101743 $_ = '';
134             }
135 130         1711 $_ = $data;
136 130         306 my @my_subs;
137 130         13678 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 130         10767 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 130         2948 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;
  0         0  
143 130         731 my $preclare = '';
144 130 50       650 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 130         569 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 130 50       458 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 130 50       321 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 130         7857 $done = 1;
152             }
153 130         1623 );
154             }
155              
156             sub base {
157 0     0 0 0 push @_, -base;
158 0         0 goto &import;
159             }
160              
161             sub all_my_bases {
162 428     428 0 644 my $class = shift;
163              
164             return $bases_map->{$class}
165 428 100       1630 if defined $bases_map->{$class};
166              
167 176         432 my @bases = ($class);
168 46     46   420 no strict 'refs';
  46         98  
  46         10295  
169 176         266 for my $base_class (@{"${class}::ISA"}) {
  176         895  
170 130         235 push @bases, @{all_my_bases($base_class)};
  130         447  
171             }
172 176         311 my $used = {};
173 176         532 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  352         1706  
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             );
199              
200             sub field {
201 820     820 1 1867 my $package = caller;
202 820         1053 my ($args, @values) = do {
203 46     46   276 no warnings;
  46         67  
  46         17098  
204 820     820   4464 local *boolean_arguments = sub { (qw(-weak)) };
  820         1709  
205 820     820   2893 local *paired_arguments = sub { (qw(-package -init)) };
  820         1536  
206 820         2762 Spiffy->parse_arguments(@_);
207             };
208 820         2100 my ($field, $default) = @values;
209 820 50       1865 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 820 50 66     2256 if defined $default && $args->{-weak};
212 820 50       962 return if defined &{"${package}::$field"};
  820         5107  
213 820 50       1755 require Scalar::Util if $args->{-weak};
214 820 100 100     5060 my $default_string =
    100 66        
215             ( ref($default) eq 'ARRAY' and not @$default )
216             ? '[]'
217             : (ref($default) eq 'HASH' and not keys %$default )
218             ? '{}'
219             : default_as_code($default);
220              
221 820         1764 my $code = $code{sub_start};
222 820 100       1951 if ($args->{-init}) {
223 184 50       533 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 184         1122 my @count = ($fragment =~ /(%s)/g);
225 184         942 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 820 100       2451 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 820         2065 $code .= sprintf $code{return_if_get}, $field;
230 820         1730 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 820 50       1551 if $args->{-weak};
233 820         1664 $code .= sprintf $code{sub_end}, $field;
234              
235 820 100 100 36   115118 my $sub = eval $code;
  36 100 100     263  
  13 100 100     24  
  13 100 100     47  
  15 100       50  
  19 100       73  
  14 100       54  
  19 100       52  
  50 100       159  
  37 100       122  
  28 100       62  
  32 100       113  
  28 100       125  
  5 100       14  
  16 100       66  
  50 100       199  
  28 100       104  
  16 100       49  
  30 100       89  
  64 100       375  
  16 100       59  
  36 100       102  
  44 100       205  
  7 100       18  
  14 100       42  
  40 100       149  
  21 100       112  
  4 100       19  
  45 100       96  
  42 100       131  
  36         70  
  7         30  
  75         362  
  12         44  
  7         11  
  35         137  
  29         99  
  7         29  
  19         63  
  37         101  
  32         59  
  43         70  
  48         150  
  25         105  
  23         46  
  98         588  
  49         164  
  29         78  
  16         120  
  38         178  
  23         67  
  23         76  
  9         40  
  8         42  
  32         100  
  37         162  
  89         330  
  19         71  
  22         67  
  21         53  
  27         70  
  23         49  
  127         366  
  98         200  
  88         124  
  24         94  
  64         442  
  7         18  
  18         60  
  87         478  
  14         30  
  21         71  
  33         133  
  13         37  
  13         32  
236 820 50       2509 die $@ if $@;
237 46     46   335 no strict 'refs';
  46         119  
  46         13888  
238 820         1027 *{"${package}::$field"} = $sub;
  820         5585  
239 820 50       3730 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 682     682 0 37038 require Data::Dumper;
244 682         464771 local $Data::Dumper::Sortkeys = 1;
245 682         2043 my $code = Data::Dumper::Dumper(shift);
246 682         40968 $code =~ s/^\$VAR1 = //;
247 682         2631 $code =~ s/;$//;
248 682         1681 return $code;
249             }
250              
251             sub const {
252 0     0 1 0 my $package = caller;
253 0         0 my ($args, @values) = do {
254 46     46   321 no warnings;
  46         99  
  46         5645  
255 0     0   0 local *paired_arguments = sub { (qw(-package)) };
  0         0  
256 0         0 Spiffy->parse_arguments(@_);
257             };
258 0         0 my ($field, $default) = @values;
259 0 0       0 $package = $args->{-package} if defined $args->{-package};
260 46     46   253 no strict 'refs';
  46         95  
  46         7674  
261 0 0       0 return if defined &{"${package}::$field"};
  0         0  
262 0     0   0 *{"${package}::$field"} = sub { $default }
  0         0  
263 0         0 }
264              
265             sub stub {
266 0     0 1 0 my $package = caller;
267 0         0 my ($args, @values) = do {
268 46     46   271 no warnings;
  46         72  
  46         5976  
269 0     0   0 local *paired_arguments = sub { (qw(-package)) };
  0         0  
270 0         0 Spiffy->parse_arguments(@_);
271             };
272 0         0 my ($field, $default) = @values;
273 0 0       0 $package = $args->{-package} if defined $args->{-package};
274 46     46   254 no strict 'refs';
  46         84  
  46         36989  
275 0 0       0 return if defined &{"${package}::$field"};
  0         0  
276 0         0 *{"${package}::$field"} =
277             sub {
278 0     0   0 require Carp;
279 0         0 Carp::confess
280             "Method $field in package $package must be subclassed";
281             }
282 0         0 }
283              
284             sub parse_arguments {
285 1118     1118 1 1725 my $class = shift;
286 1118         2315 my ($args, @values) = ({}, ());
287 1118         3697 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3502         7723  
288 1118         3596 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1938         4213  
289 1118         3184 while (@_) {
290 1702         2469 my $elem = shift;
291 1702 100 66     11889 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 168 50 33     958 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 184         711 $args->{$elem} = shift;
298             }
299             else {
300 1350         3652 push @values, $elem;
301             }
302             }
303 1118 50       10436 return wantarray ? ($args, @values) : $args;
304             }
305              
306 0     0 1 0 sub boolean_arguments { () }
307 0     0 1 0 sub paired_arguments { () }
308              
309             # get a unique id for any node
310             sub id {
311 0 0   0 0 0 if (not ref $_[0]) {
312 0 0       0 return 'undef' if not defined $_[0];
313 0 0       0 \$_[0] =~ /\((\w+)\)$/o or die;
314 0         0 return "$1-S";
315             }
316 0         0 require overload;
317 0 0       0 overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
318 0         0 return $1;
319             }
320              
321             #===============================================================================
322             # It's super, man.
323             #===============================================================================
324             package DB;
325             {
326 46     46   331 no warnings 'redefine';
  46         68  
  46         10805  
327             sub super_args {
328 0 0   0 0 0 my @dummy = caller(@_ ? $_[0] : 2);
329 0         0 return @DB::args;
330             }
331             }
332              
333             package Spiffy;
334             sub super {
335 0     0 1 0 my $method;
336 0         0 my $frame = 1;
337 0         0 while ($method = (caller($frame++))[3]) {
338 0 0       0 $method =~ s/.*::// and last;
339             }
340 0         0 my @args = DB::super_args($frame);
341 0 0       0 @_ = @_ ? ($args[0], @_) : @args;
342 0 0       0 my $class = ref $_[0] ? ref $_[0] : $_[0];
343 0         0 my $caller_class = caller;
344 0         0 my $seen = 0;
345             my @super_classes = reverse grep {
346 0 0 0     0 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
347 0         0 } reverse @{all_my_bases($class)};
  0         0  
348 0         0 for my $super_class (@super_classes) {
349 46     46   273 no strict 'refs';
  46         98  
  46         9718  
350 0 0       0 next if $super_class eq $class;
351 0 0       0 if (defined &{"${super_class}::$method"}) {
  0         0  
352 0 0       0 ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
  0         0  
  0         0  
353             if $method eq 'AUTOLOAD';
354 0         0 return &{"${super_class}::$method"};
  0         0  
355             }
356             }
357 0         0 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             my $real_mixin_import;
367              
368             BEGIN {
369 46 50   46   280 require base unless defined $INC{'base.pm'};
370 46   50     316 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 46         91 $real_base_import = \&base::import;
372 46         124 $real_mixin_import = \&mixin::import;
373 46     46   329 no warnings;
  46         85  
  46         3002  
374 46         384 *base::import = \&spiffy_base_import;
375 46         2461 *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             # }
386              
387             sub spiffy_base_import {
388 48     48 0 231158 my @base_classes = @_;
389 48         164 shift @base_classes;
390 46     46   307 no strict 'refs';
  46         103  
  46         16456  
391             goto &$real_base_import
392             unless grep {
393 48 50       150 eval "require $_" unless %{"$_\::"};
  48 50       1288  
  48         982  
394 48         12017 $_->isa('Spiffy');
395             } @base_classes;
396 0           my $inheritor = caller(0);
397 0           for my $base_class (@base_classes) {
398 0 0         next if $inheritor->isa($base_class);
399 0 0         croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
400             "See the documentation of Spiffy.pm for details\n "
401             unless $base_class->isa('Spiffy');
402 0           $stack_frame = 1; # tell import to use different caller
403 0           import($base_class, '-base');
404 0           $stack_frame = 0;
405             }
406             }
407              
408             sub mixin {
409 0     0 1   my $self = shift;
410 0           my $target_class = ref($self);
411 0           spiffy_mixin_import($target_class, @_)
412             }
413              
414             sub spiffy_mixin_import {
415 0     0 0   my $target_class = shift;
416 0 0         $target_class = caller(0)
417             if $target_class eq 'mixin';
418 0 0         my $mixin_class = shift
419             or die "Nothing to mixin";
420 0           eval "require $mixin_class";
421 0           my @roles = @_;
422 0           my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
423 0           my %methods = spiffy_mixin_methods($mixin_class, @roles);
424 46     46   325 no strict 'refs';
  46         98  
  46         1868  
425 46     46   210 no warnings;
  46         78  
  46         7980  
426 0           @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
  0            
  0            
427 0           @{"$target_class\::ISA"} = ($pseudo_class);
  0            
428 0           for (keys %methods) {
429 0           *{"$pseudo_class\::$_"} = $methods{$_};
  0            
430             }
431             }
432              
433             sub spiffy_mixin_methods {
434 0     0 0   my $mixin_class = shift;
435 46     46   357 no strict 'refs';
  46         99  
  46         25904  
436 0           my %methods = spiffy_all_methods($mixin_class);
437             map {
438 0 0         $methods{$_}
439 0           ? ($_, \ &{"$methods{$_}\::$_"})
440 0 0         : ($_, \ &{"$mixin_class\::$_"})
  0            
441             } @_
442             ? (get_roles($mixin_class, @_))
443             : (keys %methods);
444             }
445              
446             sub get_roles {
447 0     0 0   my $mixin_class = shift;
448 0           my @roles = @_;
449 0           while (grep /^!*:/, @roles) {
450             @roles = map {
451 0           s/!!//g;
  0            
452             /^!:(.*)/ ? do {
453 0           my $m = "_role_$1";
454 0           map("!$_", $mixin_class->$m);
455             } :
456 0 0         /^:(.*)/ ? do {
    0          
    0          
457 0           my $m = "_role_$1";
458 0           ($mixin_class->$m);
459             } :
460             ($_)
461             } @roles;
462             }
463 0 0 0       if (@roles and $roles[0] =~ /^!/) {
464 0           my %methods = spiffy_all_methods($mixin_class);
465 0           unshift @roles, keys(%methods);
466             }
467 0           my %roles;
468 0           for (@roles) {
469 0           s/!!//g;
470 0 0         delete $roles{$1}, next
471             if /^!(.*)/;
472 0           $roles{$_} = 1;
473             }
474 0           keys %roles;
475             }
476              
477             sub spiffy_all_methods {
478 46     46   381 no strict 'refs';
  46         108  
  46         11004  
479 0     0 0   my $class = shift;
480 0 0         return if $class eq 'Spiffy';
481             my %methods = map {
482 0           ($_, $class)
483             } grep {
484 0 0         defined &{"$class\::$_"} and not /^_/
  0            
485 0           } keys %{"$class\::"};
  0            
486 0           my %super_methods;
487 0           %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
488 0 0         if @{"$class\::ISA"};
  0            
489 0           %{{%super_methods, %methods}};
  0            
490             }
491              
492              
493             # END of naughty code.
494             #===============================================================================
495             # Debugging support
496             #===============================================================================
497             sub spiffy_dump {
498 46     46   328 no warnings;
  46         73  
  46         18073  
499 0 0   0 0   if ($dump eq 'dumper') {
500 0           require Data::Dumper;
501 0           $Data::Dumper::Sortkeys = 1;
502 0           $Data::Dumper::Indent = 1;
503 0           return Data::Dumper::Dumper(@_);
504             }
505 0           require YAML;
506 0           $YAML::UseVersion = 0;
507 0           return YAML::Dump(@_) . "...\n";
508             }
509              
510             sub at_line_number {
511 0     0 0   my ($file_path, $line_number) = (caller(1))[1,2];
512 0           " at $file_path line $line_number\n";
513             }
514              
515             sub WWW {
516 0     0 0   warn spiffy_dump(@_) . at_line_number;
517 0 0         return wantarray ? @_ : $_[0];
518             }
519              
520             sub XXX {
521 0     0 0   die spiffy_dump(@_) . at_line_number;
522             }
523              
524             sub YYY {
525 0     0 0   print spiffy_dump(@_) . at_line_number;
526 0 0         return wantarray ? @_ : $_[0];
527             }
528              
529             sub ZZZ {
530 0     0 0   require Carp;
531 0           Carp::confess spiffy_dump(@_);
532             }
533              
534             1;