File Coverage

lib/Subclass/Of.pm
Criterion Covered Total %
statement 217 248 87.5
branch 68 106 64.1
condition 18 34 52.9
subroutine 41 45 91.1
pod 2 2 100.0
total 346 435 79.5


line stmt bran cond sub pod time code
1 5     5   89759 use 5.008;
  5         14  
2 5     5   25 use strict;
  5         6  
  5         85  
3 5     5   20 use warnings;
  5         21  
  5         137  
4 5     5   26 no strict qw(refs);
  5         8  
  5         152  
5 5     5   25 no warnings qw(redefine prototype);
  5         10  
  5         275  
6              
7             package Subclass::Of;
8              
9             BEGIN {
10 5     5   16 $Subclass::Of::AUTHORITY = 'cpan:TOBYINK';
11 5         93 $Subclass::Of::VERSION = '0.007';
12             }
13              
14 5     5   24 use B qw(perlstring);
  5         8  
  5         276  
15 5     5   23 use Carp qw(carp croak);
  5         8  
  5         229  
16 5     5   1317 use Module::Runtime qw(use_package_optimistically module_notional_filename);
  5         3790  
  5         28  
17 5     5   331 use List::Util 1.33 qw(all);
  5         101  
  5         416  
18 5     5   28 use Scalar::Util qw(refaddr blessed weaken);
  5         10  
  5         346  
19 5     5   1278 use Sub::Util qw(set_subname);
  5         676  
  5         238  
20 5     5   2026 use namespace::clean;
  5         50177  
  5         757  
