File Coverage

lib/Subclass/Of.pm
Criterion Covered Total %
statement 238 273 87.1
branch 75 120 62.5
condition 24 49 48.9
subroutine 41 45 91.1
pod 2 2 100.0
total 380 489 77.7


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