Remove the & prototype hack now Moose::Exporter doesn't throw prototypes away anymore.
[gitmo/MooseX-Role-Parameterized.git] / lib / MooseX / Role / Parameterized.pm
CommitLineData
7b42fc96 1package MooseX::Role::Parameterized;
fc4a95b6 2use Moose (
3 extends => { -as => 'moose_extends' },
7557429d 4 around => { -as => 'moose_around' },
2293e5f1 5 qw/confess blessed/,
fc4a95b6 6);
767906ab 7moose_extends 'Moose::Exporter';
fc4a95b6 8
7b42fc96 9use Moose::Role ();
7b42fc96 10
d93bd54d 11use MooseX::Role::Parameterized::Meta::Role::Parameterizable;
5b82ffb1 12
a457ed60 13our $CURRENT_METACLASS;
4534bdce 14
5b82ffb1 15__PACKAGE__->setup_import_methods(
ff2ccd89 16 with_caller => ['parameter', 'role', 'method', 'has', 'with', 'extends',
17 'requires', 'excludes', 'augment', 'inner', 'before',
18 'after', 'around', 'super', 'override'],
19 as_is => [ 'confess', 'blessed' ],
19af6e75 20);
21
22sub parameter {
23 my $caller = shift;
4f3cfe3f 24
25 confess "'parameter' may not be used inside of the role block"
26 if $CURRENT_METACLASS;
27
ff2ccd89 28 my $meta = Class::MOP::Class->initialize($caller);
bd3dd853 29
ff2ccd89 30 my $names = shift;
bd3dd853 31 $names = [$names] if !ref($names);
32
33 for my $name (@$names) {
ff2ccd89 34 $meta->add_parameter($name, @_);
bd3dd853 35 }
19af6e75 36}
7b42fc96 37
e8e8ef39 38sub role (&) {
5b82ffb1 39 my $caller = shift;
40 my $role_generator = shift;
9a21e637 41 Class::MOP::Class->initialize($caller)->role_generator($role_generator);
5b82ffb1 42}
43
7b42fc96 44sub init_meta {
45 my $self = shift;
46
47 return Moose::Role->init_meta(@_,
d93bd54d 48 metaclass => 'MooseX::Role::Parameterized::Meta::Role::Parameterizable',
7b42fc96 49 );
50}
51
a457ed60 52sub has {
ff2ccd89 53 my $caller = shift;
54 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
a457ed60 55
209e00d2 56 my $names = shift;
a457ed60 57 $names = [$names] if !ref($names);
58
59 for my $name (@$names) {
ff2ccd89 60 $meta->add_attribute($name, @_);
a457ed60 61 }
62}
63
209e00d2 64sub method {
209e00d2 65 my $caller = shift;
ff2ccd89 66 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
67
209e00d2 68 my $name = shift;
69 my $body = shift;
70
ff2ccd89 71 my $method = $meta->method_metaclass->wrap(
209e00d2 72 package_name => $caller,
73 name => $name,
74 body => $body,
75 );
76
ff2ccd89 77 $meta->add_method($name => $method);
209e00d2 78}
79
03c4551d 80sub before {
ff2ccd89 81 my $caller = shift;
82 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 83
84 my $code = pop @_;
85
86 for (@_) {
767906ab 87 Carp::croak "Roles do not currently support "
03c4551d 88 . ref($_)
89 . " references for before method modifiers"
90 if ref $_;
ff2ccd89 91 $meta->add_before_method_modifier($_, $code);
03c4551d 92 }
93}
94
95sub after {
ff2ccd89 96 my $caller = shift;
97 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 98
99 my $code = pop @_;
100
101 for (@_) {
767906ab 102 Carp::croak "Roles do not currently support "
03c4551d 103 . ref($_)
104 . " references for after method modifiers"
105 if ref $_;
ff2ccd89 106 $meta->add_after_method_modifier($_, $code);
03c4551d 107 }
108}
109
110sub around {
ff2ccd89 111 my $caller = shift;
112 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 113
114 my $code = pop @_;
115
116 for (@_) {
767906ab 117 Carp::croak "Roles do not currently support "
03c4551d 118 . ref($_)
119 . " references for around method modifiers"
120 if ref $_;
ff2ccd89 121 $meta->add_around_method_modifier($_, $code);
03c4551d 122 }
123}
124
d55c8861 125sub with {
ff2ccd89 126 my $caller = shift;
127 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
128
129 Moose::Util::apply_all_roles($meta, @_);
d55c8861 130}
131
eac6d242 132sub requires {
ff2ccd89 133 my $caller = shift;
134 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
135
767906ab 136 Carp::croak "Must specify at least one method" unless @_;
ff2ccd89 137 $meta->add_required_methods(@_);
eac6d242 138}
139
fa627596 140sub excludes {
ff2ccd89 141 my $caller = shift;
142 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
143
767906ab 144 Carp::croak "Must specify at least one role" unless @_;
ff2ccd89 145 $meta->add_excluded_roles(@_);
fa627596 146}
147
20725a2d 148# see Moose.pm for discussion
149sub super {
150 return unless $Moose::SUPER_BODY;
151 $Moose::SUPER_BODY->(@Moose::SUPER_ARGS);
152}
153
154sub override {
ff2ccd89 155 my $caller = shift;
156 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
20725a2d 157
158 my ($name, $code) = @_;
ff2ccd89 159 $meta->add_override_method_modifier($name, $code);
20725a2d 160}
161
767906ab 162sub extends { Carp::croak "Roles do not currently support 'extends'" }
fc4a95b6 163
767906ab 164sub inner { Carp::croak "Roles cannot support 'inner'" }
fc4a95b6 165
767906ab 166sub augment { Carp::croak "Roles cannot support 'augment'" }
fc4a95b6 167
7b42fc96 1681;
169
a4ac31fa 170__END__
171
30788701 172=head1 NAME
173
174MooseX::Role::Parameterized - parameterized roles
175
a4ac31fa 176=head1 SYNOPSIS
177
178 package MyRole::Counter;
179 use MooseX::Role::Parameterized;
180
181 parameter name => (
a4ac31fa 182 isa => 'Str',
183 required => 1,
184 );
185
186 role {
187 my $p = shift;
188
189 my $name = $p->name;
190
191 has $name => (
192 is => 'rw',
193 isa => 'Int',
194 default => 0,
195 );
196
197 method "increment_$name" => sub {
198 my $self = shift;
199 $self->$name($self->$name + 1);
200 };
201
202 method "decrement_$name" => sub {
203 my $self = shift;
204 $self->$name($self->$name - 1);
205 };
206 };
207
208 package MyGame::Tile;
209 use Moose;
210
211 with 'MyRole::Counter' => { name => 'stepped_on' };
212
213=head1 L<MooseX::Role::Parameterized::Tutorial>
214
215B<Stop!> If you're new here, please read
216L<MooseX::Role::Parameterized::Tutorial>.
217
218=head1 DESCRIPTION
219
220Your parameterized role consists of two things: parameter declarations and a
221C<role> block.
222
223Parameters are declared using the L</parameter> keyword which very much
fa2e6c00 224resembles L<Moose/has>. You can use any option that L<Moose/has> accepts. The
225default value for the "is" option is "ro" as that's a very common case. These
226parameters will get their values when the consuming class (or role) uses
a4ac31fa 227L<Moose/with>. A parameter object will be constructed with these values, and
228passed to the C<role> block.
229
230The C<role> block then uses the usual L<Moose::Role> keywords to build up a
231role. You can shift off the parameter object to inspect what the consuming
232class provided as parameters. You can use the parameters to make your role
233customizable!
234
235There are many paths to parameterized roles (hopefully with a consistent enough
236API); I believe this to be the easiest and most flexible implementation.
c2cfd77d 237Coincidentally, Pugs has a very similar design (I'm not yet convinced that that
238is a good thing).
a4ac31fa 239
240=head1 CAVEATS
241
242You must use this syntax to declare methods in the role block:
08609551 243C<< method NAME => sub { ... }; >>. This is due to a limitation in Perl. In
244return though you can use parameters I<in your methods>!
a4ac31fa 245
9d029b3d 246L<Moose::Role/alias> and L<Moose::Role/excludes> are not yet supported. I'm
247completely unsure of whether they should be handled by this module. Until we
248figure out a plan, both declaring and providing a parameter named C<alias> or
249C<excludes> is an error.
a4ac31fa 250
251=head1 AUTHOR
252
253Shawn M Moore, C<< <sartak@bestpractical.com> >>
254
d5487cd9 255=head1 EXAMPLES
988cddaa 256
d5487cd9 257=over 4
258
259=item L<MooseX::Role::Matcher>
260
261=item L<MooseX::Role::RelatedClassRoles>
262
263=item L<MooseX::Role::XMLRPC::Client>
264
265=item L<WWW::Mechanize::TreeBuilder>
266
267=item L<TAEB::Action::Role::Item>
268
269=item L<KiokuDB::Role::Scan>
270
271=item L<Fey::Role::MakesAliasObjects>
272
273=item L<Fey::Role::HasAliasName>
274
275=item L<Fey::Role::SetOperation>
276
277=back
988cddaa 278
a4ac31fa 279=cut
280