File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 111 204 54.4
condition 39 62 62.9
subroutine 37 60 61.6
pod 0 27 0.0
total 471 791 59.5


line stmt bran cond sub pod time code
1 40     40   498 use strict; use warnings;
  40     40   71  
  40         1070  
  40         177  
  40         66  
  40         1941  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   218 use Carp;
  40         64  
  40         12994  
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 338 50 33 338 0 1852 ($_[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 223     223 0 387 my $class = shift;
33 223   33     689 $class = ref($class) || $class;
34 223         410 my $self = bless {}, $class;
35 223         587 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         587 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 40     40   322 no strict 'refs';
  40         62  
  40         1522  
48 40     40   248 no warnings;
  40         109  
  40         49598  
49 256     256   708 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 256         353 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   834 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1866 };
62 256     256   928 local *paired_arguments = sub { qw(-package) };
  256         401  
63 256         1093 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       773 if $args->{-mixin};
67              
68 256 50       537 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       520 $filter_save = 1 if $args->{-filter_save};
70 256 50       445 $dump = 'yaml' if $args->{-yaml};
71 256 50       524 $dump = 'dumper' if $args->{-dumper};
72              
73 256         736 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       523 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 256 100 66     2341 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2583 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1557 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     891 if $args->{-Base} or $args->{-base};
87              
88 256         410 for my $class (@{all_my_bases($self_package)}) {
  256         520  
89 408 50       2071 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         4638 not defined &{"$caller_package\::$_"};
  4776         13259  
92 408         2069 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     528 ? @{"$class\::EXPORT_BASE"} : (),
  216         664  
95             );
96             my @export_ok = grep {
97 2304         2264 not defined &{"$caller_package\::$_"};
  2304         6291  
98 408         2318 } @{"$class\::EXPORT_OK"};
  408         1119  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         2300 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         11285  
103 408 100       1396 next unless keys %exportable;
104              
105 376         498 my @export_save = @{"$class\::EXPORT"};
  376         1261  
106 376         2079 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         1015  
107 376         3683 @{"$class\::EXPORT"} = @export;
  376         1346  
108 376         2231 @{"$class\::EXPORT_OK"} = @export_ok;
  376         4425  
109             my @list = grep {
110 376         591 (my $v = $_) =~ s/^[\!\:]//;
  288         878  
111 288 100       2751 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         855  
112             } @export_list;
113 376         36407 Exporter::export($class, $caller_package, @list);
114 376         979 @{"$class\::EXPORT"} = @export_save;
  376         3143  
115 376         4120 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         39083  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 22293 require Filter::Util::Call;
121 112         35806 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   11199 return 0 if $done;
125 112         2085 my ($data, $end) = ('', '');
126 112         3021 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       53150 return $status if $status < 0;
128 41896 50       55566 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         44175 $data .= $_;
133 41896         76426 $_ = '';
134             }
135 112         1671 $_ = $data;
136 112         240 my @my_subs;
137 112         9237 s[^(sub\s+\w+\s+\{)(.*\n)]
138 112         6792 [${1}my \$self = shift;$2]gm;
139 112         2224 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 112         239 [${1}${2}]gm;
141 112 50       3898 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 112         4050 $preclare = join ',', map "\$$_", @my_subs;
146 112 50       2075 $preclare = "my($preclare);";
  0         0  
  0         0  
147 112 50       3547 }
  0         0  
  0         0  
