Add a comment
[gitmo/Mouse.git] / lib / Mouse / Util.pm
1 package Mouse::Util;
2 use strict;
3 use warnings;
4 use base qw/Exporter/;
5 use Carp qw(confess);
6 use B ();
7
8 our @EXPORT_OK = qw(
9     find_meta
10     does_role
11     resolve_metaclass_alias
12
13     load_class
14     is_class_loaded
15
16     apply_all_roles
17     not_supported
18
19     get_linear_isa
20     get_code_info
21 );
22 our %EXPORT_TAGS = (
23     all  => \@EXPORT_OK,
24 );
25
26 # Moose::Util compatible utilities
27
28 sub find_meta{
29     return Mouse::Module::class_of( $_[0] );
30 }
31
32 sub does_role{
33     my ($class_or_obj, $role) = @_;\r
34 \r
35     my $meta = Mouse::Module::class_of($class_or_obj);\r
36 \r
37     return 0 unless defined $meta;\r
38     return 1 if $meta->does_role($role);\r
39     return 0;
40 }
41
42
43
44 BEGIN {
45     my $impl;
46     if ($] >= 5.009_005) {
47         require mro;
48         $impl = \&mro::get_linear_isa;
49     } else {
50         my $e = do {
51             local $@;
52             eval { require MRO::Compat };
53             $@;
54         };
55         if (!$e) {
56             $impl = \&mro::get_linear_isa;
57         } else {
58 #       VVVVV   CODE TAKEN FROM MRO::COMPAT   VVVVV
59             my $_get_linear_isa_dfs; # this recurses so it isn't pretty
60             $_get_linear_isa_dfs = sub {
61                 no strict 'refs';
62
63                 my $classname = shift;
64
65                 my @lin = ($classname);
66                 my %stored;
67                 foreach my $parent (@{"$classname\::ISA"}) {
68                     my $plin = $_get_linear_isa_dfs->($parent);
69                     foreach  my $p(@$plin) {
70                         next if exists $stored{$p};
71                         push(@lin, $p);
72                         $stored{$p} = 1;
73                     }
74                 }
75                 return \@lin;
76             };
77 #       ^^^^^   CODE TAKEN FROM MRO::COMPAT   ^^^^^
78             $impl = $_get_linear_isa_dfs;
79         }
80     }
81
82
83     no warnings 'once';
84     *get_linear_isa = $impl;
85 }
86
87 { # taken from Sub::Identify
88     sub get_code_info($) {\r
89         my ($coderef) = @_;\r
90         ref($coderef) or return;\r
91
92         my $cv = B::svref_2object($coderef);\r
93         $cv->isa('B::CV') or return;
94
95         my $gv = $cv->GV;\r
96         $gv->isa('B::GV') or return;\r
97 \r
98         return ($gv->STASH->NAME, $gv->NAME);\r
99     }\r
100 }
101
102 # taken from Mouse::Util (0.90)
103 {
104     my %cache;
105
106     sub resolve_metaclass_alias {\r
107         my ( $type, $metaclass_name, %options ) = @_;\r
108 \r
109         my $cache_key = $type . q{ } . ( $options{trait} ? '-Trait' : '' );\r
110
111         return $cache{$cache_key}{$metaclass_name} ||= do{\r
112 \r
113             my $possible_full_name = join '::',
114                 'Mouse::Meta', $type, 'Custom', ($options{trait} ? 'Trait' : ()), $metaclass_name
115             ;
116
117             my $loaded_class = load_first_existing_class(\r
118                 $possible_full_name,\r
119                 $metaclass_name\r
120             );\r
121 \r
122             $loaded_class->can('register_implementation')\r
123                 ? $loaded_class->register_implementation\r
124                 : $loaded_class;
125         };\r
126     }
127 }
128
129 # taken from Class/MOP.pm
130 sub is_valid_class_name {
131     my $class = shift;
132
133     return 0 if ref($class);
134     return 0 unless defined($class);
135
136     return 1 if $class =~ /^\w+(?:::\w+)*$/;
137
138     return 0;
139 }
140
141 # taken from Class/MOP.pm
142 sub load_first_existing_class {
143     my @classes = @_
144       or return;
145
146     my $found;
147     my %exceptions;
148     for my $class (@classes) {
149         my $e = _try_load_one_class($class);
150
151         if ($e) {
152             $exceptions{$class} = $e;
153         }
154         else {
155             $found = $class;
156             last;
157         }
158     }
159     return $found if $found;
160
161     confess join(
162         "\n",
163         map {
164             sprintf( "Could not load class (%s) because : %s",
165                 $_, $exceptions{$_} )
166           } @classes
167     );
168 }
169
170 # taken from Class/MOP.pm
171 sub _try_load_one_class {
172     my $class = shift;
173
174     unless ( is_valid_class_name($class) ) {
175         my $display = defined($class) ? $class : 'undef';
176         confess "Invalid class name ($display)";
177     }
178
179     return if is_class_loaded($class);
180
181     my $file = $class . '.pm';
182     $file =~ s{::}{/}g;
183
184     return do {
185         local $@;
186         eval { require($file) };
187         $@;
188     };
189 }
190
191
192 sub load_class {
193     my $class = shift;
194     my $e = _try_load_one_class($class);
195     confess "Could not load class ($class) because : $e" if $e;
196
197     return 1;
198 }
199
200 my %is_class_loaded_cache;
201 sub is_class_loaded {
202     my $class = shift;
203
204     return 0 if ref($class) || !defined($class) || !length($class);
205
206     return 1 if $is_class_loaded_cache{$class};
207
208     # walk the symbol table tree to avoid autovififying
209     # \*{${main::}{"Foo::"}} == \*main::Foo::
210
211     my $pack = \%::;
212     foreach my $part (split('::', $class)) {
213         my $entry = \$pack->{$part . '::'};
214         return 0 if ref($entry) ne 'GLOB';
215         $pack = *{$entry}{HASH} or return 0;
216     }
217
218     # check for $VERSION or @ISA
219     return ++$is_class_loaded_cache{$class} if exists $pack->{VERSION}
220              && defined *{$pack->{VERSION}}{SCALAR} && defined ${ $pack->{VERSION} };
221     return ++$is_class_loaded_cache{$class} if exists $pack->{ISA}
222              && defined *{$pack->{ISA}}{ARRAY} && @{ $pack->{ISA} } != 0;
223
224     # check for any method
225     foreach my $name( keys %{$pack} ) {
226         my $entry = \$pack->{$name};
227         return ++$is_class_loaded_cache{$class} if ref($entry) ne 'GLOB' || defined *{$entry}{CODE};
228     }
229
230     # fail
231     return 0;
232 }
233
234
235 sub apply_all_roles {
236     my $meta = Mouse::Meta::Class->initialize(shift);
237
238     my @roles;
239
240     # Basis of Data::OptList
241     my $max = scalar(@_);
242     for (my $i = 0; $i < $max ; $i++) {
243         if ($i + 1 < $max && ref($_[$i + 1])) {
244             push @roles, [ $_[$i++] => $_[$i] ];
245         } else {
246             push @roles, [ $_[$i] => {} ];
247         }
248         my $role_name = $roles[-1][0];
249         load_class($role_name);
250         ( $role_name->can('meta') && $role_name->meta->isa('Mouse::Meta::Role') )
251             || $meta->throw_error("You can only consume roles, $role_name(".$role_name->meta.") is not a Mouse role");
252     }
253
254     if ( scalar @roles == 1 ) {
255         my ( $role, $params ) = @{ $roles[0] };
256         $role->meta->apply( $meta, ( defined $params ? %$params : () ) );
257     }
258     else {
259         Mouse::Meta::Role->combine_apply($meta, @roles);
260     }
261     return;
262 }
263
264 sub not_supported{
265     my($feature) = @_;
266
267     $feature ||= ( caller(1) )[3]; # subroutine name
268
269     local $Carp::CarpLevel = $Carp::CarpLevel + 2;
270     Carp::croak("Mouse does not currently support $feature");
271 }
272
273 1;
274
275 __END__
276
277 =head1 NAME
278
279 Mouse::Util - features, with or without their dependencies
280
281 =head1 IMPLEMENTATIONS FOR
282
283 =head2 L<MRO::Compat>
284
285 =head3 get_linear_isa
286
287 =cut
288