File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 111 204 54.4
condition 39 62 62.9
subroutine 37 60 61.6
pod 0 27 0.0
total 471 791 59.5


line stmt bran cond sub pod time code
1 40     40   504 use strict; use warnings;
  40     40   82  
  40         1099  
  40         200  
  40         80  
  40         1814  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   222 use Carp;
  40         56  
  40         13461  
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 1807 ($_[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 381 my $class = shift;
33 223   33     677 $class = ref($class) || $class;
34 223         406 my $self = bless {}, $class;
35 223         498 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         561 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   296 no strict 'refs';
  40         97  
  40         1504  
48 40     40   219 no warnings;
  40         105  
  40         48131  
49 256     256   753 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         380 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   847 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1855 };
62 256     256   945 local *paired_arguments = sub { qw(-package) };
  256         420  
63 256         1077 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       781 if $args->{-mixin};
67              
68 256 50       533 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       453 $filter_save = 1 if $args->{-filter_save};
70 256 50       487 $dump = 'yaml' if $args->{-yaml};
71 256 50       516 $dump = 'dumper' if $args->{-dumper};
72              
73 256         1094 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       520 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     2383 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2657 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1565 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     926 if $args->{-Base} or $args->{-base};
87              
88 256         409 for my $class (@{all_my_bases($self_package)}) {
  256         574  
89 408 50       2050 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         4613 not defined &{"$caller_package\::$_"};
  4776         13783  
92 408         2058 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     568 ? @{"$class\::EXPORT_BASE"} : (),
  216         704  
95             );
96             my @export_ok = grep {
97 2304         3811 not defined &{"$caller_package\::$_"};
  2304         6278  
98 408         601 } @{"$class\::EXPORT_OK"};
  408         1152  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         2379 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         11293  
103 408 100       1362 next unless keys %exportable;
104              
105 376         493 my @export_save = @{"$class\::EXPORT"};
  376         1240  
106 376         532 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         2642  
107 376         2215 @{"$class\::EXPORT"} = @export;
  376         4849  
108 376         2078 @{"$class\::EXPORT_OK"} = @export_ok;
  376         2925  
109             my @list = grep {
110 376         2262 (my $v = $_) =~ s/^[\!\:]//;
  288         865  
111 288 100       871 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         824  
112             } @export_list;
113 376         37116 Exporter::export($class, $caller_package, @list);
114 376         984 @{"$class\::EXPORT"} = @export_save;
  376         4920  
115 376         517 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         38598  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 22879 require Filter::Util::Call;
121 112         36651 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   9695 return 0 if $done;
125 112         2074 my ($data, $end) = ('', '');
126 112         4597 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       52138 return $status if $status < 0;
128 41896 50       54351 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         43040 $data .= $_;
133 41896         73651 $_ = '';
134             }
135 112         1484 $_ = $data;
136 112         231 my @my_subs;
137 112         11106 s[^(sub\s+\w+\s+\{)(.*\n)]
138 112         6739 [${1}my \$self = shift;$2]gm;
139 112         2202 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 112         221 [${1}${2}]gm;
141 112 50       2114 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;
143 0         0 my $preclare = '';
144             if (@my_subs) {
145 112         2296 $preclare = join ',', map "\$$_", @my_subs;
146 112 50       5897 $preclare = "my($preclare);";
  0         0  
  0         0  
147 112 50       3459 }
  0         0  
  0         0  
148 112         6303 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 112         949 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
151             $done = 1;
152             }
153             );
154 0     0 0 0 }
155 0         0  
156             sub base {
157             push @_, -base;
158             goto &import;
159 368     368 0 517 }
160              
161             sub all_my_bases {
162 368 100       1178 my $class = shift;
163              
164 152         316 return $bases_map->{$class}
165 40     40   415 if defined $bases_map->{$class};
  40         100  
  40         10206  
166 152         189  
  152         777  
167 112         210 my @bases = ($class);
  112         395  
168             no strict 'refs';
169 152         283 for my $base_class (@{"${class}::ISA"}) {
170 152         294 push @bases, @{all_my_bases($base_class)};
  304         1264  
171             }
172             my $used = {};
173             $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
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 712     712 0 1421 );
199 712         807  
200 40     40   318 sub field {
  40         120  
  40         16047  
201 712     712   3183 my $package = caller;
  712         1225  
202 712     712   2545 my ($args, @values) = do {
  712         1248  
203 712         2100 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 712         1612 local *paired_arguments = sub { (qw(-package -init)) };
206 712 50       1456 Spiffy->parse_arguments(@_);
207             };
208 712 50 66     1827 my ($field, $default) = @values;
209 712 50       718 $package = $args->{-package} if defined $args->{-package};
  712         3552  
210 712 50       1299 die "Cannot have a default for a weakened field ($field)"
211 712 100 100     3071 if defined $default && $args->{-weak};
    100 66        
212             return if defined &{"${package}::$field"};
213             require Scalar::Util if $args->{-weak};
214             my $default_string =
215             ( ref($default) eq 'ARRAY' and not @$default )
216             ? '[]'
217             : (ref($default) eq 'HASH' and not keys %$default )
218 712         1184 ? '{}'
219 712 100       1381 : default_as_code($default);
220 160 50       475  
221 160         951 my $code = $code{sub_start};
222 160         977 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 712 100       2268 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 712         1927 }
227 712         1385 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712 50       1181 $code .= sprintf $code{return_if_get}, $field;
230 712         1306 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 100 100 31   82598 if $args->{-weak};
  31 100 100     176  
  13 100 100     49  
  6 100 100     13  
  23 100       108  
  17 100       68  
  6 100       13  
  9 100       39  
  90 100       537  
  28 100       63  
  30 100       56  
  18 100       61  
  25 100       96  
  14 100       75  
  24 100       96  
  18 100       77  
  12 100       42  
  45 100       176  
  44 50       108  
  36 100       64  
  15 100       56  
  83 100       488  
  13 100       30  
  23 100       113  
  23 100       102  
  55 100       419  
  40 100       111  
  40 100       129  
  26 100       76  
  20 100       73  
  28         90  
  21         81  
  15         78  
  9         42  
  8         50  
  14         47  
  13         44  
  7         35  
  14         70  
  5         43  
  9         28  
  34         106  
  24         85  
  13         66  
  48         121  
  85         197  
  91         195  
  43         82  
  22         99  
  23         113  
  21         75  
  28         103  
  10         97  
  65         394  
  20         68  
  45         275  
  10         41  
  22         56  
  24         104  
  12         46  
  17         62  
  44         136  
  79         384  
  8         18  
  9         59  
  46         148  
  39         83  
  37         62  
  18         72  
  22         128  
  3         8  
  1         3  
  26         129  
  11         29  
  11         28  
