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   1740 use 5.008;
  94         365  
2 94     94   553 use strict;
  94         205  
  94         2155  
3 94     94   518 use warnings;
  94         240  
  94         5937  
4              
5             package Sub::HandlesVia::Toolkit;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.050000';
9              
10 94     94   708 use Sub::HandlesVia::Mite;
  94         229  
  94         1041  
11              
12 94     94   63966 use Type::Params qw(compile_named_oo);
  94         8923183  
  94         1290  
13 94     94   40723 use Types::Standard qw( ArrayRef HashRef Str Num Int CodeRef Bool Item Object );
  94         752  
  94         2098  
14 94     94   344842 use Types::Standard qw( is_ArrayRef is_HashRef is_Str is_Int is_CodeRef );
  94         487  
  94         522  
15              
16             my $sig;
17             sub install_delegations {
18 345   66 345 0 1924 $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         7152398 my $me = shift;
26 345         1483 my $arg = &$sig;
27              
28 345         29974 my $gen = $me->code_generator_for_attribute(
29             $arg->target,
30             $arg->attribute,
31             );
32            
33 94     94   248590 use Sub::HandlesVia::Handler;
  94         750  
  94         1437  
34 345         1004 my %handles = %{ $arg->handles };
  345         4333  
35 345         3517 for my $h (sort keys %handles) {
36            
37             my $handler = 'Sub::HandlesVia::Handler'->lookup(
38 4244         23566 $handles{$h},
39             $arg->handles_via,
40             );
41            
42 4244         13336 $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 1388 my ($me, $target, $attr, $spec) = (shift, @_);
91              
92 338         901 delete $spec->{no_inline};
93              
94             # Clean our stuff out of traits list...
95 338 100 66     2496 if (ref $spec->{traits} and not $spec->{handles_via}) {
96 223         551 my @keep = grep !$native{$_}, @{$spec->{traits}};
  223         1264  
97 223         565 my @cull = grep $native{$_}, @{$spec->{traits}};
  223         921  
98 223         640 delete $spec->{traits};
99 223 50       834 if (@keep) {
100 0         0 $spec->{traits} = \@keep;
101             }
102 223 50       787 if (@cull) {
103 223         797 $spec->{handles_via} = \@cull;
104             }
105             }
106              
107 338 50       1311 return unless $spec->{handles_via};
108              
109 338 100       1282 my @handles_via = ref($spec->{handles_via}) ? @{$spec->{handles_via}} : $spec->{handles_via};
  277         931  
110 338         1114 my $joined = join('|', @handles_via);
111              
112 338         930 for my $hv ( @handles_via ) {
113 338         1227 $trait_to_class->( $hv )->preprocess_spec( $target, $attr, $spec );
114             }
115              
116 338 100 100     2860 if ($default_type{$joined} and not exists $spec->{isa}) {
117 15         247 $spec->{isa} = $default_type{$joined};
118 15 50       89 $spec->{coerce} = 1 if $default_type{$joined}->has_coercion;
119             }
120              
121             # Canonicalize handles hashref
122 338         4736 my %canon_handles;
123             my @handles = is_ArrayRef( $spec->{handles} )
124 6         24 ? @{ delete $spec->{handles} }
125 338 100       2536 : delete( $spec->{handles} );
126 338         1404 while ( @handles ) {
127 342         884 my $item = shift @handles;
128 342 100       1576 $item = $trait_to_class->( $handles_via[0] )->expand_shortcut( $target, $attr, $spec, $item )
129             if is_Int $item;
130 342 100       2030 if ( is_Str $item ) {
    50          
131 8         31 $canon_handles{$item} = $item;
132             }
133             elsif ( is_HashRef $item ) {
134 334         4549 %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         3615 handles_via => delete( $spec->{handles_via} ),
146             handles => \%canon_handles,
147             };
148             }
149              
150             sub code_generator_for_attribute {
151 3     3 0 15 my ($me, $target, $attr) = (shift, @_);
152              
153 3         11 my ($get_slot, $set_slot, $default) = @$attr;
154 3 50       16 $set_slot = $get_slot if @$attr < 2;
155              
156 3         7 my $captures = {};
157 3         11 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   7 $get = sub { shift->generate_self . "->{$key}" };
  4         20  
174 1         3 $slot = $get;
175 1         2 ++$get_is_lvalue;
176             }
177             else {
178 2         9 my $method = B::perlstring($get_slot);
179 2     8   12 $get = sub { shift->generate_self . "->\${\\ $method}" };
  8         33  
180             }
181              
182 3 50       30 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         5 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         14 "($self\->{$key} = $val)";
203 1         39 };
204             }
205             else {
206 2         8 my $method = B::perlstring($set_slot);
207             $set = sub {
208 4     4   23 my ($gen, $val) = @_;
209 4         12 my $self = $gen->generate_self;
210 4         28 "$self\->\${\\ $method}($val)";
211 2         25 };
212             }
213              
214 3 50       21 if (is_CodeRef $default) {
215 0         0 $captures->{'$shv_default_for_reset'} = \$default;
216             }
217              
218 3         1713 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       66 ( $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