File Coverage

blib/lib/Sub/HandlesVia/Toolkit.pm
Criterion Covered Total %
statement 91 120 75.8
branch 29 48 60.4
condition 7 12 58.3
subroutine 15 21 71.4
pod 0 4 0.0
total 142 205 69.2


line stmt bran cond sub pod time code
1 94     94   1828 use 5.008;
  94         346  
2 94     94   583 use strict;
  94         213  
  94         2183  
3 94     94   511 use warnings;
  94         234  
  94         6339  
4              
5             package Sub::HandlesVia::Toolkit;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.046';
9              
10 94     94   690 use Sub::HandlesVia::Mite;
  94         229  
  94         1137  
11              
12 94     94   64927 use Type::Params qw(compile_named_oo);
  94         8920331  
  94         1340  
13 94     94   41761 use Types::Standard qw( ArrayRef HashRef Str Num Int CodeRef Bool Item Object );
  94         760  
  94         898  
14 94     94   358950 use Types::Standard qw( is_ArrayRef is_HashRef is_Str is_Int is_CodeRef );
  94         489  
  94         506  
15              
16             my $sig;
17             sub install_delegations {
18 345   66 345 0 2079 $sig ||= compile_named_oo(
19             target => Str,
20             attribute => ArrayRef->of(Str|CodeRef)->plus_coercions(Str|CodeRef, '[$_]'),
21             handles_via => ArrayRef->of(Str)->plus_coercions(Str, '[$_]'),
22             handles => HashRef->plus_coercions(ArrayRef, '+{map(+($_,$_),@$_)}'),
23             );
24            
25 345         6593868 my $me = shift;
26 345         1481 my $arg = &$sig;
27              
28 345         31009 my $gen = $me->code_generator_for_attribute(
29             $arg->target,
30             $arg->attribute,
31             );
32            
33 94     94   253362 use Sub::HandlesVia::Handler;
  94         871  
  94         1595  
34 345         1053 my %handles = %{ $arg->handles };
  345         4619  
35 345         3649 for my $h (sort keys %handles) {
36            
37             my $handler = 'Sub::HandlesVia::Handler'->lookup(
38 4244         24703 $handles{$h},
39             $arg->handles_via,
40             );
41            
42 4244         13363 $handler->install_method(
43             method_name => $h,
44             code_generator => $gen,
45             );
46             }
47             }
48              
49             my %native = qw(
50             Array 1
51             Blessed 1
52             Bool 1
53             Code 1
54             Counter 1
55             Hash 1
56             Number 1
57             Scalar 1
58             String 1
59             );
60              
61             sub known_handler_libraries {
62 0     0 0 0 sort keys %native;
63             }
64              
65             my %default_type = (
66             Array => ArrayRef,
67             Hash => HashRef,
68             String => Str,
69             Number => Num,
70             Counter => Int,
71             Code => CodeRef,
72             Bool => Bool,
73             Scalar => Item,
74             Blessed => Object,
75             );
76              
77             my $trait_to_class = sub {
78             my $hv_trait = shift;
79             my $hv_class = $hv_trait =~ /:/
80             ? $hv_trait
81             : "Sub::HandlesVia::HandlerLibrary::$hv_trait";
82             if ( $hv_class ne $hv_trait ) {
83             local $@;
84             eval "require $hv_class; 1" or warn $@;
85             }
86             $hv_class;
87             };
88              
89             sub clean_spec {
90 338     338 0 1442 my ($me, $target, $attr, $spec) = (shift, @_);
91              
92 338         994 delete $spec->{no_inline};
93              
94             # Clean our stuff out of traits list...
95 338 100 66     2650 if (ref $spec->{traits} and not $spec->{handles_via}) {
96 223         587 my @keep = grep !$native{$_}, @{$spec->{traits}};
  223         1321  
97 223         568 my @cull = grep $native{$_}, @{$spec->{traits}};
  223         1008  
98 223         626 delete $spec->{traits};
99 223 50       931 if (@keep) {
100 0         0 $spec->{traits} = \@keep;
101             }
102 223 50       931 if (@cull) {
103 223         842 $spec->{handles_via} = \@cull;
104             }
105             }
106              
107 338 50       1448 return unless $spec->{handles_via};
108              
109 338 100       1514 my @handles_via = ref($spec->{handles_via}) ? @{$spec->{handles_via}} : $spec->{handles_via};
  277         949  
110 338         1189 my $joined = join('|', @handles_via);
111              
112 338         925 for my $hv ( @handles_via ) {
113 338         1339 $trait_to_class->( $hv )->preprocess_spec( $target, $attr, $spec );
114             }
115              
116 338 100 100     3010 if ($default_type{$joined} and not exists $spec->{isa}) {
117 15         243 $spec->{isa} = $default_type{$joined};
118 15 50       95 $spec->{coerce} = 1 if $default_type{$joined}->has_coercion;
119             }
120              
121             # Canonicalize handles hashref
122 338         4917 my %canon_handles;
123             my @handles = is_ArrayRef( $spec->{handles} )
124 6         31 ? @{ delete $spec->{handles} }
125 338 100       2857 : delete( $spec->{handles} );
126 338         1702 while ( @handles ) {
127 342         933 my $item = shift @handles;
128 342 100       1675 $item = $trait_to_class->( $handles_via[0] )->expand_shortcut( $target, $attr, $spec, $item )
129             if is_Int $item;
130 342 100       2160 if ( is_Str $item ) {
    50          
131 8         35 $canon_handles{$item} = $item;
132             }
133             elsif ( is_HashRef $item ) {
134 334         5037 %canon_handles = ( %canon_handles, %$item );
135             }
136             else {
137 0         0 require Carp;
138 0         0 Carp::croak( "Unknown item as handles option: $item" );
139             }
140             }
141              
142             return {
143             target => $target,
144             attribute => $attr,
145 338         3689 handles_via => delete( $spec->{handles_via} ),
146             handles => \%canon_handles,
147             };
148             }
149              
150             sub code_generator_for_attribute {
151 3     3 0 14 my ($me, $target, $attr) = (shift, @_);
152              
153 3         12 my ($get_slot, $set_slot, $default) = @$attr;
154 3 50       17 $set_slot = $get_slot if @$attr < 2;
155              
156 3         8 my $captures = {};
157 3         13 my ($get, $set, $slot, $get_is_lvalue) = (undef, undef, undef, 0);
158              
159 3         25 require B;
160              
161 3 50       32 if (ref $get_slot) {
    50          
    100          
162 0     0   0 $get = sub { shift->generate_self . '->$shv_reader' };
  0         0  
163 0         0 $captures->{'$shv_reader'} = \$get_slot;
164             }
165             elsif ($get_slot =~ /\A \[ ([0-9]+) \] \z/sx) {
166 0         0 my $index = $1;
167 0     0   0 $get = sub { shift->generate_self . "->[$index]" };
  0         0  
168 0         0 $slot = $get;
169 0         0 ++$get_is_lvalue;
170             }
171             elsif ($get_slot =~ /\A \{ (.+) \} \z/sx) {
172 1         6 my $key = B::perlstring($1);
173 1     4   6 $get = sub { shift->generate_self . "->{$key}" };
  4         11  
174 1         3 $slot = $get;
175 1         3 ++$get_is_lvalue;
176             }
177             else {
178 2         11 my $method = B::perlstring($get_slot);
179 2     8   16 $get = sub { shift->generate_self . "->\${\\ $method}" };
  8         25  
180             }
181              
182 3 50       23 if (ref $set_slot) {
    50          
    100          
183             $set = sub {
184 0     0   0 my ($gen, $val) = @_;
185 0         0 $gen->generate_self . "->\$shv_writer($val)";
186 0         0 };
187 0         0 $captures->{'$shv_writer'} = \$set_slot;
188             }
189             elsif ($set_slot =~ /\A \[ ([0-9]+) \] \z/sx) {
190 0         0 my $index = $1;
191             $set = sub {
192 0     0   0 my ($gen, $val) = @_;
193 0         0 my $self = $gen->generate_self;
194 0         0 "($self\->[$index] = $val)";
195 0         0 };
196             }
197             elsif ($set_slot =~ /\A \{ (.+) \} \z/sx) {
198 1         6 my $key = B::perlstring($1);
199             $set = sub {
200 2     2   9 my ($gen, $val) = @_;
201 2         6 my $self = $gen->generate_self;
202 2         12 "($self\->{$key} = $val)";
203 1         6 };
204             }
205             else {
206 2         8 my $method = B::perlstring($set_slot);
207             $set = sub {
208 4     4   21 my ($gen, $val) = @_;
209 4         12 my $self = $gen->generate_self;
210 4         27 "$self\->\${\\ $method}($val)";
211 2         12 };
212             }
213              
214 3 50       17 if (is_CodeRef $default) {
215 0         0 $captures->{'$shv_default_for_reset'} = \$default;
216             }
217              
218 3         1688 require Sub::HandlesVia::CodeGenerator;
219             return 'Sub::HandlesVia::CodeGenerator'->new(
220             toolkit => $me,
221             target => $target,
222             attribute => $attr,
223             env => $captures,
224             coerce => !!0,
225             generator_for_get => $get,
226             generator_for_set => $set,
227             get_is_lvalue => $get_is_lvalue,
228             set_checks_isa => !!1,
229             set_strictly => !!1,
230             generator_for_default => sub {
231 0 0   0     my ( $gen, $handler ) = @_ or die;
232 0 0 0       if ( !$default and $handler ) {
    0          
    0          
233 0           return $handler->default_for_reset->();
234             }
235             elsif ( is_CodeRef $default ) {
236 0           return sprintf(
237             '(%s)->$shv_default_for_reset',
238             $gen->generate_self,
239             );
240             }
241             elsif ( is_Str $default ) {
242 0           require B;
243 0           return sprintf(
244             '(%s)->${\ %s }',
245             $gen->generate_self,
246             B::perlstring( $default ),
247             );
248             }
249 0           return;
250             },
251 3 100       49 ( $slot ? ( generator_for_slot => $slot ) : () ),
252             );
253             }
254              
255             1;
256              
257             __END__
258              
259             =pod
260              
261             =encoding utf-8
262              
263             =head1 NAME
264              
265             Sub::HandlesVia::Toolkit - integration with OO frameworks for Sub::HandlesVia
266              
267             =head1 DESCRIPTION
268              
269             B<< This module is part of Sub::HandlesVia's internal API. >>
270             It is mostly of interest to people extending Sub::HandlesVia.
271              
272             Detect what subclass of Sub::HandlesVia::Toolkit is suitable for a class:
273              
274             my $toolkit = Sub::HandlesVia->detect_toolkit($class);
275              
276             Extract handles_via information from a C<has> attribute spec hash:
277              
278             my $shvdata = $toolkit->clean_spec($class, $attrname, \%spec);
279              
280             This not only returns the data that Sub::HandlesVia needs, it also cleans
281             C<< %spec >> so that it can be passed to a Moose-like C<has> function
282             without it complaining about unrecognized options.
283              
284             $toolkit->install_delegations($shvdata) if $shvdata;
285              
286             =head1 BUGS
287              
288             Please report any bugs to
289             L<https://github.com/tobyink/p5-sub-handlesvia/issues>.
290              
291             =head1 SEE ALSO
292              
293             L<Sub::HandlesVia>.
294              
295             =head1 AUTHOR
296              
297             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
298              
299             =head1 COPYRIGHT AND LICENCE
300              
301             This software is copyright (c) 2020, 2022 by Toby Inkster.
302              
303             This is free software; you can redistribute it and/or modify it under
304             the same terms as the Perl 5 programming language system itself.
305              
306             =head1 DISCLAIMER OF WARRANTIES
307              
308             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
309             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
310             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
311