File Coverage

lib/Subclass/Of.pm
Criterion Covered Total %
statement 239 276 86.5
branch 75 120 62.5
condition 25 52 48.0
subroutine 41 45 91.1
pod 2 2 100.0
total 382 495 77.1


line stmt bran cond sub pod time code
1 6     6   361807 use 5.008;
  6         57  
2 6     6   30 use strict;
  6         7  
  6         120  
3 6     6   23 use warnings;
  6         9  
  6         209  
4 6     6   35 no strict qw(refs);
  6         10  
  6         202  
5 6     6   28 no warnings qw(redefine prototype);
  6         10  
  6         381  
6              
7             package Subclass::Of;
8              
9             BEGIN {
10 6     6   28 $Subclass::Of::AUTHORITY = 'cpan:TOBYINK';
11 6         162 $Subclass::Of::VERSION = '0.009';
12             }
13              
14 6     6   35 use B qw(perlstring);
  6         7  
  6         362  
15 6     6   34 use Carp qw(carp croak);
  6         9  
  6         343  
16 6     6   2070 use Module::Runtime qw(use_package_optimistically module_notional_filename);
  6         6970  
  6         31  
17 6     6   481 use List::Util 1.33 qw(all);
  6         123  
  6         598  
18 6     6   39 use Scalar::Util qw(refaddr blessed weaken);
  6         9  
  6         304  
19 6     6   1721 use Sub::Util qw(set_subname);
  6         1092  
  6         333  
20 6     6   2744 use namespace::clean;
  6         71742  
  6         41  
