File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 283 438 64.6
branch 112 206 54.3
condition 39 62 62.9
subroutine 37 60 61.6
pod 0 27 0.0
total 471 793 59.3


line stmt bran cond sub pod time code
1 40     40   220 use strict; use warnings;
  40     40   46  
  40         1243  
  40         205  
  40         69  
  40         2371  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   217 use Carp;
  40         87  
  40         11861  
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 338 50 33 338 0 1315 ($_[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 223     223 0 255 my $class = shift;
33 223   33     521 $class = ref($class) || $class;
34 223         282 my $self = bless {}, $class;
35 223         369 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         444 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 40     40   259 no strict 'refs';
  40         63  
  40         1318  
48 40     40   145 no warnings;
  40         53  
  40         50677  
49 256     256   637 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 256         278 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   633 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1585 };
62 256     256   689 local *paired_arguments = sub { qw(-package) };
  256         325  
63 256         835 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       605 if $args->{-mixin};
67              
68 256 50       431 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       345 $filter_save = 1 if $args->{-filter_save};
70 256 50       332 $dump = 'yaml' if $args->{-yaml};
71 256 50       398 $dump = 'dumper' if $args->{-dumper};
72              
73 256         627 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       397 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 256 100 66     1676 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     1995 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1315 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     712 if $args->{-Base} or $args->{-base};
87              
88 256         282 for my $class (@{all_my_bases($self_package)}) {
  256         440  
89 408 50       1586 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3247 not defined &{"$caller_package\::$_"};
  4776         9886  
92 408         1541 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     363 ? @{"$class\::EXPORT_BASE"} : (),
  216         521  
95             );
96             my @export_ok = grep {
97 2304         1586 not defined &{"$caller_package\::$_"};
  2304         4151  
98 408         387 } @{"$class\::EXPORT_OK"};
  408         811  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         554 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7628  
103 408 100       943 next unless keys %exportable;
104              
105 376         375 my @export_save = @{"$class\::EXPORT"};
  376         1045  
106 376         302 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         746  
107 376         350 @{"$class\::EXPORT"} = @export;
  376         1246  
108 376         384 @{"$class\::EXPORT_OK"} = @export_ok;
  376         775  
109             my @list = grep {
110 376         458 (my $v = $_) =~ s/^[\!\:]//;
  288         645  
111 288 100       584 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         642  
112             } @export_list;
113 376         27278 Exporter::export($class, $caller_package, @list);
114 376         713 @{"$class\::EXPORT"} = @export_save;
  376         1072  
115 376         351 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         32073  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 18480 require Filter::Util::Call;
121 112         31230 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7142 return 0 if $done;
125 112         203 my ($data, $end) = ('', '');
126 112         1086 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       37665 return $status if $status < 0;
128 41896 50       40058 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         31594 $data .= $_;
133 41896         54273 $_ = '';
134             }
135 112         1324 $_ = $data;
136 112         150 my @my_subs;
137 112         7459 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6234 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         1911 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 112         425 my $preclare = '';
144 112 50       551 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         350 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       250 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       242 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         4927 $done = 1;
152             }
153 112         1028 );
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 368     368 0 374 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       982 if defined $bases_map->{$class};
166              
167 152         302 my @bases = ($class);
168 40     40   287 no strict 'refs';
  40         61  
  40         7170  
169 152         160 for my $base_class (@{"${class}::ISA"}) {
  152         507  
170 112         157 push @bases, @{all_my_bases($base_class)};
  112         276  
171             }
172 152         179 my $used = {};
173 152         203 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1114  
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 712     712 0 1099 my $package = caller;
202 712         671 my ($args, @values) = do {
203 40     40   253 no warnings;
  40         48  
  40         11777  
204 712     712   2530 local *boolean_arguments = sub { (qw(-weak)) };
  712         1007  
205 712     712   1681 local *paired_arguments = sub { (qw(-package -init)) };
  712         881  
206 712         1591 Spiffy->parse_arguments(@_);
207             };
208 712         1127 my ($field, $default) = @values;
209 712 50       1055 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1354 if defined $default && $args->{-weak};
212 712 50       561 return if defined &{"${package}::$field"};
  712         2738  
