File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 437 64.9
branch 111 202 54.9
condition 38 62 61.2
subroutine 38 61 62.3
pod 8 27 29.6
total 479 789 60.7


line stmt bran cond sub pod time code
1             #line 1
2 17     17   83 package Spiffy;
  17         31  
  17         595  
3 17     17   243 use strict;
  17         52  
  17         746  
4 17     17   83 use 5.006001;
  17         101  
  17         525  
5 17     17   82 use warnings;
  17         47  
  17         7621  
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 1131 50 33 1131 0 10650 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 626     626 0 894 sub new {
34 626   33     2091 my $class = shift;
35 626         1558 $class = ref($class) || $class;
36 626         1679 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 626         1514 }
41             return $self;
42             }
43              
44             my $filtered_files = {};
45             my $filter_dump = 0;
46             my $filter_save = 0;
47             our $filter_result = '';
48 17     17   103 sub import {
  17         46  
  17         518  
49 17     17   77 no strict 'refs';
  17         33  
  17         22919  
50 96     96   208 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 96         169 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 96     96   423 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 96         583 )
63 96     96   434 };
  96         205  
64 96         562 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 96 50       908 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 96 50       271  
70 96 50       263 $filter_dump = 1 if $args->{-filter_dump};
71 96 50       265 $filter_save = 1 if $args->{-filter_save};
72 96 50       392 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 96         379  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 96 50       288  
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 96 100 66     1099 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 96   33     1304  
86 96 100 66     688 my $caller_package = $args->{-package} || caller($stack_frame);
  48         662  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 96         184  
  96         284  