233 712 50       2073 $code .= sprintf $code{sub_end}, $field;
234 40     40   309  
  40         81  
  40         10191  
235 712         829 my $sub = eval $code;
  712         3623  
236 712 50       2864 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 592     592 0 29553 }
241 592         282995  
242 592         1546 sub default_as_code {
243 592         29998 require Data::Dumper;
244 592         1802 local $Data::Dumper::Sortkeys = 1;
245 592         1255 my $code = Data::Dumper::Dumper(shift);
246             $code =~ s/^\$VAR1 = //;
247             $code =~ s/;$//;
248             return $code;
249 0     0 0 0 }
250 0         0  
251 40     40   305 sub const {
  40         75  
  40         4709  
252 0     0   0 my $package = caller;
  0         0  
253 0         0 my ($args, @values) = do {
254             no warnings;
255 0         0 local *paired_arguments = sub { (qw(-package)) };
256 0 0       0 Spiffy->parse_arguments(@_);
257 40     40   302 };
  40         117  
  40         5538  
258 0 0       0 my ($field, $default) = @values;
  0         0  
259 0     0   0 $package = $args->{-package} if defined $args->{-package};
  0         0  
260 0         0 no strict 'refs';
261             return if defined &{"${package}::$field"};
262             *{"${package}::$field"} = sub { $default }
263 0     0 0 0 }
264 0         0  
265 40     40   311 sub stub {
  40         106  
  40         4478  
266 0     0   0 my $package = caller;
  0         0  
267 0         0 my ($args, @values) = do {
268             no warnings;
269 0         0 local *paired_arguments = sub { (qw(-package)) };
270 0 0       0 Spiffy->parse_arguments(@_);
271 40     40   307 };
  40         100  
  40         19989  
272 0 0       0 my ($field, $default) = @values;
  0         0  
273 0         0 $package = $args->{-package} if defined $args->{-package};
274             no strict 'refs';
275 0     0   0 return if defined &{"${package}::$field"};
276 0         0 *{"${package}::$field"} =
277             sub {
278             require Carp;
279 0         0 Carp::confess
280             "Method $field in package $package must be subclassed";
281             }
282 968     968 0 1480 }
283 968         1834  
284 968         2040 sub parse_arguments {
  3016         5806  
285 968         2265 my $class = shift;
  1680         3075  
286 968         2311 my ($args, @values) = ({}, ());
287 1480         1915 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1480 100 66     11747 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 144 50 33     726 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 160         505 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1176         4436 $args->{$elem} = shift;
298             }
299             else {
300 968 50       7513 push @values, $elem;
301             }
302             }
303 0     0 0 0 return wantarray ? ($args, @values) : $args;
304 0     0 0 0 }
305              
306             sub boolean_arguments { () }
307             sub paired_arguments { () }
308 0 0   0 0 0  
309 0 0       0 # get a unique id for any node
310 0 0       0 sub id {
311 0         0 if (not ref $_[0]) {
312             return 'undef' if not defined $_[0];
313 0         0 \$_[0] =~ /\((\w+)\)$/o or die;
314 0 0       0 return "$1-S";
315 0         0 }
316             require overload;
317             overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
318             return $1;
319             }
320              
321             #===============================================================================
322             # It's super, man.
323 40     40   330 #===============================================================================
  40         87  
  40         10370  
