File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 111 206 53.8
condition 39 62 62.9
subroutine 37 60 61.6
pod 0 27 0.0
total 471 793 59.3


line stmt bran cond sub pod time code
1 40     40   201 use strict; use warnings;
  40     40   49  
  40         1375  
  40         188  
  40         56  
  40         2737  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   235 use Carp;
  40         67  
  40         12782  
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 1691 ($_[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 271 my $class = shift;
33 223   33     567 $class = ref($class) || $class;
34 223         365 my $self = bless {}, $class;
35 223         409 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         545 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   248 no strict 'refs';
  40         79  
  40         1491  
48 40     40   145 no warnings;
  40         58  
  40         56901  
49 256     256   663 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         329 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   734 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1832 };
62 256     256   829 local *paired_arguments = sub { qw(-package) };
  256         347  
63 256         1071 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       674 if $args->{-mixin};
67              
68 256 50       458 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       430 $filter_save = 1 if $args->{-filter_save};
70 256 50       410 $dump = 'yaml' if $args->{-yaml};
71 256 50       437 $dump = 'dumper' if $args->{-dumper};
72              
73 256         807 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       464 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     2018 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2480 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1583 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     841 if $args->{-Base} or $args->{-base};
87              
88 256         309 for my $class (@{all_my_bases($self_package)}) {
  256         487  
89 408 50       1982 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3682 not defined &{"$caller_package\::$_"};
  4776         11152  
92 408         1855 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     428 ? @{"$class\::EXPORT_BASE"} : (),
  216         586  
95             );
96             my @export_ok = grep {
97 2304         1862 not defined &{"$caller_package\::$_"};
  2304         4976  
98 408         482 } @{"$class\::EXPORT_OK"};
  408         970  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         618 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         8545  
103 408 100       1081 next unless keys %exportable;
104              
105 376         413 my @export_save = @{"$class\::EXPORT"};
  376         1227  
106 376         360 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         854  
107 376         481 @{"$class\::EXPORT"} = @export;
  376         1377  
108 376         429 @{"$class\::EXPORT_OK"} = @export_ok;
  376         870  
109             my @list = grep {
110 376         530 (my $v = $_) =~ s/^[\!\:]//;
  288         740  
111 288 100       681 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         684  
112             } @export_list;
113 376         32029 Exporter::export($class, $caller_package, @list);
114 376         768 @{"$class\::EXPORT"} = @export_save;
  376         1200  
115 376         403 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         34293  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 20465 require Filter::Util::Call;
121 112         34312 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   8344 return 0 if $done;
125 112         276 my ($data, $end) = ('', '');
126 112         1223 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       44215 return $status if $status < 0;
128 41896 50       47219 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         37550 $data .= $_;
133 41896         63289 $_ = '';
134             }
135 112         1247 $_ = $data;
136 112         193 my @my_subs;
137 112         8345 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6972 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2123 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         525 my $preclare = '';
144 112 50       455 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         459 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       370 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       215 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5324 $done = 1;
152             }
153 112         1206 );
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 428 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1273 if defined $bases_map->{$class};
166              
167 152         310 my @bases = ($class);
168 40     40   345 no strict 'refs';
  40         59  
  40         7602  
169 152         209 for my $base_class (@{"${class}::ISA"}) {
  152         588  
170 112         172 push @bases, @{all_my_bases($base_class)};
  112         326  
171             }
172 152         215 my $used = {};
173 152         264 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1274  
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 1211 my $package = caller;
202 712         729 my ($args, @values) = do {
203 40     40   197 no warnings;
  40         57  
  40         12906  
204 712     712   3005 local *boolean_arguments = sub { (qw(-weak)) };
  712         1128  
205 712     712   1920 local *paired_arguments = sub { (qw(-package -init)) };
  712         1038  
206 712         1759 Spiffy->parse_arguments(@_);
207             };
208 712         1347 my ($field, $default) = @values;
209 712 50       1230 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1509 if defined $default && $args->{-weak};
212 712 50       649 return if defined &{"${package}::$field"};
  712         3221  
