File Coverage

blib/lib/With/Roles.pm
Criterion Covered Total %
statement 78 90 86.6
branch 31 42 73.8
condition 20 45 44.4
subroutine 13 14 92.8
pod n/a
total 142 191 74.3


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