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   203 use strict; use warnings;
  40     40   48  
  40         1201  
  40         163  
  40         60  
  40         2591  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   200 use Carp;
  40         72  
  40         12005  
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 1272 ($_[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 215 my $class = shift;
33 223   33     524 $class = ref($class) || $class;
34 223         274 my $self = bless {}, $class;
35 223         351 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         438 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   298 no strict 'refs';
  40         57  
  40         1310  
48 40     40   162 no warnings;
  40         85  
  40         51229  
49 256     256   635 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         288 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   685 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1724 };
62 256     256   741 local *paired_arguments = sub { qw(-package) };
  256         352  
63 256         942 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       656 if $args->{-mixin};
67              
68 256 50       415 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       415 $filter_save = 1 if $args->{-filter_save};
70 256 50       411 $dump = 'yaml' if $args->{-yaml};
71 256 50       391 $dump = 'dumper' if $args->{-dumper};
72              
73 256         651 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       385 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     1827 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2157 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1359 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     681 if $args->{-Base} or $args->{-base};
87              
88 256         265 for my $class (@{all_my_bases($self_package)}) {
  256         441  
89 408 50       1764 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3416 not defined &{"$caller_package\::$_"};
  4776         10475  
92 408         1601 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     385 ? @{"$class\::EXPORT_BASE"} : (),
  216         500  
95             );
96             my @export_ok = grep {
97 2304         1617 not defined &{"$caller_package\::$_"};
  2304         4275  
98 408         452 } @{"$class\::EXPORT_OK"};
  408         940  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         531 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7845  
103 408 100       996 next unless keys %exportable;
104              
105 376         338 my @export_save = @{"$class\::EXPORT"};
  376         1034  
106 376         315 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         764  
107 376         368 @{"$class\::EXPORT"} = @export;
  376         1167  
108 376         433 @{"$class\::EXPORT_OK"} = @export_ok;
  376         772  
109             my @list = grep {
110 376         456 (my $v = $_) =~ s/^[\!\:]//;
  288         652  
111 288 100       1128 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         695  
112             } @export_list;
113 376         28914 Exporter::export($class, $caller_package, @list);
114 376         715 @{"$class\::EXPORT"} = @export_save;
  376         1209  
115 376         378 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         30495  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 19050 require Filter::Util::Call;
121 112         32301 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   8152 return 0 if $done;
125 112         219 my ($data, $end) = ('', '');
126 112         1163 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       39186 return $status if $status < 0;
128 41896 50       41804 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         32462 $data .= $_;
133 41896         55503 $_ = '';
134             }
135 112         1172 $_ = $data;
136 112         148 my @my_subs;
137 112         7502 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6175 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         1958 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         535 my $preclare = '';
144 112 50       445 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         333 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       206 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       216 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5135 $done = 1;
152             }
153 112         1071 );
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 384 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1316 if defined $bases_map->{$class};
166              
167 152         276 my @bases = ($class);
168 40     40   292 no strict 'refs';
  40         56  
  40         7327  
169 152         221 for my $base_class (@{"${class}::ISA"}) {
  152         504  
170 112         158 push @bases, @{all_my_bases($base_class)};
  112         295  
171             }
172 152         188 my $used = {};
173 152         218 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1234  
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 1214 my $package = caller;
202 712         672 my ($args, @values) = do {
203 40     40   202 no warnings;
  40         46  
  40         11703  
204 712     712   2835 local *boolean_arguments = sub { (qw(-weak)) };
  712         1094  
205 712     712   1833 local *paired_arguments = sub { (qw(-package -init)) };
  712         950  
206 712         1716 Spiffy->parse_arguments(@_);
207             };
208 712         1288 my ($field, $default) = @values;
209 712 50       1210 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1424 if defined $default && $args->{-weak};
212 712 50       628 return if defined &{"${package}::$field"};
  712         2971  
