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 0 27 0.0
total 472 793 59.5


line stmt bran cond sub pod time code
1 40     40   253 use strict; use warnings;
  40     40   57  
  40         1532  
  40         209  
  40         89  
  40         3124  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   261 use Carp;
  40         100  
  40         15350  
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 2468 ($_[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 363 my $class = shift;
33 223   33     784 $class = ref($class) || $class;
34 223         493 my $self = bless {}, $class;
35 223         583 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         673 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   320 no strict 'refs';
  40         149  
  40         1804  
48 40     40   213 no warnings;
  40         53  
  40         68813  
49 256     256   890 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         383 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   916 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         2587 };
62 256     256   1050 local *paired_arguments = sub { qw(-package) };
  256         546  
63 256         1202 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       919 if $args->{-mixin};
67              
68 256 50       631 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       558 $filter_save = 1 if $args->{-filter_save};
70 256 50       554 $dump = 'yaml' if $args->{-yaml};
71 256 50       534 $dump = 'dumper' if $args->{-dumper};
72              
73 256         895 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       579 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     2576 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2892 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         2114 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     909 if $args->{-Base} or $args->{-base};
87              
88 256         444 for my $class (@{all_my_bases($self_package)}) {
  256         695  
89 408 50       2331 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         5009 not defined &{"$caller_package\::$_"};
  4776         14770  
92 408         2336 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     539 ? @{"$class\::EXPORT_BASE"} : (),
  216         734  
95             );
96             my @export_ok = grep {
97 2304         2364 not defined &{"$caller_package\::$_"};
  2304         6335  
98 408         590 } @{"$class\::EXPORT_OK"};
  408         1265  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         793 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         12823  
103 408 100       1478 next unless keys %exportable;
104              
105 376         585 my @export_save = @{"$class\::EXPORT"};
  376         1709  
106 376         512 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         1142  
107 376         531 @{"$class\::EXPORT"} = @export;
  376         1731  
108 376         521 @{"$class\::EXPORT_OK"} = @export_ok;
  376         1085  
109             my @list = grep {
110 376         749 (my $v = $_) =~ s/^[\!\:]//;
  288         975  
111 288 100       850 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         941  
112             } @export_list;
113 376         39591 Exporter::export($class, $caller_package, @list);
114 376         1076 @{"$class\::EXPORT"} = @export_save;
  376         1597  
115 376         552 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         42754  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 31099 require Filter::Util::Call;
121 112         43493 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   10148 return 0 if $done;
125 112         809 my ($data, $end) = ('', '');
126 112         1561 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       56281 return $status if $status < 0;
128 41896 50       62281 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         60103 $data .= $_;
133 41896         82865 $_ = '';
134             }
135 112         1672 $_ = $data;
136 112         229 my @my_subs;
137 112         10622 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         9073 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2605 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 112         866 my $preclare = '';
144 112 50       580 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         427 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       384 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       285 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         6636 $done = 1;
152             }
153 112         2513 );
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 368     368 0 581 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1346 if defined $bases_map->{$class};
166              
167 152         410 my @bases = ($class);
168 40     40   346 no strict 'refs';
  40         73  
  40         9650  
169 152         232 for my $base_class (@{"${class}::ISA"}) {
  152         702  
170 112         1124 push @bases, @{all_my_bases($base_class)};
  112         414  
171             }
172 152         288 my $used = {};
173 152         365 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1446  
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 712     712 0 1690 my $package = caller;
202 712         974 my ($args, @values) = do {
203 40     40   279 no warnings;
  40         61  
  40         15710  
204 712     712   4021 local *boolean_arguments = sub { (qw(-weak)) };
  712         1484  
205 712     712   2571 local *paired_arguments = sub { (qw(-package -init)) };
  712         1325  
206 712         2476 Spiffy->parse_arguments(@_);
207             };
208 712         1846 my ($field, $default) = @values;
209 712 50       1667 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     2089 if defined $default && $args->{-weak};
212 712 50       816 return if defined &{"${package}::$field"};
  712         4477  
