File Coverage

blib/lib/Class/C3/Componentised.pm
Criterion Covered Total %
statement 81 87 93.1
branch 27 32 84.3
condition 4 6 66.6
subroutine 16 17 94.1
pod 7 7 100.0
total 135 149 90.6


line stmt bran cond sub pod time code
1             package Class::C3::Componentised;
2              
3             =head1 NAME
4              
5             Class::C3::Componentised - Load mix-ins or components to your C3-based class
6              
7             =head1 SYNOPSIS
8              
9             package MyModule;
10              
11             use strict;
12             use warnings;
13              
14             use base 'Class::C3::Componentised';
15              
16             sub component_base_class { "MyModule::Component" }
17              
18             package main;
19              
20             MyModule->load_components( qw/Foo Bar/ );
21             # Will load MyModule::Component::Foo and MyModule::Component::Bar
22              
23             =head1 DESCRIPTION
24              
25             This will inject base classes to your module using the L method
26             resolution order.
27              
28             Please note: these are not plugins that can take precedence over methods
29             declared in MyModule. If you want something like that, consider
30             L.
31              
32             =head1 METHODS
33              
34             =cut
35              
36 6     6   88081 use strict;
  6         18  
  6         180  
37 6     6   31 use warnings;
  6         12  
  6         151  
38              
39             # This will prime the Class::C3 namespace (either by loading it proper on 5.8
40             # or by installing compat shims on 5.10+). A user might have a reasonable
41             # expectation that using Class::C3:: will give him access to
42             # Class::C3 itself, and this module has been providing this historically.
43             # Therefore leaving it in indefinitely.
44 6     6   2682 use MRO::Compat;
  6         10913  
  6         213  
45              
46 6     6   42 use Carp ();
  6         12  
  6         85  
47 6     6   28 use List::Util ();
  6         11  
  6         2488  
