File Coverage

blib/lib/Iterator/Flex/Factory.pm
Criterion Covered Total %
statement 126 158 79.7
branch 48 80 60.0
condition 18 36 50.0
subroutine 17 22 77.2
pod 4 6 66.6
total 213 302 70.5


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