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