update Class::Inspector prereq
[p5sagit/Class-C3-Componentised.git] / lib / Class / C3 / Componentised.pm
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<Class::C3> 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<MooseX::Object::Pluggable>.
31
32 =head1 METHODS
33
34 =cut
35
36 use strict;
37 use warnings;
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::<something> will give him access to
42 # Class::C3 itself, and this module has been providing this historically.
43 # Therefore leaving it in indefinitely.
44 use MRO::Compat;
45
46 use Carp ();
47 use List::Util ();
48
49 our $VERSION = '1.001000';
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<Class::C3::reinitialize>.
61
62 =cut
63
64 sub load_components {
65   my $class = shift;
66   $class->_load_components( map {
67     /^\+(.*)$/
68       ? $1
69       : join ('::', $class->component_base_class, $_)
70     } grep { $_ !~ /^#/ } @_
71   );
72 }
73
74 =head2 load_own_components( @comps )
75
76 Similar to L<load_components>, but assumes every class is C<"$class::$comp">.
77
78 =cut
79
80 sub load_own_components {
81   my $class = shift;
82   $class->_load_components( map { "${class}::$_" } grep { $_ !~ /^#/ } @_ );
83 }
84
85 sub _load_components {
86   my $class = shift;
87   return unless @_;
88
89   $class->ensure_class_loaded($_) for @_;
90   $class->inject_base($class => @_);
91   Class::C3::reinitialize();
92 }
93
94 =head2 load_optional_components
95
96 As L<load_components>, but will silently ignore any components that cannot be
97 found.
98
99 =cut
100
101 sub load_optional_components {
102   my $class = shift;
103   $class->_load_components( grep
104     { $class->load_optional_class( $_ ) }
105     ( map
106       { /^\+(.*)$/
107           ? $1
108           : join ('::', $class->component_base_class, $_)
109       }
110       grep { $_ !~ /^#/ } @_
111     )
112   );
113 }
114
115 =head2 ensure_class_loaded
116
117 Given a class name, tests to see if it is already loaded or otherwise
118 defined. If it is not yet loaded, the package is require'd, and an exception
119 is thrown if the class is still not loaded.
120
121  BUG: For some reason, packages with syntax errors are added to %INC on
122       require
123 =cut
124
125 sub ensure_class_loaded {
126   my ($class, $f_class) = @_;
127
128   no strict 'refs';
129
130   # ripped from Class::Inspector for speed
131   # note that the order is important (faster items are first)
132   return if ${"${f_class}::VERSION"};
133
134   return if @{"${f_class}::ISA"};
135
136   my $file = (join ('/', split ('::', $f_class) ) ) . '.pm';
137   return if $INC{$file};
138
139   for ( keys %{"${f_class}::"} ) {
140     return if ( *{"${f_class}::$_"}{CODE} );
141   }
142
143   # require always returns true on success
144   # ill-behaved modules might very well obliterate $_
145   eval { local $_; require($file) } or do {
146
147     $@ = "Invalid class name '$f_class'" if $f_class =~ $invalid_class;
148
149     if ($class->can('throw_exception')) {
150       $class->throw_exception($@);
151     } else {
152       Carp::croak $@;
153     }
154   };
155
156   return;
157 }
158
159 =head2 ensure_class_found
160
161 Returns true if the specified class is installed or already loaded, false
162 otherwise.
163
164 =cut
165
166 sub ensure_class_found {
167   #my ($class, $f_class) = @_;
168   require Class::Inspector;
169   return Class::Inspector->loaded($_[1]) ||
170          Class::Inspector->installed($_[1]);
171 }
172
173
174 =head2 inject_base
175
176 Does the actual magic of adjusting @ISA on the target module.
177
178 =cut
179
180 sub inject_base {
181   my $class = shift;
182   my $target = shift;
183
184   mro::set_mro($target, 'c3');
185
186   for my $comp (reverse @_) {
187     my $apply = do {
188       no strict 'refs';
189       sub { unshift ( @{"${target}::ISA"}, $comp ) };
190     };
191     unless ($target eq $comp || $target->isa($comp)) {
192       our %APPLICATOR_FOR;
193       if (my $apply_class
194             = List::Util::first { $APPLICATOR_FOR{$_} } @{mro::get_linear_isa($comp)}
195       ) {
196         $APPLICATOR_FOR{$apply_class}->_apply_component_to_class($comp,$target,$apply);
197       } else {
198         $apply->();
199       }
200     }
201   }
202 }
203
204 =head2 load_optional_class
205
206 Returns a true value if the specified class is installed and loaded
207 successfully, throws an exception if the class is found but not loaded
208 successfully, and false if the class is not installed
209
210 =cut
211
212 sub load_optional_class {
213   my ($class, $f_class) = @_;
214
215   # ensure_class_loaded either returns a () (*not* true)  or throws
216   eval {
217    $class->ensure_class_loaded($f_class);
218    1;
219   } && return 1;
220
221   my $err = $@;   # so we don't lose it
222
223   if ($f_class =~ $invalid_class) {
224     $err = "Invalid class name '$f_class'";
225   }
226   else {
227     my $fn = quotemeta( (join ('/', split ('::', $f_class) ) ) . '.pm' );
228     return 0 if ($err =~ /Can't locate ${fn} in \@INC/ );
229   }
230
231   if ($class->can('throw_exception')) {
232     $class->throw_exception($err);
233   }
234   else {
235     die $err;
236   }
237 }
238
239 =head1 AUTHORS
240
241 Matt S. Trout and the L<DBIx::Class team|DBIx::Class/CONTRIBUTORS>
242
243 Pulled out into separate module by Ash Berlin C<< <ash@cpan.org> >>
244
245 Optimizations and overall bolt-tightening by Peter "ribasushi" Rabbitson
246 C<< <ribasushi@cpan.org> >>
247
248 =head1 COPYRIGHT
249
250 Copyright (c) 2006 - 2011 the Class::C3::Componentised L</AUTHORS> as listed
251 above.
252
253 =head1 LICENSE
254
255 You may distribute this code under the same terms as Perl itself.
256
257 =cut
258
259 1;