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   216 use strict; use warnings;
  40     40   50  
  40         1425  
  40         184  
  40         74  
  40         2825  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   213 use Carp;
  40         112  
  40         13609  
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 1385 ($_[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 228 my $class = shift;
33 223   33     511 $class = ref($class) || $class;
34 223         296 my $self = bless {}, $class;
35 223         375 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         434 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   291 no strict 'refs';
  40         62  
  40         1577  
48 40     40   177 no warnings;
  40         71  
  40         55766  
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         306 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   701 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1692 };
62 256     256   1228 local *paired_arguments = sub { qw(-package) };
  256         305  
63 256         939 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       713 if $args->{-mixin};
67              
68 256 50       447 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       384 $filter_save = 1 if $args->{-filter_save};
70 256 50       377 $dump = 'yaml' if $args->{-yaml};
71 256 50       397 $dump = 'dumper' if $args->{-dumper};
72              
73 256         680 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       474 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     1853 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2215 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1418 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     717 if $args->{-Base} or $args->{-base};
87              
88 256         286 for my $class (@{all_my_bases($self_package)}) {
  256         528  
89 408 50       1798 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3499 not defined &{"$caller_package\::$_"};
  4776         10469  
92 408         1701 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     460 ? @{"$class\::EXPORT_BASE"} : (),
  216         516  
95             );
96             my @export_ok = grep {
97 2304         1759 not defined &{"$caller_package\::$_"};
  2304         4601  
98 408         421 } @{"$class\::EXPORT_OK"};
  408         959  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         544 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7809  
103 408 100       1079 next unless keys %exportable;
104              
105 376         339 my @export_save = @{"$class\::EXPORT"};
  376         1017  
106 376         331 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         846  
107 376         411 @{"$class\::EXPORT"} = @export;
  376         1265  
108 376         413 @{"$class\::EXPORT_OK"} = @export_ok;
  376         826  
109             my @list = grep {
110 376         543 (my $v = $_) =~ s/^[\!\:]//;
  288         754  
111 288 100       625 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         674  
112             } @export_list;
113 376         30183 Exporter::export($class, $caller_package, @list);
114 376         737 @{"$class\::EXPORT"} = @export_save;
  376         1183  
115 376         381 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         31448  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 20271 require Filter::Util::Call;
121 112         33531 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7714 return 0 if $done;
125 112         234 my ($data, $end) = ('', '');
126 112         1170 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       40515 return $status if $status < 0;
128 41896 50       43870 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         33879 $data .= $_;
133 41896         61731 $_ = '';
134             }
135 112         1206 $_ = $data;
136 112         192 my @my_subs;
137 112         7997 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6702 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2127 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         412 my $preclare = '';
144 112 50       639 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         411 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       230 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       182 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5080 $done = 1;
152             }
153 112         1131 );
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 450 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1027 if defined $bases_map->{$class};
166              
167 152         286 my @bases = ($class);
168 40     40   293 no strict 'refs';
  40         61  
  40         7920  
169 152         171 for my $base_class (@{"${class}::ISA"}) {
  152         590  
170 112         174 push @bases, @{all_my_bases($base_class)};
  112         310  
171             }
172 152         188 my $used = {};
173 152         246 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1149  
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 1159 my $package = caller;
202 712         735 my ($args, @values) = do {
203 40     40   213 no warnings;
  40         66  
  40         13960  
204 712     712   2672 local *boolean_arguments = sub { (qw(-weak)) };
  712         1080  
205 712     712   1808 local *paired_arguments = sub { (qw(-package -init)) };
  712         977  
206 712         1668 Spiffy->parse_arguments(@_);
207             };
208 712         1183 my ($field, $default) = @values;
209 712 50       1128 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1367 if defined $default && $args->{-weak};
212 712 50       561 return if defined &{"${package}::$field"};
  712         3125  