21              
22             our ($SUPER_PKG, $SUPER_SUB, $SUPER_ARG);
23             our @EXPORT = qw(subclass_of);
24              
25             my $_v;
26             sub import
27             {
28 19     19   5287 my $me = shift;
29            
30 19 100       94 return $me->install(@_, -into => scalar caller) if @_;
31            
32 3         841 require Exporter::Tiny;
33 3         5750 our @ISA = "Exporter::Tiny";
34 3         10 @_ = $me;
35 3         13 goto \&Exporter::Tiny::import;
36             }
37              
38             {
39             my %i_made_this;
40             sub install
41             {
42 16     16 1 393 my $me = shift;
43 16 50       40 my $base = shift or croak "Subclass::Of what?";
44 16         35 my %opts = $me->_parse_opts(@_);
45            
46 16         44 my $caller = $opts{-into}[0];
47 16 100       81 my @aliases = $opts{-as} ? @{$opts{-as}} : ($base =~ /(\w+)$/);
  2         7  
48            
49 16         32 my $constant;
50             my $subclass;
51 16 100       37 if ($opts{-lazy}) {
52 5         8 my $current_sub;
53             $constant = sub () {
54 2   66 2   9 $subclass ||= do {
55 1         5 my $built = $me->_build_subclass($base, \%opts);
56 1         4 $i_made_this{refaddr($current_sub)} = $built;
57 1         5 $built;
58             };
59 5         17 };
60 5         16 weaken( $current_sub = $constant );
61 5         17 $i_made_this{refaddr($constant)} = '(unknown package)';
62             }
63             else {
64 11         26 $subclass = $me->_build_subclass($base, \%opts);
65 11         792 $constant = eval sprintf(q/sub () { %s if $] }/, perlstring($subclass));
66 11         60 $i_made_this{refaddr($constant)} = $subclass;
67             }
68            
69 16         31 for my $a (@aliases)
70             {
71 16 100       23 if (exists &{"$caller\::$a"})
  16         63  
72             {
73 5         7 my $old = $i_made_this{refaddr(\&{"$caller\::$a"})};
  5         17  
74 5 100       62 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 16         2576 *{"$caller\::$a"} = $constant;
  16         83  
83             }
84 16         73 "namespace::clean"->import(-cleanee => $caller, @aliases);
85             }
86            
87             sub _alias_to_package_name {
88 2 50   2   667 shift unless ref $_[0]; # allow call as class method
89 2         12 my @r = map $i_made_this{refaddr($_)}, @_;
90 2 0 33     7 croak('_alias_to_package_name(LIST) returns a list')
      33        
91             if @r != 1 and defined(wantarray) and !wantarray;
92 2 50       10 wantarray ? @r : $r[0];
93             }
94             }
95              
96             sub subclass_of
97             {
98 3 50   3 1 1245 my $base = shift or croak "Subclass::Of what?";
99 3         19 my %opts = __PACKAGE__->_parse_opts(@_);
100            
101 3         11 return __PACKAGE__->_build_subclass($base, \%opts);
102             }
103              
104             sub _parse_opts
105             {
106 19     19   32 shift;
107            
108 19 50 33     100 if (@_==1 and ref($_[0]) eq q(HASH))
109             {
110 0         0 return %{$_[0]};
  0         0  
111             }
112            
113 19         33 my %opts;
114 19         26 my $key = undef;
115 19         44 while (@_)
116             {
117 65         101 $_ = shift;
118            
119 65 100 66     385 if (defined and !ref and /^-/) {
      100        
120 35         64 $key = $_;
121 35   50     151 $opts{$key} ||= [];
122 35         83 next;
123             }
124            
125 30 100       40 push @{$opts{$key}}, ref eq q(ARRAY) ? @$_ : $_;
  30         122  
126             }
127            
128 19         81 return %opts;
129             }
130              
131             {
132             my %_detect_oo; # memoize
133             sub _detect_oo
134             {
135 18     18   2251 my $pkg = $_[0];
136            
137 18 100       62 return $_detect_oo{$pkg} if exists $_detect_oo{$pkg};
138            
139             # Use metaclass to determine the OO framework in use.
140             #
141 12 100       185 return $_detect_oo{$pkg} = ""
142             unless $pkg->can("meta");
143 3 50       11 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 15     15   27 my $me = shift;
160 15         28 my ($parent, $opts) = @_;
161            
162             my $child = (
163 15   50     150 $opts->{-package} ||= [ sprintf('%s::__SUBCLASS__::%04d', $parent, ++$count{$parent}) ]
164             )->[0];
165            
166 15         56 my $oo = _detect_oo(use_package_optimistically($parent));
167            
168 15 100       808 my $subclasser_method = $oo ? lc "_build_subclass_$oo" : "_build_subclass_raw";
169 15 100       35 my $attributes_method = $oo ? lc "_apply_attributes_$oo" : "_apply_attributes_raw";
170            
171 15         54 $me->$subclasser_method($parent, $child, $opts);
172 15         308 $me->$attributes_method($child, $opts);
173 13         313 $me->_apply_methods($child, $opts);
174 13         41 $me->_apply_roles($child, $opts);
175            
176 13         14248 my $i = 0; $i++ while caller($i) eq __PACKAGE__;
  13         54  
177 13         765 $INC{module_notional_filename($child)} = (caller($i))[1];
178            
179 13         548 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         6 my ($parent, $child, $opts) = @_;
215            
216 2     10   135 eval sprintf(q{
  1     1   7  
  1     1   1  
  1     1   4  
  1         268  
  1         11  
  1         8  
  1         7  
  1         2  
  1         6  
  1         295  
  1         2  
  1         3  
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 13     13   24 my $me = shift;
227 13         26 my ($parent, $child, $opts) = @_;
228            
229 13         17 @{"$child\::ISA"} = $parent;
  13         174  
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         3 my ($child, $opts) = @_;
251            
252 2 100       8 return unless $opts->{-has};
253            
254 1     1   50 my $raw = eval sprintf(q{
  1     1   6  
  1         2  
  1         3  
  1         253  
  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   4 my $has = sub { $raw->($_[0], %{$_[1]}) };
  3         4  
  3         10  
262            
263 1         4 $me->_apply_attributes_generic($has, $opts);
264             }
265              
266             sub _apply_attributes_raw
267             {
268 13     13   50 my $me = shift;
269 13         26 my ($child, $opts) = @_;
270            
271             my $has = sub {
272 5     5   9 my ($name, $opts) = @_;
273 5         17 for my $key (sort keys %$opts)
274             {
275 6 100       210 croak "Option '$key' in attribute specification not supported"
276             unless $key =~ /^(is|isa|default|lazy)$/;
277             }
278 4 50 33     14 if (exists $opts->{lazy} and not $opts->{lazy})
279             {
280 0         0 carp "Attribute '$name' will be lazy anyway.";
281             }
282 4 50 66     17 if (exists $opts->{is} and $opts->{is} !~ /^(ro|rw|lazy)$/)
283             {
284 0         0 croak "Option 'is' => '$opts->{is}' in attribute specification not supported"
285             }
286 4 100       9 if (exists $opts->{isa})
287             {
288             croak "Option 'isa' in attribute specification must be a blessed type constraint object with 'assert_valid' method"
289 1 50 33     107 unless blessed $opts->{isa} && $opts->{isa}->can('assert_valid');
290             }
291            
292 3         16 *{"$child\::$name"} = sub
293             {
294 3         13 my $self = shift;
295 3 50       10 if (@_)
296             {
297 0 0       0 croak "read-only accessor" unless $opts->{is} eq 'rw';
298 0 0       0 $opts->{isa}->assert_valid($_[0]) if $opts->{isa};
299 0         0 $self->{$name} = $_[0];
300             }
301 3 100 66     14 if (exists $opts->{default} and not exists $self->{$name})
302             {
303             my $tmp = ref($opts->{default}) eq q(CODE)
304             ? $opts->{default}->($self)
305 1 50       7 : $opts->{default};
306 1 50       5 $opts->{isa}->assert_valid($tmp) if $opts->{isa};
307 1         3 $self->{$name} = $tmp;
308             }
309 3         13 return $self->{$name};
310 3         15 };
311 13         57 };
312            
313 13         42 $me->_apply_attributes_generic($has, $opts);
314             }
315              
316             sub _apply_attributes_generic
317             {
318 14     14   26 my $me = shift;
319 14         23 my ($has, $opts) = @_;
320            
321 14 100       22 my @attrs = @{ $opts->{-has} || [] };
  14         81  
322 14         101 while (@attrs)
323             {
324 8         5853 my $name = shift(@attrs);
325 8 50       41 $name =~ /^\w+/ or croak("Not a valid attribute name: $name");
326            
327             my $spec =
328 8 100       40 ref($attrs[0]) eq q(ARRAY) ? +{@{shift(@attrs)}} :
  4 50       12  
    100          
329             ref($attrs[0]) eq q(HASH) ? shift(@attrs) :
330             ref($attrs[0]) eq q(CODE) ? { is => "rw", default => shift(@attrs) } :
331             { is => "rw" };
332            
333 8         18 $has->($name, $spec);
334             }
335             }
336              
337             sub _apply_methods
338             {
339 13     13   22 my $me = shift;
340 13         23 my ($pkg, $opts) = @_;
341            
342 13         33 my $methods = $me->_make_method_hash($pkg, $opts);
343 13         55 for my $name (sort keys %$methods)
344             {
345 10         19 *{"$pkg\::$name"} = $methods->{$name};
  10         41  
346             }
347             }
348              
349             sub _apply_roles
350             {
351 13     13   19 my $me = shift;
352 13         25 my ($pkg, $opts) = @_;
353 13 100       18 my @roles = map use_package_optimistically($_), @{ $opts->{-with} || [] };
  13         64  
354            
355 13 100       9429 return unless @roles;
356            
357             # All roles appear to be Role::Tiny; use Role::Tiny to
358             # handle composition.
359             #
360 2 100   2   36 if (all { _detect_oo($_) eq "" } @roles)
  2         10  
361             {
362 1         6 require Role::Tiny;
363 1         4 return "Role::Tiny"->apply_roles_to_package($pkg, @roles);
364             }
365            
366             # Otherwise, role composition is determined by the OO framework
367             # of the base class.
368             #
369 1         17 my $oo = _detect_oo($pkg);
370            
371 1 50       11 if ($oo eq "Moo")
372             {
373 1         4 return "Moo::Role"->apply_roles_to_package($pkg, @roles);
374             }
375            
376 0 0       0 if ($oo eq "Moose")
377             {
378 0         0 return Moose::Util::apply_all_roles($pkg, @roles);
379             }
380            
381 0 0       0 if ($oo eq "Mouse")
382             {
383 0         0 return Mouse::Util::apply_all_roles($pkg, @roles);
384             }
385            
386             # If all else fails, try using Moo because it understands quite
387             # a lot about Moose and Mouse.
388             #
389 0         0 require Moo::Role;
390 0         0 "Moo::Role"->apply_roles_to_package($pkg, @roles);
391             }
392              
393             sub _make_method_hash
394             {
395 13     13   18 shift;
396            
397 13         19 my $pkg = $_[0];
398 13         22 my $r = {};
399 13 100       19 my @methods = @{ $_[1]{-methods} || [] };
  13         63  
400            
401 13         39 while (@methods)
402             {
403 10         24 my ($name, $code) = splice(@methods, 0, 2);
404            
405 10 50       44 $name =~ /^\w+/ or croak("Not a valid method name: $name");
406 10 50       29 ref($code) eq q(CODE) or croak("Not a code reference: $code");
407            
408             $r->{$name} = set_subname "$pkg\::$name", sub {
409 7     7   8331 local $SUPER_PKG = $pkg;
        7      
410 7         14 local $SUPER_SUB = $name;
411 7         14 local $SUPER_ARG = \@_;
412 7         21 $code->(@_);
413 10         96 };
414             }
415            
416 13         31 return $r;
417             }
418              
419             sub ::SUPER
420             {
421 2 50   8   8 eval { require mro } or do { require MRO::Compat };
  2         17  
  0         0  
422            
423             my ($super) =
424 2         3 map { \&{ "$_\::$SUPER_SUB" } }
  2         8  
425 3         5 grep { exists &{"$_\::$SUPER_SUB"} }
  3         13  
426 5         13 grep { $_ ne $SUPER_PKG }
427 2         4 @{ mro::get_linear_isa($SUPER_PKG) };
  2         18  
428            
429 2 50       8 croak qq[Can't locate object method "$SUPER_SUB" via package "$SUPER_PKG"]
430             unless $super;
431            
432 2 50       7 @_ = @$SUPER_ARG unless @_;
433 2         7 goto $super;
434             }
435              
436             1;
437              
438             __END__