use warnings;
use Data::OptList;
+use Params::Util qw( _STRING );
use Sub::Exporter;
use Scalar::Util 'blessed';
-use Class::MOP 0.60;
-
-our $VERSION = '0.88';
-$VERSION = eval $VERSION;
-our $AUTHORITY = 'cpan:STEVAN';
+use List::Util qw(first);
+use List::MoreUtils qw(any all);
+use overload ();
+use Class::MOP;
my @exports = qw[
find_meta
search_class_by_role
ensure_all_roles
apply_all_roles
+ with_traits
get_all_init_args
get_all_attribute_values
resolve_metatrait_alias
}
sub search_class_by_role {
- my ($class_or_obj, $role_name) = @_;
+ my ($class_or_obj, $role) = @_;
my $meta = find_meta($class_or_obj);
return unless defined $meta;
+ my $role_name = blessed $role ? $role->name : $role;
+
foreach my $class ($meta->class_precedence_list) {
my $_meta = find_meta($class);
Moose->throw_error("Must specify at least one role to apply to $applicant");
}
- my $roles = Data::OptList::mkopt( [@_] );
-
+ # If @_ contains role meta objects, mkopt will think that they're values,
+ # because they're references. In other words (roleobj1, roleobj2,
+ # roleobj3) will become [ [ roleobj1, roleobj2 ], [ roleobj3, undef ] ]
+ # -- this is no good. We'll preprocess @_ first to eliminate the potential
+ # bug.
+ # -- rjbs, 2011-04-08
+ my $roles = Data::OptList::mkopt( [@_], {
+ moniker => 'role',
+ name_test => sub {
+ ! ref $_[0] or blessed($_[0]) && $_[0]->isa('Moose::Meta::Role')
+ }
+ });
+
+ my @role_metas;
foreach my $role (@$roles) {
- Class::MOP::load_class( $role->[0] );
- my $meta = Class::MOP::class_of( $role->[0] );
+ my $meta;
+
+ if ( blessed $role->[0] ) {
+ $meta = $role->[0];
+ }
+ else {
+ Class::MOP::load_class( $role->[0] , $role->[1] );
+ $meta = Class::MOP::class_of( $role->[0] );
+ }
unless ($meta && $meta->isa('Moose::Meta::Role') ) {
require Moose;
. $role->[0]
. " is not a Moose role" );
}
+
+ push @role_metas, [ $meta, $role->[1] ];
}
if ( defined $role_filter ) {
- @$roles = grep { local $_ = $_->[0]; $role_filter->() } @$roles;
+ @role_metas = grep { local $_ = $_->[0]; $role_filter->() } @role_metas;
}
- return unless @$roles;
+ return unless @role_metas;
my $meta = ( blessed $applicant ? $applicant : find_meta($applicant) );
- if ( scalar @$roles == 1 ) {
- my ( $role, $params ) = @{ $roles->[0] };
- my $role_meta = Class::MOP::class_of($role);
- $role_meta->apply( $meta, ( defined $params ? %$params : () ) );
+ if ( scalar @role_metas == 1 ) {
+ my ( $role, $params ) = @{ $role_metas[0] };
+ $role->apply( $meta, ( defined $params ? %$params : () ) );
}
else {
- Moose::Meta::Role->combine( @$roles )->apply($meta);
+ Moose::Meta::Role->combine(@role_metas)->apply($meta);
}
}
+sub with_traits {
+ my ($class, @roles) = @_;
+ return $class unless @roles;
+ return Moose::Meta::Class->create_anon_class(
+ superclasses => [$class],
+ roles => \@roles,
+ cache => 1,
+ )->name;
+}
+
# instance deconstruction ...
sub get_all_attribute_values {
sub add_method_modifier {
my ( $class_or_obj, $modifier_name, $args ) = @_;
- my $meta = find_meta($class_or_obj);
+ my $meta
+ = $class_or_obj->can('add_before_method_modifier')
+ ? $class_or_obj
+ : find_meta($class_or_obj);
my $code = pop @{$args};
my $add_modifier_method = 'add_' . $modifier_name . '_method_modifier';
if ( my $method_modifier_type = ref( @{$args}[0] ) ) {
$meta->$add_modifier_method( $_->name, $code )
for @matched_methods;
}
+ elsif ($method_modifier_type eq 'ARRAY') {
+ $meta->$add_modifier_method( $_, $code ) for @{$args->[0]};
+ }
+ else {
+ $meta->throw_error(
+ sprintf(
+ "Methods passed to %s must be provided as a list, arrayref or regex, not %s",
+ $modifier_name,
+ $method_modifier_type,
+ )
+ );
+ }
}
else {
$meta->$add_modifier_method( $_, $code ) for @{$args};
_create_alias('Class', $to, $trait, $from);
}
+# XXX - this should be added to Params::Util
+sub _STRINGLIKE0 ($) {
+ return _STRING( $_[0] )
+ || ( defined $_[0]
+ && $_[0] eq q{} )
+ || ( blessed $_[0]
+ && overload::Method( $_[0], q{""} )
+ && length "$_[0]" );
+}
+
+sub _reconcile_roles_for_metaclass {
+ my ($class_meta_name, $super_meta_name) = @_;
+
+ my @role_differences = _role_differences(
+ $class_meta_name, $super_meta_name,
+ );
+
+ # handle the case where we need to fix compatibility between a class and
+ # its parent, but all roles in the class are already also done by the
+ # parent
+ # see t/metaclasses/metaclass_compat_no_fixing_bug.t
+ return $super_meta_name
+ unless @role_differences;
+
+ return Moose::Meta::Class->create_anon_class(
+ superclasses => [$super_meta_name],
+ roles => [map { $_->name } @role_differences],
+ cache => 1,
+ )->name;
+}
+
+sub _role_differences {
+ my ($class_meta_name, $super_meta_name) = @_;
+ my @super_role_metas
+ = grep { !$_->isa('Moose::Meta::Role::Composite') }
+ $super_meta_name->meta->can('calculate_all_roles_with_inheritance')
+ ? $super_meta_name->meta->calculate_all_roles_with_inheritance
+ : $super_meta_name->meta->can('calculate_all_roles')
+ ? $super_meta_name->meta->calculate_all_roles
+ : ();
+ my @role_metas
+ = grep { !$_->isa('Moose::Meta::Role::Composite') }
+ $class_meta_name->meta->can('calculate_all_roles_with_inheritance')
+ ? $class_meta_name->meta->calculate_all_roles_with_inheritance
+ : $class_meta_name->meta->can('calculate_all_roles')
+ ? $class_meta_name->meta->calculate_all_roles
+ : ();
+ my @differences;
+ for my $role_meta (@role_metas) {
+ push @differences, $role_meta
+ unless any { $_->name eq $role_meta->name } @super_role_metas;
+ }
+ return @differences;
+}
+
+sub _classes_differ_by_roles_only {
+ my ( $self_meta_name, $super_meta_name ) = @_;
+
+ my $common_base_name
+ = _find_common_base( $self_meta_name, $super_meta_name );
+
+ return unless defined $common_base_name;
+
+ my @super_meta_name_ancestor_names
+ = _get_ancestors_until( $super_meta_name, $common_base_name );
+ my @class_meta_name_ancestor_names
+ = _get_ancestors_until( $self_meta_name, $common_base_name );
+
+ return
+ unless all { _is_role_only_subclass($_) }
+ @super_meta_name_ancestor_names,
+ @class_meta_name_ancestor_names;
+
+ return 1;
+}
+
+sub _find_common_base {
+ my ($meta1, $meta2) = map { Class::MOP::class_of($_) } @_;
+ return unless defined $meta1 && defined $meta2;
+
+ # FIXME? This doesn't account for multiple inheritance (not sure
+ # if it needs to though). For example, if somewhere in $meta1's
+ # history it inherits from both ClassA and ClassB, and $meta2
+ # inherits from ClassB & ClassA, does it matter? And what crazy
+ # fool would do that anyway?
+
+ my %meta1_parents = map { $_ => 1 } $meta1->linearized_isa;
+
+ return first { $meta1_parents{$_} } $meta2->linearized_isa;
+}
+
+sub _get_ancestors_until {
+ my ($start_name, $until_name) = @_;
+
+ my @ancestor_names;
+ for my $ancestor_name (Class::MOP::class_of($start_name)->linearized_isa) {
+ last if $ancestor_name eq $until_name;
+ push @ancestor_names, $ancestor_name;
+ }
+ return @ancestor_names;
+}
+
+sub _is_role_only_subclass {
+ my ($meta_name) = @_;
+ my $meta = Class::MOP::Class->initialize($meta_name);
+ my @parent_names = $meta->superclasses;
+
+ # XXX: don't feel like messing with multiple inheritance here... what would
+ # that even do?
+ return unless @parent_names == 1;
+ my ($parent_name) = @parent_names;
+ my $parent_meta = Class::MOP::Class->initialize($parent_name);
+
+ # only get the roles attached to this particular class, don't look at
+ # superclasses
+ my @roles = $meta->can('calculate_all_roles')
+ ? $meta->calculate_all_roles
+ : ();
+
+ # it's obviously not a role-only subclass if it doesn't do any roles
+ return unless @roles;
+
+ # loop over all methods that are a part of the current class
+ # (not inherited)
+ for my $method ( $meta->_get_local_methods ) {
+ # always ignore meta
+ next if $method->isa('Class::MOP::Method::Meta');
+ # we'll deal with attributes below
+ next if $method->can('associated_attribute');
+ # if the method comes from a role we consumed, ignore it
+ next if $meta->can('does_role')
+ && $meta->does_role($method->original_package_name);
+ # FIXME - this really isn't right. Just because a modifier is
+ # defined in a role doesn't mean it isn't _also_ defined in the
+ # subclass.
+ next if $method->isa('Class::MOP::Method::Wrapped')
+ && (
+ (!scalar($method->around_modifiers)
+ || any { $_->has_around_method_modifiers($method->name) } @roles)
+ && (!scalar($method->before_modifiers)
+ || any { $_->has_before_method_modifiers($method->name) } @roles)
+ && (!scalar($method->after_modifiers)
+ || any { $_->has_after_method_modifiers($method->name) } @roles)
+ );
+
+ return 0;
+ }
+
+ # loop over all attributes that are a part of the current class
+ # (not inherited)
+ # FIXME - this really isn't right. Just because an attribute is
+ # defined in a role doesn't mean it isn't _also_ defined in the
+ # subclass.
+ for my $attr (map { $meta->get_attribute($_) } $meta->get_attribute_list) {
+ next if any { $_->has_attribute($attr->name) } @roles;
+
+ return 0;
+ }
+
+ return 1;
+}
+
1;
+# ABSTRACT: Utilities for working with Moose classes
+
__END__
=pod
-=head1 NAME
-
-Moose::Util - Utilities for working with Moose classes
-
=head1 SYNOPSIS
use Moose::Util qw/find_meta does_role search_class_by_role/;
metaclass for the class, if one exists. It will B<not> create one if it
does not yet exist.
-=item B<does_role($class_or_obj, $role_name)>
+=item B<does_role($class_or_obj, $role_or_obj)>
-Returns true if C<$class_or_obj> does the given C<$role_name>.
+Returns true if C<$class_or_obj> does the given C<$role_or_obj>. The role can
+be provided as a name or a L<Moose::Meta::Role> object.
-The class must already have a metaclass for this to work.
+The class must already have a metaclass for this to work. If it doesn't, this
+function simply returns false.
-=item B<search_class_by_role($class_or_obj, $role_name)>
+=item B<search_class_by_role($class_or_obj, $role_or_obj)>
Returns the first class in the class's precedence list that does
-C<$role_name>, if any.
+C<$role_or_obj>, if any. The role can be either a name or a
+L<Moose::Meta::Role> object.
The class must already have a metaclass for this to work.
The C<$applicant> must already have a metaclass object.
-The list of C<@roles> should be a list of names, each of which can be
-followed by an optional hash reference of options (C<excludes> and
-C<alias>).
+The list of C<@roles> should a list of names or L<Moose::Meta::Role> objects,
+each of which can be followed by an optional hash reference of options
+(C<-excludes> and C<-alias>).
=item B<ensure_all_roles($applicant, @roles)>
This function is similar to L</apply_all_roles>, but only applies roles that
C<$applicant> does not already consume.
+=item B<with_traits($class_name, @role_names)>
+
+This function creates a new class from C<$class_name> with each of
+C<@role_names> applied. It returns the name of the new class.
+
=item B<get_all_attribute_values($meta, $instance)>
Returns a hash reference containing all of the C<$instance>'s
The name resolution mechanism is covered in
L<Moose/Metaclass and Trait Name Resolution>.
-=item B<english_list(@items)>
-
-Given a list of scalars, turns them into a proper list in English
-("one and two", "one, two, three, and four"). This is used to help us
-make nicer error messages.
-
=item B<meta_class_alias($to[, $from])>
=item B<meta_attribute_alias($to[, $from])>
C<$from> is unspecified), so that
L<Moose/Metaclass and Trait Name Resolution> works properly.
+=item B<english_list(@items)>
+
+Given a list of scalars, turns them into a proper list in English
+("one and two", "one, two, three, and four"). This is used to help us
+make nicer error messages.
+
=back
=head1 TODO
=head1 BUGS
-All complex software has bugs lurking in it, and this module is no
-exception. If you find a bug please either email me, or add the bug
-to cpan-RT.
-
-=head1 AUTHOR
-
-Anders Nor Berle E<lt>debolaz@gmail.comE<gt>
-
-B<with contributions from:>
-
-Robert (phaylon) Sedlacek
-
-Stevan Little
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2007-2009 by Infinity Interactive, Inc.
-
-L<http://www.iinteractive.com>
-
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself.
+See L<Moose/BUGS> for details on reporting bugs.
=cut