File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 283 440 64.3
branch 105 204 51.4
condition 36 62 58.0
subroutine 38 61 62.3
pod 0 27 0.0
total 462 794 58.1


line stmt bran cond sub pod time code
1             #line 1
2 4     4   21 package Spiffy;
  4         7  
  4         126  
3 4     4   59 use strict;
  4         11  
  4         125  
4 4     4   19 use 5.006001;
  4         5  
  4         113  
5 4     4   26 use warnings;
  4         6  
  4         1751  
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 1077 50 33 1077 0 9530 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 582     582 0 2518 sub new {
34 582   33     1977 my $class = shift;
35 582         1923 $class = ref($class) || $class;
36 582         1413 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 582         1315 }
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         161  
49 4     4   17 no strict 'refs';
  4         8  
  4         4965  
50 20     20   44 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         35 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 20     20   71 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 20         117 )
63 20     20   77 };
  20         40  
64 20         109 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 20 50       88 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 20 50       56  
70 20 50       67 $filter_dump = 1 if $args->{-filter_dump};
71 20 50       54 $filter_save = 1 if $args->{-filter_save};
72 20 50       64 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 20         80  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 20 50       66  
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 20 100 66     221 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 20   33     242  
86 20 100 66     121 my $caller_package = $args->{-package} || caller($stack_frame);
  8         112  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 20         34  
  20         56  