213 712 50       1010 require Scalar::Util if $args->{-weak};
214 712 100 100     2623 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         1081 my $code = $code{sub_start};
222 712 100       1233 if ($args->{-init}) {
223 160 50       323 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         754 my @count = ($fragment =~ /(%s)/g);
225 160         636 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1579 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1240 $code .= sprintf $code{return_if_get}, $field;
230 712         957 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       916 if $args->{-weak};
233 712         978 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 39   75027 my $sub = eval $code;
  39 100 100     114  
  23 100 100     49  
  9 100 100     22  
  23 100       66  
  13 100       57  
  9 100       27  
  21 100       56  
  18 100       39  
  15 100       25  
  29 100       59  
  37 100       81  
  37 100       92  
  19 100       56  
  58 100       102  
  61 100       105  
  128 100       398  
  26 100       69  
  33 100       68  
  60 100       97  
  55 100       97  
  55 100       70  
  35 100       121  
  19 100       44  
  16 100       40  
  7 100       16  
  9 100       20  
  8 100       15  
  9 100       32  
  11 100       21  
  13         37  
  10         28  
  6         20  
  16         49  
  20         47  
  13         38  
  6         16  
  69         253  
  45         134  
  29         35  
  26         34  
  6         24  
  57         236  
  53         268  
  35         59  
  49         86  
  37         89  
  39         153  
  14         31  
  23         54  
  27         56  
  17         90  
  24         71  
  4         14  
  7         22  
  26         53  
  17         53  
  4         7  
  24         51  
  18         51  
  3         27  
  25         49  
  30         81  
  8         13  
  8         15  
  17         42  
  20         53  
  14         26  
  37         80  
  41         126  
  12         18  
  20         47  
  52         151  
  39         53  
  39         42  
236 712 50       1626 die $@ if $@;
237 40     40   246 no strict 'refs';
  40         68  
  40         9506  
238 712         708 *{"${package}::$field"} = $sub;
  712         3625  
239 712 50       2376 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 26199 require Data::Dumper;
244 592         299282 local $Data::Dumper::Sortkeys = 1;
245 592         1240 my $code = Data::Dumper::Dumper(shift);
246 592         26889 $code =~ s/^\$VAR1 = //;
247 592         1658 $code =~ s/;$//;
248 592         1137 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   233 no warnings;
  40         58  
  40         3793  
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   158 no strict 'refs';
  40         63  
  40         5288  
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   189 no warnings;
  40         98  
  40         3950  
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   162 no strict 'refs';
  40         60  
  40         22957  
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 1168 my $class = shift;
286 968         1550 my ($args, @values) = ({}, ());
287 968         1834 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4671  
288 968         1915 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2544  
289 968         2004 while (@_) {
290 1480         1470 my $elem = shift;
291 1480 100 66     6848 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     617 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         457 $args->{$elem} = shift;
298             }
299             else {
300 1176         2180 push @values, $elem;
301             }
302             }
303 968 50       6676 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   239 no warnings 'redefine';
  40         49  
  40         7433  
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   200 no strict 'refs';
  40         48  
  40         6720  
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   177 require base unless defined $INC{'base.pm'};
370 40   50     217 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         52 $real_base_import = \&base::import;
372 40         84 $real_mixin_import = \&mixin::import;
373 40     40   207 no warnings;
  40         87  
  40         2009  
374 40         249 *base::import = \&spiffy_base_import;
375 40         1553 *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 232 my @base_classes = @_;
389 80         111 shift @base_classes;
390 40     40   149 no strict 'refs';
  40         69  
  40         10766  
391             goto &$real_base_import
392             unless grep {
393 80 50       154 eval "require $_" unless %{"$_\::"};
  80 50       1093  
  80         519  
394 80         14415 $_->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   221 no strict 'refs';
  40         80  
  40         1284  
425 40     40   149 no warnings;
  40         53  
  40         5583  
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   185 no strict 'refs';
  40         43  
  40         16841  
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   234 no strict 'refs';
  40         93  
  40         7341  
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   194 no warnings;
  40         64  
  40         12280  
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;