File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 283 438 64.6
branch 112 206 54.3
condition 38 62 61.2
subroutine 37 60 61.6
pod 0 27 0.0
total 470 793 59.2


line stmt bran cond sub pod time code
1 40     40   204 use strict; use warnings;
  40     40   45  
  40         1246  
  40         196  
  40         70  
  40         2498  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   212 use Carp;
  40         82  
  40         11780  
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 1403 ($_[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 247 my $class = shift;
33 223   33     553 $class = ref($class) || $class;
34 223         300 my $self = bless {}, $class;
35 223         366 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         445 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   321 no strict 'refs';
  40         55  
  40         1354  
48 40     40   160 no warnings;
  40         56  
  40         52130  
49 256     256   597 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         301 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   670 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1681 };
62 256     256   744 local *paired_arguments = sub { qw(-package) };
  256         307  
63 256         890 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       616 if $args->{-mixin};
67              
68 256 50       413 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       377 $filter_save = 1 if $args->{-filter_save};
70 256 50       369 $dump = 'yaml' if $args->{-yaml};
71 256 50       406 $dump = 'dumper' if $args->{-dumper};
72              
73 256         677 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       403 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     1873 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2146 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1416 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     686 if $args->{-Base} or $args->{-base};
87              
88 256         270 for my $class (@{all_my_bases($self_package)}) {
  256         436  
89 408 50       1665 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3474 not defined &{"$caller_package\::$_"};
  4776         9973  
92 408         1569 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     427 ? @{"$class\::EXPORT_BASE"} : (),
  216         499  
95             );
96             my @export_ok = grep {
97 2304         1687 not defined &{"$caller_package\::$_"};
  2304         4363  
98 408         397 } @{"$class\::EXPORT_OK"};
  408         828  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         513 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7752  
103 408 100       1061 next unless keys %exportable;
104              
105 376         361 my @export_save = @{"$class\::EXPORT"};
  376         1040  
106 376         383 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         778  
107 376         371 @{"$class\::EXPORT"} = @export;
  376         1333  
108 376         405 @{"$class\::EXPORT_OK"} = @export_ok;
  376         793  
109             my @list = grep {
110 376         441 (my $v = $_) =~ s/^[\!\:]//;
  288         668  
111 288 100       595 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         651  
112             } @export_list;
113 376         27964 Exporter::export($class, $caller_package, @list);
114 376         739 @{"$class\::EXPORT"} = @export_save;
  376         1104  
115 376         381 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         32551  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 18862 require Filter::Util::Call;
121 112         31953 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7650 return 0 if $done;
125 112         259 my ($data, $end) = ('', '');
126 112         1117 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       39800 return $status if $status < 0;
128 41896 50       43108 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         32725 $data .= $_;
133 41896         57445 $_ = '';
134             }
135 112         1256 $_ = $data;
136 112         170 my @my_subs;
137 112         8169 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6661 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         1996 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         570 my $preclare = '';
144 112 50       410 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       268 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       268 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5286 $done = 1;
152             }
153 112         1030 );
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 388 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1025 if defined $bases_map->{$class};
166              
167 152         271 my @bases = ($class);
168 40     40   289 no strict 'refs';
  40         46  
  40         7327  
169 152         176 for my $base_class (@{"${class}::ISA"}) {
  152         553  
170 112         146 push @bases, @{all_my_bases($base_class)};
  112         278  
171             }
172 152         190 my $used = {};
173 152         231 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1163  
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 1180 my $package = caller;
202 712         711 my ($args, @values) = do {
203 40     40   215 no warnings;
  40         48  
  40         12047  
204 712     712   2932 local *boolean_arguments = sub { (qw(-weak)) };
  712         1149  
205 712     712   1988 local *paired_arguments = sub { (qw(-package -init)) };
  712         1030  
206 712         1822 Spiffy->parse_arguments(@_);
207             };
208 712         1365 my ($field, $default) = @values;
209 712 50       1253 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1471 if defined $default && $args->{-weak};
212 712 50       609 return if defined &{"${package}::$field"};
  712         3290  
