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   198 use strict; use warnings;
  40     40   43  
  40         1267  
  40         177  
  40         66  
  40         2400  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   177 use Carp;
  40         94  
  40         11755  
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 1340 ($_[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 232 my $class = shift;
33 223   33     517 $class = ref($class) || $class;
34 223         279 my $self = bless {}, $class;
35 223         404 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         398 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   254 no strict 'refs';
  40         112  
  40         1401  
48 40     40   148 no warnings;
  40         44  
  40         51997  
49 256     256   639 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         291 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   645 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1568 };
62 256     256   723 local *paired_arguments = sub { qw(-package) };
  256         308  
63 256         895 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       630 if $args->{-mixin};
67              
68 256 50       375 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       383 $filter_save = 1 if $args->{-filter_save};
70 256 50       385 $dump = 'yaml' if $args->{-yaml};
71 256 50       375 $dump = 'dumper' if $args->{-dumper};
72              
73 256         624 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       402 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     1772 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2087 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1475 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     617 if $args->{-Base} or $args->{-base};
87              
88 256         279 for my $class (@{all_my_bases($self_package)}) {
  256         439  
89 408 50       1642 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3242 not defined &{"$caller_package\::$_"};
  4776         9421  
92 408         1516 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     354 ? @{"$class\::EXPORT_BASE"} : (),
  216         462  
95             );
96             my @export_ok = grep {
97 2304         1507 not defined &{"$caller_package\::$_"};
  2304         4153  
98 408         364 } @{"$class\::EXPORT_OK"};
  408         776  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         511 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7423  
103 408 100       987 next unless keys %exportable;
104              
105 376         296 my @export_save = @{"$class\::EXPORT"};
  376         981  
106 376         330 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         766  
107 376         390 @{"$class\::EXPORT"} = @export;
  376         1155  
108 376         409 @{"$class\::EXPORT_OK"} = @export_ok;
  376         751  
109             my @list = grep {
110 376         498 (my $v = $_) =~ s/^[\!\:]//;
  288         633  
111 288 100       547 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         627  
112             } @export_list;
113 376         27093 Exporter::export($class, $caller_package, @list);
114 376         675 @{"$class\::EXPORT"} = @export_save;
  376         1094  
115 376         348 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         29750  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 18421 require Filter::Util::Call;
121 112         31013 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7384 return 0 if $done;
125 112         203 my ($data, $end) = ('', '');
126 112         996 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       37804 return $status if $status < 0;
128 41896 50       40598 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         31393 $data .= $_;
133 41896         53611 $_ = '';
134             }
135 112         1125 $_ = $data;
136 112         157 my @my_subs;
137 112         7616 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6041 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         1916 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         529 my $preclare = '';
144 112 50       365 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         317 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       246 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       202 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         4681 $done = 1;
152             }
153 112         1036 );
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 359 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1010 if defined $bases_map->{$class};
166              
167 152         260 my @bases = ($class);
168 40     40   325 no strict 'refs';
  40         48  
  40         6790  
169 152         181 for my $base_class (@{"${class}::ISA"}) {
  152         518  
170 112         130 push @bases, @{all_my_bases($base_class)};
  112         297  
171             }
172 152         194 my $used = {};
173 152         215 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1087  
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 1069 my $package = caller;
202 712         635 my ($args, @values) = do {
203 40     40   179 no warnings;
  40         41  
  40         11450  
204 712     712   2686 local *boolean_arguments = sub { (qw(-weak)) };
  712         1064  
205 712     712   1787 local *paired_arguments = sub { (qw(-package -init)) };
  712         940  
206 712         1581 Spiffy->parse_arguments(@_);
207             };
208 712         1184 my ($field, $default) = @values;
209 712 50       1118 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1394 if defined $default && $args->{-weak};
212 712 50       534 return if defined &{"${package}::$field"};
  712         2855  
