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   193 use strict; use warnings;
  40     40   44  
  40         1290  
  40         153  
  40         100  
  40         2483  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   228 use Carp;
  40         73  
  40         11998  
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 1296 ($_[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 245 my $class = shift;
33 223   33     521 $class = ref($class) || $class;
34 223         275 my $self = bless {}, $class;
35 223         358 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         419 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   292 no strict 'refs';
  40         79  
  40         1309  
48 40     40   144 no warnings;
  40         42  
  40         52636  
49 256     256   645 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         269 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   628 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1563 };
62 256     256   707 local *paired_arguments = sub { qw(-package) };
  256         284  
63 256         854 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       594 if $args->{-mixin};
67              
68 256 50       386 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       354 $filter_save = 1 if $args->{-filter_save};
70 256 50       331 $dump = 'yaml' if $args->{-yaml};
71 256 50       344 $dump = 'dumper' if $args->{-dumper};
72              
73 256         604 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       361 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     1847 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2078 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1331 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     652 if $args->{-Base} or $args->{-base};
87              
88 256         291 for my $class (@{all_my_bases($self_package)}) {
  256         403  
89 408 50       1675 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3361 not defined &{"$caller_package\::$_"};
  4776         9759  
92 408         1600 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     360 ? @{"$class\::EXPORT_BASE"} : (),
  216         532  
95             );
96             my @export_ok = grep {
97 2304         1665 not defined &{"$caller_package\::$_"};
  2304         4271  
98 408         448 } @{"$class\::EXPORT_OK"};
  408         842  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         502 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7510  
103 408 100       964 next unless keys %exportable;
104              
105 376         333 my @export_save = @{"$class\::EXPORT"};
  376         961  
106 376         382 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         730  
107 376         356 @{"$class\::EXPORT"} = @export;
  376         1149  
108 376         383 @{"$class\::EXPORT_OK"} = @export_ok;
  376         717  
109             my @list = grep {
110 376         428 (my $v = $_) =~ s/^[\!\:]//;
  288         645  
111 288 100       583 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         592  
112             } @export_list;
113 376         27701 Exporter::export($class, $caller_package, @list);
114 376         649 @{"$class\::EXPORT"} = @export_save;
  376         1042  
115 376         390 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         30647  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 18699 require Filter::Util::Call;
121 112         31776 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7205 return 0 if $done;
125 112         200 my ($data, $end) = ('', '');
126 112         1022 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       38614 return $status if $status < 0;
128 41896 50       41745 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         32051 $data .= $_;
133 41896         55855 $_ = '';
134             }
135 112         1175 $_ = $data;
136 112         147 my @my_subs;
137 112         9002 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6222 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         1944 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         561 my $preclare = '';
144 112 50       368 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         315 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       200 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       229 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         4712 $done = 1;
152             }
153 112         1065 );
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 373 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       960 if defined $bases_map->{$class};
166              
167 152         276 my @bases = ($class);
168 40     40   290 no strict 'refs';
  40         51  
  40         7298  
169 152         164 for my $base_class (@{"${class}::ISA"}) {
  152         516  
170 112         133 push @bases, @{all_my_bases($base_class)};
  112         352  
171             }
172 152         183 my $used = {};
173 152         205 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1034  
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 1064 my $package = caller;
202 712         632 my ($args, @values) = do {
203 40     40   197 no warnings;
  40         44  
  40         12245  
204 712     712   2611 local *boolean_arguments = sub { (qw(-weak)) };
  712         972  
205 712     712   1690 local *paired_arguments = sub { (qw(-package -init)) };
  712         952  
206 712         1565 Spiffy->parse_arguments(@_);
207             };
208 712         1204 my ($field, $default) = @values;
209 712 50       1088 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1388 if defined $default && $args->{-weak};
212 712 50       541 return if defined &{"${package}::$field"};
  712         2837  
