File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 112 204 54.9
condition 39 62 62.9
subroutine 37 60 61.6
pod 0 27 0.0
total 472 791 59.6


line stmt bran cond sub pod time code
1 40     40   482 use strict; use warnings;
  40     40   69  
  40         1058  
  40         188  
  40         68  
  40         1851  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   211 use Carp;
  40         62  
  40         12930  
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 1905 ($_[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 398 my $class = shift;
33 223   33     673 $class = ref($class) || $class;
34 223         422 my $self = bless {}, $class;
35 223         503 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         559 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         93  
  40         1459  
48 40     40   231 no warnings;
  40         99  
  40         49049  
49 256     256   761 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         350 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   807 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1786 };
62 256     256   927 local *paired_arguments = sub { qw(-package) };
  256         398  
63 256         1142 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       807 if $args->{-mixin};
67              
68 256 50       518 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       500 $filter_save = 1 if $args->{-filter_save};
70 256 50       520 $dump = 'yaml' if $args->{-yaml};
71 256 50       520 $dump = 'dumper' if $args->{-dumper};
72              
73 256         712 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       564 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     2430 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2613 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1606 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     923 if $args->{-Base} or $args->{-base};
87              
88 256         388 for my $class (@{all_my_bases($self_package)}) {
  256         575  
89 408 50       2044 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         4760 not defined &{"$caller_package\::$_"};
  4776         13066  
92 408         2053 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     555 ? @{"$class\::EXPORT_BASE"} : (),
  216         623  
95             );
96             my @export_ok = grep {
97 2304         3947 not defined &{"$caller_package\::$_"};
  2304         6264  
98 408         638 } @{"$class\::EXPORT_OK"};
  408         1135  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         2433 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         11201  
103 408 100       1368 next unless keys %exportable;
104              
105 376         490 my @export_save = @{"$class\::EXPORT"};
  376         1224  
106 376         2288 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         2708  
107 376         2199 @{"$class\::EXPORT"} = @export;
  376         2889  
108 376         2177 @{"$class\::EXPORT_OK"} = @export_ok;
  376         2924  
109             my @list = grep {
110 376         595 (my $v = $_) =~ s/^[\!\:]//;
  288         1465  
111 288 100       805 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         825  
112             } @export_list;
113 376         38555 Exporter::export($class, $caller_package, @list);
114 376         1043 @{"$class\::EXPORT"} = @export_save;
  376         4961  
115 376         552 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         38754  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 22549 require Filter::Util::Call;
121 112         36129 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   11029 return 0 if $done;
125 112         1959 my ($data, $end) = ('', '');
126 112         3119 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       52138 return $status if $status < 0;
128 41896 50       54624 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         43382 $data .= $_;
133 41896         74534 $_ = '';
134             }
135 112         1588 $_ = $data;
136 112         226 my @my_subs;
137 112         9205 s[^(sub\s+\w+\s+\{)(.*\n)]
138 112         6940 [${1}my \$self = shift;$2]gm;
139 112         2182 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 112         242 [${1}${2}]gm;
141 112 50       3686 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         2286 $preclare = join ',', map "\$$_", @my_subs;
146 112 50       5305 $preclare = "my($preclare);";
  0         0  
  0         0  
147 112 50       3492 }
  0         0  
  0         0  
