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