Add an arg check to appply_metaroles()
[gitmo/Mouse.git] / lib / Mouse / Util / MetaRole.pm
CommitLineData
f87debb9 1package Mouse::Util::MetaRole;
2use Mouse::Util; # enables strict and warnings
733f404b 3use Scalar::Util ();
f87debb9 4
f87debb9 5sub apply_metaclass_roles {
733f404b 6 my %args = @_;
7 _fixup_old_style_args(\%args);
8
9 return apply_metaroles(%args);
10}
11
12sub apply_metaroles {
13 my %args = @_;
14
15 my $for = Scalar::Util::blessed($args{for})
16 ? $args{for}
17 : Mouse::Util::get_metaclass_by_name( $args{for} );
18
add53fbe 19 if(!$for){
20 Carp::confess("You must pass an initialized class, but '$args{for}' has no metaclass");
21 }
22
733f404b 23 if ( Mouse::Util::is_a_metarole($for) ) {
24 return _make_new_metaclass( $for, $args{role_metaroles}, 'role' );
25 }
26 else {
27 return _make_new_metaclass( $for, $args{class_metaroles}, 'class' );
28 }
29}
30
31sub _make_new_metaclass {
32 my($for, $roles, $primary) = @_;
33
34 return $for unless keys %{$roles};
35
36 my $new_metaclass = exists($roles->{$primary})
37 ? _make_new_class( ref $for, $roles->{$primary} ) # new class with traits
38 : ref $for;
39
40 my %classes;
41
42 for my $key ( grep { $_ ne $primary } keys %{$roles} ) {
43 my $metaclass;
44 my $attr = $for->can($metaclass = ($key . '_metaclass'))
45 || $for->can($metaclass = ($key . '_class'))
46 || $for->throw_error("Unknown metaclass '$key'");
f87debb9 47
733f404b 48 $classes{ $metaclass }
49 = _make_new_class( $for->$attr(), $roles->{$key} );
50 }
51
52 return $new_metaclass->reinitialize( $for, %classes );
53}
54
55
56sub _fixup_old_style_args {
57 my $args = shift;
58
59 return if $args->{class_metaroles} || $args->{roles_metaroles};
60
61 $args->{for} = delete $args->{for_class}
62 if exists $args->{for_class};
63
64 my @old_keys = qw(
65 attribute_metaclass_roles
66 method_metaclass_roles
67 wrapped_method_metaclass_roles
68 instance_metaclass_roles
69 constructor_class_roles
70 destructor_class_roles
71 error_class_roles
f87debb9 72
733f404b 73 application_to_class_class_roles
74 application_to_role_class_roles
75 application_to_instance_class_roles
76 application_role_summation_class_roles
d9659f80 77 );
f87debb9 78
733f404b 79 my $for = Scalar::Util::blessed($args->{for})
80 ? $args->{for}
81 : Mouse::Util::get_metaclass_by_name( $args->{for} );
f87debb9 82
733f404b 83 my $top_key;
84 if( Mouse::Util::is_a_metaclass($for) ){
85 $top_key = 'class_metaroles';
f87debb9 86
733f404b 87 $args->{class_metaroles}{class} = delete $args->{metaclass_roles}
88 if exists $args->{metaclass_roles};
f87debb9 89 }
733f404b 90 else {
91 $top_key = 'role_metaroles';
f87debb9 92
733f404b 93 $args->{role_metaroles}{role} = delete $args->{metaclass_roles}
94 if exists $args->{metaclass_roles};
95 }
96
97 for my $old_key (@old_keys) {
98 my ($new_key) = $old_key =~ /^(.+)_(?:class|metaclass)_roles$/;
99
100 $args->{$top_key}{$new_key} = delete $args->{$old_key}
101 if exists $args->{$old_key};
102 }
103
104 return;
f87debb9 105}
106
733f404b 107
f87debb9 108sub apply_base_class_roles {
109 my %options = @_;
110
111 my $for = $options{for_class};
112
113 my $meta = Mouse::Util::class_of($for);
114
115 my $new_base = _make_new_class(
116 $for,
117 $options{roles},
118 [ $meta->superclasses() ],
119 );
120
121 $meta->superclasses($new_base)
122 if $new_base ne $meta->name();
123 return;
124}
125
f87debb9 126sub _make_new_class {
127 my($existing_class, $roles, $superclasses) = @_;
128
d9659f80 129 if(!$superclasses){
130 return $existing_class if !$roles;
f87debb9 131
d9659f80 132 my $meta = Mouse::Meta::Class->initialize($existing_class);
f87debb9 133
d9659f80 134 return $existing_class
135 if !grep { !ref($_) && !$meta->does_role($_) } @{$roles};
136 }
f87debb9 137
138 return Mouse::Meta::Class->create_anon_class(
139 superclasses => $superclasses ? $superclasses : [$existing_class],
140 roles => $roles,
141 cache => 1,
142 )->name();
143}
144
1451;
f87debb9 146__END__
147
148=head1 NAME
149
150Mouse::Util::MetaRole - Apply roles to any metaclass, as well as the object base class
151
152=head1 SYNOPSIS
153
154 package MyApp::Mouse;
155
156 use Mouse ();
157 use Mouse::Exporter;
158 use Mouse::Util::MetaRole;
159
160 use MyApp::Role::Meta::Class;
161 use MyApp::Role::Meta::Method::Constructor;
162 use MyApp::Role::Object;
163
164 Mouse::Exporter->setup_import_methods( also => 'Mouse' );
165
166 sub init_meta {
167 shift;
241dd25c 168 my %args = @_;
f87debb9 169
241dd25c 170 Mouse->init_meta(%args);
f87debb9 171
241dd25c 172 Mouse::Util::MetaRole::apply_metaroles(
173 for => $args{for_class},
174 class_metaroles => {
175 class => ['MyApp::Role::Meta::Class'],
176 constructor => ['MyApp::Role::Meta::Method::Constructor'],
177 },
f87debb9 178 );
179
180 Mouse::Util::MetaRole::apply_base_class_roles(
241dd25c 181 for => $args{for_class},
182 roles => ['MyApp::Role::Object'],
f87debb9 183 );
184
241dd25c 185 return $args{for_class}->meta();
f87debb9 186 }
187
188=head1 DESCRIPTION
189
190This utility module is designed to help authors of Mouse extensions
191write extensions that are able to cooperate with other Mouse
192extensions. To do this, you must write your extensions as roles, which
193can then be dynamically applied to the caller's metaclasses.
194
195This module makes sure to preserve any existing superclasses and roles
196already set for the meta objects, which means that any number of
197extensions can apply roles in any order.
198
199=head1 USAGE
200
201B<It is very important that you only call this module's functions when
202your module is imported by the caller>. The process of applying roles
203to the metaclass reinitializes the metaclass object, which wipes out
204any existing attributes already defined. However, as long as you do
205this when your module is imported, the caller should not have any
206attributes defined yet.
207
208The easiest way to ensure that this happens is to use
209L<Mouse::Exporter>, which can generate the appropriate C<init_meta>
210method for you, and make sure it is called when imported.
211
212=head1 FUNCTIONS
213
214This module provides two functions.
215
241dd25c 216=head2 apply_metaroles( ... )
f87debb9 217
218This function will apply roles to one or more metaclasses for the
219specified class. It accepts the following parameters:
220
221=over 4
222
241dd25c 223=item * for => $name
f87debb9 224
241dd25c 225This specifies the class or for which to alter the meta classes. This can be a
226package name, or an appropriate meta-object (a L<Mouse::Meta::Class> or
227L<Mouse::Meta::Role>).
f87debb9 228
241dd25c 229=item * class_metaroles => \%roles
f87debb9 230
241dd25c 231This is a hash reference specifying which metaroles will be applied to the
232class metaclass and its contained metaclasses and helper classes.
f87debb9 233
241dd25c 234Each key should in turn point to an array reference of role names.
f87debb9 235
241dd25c 236It accepts the following keys:
f87debb9 237
241dd25c 238=over 8
f87debb9 239
241dd25c 240=item class
241
242=item attribute
243
244=item method
245
246=item constructor
247
248=item destructor
249
250=back
251
252=item * role_metaroles => \%roles
253
254This is a hash reference specifying which metaroles will be applied to the
255role metaclass and its contained metaclasses and helper classes.
256
257It accepts the following keys:
258
259=over 8
260
261=item role
262
263=item method
264
265=back
f87debb9 266
267=back
268
241dd25c 269=head2 apply_base_class_roles( for => $class, roles => \@roles )
f87debb9 270
271This function will apply the specified roles to the object's base class.
272
a9cf013d 273=head1 SEE ALSO
f87debb9 274
275L<Moose::Util::MetaRole>
276
277=cut