File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 281 435 64.6
branch 104 200 52.0
condition 37 62 59.6
subroutine 38 61 62.3
pod 8 27 29.6
total 468 785 59.6


line stmt bran cond sub pod time code
1             #line 1
2 4     4   17 package Spiffy;
  4         5  
  4         102  
3 4     4   64 use strict;
  4         10  
4 4     4   20 use 5.006001;
  4         4  
  4         105  
5 4     4   24 use warnings;
  4         5  
  4         1306  
6             use Carp;
7             require Exporter;
8             our $VERSION = '0.30';
9             our @EXPORT = ();
10             our @EXPORT_BASE = qw(field const stub super);
11             our @EXPORT_OK = (@EXPORT_BASE, qw(id WWW XXX YYY ZZZ));
12             our %EXPORT_TAGS = (XXX => [qw(WWW XXX YYY ZZZ)]);
13              
14             my $stack_frame = 0;
15             my $dump = 'yaml';
16             my $bases_map = {};
17              
18             sub WWW; sub XXX; sub YYY; sub ZZZ;
19              
20             # This line is here to convince "autouse" into believing we are autousable.
21 563 50 33 563 0 3229 sub can {
22             ($_[1] eq 'import' and caller()->isa('autouse'))
23             ? \&Exporter::import # pacify autouse's equality test
24             : $_[0]->SUPER::can($_[1]) # normal case
25             }
26              
27             # TODO
28             #
29             # Exported functions like field and super should be hidden so as not to
30             # be confused with methods that can be inherited.
31             #
32              
33 299     299 0 303 sub new {
34 299   33     810 my $class = shift;
35 299         374 $class = ref($class) || $class;
36 299         570 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 299         433 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 4     4   22 sub import {
  4         6  
  4         128  
49 4     4   18 no strict 'refs';
  4         5  
  4         4274  
50 20     20   61 no warnings;
51             my $self_package = shift;
52              
53             # XXX Using parse_arguments here might cause confusion, because the
54             # subclass's boolean_arguments and paired_arguments can conflict, causing
55 20         27 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 20     20   68 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 20         134 )
63 20     20   70 };
  20         30  
64 20         64 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66             };
67 20 50       67 return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 20 50       48  
70 20 50       40 $filter_dump = 1 if $args->{-filter_dump};
71 20 50       41 $filter_save = 1 if $args->{-filter_save};
72 20 50       45 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 20         54  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 20 50       54  
77 0 0       0 if ($args->{-XXX}) {
  0         0  
78             push @EXPORT_BASE, @{$EXPORT_TAGS{XXX}}
79             unless grep /^XXX$/, @EXPORT_BASE;
80             }
81              
82             spiffy_filter()
83 20 100 66     180 if ($args->{-selfless} or $args->{-Base}) and
      66        
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 20   33     211  
86 8         94 my $caller_package = $args->{-package} || caller($stack_frame);
87 20 100 66     107 push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 20         27  
  20         41  
