File Coverage

blib/lib/Role/Declare.pm
Criterion Covered Total %
statement 172 175 98.2
branch 21 28 75.0
condition 10 15 66.6
subroutine 50 50 100.0
pod 0 13 0.0
total 253 281 90.0


line stmt bran cond sub pod time code
1             package Role::Declare;
2 3     3   375480 use strict;
  3         27  
  3         87  
3 3     3   16 use warnings;
  3         6  
  3         141  
4             our $VERSION = 0.05;
5              
6 3     3   1859 use Attribute::Handlers;
  3         15360  
  3         15  
7 3     3   116 use Carp qw[ croak ];
  3         13  
  3         135  
8 3     3   1556 use Data::Alias;
  3         2651  
  3         167  
9 3     3   1894 use Function::Parameters;
  3         11262  
  3         19  
10 3     3   2932 use Import::Into;
  3         8704  
  3         107  
11 3     3   1827 use Role::Tiny;
  3         13833  
  3         24  
12 3     3   534 use Scalar::Util qw[ refaddr ];
  3         13  
  3         148  
13 3     3   1926 use Types::Standard ':all';
  3         314278  
  3         49  
14              
15 3     3   163074 use namespace::clean;
  3         42776  
  3         20  
16              
17             use constant { # Attribute::Handlers argument positions
18 3         1338 PACKAGE => 0,
19             SYMBOL => 1,
20             REFERENT => 2,
21             ATTRIBUTE => 3,
22             DATA => 4,
23 3     3   5739 };
  3         8  
24              
25             my %return_hooks;
26              
27             sub _install_hook {
28 18     18   41 my ( $type, $target, $hook ) = @_;
29 18         92 alias my $hook_slot = $return_hooks{ refaddr($target) }{$type};
30 18 50       42 croak "A $type hook for $target already exists" if defined $hook_slot;
31 18         23 $hook_slot = $hook;
32 18         34 return;
33             }
34              
35 13     13   27 sub _install_scalar_hook { return _install_hook('scalar', @_) }
36 5     5   13 sub _install_list_hook { return _install_hook('list', @_) }
37              
38              
39             sub Return : ATTR(CODE,BEGIN) {
40 10     10 0 1892 my ($referent, $data) = @_[ REFERENT, DATA ];
41              
42 10 50       31 croak 'Only a single constraint is supported' if @$data != 1;
43 10         20 my $constraint = $data->[0];
44            
45             _install_scalar_hook($referent, sub {
46 23     23   53 my $orig = shift;
47 23         53 return $constraint->assert_return(scalar &$orig);
48 10         45 });
49              
50 10         24 return;
51 3     3   33 }
  3         9  
  3         32  
52              
53             sub ReturnMaybe : ATTR(CODE,BEGIN) {
54 2     2 0 2109 $_[DATA][0] = Maybe[ $_[DATA][0] ];
55 2         906 goto &Return;
56 3     3   3755 }
  3         7  
  3         19  
57              
58             sub _make_list_check {
59 5     5   16 my ($constraint, %args) = @_;
60 5         10 my $allow_empty = $args{allow_empty};
61 5 50       15 croak 'List constraint not defined' if not $constraint;
62              
63             return sub {
64 11     11   25 my $orig = shift;
65 11         30 my $retval = [&$orig];
66 11 100 66     106 return if not @$retval and $allow_empty;
67 10         14 return @{ $constraint->assert_return($retval) };
  10         33  
68 5         55 };
69             }
70              
71             sub ReturnList : ATTR(CODE,BEGIN) {
72 2     2 0 1034 my ($referent, $data) = @_[ REFERENT, DATA ];
73 2         8 my $type = ArrayRef($data);
74 2         4455 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
75 2         6 return;
76 3     3   4240 }
  3         7  
  3         22  
77              
78             sub ReturnMaybeList : ATTR(CODE,BEGIN) {
79 1     1 0 482 my ($referent, $data) = @_[ REFERENT, DATA ];
80 1         3 my $type = ArrayRef($data);
81 1         133 _install_list_hook($referent, _make_list_check($type, allow_empty => 1));
82 1         2 return;
83 3     3   4282 }
  3         11  
  3         27  
84              
85             sub ReturnTuple : ATTR(CODE,BEGIN) {
86 1     1 0 495 my ($referent, $data) = @_[ REFERENT, DATA ];
87              
88 1         4 my $type = Tuple($data);
89 1         7890 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
90 1         2 return;
91 3     3   4068 }
  3         7  
  3         17  
92              
93             sub ReturnCycleTuple : ATTR(CODE,BEGIN) {
94 1     1 0 4 my ($referent, $data) = @_[ REFERENT, DATA ];
95              
96 1         9 my $type = CycleTuple($data);
97 1         18221 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
98 1         3 return;
99 3     3   5048 }
  3         8  
  3         12  
