File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 112 206 54.3
condition 39 62 62.9
subroutine 37 60 61.6
pod 8 27 29.6
total 480 793 60.5


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