File Coverage

blib/lib/Iterator/Flex/Factory.pm
Criterion Covered Total %
statement 122 157 77.7
branch 41 78 52.5
condition 12 31 38.7
subroutine 17 22 77.2
pod 4 6 66.6
total 196 294 66.6


line stmt bran cond sub pod time code
1             package Iterator::Flex::Factory;
2              
3             # ABSTRACT: Create on-the-fly Iterator::Flex classes/objects
4              
5 43     43   527328 use v5.28;
  43         159  
6 43     43   234 use strict;
  43         95  
  43         1104  
7 43     43   198 use warnings;
  43         103  
  43         2636  
8              
9 43     43   1266 use experimental qw( signatures declared_refs refaliasing);
  43         5063  
  43         305  
10              
11             our $VERSION = '0.33';
12              
13 43     43   15500 use Ref::Util ();
  43         19444  
  43         973  
14 43     43   22425 use Role::Tiny ();
  43         236548  
  43         1334  
15 43     43   19348 use Role::Tiny::With ();
  43         11973  
  43         1127  
16 43     43   4487 use Module::Runtime;
  43         15967  
  43         312  
17              
18 43     43   1775 use Exporter 'import';
  43         97  
  43         2328  
19              
20             our @EXPORT_OK = qw( to_iterator construct_from_iterable construct_from_attr );
21              
22 43     43   22981 use Iterator::Flex::Base;
  43         160  
  43         2805  
23 43         131031 use Iterator::Flex::Utils qw[
24             :ExhaustionActions
25             :default
26             :RegistryIndices
27             :SignalParameters
28             :IterAttrs
29             parse_pars
30             throw_failure
31             can_meth
32 43     43   352 ];
  43         75  
