File Coverage

blib/lib/With/Roles.pm
Criterion Covered Total %
statement 86 98 87.7
branch 32 44 72.7
condition 22 48 45.8
subroutine 13 14 92.8
pod n/a
total 153 204 75.0


line stmt bran cond sub pod time code
1             package With::Roles;
2 10     10   544352 use strict;
  10         69  
  10         312  
3 10     10   63 use warnings;
  10         19  
  10         657  
4              
5             our $VERSION = '0.001002';
6             $VERSION =~ tr/_//d;
7              
8 8     8   57 use Carp qw(croak);
  8         15  
  8         2922  
9              
10             my %COMPOSITE_NAME;
11             my %COMPOSITE_KEY;
12              
13             my $role_suffix = 'A000';
14             sub _composite_name {
15 18     18   148 my ($base, $role_base, @roles) = @_;
16 18         100 my $key = join('+', $base, map join('|', @$_), @roles);
17             return $COMPOSITE_NAME{$key}
18 18 100       68 if exists $COMPOSITE_NAME{$key};
19              
20 16         559 my ($cut) = map qr/$_/, join '|', map quotemeta, @$role_base, $base;
21              
22 16         85 my $new_name = $base;
23 16         38 for my $roles (@roles) {
24             # this creates the potential for ambiguity, but it's unlikely to happen and
25             # we will keep the resulting composite
26 18         42 my @short_names = @$roles;
27 18         39 for (@short_names) {
28 19         390 s/\A${cut}::/::/;
29             $_ = join '::',
30 19         85 map { s/\W/_/g; $_ }
  49         91  
  49         125  
31             split /::/;
32             }
33 18         73 $new_name .= '__WITH__' . join '__AND__', @short_names;
34             }
35              
36 16 50 33     110 if ($COMPOSITE_KEY{$new_name} || length($new_name) > 252) {
37 0         0 my $abbrev = substr $new_name, 0, 250 - length $role_suffix;
38 0         0 $abbrev =~ s/(?
39 0         0 $new_name = $abbrev.'__'.$role_suffix++;
40             }
41              
42 16         40 $COMPOSITE_KEY{$new_name} = $key;
43              
44 16         100 return $COMPOSITE_NAME{$key} = $new_name;
45             }
46              
47             sub _gen {
48 12     12   40 my ($pack, $type, @ops) = @_;
49 12         21 my $e;
50             {
51 12         33 local $@;
  12         18  
52 7     7   66 no strict 'refs';
  7         13  
  7         263  
53 7     7   39 no warnings 'once';
  7         26  
  7         2068  
54 84         413 local *{"${pack}::${_}"}
55 12         32 for qw(with extends requires has around after before);
56              
57 12 50       181 my $code = join('',
58             "package $pack;\n",
59             (defined $type ? "use $type;\n" : ()),
60             (
61             map "$ops[$_-1](\@{\$ops[$_]});\n",
62             map $_*2+1,
63             0 .. (@ops/2-1)
64             ),
65             "1;\n",
66             );
67              
68 12 50   5   1009 eval $code or $e = $@;
  5         1542  
  5         870  
  5         206  
69             }
70 12 50       72 die $e if defined $e;
71             }
72              
73             sub _require {
74 0     0   0 my $package = shift;
75 0         0 (my $module = "$package.pm") =~ s{::|'}{/}g;
76 0         0 require $module;
77             }
78              
79             sub _extends {
80 7     7   66 no strict 'refs';
  7         21  
  7         6209  
81 7     7   17 my $caller = caller;
82 7         18 @{"${caller}::ISA"} = (@_);
  7         148  
83 7         34 _copy_mro($_[0], $caller);
84             }
85              
86             sub _copy_mro {
87 10     10   20 my $source = shift;
88 10   66     42 my $target = shift || caller;
89 10 50       317 mro::set_mro($target, mro::get_mro($source))
90             if defined &mro::set_mro;
91             }
92              
93             sub _detect_type {
94 12     12   78 my ($base, @roles) = @_;
95 12         31 my $meta;
96 12 100 100     215 if (
    100 66        
    50 33        
    50 33        
    50 33        
    50 33        
    100 33        
      33        
      33        
      33        
      66        
97             $INC{'Moo/Role.pm'}
98             and Moo::Role->is_role($base)
99             ) {
100 1         73 return 'Moo::Role';
101             }
102             elsif (
103             $INC{'Moo.pm'}
104             and Moo->_accessor_maker_for($base)
105             ) {
106 3         29669 return 'Moo';
107             }
108             elsif (
109             $INC{'Class/MOP.pm'}
110             and $meta = Class::MOP::class_of($base)
111             and $meta->isa('Moose::Meta::Role')
112             ) {
113 0         0 return 'Moose::Role';
114             }
115             elsif (
116             $INC{'Class/MOP.pm'}
117             and $meta = Class::MOP::class_of($base)
118             and $meta->isa('Class::MOP::Class')
119             ) {
120 0         0 return 'Moose';
121             }
122             elsif (
123             defined &Mouse::Util::find_meta
124             and $meta = Mouse::Util::find_meta($base)
125             and $meta->isa('Mouse::Meta::Role')
126             ) {
127 0         0 return 'Mouse::Role';
128             }
129             elsif (
130             defined &Mouse::Util::find_meta
131             and $meta = Mouse::Util::find_meta($base)
132             and $meta->isa('Mouse::Meta::Class')
133             ) {
134 0         0 return 'Mouse';
135             }
136             elsif (
137             $INC{'Role/Tiny.pm'}
138             and Role::Tiny->is_role($base)
139             ) {
140 1         10 return 'Role::Tiny';
141             }
142             else {
143 7         72 local $@;
144 0         0 eval { _require($_) }
145 7   33     64 for grep !($INC{'Role/Tiny.pm'} && Role::Tiny->is_role($_)), @roles;
146 7 50 33     96 if (
147             $INC{'Role/Tiny.pm'}
148             and !grep !Role::Tiny->is_role($_), @roles
149             ) {
150 7         81 return 'Role::Tiny::With';
151             }
152             else {
153 0         0 return undef;
154             }
155             }
156             }
157              
158             my %BASE;
159             sub with::roles {
160 14     14   6974 my ($self, @roles) = @_;
161 14 50       47 return $self
162             if !@roles;
163              
164 14   66     66 my $base = ref $self || $self;
165              
166 14 100       29 my ($orig_base, @base_roles) = @{ $BASE{$base} || [$base] };
  14         89  
167              
168 14 100       145 my $role_base = $self->can('ROLE_BASE') ? $self->ROLE_BASE : $orig_base.'::Role';
169              
170 14         80 s/\A\+/${role_base}::/ for @roles;
171              
172 14         39 my @all_roles = (@base_roles, [ @roles ]);
173              
174 14         54 my $new = _composite_name($orig_base, [ $role_base ], @all_roles);
175              
176 14 100       56 if (!exists $BASE{$new}) {
177 12 50       36 my $type = _detect_type($base, @roles)
178             or croak "Can't determine class or role type of $base or @roles!";
179              
180 12         26 my @ops;
181              
182 12 100       44 if ($type eq 'Role::Tiny::With') {
    100          
183 7         20 push @ops, __PACKAGE__.'::_extends', [ $base ];
184             }
185             elsif ($type =~ /Role/) {
186 2         7 push @ops, with => [ $base ];
187             }
188             else {
189 3         10 push @ops, extends => [ $base ];
190 3         10 push @ops, __PACKAGE__.'::_copy_mro' => [ $base ];
191             }
192              
193 12         37 push @ops, with => [ @roles ];
194              
195 12         37 _gen($new, $type, @ops);
196             }
197              
198 14         48 $BASE{$new} = [$orig_base, @all_roles];
199              
200 14 100       37 if (ref $self) {
201             # using $_[0] rather than $self, to work around how overload magic is
202             # applied on perl 5.8
203 2         10 return bless $_[0], $new;
204             }
205              
206 12         74 return $new;
207             }
208              
209             1;
210             __END__