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