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