33              
34              
35              
36              
37              
38              
39              
40              
41              
42              
43              
44              
45 141     141 1 271 sub to_iterator ( $iterable = undef, $pars = {} ) {
  141         3206  
  141         249  
  141         232  
46             return defined $iterable
47             ? construct_from_iterable( $iterable, $pars )
48             : construct( {
49       0     ( +NEXT ) => sub { },
50 141 50       627 } );
51             }
52              
53              
54              
55             ############################################################################
56              
57              
58              
59              
60              
61              
62              
63              
64              
65              
66              
67              
68 91     91 1 179 sub construct ( $in_ipar = {}, $in_gpar = {} ) { ## no critic (ExcessComplexity)
  91         167  
  91         216  
  91         154  
69              
70 91 50       253 throw_failure( parameter => q{'iterator parameters' parameter must be a hashref} )
71             unless Ref::Util::is_hashref( $in_ipar );
72              
73 91 50       236 throw_failure( parameter => q{'general parameters' parameter must be a hashref} )
74             unless Ref::Util::is_hashref( $in_gpar );
75              
76 91         394 my %ipar = $in_ipar->%*;
77 91         210 my %ipar_k;
78 91         382 @ipar_k{ keys %ipar } = ();
79 91         340 my %gpar = $in_gpar->%*;
80 91         163 my %gpar_k;
81 91         257 @gpar_k{ keys %gpar } = ();
82              
83 91         202 my $par;
84             my @roles;
85              
86 91   50     474 my $class = $ipar{ +CLASS } // 'Iterator::Flex::Base';
87 91         242 delete $ipar_k{ +CLASS };
88              
89 91 50       255 throw_failure( parameter => q{'class' parameter must be a string} )
90             if Ref::Util::is_ref( $class );
91              
92 91 50 33     420 throw_failure( parameter => "can't load class $class" )
93             if $class ne 'Iterator::Flex::Base'
94             && !Module::Runtime::require_module( $class );
95              
96 91         225 delete $ipar_k{ +_NAME };
97 0         0 throw_failure( parameter => "'@{[ _NAME ]}' parameter value must be a string\n" )
98 91 50 33     300 if defined( $par = $ipar{ +_NAME } ) && Ref::Util::is_ref( $par );
99              
100 91         233 push @roles, 'State::Registry';
101              
102 91         208 delete $gpar_k{ +INPUT_EXHAUSTION };
103 91   100     345 my $input_exhaustion = $gpar{ +INPUT_EXHAUSTION } // [ ( +RETURN ) => undef ];
104              
105             my @input_exhaustion
106 91 100       341 = Ref::Util::is_arrayref( $input_exhaustion )
107             ? ( $input_exhaustion->@* )
108             : ( $input_exhaustion );
109              
110 91         181 delete $gpar_k{ +EXHAUSTION };
111 91         253 my $has_output_exhaustion_policy = defined $gpar{ +EXHAUSTION };
112              
113 91 100       303 if ( $input_exhaustion[0] eq RETURN ) {
    100          
114 70         188 push @roles, 'Exhaustion::ImportedReturn', 'Wrap::Return';
115 70 50       227 push $input_exhaustion->@*, undef if @input_exhaustion == 1;
116 70         198 $gpar{ +INPUT_EXHAUSTION } = \@input_exhaustion;
117 70 100       229 $gpar{ +EXHAUSTION } = $gpar{ +INPUT_EXHAUSTION }
118             unless $has_output_exhaustion_policy;
119             }
120              
121             elsif ( $input_exhaustion[0] eq THROW ) {
122 18         38 push @roles, 'Exhaustion::ImportedThrow', 'Wrap::Throw';
123 18         41 $gpar{ +INPUT_EXHAUSTION } = \@input_exhaustion;
124 18 100       54 $gpar{ +EXHAUSTION } = [ ( +THROW ) => PASSTHROUGH ]
125             unless $has_output_exhaustion_policy;
126             }
127              
128             throw_failure( parameter => q{missing or undefined 'next' parameter} )
129 91 50       307 if !defined( $ipar{ +NEXT } );
130              
131 91         234 for my $method ( NEXT, REWIND, RESET, PREV, CURRENT ) {
132              
133 455         698 delete $ipar_k{$method};
134 455 100       1038 next unless defined( my $code = $ipar{$method} );
135              
136 236 50       526 throw_failure( parameter => "'$method' parameter value must be a code reference\n" )
137             unless Ref::Util::is_coderef( $code );
138              
139             # if $class can't perform the required method, add a role
140             # which can.
141 236 100       496 if ( $method eq NEXT ) {
142             # next is always a closure, but the caller may want to
143             # keep track of $self
144 91 100       316 push @roles, defined $ipar{ +_SELF } ? 'Next::ClosedSelf' : 'Next::Closure';
145 91         265 delete $ipar_k{ +_SELF };
146             }
147             else {
148 145 50       771 my $impl = $class->can( $method ) ? 'Method' : 'Closure';
149 145         551 push @roles, ucfirst( $method ) . q{::} . $impl;
150             }
151             }
152              
153             # these are dealt with in the iterator constructor.
154 91         243 delete @ipar_k{ METHODS, FREEZE };
155 91         186 delete $gpar_k{ +ERROR };
156              
157 91 50 33     473 if ( !!%ipar_k || !!%gpar_k ) {
158              
159 0 0       0 throw_failure( parameter => "unknown iterator parameters: @{[ join( ', ', keys %ipar_k ) ]}" )
  0         0  
160             if %ipar_k;
161 0 0       0 throw_failure( parameter => "unknown iterator parameters: @{[ join( ', ', keys %gpar_k ) ]}" )
  0         0  
162             if %gpar_k;
163             }
164              
165 91         265 $ipar{_roles} = \@roles;
166              
167 91         483 return $class->new_from_attrs( \%ipar, \%gpar );
168             }
169              
170              
171              
172              
173              
174              
175              
176              
177              
178              
179              
180              
181              
182              
183              
184              
185              
186              
187              
188              
189              
190              
191              
192              
193              
194              
195              
196              
197              
198              
199              
200              
201              
202              
203 200     200 1 345 sub construct_from_iterable ( $obj, $pars = {} ) {
  200         326  
  200         317  
  200         322  
204              
205 200         833 my ( $mpars, $ipars, $spars ) = parse_pars( $pars );
206              
207 200         502 my $action_on_failure = delete $mpars->{action_on_failure};
208              
209 200 50       578 throw_failure( parameter =>
210 0         0 "unknown parameters pased to construct_from_iterable: @{[ join ', ', keys $mpars->%* ]}" )
211             if $mpars->%*;
212              
213             ## no critic ( CascadingIfElse )
214 200 100       701 if ( Ref::Util::is_blessed_ref( $obj ) ) {
    100          
    50          
    0          
215              
216 80 50       858 return construct_from_iterator_flex( $obj, $ipars, $spars )
217             if $obj->isa( 'Iterator::Flex::Base' );
218              
219 0         0 return construct_from_object( $obj, $ipars, $spars );
220             }
221              
222             elsif ( Ref::Util::is_arrayref( $obj ) ) {
223 68 50       151 throw_failure(
224 0         0 parameter => "unknown parameters pased to construct_from_iterable: @{[ join ', ', $ipars->%* ]}" )
225             if $ipars->%*;
226 68         4091 require Iterator::Flex::Array;
227 68         243 return Iterator::Flex::Array->new( $obj, $spars );
228             }
229              
230             elsif ( Ref::Util::is_coderef( $obj ) ) {
231 52         221 return construct( { $ipars->%*, next => $obj }, $spars );
232             }
233              
234             elsif ( Ref::Util::is_globref( $obj ) ) {
235             return construct( {
236 0     0   0 $ipars->%*, next => sub { scalar <$obj> },
237             },
238 0         0 $spars,
239             );
240             }
241              
242             return undef
243 0 0       0 if $action_on_failure eq RETURN;
244              
245 0   0     0 throw_failure(
246             parameter => sprintf q{'%s' object is not iterable},
247             ( ref( $obj ) || 'SCALAR' ) );
248             }
249              
250              
251              
252              
253              
254              
255              
256              
257              
258              
259              
260              
261              
262              
263              
264              
265              
266              
267              
268              
269              
270              
271              
272 0     0 1 0 sub construct_from_object ( $obj, $ipar, $gpar ) {
  0         0  
  0         0  
  0         0  
  0         0  
273              
274 0         0 my %ipar = $ipar->%*;
275 0         0 my %gpar = $gpar->%*;
276              
277 0   0     0 $gpar{ +INPUT_EXHAUSTION } //= [ ( +RETURN ) => undef ];
278              
279 0 0       0 if ( !exists $ipar{ +NEXT } ) {
280 0         0 my $code;
281             ## no critic( CascadingIfElse )
282 0 0 0     0 if ( $code = can_meth( $obj, 'iter' ) ) {
    0          
    0          
    0          
283 0         0 $ipar{ +NEXT } = $code->( $obj );
284             }
285             elsif ( $code = can_meth( $obj, 'next' )
286             || overload::Method( $obj, '<>', undef, undef ) )
287             {
288 0     0   0 $ipar{ +NEXT } = sub { $code->( $obj ) };
  0         0  
289             }
290              
291             elsif ( $code = overload::Method( $obj, '&{}', undef, undef ) ) {
292 0         0 $ipar{ +NEXT } = $code->( $obj );
293             }
294              
295             elsif ( $code = overload::Method( $obj, '@{}', undef, undef ) ) {
296 0         0 require Iterator::Flex::Array;
297 0         0 return Iterator::Flex::Array->new( $code->( $obj ), \%gpar );
298             }
299              
300             }
301              
302 0         0 for my $method ( grep { !exists $ipar{$_} } PREV, CURRENT ) {
  0         0  
303 0         0 my $code = can_meth( $obj, $method );
304 0     0   0 $ipar{$method} = sub { $code->( $obj ) }
305 0 0       0 if $code;
306             }
307              
308 0         0 return construct( \%ipar, \%gpar );
309             }
310              
311              
312             # create a proxy object for an Iterator::Flex object. This is only
313             # required if an adaptor needs a different exhaustion signal than is
314             # provided by the object.
315              
316             # Currently, proxy objects are not treated specially when de-serializing
317             # (e.g., they'll be run through to_iterator), but it *should* be a no-op.
318              
319              
320 80     80 0 175 sub construct_from_iterator_flex ( $obj, $, $gpar ) {
  80         153  
  80         147  
  80         138  
321              
322             my \@registry
323             = exists $REGISTRY{ refaddr $obj }
324 80 50       535 ? $REGISTRY{ refaddr $obj }[REG_GENERAL]
325             : throw_failure( internal => q{non-registered Iterator::Flex iterator} );
326              
327              
328             # if caller didn't specify an exhaustion, set it to return => undef
329 80         145 my @want = do {
330 80   100     425 my $exhaustion = $gpar->{ +EXHAUSTION } // [ ( +RETURN ) => undef ];
331 80 100       356 Ref::Util::is_arrayref( $exhaustion )
332             ? ( $exhaustion->@* )
333             : ( $exhaustion );
334             };
335              
336              
337             # multiple different output exhaustion roles may have been
338             # applied, so the object may claim to support both roles,
339             # Exhaustion::Throw and Exhaustion::Return, although only the
340             # latest one applied will work. So, use what's in the registry to
341             # figure out what it actually does.
342              
343 80   33     310 my \@have = $registry[REG_GP_EXHAUSTION] // throw_failure(
344             internal => q{registered Iterator::Flex iterator doesn't have a registered exhaustion} );
345              
346             # reuse the object if the requested and existing exhaustion signals are the same.
347 80 50 33     934 return $obj
      66        
348             if $want[0] eq $have[0]
349             && ( ( defined $want[1] && defined $have[1] && $want[1] eq $have[1] )
350             || ( !defined $want[1] && !defined $have[1] ) );
351              
352             # now we need a proxy object.
353 36         197 my %gpars = (
354             exhaustion => [@want],
355             input_exhaustion => [@have],
356             );
357              
358 36         80 my %ipars;
359 36         108 for my $method ( NEXT, PREV, CURRENT, REWIND, RESET, FREEZE ) {
360 216 100       552 next unless defined( my $code = can_meth( $obj, $method ) );
361 197     656   930 $ipars{$method} = sub { $code->( $obj ) };
  656         1529  
362             }
363              
364 36         168 return construct( \%ipars, \%gpars );
365             }
366              
367 3     3 0 361683 sub construct_from_attr ( $in_ipar = {}, $in_gpar = {} ) {
  3         8  
  3         6  
  3         6  
368 3         11 my %gpar = $in_gpar->%*;
369              
370             # this indicates that there should be no wrapping of 'next'
371 3         11 $gpar{ +INPUT_EXHAUSTION } = PASSTHROUGH;
372 3         15 construct( $in_ipar, \%gpar );
373             }
374              
375             1;
376              
377             #
378             # This file is part of Iterator-Flex
379             #
380             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
381             #
382             # This is free software, licensed under:
383             #
384             # The GNU General Public License, Version 3, June 2007
385             #
386              
387             __END__