213 712 50       955 require Scalar::Util if $args->{-weak};
214 712 100 100     2577 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         952 my $code = $code{sub_start};
222 712 100       1207 if ($args->{-init}) {
223 160 50       324 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         751 my @count = ($fragment =~ /(%s)/g);
225 160         614 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1563 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1153 $code .= sprintf $code{return_if_get}, $field;
230 712         960 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       943 if $args->{-weak};
233 712         941 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 14   72544 my $sub = eval $code;
  14 100 100     39  
  9 100 100     25  
  8 100 100     15  
  10 100       34  
  18 100       65  
  11 100       25  
  28 100       61  
  27 100       90  
  9 100       25  
  19 100       35  
  71 100       379  
  25 100       67  
  17 100       29  
  30 100       76  
  22 100       59  
  11 100       19  
  58 100       79  
  54 100       67  
  52 100       55  
  84 100       305  
  18 100       40  
  17 100       36  
  4 100       19  
  5 100       18  
  10 100       21  
  72 100       293  
  13 100       25  
  71 100       231  
  22 100       54  
  22         58  
  20         33  
  28         85  
  20         71  
  7         13  
  9         23  
  45         96  
  34         107  
  8         17  
  18         49  
  33         124  
  52         165  
  42         68  
  39         119  
  69         134  
  41         82  
  25         37  
  9         21  
  11         27  
  4         6  
  8         16  
  49         185  
  21         50  
  14         37  
  20         49  
  28         70  
  12         33  
  12         27  
  12         35  
  10         18  
  46         106  
  38         52  
  53         63  
  39         67  
  31         56  
  24         90  
  22         53  
  11         15  
  20         39  
  24         65  
  9         15  
  8         15  
  71         206  
  27         38  
  27         29  
236 712 50       1454 die $@ if $@;
237 40     40   289 no strict 'refs';
  40         57  
  40         9335  
238 712         634 *{"${package}::$field"} = $sub;
  712         3353  
239 712 50       2237 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 23855 require Data::Dumper;
244 592         287927 local $Data::Dumper::Sortkeys = 1;
245 592         1224 my $code = Data::Dumper::Dumper(shift);
246 592         24836 $code =~ s/^\$VAR1 = //;
247 592         1567 $code =~ s/;$//;
248 592         985 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   221 no warnings;
  40         51  
  40         3747  
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   160 no strict 'refs';
  40         53  
  40         5168  
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   183 no warnings;
  40         53  
  40         3963  
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   209 no strict 'refs';
  40         61  
  40         21546  
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 1053 my $class = shift;
286 968         1427 my ($args, @values) = ({}, ());
287 968         1637 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4542  
288 968         1833 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2430  
289 968         1954 while (@_) {
290 1480         1470 my $elem = shift;
291 1480 100 66     6542 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     567 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         383 $args->{$elem} = shift;
298             }
299             else {
300 1176         2137 push @values, $elem;
301             }
302             }
303 968 50       6328 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   243 no warnings 'redefine';
  40         46  
  40         7089  
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   176 no strict 'refs';
  40         44  
  40         6282  
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   176 require base unless defined $INC{'base.pm'};
370 40   50     214 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         53 $real_base_import = \&base::import;
372 40         114 $real_mixin_import = \&mixin::import;
373 40     40   217 no warnings;
  40         57  
  40         2024  
374 40         252 *base::import = \&spiffy_base_import;
375 40         1582 *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 291 my @base_classes = @_;
389 80         100 shift @base_classes;
390 40     40   148 no strict 'refs';
  40         59  
  40         10386  
391             goto &$real_base_import
392             unless grep {
393 80 50       166 eval "require $_" unless %{"$_\::"};
  80 50       972  
  80         647  
394 80         14003 $_->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   237 no strict 'refs';
  40         64  
  40         1197  
425 40     40   160 no warnings;
  40         55  
  40         5699  
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   214 no strict 'refs';
  40         45  
  40         16536  
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   264 no strict 'refs';
  40         72  
  40         7100  
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   198 no warnings;
  40         47  
  40         11652  
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;