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   357 use strict; use warnings;
  40     40   57  
  40         1461  
  40         176  
  40         80  
  40         2949  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   231 use Carp;
  40         83  
  40         13827  
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 1428 ($_[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 270 my $class = shift;
33 223   33     512 $class = ref($class) || $class;
34 223         289 my $self = bless {}, $class;
35 223         396 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         471 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   315 no strict 'refs';
  40         90  
  40         1797  
48 40     40   199 no warnings;
  40         57  
  40         60626  
49 256     256   685 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         340 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   756 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1835 };
62 256     256   821 local *paired_arguments = sub { qw(-package) };
  256         349  
63 256         1093 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       654 if $args->{-mixin};
67              
68 256 50       525 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       437 $filter_save = 1 if $args->{-filter_save};
70 256 50       432 $dump = 'yaml' if $args->{-yaml};
71 256 50       434 $dump = 'dumper' if $args->{-dumper};
72              
73 256         727 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       450 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     2667 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2252 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1597 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     776 if $args->{-Base} or $args->{-base};
87              
88 256         313 for my $class (@{all_my_bases($self_package)}) {
  256         499  
89 408 50       1869 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3705 not defined &{"$caller_package\::$_"};
  4776         11189  
92 408         1690 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     433 ? @{"$class\::EXPORT_BASE"} : (),
  216         616  
95             );
96             my @export_ok = grep {
97 2304         1799 not defined &{"$caller_package\::$_"};
  2304         4658  
98 408         461 } @{"$class\::EXPORT_OK"};
  408         921  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         587 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         8336  
103 408 100       1046 next unless keys %exportable;
104              
105 376         350 my @export_save = @{"$class\::EXPORT"};
  376         1124  
106 376         402 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         837  
107 376         418 @{"$class\::EXPORT"} = @export;
  376         1864  
108 376         398 @{"$class\::EXPORT_OK"} = @export_ok;
  376         789  
109             my @list = grep {
110 376         515 (my $v = $_) =~ s/^[\!\:]//;
  288         744  
111 288 100       605 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         647  
112             } @export_list;
113 376         32153 Exporter::export($class, $caller_package, @list);
114 376         857 @{"$class\::EXPORT"} = @export_save;
  376         1194  
115 376         399 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         33070  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 22386 require Filter::Util::Call;
121 112         37207 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   8367 return 0 if $done;
125 112         229 my ($data, $end) = ('', '');
126 112         1312 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       44119 return $status if $status < 0;
128 41896 50       47924 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         36487 $data .= $_;
133 41896         64433 $_ = '';
134             }
135 112         1448 $_ = $data;
136 112         173 my @my_subs;
137 112         8333 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         7325 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2139 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         649 my $preclare = '';
144 112 50       502 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         400 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       317 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       247 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5425 $done = 1;
152             }
153 112         1172 );
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 445 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1051 if defined $bases_map->{$class};
166              
167 152         326 my @bases = ($class);
168 40     40   305 no strict 'refs';
  40         63  
  40         8382  
169 152         175 for my $base_class (@{"${class}::ISA"}) {
  152         558  
170 112         192 push @bases, @{all_my_bases($base_class)};
  112         333  
171             }
172 152         219 my $used = {};
173 152         236 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1213  
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 1216 my $package = caller;
202 712         723 my ($args, @values) = do {
203 40     40   228 no warnings;
  40         54  
  40         13206  
204 712     712   3040 local *boolean_arguments = sub { (qw(-weak)) };
  712         1165  
205 712     712   2004 local *paired_arguments = sub { (qw(-package -init)) };
  712         1044  
206 712         1874 Spiffy->parse_arguments(@_);
207             };
208 712         1391 my ($field, $default) = @values;
209 712 50       1268 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1612 if defined $default && $args->{-weak};
212 712 50       620 return if defined &{"${package}::$field"};
  712         3355  
