File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 111 206 53.8
condition 39 62 62.9
subroutine 37 60 61.6
pod 8 27 29.6
total 479 793 60.4


line stmt bran cond sub pod time code
1 40     40   298 use strict; use warnings;
  40     40   84  
  40         1590  
  40         225  
  40         89  
  40         3468  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   275 use Carp;
  40         113  
  40         23393  
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 2184 ($_[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 362 my $class = shift;
33 223   33     797 $class = ref($class) || $class;
34 223         479 my $self = bless {}, $class;
35 223         507 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         640 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   370 no strict 'refs';
  40         107  
  40         1932  
48 40     40   210 no warnings;
  40         85  
  40         73607  
49 256     256   3998 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         385 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   982 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         2215 };
62 256     256   1084 local *paired_arguments = sub { qw(-package) };
  256         445  
63 256         1267 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       922 if $args->{-mixin};
67              
68 256 50       553 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       550 $filter_save = 1 if $args->{-filter_save};
70 256 50       627 $dump = 'yaml' if $args->{-yaml};
71 256 50       586 $dump = 'dumper' if $args->{-dumper};
72              
73 256         949 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       574 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     2520 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2798 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1953 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     977 if $args->{-Base} or $args->{-base};
87              
88 256         377 for my $class (@{all_my_bases($self_package)}) {
  256         588  
89 408 50       2295 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         4849 not defined &{"$caller_package\::$_"};
  4776         14730  
92 408         2173 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     635 ? @{"$class\::EXPORT_BASE"} : (),
  216         765  
95             );
96             my @export_ok = grep {
97 2304         2998 not defined &{"$caller_package\::$_"};
  2304         6215  
98 408         595 } @{"$class\::EXPORT_OK"};
  408         1194  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         749 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         10637  
103 408 100       1356 next unless keys %exportable;
104              
105 376         454 my @export_save = @{"$class\::EXPORT"};
  376         1533  
106 376         478 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         1061  
107 376         571 @{"$class\::EXPORT"} = @export;
  376         4765  
108 376         574 @{"$class\::EXPORT_OK"} = @export_ok;
  376         1143  
109             my @list = grep {
110 376         696 (my $v = $_) =~ s/^[\!\:]//;
  288         2571  
111 288 100       1612 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         907  
112             } @export_list;
113 376         39472 Exporter::export($class, $caller_package, @list);
114 376         1015 @{"$class\::EXPORT"} = @export_save;
  376         1535  
115 376         530 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         42410  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 25534 require Filter::Util::Call;
121 112         46386 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   10445 return 0 if $done;
125 112         287 my ($data, $end) = ('', '');
126 112         1316 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       64985 return $status if $status < 0;
128 41896 50       67647 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         47410 $data .= $_;
133 41896         88497 $_ = '';
134             }
135 112         1558 $_ = $data;
136 112         234 my @my_subs;
137 112         10415 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         8587 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         3361 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         803 my $preclare = '';
144 112 50       627 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         423 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       273 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       319 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5969 $done = 1;
152             }
153 112         1381 );
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 519 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1362 if defined $bases_map->{$class};
166              
167 152         359 my @bases = ($class);
168 40     40   345 no strict 'refs';
  40         69  
  40         9610  
169 152         213 for my $base_class (@{"${class}::ISA"}) {
  152         785  
170 112         192 push @bases, @{all_my_bases($base_class)};
  112         423  
171             }
172 152         257 my $used = {};
173 152         330 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1426  
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 1 1580 my $package = caller;
202 712         960 my ($args, @values) = do {
203 40     40   269 no warnings;
  40         65  
  40         15677  
204 712     712   3777 local *boolean_arguments = sub { (qw(-weak)) };
  712         1491  
205 712     712   2522 local *paired_arguments = sub { (qw(-package -init)) };
  712         1291  
206 712         2753 Spiffy->parse_arguments(@_);
207             };
208 712         1723 my ($field, $default) = @values;
209 712 50       1545 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1928 if defined $default && $args->{-weak};
212 712 50       851 return if defined &{"${package}::$field"};
  712         4351  