21              
22             our ($SUPER_PKG, $SUPER_SUB, $SUPER_ARG);
23             our @EXPORT = qw(subclass_of);
24              
25             my $_v;
26             sub import
27             {
28 20     20   5239 my $me = shift;
29            
30 20 100       124 return $me->install(@_, -into => scalar caller) if @_;
31            
32 3         837 require Exporter::Tiny;
33 3         6328 our @ISA = "Exporter::Tiny";
34 3         12 @_ = $me;
35 3         12 goto \&Exporter::Tiny::import;
36             }
37              
38             {
39             my %i_made_this;
40             sub install
41             {
42 17     17 1 413 my $me = shift;
43 17 50       52 my $base = shift or croak "Subclass::Of what?";
44 17         41 my %opts = $me->_parse_opts(@_);
45            
46 17         37 my $caller = $opts{-into}[0];
47 17 100       95 my @aliases = $opts{-as} ? @{$opts{-as}} : ($base =~ /(\w+)$/);
  3         7  
48            
49 17         29 my $constant;
50             my $subclass;
51 17 100       35 if ($opts{-lazy}) {
52 5         7 my $current_sub;
53             $constant = sub () {
54 2   66 2   10 $subclass ||= do {
55 1         8 my $built = $me->_build_subclass($base, \%opts);
56 1         5 $i_made_this{refaddr($current_sub)} = $built;
57 1         6 $built;
58             };
59 5         20 };
60 5         23 weaken( $current_sub = $constant );
61 5         18 $i_made_this{refaddr($constant)} = '(unknown package)';
62             }
63             else {
64 12         37 $subclass = $me->_build_subclass($base, \%opts);
65 12         1082 $constant = eval sprintf(q/sub () { %s if $] }/, perlstring($subclass));
66 12         71 $i_made_this{refaddr($constant)} = $subclass;
67             }
68            
69 17         39 for my $a (@aliases)
70             {
71 17 100       21 if (exists &{"$caller\::$a"})
  17         86  
72             {
73 5         5 my $old = $i_made_this{refaddr(\&{"$caller\::$a"})};
  5         16  
74 5 100       75 carp(
    100          
    100          
75             $old
76             ? "Subclass::Of is overwriting alias '$a'"
77             .($old eq '(unknown package)'?"":"; was '$old'")
78             .($subclass?"; now '$subclass'":"")
79             : "Subclass::Of is overwriting function '$a'",
80             );
81             }
82 17         2722 *{"$caller\::$a"} = $constant;
  17         137  
83             }
84 17         86 "namespace::clean"->import(-cleanee => $caller, @aliases);
85             }
86            
87             sub _alias_to_package_name {
88 2 50   2   873 shift unless ref $_[0]; # allow call as class method
89 2         14 my @r = map $i_made_this{refaddr($_)}, @_;
90 2 0 33     8 croak('_alias_to_package_name(LIST) returns a list')
      33        
91             if @r != 1 and defined(wantarray) and !wantarray;
92 2 50       12 wantarray ? @r : $r[0];
93             }
94             }
95              
96             sub subclass_of
97             {
98 3 50   3 1 1395 my $base = shift or croak "Subclass::Of what?";
99 3         16 my %opts = __PACKAGE__->_parse_opts(@_);
100            
101 3         10 return __PACKAGE__->_build_subclass($base, \%opts);
102             }
103              
104             sub _parse_opts
105             {
106 20     20   29 shift;
107            
108 20 50 33     64 if (@_==1 and ref($_[0]) eq q(HASH))
109             {
110 0         0 return %{$_[0]};
  0         0  
111             }
112            
113 20         27 my %opts;
114 20         30 my $key = undef;
115 20         46 while (@_)
116             {
117 71         97 $_ = shift;
118            
119 71 100 66     362 if (defined and !ref and /^-/) {
      100        
120 38         59 $key = $_;
121 38   50     147 $opts{$key} ||= [];
122 38         115 next;
123             }
124            
125 33 100       58 push @{$opts{$key}}, ref eq q(ARRAY) ? @$_ : $_;
  33         148  
126             }
127            
128 20         91 return %opts;
129             }
130              
131             {
132             my %_detect_oo; # memoize
133             sub _detect_oo
134             {
135 19     19   3136 my $pkg = $_[0];
136            
137 19 100       60 return $_detect_oo{$pkg} if exists $_detect_oo{$pkg};
138            
139             # Use metaclass to determine the OO framework in use.
140             #
141 13 100       172 return $_detect_oo{$pkg} = ""
142             unless $pkg->can("meta");
143 3 50       10 return $_detect_oo{$pkg} = "Moo"
144             if ref($pkg->meta) eq "Moo::HandleMoose::FakeMetaClass";
145 0 0       0 return $_detect_oo{$pkg} = "Mouse"
146             if $pkg->meta->isa("Mouse::Meta::Module");
147 0 0       0 return $_detect_oo{$pkg} = "Moose"
148             if $pkg->meta->isa("Moose::Meta::Class");
149 0 0       0 return $_detect_oo{$pkg} = "Moose"
150             if $pkg->meta->isa("Moose::Meta::Role");
151 0         0 return $_detect_oo{$pkg} = "";
152             }
153             }
154              
155             {
156             my %count;
157             sub _build_subclass
158             {
159 16     16   26 my $me = shift;
160 16         29 my ($parent, $opts) = @_;
161            
162             my $child = (
163 16   50     151 $opts->{-package} ||= [ sprintf('%s::__SUBCLASS__::%04d', $parent, ++$count{$parent}) ]
164             )->[0];
165            
166 16         62 my $oo = _detect_oo(use_package_optimistically($parent));
167            
168 16 100       956 my $subclasser_method = $oo ? lc "_build_subclass_$oo" : "_build_subclass_raw";
169 16 100       34 my $attributes_method = $oo ? lc "_apply_attributes_$oo" : "_apply_attributes_raw";
170            
171 16         63 $me->$subclasser_method($parent, $child, $opts);
172 16         406 $me->$attributes_method($child, $opts);
173 14         341 $me->_apply_methods($child, $opts);
174 14         43 $me->_apply_roles($child, $opts);
175            
176 14         17616 my $i = 0; $i++ while caller($i) eq __PACKAGE__;
  14         61  
177 14         764 $INC{module_notional_filename($child)} = (caller($i))[1];
178            
179 14         598 return $child;
180             }
181             }
182              
183             sub _build_subclass_moose
184             {
185 0     0   0 my $me = shift;
186 0         0 my ($parent, $child, $opts) = @_;
187            
188             # "Moose::Meta::Class"->initialize($child, superclasses => [$parent]);
189            
190 0         0 eval sprintf(q{
191             package %s;
192             use Moose;
193             extends %s;
194             use namespace::clean;
195             }, $child, perlstring($parent));
196             }
197              
198             sub _build_subclass_mouse
199             {
200 0     0   0 my $me = shift;
201 0         0 my ($parent, $child, $opts) = @_;
202            
203 0         0 eval sprintf(q{
204             package %s;
205             use Mouse;
206             extends %s;
207             use namespace::clean;
208             }, $child, perlstring($parent));
209             }
210              
211             sub _build_subclass_moo
212             {
213 2     2   3 my $me = shift;
214 2         4 my ($parent, $child, $opts) = @_;
215            
216 2     12   141 eval sprintf(q{
  1     4   8  
  1     4   2  
  1     1   4  
  1         355  
  1         2  
  1         5  
  1         8  
  1         2  
  1         15  
  1         374  
  1         2  
  1         4  
217             package %s;
218             use Moo;
219             extends %s;
220             use namespace::clean;
221             }, $child, perlstring($parent));
222             }
223              
224             sub _build_subclass_raw
225             {
226 14     14   21 my $me = shift;
227 14         29 my ($parent, $child, $opts) = @_;
228            
229 14         22 @{"$child\::ISA"} = $parent;
  14         332  
230             }
231              
232             sub _apply_attributes_moose
233             {
234 0     0   0 my $me = shift;
235 0         0 my ($child, $opts) = @_;
236            
237 0 0       0 return unless $opts->{-has};
238            
239 0         0 my $meta = $child->meta;
240 0     0   0 my $has = sub { $meta->add_attribute(@_) };
  0         0  
241            
242 0         0 $me->_apply_attributes_generic($has, $opts);
243             }
244              
245             *_apply_attributes_mouse = \&_apply_attributes_moose;
246              
247             sub _apply_attributes_moo
248             {
249 2     2   5 my $me = shift;
250 2         4 my ($child, $opts) = @_;
251            
252 2 100       6 return unless $opts->{-has};
253            
254 1     1   66 my $raw = eval sprintf(q{
  1     1   7  
  1         2  
  1         4  
  1         282  
  1         2  
  1         4  
255             package %s;
256             use Moo;
257             my $sub = \&has;
258             use namespace::clean;
259             return $sub;
260             }, $child);
261 1     3   5 my $has = sub { $raw->($_[0], %{$_[1]}) };
  3         5  
  3         12  
262            
263 1         4 $me->_apply_attributes_generic($has, $opts);
264             }
265              
266             my $fieldhash;
267             sub _apply_attributes_raw
268             {
269 14     14   26 my $me = shift;
270 14         27 my ($child, $opts) = @_;
271            
272             my $has = sub {
273 6     6   11 my ($name, $opts) = @_;
274 6         25 for my $key (sort keys %$opts)
275             {
276 8 100       264 croak "Option '$key' in attribute specification not supported"
277             unless $key =~ /^(is|isa|default|lazy|fieldhash)$/;
278             }
279 5 50 33     18 if (exists $opts->{lazy} and not $opts->{lazy})
280             {
281 0         0 carp "Attribute '$name' will be lazy anyway.";
282             }
283 5 50 66     32 if (exists $opts->{is} and $opts->{is} !~ /^(ro|rw|lazy)$/)
284             {
285 0         0 croak "Option 'is' => '$opts->{is}' in attribute specification not supported"
286             }
287 5 100       11 if (exists $opts->{isa})
288             {
289             croak "Option 'isa' in attribute specification must be a blessed type constraint object with 'assert_valid' method"
290 1 50 33     122 unless blessed $opts->{isa} && $opts->{isa}->can('assert_valid');
291             }
292            
293 4         5 my $code;
294 4 100 66     11 if (exists $opts->{fieldhash} and $opts->{fieldhash})
295             {
296 1   33     3 $fieldhash ||= do {
297 1         2 my $impl;
298 1   33     2 $impl ||= eval { require Hash::FieldHash; 'Hash::FieldHash' };
  1         272  
  0         0  
299 1   33     5 $impl ||= eval { require Hash::Util::FieldHash; 'Hash::Util::FieldHash' };
  1         528  
  1         895  
300 1   33     6 $impl ||= do { require Hash::Util::FieldHash::Compat; 'Hash::Util::FieldHash::Compat' };
  0         0  
  0         0  
301 1         10 $impl->can('fieldhash');
302             };
303 1         2 my %data;
304 1         3 $fieldhash->(\%data);
305            
306             $code = sub
307             {
308 2         552 my $self = shift;
309 2 100       6 if (@_)
310             {
311 1 50       5 croak "read-only accessor" unless $opts->{is} eq 'rw';
312 1 50       4 $opts->{isa}->assert_valid($_[0]) if $opts->{isa};
313 1         10 $data{$self} = $_[0];
314             }
315 2 50 33     8 if (exists $opts->{default} and not exists $self->{$name})
316             {
317             my $tmp = ref($opts->{default}) eq q(CODE)
318             ? $opts->{default}->($self)
319 0 0       0 : $opts->{default};
320 0 0       0 $opts->{isa}->assert_valid($tmp) if $opts->{isa};
321 0         0 $data{$self} = $tmp;
322             }
323 2         8 $data{$self};
324 1         16 };
325             }
326             else
327             {
328             $code = sub
329             {
330 3         15 my $self = shift;
331 3 50       10 if (@_)
332             {
333 0 0       0 croak "read-only accessor" unless $opts->{is} eq 'rw';
334 0 0       0 $opts->{isa}->assert_valid($_[0]) if $opts->{isa};
335 0         0 $self->{$name} = $_[0];
336             }
337 3 100 66     12 if (exists $opts->{default} and not exists $self->{$name})
338             {
339             my $tmp = ref($opts->{default}) eq q(CODE)
340             ? $opts->{default}->($self)
341 1 50       8 : $opts->{default};
342 1 50       6 $opts->{isa}->assert_valid($tmp) if $opts->{isa};
343 1         2 $self->{$name} = $tmp;
344             }
345 3         20 $self->{$name};
346 3         12 };
347             }
348            
349 4         28 *{"$child\::$name"} = set_subname("$child\::$name", $code);
  4         36  
350 14         94 };
351            
352 14         41 $me->_apply_attributes_generic($has, $opts);
353             }
354              
355             sub _apply_attributes_generic
356             {
357 15     15   22 my $me = shift;
358 15         27 my ($has, $opts) = @_;
359            
360 15 100       21 my @attrs = @{ $opts->{-has} || [] };
  15         70  
361 15         152 while (@attrs)
362             {
363 9         6962 my $name = shift(@attrs);
364 9 50       37 $name =~ /^\w+/ or croak("Not a valid attribute name: $name");
365            
366             my $spec =
367 9 100       48 ref($attrs[0]) eq q(ARRAY) ? +{@{shift(@attrs)}} :
  5 50       14  
    100          
368             ref($attrs[0]) eq q(HASH) ? shift(@attrs) :
369             ref($attrs[0]) eq q(CODE) ? { is => "rw", default => shift(@attrs) } :
370             { is => "rw" };
371            
372 9         19 $has->($name, $spec);
373             }
374             }
375              
376             sub _apply_methods
377             {
378 14     14   22 my $me = shift;
379 14         26 my ($pkg, $opts) = @_;
380            
381 14         33 my $methods = $me->_make_method_hash($pkg, $opts);
382 14         66 for my $name (sort keys %$methods)
383             {
384 10         17 *{"$pkg\::$name"} = $methods->{$name};
  10         61  
385             }
386             }
387              
388             sub _apply_roles
389             {
390 14     14   31 my $me = shift;
391 14         26 my ($pkg, $opts) = @_;
392 14 100       19 my @roles = map use_package_optimistically($_), @{ $opts->{-with} || [] };
  14         66  
393            
394 14 100       10486 return unless @roles;
395            
396             # All roles appear to be Role::Tiny; use Role::Tiny to
397             # handle composition.
398             #
399 2 100   2   13 if (all { _detect_oo($_) eq "" } @roles)
  2         6  
400             {
401 1         6 require Role::Tiny;
402 1         6 return "Role::Tiny"->apply_roles_to_package($pkg, @roles);
403             }
404            
405             # Otherwise, role composition is determined by the OO framework
406             # of the base class.
407             #
408 1         16 my $oo = _detect_oo($pkg);
409            
410 1 50       12 if ($oo eq "Moo")
411             {
412 1         3 return "Moo::Role"->apply_roles_to_package($pkg, @roles);
413             }
414            
415 0 0       0 if ($oo eq "Moose")
416             {
417 0         0 return Moose::Util::apply_all_roles($pkg, @roles);
418             }
419            
420 0 0       0 if ($oo eq "Mouse")
421             {
422 0         0 return Mouse::Util::apply_all_roles($pkg, @roles);
423             }
424            
425             # If all else fails, try using Moo because it understands quite
426             # a lot about Moose and Mouse.
427             #
428 0         0 require Moo::Role;
429 0         0 "Moo::Role"->apply_roles_to_package($pkg, @roles);
430             }
431              
432             sub _make_method_hash
433             {
434 14     14   17 shift;
435            
436 14         22 my $pkg = $_[0];
437 14         21 my $r = {};
438 14 100       17 my @methods = @{ $_[1]{-methods} || [] };
  14         64  
439            
440 14         38 while (@methods)
441             {
442 10         22 my ($name, $code) = splice(@methods, 0, 2);
443            
444 10 50       42 $name =~ /^\w+/ or croak("Not a valid method name: $name");
445 10 50       31 ref($code) eq q(CODE) or croak("Not a code reference: $code");
446            
447             $r->{$name} = set_subname "$pkg\::$name", sub {
448 7     7   7031 local $SUPER_PKG = $pkg;
        7      
449 7         14 local $SUPER_SUB = $name;
450 7         14 local $SUPER_ARG = \@_;
451 7         52 $code->(@_);
452 10         107 };
453             }
454            
455 14         29 return $r;
456             }
457              
458             sub ::SUPER
459             {
460 2 50   8   24 eval { require mro } or do { require MRO::Compat };
  2         18  
  0         0  
461            
462             my ($super) =
463 2         3 map { \&{ "$_\::$SUPER_SUB" } }
  2         10  
464 3         5 grep { exists &{"$_\::$SUPER_SUB"} }
  3         13  
465 5         11 grep { $_ ne $SUPER_PKG }
466 2         5 @{ mro::get_linear_isa($SUPER_PKG) };
  2         9  
467            
468 2 50       17 croak qq[Can't locate object method "$SUPER_SUB" via package "$SUPER_PKG"]
469             unless $super;
470            
471 2 50       9 @_ = @$SUPER_ARG unless @_;
472 2         12 goto $super;
473             }
474              
475             1;
476              
477             __END__