File Coverage

blib/lib/Role/Declare.pm
Criterion Covered Total %
statement 169 172 98.2
branch 21 28 75.0
condition 12 17 70.5
subroutine 49 49 100.0
pod 0 13 0.0
total 251 279 89.9


line stmt bran cond sub pod time code
1             package Role::Declare;
2 3     3   368597 use strict;
  3         25  
  3         90  
3 3     3   15 use warnings;
  3         5  
  3         128  
4             our $VERSION = 0.06;
5              
6 3     3   1771 use Attribute::Handlers;
  3         15191  
  3         16  
7 3     3   113 use Carp qw[ croak ];
  3         8  
  3         138  
8 3     3   1874 use Function::Parameters;
  3         10926  
  3         13  
9 3     3   2745 use Import::Into;
  3         8690  
  3         104  
10 3     3   1819 use Role::Tiny;
  3         13322  
  3         18  
11 3     3   527 use Scalar::Util qw[ refaddr ];
  3         5  
  3         149  
12 3     3   1787 use Types::Standard ':all';
  3         309618  
  3         44  
13              
14 3     3   160288 use namespace::clean;
  3         42303  
  3         21  
15              
16             use constant { # Attribute::Handlers argument positions
17 3         1267 PACKAGE => 0,
18             SYMBOL => 1,
19             REFERENT => 2,
20             ATTRIBUTE => 3,
21             DATA => 4,
22 3     3   5283 };
  3         7  
23              
24             my %return_hooks;
25              
26             sub _install_hook {
27 18     18   45 my ($type, $target, $hook) = @_;
28 18   100     117 my $hooks = $return_hooks{ refaddr($target) } //= {};
29 18 50       52 croak "A $type hook for $target already exists" if defined $hooks->{$type};
30 18         39 $hooks->{$type} = $hook;
31 18         32 return;
32             }
33              
34 13     13   27 sub _install_scalar_hook { return _install_hook('scalar', @_) }
35 5     5   17 sub _install_list_hook { return _install_hook('list', @_) }
36              
37              
38             sub Return : ATTR(CODE,BEGIN) {
39 10     10 0 2129 my ($referent, $data) = @_[ REFERENT, DATA ];
40              
41 10 50       29 croak 'Only a single constraint is supported' if @$data != 1;
42 10         16 my $constraint = $data->[0];
43            
44             _install_scalar_hook($referent, sub {
45 23     23   42 my $orig = shift;
46 23         53 return $constraint->assert_return(scalar &$orig);
47 10         50 });
48              
49 10         36 return;
50 3     3   31 }
  3         7  
  3         38  
51              
52             sub ReturnMaybe : ATTR(CODE,BEGIN) {
53 2     2 0 1954 $_[DATA][0] = Maybe[ $_[DATA][0] ];
54 2         736 goto &Return;
55 3     3   4163 }
  3         8  
  3         23  
56              
57             sub _make_list_check {
58 5     5   18 my ($constraint, %args) = @_;
59 5         10 my $allow_empty = $args{allow_empty};
60 5 50       14 croak 'List constraint not defined' if not $constraint;
61              
62             return sub {
63 11     11   25 my $orig = shift;
64 11         26 my $retval = [&$orig];
65 11 100 66     91 return if not @$retval and $allow_empty;
66 10         14 return @{ $constraint->assert_return($retval) };
  10         31  
67 5         60 };
68             }
69              
70             sub ReturnList : ATTR(CODE,BEGIN) {
71 2     2 0 991 my ($referent, $data) = @_[ REFERENT, DATA ];
72 2         22 my $type = ArrayRef($data);
73 2         4877 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
74 2         5 return;
75 3     3   4200 }
  3         18  
  3         14  
76              
77             sub ReturnMaybeList : ATTR(CODE,BEGIN) {
78 1     1 0 477 my ($referent, $data) = @_[ REFERENT, DATA ];
79 1         4 my $type = ArrayRef($data);
80 1         139 _install_list_hook($referent, _make_list_check($type, allow_empty => 1));
81 1         2 return;
82 3     3   3689 }
  3         14  
  3         27  
83              
84             sub ReturnTuple : ATTR(CODE,BEGIN) {
85 1     1 0 567 my ($referent, $data) = @_[ REFERENT, DATA ];
86              
87 1         5 my $type = Tuple($data);
88 1         8155 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
89 1         3 return;
90 3     3   3889 }
  3         7  
  3         15  
91              
92             sub ReturnCycleTuple : ATTR(CODE,BEGIN) {
93 1     1 0 5 my ($referent, $data) = @_[ REFERENT, DATA ];
94              
95 1         6 my $type = CycleTuple($data);
96 1         18414 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
97 1         3 return;
98 3     3   3298 }
  3         6  
  3         14  
