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   188 use strict; use warnings;
  40     40   44  
  40         1285  
  40         204  
  40         71  
  40         2713  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   218 use Carp;
  40         95  
  40         12444  
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 1337 ($_[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 260 my $class = shift;
33 223   33     528 $class = ref($class) || $class;
34 223         297 my $self = bless {}, $class;
35 223         349 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   277 no strict 'refs';
  40         67  
  40         1344  
48 40     40   139 no warnings;
  40         43  
  40         53736  
49 256     256   595 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         307 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   659 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1593 };
62 256     256   719 local *paired_arguments = sub { qw(-package) };
  256         301  
63 256         842 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       648 if $args->{-mixin};
67              
68 256 50       396 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       367 $filter_save = 1 if $args->{-filter_save};
70 256 50       341 $dump = 'yaml' if $args->{-yaml};
71 256 50       390 $dump = 'dumper' if $args->{-dumper};
72              
73 256         710 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       372 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     1821 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2099 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1386 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     663 if $args->{-Base} or $args->{-base};
87              
88 256         257 for my $class (@{all_my_bases($self_package)}) {
  256         434  
89 408 50       1631 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3305 not defined &{"$caller_package\::$_"};
  4776         10164  
92 408         1551 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     391 ? @{"$class\::EXPORT_BASE"} : (),
  216         522  
95             );
96             my @export_ok = grep {
97 2304         1669 not defined &{"$caller_package\::$_"};
  2304         4127  
98 408         415 } @{"$class\::EXPORT_OK"};
  408         920  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         506 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7399  
103 408 100       1022 next unless keys %exportable;
104              
105 376         321 my @export_save = @{"$class\::EXPORT"};
  376         966  
106 376         334 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         732  
107 376         358 @{"$class\::EXPORT"} = @export;
  376         1186  
108 376         343 @{"$class\::EXPORT_OK"} = @export_ok;
  376         754  
109             my @list = grep {
110 376         445 (my $v = $_) =~ s/^[\!\:]//;
  288         681  
111 288 100       572 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         586  
112             } @export_list;
113 376         27272 Exporter::export($class, $caller_package, @list);
114 376         684 @{"$class\::EXPORT"} = @export_save;
  376         1145  
115 376         381 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         31402  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 18619 require Filter::Util::Call;
121 112         31293 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7232 return 0 if $done;
125 112         210 my ($data, $end) = ('', '');
126 112         1049 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       40659 return $status if $status < 0;
128 41896 50       43321 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         33559 $data .= $_;
133 41896         58296 $_ = '';
134             }
135 112         1152 $_ = $data;
136 112         168 my @my_subs;
137 112         7761 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6548 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2031 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         450 my $preclare = '';
144 112 50       480 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         311 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       213 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       193 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         4769 $done = 1;
152             }
153 112         1059 );
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       938 if defined $bases_map->{$class};
166              
167 152         245 my @bases = ($class);
168 40     40   295 no strict 'refs';
  40         51  
  40         7605  
169 152         151 for my $base_class (@{"${class}::ISA"}) {
  152         511  
170 112         149 push @bases, @{all_my_bases($base_class)};
  112         258  
171             }
172 152         215 my $used = {};
173 152         236 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1090  
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 1105 my $package = caller;
202 712         660 my ($args, @values) = do {
203 40     40   191 no warnings;
  40         50  
  40         12215  
204 712     712   2615 local *boolean_arguments = sub { (qw(-weak)) };
  712         1013  
205 712     712   1710 local *paired_arguments = sub { (qw(-package -init)) };
  712         974  
206 712         1611 Spiffy->parse_arguments(@_);
207             };
208 712         1174 my ($field, $default) = @values;
209 712 50       1147 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1350 if defined $default && $args->{-weak};
212 712 50       552 return if defined &{"${package}::$field"};
  712         2748  