324             package DB;
325 0 0   0 0 0 {
326 0         0 no warnings 'redefine';
327             sub super_args {
328             my @dummy = caller(@_ ? $_[0] : 2);
329             return @DB::args;
330             }
331             }
332 0     0 0 0  
333 0         0 package Spiffy;
334 0         0 sub super {
335 0 0       0 my $method;
336             my $frame = 1;
337 0         0 while ($method = (caller($frame++))[3]) {
338 0 0       0 $method =~ s/.*::// and last;
339 0 0       0 }
340 0         0 my @args = DB::super_args($frame);
341 0         0 @_ = @_ ? ($args[0], @_) : @args;
342             my $class = ref $_[0] ? ref $_[0] : $_[0];
343 0 0 0     0 my $caller_class = caller;
344 0         0 my $seen = 0;
  0         0  
345 0         0 my @super_classes = reverse grep {
346 40     40   291 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  40         67  
  40         7862  
347 0 0       0 } reverse @{all_my_bases($class)};
348 0 0       0 for my $super_class (@super_classes) {
  0         0  
349 0 0       0 no strict 'refs';
  0         0  
  0         0  
350             next if $super_class eq $class;
351 0         0 if (defined &{"${super_class}::$method"}) {
  0         0  
352             ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
353             if $method eq 'AUTOLOAD';
354 0         0 return &{"${super_class}::$method"};
355             }
356             }
357             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 40 50   40   230 my $real_mixin_import;
367 40   50     298  
368 40         72 BEGIN {
369 40         115 require base unless defined $INC{'base.pm'};
370 40     40   289 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  40         75  
  40         3121  
371 40         374 $real_base_import = \&base::import;
372 40         2413 $real_mixin_import = \&mixin::import;
373             no warnings;
374             *base::import = \&spiffy_base_import;
375             *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 161     161 0 5643 # }
386 161         320  
387 40     40   267 sub spiffy_base_import {
  40         96  
  40         13107  
388             my @base_classes = @_;
389             shift @base_classes;
390 161 50       354 no strict 'refs';
  161 50       251  
  161         934  
391 161         35631 goto &$real_base_import
392             unless grep {
393 0           eval "require $_" unless %{"$_\::"};
394 0           $_->isa('Spiffy');
395 0 0         } @base_classes;
396 0 0         my $inheritor = caller(0);
397             for my $base_class (@base_classes) {
398             next if $inheritor->isa($base_class);
399 0           croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
400 0           "See the documentation of Spiffy.pm for details\n "
401 0           unless $base_class->isa('Spiffy');
402             $stack_frame = 1; # tell import to use different caller
403             import($base_class, '-base');
404             $stack_frame = 0;
405             }
406 0     0 0   }
407 0            
408 0           sub mixin {
409             my $self = shift;
410             my $target_class = ref($self);
411             spiffy_mixin_import($target_class, @_)
412 0     0 0   }
413 0 0          
414             sub spiffy_mixin_import {
415 0 0         my $target_class = shift;
416             $target_class = caller(0)
417 0           if $target_class eq 'mixin';
418 0           my $mixin_class = shift
419 0           or die "Nothing to mixin";
420 0           eval "require $mixin_class";
421 40     40   273 my @roles = @_;
  40         84  
  40         1728  
422 40     40   225 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  40         73  
  40         5788  
423 0           my %methods = spiffy_mixin_methods($mixin_class, @roles);
  0            
  0            
424 0           no strict 'refs';
  0            
425 0           no warnings;
426 0           @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
  0            
427             @{"$target_class\::ISA"} = ($pseudo_class);
428             for (keys %methods) {
429             *{"$pseudo_class\::$_"} = $methods{$_};
430             }
431 0     0 0   }
432 40     40   290  
  40         92  
  40         17885  
433 0           sub spiffy_mixin_methods {
434             my $mixin_class = shift;
435 0 0         no strict 'refs';
436 0           my %methods = spiffy_all_methods($mixin_class);
437 0 0         map {
  0            
438             $methods{$_}
439             ? ($_, \ &{"$methods{$_}\::$_"})
440             : ($_, \ &{"$mixin_class\::$_"})
441             } @_
442             ? (get_roles($mixin_class, @_))
443             : (keys %methods);
444 0     0 0   }
445 0            
446 0           sub get_roles {
447             my $mixin_class = shift;
448 0           my @roles = @_;
  0            
449             while (grep /^!*:/, @roles) {
450 0           @roles = map {
451 0           s/!!//g;
452             /^!:(.*)/ ? do {
453 0 0         my $m = "_role_$1";
    0          
454 0           map("!$_", $mixin_class->$m);
455 0           } :
456             /^:(.*)/ ? do {
457             my $m = "_role_$1";
458             ($mixin_class->$m);
459             } :
460 0 0 0       ($_)
461 0           } @roles;
462 0           }
463             if (@roles and $roles[0] =~ /^!/) {
464 0           my %methods = spiffy_all_methods($mixin_class);
465 0           unshift @roles, keys(%methods);
466 0           }
467 0 0         my %roles;
468             for (@roles) {
469 0           s/!!//g;
470             delete $roles{$1}, next
471 0           if /^!(.*)/;
472             $roles{$_} = 1;
473             }
474             keys %roles;
475 40     40   281 }
  40         837  
  40         7901  
476 0     0 0    
477 0 0         sub spiffy_all_methods {
478             no strict 'refs';
479 0           my $class = shift;
480             return if $class eq 'Spiffy';
481 0 0         my %methods = map {
  0            
482 0           ($_, $class)
  0            
483 0           } grep {
484 0           defined &{"$class\::$_"} and not /^_/
485 0 0         } keys %{"$class\::"};
  0            
486 0           my %super_methods;
  0            
487             %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
488             if @{"$class\::ISA"};
489             %{{%super_methods, %methods}};
490             }
491              
492              
493             # END of naughty code.
494             #===============================================================================
495 40     40   271 # Debugging support
  40         74  
  40         14564  
496 0 0   0 0   #===============================================================================
497 0           sub spiffy_dump {
498 0           no warnings;
499 0           if ($dump eq 'dumper') {
500 0           require Data::Dumper;
501             $Data::Dumper::Sortkeys = 1;
502 0           $Data::Dumper::Indent = 1;
503 0           return Data::Dumper::Dumper(@_);
504 0           }
505             require YAML;
506             $YAML::UseVersion = 0;
507             return YAML::Dump(@_) . "...\n";
508 0     0 0   }
509 0            
510             sub at_line_number {
511             my ($file_path, $line_number) = (caller(1))[1,2];
512             " at $file_path line $line_number\n";
513 0     0 0   }
514 0 0          
515             sub WWW {
516             warn spiffy_dump(@_) . at_line_number;
517             return wantarray ? @_ : $_[0];
518 0     0 0   }
519              
520             sub XXX {
521             die spiffy_dump(@_) . at_line_number;
522 0     0 0   }
523 0 0          
524             sub YYY {
525             print spiffy_dump(@_) . at_line_number;
526             return wantarray ? @_ : $_[0];
527 0     0 0   }
528 0            
529             sub ZZZ {
530             require Carp;
531             Carp::confess spiffy_dump(@_);
532             }
533              
534             1;