99              
100             sub ReturnHash : ATTR(CODE,BEGIN) {
101 1     1 0 477 my $data = $_[DATA];
102 1 50       5 croak 'Only a single constraint is supported' if @$data != 1;
103 1         5 unshift @$data, Str;
104 1         7 goto &ReturnCycleTuple;
105 3     3   2919 }
  3         6  
  3         13  
106              
107             sub _make_self_check {
108 3     3   8 my %args = @_;
109 3         6 my $allow_undef = $args{undef_ok};
110             return sub {
111 8     8   16 my $orig = shift;
112 8         23 my $orig_self_addr = refaddr($_[0]);
113 8         22 my $self = &$orig;
114 7 100 66     57 return $self if not defined $self and $allow_undef;
115 6 100 100     36 return $self if ref $self and refaddr($self) eq $orig_self_addr;
116 3         43 croak "$self was not the original invocant";
117 3         30 };
118             }
119              
120             sub ReturnSelf : ATTR(CODE,BEGIN) {
121 1     1 0 394 my $referent = $_[REFERENT];
122 1         3 _install_scalar_hook($referent, _make_self_check(undef_ok => 0));
123 1         3 return;
124 3     3   2552 }
  3         8  
  3         14  
125              
126             sub ReturnMaybeSelf : ATTR(CODE,BEGIN) {
127 2     2 0 812 my $referent = $_[REFERENT];
128 2         5 _install_scalar_hook($referent, _make_self_check(undef_ok => 1));
129 2         5 return;
130 3     3   3716 }
  3         11  
  3         18  
131              
132             sub ReturnObject : ATTR(CODE,BEGIN) {
133 1     1 0 767 $_[DATA][0] = Object;
134 1         6 goto &Return;
135 3     3   4976 }
  3         8  
  3         36  
136              
137             sub ReturnMaybeObject : ATTR(CODE,BEGIN) {
138 2     2 0 888 $_[DATA][0] = Maybe[Object];
139 2         587 goto &Return;
140 3     3   4769 }
  3         6  
  3         44  
141              
142             sub ReturnInstanceOf : ATTR(CODE,BEGIN) {
143 1     1 0 629 $_[DATA][0] = InstanceOf[$_[DATA][0]];
144 1         4431 goto &Return;
145 3     3   4683 }
  3         6  
  3         12  
146              
147             sub ReturnMaybeInstanceOf : ATTR(CODE,BEGIN) {
148 2     2 0 1287 $_[DATA][0] = Maybe[InstanceOf[$_[DATA][0]]];
149 2         830 goto &Return;
150 3     3   2834 }
  3         6  
  3         14  
151              
152             sub _build_validator {
153 17     17   32 my ($hooks) = @_;
154 17         27 my $val_scalar = $hooks->{scalar};
155 17         26 my $val_list = $hooks->{list};
156             return sub {
157 42 100 66 42   52524 goto &$val_list if wantarray and $val_list;
158 31 50 33     199 goto &$val_scalar if defined wantarray and $val_scalar;
159              
160             # void context or no validators
161 0         0 my $orig = shift;
162 0         0 goto &$orig;
163 17         71 };
164             }
165              
166             sub import {
167 3     3   24 my ($class, $mode) = @_;
168 3         15 my $package = scalar caller;
169 3 50       67 return if $class ne __PACKAGE__; # don't let this import spread around
170              
171 3         6 my $lax;
172 3 100       16 if (defined $mode) {
173 1 50       4 if ($mode eq '-lax') {
174 1         3 $lax = 1;
175             }
176             else {
177 0         0 croak "Unsupported mode: $mode";
178             }
179             }
180              
181             # make the caller a role first, so we can install modifiers
182 3         34 Role::Tiny->import::into($package);
183 3         1225 my $before = $package->can('before');
184 3         14 my $around = $package->can('around');
185              
186             my $installer = sub {
187 18     18   3674 my ($name, $coderef) = @_;
188 18         73 $before->($name, $coderef);
189              
190 18         194 my $hooks = delete $return_hooks{ refaddr($coderef) };
191 18 100       81 if (defined $hooks) {
192 17         38 my $return_validator = _build_validator($hooks);
193 17         48 $around->($name, $return_validator);
194             }
195              
196 18         1053 return;
197 3         14 };
198              
199 3         11 my %common_args = (
200             name => 'required',
201             install_sub => $installer,
202             );
203 3 100       11 $common_args{check_argument_count} = 0 if $lax;
204 3         73 Function::Parameters->import(
205             {
206             class_method => {
207             %common_args,
208             shift => [ [ '$class', ClassName ] ],
209             },
210             },
211             {
212             instance_method => {
213             %common_args,
214             shift => [ [ '$self', Object ] ],
215             },
216             },
217             {
218             method => {
219             %common_args,
220             shift => [ '$self' ],
221             },
222             },
223             );
224              
225             # allow importing package to use our attributes
226 3         2354 parent->import::into($package, $class);
227              
228 3         3272 return;
229             }
230              
231             1;