148 112         4691 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 112         970 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 495 }
160              
161             sub all_my_bases {
162 368 100       1154 my $class = shift;
163              
164 152         309 return $bases_map->{$class}
165 40     40   327 if defined $bases_map->{$class};
  40         106  
  40         10199  
166 152         196  
  152         775  
167 112         189 my @bases = ($class);
  112         370  
168             no strict 'refs';
169 152         329 for my $base_class (@{"${class}::ISA"}) {
170 152         303 push @bases, @{all_my_bases($base_class)};
  304         1263  
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 1360 );
199 712         828  
200 40     40   302 sub field {
  40         115  
  40         16023  
201 712     712   3167 my $package = caller;
  712         1207  
202 712     712   2649 my ($args, @values) = do {
  712         1231  
203 712         2080 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 712         1594 local *paired_arguments = sub { (qw(-package -init)) };
206 712 50       1420 Spiffy->parse_arguments(@_);
207             };
208 712 50 66     1756 my ($field, $default) = @values;
209 712 50       723 $package = $args->{-package} if defined $args->{-package};
  712         3896  
210 712 50       1335 die "Cannot have a default for a weakened field ($field)"
211 712 100 100     3115 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         1221 ? '{}'
219 712 100       1425 : default_as_code($default);
220 160 50       518  
221 160         963 my $code = $code{sub_start};
222 160         1085 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 712 100       2267 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 712         2049 }
227 712         1379 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712 50       1280 $code .= sprintf $code{return_if_get}, $field;
230 712         1353 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 100 100 62   83540 if $args->{-weak};
  62 100 100     390  
  17 100 100     55  
  11 100 100     29  
  36 100       148  
  43 100       211  
  5 100       15  
  12 100       38  
  56 100       292  
  22 100       88  
  10 100       31  
  9 100       48  
  25 100       76  
  24 100       54  
  46 100       147  
  14 100       65  
  6 100       24  
  26 100       116  
  24 100       113  
  27 100       109  
  36 100       117  
  35 100       145  
  10 100       39  
  11 100       35  
  17 100       60  
  31 100       110  
  26 100       88  
  19 100       73  
  14 100       64  
  16 100       53  
  17         60  
  4         18  
  17         78  
  17         66  
  5         20  
  50         161  
  54         125  
  56         142  
  20         64  
  26         118  
  8         32  
  7         21  
  45         182  
  31         75  
  34         68  
  31         130  
  20         68  
  79         480  
  20         63  
  12         24  
  22         86  
  20         87  
  3         8  
  54         297  
  81         451  
  44         126  
  28         112  
  7         30  
  10         24  
  48         142  
  46         195  
  18         42  
  39         136  
  115         571  
  43         84  
  31         143  
  14         66  
  12         28  
  18         83  
  19         82  
  9         22  
  2         8  
  31         139  
  12         35  
  12         28  
233 712 50       1959 $code .= sprintf $code{sub_end}, $field;
234 40     40   303  
  40         79  
  40         10439  
235 712         879 my $sub = eval $code;
  712         3543  
236 712 50       2895 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 592     592 0 28995 }
241 592         284323  
242 592         1491 sub default_as_code {
243 592         29665 require Data::Dumper;
244 592         1794 local $Data::Dumper::Sortkeys = 1;
245 592         1273 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   296 sub const {
  40         79  
  40         4839  
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   313 };
  40         118  
  40         5643  
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   345 sub stub {
  40         108  
  40         4590  
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   332 };
  40         84  
  40         20774  
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 1454 }
283 968         1792  
284 968         2004 sub parse_arguments {
  3016         5596  
285 968         2256 my $class = shift;
  1680         3075  
286 968         2303 my ($args, @values) = ({}, ());
287 1480         1935 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1480 100 66     13144 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 144 50 33     751 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 160         513 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1176         2731 $args->{$elem} = shift;
298             }
299             else {
300 968 50       7490 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   329 #===============================================================================
  40         152  
  40         10673  
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   280 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  40         77  
  40         7738  
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   236 my $real_mixin_import;
367 40   50     307  
368 40         73 BEGIN {
369 40         130 require base unless defined $INC{'base.pm'};
370 40     40   301 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  40         76  
  40         3083  
371 40         385 $real_base_import = \&base::import;
372 40         2294 $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 5674 # }
386 161         320  
387 40     40   302 sub spiffy_base_import {
  40         78  
  40         13511  
388             my @base_classes = @_;
389             shift @base_classes;
390 161 50       421 no strict 'refs';
  161 50       263  
  161         1264  
391 161         36247 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   285 my @roles = @_;
  40         80  
  40         1728  
422 40     40   272 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  40         70  
  40         5881  
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         85  
  40         18062  
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   282 }
  40         854  
  40         8094  
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   285 # Debugging support
  40         69  
  40         14761  
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;