213 712 50       937 require Scalar::Util if $args->{-weak};
214 712 100 100     2550 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 712         982 my $code = $code{sub_start};
222 712 100       1167 if ($args->{-init}) {
223 160 50       319 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         706 my @count = ($fragment =~ /(%s)/g);
225 160         578 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1459 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1166 $code .= sprintf $code{return_if_get}, $field;
230 712         945 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       881 if $args->{-weak};
233 712         931 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 53   69977 my $sub = eval $code;
  53 100 100     117  
  48 100 100     110  
  28 100 100     51  
  57 100       187  
  27 100       58  
  15 100       17  
  19 100       43  
  13 100       29  
  81 100       337  
  11 100       16  
  19 100       45  
  15 100       44  
  0 100       0  
  16 100       55  
  17 100       41  
  72 100       338  
  24 100       56  
  54 100       72  
  52 100       89  
  56 100       74  
  23 100       55  
  20 100       49  
  26 100       41  
  39 100       77  
  40 100       77  
  8 100       17  
  4 100       26  
  54 100       174  
  20 100       51  
  6         28  
  13         29  
  28         66  
  17         39  
  5         13  
  19         61  
  23         66  
  8         21  
  11         20  
  72         106  
  76         113  
  92         118  
  43         117  
  16         47  
  3         9  
  65         211  
  43         90  
  26         55  
  23         51  
  31         82  
  11         26  
  14         37  
  31         73  
  31         76  
  5         9  
  7         21  
  17         30  
  16         48  
  3         7  
  2         5  
  26         84  
  22         43  
  30         78  
  22         57  
  6         12  
  10         31  
  82         302  
  14         27  
  18         83  
  20         70  
  6         13  
  5         16  
  32         93  
  10         15  
  10         16  
236 712 50       1449 die $@ if $@;
237 40     40   245 no strict 'refs';
  40         76  
  40         9284  
238 712         609 *{"${package}::$field"} = $sub;
  712         3378  
239 712 50       2259 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 24571 require Data::Dumper;
244 592         283712 local $Data::Dumper::Sortkeys = 1;
245 592         1152 my $code = Data::Dumper::Dumper(shift);
246 592         24447 $code =~ s/^\$VAR1 = //;
247 592         1592 $code =~ s/;$//;
248 592         989 return $code;
249             }
250              
251             sub const {
252 0     0 0 0 my $package = caller;
253 0         0 my ($args, @values) = do {
254 40     40   222 no warnings;
  40         53  
  40         3853  
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 40     40   167 no strict 'refs';
  40         61  
  40         5099  
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 0 0 my $package = caller;
267 0         0 my ($args, @values) = do {
268 40     40   188 no warnings;
  40         70  
  40         3827  
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 40     40   174 no strict 'refs';
  40         60  
  40         22340  
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 968     968 0 1036 my $class = shift;
286 968         1398 my ($args, @values) = ({}, ());
287 968         1645 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4510  
288 968         1744 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2367  
289 968         1819 while (@_) {
290 1480         1413 my $elem = shift;
291 1480 100 66     6238 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     613 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         386 $args->{$elem} = shift;
298             }
299             else {
300 1176         2049 push @values, $elem;
301             }
302             }
303 968 50       6146 return wantarray ? ($args, @values) : $args;
304             }
305              
306 0     0 0 0 sub boolean_arguments { () }
307 0     0 0 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 40     40   253 no warnings 'redefine';
  40         43  
  40         7385  
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 0 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 40     40   186 no strict 'refs';
  40         42  
  40         6110  
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 40 50   40   189 require base unless defined $INC{'base.pm'};
370 40   50     208 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         52 $real_base_import = \&base::import;
372 40         82 $real_mixin_import = \&mixin::import;
373 40     40   219 no warnings;
  40         77  
  40         1901  
374 40         251 *base::import = \&spiffy_base_import;
375 40         1614 *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 80     80 0 222 my @base_classes = @_;
389 80         124 shift @base_classes;
390 40     40   163 no strict 'refs';
  40         57  
  40         11009  
391             goto &$real_base_import
392             unless grep {
393 80 50       147 eval "require $_" unless %{"$_\::"};
  80 50       846  
  80         779  
394 80         14014 $_->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 0   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 40     40   211 no strict 'refs';
  40         69  
  40         1222  
425 40     40   138 no warnings;
  40         54  
  40         5440  
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 40     40   175 no strict 'refs';
  40         43  
  40         16007  
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 40     40   261 no strict 'refs';
  40         55  
  40         7280  
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 40     40   189 no warnings;
  40         50  
  40         12047  
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;