213 712 50       1308 require Scalar::Util if $args->{-weak};
214 712 100 100     2841 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         1166 my $code = $code{sub_start};
222 712 100       1242 if ($args->{-init}) {
223 160 50       353 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         814 my @count = ($fragment =~ /(%s)/g);
225 160         727 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1705 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1314 $code .= sprintf $code{return_if_get}, $field;
230 712         1027 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       985 if $args->{-weak};
233 712         1054 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 38   77727 my $sub = eval $code;
  38 100 100     129  
  27 100 100     74  
  1 100 100     7  
  14 100       42  
  44 100       85  
  40 100       66  
  41 100       81  
  11 100       34  
  27 100       70  
  94 100       403  
  21 100       54  
  25 100       41  
  65 100       135  
  93 100       409  
  48 100       54  
  14 100       40  
  13 100       52  
  15 100       60  
  4 100       29  
  14 100       36  
  17 100       51  
  34 100       62  
  38 100       118  
  29 100       74  
  3 100       11  
  17 100       41  
  27 100       140  
  16 100       62  
  19 50       103  
  18         41  
  21         91  
  23         87  
  9         24  
  32         151  
  42         159  
  15         76  
  34         82  
  45         104  
  43         100  
  20         50  
  22         59  
  33         72  
  27         67  
  41         61  
  44         96  
  34         60  
  59         227  
  24         70  
  15         31  
  10         28  
  11         38  
  19         65  
  85         378  
  37         87  
  16         31  
  25         77  
  35         90  
  31         198  
  10         37  
  9         29  
  16         66  
  11         68  
  8         44  
  13         44  
  17         90  
  28         89  
  20         54  
  61         457  
  17         64  
  9         16  
  4         17  
  13         34  
  13         28  
  13         24  
236 712 50       1608 die $@ if $@;
237 40     40   281 no strict 'refs';
  40         58  
  40         10159  
238 712         729 *{"${package}::$field"} = $sub;
  712         3845  
239 712 50       2439 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 26594 require Data::Dumper;
244 592         301726 local $Data::Dumper::Sortkeys = 1;
245 592         1313 my $code = Data::Dumper::Dumper(shift);
246 592         27247 $code =~ s/^\$VAR1 = //;
247 592         1816 $code =~ s/;$//;
248 592         1144 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   217 no warnings;
  40         44  
  40         3893  
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   173 no strict 'refs';
  40         59  
  40         5561  
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   218 no warnings;
  40         63  
  40         4196  
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   172 no strict 'refs';
  40         104  
  40         23651  
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 1195 my $class = shift;
286 968         1562 my ($args, @values) = ({}, ());
287 968         1882 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         5012  
288 968         2038 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2519  
289 968         2172 while (@_) {
290 1480         1589 my $elem = shift;
291 1480 100 66     7234 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     623 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         455 $args->{$elem} = shift;
298             }
299             else {
300 1176         2300 push @values, $elem;
301             }
302             }
303 968 50       6993 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   249 no warnings 'redefine';
  40         55  
  40         7774  
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   195 no strict 'refs';
  40         44  
  40         7040  
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   196 require base unless defined $INC{'base.pm'};
370 40   50     224 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         59 $real_base_import = \&base::import;
372 40         94 $real_mixin_import = \&mixin::import;
373 40     40   224 no warnings;
  40         67  
  40         2033  
374 40         260 *base::import = \&spiffy_base_import;
375 40         1604 *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 278 my @base_classes = @_;
389 80         129 shift @base_classes;
390 40     40   147 no strict 'refs';
  40         68  
  40         11016  
391             goto &$real_base_import
392             unless grep {
393 80 50       169 eval "require $_" unless %{"$_\::"};
  80 50       744  
  80         1017  
394 80         15320 $_->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   205 no strict 'refs';
  40         99  
  40         1406  
425 40     40   168 no warnings;
  40         76  
  40         5720  
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   196 no strict 'refs';
  40         48  
  40         17464  
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   263 no strict 'refs';
  40         84  
  40         7606  
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   218 no warnings;
  40         57  
  40         12912  
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;