File Coverage

blib/lib/With/Roles.pm
Criterion Covered Total %
statement 83 95 87.3
branch 32 44 72.7
condition 22 48 45.8
subroutine 12 13 92.3
pod n/a
total 149 200 74.5


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