90 24 50       130 for my $class (@{all_my_bases($self_package)}) {
91             next unless $class->isa('Spiffy');
92 220         131 my @export = grep {
  220         548  
93 24         146 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 24 100 66     23 ($args->{-Base} or $args->{-base})
  8         22  
96             ? @{"$class\::EXPORT_BASE"} : (),
97             );
98 180         116 my @export_ok = grep {
  180         418  
99 24         25 not defined &{"$caller_package\::$_"};
  24         63  
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 24         32 # if there is nothing to do (optimization)
  352         391  
104 24 50       79 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 24         23  
  24         64  
107 24         24 my @export_save = @{"$class\::EXPORT"};
  24         55  
108 24         27 my @export_ok_save = @{"$class\::EXPORT_OK"};
  24         58  
109 24         27 @{"$class\::EXPORT"} = @export;
  24         54  
110             @{"$class\::EXPORT_OK"} = @export_ok;
111 24         34 my @list = grep {
  8         41  
112 8 50       38 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 24         1910 } @export_list;
115 24         36 Exporter::export($class, $caller_package, @list);
  24         66  
116 24         23 @{"$class\::EXPORT"} = @export_save;
  24         3228  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 8     8 0 2206 sub spiffy_filter {
122 8         3177 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 8 50   8   18 sub {
126 8         15 return 0 if $done;
127 8         80 my ($data, $end) = ('', '');
128 3852 50       3985 while (my $status = Filter::Util::Call::filter_read()) {
129 3852 100       4296 return $status if $status < 0;
130 8         17 if (/^__(?:END|DATA)__\r?$/) {
131 8         15 $end = $_;
132             last;
133 3844         2669 }
134 3844         6323 $data .= $_;
135             $_ = '';
136 8         80 }
137 8         13 $_ = $data;
138 8         651 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 8         439 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 8         160 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 8         14 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 8 50       37 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 8         156 }
150 8 50       28 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 8 50       24 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 8         363 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 8         62 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 24     24 0 31 sub all_my_bases {
164             my $class = shift;
165              
166 24 100       84 return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 8         13  
169 4     4   22 my @bases = ($class);
  4         6  
  4         853  
170 8         9 no strict 'refs';
  8         31  
171 4         9 for my $base_class (@{"${class}::ISA"}) {
  4         18  
172             push @bases, @{all_my_bases($base_class)};
173 8         12 }
174 8         13 my $used = {};
  12         48  
175             $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
176             }
177              
178             my %code = (
179             sub_start =>
180             "sub {\n",
181             set_default =>
182             " \$_[0]->{%s} = %s\n unless exists \$_[0]->{%s};\n",
183             init =>
184             " return \$_[0]->{%s} = do { my \$self = \$_[0]; %s }\n" .
185             " unless \$#_ > 0 or defined \$_[0]->{%s};\n",
186             weak_init =>
187             " return do {\n" .
188             " \$_[0]->{%s} = do { my \$self = \$_[0]; %s };\n" .
189             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n" .
190             " \$_[0]->{%s};\n" .
191             " } unless \$#_ > 0 or defined \$_[0]->{%s};\n",
192             return_if_get =>
193             " return \$_[0]->{%s} unless \$#_ > 0;\n",
194             set =>
195             " \$_[0]->{%s} = \$_[1];\n",
196             weaken =>
197             " Scalar::Util::weaken(\$_[0]->{%s}) if ref \$_[0]->{%s};\n",
198             sub_end =>
199             " return \$_[0]->{%s};\n}\n",
200             );
201              
202 68     68 1 107 sub field {
203 68         61 my $package = caller;
204 4     4   19 my ($args, @values) = do {
  4         10  
  4         1062  
205 68     68   273 no warnings;
  68         91  
206 68     68   185 local *boolean_arguments = sub { (qw(-weak)) };
  68         90  
207 68         165 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 68         108 };
210 68 50       161 my ($field, $default) = @values;
211             $package = $args->{-package} if defined $args->{-package};
212 68 50 66     174 die "Cannot have a default for a weakened field ($field)"
213 68 50       53 if defined $default && $args->{-weak};
  68         330  
214 68 50       120 return if defined &{"${package}::$field"};
215 68 100 100     312 require Scalar::Util if $args->{-weak};
    100 66        
216             my $default_string =
217             ( ref($default) eq 'ARRAY' and not @$default )
218             ? '[]'
219             : (ref($default) eq 'HASH' and not keys %$default )
220             ? '{}'
221             : default_as_code($default);
222 68         87  
223 68 100       178 my $code = $code{sub_start};
224 16 50       38 if ($args->{-init}) {
225 16         1044 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 68 100       202 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 68         175 if defined $default;
230 68         102 $code .= sprintf $code{return_if_get}, $field;
231             $code .= sprintf $code{set}, $field;
232 68 50       120 $code .= sprintf $code{weaken}, $field, $field
233 68         94 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 68 100 100 137   6645  
  137 100 100     1091  
  65 100 66     269  
  4 100 100     5  
  12 100       76  
  35 100       94  
  23 100       91  
  11 50       31  
  114 100       1287  
  22 100       47  
  35 100       109  
  59 100       194  
  40 100       111  
  13 100       34  
  15 100       74  
  3 100       13  
  1 100       2  
  25 100       99  
  23 100       111  
  1 50       15  
  3 100       26  
  55 100       139  
  64 50       122  
  64 100       108  
  23 100       54  
  157 100       260  
  157 100       196  
  165 100       227  
  15         59  
  2         7  
  4         33  
  14         34  
  1         3  
  1         6  
  19         70  
  17         74  
  0         0  
  74         560  
  29         154  
  6         10  
  6         17  
  25         150  
  12         34  
  7         20  
  17         48  
  56         257  
  13         16  
  25         97  
  7         27  
  7         19  
  7         38  
  1         2  
  1         17  
  4         15  
  8         37  
  2         7  
  40         123  
  45         104  
  42         80  
  5         23  
  3         20  
  3         23  
  28         76  
  26         52  
  272         1646  
  15         35  
  14         16  
  42         77  
  42         148  
  1         2  
  1         3  
236 68 50       141 my $sub = eval $code;
237 4     4   18 die $@ if $@;
  4         7  
  4         716  
238 68         58 no strict 'refs';
  68         294  
239 68 50       241 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 56     56 0 2627 sub default_as_code {
244 56         18384 require Data::Dumper;
245 56         128 local $Data::Dumper::Sortkeys = 1;
246 56         2430 my $code = Data::Dumper::Dumper(shift);
247 56         109 $code =~ s/^\$VAR1 = //;
248 56         102 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 4     4   18 my ($args, @values) = do {
  4         5  
  4         320  
255 0     0   0 no warnings;
  0         0  
256 0         0 local *paired_arguments = sub { (qw(-package)) };
257             Spiffy->parse_arguments(@_);
258 0         0 };
259 0 0       0 my ($field, $default) = @values;
260 4     4   15 $package = $args->{-package} if defined $args->{-package};
  4         5  
  4         416  
261 0 0       0 no strict 'refs';
  0         0  
262 0     0   0 return if defined &{"${package}::$field"};
  0         0  
263 0         0 *{"${package}::$field"} = sub { $default }
264             }
265              
266 0     0 1 0 sub stub {
267 0         0 my $package = caller;
268 4     4   28 my ($args, @values) = do {
  4         5  
  4         357  
269 0     0   0 no warnings;
  0         0  
270 0         0 local *paired_arguments = sub { (qw(-package)) };
271             Spiffy->parse_arguments(@_);
272 0         0 };
273 0 0       0 my ($field, $default) = @values;
274 4     4   17 $package = $args->{-package} if defined $args->{-package};
  4         4  
  4         1578  
275 0 0       0 no strict 'refs';
  0         0  
276 0         0 return if defined &{"${package}::$field"};
277             *{"${package}::$field"} =
278 0     0   0 sub {
279 0         0 require Carp;
280             Carp::confess
281             "Method $field in package $package must be subclassed";
282 0         0 }
283             }
284              
285 88     88 1 103 sub parse_arguments {
286 88         162 my $class = shift;
287 88         185 my ($args, @values) = ({}, ());
  248         439  
288 88         189 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  156         233  
289 88         179 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 128         111 while (@_) {
291 128 100 66     787 my $elem = shift;
    100 66        
      66        
292 8 50 33     41 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 16         41 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 104         202 else {
301             push @values, $elem;
302             }
303 88 50       648 }
304             return wantarray ? ($args, @values) : $args;
305             }
306 0     0 1 0  
307 0     0 1 0 sub boolean_arguments { () }
308             sub paired_arguments { () }
309              
310             # get a unique id for any node
311 0 0   0 0 0 sub id {
312 0 0       0 if (not ref $_[0]) {
313 0 0       0 return 'undef' if not defined $_[0];
314 0         0 \$_[0] =~ /\((\w+)\)$/o or die;
315             return "$1-S";
316 0         0 }
317 0 0       0 require overload;
318 0         0 overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
319             return $1;
320             }
321              
322             #===============================================================================
323             # It's super, man.
324             #===============================================================================
325             package DB;
326 4     4   19 {
  4         5  
  4         776  
327             no warnings 'redefine';
328 0 0   0 0 0 sub super_args {
329 0         0 my @dummy = caller(@_ ? $_[0] : 2);
330             return @DB::args;
331             }
332             }
333              
334             package Spiffy;
335 0     0 1 0 sub super {
336 0         0 my $method;
337 0         0 my $frame = 1;
338 0 0       0 while ($method = (caller($frame++))[3]) {
339             $method =~ s/.*::// and last;
340 0         0 }
341 0 0       0 my @args = DB::super_args($frame);
342 0 0       0 @_ = @_ ? ($args[0], @_) : @args;
343 0         0 my $class = ref $_[0] ? ref $_[0] : $_[0];
344 0         0 my $caller_class = caller;
345             my $seen = 0;
346 0 0 0     0 my @super_classes = reverse grep {
347 0         0 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  0         0  
348 0         0 } reverse @{all_my_bases($class)};
349 4     4   18 for my $super_class (@super_classes) {
  4         11  
  4         641  
350 0 0       0 no strict 'refs';
351 0 0       0 next if $super_class eq $class;
  0         0  
352 0 0       0 if (defined &{"${super_class}::$method"}) {
  0         0  
  0         0  
353             ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
354 0         0 if $method eq 'AUTOLOAD';
  0         0  
355             return &{"${super_class}::$method"};
356             }
357 0         0 }
358             return;
359             }
360              
361             #===============================================================================
362             # This code deserves a spanking, because it is being very naughty.
363             # It is exchanging base.pm's import() for its own, so that people
364             # can use base.pm with Spiffy modules, without being the wiser.
365             #===============================================================================
366             my $real_base_import;
367             my $real_mixin_import;
368              
369 4 50   4   17 BEGIN {
370 4   50     25 require base unless defined $INC{'base.pm'};
371 4         7 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 4         10 $real_base_import = \&base::import;
373 4     4   24 $real_mixin_import = \&mixin::import;
  4         5  
  4         214  
374 4         19 no warnings;
375 4         170 *base::import = \&spiffy_base_import;
376             *mixin::import = \&spiffy_mixin_import;
377             }
378              
379             # my $i = 0;
380             # while (my $caller = caller($i++)) {
381             # next unless $caller eq 'base' or $caller eq 'mixin';
382             # croak <
383             # Spiffy.pm must be loaded before calling 'use base' or 'use mixin' with a
384             # Spiffy module. See the documentation of Spiffy.pm for details.
385             # END
386             # }
387              
388 50     50 0 55575 sub spiffy_base_import {
389 50         81 my @base_classes = @_;
390 4     4   16 shift @base_classes;
  4         7  
  4         880  
391             no strict 'refs';
392             goto &$real_base_import
393 50 100       110 unless grep {
  50 50       67  
  50         612  
394 50         17756 eval "require $_" unless %{"$_\::"};
395             $_->isa('Spiffy');
396 0           } @base_classes;
397 0           my $inheritor = caller(0);
398 0 0         for my $base_class (@base_classes) {
399 0 0         next if $inheritor->isa($base_class);
400             croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
401             "See the documentation of Spiffy.pm for details\n "
402 0           unless $base_class->isa('Spiffy');
403 0           $stack_frame = 1; # tell import to use different caller
404 0           import($base_class, '-base');
405             $stack_frame = 0;
406             }
407             }
408              
409 0     0 1   sub mixin {
410 0           my $self = shift;
411 0           my $target_class = ref($self);
412             spiffy_mixin_import($target_class, @_)
413             }
414              
415 0     0 0   sub spiffy_mixin_import {
416 0 0         my $target_class = shift;
417             $target_class = caller(0)
418 0 0         if $target_class eq 'mixin';
419             my $mixin_class = shift
420 0           or die "Nothing to mixin";
421 0           eval "require $mixin_class";
422 0           my @roles = @_;
423 0           my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
424 4     4   19 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  4         5  
  4         138  
425 4     4   15 no strict 'refs';
  4         4  
  4         572  
426 0           no warnings;
  0            
  0            
427 0           @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
  0            
428 0           @{"$target_class\::ISA"} = ($pseudo_class);
429 0           for (keys %methods) {
  0            
430             *{"$pseudo_class\::$_"} = $methods{$_};
431             }
432             }
433              
434 0     0 0   sub spiffy_mixin_methods {
435 4     4   18 my $mixin_class = shift;
  4         5  
  4         1331  
436 0           no strict 'refs';
437             my %methods = spiffy_all_methods($mixin_class);
438 0 0         map {
439 0           $methods{$_}
440 0 0         ? ($_, \ &{"$methods{$_}\::$_"})
  0            
441             : ($_, \ &{"$mixin_class\::$_"})
442             } @_
443             ? (get_roles($mixin_class, @_))
444             : (keys %methods);
445             }
446              
447 0     0 0   sub get_roles {
448 0           my $mixin_class = shift;
449 0           my @roles = @_;
450             while (grep /^!*:/, @roles) {
451 0           @roles = map {
  0            
452             s/!!//g;
453 0           /^!:(.*)/ ? do {
454 0           my $m = "_role_$1";
455             map("!$_", $mixin_class->$m);
456 0 0         } :
    0          
457 0           /^:(.*)/ ? do {
458 0           my $m = "_role_$1";
459             ($mixin_class->$m);
460             } :
461             ($_)
462             } @roles;
463 0 0 0       }
464 0           if (@roles and $roles[0] =~ /^!/) {
465 0           my %methods = spiffy_all_methods($mixin_class);
466             unshift @roles, keys(%methods);
467 0           }
468 0           my %roles;
469 0           for (@roles) {
470 0 0         s/!!//g;
471             delete $roles{$1}, next
472 0           if /^!(.*)/;
473             $roles{$_} = 1;
474 0           }
475             keys %roles;
476             }
477              
478 4     4   16 sub spiffy_all_methods {
  4         4  
  4         537  
479 0     0 0   no strict 'refs';
480 0 0         my $class = shift;
481             return if $class eq 'Spiffy';
482 0           my %methods = map {
483             ($_, $class)
484 0 0         } grep {
  0            
485 0           defined &{"$class\::$_"} and not /^_/
  0            
486 0           } keys %{"$class\::"};
487 0           my %super_methods;
488 0 0         %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
  0            
489 0           if @{"$class\::ISA"};
  0            
490             %{{%super_methods, %methods}};
491             }
492              
493              
494             # END of naughty code.
495             #===============================================================================
496             # Debugging support
497             #===============================================================================
498 4     4   15 sub spiffy_dump {
  4         4  
  4         957  
499 0 0   0 0   no warnings;
500 0           if ($dump eq 'dumper') {
501 0           require Data::Dumper;
502 0           $Data::Dumper::Sortkeys = 1;
503 0           $Data::Dumper::Indent = 1;
504             return Data::Dumper::Dumper(@_);
505 0           }
506 0           require YAML;
507 0           $YAML::UseVersion = 0;
508             return YAML::Dump(@_) . "...\n";
509             }
510              
511 0     0 0   sub at_line_number {
512 0           my ($file_path, $line_number) = (caller(1))[1,2];
513             " at $file_path line $line_number\n";
514             }
515              
516 0     0 0   sub WWW {
517 0 0         warn spiffy_dump(@_) . at_line_number;
518             return wantarray ? @_ : $_[0];
519             }
520              
521 0     0 0   sub XXX {
522             die spiffy_dump(@_) . at_line_number;
523             }
524              
525 0     0 0   sub YYY {
526 0 0         print spiffy_dump(@_) . at_line_number;
527             return wantarray ? @_ : $_[0];
528             }
529              
530 0     0 0   sub ZZZ {
531 0           require Carp;
532             Carp::confess spiffy_dump(@_);
533             }
534              
535             1;
536              
537             __END__