File Coverage

inc/Spiffy.pm
Criterion Covered Total %
statement 254 440 57.7
branch 80 204 39.2
condition 33 62 53.2
subroutine 38 61 62.3
pod 0 27 0.0
total 405 794 51.0


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