148 112         6519 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 112         878 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 368     368 0 519 }
160              
161             sub all_my_bases {
162 368 100       1180 my $class = shift;
163              
164 152         301 return $bases_map->{$class}
165 40     40   332 if defined $bases_map->{$class};
  40         80  
  40         9905  
166 152         188  
  152         827  
167 112         184 my @bases = ($class);
  112         369  
168             no strict 'refs';
169 152         275 for my $base_class (@{"${class}::ISA"}) {
170 152         288 push @bases, @{all_my_bases($base_class)};
  304         1236  
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 712     712 0 1358 );
199 712         820  
200 40     40   306 sub field {
  40         105  
  40         16193  
201 712     712   3174 my $package = caller;
  712         1238  
202 712     712   2661 my ($args, @values) = do {
  712         1247  
203 712         2109 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 712         1651 local *paired_arguments = sub { (qw(-package -init)) };
206 712 50       1449 Spiffy->parse_arguments(@_);
207             };
208 712 50 66     1771 my ($field, $default) = @values;
209 712 50       758 $package = $args->{-package} if defined $args->{-package};
  712         3498  
210 712 50       1293 die "Cannot have a default for a weakened field ($field)"
211 712 100 100     3210 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 712         1237 ? '{}'
219 712 100       1417 : default_as_code($default);
220 160 50       475  
221 160         879 my $code = $code{sub_start};
222 160         964 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 712 100       2245 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 712         1980 }
227 712         1363 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712 50       1209 $code .= sprintf $code{return_if_get}, $field;
230 712         1309 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 100 100 41   82844 if $args->{-weak};
  41 100 100     222  
  11 100 100     40  
  5 100 100     10  
  35 100       104  
  36 100       138  
  13 100       32  
  32 100       101  
  56 100       214  
  20 100       38  
  18 100       32  
  51 100       319  
  9 100       26  
  9 100       23  
  28 100       102  
  18 50       79  
  4 100       10  
  59 100       168  
  70 100       193  
  62 100       166  
  92 100       582  
  24 100       90  
  28 100       100  
  32 100       128  
  20 100       71  
  14 100       39  
  10 100       62  
  4 100       20  
  6 100       29  
  65 100       407  
  46         276  
  14         33  
  24         75  
  20         87  
  48         318  
  12         26  
  14         38  
  20         88  
  23         68  
  25         53  
  12         45  
  21         73  
  17         68  
  5         19  
  5         29  
  20         57  
  23         108  
  6         37  
  9         54  
  33         132  
  32         117  
  30         99  
  33         85  
  27         58  
  32         94  
  17         50  
  16         41  
  26         66  
  100         585  
  22         65  
  11         31  
  20         120  
  20         85  
  9         36  
  9         32  
  49         162  
  39         86  
  36         77  
  42         135  
  49         141  
  39         64  
  18         58  
  26         108  
  5         28  
  5         25  
233 712 50       1996 $code .= sprintf $code{sub_end}, $field;
234 40     40   319  
  40         77  
  40         10105  
235 712         857 my $sub = eval $code;
  712         3573  
236 712 50       2866 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 592     592 0 29352 }
241 592         283207  
242 592         1563 sub default_as_code {
243 592         29646 require Data::Dumper;
244 592         1804 local $Data::Dumper::Sortkeys = 1;
245 592         1310 my $code = Data::Dumper::Dumper(shift);
246             $code =~ s/^\$VAR1 = //;
247             $code =~ s/;$//;
248             return $code;
249 0     0 0 0 }
250 0         0  
251 40     40   307 sub const {
  40         73  
  40         4763  
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 40     40   325 };
  40         96  
  40         5594  
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 0 0 }
264 0         0  
265 40     40   319 sub stub {
  40         137  
  40         4427  
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 40     40   325 };
  40         122  
  40         19829  
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 968     968 0 1389 }
283 968         1832  
284 968         2184 sub parse_arguments {
  3016         5837  
285 968         2352 my $class = shift;
  1680         3075  
286 968         2253 my ($args, @values) = ({}, ());
287 1480         1862 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1480 100 66     11949 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 144 50 33     745 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 160         542 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1176         2839 $args->{$elem} = shift;
298             }
299             else {
300 968 50       8153 push @values, $elem;
301             }
302             }
303 0     0 0 0 return wantarray ? ($args, @values) : $args;
304 0     0 0 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 40     40   309 #===============================================================================
  40         75  
  40         10366  
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 0 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 40     40   289 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  40         104  
  40         7961  
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 40 50   40   257 my $real_mixin_import;
367 40   50     293  
368 40         71 BEGIN {
369 40         122 require base unless defined $INC{'base.pm'};
370 40     40   278 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  40         84  
  40         3263  
371 40         426 $real_base_import = \&base::import;
372 40         2384 $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 161     161 0 5556 # }
386 161         316  
387 40     40   280 sub spiffy_base_import {
  40         92  
  40         13174  
388             my @base_classes = @_;
389             shift @base_classes;
390 161 50       380 no strict 'refs';
  161 50       270  
  161         942  
391 161         36225 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 0   }
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 40     40   350 my @roles = @_;
  40         116  
  40         1658  
422 40     40   239 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  40         70  
  40         5885  
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 40     40   298  
  40         103  
  40         18246  
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 40     40   278 }
  40         968  
  40         8032  
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 40     40   279 # Debugging support
  40         80  
  40         15220  
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;