90 147 50       1158 for my $class (@{all_my_bases($self_package)}) {
91 1960         8345 next unless $class->isa('Spiffy');
92 1960         1979 my @export = grep {
  147         1019  
93 65         210 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 147 100 66     1427 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 864         3637 );
98 864         902 my @export_ok = grep {
  147         669  
99 147         214 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 147         311 # if there is nothing to do (optimization)
  2184         3773  
104 147 50       623 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 147         203  
  147         570  
107 147         192 my @export_save = @{"$class\::EXPORT"};
  147         475  
108 147         201 my @export_ok_save = @{"$class\::EXPORT_OK"};
  147         771  
109 147         273 @{"$class\::EXPORT"} = @export;
  147         1890  
110 31         157 @{"$class\::EXPORT_OK"} = @export_ok;
111 147         285 my @list = grep {
112 31 50       214 (my $v = $_) =~ s/^[\!\:]//;
  0         0  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 147         22850 } @export_list;
115 147         282 Exporter::export($class, $caller_package, @list);
  147         795  
116 147         262 @{"$class\::EXPORT"} = @export_save;
  147         15419  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 48     48 0 20549 sub spiffy_filter {
122 48         22306 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 65 100   65   43316 sub {
126 48         116 return 0 if $done;
127 48         731 my ($data, $end) = ('', '');
128 16953 50       30313 while (my $status = Filter::Util::Call::filter_read()) {
129 16953 100       34585 return $status if $status < 0;
130 31         74 if (/^__(?:END|DATA)__\r?$/) {
131 31         109 $end = $_;
132             last;
133 16922         20195 }
134 16922         47686 $data .= $_;
135             $_ = '';
136 48         160 }
137 48         75 $_ = $data;
138 48         4749 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 48         3284 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 48         1703 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 48         102 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 48 50       162 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 48         891 }
150 48 50       158 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 48 50       135 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 48         2298 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 48         424 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 130     130 0 238 sub all_my_bases {
164             my $class = shift;
165 130 100       591  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 51         122  
169 17     17   111 my @bases = ($class);
  17         41  
  17         4955  
170 51         91 no strict 'refs';
  51         348  
171 34         69 for my $base_class (@{"${class}::ISA"}) {
  34         123  
172             push @bases, @{all_my_bases($base_class)};
173 51         136 }
174 51         107 my $used = {};
  102         504  
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 303     303 1 841 sub field {
203 303         876 my $package = caller;
204 17     17   92 my ($args, @values) = do {
  17         37  
  17         7152  
205 303     303   1166 no warnings;
  303         559  
206 303     303   1027 local *boolean_arguments = sub { (qw(-weak)) };
  303         588  
207 303         979 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 303         599 };
210 303 50       925 my ($field, $default) = @values;
211 303 50 66     1454 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 303 50       322 if defined $default && $args->{-weak};
  303         1819  
214 303 50       1867 return if defined &{"${package}::$field"};
215 303 100 100     1891 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 303         534  
223 303 100       998 my $code = $code{sub_start};
224 68 50       206 if ($args->{-init}) {
225 68         322 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 303 100       982 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 303         901 if defined $default;
230 303         696 $code .= sprintf $code{return_if_get}, $field;
231 303 50       658 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 303         761 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 303 100 100 102   37722  
  102 100 100     3329  
  93 100 100     336  
  52 100 100     139  
  10 100       64  
  53 100       278  
  22 100       113  
  56 100       676  
  48 100       210  
  53 100       269  
  66 100       234  
  58 100       176  
  22 100       90  
  19 100       92  
  101 100       1059  
  272 100       3321  
  87 100       252  
  84 100       296  
  53 100       6221  
  36 100       136  
  57 100       498  
  54 100       236  
  68 100       375  
  23 100       136  
  72 100       310  
  107 100       1065  
  46 100       177  
  130 100       410  
  129 100       349  
  136         518  
  70         282  
  71         377  
  21         117  
  36         151  
  34         177  
  16         90  
  26         253  
  23         147  
  41         207  
  14         79  
  34         2034  
  209         2898  
  46         204  
  27         94  
  9         64  
  73         649  
  33         191  
  26         143  
  56         203  
  96         422  
  82         396  
  39         180  
  62         247  
  80         411  
  29         106  
  34         174  
  75         224  
  51         132  
  51         183  
  128         724  
  83         404  
  55         191  
  67         507  
  149         1620  
  46         201  
  73         344  
  71         225  
  19         66  
  174         1872  
  88         397  
  50         124  
  48         219  
236 303 50       808 my $sub = eval $code;
237 17     17   105 die $@ if $@;
  17         39  
  17         3748  
238 303         342 no strict 'refs';
  303         1788  
239 303 50       1488 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 457     252 0 24719 sub default_as_code {
244 426         181847 require Data::Dumper;
245 426         1373 local $Data::Dumper::Sortkeys = 1;
246 252         15114 my $code = Data::Dumper::Dumper(shift);
247 252         704 $code =~ s/^\$VAR1 = //;
248 252         598 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 1 0 sub const {
253 0         0 my $package = caller;
254 17     17   114 my ($args, @values) = do {
  17         35  
  17         1881  
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 100       0 my ($field, $default) = @values;
260 17     17   85 $package = $args->{-package} if defined $args->{-package};
  17         47  
  17         2107  
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 17     17   84 my ($args, @values) = do {
  17         29  
  17         1649  
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 17     17   105 $package = $args->{-package} if defined $args->{-package};
  17         30  
  17         8094  
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 399     399 1 595 sub parse_arguments {
286 399         893 my $class = shift;
287 399         1289 my ($args, @values) = ({}, ());
  1167         2914  
288 399         1398 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  702         2176  
289 399         1163 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 586         795 while (@_) {
291 586 100 66     4839 my $elem = shift;
    100 66        
      66        
292 48 50 33     309 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 68         250 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 470         1459 else {
301             push @values, $elem;
302             }
303 399 50       5356 }
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 17     17   98 {
  17         35  
  17         4416  
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 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 17     17   139 for my $super_class (@super_classes) {
  17         31  
  17         3384  
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 17 50   17   112 BEGIN {
370 17   50     143 require base unless defined $INC{'base.pm'};
371 17         38 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 17         73 $real_base_import = \&base::import;
373 17     17   87 $real_mixin_import = \&mixin::import;
  17         38  
  17         1111  
374 17         189 no warnings;
375 17         803 *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 182     182 0 2375 sub spiffy_base_import {
389 182         280 my @base_classes = @_;
390 17     17   84 shift @base_classes;
  17         36  
  17         5363  
391 182         855 no strict 'refs';
392             goto &$real_base_import
393 182 50       327 unless grep {
  182 50       261  
394 182         19354 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 17     17   92 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  17         32  
  17         470  
425 17     17   79 no strict 'refs';
  17         32  
  17         2359  
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 17     17   139 my $mixin_class = shift;
  17         30  
  17         8268  
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 17     17   104 sub spiffy_all_methods {
  17         36  
  17         3643  
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 17     17   107 sub spiffy_dump {
  17         39  
  17         7634  
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__