48              
49             our $VERSION = '1.001_001';
50             $VERSION =~ tr/_//d;
51              
52             my $invalid_class = qr/(?: \b:\b | \:{3,} | \:\:$ )/x;
53              
54             =head2 load_components( @comps )
55              
56             Loads the given components into the current module. If a module begins with a
57             C<+> character, it is taken to be a fully qualified class name, otherwise
58             C<< $class->component_base_class >> is prepended to it.
59              
60             Calling this will call C.
61              
62             =cut
63              
64             sub load_components {
65 10     10 1 7658 my $class = shift;
66             $class->_load_components( map {
67 10 100       78 /^\+(.*)$/
68             ? $1
69             : join ('::', $class->component_base_class, $_)
70 10         25 } grep { $_ !~ /^#/ } @_
  10         38  
71             );
72             }
73              
74             =head2 load_own_components( @comps )
75              
76             Similar to L, but assumes every
77             class is C<"$class::$comp">.
78              
79             =cut
80              
81             sub load_own_components {
82 0     0 1 0 my $class = shift;
83 0         0 $class->_load_components( map { "${class}::$_" } grep { $_ !~ /^#/ } @_ );
  0         0  
  0         0  
84             }
85              
86             sub _load_components {
87 10     10   62 my $class = shift;
88 10 100       30 return unless @_;
89              
90 9         37 $class->ensure_class_loaded($_) for @_;
91 7         45 $class->inject_base($class => @_);
92 7         48 Class::C3::reinitialize();
93             }
94              
95             =head2 load_optional_components
96              
97             As L, but will silently ignore any
98             components that cannot be found.
99              
100             =cut
101              
102             sub load_optional_components {
103 2     2 1 705 my $class = shift;
104             $class->_load_components( grep
105 1         9 { $class->load_optional_class( $_ ) }
106             ( map
107 2 100       20 { /^\+(.*)$/
108             ? $1
109             : join ('::', $class->component_base_class, $_)
110             }
111 2         6 grep { $_ !~ /^#/ } @_
  2         7  
112             )
113             );
114             }
115              
116             =head2 ensure_class_loaded
117              
118             Given a class name, tests to see if it is already loaded or otherwise
119             defined. If it is not yet loaded, the package is require'd, and an exception
120             is thrown if the class is still not loaded.
121              
122             BUG: For some reason, packages with syntax errors are added to %INC on
123             require
124             =cut
125              
126             sub ensure_class_loaded {
127 19     19 1 128 my ($class, $f_class) = @_;
128              
129 6     6   44 no strict 'refs';
  6         13  
  6         1935  
130              
131             # ripped from Class::Inspector for speed
132             # note that the order is important (faster items are first)
133 19 50       30 return if ${"${f_class}::VERSION"};
  19         155  
134              
135 19 100       74 return if @{"${f_class}::ISA"};
  19         115  
136              
137 17         95 my $file = (join ('/', split ('::', $f_class) ) ) . '.pm';
138 17 50       54 return if $INC{$file};
139              
140 17         26 for ( keys %{"${f_class}::"} ) {
  17         69  
141 36 100       54 return if ( *{"${f_class}::$_"}{CODE} );
  36         122  
142             }
143              
144             # require always returns true on success
145             # ill-behaved modules might very well obliterate $_
146 14 100       28 eval { local $_; require($file) } or do {
  14         32  
  14         2829  
147              
148 8 100       589 $@ = "Invalid class name '$f_class'" if $f_class =~ $invalid_class;
149              
150 8 50       66 if ($class->can('throw_exception')) {
151 0         0 $class->throw_exception($@);
152             } else {
153 8         139 Carp::croak $@;
154             }
155             };
156              
157 6         1117 return;
158             }
159              
160             =head2 ensure_class_found
161              
162             Returns true if the specified class is installed or already loaded, false
163             otherwise.
164              
165             =cut
166              
167             sub ensure_class_found {
168             #my ($class, $f_class) = @_;
169 5     5 1 3507 require Class::Inspector;
170 5   100     24 return Class::Inspector->loaded($_[1]) ||
171             Class::Inspector->installed($_[1]);
172             }
173              
174              
175             =head2 inject_base
176              
177             Does the actual magic of adjusting C<@ISA> on the target module.
178              
179             =cut
180              
181             sub inject_base {
182 7     7 1 15 my $class = shift;
183 7         10 my $target = shift;
184              
185 7         33 mro::set_mro($target, 'c3');
186              
187 7         12 for my $comp (reverse @_) {
188 7         12 my $apply = do {
189 6     6   44 no strict 'refs';
  6         10  
  6         2295  
190 7     7   26 sub { unshift ( @{"${target}::ISA"}, $comp ) };
  7         10  
  7         136  
191             };
192 7 50 33     90 unless ($target eq $comp || $target->isa($comp)) {
193 7         12 our %APPLICATOR_FOR;
194 7 100       18 if (my $apply_class
195 8     8   26 = List::Util::first { $APPLICATOR_FOR{$_} } @{mro::get_linear_isa($comp)}
  7         46  
196             ) {
197 4         17 $APPLICATOR_FOR{$apply_class}->_apply_component_to_class($comp,$target,$apply);
198             } else {
199 3         7 $apply->();
200             }
201             }
202             }
203             }
204              
205             =head2 load_optional_class
206              
207             Returns a true value if the specified class is installed and loaded
208             successfully, throws an exception if the class is found but not loaded
209             successfully, and false if the class is not installed
210              
211             =cut
212              
213             sub load_optional_class {
214 9     9 1 4877 my ($class, $f_class) = @_;
215              
216             # ensure_class_loaded either returns a () (*not* true) or throws
217 9 100       16 eval {
218 9         26 $class->ensure_class_loaded($f_class);
219 3         14 1;
220             } && return 1;
221              
222 6         2200 my $err = $@; # so we don't lose it
223              
224 6 100       66 if ($f_class =~ $invalid_class) {
225 1         3 $err = "Invalid class name '$f_class'";
226             }
227             else {
228 5         26 my $fn = quotemeta( (join ('/', split ('::', $f_class) ) ) . '.pm' );
229 5 100       90 return 0 if ($err =~ /Can't locate ${fn} in \@INC/ );
230             }
231              
232 3 50       18 if ($class->can('throw_exception')) {
233 0         0 $class->throw_exception($err);
234             }
235             else {
236 3         16 die $err;
237             }
238             }
239              
240             =head1 AUTHORS
241              
242             Matt S. Trout and the L
243              
244             Pulled out into separate module by Ash Berlin C<< >>
245              
246             Optimizations and overall bolt-tightening by Peter "ribasushi" Rabbitson
247             C<< >>
248              
249             =head1 COPYRIGHT
250              
251             Copyright (c) 2006 - 2011 the Class::C3::Componentised L as listed
252             above.
253              
254             =head1 LICENSE
255              
256             You may distribute this code under the same terms as Perl itself.
257              
258             =cut
259              
260             1;