213 712 50       1459 require Scalar::Util if $args->{-weak};
214 712 100 100     3725 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 712         1622 my $code = $code{sub_start};
222 712 100       1723 if ($args->{-init}) {
223 160 50       450 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         1002 my @count = ($fragment =~ /(%s)/g);
225 160         882 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       2253 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1769 $code .= sprintf $code{return_if_get}, $field;
230 712         1429 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       1367 if $args->{-weak};
233 712         1425 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 56   103452 my $sub = eval $code;
  56 100 100     971  
  50 100 100     167  
  43 100 100     82  
  7 100       43  
  15 100       76  
  6 100       18  
  1 100       1  
  21 100       81  
  9 100       27  
  8 100       25  
  15 100       62  
  5 100       13  
  11 100       46  
  44 100       210  
  27 100       99  
  26 100       77  
  31 100       88  
  48 100       160  
  21 100       127  
  3 100       23  
  15 100       78  
  34 100       109  
  30 100       97  
  48 100       172  
  15 100       62  
  5 100       11  
  6 100       28  
  29 100       104  
  40 100       154  
  15         88  
  9         53  
  20         75  
  18         58  
  23         48  
  25         109  
  19         92  
  12         49  
  17         80  
  15         54  
  10         64  
  74         538  
  39         76  
  52         115  
  50         143  
  14         55  
  11         52  
  11         73  
  69         539  
  29         106  
  18         59  
  24         65  
  23         134  
  10         45  
  21         104  
  78         356  
  12         39  
  16         50  
  26         99  
  22         81  
  53         969  
  32         132  
  15         74  
  6         23  
  14         53  
  12         52  
  83         578  
  9         17  
  19         54  
  69         530  
  7         15  
  7         14  
  84         278  
  60         148  
  60         169  
236 712 50       2307 die $@ if $@;
237 40     40   340 no strict 'refs';
  40         94  
  40         12027  
238 712         919 *{"${package}::$field"} = $sub;
  712         5039  
239 712 50       3288 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 34435 require Data::Dumper;
244 592         409564 local $Data::Dumper::Sortkeys = 1;
245 592         1928 my $code = Data::Dumper::Dumper(shift);
246 592         35939 $code =~ s/^\$VAR1 = //;
247 592         2420 $code =~ s/;$//;
248 592         1507 return $code;
249             }
250              
251             sub const {
252 0     0 0 0 my $package = caller;
253 0         0 my ($args, @values) = do {
254 40     40   299 no warnings;
  40         64  
  40         4835  
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 40     40   264 no strict 'refs';
  40         58  
  40         6835  
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 0 0 my $package = caller;
267 0         0 my ($args, @values) = do {
268 40     40   279 no warnings;
  40         100  
  40         5109  
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 40     40   228 no strict 'refs';
  40         97  
  40         29218  
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 968     968 0 1514 my $class = shift;
286 968         2057 my ($args, @values) = ({}, ());
287 968         2397 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         6479  
288 968         2704 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         3636  
289 968         2822 while (@_) {
290 1480         2234 my $elem = shift;
291 1480 100 66     9685 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     797 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         592 $args->{$elem} = shift;
298             }
299             else {
300 1176         4030 push @values, $elem;
301             }
302             }
303 968 50       10052 return wantarray ? ($args, @values) : $args;
304             }
305              
306 0     0 0 0 sub boolean_arguments { () }
307 0     0 0 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 40     40   324 no warnings 'redefine';
  40         60  
  40         9234  
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 0 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 40     40   252 no strict 'refs';
  40         54  
  40         8487  
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 40 50   40   244 require base unless defined $INC{'base.pm'};
370 40   50     329 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         78 $real_base_import = \&base::import;
372 40         126 $real_mixin_import = \&mixin::import;
373 40     40   302 no warnings;
  40         87  
  40         2551  
374 40         358 *base::import = \&spiffy_base_import;
375 40         2095 *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 80     80 0 368 my @base_classes = @_;
389 80         147 shift @base_classes;
390 40     40   216 no strict 'refs';
  40         67  
  40         15851  
391             goto &$real_base_import
392             unless grep {
393 80 50       211 eval "require $_" unless %{"$_\::"};
  80 50       1439  
  80         1151  
394 80         19700 $_->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 0   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 40     40   268 no strict 'refs';
  40         100  
  40         1522  
425 40     40   190 no warnings;
  40         73  
  40         7155  
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 40     40   267 no strict 'refs';
  40         65  
  40         21901  
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 40     40   300 no strict 'refs';
  40         95  
  40         10308  
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 40     40   260 no warnings;
  40         67  
  40         16727  
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;