Using "parameter" inside the role block is an error
[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
5b82ffb1 38sub role {
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
5b82ffb1 52# give role a (&) prototype
03c4551d 53moose_around _make_wrapper => sub {
5b82ffb1 54 my $orig = shift;
55 my ($self, $caller, $sub, $fq_name) = @_;
56
57 if ($fq_name =~ /::role$/) {
58 return sub (&) { $sub->($caller, @_) };
59 }
60
61 return $orig->(@_);
62};
63
a457ed60 64sub has {
ff2ccd89 65 my $caller = shift;
66 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
a457ed60 67
209e00d2 68 my $names = shift;
a457ed60 69 $names = [$names] if !ref($names);
70
71 for my $name (@$names) {
ff2ccd89 72 $meta->add_attribute($name, @_);
a457ed60 73 }
74}
75
209e00d2 76sub method {
209e00d2 77 my $caller = shift;
ff2ccd89 78 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
79
209e00d2 80 my $name = shift;
81 my $body = shift;
82
ff2ccd89 83 my $method = $meta->method_metaclass->wrap(
209e00d2 84 package_name => $caller,
85 name => $name,
86 body => $body,
87 );
88
ff2ccd89 89 $meta->add_method($name => $method);
209e00d2 90}
91
03c4551d 92sub before {
ff2ccd89 93 my $caller = shift;
94 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 95
96 my $code = pop @_;
97
98 for (@_) {
767906ab 99 Carp::croak "Roles do not currently support "
03c4551d 100 . ref($_)
101 . " references for before method modifiers"
102 if ref $_;
ff2ccd89 103 $meta->add_before_method_modifier($_, $code);
03c4551d 104 }
105}
106
107sub after {
ff2ccd89 108 my $caller = shift;
109 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 110
111 my $code = pop @_;
112
113 for (@_) {
767906ab 114 Carp::croak "Roles do not currently support "
03c4551d 115 . ref($_)
116 . " references for after method modifiers"
117 if ref $_;
ff2ccd89 118 $meta->add_after_method_modifier($_, $code);
03c4551d 119 }
120}
121
122sub around {
ff2ccd89 123 my $caller = shift;
124 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
03c4551d 125
126 my $code = pop @_;
127
128 for (@_) {
767906ab 129 Carp::croak "Roles do not currently support "
03c4551d 130 . ref($_)
131 . " references for around method modifiers"
132 if ref $_;
ff2ccd89 133 $meta->add_around_method_modifier($_, $code);
03c4551d 134 }
135}
136
d55c8861 137sub with {
ff2ccd89 138 my $caller = shift;
139 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
140
141 Moose::Util::apply_all_roles($meta, @_);
d55c8861 142}
143
eac6d242 144sub requires {
ff2ccd89 145 my $caller = shift;
146 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
147
767906ab 148 Carp::croak "Must specify at least one method" unless @_;
ff2ccd89 149 $meta->add_required_methods(@_);
eac6d242 150}
151
fa627596 152sub excludes {
ff2ccd89 153 my $caller = shift;
154 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
155
767906ab 156 Carp::croak "Must specify at least one role" unless @_;
ff2ccd89 157 $meta->add_excluded_roles(@_);
fa627596 158}
159
20725a2d 160# see Moose.pm for discussion
161sub super {
162 return unless $Moose::SUPER_BODY;
163 $Moose::SUPER_BODY->(@Moose::SUPER_ARGS);
164}
165
166sub override {
ff2ccd89 167 my $caller = shift;
168 my $meta = $CURRENT_METACLASS || Class::MOP::Class->initialize($caller);
20725a2d 169
170 my ($name, $code) = @_;
ff2ccd89 171 $meta->add_override_method_modifier($name, $code);
20725a2d 172}
173
767906ab 174sub extends { Carp::croak "Roles do not currently support 'extends'" }
fc4a95b6 175
767906ab 176sub inner { Carp::croak "Roles cannot support 'inner'" }
fc4a95b6 177
767906ab 178sub augment { Carp::croak "Roles cannot support 'augment'" }
fc4a95b6 179
7b42fc96 1801;
181
a4ac31fa 182__END__
183
30788701 184=head1 NAME
185
186MooseX::Role::Parameterized - parameterized roles
187
a4ac31fa 188=head1 SYNOPSIS
189
190 package MyRole::Counter;
191 use MooseX::Role::Parameterized;
192
193 parameter name => (
194 is => 'ro',
195 isa => 'Str',
196 required => 1,
197 );
198
199 role {
200 my $p = shift;
201
202 my $name = $p->name;
203
204 has $name => (
205 is => 'rw',
206 isa => 'Int',
207 default => 0,
208 );
209
210 method "increment_$name" => sub {
211 my $self = shift;
212 $self->$name($self->$name + 1);
213 };
214
215 method "decrement_$name" => sub {
216 my $self = shift;
217 $self->$name($self->$name - 1);
218 };
219 };
220
221 package MyGame::Tile;
222 use Moose;
223
224 with 'MyRole::Counter' => { name => 'stepped_on' };
225
226=head1 L<MooseX::Role::Parameterized::Tutorial>
227
228B<Stop!> If you're new here, please read
229L<MooseX::Role::Parameterized::Tutorial>.
230
231=head1 DESCRIPTION
232
233Your parameterized role consists of two things: parameter declarations and a
234C<role> block.
235
236Parameters are declared using the L</parameter> keyword which very much
237resembles L<Moose/has>. You can use any option that L<Moose/has> accepts.
238These parameters will get their values when the consuming class (or role) uses
239L<Moose/with>. A parameter object will be constructed with these values, and
240passed to the C<role> block.
241
242The C<role> block then uses the usual L<Moose::Role> keywords to build up a
243role. You can shift off the parameter object to inspect what the consuming
244class provided as parameters. You can use the parameters to make your role
245customizable!
246
247There are many paths to parameterized roles (hopefully with a consistent enough
248API); I believe this to be the easiest and most flexible implementation.
c2cfd77d 249Coincidentally, Pugs has a very similar design (I'm not yet convinced that that
250is a good thing).
a4ac31fa 251
252=head1 CAVEATS
253
254You must use this syntax to declare methods in the role block:
08609551 255C<< method NAME => sub { ... }; >>. This is due to a limitation in Perl. In
256return though you can use parameters I<in your methods>!
a4ac31fa 257
9d029b3d 258L<Moose::Role/alias> and L<Moose::Role/excludes> are not yet supported. I'm
259completely unsure of whether they should be handled by this module. Until we
260figure out a plan, both declaring and providing a parameter named C<alias> or
261C<excludes> is an error.
a4ac31fa 262
263=head1 AUTHOR
264
265Shawn M Moore, C<< <sartak@bestpractical.com> >>
266
988cddaa 267=head1 SEE ALSO
268
269L<MooseX::Role::Matcher>
270
a4ac31fa 271=cut
272