213 712 50       993 require Scalar::Util if $args->{-weak};
214 712 100 100     2546 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         1050 my $code = $code{sub_start};
222 712 100       1162 if ($args->{-init}) {
223 160 50       331 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         740 my @count = ($fragment =~ /(%s)/g);
225 160         678 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1548 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1192 $code .= sprintf $code{return_if_get}, $field;
230 712         988 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       984 if $args->{-weak};
233 712         958 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 90   70898 my $sub = eval $code;
  90 100 100     307  
  27 100 100     37  
  27 100 100     34  
  34 100       92  
  14 100       31  
  9 100       16  
  27 100       66  
  23 100       52  
  13 100       26  
  15 100       34  
  23 100       74  
  18 100       72  
  4 100       12  
  23 100       43  
  34 100       77  
  17 100       30  
  29 100       55  
  36 100       92  
  27 100       34  
  19 100       72  
  16 100       50  
  9 100       74  
  12 100       27  
  152 100       653  
  22 100       45  
  25 100       44  
  59 100       117  
  49 100       64  
  55 100       103  
  40         82  
  37         95  
  9         21  
  10         36  
  43         222  
  17         37  
  24         56  
  10         50  
  21         52  
  52         105  
  38         54  
  44         81  
  41         83  
  28         47  
  30         39  
  13         47  
  14         27  
  41         141  
  23         56  
  17         38  
  7         21  
  9         22  
  16         56  
  23         54  
  25         67  
  18         42  
  28         54  
  39         87  
  19         49  
  5         11  
  12         40  
  45         201  
  51         195  
  18         37  
  18         36  
  7         22  
  15         42  
  18         38  
  24         62  
  19         52  
  5         9  
  5         10  
  33         58  
  31         43  
  31         33  
236 712 50       1549 die $@ if $@;
237 40     40   274 no strict 'refs';
  40         60  
  40         9635  
238 712         634 *{"${package}::$field"} = $sub;
  712         3430  
239 712 50       2338 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 25285 require Data::Dumper;
244 592         290918 local $Data::Dumper::Sortkeys = 1;
245 592         1268 my $code = Data::Dumper::Dumper(shift);
246 592         25658 $code =~ s/^\$VAR1 = //;
247 592         1638 $code =~ s/;$//;
248 592         1022 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   258 no warnings;
  40         80  
  40         4077  
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   187 no strict 'refs';
  40         86  
  40         5651  
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   216 no warnings;
  40         66  
  40         4063  
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   187 no strict 'refs';
  40         59  
  40         23448  
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 1092 my $class = shift;
286 968         1522 my ($args, @values) = ({}, ());
287 968         1827 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4668  
288 968         1875 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2316  
289 968         2036 while (@_) {
290 1480         1456 my $elem = shift;
291 1480 100 66     6659 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     633 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         422 $args->{$elem} = shift;
298             }
299             else {
300 1176         2214 push @values, $elem;
301             }
302             }
303 968 50       6458 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   232 no warnings 'redefine';
  40         51  
  40         7744  
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   234 no strict 'refs';
  40         48  
  40         7227  
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   192 require base unless defined $INC{'base.pm'};
370 40   50     232 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         60 $real_base_import = \&base::import;
372 40         104 $real_mixin_import = \&mixin::import;
373 40     40   264 no warnings;
  40         64  
  40         2090  
374 40         290 *base::import = \&spiffy_base_import;
375 40         1601 *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 249 my @base_classes = @_;
389 80         121 shift @base_classes;
390 40     40   171 no strict 'refs';
  40         67  
  40         11978  
391             goto &$real_base_import
392             unless grep {
393 80 50       177 eval "require $_" unless %{"$_\::"};
  80 50       918  
  80         964  
394 80         15982 $_->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   233 no strict 'refs';
  40         84  
  40         1311  
425 40     40   165 no warnings;
  40         76  
  40         6006  
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   243 no strict 'refs';
  40         47  
  40         17494  
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   291 no strict 'refs';
  40         77  
  40         7791  
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   212 no warnings;
  40         48  
  40         12839  
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;