fix this broken test (oops again)
[gitmo/Moose.git] / lib / Moose / Util.pm
CommitLineData
9a641848 1package Moose::Util;
2
9a641848 3use strict;
4use warnings;
5
9ac41ce6 6use Data::OptList;
88e88a7b 7use Params::Util qw( _STRING );
7125b244 8use Sub::Exporter;
d7d8a8c7 9use Scalar::Util 'blessed';
61907a02 10use List::MoreUtils qw(any);
f5bc97e5 11use Class::MOP 0.60;
9a641848 12
b6cca0d5 13our $VERSION = '1.14';
e606ae5f 14$VERSION = eval $VERSION;
7125b244 15our $AUTHORITY = 'cpan:STEVAN';
9a641848 16
7125b244 17my @exports = qw[
d03bd989 18 find_meta
6532ca5a 19 does_role
d03bd989 20 search_class_by_role
b099a649 21 ensure_all_roles
d7d8a8c7 22 apply_all_roles
d26f5671 23 with_traits
ab76842e 24 get_all_init_args
25 get_all_attribute_values
a5e883ae 26 resolve_metatrait_alias
27 resolve_metaclass_alias
5f71050b 28 add_method_modifier
d939e016 29 english_list
27f2f43f 30 meta_attribute_alias
31 meta_class_alias
7125b244 32];
9a641848 33
7125b244 34Sub::Exporter::setup_exporter({
35 exports => \@exports,
11065d1f 36 groups => { all => \@exports }
7125b244 37});
38
39## some utils for the utils ...
40
56ea1a11 41sub find_meta { Class::MOP::class_of(@_) }
9a641848 42
7125b244 43## the functions ...
adf82331 44
7125b244 45sub does_role {
46 my ($class_or_obj, $role) = @_;
adf82331 47
6532ca5a 48 my $meta = find_meta($class_or_obj);
d03bd989 49
7125b244 50 return unless defined $meta;
10a745f5 51 return unless $meta->can('does_role');
7125b244 52 return 1 if $meta->does_role($role);
53 return;
9a641848 54}
55
1631b53f 56sub search_class_by_role {
560c498d 57 my ($class_or_obj, $role) = @_;
d03bd989 58
6532ca5a 59 my $meta = find_meta($class_or_obj);
7125b244 60
61 return unless defined $meta;
62
560c498d 63 my $role_name = blessed $role ? $role->name : $role;
64
7125b244 65 foreach my $class ($meta->class_precedence_list) {
d03bd989 66
67 my $_meta = find_meta($class);
1631b53f 68
7125b244 69 next unless defined $_meta;
70
71 foreach my $role (@{ $_meta->roles || [] }) {
1631b53f 72 return $class if $role->name eq $role_name;
73 }
74 }
75
7125b244 76 return;
1631b53f 77}
78
b099a649 79# this can possibly behave in unexpected ways because the roles being composed
80# before being applied could differ from call to call; I'm not sure if or how
81# to document this possible quirk.
82sub ensure_all_roles {
83 my $applicant = shift;
84 _apply_all_roles($applicant, sub { !does_role($applicant, $_) }, @_);
85}
86
d7d8a8c7 87sub apply_all_roles {
88 my $applicant = shift;
8d4d1cdc 89 _apply_all_roles($applicant, undef, @_);
b099a649 90}
91
92sub _apply_all_roles {
93 my $applicant = shift;
94 my $role_filter = shift;
e606ae5f 95
70ea9161 96 unless (@_) {
97 require Moose;
98 Moose->throw_error("Must specify at least one role to apply to $applicant");
99 }
e606ae5f 100
101 my $roles = Data::OptList::mkopt( [@_] );
102
560c498d 103 my @role_metas;
70ea9161 104 foreach my $role (@$roles) {
560c498d 105 my $meta;
106
107 if ( blessed $role->[0] ) {
108 $meta = $role->[0];
109 }
110 else {
2e7f6cf4 111 Class::MOP::load_class( $role->[0] , $role->[1] );
560c498d 112 $meta = Class::MOP::class_of( $role->[0] );
113 }
70ea9161 114
c8d9f1e2 115 unless ($meta && $meta->isa('Moose::Meta::Role') ) {
70ea9161 116 require Moose;
117 Moose->throw_error( "You can only consume roles, "
118 . $role->[0]
119 . " is not a Moose role" );
120 }
560c498d 121
122 push @role_metas, [ $meta, $role->[1] ];
70ea9161 123 }
e606ae5f 124
82a24871 125 if ( defined $role_filter ) {
560c498d 126 @role_metas = grep { local $_ = $_->[0]; $role_filter->() } @role_metas;
8d4d1cdc 127 }
b099a649 128
560c498d 129 return unless @role_metas;
b099a649 130
6fab0b75 131 my $meta = ( blessed $applicant ? $applicant : find_meta($applicant) );
132
560c498d 133 if ( scalar @role_metas == 1 ) {
134 my ( $role, $params ) = @{ $role_metas[0] };
135 $role->apply( $meta, ( defined $params ? %$params : () ) );
d7d8a8c7 136 }
137 else {
560c498d 138 Moose::Meta::Role->combine(@role_metas)->apply($meta);
e606ae5f 139 }
d7d8a8c7 140}
141
d26f5671 142sub with_traits {
143 my ($class, @roles) = @_;
144 return $class unless @roles;
145 return Moose::Meta::Class->create_anon_class(
146 superclasses => [$class],
147 roles => \@roles,
148 cache => 1,
149 )->name;
150}
151
ab76842e 152# instance deconstruction ...
153
154sub get_all_attribute_values {
155 my ($class, $instance) = @_;
156 return +{
157 map { $_->name => $_->get_value($instance) }
158 grep { $_->has_value($instance) }
b2df9268 159 $class->get_all_attributes
ab76842e 160 };
161}
162
163sub get_all_init_args {
164 my ($class, $instance) = @_;
165 return +{
166 map { $_->init_arg => $_->get_value($instance) }
167 grep { $_->has_value($instance) }
d03bd989 168 grep { defined($_->init_arg) }
b2df9268 169 $class->get_all_attributes
ab76842e 170 };
171}
172
50fbbf3d 173sub resolve_metatrait_alias {
50fbbf3d 174 return resolve_metaclass_alias( @_, trait => 1 );
a3738e5b 175}
176
27f2f43f 177sub _build_alias_package_name {
178 my ($type, $name, $trait) = @_;
179 return 'Moose::Meta::'
180 . $type
181 . '::Custom::'
182 . ( $trait ? 'Trait::' : '' )
183 . $name;
184}
185
50fbbf3d 186{
187 my %cache;
a3738e5b 188
50fbbf3d 189 sub resolve_metaclass_alias {
190 my ( $type, $metaclass_name, %options ) = @_;
a3738e5b 191
50fbbf3d 192 my $cache_key = $type . q{ } . ( $options{trait} ? '-Trait' : '' );
193 return $cache{$cache_key}{$metaclass_name}
194 if $cache{$cache_key}{$metaclass_name};
195
27f2f43f 196 my $possible_full_name = _build_alias_package_name(
197 $type, $metaclass_name, $options{trait}
198 );
50fbbf3d 199
200 my $loaded_class = Class::MOP::load_first_existing_class(
201 $possible_full_name,
202 $metaclass_name
203 );
204
205 return $cache{$cache_key}{$metaclass_name}
206 = $loaded_class->can('register_implementation')
207 ? $loaded_class->register_implementation
208 : $loaded_class;
209 }
a3738e5b 210}
211
5f71050b 212sub add_method_modifier {
213 my ( $class_or_obj, $modifier_name, $args ) = @_;
d5447d26 214 my $meta
215 = $class_or_obj->can('add_before_method_modifier')
216 ? $class_or_obj
217 : find_meta($class_or_obj);
5f71050b 218 my $code = pop @{$args};
219 my $add_modifier_method = 'add_' . $modifier_name . '_method_modifier';
220 if ( my $method_modifier_type = ref( @{$args}[0] ) ) {
221 if ( $method_modifier_type eq 'Regexp' ) {
e606ae5f 222 my @all_methods = $meta->get_all_methods;
5f71050b 223 my @matched_methods
e606ae5f 224 = grep { $_->name =~ @{$args}[0] } @all_methods;
225 $meta->$add_modifier_method( $_->name, $code )
5f71050b 226 for @matched_methods;
227 }
775666aa 228 elsif ($method_modifier_type eq 'ARRAY') {
229 $meta->$add_modifier_method( $_, $code ) for @{$args->[0]};
230 }
231 else {
232 $meta->throw_error(
233 sprintf(
234 "Methods passed to %s must be provided as a list, arrayref or regex, not %s",
235 $modifier_name,
236 $method_modifier_type,
237 )
238 );
239 }
5f71050b 240 }
241 else {
242 $meta->$add_modifier_method( $_, $code ) for @{$args};
243 }
244}
d9bb6c63 245
d939e016 246sub english_list {
247 my @items = sort @_;
248
249 return $items[0] if @items == 1;
250 return "$items[0] and $items[1]" if @items == 2;
251
252 my $tail = pop @items;
253 my $list = join ', ', @items;
254 $list .= ', and ' . $tail;
255
256 return $list;
257}
258
833b56a7 259sub _caller_info {
260 my $level = @_ ? ($_[0] + 1) : 2;
261 my %info;
262 @info{qw(package file line)} = caller($level);
263 return \%info;
264}
265
27f2f43f 266sub _create_alias {
267 my ($type, $name, $trait, $for) = @_;
268 my $package = _build_alias_package_name($type, $name, $trait);
269 Class::MOP::Class->initialize($package)->add_method(
270 register_implementation => sub { $for }
271 );
272}
273
274sub meta_attribute_alias {
275 my ($to, $from) = @_;
276 $from ||= caller;
277 my $meta = Class::MOP::class_of($from);
278 my $trait = $meta->isa('Moose::Meta::Role');
279 _create_alias('Attribute', $to, $trait, $from);
280}
281
282sub meta_class_alias {
283 my ($to, $from) = @_;
284 $from ||= caller;
285 my $meta = Class::MOP::class_of($from);
286 my $trait = $meta->isa('Moose::Meta::Role');
287 _create_alias('Class', $to, $trait, $from);
288}
289
88e88a7b 290# XXX - this should be added to Params::Util
291sub _STRINGLIKE ($) {
292 return _STRING( $_[0] )
293 || ( blessed $_[0]
294 && overload::Method( $_[0], q{""} )
295 && length "$_[0]" );
296}
297
61907a02 298sub _reconcile_roles_for_metaclass {
299 my ($class_meta_name, $super_meta_name) = @_;
300
301 my @role_differences = _role_differences(
302 $class_meta_name, $super_meta_name,
303 );
304
305 # handle the case where we need to fix compatibility between a class and
306 # its parent, but all roles in the class are already also done by the
307 # parent
308 # see t/050/054.t
309 return $super_meta_name
310 unless @role_differences;
311
312 return Moose::Meta::Class->create_anon_class(
313 superclasses => [$super_meta_name],
314 roles => [map { $_->name } @role_differences],
315 cache => 1,
316 )->name;
317}
318
319sub _role_differences {
320 my ($class_meta_name, $super_meta_name) = @_;
321 my @super_role_metas = $super_meta_name->meta->can('calculate_all_roles_with_inheritance')
322 ? $super_meta_name->meta->calculate_all_roles_with_inheritance
cfa43121 323 : $super_meta_name->meta->can('calculate_all_roles')
324 ? $super_meta_name->meta->calculate_all_roles
61907a02 325 : ();
326 my @role_metas = $class_meta_name->meta->can('calculate_all_roles_with_inheritance')
327 ? $class_meta_name->meta->calculate_all_roles_with_inheritance
cfa43121 328 : $class_meta_name->meta->can('calculate_all_roles')
329 ? $class_meta_name->meta->calculate_all_roles
61907a02 330 : ();
331 my @differences;
332 for my $role_meta (@role_metas) {
333 push @differences, $role_meta
334 unless any { $_->name eq $role_meta->name } @super_role_metas;
335 }
336 return @differences;
337}
338
9a641848 3391;
340
341__END__
342
343=pod
344
345=head1 NAME
346
7125b244 347Moose::Util - Utilities for working with Moose classes
9a641848 348
349=head1 SYNOPSIS
350
6532ca5a 351 use Moose::Util qw/find_meta does_role search_class_by_role/;
352
353 my $meta = find_meta($object) || die "No metaclass found";
9a641848 354
adf82331 355 if (does_role($object, $role)) {
356 print "The object can do $role!\n";
9a641848 357 }
358
1631b53f 359 my $class = search_class_by_role($object, 'FooRole');
360 print "Nearest class with 'FooRole' is $class\n";
361
7125b244 362=head1 DESCRIPTION
363
2c3bf4e7 364This module provides a set of utility functions. Many of these
365functions are intended for use in Moose itself or MooseX modules, but
366some of them may be useful for use in your own code.
7125b244 367
368=head1 EXPORTED FUNCTIONS
9a641848 369
370=over 4
371
2c3bf4e7 372=item B<find_meta($class_or_obj)>
373
374This method takes a class name or object and attempts to find a
3ff98e47 375metaclass for the class, if one exists. It will B<not> create one if it
2c3bf4e7 376does not yet exist.
377
560c498d 378=item B<does_role($class_or_obj, $role_or_obj)>
2c3bf4e7 379
560c498d 380Returns true if C<$class_or_obj> does the given C<$role_or_obj>. The role can
381be provided as a name or a L<Moose::Meta::Role> object.
6532ca5a 382
560c498d 383The class must already have a metaclass for this to work. If it doesn't, this
384function simply returns false.
6532ca5a 385
560c498d 386=item B<search_class_by_role($class_or_obj, $role_or_obj)>
7125b244 387
2c3bf4e7 388Returns the first class in the class's precedence list that does
560c498d 389C<$role_or_obj>, if any. The role can be either a name or a
390L<Moose::Meta::Role> object.
7125b244 391
2c3bf4e7 392The class must already have a metaclass for this to work.
7125b244 393
2c3bf4e7 394=item B<apply_all_roles($applicant, @roles)>
7125b244 395
2c3bf4e7 396This function applies one or more roles to the given C<$applicant> The
397applicant can be a role name, class name, or object.
d7d8a8c7 398
2c3bf4e7 399The C<$applicant> must already have a metaclass object.
400
560c498d 401The list of C<@roles> should a list of names or L<Moose::Meta::Role> objects,
402each of which can be followed by an optional hash reference of options
403(C<-excludes> and C<-alias>).
d7d8a8c7 404
b099a649 405=item B<ensure_all_roles($applicant, @roles)>
406
407This function is similar to L</apply_all_roles>, but only applies roles that
408C<$applicant> does not already consume.
409
d26f5671 410=item B<with_traits($class_name, @role_names)>
411
412This function creates a new class from C<$class_name> with each of
413C<@role_names> applied. It returns the name of the new class.
414
ab76842e 415=item B<get_all_attribute_values($meta, $instance)>
416
2c3bf4e7 417Returns a hash reference containing all of the C<$instance>'s
418attributes. The keys are attribute names.
ab76842e 419
420=item B<get_all_init_args($meta, $instance)>
421
2c3bf4e7 422Returns a hash reference containing all of the C<init_arg> values for
423the instance's attributes. The values are the associated attribute
424values. If an attribute does not have a defined C<init_arg>, it is
425skipped.
426
427This could be useful in cloning an object.
ab76842e 428
a3738e5b 429=item B<resolve_metaclass_alias($category, $name, %options)>
430
431=item B<resolve_metatrait_alias($category, $name, %options)>
432
2c3bf4e7 433Resolves a short name to a full class name. Short names are often used
434when specifying the C<metaclass> or C<traits> option for an attribute:
a3738e5b 435
436 has foo => (
437 metaclass => "Bar",
438 );
439
8a8856de 440The name resolution mechanism is covered in
441L<Moose/Metaclass and Trait Name Resolution>.
5f71050b 442
27f2f43f 443=item B<meta_class_alias($to[, $from])>
444
445=item B<meta_attribute_alias($to[, $from])>
446
447Create an alias from the class C<$from> (or the current package, if
448C<$from> is unspecified), so that
449L<Moose/Metaclass and Trait Name Resolution> works properly.
450
57385c0d 451=item B<english_list(@items)>
452
453Given a list of scalars, turns them into a proper list in English
454("one and two", "one, two, three, and four"). This is used to help us
455make nicer error messages.
456
7125b244 457=back
9a641848 458
7125b244 459=head1 TODO
9a641848 460
7125b244 461Here is a list of possible functions to write
9a641848 462
7125b244 463=over 4
1631b53f 464
7125b244 465=item discovering original method from modified method
1631b53f 466
7125b244 467=item search for origin class of a method or attribute
1631b53f 468
9a641848 469=back
470
471=head1 BUGS
472
d4048ef3 473See L<Moose/BUGS> for details on reporting bugs.
9a641848 474
475=head1 AUTHOR
476
477Anders Nor Berle E<lt>debolaz@gmail.comE<gt>
478
7125b244 479B<with contributions from:>
480
481Robert (phaylon) Sedlacek
482
483Stevan Little
484
9a641848 485=head1 COPYRIGHT AND LICENSE
486
2840a3b2 487Copyright 2007-2009 by Infinity Interactive, Inc.
9a641848 488
489L<http://www.iinteractive.com>
490
491This library is free software; you can redistribute it and/or modify
b60c9fa0 492it under the same terms as Perl itself.
9a641848 493
494=cut
495