213 712 50       919 require Scalar::Util if $args->{-weak};
214 712 100 100     2569 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         982 my $code = $code{sub_start};
222 712 100       1182 if ($args->{-init}) {
223 160 50       313 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         686 my @count = ($fragment =~ /(%s)/g);
225 160         609 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1605 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1184 $code .= sprintf $code{return_if_get}, $field;
230 712         892 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       890 if $args->{-weak};
233 712         908 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 16   69561 my $sub = eval $code;
  16 50 100     43  
  4 100 100     6  
  4 100 100     6  
  6 100       13  
  36 100       93  
  20 100       85  
  8 100       21  
  24 100       41  
  100 100       350  
  28 100       36  
  12 100       30  
  32 100       97  
  18 100       61  
  5 100       14  
  22 100       56  
  6 100       17  
  14 100       27  
  93 100       335  
  13 100       45  
  16 100       25  
  24 100       59  
  14 100       25  
  19 100       45  
  26 100       73  
  12 100       21  
  13 100       44  
  49 100       92  
  48 100       85  
  47 100       77  
  57         214  
  31         110  
  16         41  
  12         39  
  8         16  
  50         80  
  48         71  
  49         67  
  10         27  
  26         43  
  30         62  
  37         55  
  19         46  
  33         64  
  23         50  
  20         26  
  17         46  
  7         10  
  11         31  
  24         54  
  23         82  
  17         52  
  15         39  
  8         35  
  28         69  
  33         76  
  16         41  
  31         82  
  57         147  
  40         120  
  10         23  
  21         53  
  32         127  
  7         12  
  13         25  
  57         266  
  22         57  
  16         23  
  15         25  
  73         264  
  8         9  
  7         9  
  89         301  
  43         60  
  43         47  
236 712 50       1499 die $@ if $@;
237 40     40   260 no strict 'refs';
  40         80  
  40         9718  
238 712         669 *{"${package}::$field"} = $sub;
  712         3279  
239 712 50       2290 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 23835 require Data::Dumper;
244 592         280397 local $Data::Dumper::Sortkeys = 1;
245 592         1239 my $code = Data::Dumper::Dumper(shift);
246 592         24418 $code =~ s/^\$VAR1 = //;
247 592         1563 $code =~ s/;$//;
248 592         993 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   266 no warnings;
  40         51  
  40         3950  
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   193 no strict 'refs';
  40         76  
  40         5162  
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         58  
  40         3916  
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   163 no strict 'refs';
  40         61  
  40         22280  
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 1039 my $class = shift;
286 968         1357 my ($args, @values) = ({}, ());
287 968         1636 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4397  
288 968         1823 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2418  
289 968         1893 while (@_) {
290 1480         1416 my $elem = shift;
291 1480 100 66     6643 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     597 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         368 $args->{$elem} = shift;
298             }
299             else {
300 1176         2102 push @values, $elem;
301             }
302             }
303 968 50       6275 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   227 no warnings 'redefine';
  40         85  
  40         7399  
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   188 no strict 'refs';
  40         49  
  40         6531  
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   175 require base unless defined $INC{'base.pm'};
370 40   50     220 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         58 $real_base_import = \&base::import;
372 40         84 $real_mixin_import = \&mixin::import;
373 40     40   235 no warnings;
  40         59  
  40         1926  
374 40         277 *base::import = \&spiffy_base_import;
375 40         1606 *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 290 my @base_classes = @_;
389 80         147 shift @base_classes;
390 40     40   155 no strict 'refs';
  40         67  
  40         10768  
391             goto &$real_base_import
392             unless grep {
393 80 50       148 eval "require $_" unless %{"$_\::"};
  80 50       845  
  80         851  
394 80         13932 $_->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   235 no strict 'refs';
  40         72  
  40         1222  
425 40     40   179 no warnings;
  40         86  
  40         5541  
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         42  
  40         16320  
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   253 no strict 'refs';
  40         61  
  40         7273  
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   190 no warnings;
  40         45  
  40         12162  
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;