90 24 50       185 for my $class (@{all_my_bases($self_package)}) {
91 236         921 next unless $class->isa('Spiffy');
92 236         203 my @export = grep {
  24         185  
93 8         23 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 24 100 66     38 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 180         755 );
98 180         175 my @export_ok = grep {
  24         76  
99 24         35 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 24         51 # if there is nothing to do (optimization)
  368         614  
104 24 50       174 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 24         36  
  24         88  
107 24         27 my @export_save = @{"$class\::EXPORT"};
  24         82  
108 24         32 my @export_ok_save = @{"$class\::EXPORT_OK"};
  24         99  
109 24         39 @{"$class\::EXPORT"} = @export;
  24         85  
110 8         42 @{"$class\::EXPORT_OK"} = @export_ok;
111 24         55 my @list = grep {
112 8 50       41 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 24         4683 } @export_list;
115 24         55 Exporter::export($class, $caller_package, @list);
  24         111  
116 24         45 @{"$class\::EXPORT"} = @export_save;
  24         4218  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 8     8 0 5109 sub spiffy_filter {
122 8         4478 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 8 50   8   25 sub {
126 8         16 return 0 if $done;
127 8         122 my ($data, $end) = ('', '');
128 4028 50       6024 while (my $status = Filter::Util::Call::filter_read()) {
129 4028 100       6543 return $status if $status < 0;
130 8         23 if (/^__(?:END|DATA)__\r?$/) {
131 8         23 $end = $_;
132             last;
133 4020         4140 }
134 4020         10326 $data .= $_;
135             $_ = '';
136 8         34 }
137 8         14 $_ = $data;
138 8         1192 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 8         834 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 8         448 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 8         23 [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         203 }
150 8 50       45 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 8 50       30 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 8         423 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 8         74 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 24     24 0 38 sub all_my_bases {
164             my $class = shift;
165 24 100       114  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 8         18  
169 4     4   36 my @bases = ($class);
  4         7  
  4         900  
170 8         14 no strict 'refs';
  8         42  
171 4         9 for my $base_class (@{"${class}::ISA"}) {
  4         20  
172             push @bases, @{all_my_bases($base_class)};
173 8         17 }
174 8         16 my $used = {};
  12         77  
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 72     72 0 138 sub field {
203 72         83 my $package = caller;
204 4     4   24 my ($args, @values) = do {
  4         6  
  4         1641  
205 72     72   282 no warnings;
  72         129  
206 72     72   227 local *boolean_arguments = sub { (qw(-weak)) };
  72         144  
207 72         228 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 72         150 };
210 72 50       208 my ($field, $default) = @values;
211 72 50 66     256 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 72 50       83 if defined $default && $args->{-weak};
  72         479  
214 72 50       169 return if defined &{"${package}::$field"};
215 72 100 100     446 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 72         127  
223 72 100       213 my $code = $code{sub_start};
224 16 50       44 if ($args->{-init}) {
225 16         78 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 72 100       214 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 72         217 if defined $default;
230 72         141 $code .= sprintf $code{return_if_get}, $field;
231 72 50       159 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 72         146 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 72 100 66 154   8757  
  154 100 100     1367  
  72 100 100     439  
  12 50 66     57  
  23 100       78  
  21 100       116  
  1 100       2  
  7 50       30  
  7 100       49  
  1 100       10  
  0 100       0  
  65 100       314  
  44 100       257  
  2 100       27  
  45 100       196  
  53 50       595  
  4 100       12  
  4 50       15  
  15 100       43  
  15 100       39  
  15 100       31  
  11 100       62  
  31 100       114  
  24 100       63  
  25 100       73  
  45 100       96  
  45 100       79  
  74 50       261  
  11 50       34  
  19 100       80  
  17         90  
  5         32  
  4         48  
  15         89  
  4         19  
  24         328  
  25         81  
  153         531  
  153         751  
  1         10  
  1         8  
  410         4682  
  66         166  
  66         162  
  1         12  
  0         0  
  0         0  
  63         193  
  206         739  
  146         798  
  12         40  
  78         448  
  155         496  
  164         733  
  14         62  
  4         18  
  5         39  
  2         12  
  2         15  
  46         304  
  62         257  
  63         344  
  44         177  
  1         2  
  138         421  
  753         8120  
  180         429  
  45         93  
  3         21  
  2         21  
  0         0  
  1         10  
  350         814  
  348         765  
  348         712  
236 72 50       191 my $sub = eval $code;
237 4     4   22 die $@ if $@;
  4         6  
  4         805  
238 72         75 no strict 'refs';
  72         346  
239 72 50       338 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 60     60 0 10657 sub default_as_code {
244 60         49252 require Data::Dumper;
245 60         161 local $Data::Dumper::Sortkeys = 1;
246 60         3250 my $code = Data::Dumper::Dumper(shift);
247 60         164 $code =~ s/^\$VAR1 = //;
248 60         146 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 0 0 sub const {
253 0         0 my $package = caller;
254 4     4   24 my ($args, @values) = do {
  4         5  
  4         433  
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   23 $package = $args->{-package} if defined $args->{-package};
  4         7  
  4         523  
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 0 0 sub stub {
267 0         0 my $package = caller;
268 4     4   18 my ($args, @values) = do {
  4         7  
  4         410  
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   22 $package = $args->{-package} if defined $args->{-package};
  4         5  
  4         2417  
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 92     92 0 127 sub parse_arguments {
286 92         197 my $class = shift;
287 92         244 my ($args, @values) = ({}, ());
  252         645  
288 92         269 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  164         397  
289 92         254 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 136         201 while (@_) {
291 136 100 66     1080 my $elem = shift;
    100 66        
      66        
292 8 50 33     66 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 16         58 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 112         318 else {
301             push @values, $elem;
302             }
303 92 50       976 }
304             return wantarray ? ($args, @values) : $args;
305             }
306 0     0 0 0  
307 0     0 0 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   20 {
  4         7  
  4         998  
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 0 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 0 0 0     0 my $seen = 0;
346 0         0 my @super_classes = reverse grep {
347 0         0 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
348 0         0 } reverse @{all_my_bases($class)};
349 4     4   20 for my $super_class (@super_classes) {
  4         6  
  4         746  
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   23 BEGIN {
370 4   50     101 require base unless defined $INC{'base.pm'};
371 4         8 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 4         12 $real_base_import = \&base::import;
373 4     4   23 $real_mixin_import = \&mixin::import;
  4         6  
  4         222  
374 4         52 no warnings;
375 4         190 *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 28     28 0 542 sub spiffy_base_import {
389 28         52 my @base_classes = @_;
390 4     4   20 shift @base_classes;
  4         6  
  4         1198  
391 28         368 no strict 'refs';
392             goto &$real_base_import
393 28 100       55 unless grep {
  28 50       53  
394 28         22755 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 0   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         6  
  4         126  
425 4     4   23 no strict 'refs';
  4         7  
  4         537  
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         6  
  4         1750  
436 0           no strict 'refs';
437 0           my %methods = spiffy_all_methods($mixin_class);
438 0 0         map {
439 0           $methods{$_}
440 0 0         ? ($_, \ &{"$methods{$_}\::$_"})
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 0           while (grep /^!*:/, @roles) {
451 0           @roles = map {
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   28 sub spiffy_all_methods {
  4         7  
  4         798  
479 0     0 0   no strict 'refs';
480 0 0         my $class = shift;
481 0           return if $class eq 'Spiffy';
482 0           my %methods = map {
483             ($_, $class)
484 0 0         } grep {
  0            
485 0           defined &{"$class\::$_"} and not /^_/
486 0           } keys %{"$class\::"};
487 0           my %super_methods;
  0            
488 0 0         %super_methods = spiffy_all_methods(${"$class\::ISA"}[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   22 sub spiffy_dump {
  4         7  
  4         1474  
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__