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