File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 284 438 64.8
branch 111 204 54.4
condition 39 62 62.9
subroutine 37 60 61.6
pod 8 27 29.6
total 479 791 60.5


line stmt bran cond sub pod time code
1 46     46   525 use strict; use warnings;
  46     46   73  
  46         1048  
  46         194  
  46         58  
  46         1867  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 46     46   202 use Carp;
  46         71  
  46         12252  
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 1886 ($_[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 357 my $class = shift;
33 266   33     671 $class = ref($class) || $class;
34 266         411 my $self = bless {}, $class;
35 266         516 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 266         608 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   278 no strict 'refs';
  46         69  
  46         1444  
48 46     46   218 no warnings;
  46         65  
  46         45528  
49 298     298   784 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         363 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 298     298   904 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 298         2091 };
62 298     298   1039 local *paired_arguments = sub { qw(-package) };
  298         422  
63 298         1250 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 298 50       830 if $args->{-mixin};
67              
68 298 50       634 $filter_dump = 1 if $args->{-filter_dump};
69 298 50       514 $filter_save = 1 if $args->{-filter_save};
70 298 50       493 $dump = 'yaml' if $args->{-yaml};
71 298 50       551 $dump = 'dumper' if $args->{-dumper};
72              
73 298         896 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 298 50       588 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     2518 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 298   33     2610 my $caller_package = $args->{-package} || caller($stack_frame);
85 168         1694 push @{"$caller_package\::ISA"}, $self_package
86 298 100 66     914 if $args->{-Base} or $args->{-base};
87              
88 298         405 for my $class (@{all_my_bases($self_package)}) {
  298         600  
89 474 50       2155 next unless $class->isa('Spiffy');
90             my @export = grep {
91 5584         5920 not defined &{"$caller_package\::$_"};
  5584         13073  
92 474         2068 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 474 100 66     587 ? @{"$class\::EXPORT_BASE"} : (),
  252         666  
95             );
96             my @export_ok = grep {
97 2682         4616 not defined &{"$caller_package\::$_"};
  2682         5942  
98 474         570 } @{"$class\::EXPORT_OK"};
  474         1113  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 474         728 my %exportable = map { ($_, 1) } @export, @export_ok;
  7562         11592  
103 474 100       1338 next unless keys %exportable;
104              
105 436         464 my @export_save = @{"$class\::EXPORT"};
  436         2446  
106 436         1797 my @export_ok_save = @{"$class\::EXPORT_OK"};
  436         1037  
107 436         1795 @{"$class\::EXPORT"} = @export;
  436         4112  
108 436         1930 @{"$class\::EXPORT_OK"} = @export_ok;
  436         942  
109             my @list = grep {
110 436         2048 (my $v = $_) =~ s/^[\!\:]//;
  318         803  
111 318 100       2033 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  234         741  
112             } @export_list;
113 436         36473 Exporter::export($class, $caller_package, @list);
114 436         995 @{"$class\::EXPORT"} = @export_save;
  436         4144  
115 436         546 @{"$class\::EXPORT_OK"} = @export_ok_save;
  436         41098  
116             }
117             }
118              
119             sub spiffy_filter {
120 130     130 0 22096 require Filter::Util::Call;
121 130         35302 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 260 100   260   7629 return 0 if $done;
125 130         263 my ($data, $end) = ('', '');
126 130         6257 while (my $status = Filter::Util::Call::filter_read()) {
127 49818 50       51122 return $status if $status < 0;
128 49818 50       53564 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 49818         42505 $data .= $_;
133 49818         72896 $_ = '';
134             }
135 130         1431 $_ = $data;
136 130         229 my @my_subs;
137 130         9295 s[^(sub\s+\w+\s+\{)(.*\n)]
138 130         6680 [${1}my \$self = shift;$2]gm;
139 130         2165 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
  0         0  
  0         0  
140 130         237 [${1}${2}]gm;
141 130 50       537 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;
143 0         0 my $preclare = '';
144             if (@my_subs) {
145 130         4772 $preclare = join ',', map "\$$_", @my_subs;
146 130 50       3434 $preclare = "my($preclare);";
  0         0  
  0         0  
147 130 50       330 }
  0         0  
  0         0  
148 130         7389 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149             if ($filter_dump) { print; exit }
150 130         857 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
151             $done = 1;
152             }
153             );
154 0     0 0 0 }
155 0         0  
156             sub base {
157             push @_, -base;
158             goto &import;
159 428     428 0 533 }
160              
161             sub all_my_bases {
162 428 100       1227 my $class = shift;
163              
164 176         326 return $bases_map->{$class}
165 46     46   324 if defined $bases_map->{$class};
  46         89  
  46         9375  
166 176         197  
  176         828  
167 130         222 my @bases = ($class);
  130         385  
168             no strict 'refs';
169 176         296 for my $base_class (@{"${class}::ISA"}) {
170 176         334 push @bases, @{all_my_bases($base_class)};
  352         1225  
171             }
172             my $used = {};
173             $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
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 820     820 1 1302 );
199 820         788  
200 46     46   299 sub field {
  46         89  
  46         15182  
201 820     820   3189 my $package = caller;
  820         1215  
202 820     820   2418 my ($args, @values) = do {
  820         1229  
203 820         2105 no warnings;
204             local *boolean_arguments = sub { (qw(-weak)) };
205 820         1577 local *paired_arguments = sub { (qw(-package -init)) };
206 820 50       1385 Spiffy->parse_arguments(@_);
207             };
208 820 50 66     1653 my ($field, $default) = @values;
209 820 50       712 $package = $args->{-package} if defined $args->{-package};
  820         3505  
210 820 50       1290 die "Cannot have a default for a weakened field ($field)"
211 820 100 100     3097 if defined $default && $args->{-weak};
    100 66        
212             return if defined &{"${package}::$field"};
213             require Scalar::Util if $args->{-weak};
214             my $default_string =
215             ( ref($default) eq 'ARRAY' and not @$default )
216             ? '[]'
217             : (ref($default) eq 'HASH' and not keys %$default )
218 820         1134 ? '{}'
219 820 100       1384 : default_as_code($default);
220 184 50       394  
221 184         876 my $code = $code{sub_start};
222 184         920 if ($args->{-init}) {
223             my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 820 100       2137 my @count = ($fragment =~ /(%s)/g);
225             $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226 820         1925 }
227 820         1271 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 820 50       1161 $code .= sprintf $code{return_if_get}, $field;
230 820         1249 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 820 100 100 77   76671 if $args->{-weak};
  77 100 100     333  
  38 100 100     77  
  32 100 100     57  
  132 100       695  
  30 100       88  
  14 100       20  
  18 100       49  
  23 100       87  
  24 100       81  
  23 100       107  
  17 100       63  
  8 100       20  
  12 100       41  
  24 100       110  
  25 100       72  
  26 100       90  
  25 100       88  
  23 50       67  
  38 100       114  
  144 100       718  
  26 100       60  
  22 100       61  
  63 100       146  
  76 100       182  
  60 100       123  
  22 100       80  
  17 100       44  
  14 100       33  
  47 100       131  
  62         233  
  36         72  
  33         113  
  45         159  
  20         60  
  25         63  
  56         261  
  31         98  
  17         47  
  16         60  
  34         87  
  40         126  
  6         26  
  6         18  
  75         348  
  30         73  
  22         30  
  8         31  
  56         261  
  14         47  
  13         53  
  23         87  
  15         43  
  15         32  
  19         83  
  35         89  
  32         85  
  53         139  
  49         145  
  36         107  
  49         116  
  52         180  
  30         108  
  2         4  
  7         19  
  12         37  
  6         22  
  8         22  
  13         52  
  43         110  
  42         74  
  54         96  
  35         144  
  6         14  
  6         19  
233 820 50       1851 $code .= sprintf $code{sub_end}, $field;
234 46     46   279  
  46         71  
  46         9935  
235 820         800 my $sub = eval $code;
  820         3558  
236 820 50       2826 die $@ if $@;
237             no strict 'refs';
238             *{"${package}::$field"} = $sub;
239             return $code if defined wantarray;
240 682     682 0 28882 }
241 682         265909  
242 682         1531 sub default_as_code {
243 682         28965 require Data::Dumper;
244 682         1688 local $Data::Dumper::Sortkeys = 1;
245 682         1222 my $code = Data::Dumper::Dumper(shift);
246             $code =~ s/^\$VAR1 = //;
247             $code =~ s/;$//;
248             return $code;
249 0     0 1 0 }
250 0         0  
251 46     46   284 sub const {
  46         75  
  46         4378  
252 0     0   0 my $package = caller;
  0         0  
253 0         0 my ($args, @values) = do {
254             no warnings;
255 0         0 local *paired_arguments = sub { (qw(-package)) };
256 0 0       0 Spiffy->parse_arguments(@_);
257 46     46   275 };
  46         91  
  46         5182  
258 0 0       0 my ($field, $default) = @values;
  0         0  
259 0     0   0 $package = $args->{-package} if defined $args->{-package};
  0         0  
260 0         0 no strict 'refs';
261             return if defined &{"${package}::$field"};
262             *{"${package}::$field"} = sub { $default }
263 0     0 1 0 }
264 0         0  
265 46     46   294 sub stub {
  46         78  
  46         4273  
266 0     0   0 my $package = caller;
  0         0  
267 0         0 my ($args, @values) = do {
268             no warnings;
269 0         0 local *paired_arguments = sub { (qw(-package)) };
270 0 0       0 Spiffy->parse_arguments(@_);
271 46     46   256 };
  46         97  
  46         19298  
272 0 0       0 my ($field, $default) = @values;
  0         0  
273 0         0 $package = $args->{-package} if defined $args->{-package};
274             no strict 'refs';
275 0     0   0 return if defined &{"${package}::$field"};
276 0         0 *{"${package}::$field"} =
277             sub {
278             require Carp;
279 0         0 Carp::confess
280             "Method $field in package $package must be subclassed";
281             }
282 1118     1118 1 1439 }
283 1118         1795  
284 1118         2056 sub parse_arguments {
  3502         5803  
285 1118         2230 my $class = shift;
  1938         2985  
286 1118         2260 my ($args, @values) = ({}, ());
287 1702         1846 my %booleans = map { ($_, 1) } $class->boolean_arguments;
288 1702 100 66     12087 my %pairs = map { ($_, 1) } $class->paired_arguments;
    100 66        
      66        
289 168 50 33     694 while (@_) {
290             my $elem = shift;
291             if (defined $elem and defined $booleans{$elem}) {
292             $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294 184         452 : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 1350         4178 $args->{$elem} = shift;
298             }
299             else {
300 1118 50       7184 push @values, $elem;
301             }
302             }
303 0     0 1 0 return wantarray ? ($args, @values) : $args;
304 0     0 1 0 }
305              
306             sub boolean_arguments { () }
307             sub paired_arguments { () }
308 0 0   0 0 0  
309 0 0       0 # get a unique id for any node
310 0 0       0 sub id {
311 0         0 if (not ref $_[0]) {
312             return 'undef' if not defined $_[0];
313 0         0 \$_[0] =~ /\((\w+)\)$/o or die;
314 0 0       0 return "$1-S";
315 0         0 }
316             require overload;
317             overload::StrVal($_[0]) =~ /\((\w+)\)$/o or die;
318             return $1;
319             }
320              
321             #===============================================================================
322             # It's super, man.
323 46     46   293 #===============================================================================
  46         99  
  46         9876  