100              
101             sub ReturnHash : ATTR(CODE,BEGIN) {
102 1     1 0 501 my $data = $_[DATA];
103 1 50       6 croak 'Only a single constraint is supported' if @$data != 1;
104 1         5 unshift @$data, Str;
105 1         5 goto &ReturnCycleTuple;
106 3     3   3321 }
  3         10  
  3         20  
107              
108             sub _make_self_check {
109 3     3   8 my %args = @_;
110 3         7 my $allow_undef = $args{undef_ok};
111             return sub {
112 8     8   15 my $orig = shift;
113 8         25 my $orig_self_addr = refaddr($_[0]);
114 8         18 my $self = &$orig;
115 7 100 66     61 return $self if not defined $self and $allow_undef;
116 6 100 100     35 return $self if ref $self and refaddr($self) eq $orig_self_addr;
117 3         44 croak "$self was not the original invocant";
118 3         30 };
119             }
120              
121             sub ReturnSelf : ATTR(CODE,BEGIN) {
122 1     1 0 396 my $referent = $_[REFERENT];
123 1         3 _install_scalar_hook($referent, _make_self_check(undef_ok => 0));
124 1         2 return;
125 3     3   4122 }
  3         7  
  3         13  
126              
127             sub ReturnMaybeSelf : ATTR(CODE,BEGIN) {
128 2     2 0 824 my $referent = $_[REFERENT];
129 2         6 _install_scalar_hook($referent, _make_self_check(undef_ok => 1));
130 2         5 return;
131 3     3   2843 }
  3         7  
  3         43  
132              
133             sub ReturnObject : ATTR(CODE,BEGIN) {
134 1     1 0 423 $_[DATA][0] = Object;
135 1         7 goto &Return;
136 3     3   5455 }
  3         16  
  3         49  
137              
138             sub ReturnMaybeObject : ATTR(CODE,BEGIN) {
139 2     2 0 859 $_[DATA][0] = Maybe[Object];
140 2         656 goto &Return;
141 3     3   4150 }
  3         9  
  3         12  
142              
143             sub ReturnInstanceOf : ATTR(CODE,BEGIN) {
144 1     1 0 652 $_[DATA][0] = InstanceOf[$_[DATA][0]];
145 1         4962 goto &Return;
146 3     3   3312 }
  3         10  
  3         14  
147              
148             sub ReturnMaybeInstanceOf : ATTR(CODE,BEGIN) {
149 2     2 0 1307 $_[DATA][0] = Maybe[InstanceOf[$_[DATA][0]]];
150 2         806 goto &Return;
151 3     3   3522 }
  3         8  
  3         22  
152              
153             sub _build_validator {
154 17     17   32 my ($hooks) = @_;
155 17         28 my $val_scalar = $hooks->{scalar};
156 17         26 my $val_list = $hooks->{list};
157             return sub {
158 42 100 66 42   51999 goto &$val_list if wantarray and $val_list;
159 31 50 33     218 goto &$val_scalar if defined wantarray and $val_scalar;
160              
161             # void context or no validators
162 0         0 my $orig = shift;
163 0         0 goto &$orig;
164 17         90 };
165             }
166              
167             sub import {
168 3     3   28 my ($class, $mode) = @_;
169 3         13 my $package = scalar caller;
170 3 50       66 return if $class ne __PACKAGE__; # don't let this import spread around
171              
172 3         6 my $lax;
173 3 100       15 if (defined $mode) {
174 1 50       2 if ($mode eq '-lax') {
175 1         2 $lax = 1;
176             }
177             else {
178 0         0 croak "Unsupported mode: $mode";
179             }
180             }
181              
182             # make the caller a role first, so we can install modifiers
183 3         35 Role::Tiny->import::into($package);
184 3         1316 my $before = $package->can('before');
185 3         18 my $around = $package->can('around');
186              
187             my $installer = sub {
188 18     18   3481 my ($name, $coderef) = @_;
189 18         65 $before->($name, $coderef);
190              
191 18         182 my $hooks = delete $return_hooks{ refaddr($coderef) };
192 18 100       49 if (defined $hooks) {
193 17         32 my $return_validator = _build_validator($hooks);
194 17         43 $around->($name, $return_validator);
195             }
196              
197 18         1011 return;
198 3         35 };
199              
200 3         13 my %common_args = (
201             name => 'required',
202             install_sub => $installer,
203             );
204 3 100       12 $common_args{check_argument_count} = 0 if $lax;
205 3         31 Function::Parameters->import(
206             {
207             class_method => {
208             %common_args,
209             shift => [ [ '$class', ClassName ] ],
210             },
211             },
212             {
213             instance_method => {
214             %common_args,
215             shift => [ [ '$self', Object ] ],
216             },
217             },
218             {
219             method => {
220             %common_args,
221             shift => [ '$self' ],
222             },
223             },
224             );
225              
226             # allow importing package to use our attributes
227 3         2484 parent->import::into($package, $class);
228              
229 3         3234 return;
230             }
231              
232             1;