213 712 50       1434 require Scalar::Util if $args->{-weak};
214 712 100 100     3551 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         1442 my $code = $code{sub_start};
222 712 100       1619 if ($args->{-init}) {
223 160 50       423 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         930 my @count = ($fragment =~ /(%s)/g);
225 160         850 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       2068 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1612 $code .= sprintf $code{return_if_get}, $field;
230 712         5463 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       1305 if $args->{-weak};
233 712         1389 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 21   102608 my $sub = eval $code;
  21 100 100     108  
  6 100 100     8  
  6 100 100     14  
  36 100       168  
  27 100       118  
  13 100       44  
  16 100       72  
  10 100       24  
  10 100       50  
  21 100       100  
  21 100       59  
  62 100       180  
  61 100       184  
  130 100       633  
  22 100       81  
  21 100       63  
  27 100       84  
  26 100       99  
  13 100       39  
  28 100       80  
  71 100       189  
  74 100       155  
  61 100       126  
  16 50       73  
  5 100       22  
  9 100       25  
  33 100       147  
  18 100       62  
  17 100       132  
  19         75  
  23         101  
  12         41  
  7         17  
  36         104  
  34         154  
  4         22  
  3         10  
  117         801  
  43         76  
  51         103  
  69         316  
  14         58  
  4         11  
  11         44  
  36         139  
  68         265  
  18         59  
  24         93  
  22         77  
  14         62  
  15         46  
  15         187  
  5         20  
  10         31  
  13         64  
  9         23  
  7         41  
  24         50  
  39         73  
  45         114  
  15         41  
  14         52  
  15         45  
  17         48  
  28         92  
  17         58  
  3         6  
  16         49  
  30         142  
  5         12  
  17         68  
  80         528  
  16         43  
  16         31  
236 712 50       2165 die $@ if $@;
237 40     40   331 no strict 'refs';
  40         68  
  40         12085  
238 712         916 *{"${package}::$field"} = $sub;
  712         4713  
239 712 50       3207 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 31466 require Data::Dumper;
244 592         407834 local $Data::Dumper::Sortkeys = 1;
245 592         1797 my $code = Data::Dumper::Dumper(shift);
246 592         35552 $code =~ s/^\$VAR1 = //;
247 592         2168 $code =~ s/;$//;
248 592         1416 return $code;
249             }
250              
251             sub const {
252 0     0 1 0 my $package = caller;
253 0         0 my ($args, @values) = do {
254 40     40   390 no warnings;
  40         64  
  40         5069  
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   229 no strict 'refs';
  40         79  
  40         7023  
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 1 0 my $package = caller;
267 0         0 my ($args, @values) = do {
268 40     40   274 no warnings;
  40         85  
  40         9422  
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   243 no strict 'refs';
  40         109  
  40         29986  
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 1 3644 my $class = shift;
286 968         1944 my ($args, @values) = ({}, ());
287 968         2438 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         6225  
288 968         2567 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         3413  
289 968         2794 while (@_) {
290 1480         2153 my $elem = shift;
291 1480 100 66     10725 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     776 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         601 $args->{$elem} = shift;
298             }
299             else {
300 1176         3092 push @values, $elem;
301             }
302             }
303 968 50       8866 return wantarray ? ($args, @values) : $args;
304             }
305              
306 0     0 1 0 sub boolean_arguments { () }
307 0     0 1 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   298 no warnings 'redefine';
  40         61  
  40         19827  
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 1 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   258 no strict 'refs';
  40         72  
  40         9515  
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   227 require base unless defined $INC{'base.pm'};
370 40   50     284 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         79 $real_base_import = \&base::import;
372 40         149 $real_mixin_import = \&mixin::import;
373 40     40   314 no warnings;
  40         91  
  40         5351  
374 40         343 *base::import = \&spiffy_base_import;
375 40         2137 *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 320 my @base_classes = @_;
389 80         139 shift @base_classes;
390 40     40   240 no strict 'refs';
  40         99  
  40         14857  
391             goto &$real_base_import
392             unless grep {
393 80 50       243 eval "require $_" unless %{"$_\::"};
  80 50       1158  
  80         1115  
394 80         17569 $_->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 1   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   293 no strict 'refs';
  40         91  
  40         1621  
425 40     40   218 no warnings;
  40         94  
  40         8649  
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   267 no strict 'refs';
  40         60  
  40         22740  
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   336 no strict 'refs';
  40         77  
  40         9735  
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   275 no warnings;
  40         87  
  40         16940  
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;