324             package DB;
325 0 0   0 0 0 {
326 0         0 no warnings 'redefine';
327             sub super_args {
328             my @dummy = caller(@_ ? $_[0] : 2);
329             return @DB::args;
330             }
331             }
332 0     0 1 0  
333 0         0 package Spiffy;
334 0         0 sub super {
335 0 0       0 my $method;
336             my $frame = 1;
337 0         0 while ($method = (caller($frame++))[3]) {
338 0 0       0 $method =~ s/.*::// and last;
339 0 0       0 }
340 0         0 my @args = DB::super_args($frame);
341 0         0 @_ = @_ ? ($args[0], @_) : @args;
342             my $class = ref $_[0] ? ref $_[0] : $_[0];
343 0 0 0     0 my $caller_class = caller;
344 0         0 my $seen = 0;
  0         0  
345 0         0 my @super_classes = reverse grep {
346 46     46   304 ($seen or $seen = ($_ eq $caller_class)) ? 0 : 1;
  46         63  
  46         7394  
347 0 0       0 } reverse @{all_my_bases($class)};
348 0 0       0 for my $super_class (@super_classes) {
  0         0  
349 0 0       0 no strict 'refs';
  0         0  
  0         0  
350             next if $super_class eq $class;
351 0         0 if (defined &{"${super_class}::$method"}) {
  0         0  
352             ${"$super_class\::AUTOLOAD"} = ${"$class\::AUTOLOAD"}
353             if $method eq 'AUTOLOAD';
354 0         0 return &{"${super_class}::$method"};
355             }
356             }
357             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 46 50   46   270 my $real_mixin_import;
367 46   50     308  
368 46         84 BEGIN {
369 46         147 require base unless defined $INC{'base.pm'};
370 46     46   258 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
  46         79  
  46         2863  
371 46         364 $real_base_import = \&base::import;
372 46         2202 $real_mixin_import = \&mixin::import;
373             no warnings;
374             *base::import = \&spiffy_base_import;
375             *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 139     139 0 4670 # }
386 139         246  
387 46     46   253 sub spiffy_base_import {
  46         84  
  46         12077  
388             my @base_classes = @_;
389             shift @base_classes;
390 139 50       294 no strict 'refs';
  139 50       205  
  139         774  
391 139         26736 goto &$real_base_import
392             unless grep {
393 0           eval "require $_" unless %{"$_\::"};
394 0           $_->isa('Spiffy');
395 0 0         } @base_classes;
396 0 0         my $inheritor = caller(0);
397             for my $base_class (@base_classes) {
398             next if $inheritor->isa($base_class);
399 0           croak "Can't mix Spiffy and non-Spiffy classes in 'use base'.\n",
400 0           "See the documentation of Spiffy.pm for details\n "
401 0           unless $base_class->isa('Spiffy');
402             $stack_frame = 1; # tell import to use different caller
403             import($base_class, '-base');
404             $stack_frame = 0;
405             }
406 0     0 1   }
407 0            
408 0           sub mixin {
409             my $self = shift;
410             my $target_class = ref($self);
411             spiffy_mixin_import($target_class, @_)
412 0     0 0   }
413 0 0          
414             sub spiffy_mixin_import {
415 0 0         my $target_class = shift;
416             $target_class = caller(0)
417 0           if $target_class eq 'mixin';
418 0           my $mixin_class = shift
419 0           or die "Nothing to mixin";
420 0           eval "require $mixin_class";
421 46     46   264 my @roles = @_;
  46         71  
  46         1552  
422 46     46   212 my $pseudo_class = join '-', $target_class, $mixin_class, @roles;
  46         73  
  46         5370  
423 0           my %methods = spiffy_mixin_methods($mixin_class, @roles);
  0            
  0            
424 0           no strict 'refs';
  0            
425 0           no warnings;
426 0           @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"};
  0            
427             @{"$target_class\::ISA"} = ($pseudo_class);
428             for (keys %methods) {
429             *{"$pseudo_class\::$_"} = $methods{$_};
430             }
431 0     0 0   }
432 46     46   274  
  46         85  
  46         16816  
433 0           sub spiffy_mixin_methods {
434             my $mixin_class = shift;
435 0 0         no strict 'refs';
436 0           my %methods = spiffy_all_methods($mixin_class);
437 0 0         map {
  0            
438             $methods{$_}
439             ? ($_, \ &{"$methods{$_}\::$_"})
440             : ($_, \ &{"$mixin_class\::$_"})
441             } @_
442             ? (get_roles($mixin_class, @_))
443             : (keys %methods);
444 0     0 0   }
445 0            
446 0           sub get_roles {
447             my $mixin_class = shift;
448 0           my @roles = @_;
  0            
449             while (grep /^!*:/, @roles) {
450 0           @roles = map {
451 0           s/!!//g;
452             /^!:(.*)/ ? do {
453 0 0         my $m = "_role_$1";
    0          
454 0           map("!$_", $mixin_class->$m);
455 0           } :
456             /^:(.*)/ ? do {
457             my $m = "_role_$1";
458             ($mixin_class->$m);
459             } :
460 0 0 0       ($_)
461 0           } @roles;
462 0           }
463             if (@roles and $roles[0] =~ /^!/) {
464 0           my %methods = spiffy_all_methods($mixin_class);
465 0           unshift @roles, keys(%methods);
466 0           }
467 0 0         my %roles;
468             for (@roles) {
469 0           s/!!//g;
470             delete $roles{$1}, next
471 0           if /^!(.*)/;
472             $roles{$_} = 1;
473             }
474             keys %roles;
475 46     46   290 }
  46         772  
  46         7313  
476 0     0 0    
477 0 0         sub spiffy_all_methods {
478             no strict 'refs';
479 0           my $class = shift;
480             return if $class eq 'Spiffy';
481 0 0         my %methods = map {
  0            
482 0           ($_, $class)
  0            
483 0           } grep {
484 0           defined &{"$class\::$_"} and not /^_/
485 0 0         } keys %{"$class\::"};
  0            
486 0           my %super_methods;
  0            
487             %super_methods = spiffy_all_methods(${"$class\::ISA"}[0])
488             if @{"$class\::ISA"};
489             %{{%super_methods, %methods}};
490             }
491              
492              
493             # END of naughty code.
494             #===============================================================================
495 46     46   256 # Debugging support
  46         85  
  46         13902  
496 0 0   0 0   #===============================================================================
497 0           sub spiffy_dump {
498 0           no warnings;
499 0           if ($dump eq 'dumper') {
500 0           require Data::Dumper;
501             $Data::Dumper::Sortkeys = 1;
502 0           $Data::Dumper::Indent = 1;
503 0           return Data::Dumper::Dumper(@_);
504 0           }
505             require YAML;
506             $YAML::UseVersion = 0;
507             return YAML::Dump(@_) . "...\n";
508 0     0 0   }
509 0            
510             sub at_line_number {
511             my ($file_path, $line_number) = (caller(1))[1,2];
512             " at $file_path line $line_number\n";
513 0     0 0   }
514 0 0          
515             sub WWW {
516             warn spiffy_dump(@_) . at_line_number;
517             return wantarray ? @_ : $_[0];
518 0     0 0   }
519              
520             sub XXX {
521             die spiffy_dump(@_) . at_line_number;
522 0     0 0   }
523 0 0          
524             sub YYY {
525             print spiffy_dump(@_) . at_line_number;
526             return wantarray ? @_ : $_[0];
527 0     0 0   }
528 0            
529             sub ZZZ {
530             require Carp;
531             Carp::confess spiffy_dump(@_);
532             }
533              
534             1;