213 712 50       1028 require Scalar::Util if $args->{-weak};
214 712 100 100     2740 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         1079 my $code = $code{sub_start};
222 712 100       1239 if ($args->{-init}) {
223 160 50       350 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         805 my @count = ($fragment =~ /(%s)/g);
225 160         685 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1698 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1373 $code .= sprintf $code{return_if_get}, $field;
230 712         1040 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       992 if $args->{-weak};
233 712         1081 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 38   75709 my $sub = eval $code;
  38 100 100     111  
  24 100 66     65  
  2 100 100     8  
  27 100       108  
  9 100       29  
  7 100       14  
  23 100       76  
  7 100       19  
  8 100       21  
  27 100       49  
  28 100       72  
  28 100       57  
  3 100       8  
  0 100       0  
  29 100       64  
  23 100       60  
  61 100       104  
  86 100       157  
  79 100       157  
  31 100       55  
  22 100       64  
  57 100       109  
  51 100       81  
  55 100       76  
  15 100       58  
  21 100       42  
  27 100       89  
  11 100       27  
  4 100       10  
  18         38  
  29         102  
  76         268  
  76         336  
  29         78  
  29         52  
  36         116  
  8         21  
  18         85  
  61         158  
  39         50  
  41         60  
  22         45  
  34         80  
  25         49  
  21         68  
  24         53  
  8         25  
  8         20  
  14         35  
  23         48  
  28         85  
  15         34  
  51         198  
  34         71  
  72         270  
  10         15  
  11         21  
  13         41  
  4         8  
  6         17  
  25         62  
  51         196  
  5         10  
  18         35  
  92         442  
  15         32  
  12         18  
  4         15  
  10         43  
  5         11  
  16         38  
  22         71  
  10         19  
  10         17  
236 712 50       1573 die $@ if $@;
237 40     40   282 no strict 'refs';
  40         80  
  40         9719  
238 712         733 *{"${package}::$field"} = $sub;
  712         3634  
239 712 50       2441 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 24988 require Data::Dumper;
244 592         309993 local $Data::Dumper::Sortkeys = 1;
245 592         1328 my $code = Data::Dumper::Dumper(shift);
246 592         26772 $code =~ s/^\$VAR1 = //;
247 592         1731 $code =~ s/;$//;
248 592         1168 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   210 no warnings;
  40         43  
  40         3873  
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   167 no strict 'refs';
  40         48  
  40         5329  
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   198 no warnings;
  40         71  
  40         4170  
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   165 no strict 'refs';
  40         75  
  40         22865  
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 1170 my $class = shift;
286 968         1603 my ($args, @values) = ({}, ());
287 968         1810 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4881  
288 968         1940 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2701  
289 968         2166 while (@_) {
290 1480         1466 my $elem = shift;
291 1480 100 66     7197 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     566 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         451 $args->{$elem} = shift;
298             }
299             else {
300 1176         2299 push @values, $elem;
301             }
302             }
303 968 50       6707 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   237 no warnings 'redefine';
  40         43  
  40         7386  
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   208 no strict 'refs';
  40         50  
  40         6764  
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   207 require base unless defined $INC{'base.pm'};
370 40   50     257 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         64 $real_base_import = \&base::import;
372 40         93 $real_mixin_import = \&mixin::import;
373 40     40   225 no warnings;
  40         54  
  40         2145  
374 40         275 *base::import = \&spiffy_base_import;
375 40         1623 *mixin::import = \&spiffy_mixin_import;
376             }
377              
378             # my $i = 0;
379             # while (my $caller = caller($i++)) {
380             # next unless $caller eq 'base' or $caller eq 'mixin';
381             # croak <
382             # Spiffy.pm must be loaded before calling 'use base' or 'use mixin' with a
383             # Spiffy module. See the documentation of Spiffy.pm for details.
384             # END
385             # }
386              
387             sub spiffy_base_import {
388 80     80 0 278 my @base_classes = @_;
389 80         117 shift @base_classes;
390 40     40   157 no strict 'refs';
  40         77  
  40         11182  
391             goto &$real_base_import
392             unless grep {
393 80 50       167 eval "require $_" unless %{"$_\::"};
  80 50       1113  
  80         1052  
394 80         14606 $_->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         113  
  40         1223  
425 40     40   143 no warnings;
  40         57  
  40         5567  
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   197 no strict 'refs';
  40         51  
  40         17145  
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   274 no strict 'refs';
  40         54  
  40         7417  
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   195 no warnings;
  40         48  
  40         12292  
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;