213 712 50       908 require Scalar::Util if $args->{-weak};
214 712 100 100     2538 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         993 my $code = $code{sub_start};
222 712 100       1129 if ($args->{-init}) {
223 160 50       304 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         733 my @count = ($fragment =~ /(%s)/g);
225 160         619 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1472 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1170 $code .= sprintf $code{return_if_get}, $field;
230 712         954 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       860 if $args->{-weak};
233 712         931 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 33   67505 my $sub = eval $code;
  33 100 100     85  
  22 100 100     95  
  2 100 100     4  
  21 100       57  
  37 100       84  
  28 100       89  
  27 100       68  
  20 100       40  
  15 100       39  
  11 100       37  
  4 100       12  
  9 100       34  
  15 100       56  
  7 100       23  
  17 100       38  
  26 100       83  
  15 100       29  
  10 100       26  
  58 100       226  
  20 100       46  
  19 100       43  
  2 100       2  
  6 100       14  
  24 100       35  
  25 100       42  
  31 100       69  
  4 100       10  
  23 100       65  
  40 100       102  
  27         40  
  42         201  
  10         35  
  7         18  
  5         14  
  26         66  
  17         40  
  5         13  
  12         32  
  98         351  
  29         62  
  18         29  
  54         99  
  95         155  
  80         99  
  98         277  
  45         76  
  43         66  
  54         92  
  10         28  
  9         31  
  18         37  
  18         51  
  5         6  
  3         7  
  37         72  
  37         121  
  1         5  
  5         8  
  73         371  
  9         25  
  29         60  
  59         154  
  25         35  
  26         49  
  99         384  
  20         33  
  17         27  
  25         59  
  28         92  
  8         9  
  10         18  
  42         142  
  16         19  
  16         25  
236 712 50       1427 die $@ if $@;
237 40     40   299 no strict 'refs';
  40         59  
  40         9523  
238 712         617 *{"${package}::$field"} = $sub;
  712         3201  
239 712 50       2305 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 23351 require Data::Dumper;
244 592         277162 local $Data::Dumper::Sortkeys = 1;
245 592         1209 my $code = Data::Dumper::Dumper(shift);
246 592         24821 $code =~ s/^\$VAR1 = //;
247 592         1571 $code =~ s/;$//;
248 592         1057 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   263 no warnings;
  40         46  
  40         3751  
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   171 no strict 'refs';
  40         45  
  40         5345  
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   203 no warnings;
  40         69  
  40         4121  
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   170 no strict 'refs';
  40         94  
  40         22693  
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 1028 my $class = shift;
286 968         1444 my ($args, @values) = ({}, ());
287 968         1717 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4584  
288 968         1734 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2249  
289 968         1982 while (@_) {
290 1480         1356 my $elem = shift;
291 1480 100 66     6374 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     625 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         376 $args->{$elem} = shift;
298             }
299             else {
300 1176         2085 push @values, $elem;
301             }
302             }
303 968 50       6158 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   230 no warnings 'redefine';
  40         42  
  40         7773  
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   207 no strict 'refs';
  40         46  
  40         6834  
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   183 require base unless defined $INC{'base.pm'};
370 40   50     233 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         58 $real_base_import = \&base::import;
372 40         89 $real_mixin_import = \&mixin::import;
373 40     40   223 no warnings;
  40         74  
  40         2007  
374 40         269 *base::import = \&spiffy_base_import;
375 40         1634 *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 266 my @base_classes = @_;
389 80         119 shift @base_classes;
390 40     40   167 no strict 'refs';
  40         40  
  40         11333  
391             goto &$real_base_import
392             unless grep {
393 80 50       155 eval "require $_" unless %{"$_\::"};
  80 50       948  
  80         904  
394 80         13779 $_->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   212 no strict 'refs';
  40         83  
  40         1214  
425 40     40   202 no warnings;
  40         62  
  40         5682  
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   192 no strict 'refs';
  40         46  
  40         17529  
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   275 no strict 'refs';
  40         68  
  40         7713  
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   214 no warnings;
  40         62  
  40         12707  
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;