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 0 27 0.0
total 472 793 59.5


line stmt bran cond sub pod time code
1 40     40   191 use strict; use warnings;
  40     40   49  
  40         1307  
  40         232  
  40         82  
  40         2647  
2             package Spiffy;
3             our $VERSION = '0.46';
4              
5 40     40   275 use Carp;
  40         138  
  40         12675  
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 1309 ($_[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 220 my $class = shift;
33 223   33     484 $class = ref($class) || $class;
34 223         278 my $self = bless {}, $class;
35 223         367 while (@_) {
36 0         0 my $method = shift;
37 0         0 $self->$method(shift);
38             }
39 223         486 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   282 no strict 'refs';
  40         58  
  40         1508  
48 40     40   158 no warnings;
  40         76  
  40         54389  
49 256     256   644 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         302 my ($args, @export_list) = do {
55             local *boolean_arguments = sub {
56 256     256   670 qw(
57             -base -Base -mixin -selfless
58             -XXX -dumper -yaml
59             -filter_dump -filter_save
60             )
61 256         1668 };
62 256     256   753 local *paired_arguments = sub { qw(-package) };
  256         339  
63 256         888 $self_package->parse_arguments(@_);
64             };
65             return spiffy_mixin_import(scalar(caller(0)), $self_package, @export_list)
66 256 50       686 if $args->{-mixin};
67              
68 256 50       426 $filter_dump = 1 if $args->{-filter_dump};
69 256 50       376 $filter_save = 1 if $args->{-filter_save};
70 256 50       403 $dump = 'yaml' if $args->{-yaml};
71 256 50       389 $dump = 'dumper' if $args->{-dumper};
72              
73 256         659 local @EXPORT_BASE = @EXPORT_BASE;
74              
75 256 50       412 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     1785 not $filtered_files->{(caller($stack_frame))[1]}++;
      100        
83              
84 256   33     2106 my $caller_package = $args->{-package} || caller($stack_frame);
85 144         1482 push @{"$caller_package\::ISA"}, $self_package
86 256 100 66     704 if $args->{-Base} or $args->{-base};
87              
88 256         335 for my $class (@{all_my_bases($self_package)}) {
  256         457  
89 408 50       1716 next unless $class->isa('Spiffy');
90             my @export = grep {
91 4776         3270 not defined &{"$caller_package\::$_"};
  4776         9993  
92 408         2482 } ( @{"$class\::EXPORT"},
93             ($args->{-Base} or $args->{-base})
94 408 100 66     421 ? @{"$class\::EXPORT_BASE"} : (),
  216         497  
95             );
96             my @export_ok = grep {
97 2304         1671 not defined &{"$caller_package\::$_"};
  2304         4272  
98 408         414 } @{"$class\::EXPORT_OK"};
  408         830  
99              
100             # Avoid calling the expensive Exporter::export
101             # if there is nothing to do (optimization)
102 408         522 my %exportable = map { ($_, 1) } @export, @export_ok;
  6472         7555  
103 408 100       995 next unless keys %exportable;
104              
105 376         339 my @export_save = @{"$class\::EXPORT"};
  376         1022  
106 376         327 my @export_ok_save = @{"$class\::EXPORT_OK"};
  376         790  
107 376         385 @{"$class\::EXPORT"} = @export;
  376         1184  
108 376         380 @{"$class\::EXPORT_OK"} = @export_ok;
  376         783  
109             my @list = grep {
110 376         505 (my $v = $_) =~ s/^[\!\:]//;
  288         662  
111 288 100       596 $exportable{$v} or ${"$class\::EXPORT_TAGS"}{$v};
  216         611  
112             } @export_list;
113 376         28210 Exporter::export($class, $caller_package, @list);
114 376         690 @{"$class\::EXPORT"} = @export_save;
  376         1070  
115 376         358 @{"$class\::EXPORT_OK"} = @export_ok_save;
  376         31199  
116             }
117             }
118              
119             sub spiffy_filter {
120 112     112 0 19835 require Filter::Util::Call;
121 112         32827 my $done = 0;
122             Filter::Util::Call::filter_add(
123             sub {
124 224 100   224   7272 return 0 if $done;
125 112         209 my ($data, $end) = ('', '');
126 112         1093 while (my $status = Filter::Util::Call::filter_read()) {
127 41896 50       39751 return $status if $status < 0;
128 41896 50       42564 if (/^__(?:END|DATA)__\r?$/) {
129 0         0 $end = $_;
130 0         0 last;
131             }
132 41896         32596 $data .= $_;
133 41896         56933 $_ = '';
134             }
135 112         1338 $_ = $data;
136 112         179 my @my_subs;
137 112         7837 s[^(sub\s+\w+\s+\{)(.*\n)]
138             [${1}my \$self = shift;$2]gm;
139 112         6436 s[^(sub\s+\w+)\s*\(\s*\)(\s+\{.*\n)]
140             [${1}${2}]gm;
141 112         2009 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         581 my $preclare = '';
144 112 50       467 if (@my_subs) {
145 0         0 $preclare = join ',', map "\$$_", @my_subs;
146 0         0 $preclare = "my($preclare);";
147             }
148 112         396 $_ = "use strict;use warnings;$preclare${_};1;\n$end";
149 112 50       276 if ($filter_dump) { print; exit }
  0         0  
  0         0  
150 112 50       207 if ($filter_save) { $filter_result = $_; $_ = $filter_result; }
  0         0  
  0         0  
151 112         5042 $done = 1;
152             }
153 112         1073 );
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 405 my $class = shift;
163              
164             return $bases_map->{$class}
165 368 100       1019 if defined $bases_map->{$class};
166              
167 152         271 my @bases = ($class);
168 40     40   285 no strict 'refs';
  40         53  
  40         7343  
169 152         201 for my $base_class (@{"${class}::ISA"}) {
  152         556  
170 112         141 push @bases, @{all_my_bases($base_class)};
  112         284  
171             }
172 152         182 my $used = {};
173 152         230 $bases_map->{$class} = [grep {not $used->{$_}++} @bases];
  304         1174  
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 1165 my $package = caller;
202 712         634 my ($args, @values) = do {
203 40     40   197 no warnings;
  40         46  
  40         12212  
204 712     712   2590 local *boolean_arguments = sub { (qw(-weak)) };
  712         1061  
205 712     712   1710 local *paired_arguments = sub { (qw(-package -init)) };
  712         912  
206 712         1590 Spiffy->parse_arguments(@_);
207             };
208 712         1177 my ($field, $default) = @values;
209 712 50       1145 $package = $args->{-package} if defined $args->{-package};
210             die "Cannot have a default for a weakened field ($field)"
211 712 50 66     1340 if defined $default && $args->{-weak};
212 712 50       555 return if defined &{"${package}::$field"};
  712         3002  
213 712 50       942 require Scalar::Util if $args->{-weak};
214 712 100 100     2571 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         1031 my $code = $code{sub_start};
222 712 100       1152 if ($args->{-init}) {
223 160 50       317 my $fragment = $args->{-weak} ? $code{weak_init} : $code{init};
224 160         694 my @count = ($fragment =~ /(%s)/g);
225 160         611 $code .= sprintf $fragment, $field, $args->{-init}, ($field) x (@count - 2);
226             }
227 712 100       1491 $code .= sprintf $code{set_default}, $field, $default_string, $field
228             if defined $default;
229 712         1182 $code .= sprintf $code{return_if_get}, $field;
230 712         970 $code .= sprintf $code{set}, $field;
231             $code .= sprintf $code{weaken}, $field, $field
232 712 50       955 if $args->{-weak};
233 712         926 $code .= sprintf $code{sub_end}, $field;
234              
235 712 100 100 41   69915 my $sub = eval $code;
  41 100 100     135  
  22 100 100     59  
  2 100 100     8  
  21 100       45  
  32 100       99  
  19 100       38  
  33 100       133  
  37 100       95  
  52 100       237  
  7 100       16  
  42 100       167  
  8 100       23  
  7 100       10  
  3 100       62  
  14 100       53  
  7 100       27  
  3 100       17  
  22 100       63  
  11 100       30  
  67 100       264  
  19 100       42  
  13 100       31  
  20 100       54  
  16 100       91  
  25 100       96  
  24 100       43  
  43 100       132  
  20 100       47  
  9 100       14  
  29         93  
  14         45  
  21         45  
  34         87  
  17         46  
  14         38  
  80         340  
  16         54  
  13         44  
  8         60  
  20         43  
  25         60  
  27         47  
  43         63  
  40         79  
  36         64  
  9         19  
  55         207  
  14         35  
  34         67  
  39         92  
  29         55  
  16         32  
  11         19  
  82         309  
  26         66  
  20         41  
  10         17  
  13         24  
  19         42  
  13         32  
  6         13  
  8         36  
  8         17  
  6         20  
  50         87  
  48         67  
  45         70  
  32         64  
  35         113  
  7         12  
  12         24  
  92         149  
  83         133  
  83         95  
236 712 50       1484 die $@ if $@;
237 40     40   303 no strict 'refs';
  40         61  
  40         10205  
238 712         659 *{"${package}::$field"} = $sub;
  712         3546  
239 712 50       2317 return $code if defined wantarray;
240             }
241              
242             sub default_as_code {
243 592     592 0 25075 require Data::Dumper;
244 592         281995 local $Data::Dumper::Sortkeys = 1;
245 592         1246 my $code = Data::Dumper::Dumper(shift);
246 592         25133 $code =~ s/^\$VAR1 = //;
247 592         1619 $code =~ s/;$//;
248 592         1011 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   258 no warnings;
  40         69  
  40         3956  
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   166 no strict 'refs';
  40         66  
  40         5361  
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   201 no warnings;
  40         70  
  40         4378  
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   178 no strict 'refs';
  40         76  
  40         23324  
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 1080 my $class = shift;
286 968         1502 my ($args, @values) = ({}, ());
287 968         1699 my %booleans = map { ($_, 1) } $class->boolean_arguments;
  3016         4560  
288 968         1831 my %pairs = map { ($_, 1) } $class->paired_arguments;
  1680         2456  
289 968         1952 while (@_) {
290 1480         1448 my $elem = shift;
291 1480 100 66     6469 if (defined $elem and defined $booleans{$elem}) {
    100 66        
      66        
292 144 50 33     603 $args->{$elem} = (@_ and $_[0] =~ /^[01]$/)
293             ? shift
294             : 1;
295             }
296             elsif (defined $elem and defined $pairs{$elem} and @_) {
297 160         421 $args->{$elem} = shift;
298             }
299             else {
300 1176         2183 push @values, $elem;
301             }
302             }
303 968 50       6244 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   248 no warnings 'redefine';
  40         50  
  40         7665  
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   195 no strict 'refs';
  40         46  
  40         6886  
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   184 require base unless defined $INC{'base.pm'};
370 40   50     231 $INC{'mixin.pm'} ||= 'Spiffy/mixin.pm';
371 40         67 $real_base_import = \&base::import;
372 40         85 $real_mixin_import = \&mixin::import;
373 40     40   219 no warnings;
  40         65  
  40         2080  
374 40         252 *base::import = \&spiffy_base_import;
375 40         1615 *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 277 my @base_classes = @_;
389 80         146 shift @base_classes;
390 40     40   169 no strict 'refs';
  40         62  
  40         11015  
391             goto &$real_base_import
392             unless grep {
393 80 50       165 eval "require $_" unless %{"$_\::"};
  80 50       1081  
  80         856  
394 80         13742 $_->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   214 no strict 'refs';
  40         64  
  40         1220  
425 40     40   151 no warnings;
  40         74  
  40         5855  
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   181 no strict 'refs';
  40         46  
  40         16946  
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   264 no strict 'refs';
  40         126  
  40         7400  
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   209 no warnings;
  40         53  
  40         12223  
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;