File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 288 440 65.4
branch 114 204 55.8
condition 38 62 61.2
subroutine 38 61 62.3
pod 0 27 0.0
total 478 794 60.2


line stmt bran cond sub pod time code
1             #line 1
2 21     21   80 package Spiffy;
  21         26  
  21         603  
3 21     21   262 use strict;
  21         45  
  21         608  
4 21     21   74 use 5.006001;
  21         22  
  21         467  
5 21     21   72 use warnings;
  21         21  
  21         5673  
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 144 50 33 144 0 1309 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 108     108 0 134 sub new {
34 108   33     343 my $class = shift;
35 108         199 $class = ref($class) || $class;
36 108         246 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 108         261 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 21     21   120 sub import {
  21         40  
  21         574  
49 21     21   89 no strict 'refs';
  21         27  
  21         19656  
50 126     126   209 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 126         154 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 126     126   421 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 126         634 )
63 126     126   363 };
  126         188  
64 126         637 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 126 50       421 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 126 50       279  
70 126 50       333 $filter_dump = 1 if $args->{-filter_dump};
71 126 50       254 $filter_save = 1 if $args->{-filter_save};
72 126 50       251 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 126         336  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 126 50       305  
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 126 100 66     1088 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 126   33     1214  
86 126 100 66     550 my $caller_package = $args->{-package} || caller($stack_frame);
  63         650  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 126         171  
  126         280  