213 712 50       1055 require Scalar::Util if $args->{-weak};
214 712 100 100     2927 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         1137 my $code = $code{sub_start};
222 712 100       1273 if ($args->{-init}) {
223 160 50       373 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         856 my @count = ($fragment =~ /(%s)/g);
225 160         728 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1754 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1486 $code .= sprintf $code{return_if_get}, $field;
230 712         1098 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       994 if $args->{-weak};
233 712         1066 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 122   78529 my $sub = eval $code;
  122 100 100     330  
  66 100 100     111  
  48 100 100     69  
  6 100       21  
  17 100       73  
  5 100       11  
  7 100       18  
  18 100       51  
  7 100       17  
  3 100       5  
  19 100       66  
  8 100       21  
  28 100       102  
  36 100       90  
  26 100       40  
  17 100       37  
  30 100       77  
  10 100       21  
  10 100       29  
  21 100       59  
  26 100       80  
  12 100       122  
  17 100       44  
  124 100       512  
  27 100       97  
  14 100       27  
  19 100       64  
  13 100       47  
  12 100       31  
  13         29  
  9         28  
  24         62  
  24         56  
  17         32  
  25         41  
  52         203  
  20         30  
  29         70  
  19         31  
  27         59  
  29         80  
  15         45  
  12         26  
  38         111  
  16         25  
  17         29  
  38         99  
  36         111  
  24         64  
  12         26  
  21         54  
  20         58  
  37         83  
  48         75  
  48         71  
  92         332  
  10         30  
  10         20  
  29         98  
  25         67  
  8         21  
  11         35  
  41         64  
  41         66  
  46         88  
  9         45  
  3         7  
  55         259  
  24         68  
  12         18  
  32         74  
  53         127  
  21         37  
  21         23  
236 712 50       1709 die $@ if $@;
237 40     40   320 no strict 'refs';
  40         93  
  40         10838  
238 712         764 *{"${package}::$field"} = $sub;
  712         3859  
239 712 50       2618 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 27531 require Data::Dumper;
244 592         327051 local $Data::Dumper::Sortkeys = 1;
245 592         1359 my $code = Data::Dumper::Dumper(shift);
246 592         28340 $code =~ s/^\$VAR1 = //;
247 592         1849 $code =~ s/;$//;
248 592         1205 return $code;
249             }
250              
251             sub const {
252 0     0 0 0 my $package = caller;
253 0         0 my ($args, @values) = do {
254 40     40   258 no warnings;
  40         63  
  40         4289  
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   202 no strict 'refs';
  40         52  
  40         5998  
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   255 no warnings;
  40         68  
  40         4539  
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   208 no strict 'refs';
  40         83  
  40         25909  
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 1163 my $class = shift;
286 968         1661 my ($args, @values) = ({}, ());
287 968         1825 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         5130  
288 968         2051 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2748  
289 968         2187 while (@_) {
290 1480         1708 my $elem = shift;
291 1480 100 66     7354 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     648 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         424 $args->{$elem} = shift;
298             }
299             else {
300 1176         2312 push @values, $elem;
301             }
302             }
303 968 50       7054 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   307 no warnings 'redefine';
  40         52  
  40         8515  
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   217 no strict 'refs';
  40         52  
  40         7602  
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   200 require base unless defined $INC{'base.pm'};
370 40   50     273 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         83 $real_base_import = \&base::import;
372 40         102 $real_mixin_import = \&mixin::import;
373 40     40   296 no warnings;
  40         70  
  40         2264  
374 40         328 *base::import = \&spiffy_base_import;
375 40         1955 *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 257 my @base_classes = @_;
389 80         108 shift @base_classes;
390 40     40   231 no strict 'refs';
  40         79  
  40         12992  
391             goto &$real_base_import
392             unless grep {
393 80 50       159 eval "require $_" unless %{"$_\::"};
  80 50       1206  
  80         1071  
394 80         14934 $_->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   252 no strict 'refs';
  40         75  
  40         1525  
425 40     40   189 no warnings;
  40         88  
  40         6469  
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   224 no strict 'refs';
  40         58  
  40         19172  
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   271 no strict 'refs';
  40         93  
  40         8544  
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   237 no warnings;
  40         61  
  40         14655  
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;