also update Role::Tiny to handle VSTRING and credit doy for pointing it out
[gitmo/Role-Tiny.git] / lib / Role / Tiny.pm
CommitLineData
ab3370e7 1package Role::Tiny;
2
119014a7 3sub _getglob { \*{$_[0]} }
5e03b55c 4sub _getstash { \%{"$_[0]::"} }
119014a7 5
b1eebd55 6use strict;
7use warnings FATAL => 'all';
ab3370e7 8
9our %INFO;
10our %APPLIED_TO;
1947330a 11our %COMPOSED;
ab3370e7 12
5e03b55c 13# inlined from Moo::_Utils - update that first.
14
fb5074f6 15sub _load_module {
fb5074f6 16 (my $proto = $_[0]) =~ s/::/\//g;
5e03b55c 17 return 1 if $INC{"${proto}.pm"};
18 # can't just ->can('can') because a sub-package Foo::Bar::Baz
19 # creates a 'Baz::' key in Foo::Bar's symbol table
20 return 1 if grep !/::$/, keys %{_getstash($_[0])||{}};
59812c87 21 { local $@; require "${proto}.pm"; }
fb5074f6 22 return 1;
23}
24
7b8177f8 25{ # \[] is REF, not SCALAR. \v1 is VSTRING (thanks to doy for that one)
26 my %reftypes = map +($_ => 1), qw(SCALAR REF VSTRING);
27 sub _is_scalar_ref { $reftypes{ref($_[0])} }
28}
29
ab3370e7 30sub import {
31 my $target = caller;
a1164a0b 32 my $me = shift;
de3d4906 33 strictures->import;
1ba11455 34 return if $INFO{$target}; # already exported into this package
ab3370e7 35 # get symbol table reference
36 my $stash = do { no strict 'refs'; \%{"${target}::"} };
37 # install before/after/around subs
38 foreach my $type (qw(before after around)) {
5a247406 39 *{_getglob "${target}::${type}"} = sub {
59812c87 40 { local $@; require Class::Method::Modifiers; }
ab3370e7 41 push @{$INFO{$target}{modifiers}||=[]}, [ $type => @_ ];
42 };
43 }
5a247406 44 *{_getglob "${target}::requires"} = sub {
ab3370e7 45 push @{$INFO{$target}{requires}||=[]}, @_;
46 };
5a247406 47 *{_getglob "${target}::with"} = sub {
48 die "Only one role supported at a time by with" if @_ > 1;
369a4c50 49 $me->apply_role_to_package($target, $_[0]);
96d3f07a 50 };
7b8177f8 51 # grab all *non-constant* (stash slot is not a scalarref) subs present
ab3370e7 52 # in the symbol table and store their refaddrs (no need to forcibly
53 # inflate constant subs into real subs) - also add '' to here (this
54 # is used later)
55 @{$INFO{$target}{not_methods}={}}{
7b8177f8 56 '', map { *$_{CODE}||() } grep !_is_scalar_ref($_), values %$stash
ab3370e7 57 } = ();
58 # a role does itself
59 $APPLIED_TO{$target} = { $target => undef };
60}
61
62sub apply_role_to_package {
369a4c50 63 my ($me, $to, $role) = @_;
1947330a 64
fb5074f6 65 _load_module($role);
66
ab3370e7 67 die "This is apply_role_to_package" if ref($to);
1947330a 68 die "${role} is not a Role::Tiny" unless my $info = $INFO{$role};
69
70 $me->_check_requires($to, $role, @{$info->{requires}||[]});
71
72 $me->_install_methods($to, $role);
73
74 $me->_install_modifiers($to, $info->{modifiers});
75
76 # only add does() method to classes and only if they don't have one
77 if (not $INFO{$to} and not $to->can('does')) {
78 *{_getglob "${to}::does"} = \&does_role;
79 }
80
1947330a 81 # copy our role list into the target's
82 @{$APPLIED_TO{$to}||={}}{keys %{$APPLIED_TO{$role}}} = ();
83}
84
85sub apply_roles_to_object {
86 my ($me, $object, @roles) = @_;
87 die "No roles supplied!" unless @roles;
88 my $class = ref($object);
89 bless($object, $me->create_class_with_roles($class, @roles));
90 $object;
91}
92
93sub create_class_with_roles {
94 my ($me, $superclass, @roles) = @_;
95
fb5074f6 96 die "No roles supplied!" unless @roles;
97
c69190f1 98 my $new_name = join(
99 '__WITH__', $superclass, my $compose_name = join '__AND__', @roles
100 );
101
1947330a 102 return $new_name if $COMPOSED{class}{$new_name};
103
104 foreach my $role (@roles) {
fb5074f6 105 _load_module($role);
1947330a 106 die "${role} is not a Role::Tiny" unless my $info = $INFO{$role};
107 }
108
786e5ba0 109 if ($] >= 5.010) {
59812c87 110 { local $@; require mro; }
b1eebd55 111 } else {
59812c87 112 { local $@; require MRO::Compat; }
b1eebd55 113 }
1947330a 114
115 my @composable = map $me->_composable_package_for($_), reverse @roles;
116
117 *{_getglob("${new_name}::ISA")} = [ @composable, $superclass ];
118
119 my @info = map +($INFO{$_} ? $INFO{$_} : ()), @roles;
120
121 $me->_check_requires(
122 $new_name, $compose_name,
123 do { my %h; @h{map @{$_->{requires}||[]}, @info} = (); keys %h }
124 );
1947330a 125
126 *{_getglob "${new_name}::does"} = \&does_role unless $new_name->can('does');
127
128 @{$APPLIED_TO{$new_name}||={}}{
129 map keys %{$APPLIED_TO{$_}}, @roles
130 } = ();
131
132 $COMPOSED{class}{$new_name} = 1;
133 return $new_name;
134}
135
136sub _composable_package_for {
137 my ($me, $role) = @_;
138 my $composed_name = 'Role::Tiny::_COMPOSABLE::'.$role;
139 return $composed_name if $COMPOSED{role}{$composed_name};
140 $me->_install_methods($composed_name, $role);
141 my $base_name = $composed_name.'::_BASE';
142 *{_getglob("${composed_name}::ISA")} = [ $base_name ];
143 my $modifiers = $INFO{$role}{modifiers}||[];
b1eebd55 144 my @mod_base;
1947330a 145 foreach my $modified (
146 do { my %h; @h{map $_->[1], @$modifiers} = (); keys %h }
147 ) {
b1eebd55 148 push @mod_base, "sub ${modified} { shift->next::method(\@_) }";
1947330a 149 }
59812c87 150 {
151 local $@;
152 eval(my $code = join "\n", "package ${base_name};", @mod_base);
153 die "Evaling failed: $@\nTrying to eval:\n${code}" if $@;
154 }
1947330a 155 $me->_install_modifiers($composed_name, $modifiers);
156 $COMPOSED{role}{$composed_name} = 1;
157 return $composed_name;
158}
159
160sub _check_requires {
161 my ($me, $to, $name, @requires) = @_;
162 if (my @requires_fail = grep !$to->can($_), @requires) {
163 # role -> role, add to requires, role -> class, error out
164 if (my $to_info = $INFO{$to}) {
165 push @{$to_info->{requires}||=[]}, @requires_fail;
166 } else {
167 die "Can't apply ${name} to ${to} - missing ".join(', ', @requires_fail);
168 }
169 }
170}
171
4db3a740 172sub _concrete_methods_of {
173 my ($me, $role) = @_;
1947330a 174 my $info = $INFO{$role};
4db3a740 175 $info->{methods} ||= do {
ab3370e7 176 # grab role symbol table
177 my $stash = do { no strict 'refs'; \%{"${role}::"}};
178 my $not_methods = $info->{not_methods};
179 +{
180 # grab all code entries that aren't in the not_methods list
181 map {
934ea2c1 182 my $code = *{$stash->{$_}}{CODE};
183 # rely on the '' key we added in import for "no code here"
184 exists $not_methods->{$code||''} ? () : ($_ => $code)
7b8177f8 185 } grep !_is_scalar_ref($stash->{$_}), keys %$stash
ab3370e7 186 };
187 };
4db3a740 188}
189
190sub methods_provided_by {
191 my ($me, $role) = @_;
192 die "${role} is not a Role::Tiny" unless my $info = $INFO{$role};
193 (keys %{$me->_concrete_methods_of($role)}, @{$info->{requires}||[]});
194}
195
196sub _install_methods {
197 my ($me, $to, $role) = @_;
198
199 my $info = $INFO{$role};
200
201 my $methods = $me->_concrete_methods_of($role);
1947330a 202
ab3370e7 203 # grab target symbol table
204 my $stash = do { no strict 'refs'; \%{"${to}::"}};
1947330a 205
ab3370e7 206 # determine already extant methods of target
207 my %has_methods;
208 @has_methods{grep
7b8177f8 209 +(_is_scalar_ref($stash->{$_}) || *{$stash->{$_}}{CODE}),
ab3370e7 210 keys %$stash
211 } = ();
ab3370e7 212
1947330a 213 foreach my $i (grep !exists $has_methods{$_}, keys %$methods) {
ab3370e7 214 no warnings 'once';
5a247406 215 *{_getglob "${to}::${i}"} = $methods->{$i};
ab3370e7 216 }
1947330a 217}
ab3370e7 218
1947330a 219sub _install_modifiers {
220 my ($me, $to, $modifiers) = @_;
dccea57d 221 if (my $info = $INFO{$to}) {
222 push @{$info->{modifiers}}, @{$modifiers||[]};
223 } else {
224 foreach my $modifier (@{$modifiers||[]}) {
225 $me->_install_single_modifier($to, @$modifier);
226 }
96d3f07a 227 }
ab3370e7 228}
229
dccea57d 230sub _install_single_modifier {
231 my ($me, @args) = @_;
232 Class::Method::Modifiers::install_modifier(@args);
233}
234
ab3370e7 235sub does_role {
390ac406 236 my ($proto, $role) = @_;
237 return exists $APPLIED_TO{ref($proto)||$proto}{$role};
ab3370e7 238}
239
2401;
5febcf4d 241
0b6e5fff 242=head1 NAME
243
244Role::Tiny - Roles. Like a nouvelle cusine portion size slice of Moose.
5febcf4d 245
246=head1 SYNOPSIS
247
248 package Some::Role;
249
250 use Role::Tiny;
251
252 sub foo { ... }
253
254 sub bar { ... }
255
256 1;
257
258else where
259
260 package Some::Class;
261
a1164a0b 262 use Role::Tiny::With;
5febcf4d 263
264 # bar gets imported, but not foo
a1164a0b 265 with 'Some::Role';
5febcf4d 266
267 sub foo { ... }
268
269 1;
270
271=head1 DESCRIPTION
272
273C<Role::Tiny> is a minimalist role composition tool.
274
275=head1 ROLE COMPOSITION
276
277Role composition can be thought of as much more clever and meaningful multiple
278inheritance. The basics of this implementation of roles is:
279
280=over 2
281
282=item *
283
284If a method is already defined on a class, that method will not be composed in
285from the role.
286
287=item *
288
289If a method that the role L</requires> to be implemented is not implemented,
290role application will fail loudly.
291
0d39f9d3 292=back
293
5febcf4d 294Unlike L<Class::C3>, where the B<last> class inherited from "wins," role
295composition is the other way around, where first wins. In a more complete
296system (see L<Moose>) roles are checked to see if they clash. The goal of this
297is to be much simpler, hence disallowing composition of multiple roles at once.
298
299=head1 METHODS
300
301=head2 apply_role_to_package
302
369a4c50 303 Role::Tiny->apply_role_to_package('Some::Package', 'Some::Role');
5febcf4d 304
a1164a0b 305Composes role with package. See also L<Role::Tiny::With>.
5febcf4d 306
307=head2 apply_roles_to_object
308
309 Role::Tiny->apply_roles_to_object($foo, qw(Some::Role1 Some::Role2));
310
311Composes roles in order into object directly. Object is reblessed into the
312resulting class.
313
314=head2 create_class_with_roles
315
316 Role::Tiny->create_class_with_roles('Some::Base', qw(Some::Role1 Some::Role2));
317
318Creates a new class based on base, with the roles composed into it in order.
319New class is returned.
320
54e4000d 321=head1 SUBROUTINES
5febcf4d 322
323=head2 does_role
324
54e4000d 325 if (Role::Tiny::does_role($foo, 'Some::Role')) {
5febcf4d 326 ...
327 }
328
329Returns true if class has been composed with role.
330
54e4000d 331This subroutine is also installed as ->does on any class a Role::Tiny is
332composed into unless that class already has an ->does method, so
333
334 if ($foo->does_role('Some::Role')) {
335 ...
336 }
337
338will work for classes but to test a role, one must use ::does_role directly
339
5febcf4d 340=head1 IMPORTED SUBROUTINES
341
342=head2 requires
343
344 requires qw(foo bar);
345
346Declares a list of methods that must be defined to compose role.
347
348=head2 with
349
350 with 'Some::Role1';
351 with 'Some::Role2';
352
353Composes another role into the current role. Only one role may be composed in
354at a time to allow the code to remain as simple as possible.
355
356=head2 before
357
358 before foo => sub { ... };
359
360See L<< Class::Method::Modifiers/before method(s) => sub { ... } >> for full
361documentation.
362
363=head2 around
364
365 around foo => sub { ... };
366
367See L<< Class::Method::Modifiers/around method(s) => sub { ... } >> for full
368documentation.
369
370=head2 after
371
372 after foo => sub { ... };
373
374See L<< Class::Method::Modifiers/after method(s) => sub { ... } >> for full
375documentation.
376
40f3e3aa 377=head1 AUTHORS
378
379See L<Moo> for authors.
380
381=head1 COPYRIGHT AND LICENSE
382
383See L<Moo> for the copyright and license.
384
385=cut