90 189 50       1156 for my $class (@{all_my_bases($self_package)}) {
91 2394         6448 next unless $class->isa('Spiffy');
92 2394         1529 my @export = grep {
  189         1252  
93 84         217 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 189 100 66     204 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 1134         2695 );
98 1134         738 my @export_ok = grep {
  189         486  
99 189         185 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 189         282 # if there is nothing to do (optimization)
  3192         3840  
104 189 100       619 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 168         170  
  168         531  
107 168         158 my @export_save = @{"$class\::EXPORT"};
  168         391  
108 168         194 my @export_ok_save = @{"$class\::EXPORT_OK"};
  168         578  
109 168         231 @{"$class\::EXPORT"} = @export;
  168         410  
110 122         350 @{"$class\::EXPORT_OK"} = @export_ok;
111 168         264 my @list = grep {
112 122 100       329 (my $v = $_) =~ s/^[\!\:]//;
  80         302  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 168         15969 } @export_list;
115 168         275 Exporter::export($class, $caller_package, @list);
  168         684  
116 168         233 @{"$class\::EXPORT"} = @export_save;
  168         16203  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 63     63 0 12126 sub spiffy_filter {
122 63         15332 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 84 100   84   5360 sub {
126 63         119 return 0 if $done;
127 63         416 my ($data, $end) = ('', '');
128 22029 50       26897 while (my $status = Filter::Util::Call::filter_read()) {
129 22029 100       28706 return $status if $status < 0;
130 42         82 if (/^__(?:END|DATA)__\r?$/) {
131 42         140 $end = $_;
132             last;
133 21987         17005 }
134 21987         42679 $data .= $_;
135             $_ = '';
136 63         152 }
137 63         80 $_ = $data;
138 63         4507 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 63         3385 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 63         1803 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 63         106 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 63 50       191 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 63         825 }
150 63 50       205 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 63 50       140 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 63         2129 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 63         440 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 168     168 0 210 sub all_my_bases {
164             my $class = shift;
165 168 100       637  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 63         112  
169 21     21   101 my @bases = ($class);
  21         22  
  21         4094  
170 63         77 no strict 'refs';
  63         272  
171 42         62 for my $base_class (@{"${class}::ISA"}) {
  42         139  
172             push @bases, @{all_my_bases($base_class)};
173 63         114 }
174 63         125 my $used = {};
  126         468  
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 378     378 0 634 sub field {
203 378         358 my $package = caller;
204 21     21   96 my ($args, @values) = do {
  21         23  
  21         6025  
205 378     378   1205 no warnings;
  378         552  
206 378     378   837 local *boolean_arguments = sub { (qw(-weak)) };
  378         588  
207 378         972 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 378         587 };
210 378 50       989 my ($field, $default) = @values;
211 378 50 66     1117 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 378 50       310 if defined $default && $args->{-weak};
  378         1763  
214 378 50       785 return if defined &{"${package}::$field"};
215 378 100 100     2018 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 378         597  
223 378 100       880 my $code = $code{sub_start};
224 84 50       189 if ($args->{-init}) {
225 84         397 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 378 100       1088 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 378         930 if defined $default;
230 378         597 $code .= sprintf $code{return_if_get}, $field;
231 378 50       683 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 378         595 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 378 100 100 24   37051  
  24 100 100     162  
  11 100 100     43  
  6 100 100     97  
  11 100       52  
  18 100       123  
  7 100       23  
  11 100       141  
  17 100       74  
  17 100       64  
  14 100       40  
  22 100       143  
  18 100       113  
  12 100       74  
  18 100       91  
  16 100       66  
  13 100       50  
  15 100       74  
  14 100       95  
  12 100       75  
  19 100       72  
  17 100       75  
  17 100       88  
  14 100       81  
  13 100       84  
  13 100       96  
  21 100       116  
  13 100       71  
  13 100       67  
  14 100       62  
  12         49  
  28         205  
  21         89  
  19         72  
  16         120  
  19         118  
  15         94  
  18         110  
  26         127  
  20         69  
  26         114  
  19         67  
  18         71  
  21         123  
  24         89  
  25         142  
  22         112  
  18         115  
  17         94  
  9         46  
  7         37  
  10         44  
  13         86  
  13         49  
  7         36  
  24         87  
  20         119  
  13         73  
  18         99  
  10         54  
  6         45  
  10         53  
  25         140  
  17         93  
  14         69  
  13         97  
  12         53  
  7         25  
  16         73  
  33         188  
  19         52  
  14         106  
  28         189  
  6         16  
  6         16  
236 378 50       936 my $sub = eval $code;
237 21     21   93 die $@ if $@;
  21         23  
  21         3561  
238 378         382 no strict 'refs';
  378         1699  
239 378 50       1544 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 315     315 0 16159 sub default_as_code {
244 315         168295 require Data::Dumper;
245 315         737 local $Data::Dumper::Sortkeys = 1;
246 315         13933 my $code = Data::Dumper::Dumper(shift);
247 315         765 $code =~ s/^\$VAR1 = //;
248 315         743 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 0 0 sub const {
253 0         0 my $package = caller;
254 21     21   91 my ($args, @values) = do {
  21         21  
  21         1758  
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 21     21   81 $package = $args->{-package} if defined $args->{-package};
  21         22  
  21         2219  
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 21     21   89 my ($args, @values) = do {
  21         24  
  21         1735  
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 21     21   79 $package = $args->{-package} if defined $args->{-package};
  21         23  
  21         8015  
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 504     504 0 597 sub parse_arguments {
286 504         811 my $class = shift;
287 504         1005 my ($args, @values) = ({}, ());
  1512         2812  
288 504         1113 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  882         1488  
289 504         1226 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 775         761 while (@_) {
291 775 100 66     5195 my $elem = shift;
    100 66        
      66        
292 63 50 33     325 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 84         392 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 628         1394 else {
301             push @values, $elem;
302             }
303 504 50       4392 }
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 21     21   98 {
  21         28  
  21         4407  
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 21     21   103 for my $super_class (@super_classes) {
  21         33  
  21         3318  
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 21 50   21   86 BEGIN {
370 21   50     104 require base unless defined $INC{'base.pm'};
371 21         35 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 21         51 $real_base_import = \&base::import;
373 21     21   97 $real_mixin_import = \&mixin::import;
  21         32  
  21         1013  
374 21         110 no warnings;
375 21         848 *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 210     210 0 208195 sub spiffy_base_import {
389 210         303 my @base_classes = @_;
390 21     21   86 shift @base_classes;
  21         21  
  21         4965  
391 210         3085 no strict 'refs';
392             goto &$real_base_import
393 210 100       357 unless grep {
  210 50       192  
394 210         521097 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 21     21   194 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  21         27  
  21         539  
425 21     21   100 no strict 'refs';
  21         22  
  21         2342  
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 21     21   83 my $mixin_class = shift;
  21         22  
  21         7904  
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 21     21   97 sub spiffy_all_methods {
  21         25  
  21         3267  
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 21     21   92 sub spiffy_dump {
  21         22  
  21         5644  
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__