File Coverage

blib/lib/decorators.pm
Criterion Covered Total %
statement 72 73 98.6
branch 29 40 72.5
condition 4 9 44.4
subroutine 14 14 100.0
pod 0 1 0.0
total 119 137 86.8


line stmt bran cond sub pod time code
1             package decorators;
2             # ABSTRACT: Apply decorators to your methods
3              
4 11     11   792040 use strict;
  11         101  
  11         256  
5 11     11   45 use warnings;
  11         14  
  11         414  
6              
7             our $VERSION = '0.01';
8             our $AUTHORITY = 'cpan:STEVAN';
9              
10 11     11   49 use Carp ();
  11         15  
  11         143  
11 11     11   44 use Scalar::Util ();
  11         41  
  11         146  
12 11     11   4026 use MOP (); # this is how we do most of our work
  11         453641  
  11         238  
13 11     11   80 use Module::Runtime (); # decorator provider loading
  11         20  
  11         7611  
14              
15             ## --------------------------------------------------------
16             ## Importers
17             ## --------------------------------------------------------
18              
19             sub import {
20 34     34   29151 my $class = shift;
21 34         134 $class->import_into( scalar caller, @_ );
22             }
23              
24             ## --------------------------------------------------------
25             ## Trait collection
26             ## --------------------------------------------------------
27              
28             sub import_into {
29 34     34 0 98 my (undef, $package, @providers) = @_;
30              
31 34 50       96 Carp::confess('You must provide a valid package argument')
32             unless $package;
33              
34 34 50       95 Carp::confess('The package argument cannot be a reference or blessed object')
35             if ref $package;
36              
37             # convert this into a metaobject
38 34         138 my $meta = MOP::Role->new( $package );
39              
40 34 50 33     2060 Carp::confess('Cannot install decorator collectors, MODIFY_CODE_ATTRIBUTES method already exists')
41             if $meta->has_method('MODIFY_CODE_ATTRIBUTES') || $meta->has_method_alias('MODIFY_CODE_ATTRIBUTES');
42              
43 34 50 33     1507 Carp::confess('Cannot install decorator collectors, FETCH_CODE_ATTRIBUTES method already exists')
44             if $meta->has_method('FETCH_CODE_ATTRIBUTES') || $meta->has_method_alias('FETCH_CODE_ATTRIBUTES');
45              
46             # now install the collectors ...
47              
48 34         1170 my %accepted; # shared data between the collectors ...
49              
50             $meta->alias_method(
51             FETCH_CODE_ATTRIBUTES => sub {
52 301     301   41544 my (undef, $code) = @_;
53             # return just the strings, as expected by attributes ...
54 301 100       653 return $accepted{ $code } ? @{ $accepted{ $code } } : ();
  298         3841  
55             }
56 34         230 );
57              
58             $meta->alias_method(
59             MODIFY_CODE_ATTRIBUTES => sub {
60 82     82   10933 my ($pkg, $code, @attrs) = @_;
61              
62 82         250 my $role = MOP::Role->new( $pkg ); # the actual Package that Perl is talking about ...
63 82         4393 my $method = MOP::Method->new( $code ); # the actual CV that Perl is talking about ...
64 82         3200 my @attributes = map MOP::Method::Attribute->new( $_ ), @attrs; # inflate the attributes ...
65              
66 82         4421 my $decorators = _create_decorator_meta_object_for( $role->name );
67             # preparing the attrbutes returns the ones that are unhandled ...
68             # my @unhandled = map $_->original, $decorators->filter_unhandled( @attributes );
69 82         3912 my @unhandled = map $_->original, grep !_has_decorator( $decorators, $_->name ), @attributes;
70              
71             # return the bad decorators as strings, as expected by attributes ...
72 82 50       1043 return @unhandled if @unhandled;
73              
74             # process the attributes ...
75 82         137 foreach my $attribute ( @attributes ) {
76 127         1796 my $d = _get_decorator( $decorators, $attribute->name );
77              
78 127 50       16610 $d or die 'This should never happen, as we have already checked this above ^^';
79              
80             # we know that this will be a no-op,
81             # so, we no-op and go to the next one
82 127 100       241 next if $d->has_code_attributes('TagMethod');
83              
84 114 100       1872 if ( $d->has_code_attributes('CreateMethod') ) {
85 34 50       87 $method->is_required
86             or die 'The method ('.$method->name.') must be bodyless for a `CreateMethod` decorator ('.$d->name.') '
87             .'to be applied to it, please check the order of your decorators, `CreateMethod` decorators '
88             .'should usually be applied early in the list when possible.';
89             }
90              
91 114 100       1761 $d->body->( $role, $method, @{ $attribute->args || [] } );
  114         224  
92              
93 114 100 66     8181 if ( $d->has_code_attributes('WrapMethod') || $d->has_code_attributes('CreateMethod') ) {
94 34         94 my $name = $method->name;
95 34         501 $method = $role->get_method( $name );
96 34 50       4002 Carp::croak('Failed to find new overwriten method ('.$name.') in class ('.$role->name.')')
97             unless defined $method;
98             }
99             }
100              
101             # store the decorators we applied ...
102 82         1963 $accepted{ $method->body } = [ map $_->original, @attributes ];
103              
104 82         859 return;
105             }
106 34         1419 );
107              
108 34 100       1124 if ( @providers ) {
109             # so we can use lowercase attributes ...
110 33 100       579 warnings->unimport('reserved')
111             if grep /^:/, @providers;
112              
113             # expand any tags, they should match
114             # the provider names available in the
115             # decorators::providers::* namespace
116 33 100       192 @providers = map /^\:/ ? 'decorators::providers:'.$_ : $_, @providers;
117              
118             # load the providers, and then ...
119 33         150 Module::Runtime::use_package_optimistically( $_ ) foreach @providers;
120              
121 33         2742 _set_decorator_providers(
122             _create_decorator_meta_object_for( $package ),
123             @providers
124             );
125             }
126              
127 34         45293 return;
128             }
129              
130             ## methods to deal with the internals
131              
132             sub _create_decorator_meta_object_for {
133 115     115   1090 my ($namespace) = @_;
134 115         346 return MOP::Role->new( $namespace.'::__DECORATORS__' );
135             }
136              
137             sub _set_decorator_providers {
138 33     33   1902 my ($decorators, @providers) = @_;
139 33         115 $decorators->set_roles( @providers );
140 33         1030 MOP::Util::compose_roles( $decorators );
141             }
142              
143             # methods to deal with locating decorators
144              
145             sub _has_decorator {
146 254     254   1959 my ($decorators, $name) = @_;
147              
148 254 50       636 return unless $decorators->has_method( $name );
149              
150 254         35807 my $method = $decorators->get_method( $name );
151 254 100       32766 return 1 if $method->origin_stash eq 'decorators::providers::for_providers';
152 106 50       1182 return 1 if $method->has_code_attributes('Decorator');
153 0         0 return;
154             }
155              
156             sub _get_decorator {
157 127     127   1071 my ($decorators, $name) = @_;
158 127 50       227 return unless _has_decorator( $decorators, $name );
159 127         1632 return $decorators->get_method( $name );
160             }
161              
162             1;
163              
164             __END__