File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 288 440 65.4
branch 113 204 55.3
condition 40 62 64.5
subroutine 38 61 62.3
pod 0 27 0.0
total 479 794 60.3


line stmt bran cond sub pod time code
1             #line 1
2 21     21   109 package Spiffy;
  21         41  
  21         1022  
3 21     21   379 use strict;
  21         62  
  21         835  
4 21     21   98 use 5.006001;
  21         50  
  21         894  
5 21     21   98 use warnings;
  21         37  
  21         9827  
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 1125 50 33 1125 0 10597 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 660     660 0 1330 sub new {
34 660   33     2322 my $class = shift;
35 660         1664 $class = ref($class) || $class;
36 660         4911 my $self = bless {}, $class;
37 0         0 while (@_) {
38 0         0 my $method = shift;
39             $self->$method(shift);
40 660         1635 }
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   236 sub import {
  21         59  
  21         1167  
49 21     21   212 no strict 'refs';
  21         49  
  21         35881  
50 147     147   709 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 147         289 # difficult debugging. Consider using something truly local.
56             my ($args, @export_list) = do {
57 147     147   557 local *boolean_arguments = sub {
58             qw(
59             -base -Base -mixin -selfless
60             -XXX -dumper -yaml
61             -filter_dump -filter_save
62 147         835 )
63 147     147   504 };
  147         314  
64 147         1158 local *paired_arguments = sub { qw(-package) };
65             $self_package->parse_arguments(@_);
66 147 50       581 };
67             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
68             if $args->{-mixin};
69 147 50       471  
70 147 50       377 $filter_dump = 1 if $args->{-filter_dump};
71 147 50       355 $filter_save = 1 if $args->{-filter_save};
72 147 50       455 $dump = 'yaml' if $args->{-yaml};
73             $dump = 'dumper' if $args->{-dumper};
74 147         637  
75             local @EXPORT_BASE = @EXPORT_BASE;
76 147 50       440  
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 147 100 66     1462 spiffy_filter()
      66        
83             if ($args->{-selfless} or $args->{-Base}) and
84             not $filtered_files->{(caller($stack_frame))[1]}++;
85 147   33     1624  
86 147 100 100     860 my $caller_package = $args->{-package} || caller($stack_frame);
  84         1060  
87             push @{"$caller_package\::ISA"}, $self_package
88             if $args->{-Base} or $args->{-base};
89 147         232  
  147         408  
90 231 50       2031 for my $class (@{all_my_bases($self_package)}) {
91 2478         22838 next unless $class->isa('Spiffy');
92 2478         2785 my @export = grep {
  231         1911  
93 126         552 not defined &{"$caller_package\::$_"};
94             } ( @{"$class\::EXPORT"},
95 231 100 100     327 ($args->{-Base} or $args->{-base})
96             ? @{"$class\::EXPORT_BASE"} : (),
97 1323         5367 );
98 1323         1277 my @export_ok = grep {
  231         931  
99 231         358 not defined &{"$caller_package\::$_"};
100             } @{"$class\::EXPORT_OK"};
101              
102             # Avoid calling the expensive Exporter::export
103 231         428 # if there is nothing to do (optimization)
  3465         6865  
104 231 100       1008 my %exportable = map { ($_, 1) } @export, @export_ok;
105             next unless keys %exportable;
106 189         268  
  189         848  
107 189         225 my @export_save = @{"$class\::EXPORT"};
  189         622  
108 189         261 my @export_ok_save = @{"$class\::EXPORT_OK"};
  189         1188  
109 189         393 @{"$class\::EXPORT"} = @export;
  189         896  
110 78         521 @{"$class\::EXPORT_OK"} = @export_ok;
111 189         460 my @list = grep {
112 78 100       394 (my $v = $_) =~ s/^[\!\:]//;
  36         183  
113             $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
114 189         26776 } @export_list;
115 189         441 Exporter::export($class, $caller_package, @list);
  189         1064  
116 189         360 @{"$class\::EXPORT"} = @export_save;
  189         36202  
117             @{"$class\::EXPORT_OK"} = @export_ok_save;
118             }
119             }
120              
121 63     63 0 28410 sub spiffy_filter {
122 63         29701 require Filter::Util::Call;
123             my $done = 0;
124             Filter::Util::Call::filter_add(
125 84 100   84   1121 sub {
126 63         256 return 0 if $done;
127 63         685 my ($data, $end) = ('', '');
128 21651 50       37294 while (my $status = Filter::Util::Call::filter_read()) {
129 21651 100       42175 return $status if $status < 0;
130 42         121 if (/^__(?:END|DATA)__\r?$/) {
131 42         118 $end = $_;
132             last;
133 21609         39081 }
134 21609         66382 $data .= $_;
135             $_ = '';
136 63         1351 }
137 63         99 $_ = $data;
138 63         7088 my @my_subs;
139             s[^(sub\s+\w+\s+\{)(.*\n)]
140 63         5799 [${1}my \$self = shift;$2]gm;
141             s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
142 63         2830 [${1}${2}]gm;
143 0         0 s[^my\s+sub\s+(\w+)(\s+\{)(.*)((?s:.*?\n))\}\n]
  0         0  
144 63         142 [push @my_subs, $1; "\$$1 = sub$2my \$self = shift;$3$4\};\n"]gem;
145 63 50       239 my $preclare = '';
146 0         0 if (@my_subs) {
147 0         0 $preclare = join ',', map "\$$_", @my_subs;
148             $preclare = "my($preclare);";
149 63         1689 }
150 63 50       354 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
  0         0  
  0         0  
151 63 50       168 if ($filter_dump) { print; exit }
  0         0  
  0         0  
152 63         3605 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
153             $done = 1;
154 63         511 }
155             );
156             }
157              
158 0     0 0 0 sub base {
159 0         0 push @_, -base;
160             goto &import;
161             }
162              
163 210     210 0 333 sub all_my_bases {
164             my $class = shift;
165 210 100       923  
166             return $bases_map->{$class}
167             if defined $bases_map->{$class};
168 84         190  
169 21     21   147 my @bases = ($class);
  21         39  
  21         14720  
170 84         119 no strict 'refs';
  84         993  
171 63         111 for my $base_class (@{"${class}::ISA"}) {
  63         199  
172             push @bases, @{all_my_bases($base_class)};
173 84         233 }
174 84         178 my $used = {};
  168         18347  
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 813 sub field {
203 378         6256 my $package = caller;
204 21     21   127 my ($args, @values) = do {
  21         34  
  21         9709  
205 378     378   3214 no warnings;
  378         813  
206 378     378   1282 local *boolean_arguments = sub { (qw(-weak)) };
  378         779  
207 378         1425 local *paired_arguments = sub { (qw(-package -init)) };
208             Spiffy->parse_arguments(@_);
209 378         804 };
210 378 50       1275 my ($field, $default) = @values;
211 378 50 66     1556 $package = $args->{-package} if defined $args->{-package};
212             die "Cannot have a default for a weakened field ($field)"
213 378 50       421 if defined $default && $args->{-weak};
  378         5532  
214 378 50       1110 return if defined &{"${package}::$field"};
215 378 100 100     10987 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         852  
223 378 100       1309 my $code = $code{sub_start};
224 84 50       252 if ($args->{-init}) {
225 84         659 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
226             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x 4;
227 378 100       1500 }
228             $code .= sprintf $code{set_default}, $field, $default_string, $field
229 378         1213 if defined $default;
230 378         781 $code .= sprintf $code{return_if_get}, $field;
231 378 50       1427 $code .= sprintf $code{set}, $field;
232             $code .= sprintf $code{weaken}, $field, $field
233 378         813 if $args->{-weak};
234             $code .= sprintf $code{sub_end}, $field;
235 378 100 100 278   51747  
  278 100 100     2004  
  128 100 100     431  
  92 100 100     234  
  103 100       297  
  362 100       2041  
  251 100       892  
  224 100       615  
  66 100       358  
  12 100       58  
  28 100       129  
  48 100       266  
  46 100       117  
  43 100       106  
  83 100       239  
  86 100       524  
  21 100       59  
  3 100       29  
  46 100       167  
  48 100       280  
  31 100       167  
  178 100       2243  
  77 100       260  
  77 100       367  
  35 100       166  
  10 100       51  
  5 100       16  
  11 100       61  
  8 100       41  
  2 100       16  
  6         42  
  68         266  
  48         322  
  7         23  
  52         315  
  38         132  
  180         1705  
  59         147  
  68         256  
  110         498  
  90         236  
  103         254  
  45         325  
  25         73  
  30         112  
  85         452  
  138         816  
  227         3909  
  117         329  
  98         702  
  28         110  
  27         168  
  89         259  
  89         290  
  65         150  
  6         37  
  68         220  
  178         1773  
  92         281  
  178         974  
  83         434  
  36         122  
  179         2123  
  67         248  
  47         168  
  81         290  
  97         588  
  21         57  
  25         96  
  67         334  
  6         23  
  29         104  
  106         574  
  27         74  
  27         77  
236 378 50       1609 my $sub = eval $code;
237 21     21   124 die $@ if $@;
  21         75  
  21         6335  
238 378         576 no strict 'refs';
  378         2014  
239 378 50       2106 *{"${package}::$field"} = $sub;
240             return $code if defined wantarray;
241             }
242              
243 315     315 0 29048 sub default_as_code {
244 315         331204 require Data::Dumper;
245 315         1124 local $Data::Dumper::Sortkeys = 1;
246 315         19968 my $code = Data::Dumper::Dumper(shift);
247 315         867 $code =~ s/^\$VAR1 = //;
248 315         803 $code =~ s/;$//;
249             return $code;
250             }
251              
252 0     0 0 0 sub const {
253 0         0 my $package = caller;
254 21     21   112 my ($args, @values) = do {
  21         32  
  21         2737  
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   190 $package = $args->{-package} if defined $args->{-package};
  21         35  
  21         3718  
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   199 my ($args, @values) = do {
  21         31  
  21         3310  
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   122 $package = $args->{-package} if defined $args->{-package};
  21         41  
  21         24761  
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 525     525 0 913 sub parse_arguments {
286 525         1239 my $class = shift;
287 525         1958 my ($args, @values) = ({}, ());
  1701         4988  
288 525         1984 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  903         2620  
289 525         1490 my %pairs = map { ($_, 1) } $class->paired_arguments;
290 774         1188 while (@_) {
291 774 100 66     6873 my $elem = shift;
    100 66        
      66        
292 84 50 33     590 if (defined $elem and defined $booleans{$elem}) {
293             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
294             ? shift
295             : 1;
296             }
297 84         311 elsif (defined $elem and defined $pairs{$elem} and @_) {
298             $args->{$elem} = shift;
299             }
300 606         2192 else {
301             push @values, $elem;
302             }
303 525 50       7557 }
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   172 {
  21         43  
  21         6432  
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   126 for my $super_class (@super_classes) {
  21         48  
  21         5248  
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   181 BEGIN {
370 21   50     188 require base unless defined $INC{'base.pm'};
371 21         48 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
372 21         80 $real_base_import = \&base::import;
373 21     21   119 $real_mixin_import = \&mixin::import;
  21         41  
  21         1528  
374 21         201 no warnings;
375 21         1138 *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 33     33 0 2663 sub spiffy_base_import {
389 33         83 my @base_classes = @_;
390 21     21   259 shift @base_classes;
  21         37  
  21         8327  
391 33         211 no strict 'refs';
392             goto &$real_base_import
393 33 50       99 unless grep {
  33 50       82  
394 33         14409 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   122 my %methods = spiffy_mixin_methods($mixin_class, @roles);
  21         69  
  21         699  
425 21     21   109 no strict 'refs';
  21         38  
  21         3770  
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   107 my $mixin_class = shift;
  21         38  
  21         24475  
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   196 sub spiffy_all_methods {
  21         39  
  21         4455  
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   116 sub spiffy_dump {
  21         40  
  21         12835  
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;