File Coverage

blib/lib/Attean/API/Iterator.pm
Criterion Covered Total %
statement 162 197 82.2
branch 30 36 83.3
condition 14 23 60.8
subroutine 42 52 80.7
pod 9 22 40.9
total 257 330 77.8


line stmt bran cond sub pod time code
1 50     50   611 use v5.14;
  50         163  
2 50     50   292 use warnings;
  50         88  
  50         2347  
3              
4             =head1 NAME
5              
6             Attean::API::Iterator - Typed iterator
7              
8             =head1 VERSION
9              
10             This document describes Attean::API::Iterator version 0.032
11              
12             =head1 DESCRIPTION
13              
14             The Attean::API::Iterator role defines a common API for typed iterators.
15             This package also defines several type-specific iterator roles:
16              
17             =over 4
18              
19             =item * L<Attean::API::TripleIterator>
20              
21             =item * L<Attean::API::QuadIterator>
22              
23             =item * L<Attean::API::MixedStatementIterator>
24              
25             =item * L<Attean::API::ResultIterator>
26              
27             =back
28              
29             These roles will automatically be applied to iterators during construction when appropriate.
30              
31             =head1 ATTRIBUTES
32              
33             The following attributes exist:
34              
35             =over 4
36              
37             =item C<< item_type >>
38              
39             A string indicating the type of elements returned by the iterator.
40              
41             =back
42              
43             =head1 REQUIRED METHODS
44              
45             The following methods are required by the L<Attean::API::Iterator> role:
46              
47             =over 4
48              
49             =item C<< next >>
50              
51             Returns the next element from the iterator, or C<< undef >> upon exhaustion.
52              
53             =back
54              
55             =head1 METHODS
56              
57             The L<Attean::API::Iterator> role provides default implementations of the
58             following methods:
59              
60             =over 4
61              
62             =item C<< elements >>
63              
64             Returns a list of all remaining elements in the iterator.
65              
66             =item C<< map( \&mapper [, $result_type] ) >>
67              
68             Returns a new L<Attean::API::Iterator> object with each element mapped using
69             the supplied C<< &mapper >> function. If the iterator elements are of the same
70             type as those in the referent iterator, only a mapping function is required.
71             Otherwise, the supplied L<Type::Tiny> C<< $result_type >> object must indicate
72             the new iterator's type information.
73              
74             =item C<< grep( \&filter ) >>
75              
76             Returns a new L<Attean::API::Iterator> object that filters elements from the
77             referent iterator based on whether calling C<< &filter( $element ) >> for each
78             C<< $element >> results in a true value.
79              
80             =item C<< offset( $offset ) >>
81              
82             Returns the L<Attean::API::Iterator> referent after skipping the first
83             C<< $offset >> elements.
84              
85             =item C<< limit( $limit ) >>
86              
87             Returns a new L<Attean::API::Iterator> object which returns the first
88             C<< $limit >> elements of the referent.
89              
90             =item C<< materialize >>
91              
92             Returns a new L<Attean::API::RepeatableIterator> object containing all the
93             elements from the referent.
94              
95             =cut
96              
97             use Scalar::Util qw(blessed);
98 50     50   256 use Types::Standard qw(Str Object InstanceOf);
  50         91  
  50         2059  
99 50     50   292 use Carp qw(confess);
  50         94  
  50         558  
100 50     50   33590  
  50         101  
  50         1944  
101             use Moo::Role;
102 50     50   264
  50         90  
  50         324  
103             has 'item_type' => (is => 'ro', isa => Str, required => 1);
104             requires 'next';
105              
106             around 'BUILD' => sub {
107       645 0   my $orig = shift;
108             my $self = shift;
109             my $args = shift;
110             $self->$orig($args);
111             my $role = $self->item_type;
112             if (Moo::Role->is_role($role)) {
113             my $check = sub {
114             my $check = shift;
115             return ($role eq $check or Moo::Role::does_role($role, $check));
116             };
117             if ($check->('Attean::API::Quad')) {
118             Moo::Role->apply_roles_to_object($self, 'Attean::API::QuadIterator');
119             } elsif ($check->('Attean::API::Triple')) {
120             Moo::Role->apply_roles_to_object($self, 'Attean::API::TripleIterator');
121             } elsif ($check->('Attean::API::TripleOrQuad')) {
122             Moo::Role->apply_roles_to_object($self, 'Attean::API::MixedStatementIterator');
123             } elsif ($check->('Attean::API::Result')) {
124             Moo::Role->apply_roles_to_object($self, 'Attean::API::ResultIterator');
125             my $vars = $args->{variables} // confess "Construction of a Attean::API::ResultIterator must include a variables list";
126             $self->variables($vars);
127             } elsif ($check->('Attean::API::Term')) {
128             Moo::Role->apply_roles_to_object($self, 'Attean::API::TermIterator');
129             } elsif ($check->('Attean::API::ResultOrTerm')) {
130             Moo::Role->apply_roles_to_object($self, 'Attean::API::ResultOrTermIterator');
131             $self->variables($args->{variables} || []);
132             }
133              
134             if ($self->does('Attean::API::RepeatableIterator') and $check->('Attean::API::Binding')) {
135             Moo::Role->apply_roles_to_object($self, 'Attean::API::CanonicalizingBindingSet');
136             }
137             }
138             };
139            
140             if ($ENV{ATTEAN_TYPECHECK}) {
141             around 'next' => sub {
142             my $orig = shift;
143             my $self = shift;
144             my $type = $self->item_type;
145             my $class = ref($self);
146             my $term = $self->$orig(@_);
147             return unless defined($term);
148             if (blessed($term)) {
149             unless ($term->does($type) or $term->isa($type)) {
150             die "${class} returned an element that failed conformance check for $type: $term";
151             }
152             }
153             return $term;
154             };
155             }
156            
157             my $self = shift;
158             my @elements;
159             while (my $item = $self->next) { push(@elements, $item); }
160 61     61 1 2751 return @elements;
161 61         92 }
162 61         175
  104         287  
163 61         255 my $self = shift;
164             my $block = shift;
165             my $type = shift || $self->item_type;
166            
167 165     165 1 1104 my $generator;
168 165         239 if (blessed($block) and $block->does('Attean::Mapper')) {
169 165   66     463 $generator = sub {
170             my $item = $self->next();
171 165         232 return unless defined($item);
172 165 100 66     544 my $new = $block->map($item);
173             return $new;
174 4     4   11 }
175 4 50       9 } else {
176 4         18 my @buffer;
177 4         8 $generator = sub {
178             while (1) {
179 2         28 return shift(@buffer) if (scalar(@buffer));
180 163         251 my $item = $self->next();
181             return unless defined($item);
182 503     503   672 local($_) = $item;
183 859 100       6864 push(@buffer, $block->($item));
184 503         1389 }
185 503 100       1019 }
186 356         522 }
187 356         916
188             # copy variables into new iterator if $self does ::ResultIterator or ::ResultOrTermIterator
189             my %args = @_;
190 163         557 if ($self->can('variables') and not exists $args{variables}) {
191             $args{variables} = $self->variables;
192             }
193 165         394
194 165 100 100     1008 return Attean::CodeIterator->new( %args, item_type => $type, generator => $generator );
195 101         729 }
196              
197             my $self = shift;
198 165         3303 my $block = shift;
199            
200             # copy variables into new iterator if $self does ::ResultIterator or ::ResultOrTermIterator
201             my %args = @_;
202 195     195 1 592 if ($self->can('variables') and not exists $args{variables}) {
203 195         257 $args{variables} = $self->variables;
204             }
205            
206 195         336 Attean::CodeIterator->new(
207 195 100 66     1149 %args,
208 182         579 item_type => $self->item_type,
209             generator => sub {
210             while (1) {
211             my $item = $self->next();
212             return unless defined($item);
213             local($_) = $item;
214             return $item if ($block->($item));
215 682     682   841 }
216 1196         2976 }
217 1196 100       2184 );
218 1014         1388 }
219 1014 100       1789
220             my $self = shift;
221             my $offset = shift;
222 195         4154 $self->next for (1 .. $offset);
223             return $self;
224             }
225            
226 3     3 1 33 my $self = shift;
227 3         4 my $limit = shift;
228 3         17
229 3         16 # copy variables into new iterator if $self does ::ResultIterator or ::ResultOrTermIterator
230             my %args = @_;
231             if ($self->can('variables') and not exists $args{variables}) {
232             $args{variables} = $self->variables;
233 3     3 1 17 }
234 3         6
235             Attean::CodeIterator->new(
236             %args,
237 3         7 item_type => $self->item_type,
238 3 100 66     30 generator => sub {
239 2         35 return unless $limit;
240             my $item = $self->next();
241             return unless defined($item);
242             $limit--;
243             return $item;
244             }
245             );
246 10 100   10   25 }
247 7         16
248 7 50       21 my $self = shift;
249 7         9 my @data = $self->elements;
250 7         14 my %args = @_;
251             if ($self->can('variables') and not exists $args{variables}) {
252 3         78 $args{variables} = $self->variables;
253             }
254            
255             return Attean::ListIterator->new( %args, values => \@data, item_type => $self->item_type );
256 0     0 1 0 }
257 0         0
258 0         0 =item C<< debug( [$name] ) >>
259 0 0 0     0  
260 0         0 Print each item as it is consumed (with the string generated by C<< as_string >>), prepended by C<< $name >>.
261              
262             =cut
263 0         0  
264             my $self = shift;
265             my $name = shift // 'Iterator item';
266             return $self->grep(sub { my $r = shift; say "$name: " . $r->as_string; return 1; });
267             }
268             }
269              
270             use Moo::Role;
271             my $self = shift;
272            
273 0     0 1 0 my %seen;
274 0   0     0 return $self->grep(sub {
275 0     0   0 my $r = shift;
  0         0  
  0         0  
  0         0  
276             return not($seen{ $r->as_string }++);
277             });
278             }
279             }
280 50     50   68085  
  50         111  
  50         243  
281             use Moo::Role;
282 2     2 0 55 requires 'reset';
283            
284 2         3 my $self = shift;
285             my @elements;
286 16     16   20 while (my $item = $self->next) { push(@elements, $item); }
287 16         43 $self->reset;
288 2         16 return @elements;
289             }
290            
291             my $self = shift;
292             my $item = $self->next;
293 50     50   19117 $self->reset;
  50         124  
  50         205  
294             return $item;
295             }
296              
297 805     805 1 20907 my $self = shift;
298 805         986 return $self;
299 805         1860 }
  1488         3380  
300 805         2081  
301 805         16350 my $self = shift;
302             my @elements = $self->elements;
303             return scalar(@elements);
304             }
305 0     0 1 0
306 0         0 with 'Attean::API::Iterator';
307 0         0 }
308 0         0  
309             use Moo::Role;
310             my $self = shift;
311             my $mapper = Attean::TermMap->canonicalization_map;
312 2     2 0 5 return $self->map(sub { shift->apply_map( $mapper ) });
313 2         4 }
314             }
315              
316             use Moo::Role;
317 0     0 0 0 use Types::Standard qw(ArrayRef Str);
318 0         0 has 'variables' => (is => 'rw', isa => ArrayRef[Str], default => sub { [] });
319 0         0 with 'Attean::API::StringyItemIterator';
320            
321             my $self = shift;
322             my $mapper = Attean::TermMap->canonicalization_map;
323             return $self->map(sub{
324             my $item = shift;
325             if ($item->does('Attean::API::Term')) {
326 50     50   23227 return $mapper->map($item);
  50         122  
  50         211  
327             } else {
328 0     0 0 0 my %values = map { $_ => $mapper->map($item->value($_)) } $item->variables;
329 0         0 return Attean::Result->new( bindings => \%values );
330 0     0   0 }
  0         0  
331             });
332             }
333            
334             around 'grep' => sub {
335 50     50   18522 my $orig = shift;
  50         105  
  50         199  
336 50     50   14938 my $self = shift;
  50         116  
  50         316  
337             my $block = shift;
338             my $iter = $orig->($self, $block, @_);
339             Attean::CodeIterator->new(
340             item_type => $iter->item_type,
341 0     0 0 0 generator => sub {
342 0         0 return $iter->next();
343             },
344 0     0   0 variables => $self->variables,
345 0 0       0 );
346 0         0 };
347             }
348 0         0  
  0         0  
349 0         0 use Moo::Role;
350             use Scalar::Util qw(blessed);
351 0         0 with 'Attean::API::StringyItemIterator';
352             requires 'variables';
353              
354             my $self = shift;
355             my @nodes = @_;
356            
357             if (scalar(@nodes) == 1 and $nodes[0]->does('Attean::API::QuadPattern')) {
358             my $pattern = $nodes[0];
359             @nodes = $pattern->values;
360             }
361              
362             my %bound;
363             my @pos_names = $self->variables;
364             foreach my $pos (0 .. $#pos_names) {
365             my $n = $nodes[ $pos ];
366             if (blessed($n)) {
367             $bound{ $pos_names[$pos] } = $n;
368             }
369             }
370 50     50   39279
  50         112  
  50         214  
371 50     50   15558 my $pattern = Attean::QuadPattern->new( %bound );
  50         103  
  50         12105  
372             return $self->grep(sub {
373             my $q = shift;
374             my $binding = $pattern->unify($q);
375             return $binding ? 1 : 0;
376 123     123 0 3340 });
377 123         255 }
378             }
379 123 100 100     521  
380 101         1391 use Moo::Role;
381 101         306 with 'Attean::API::CanonicalizingBindingIterator';
382             with 'Attean::API::StatementIterator';
383              
384 123         406 my $self = shift;
385 123         334 my $graph = shift;
386 123         345 return $self->map(sub { $_->as_quad($graph) }, 'Attean::API::Quad');
387 492         594 }
388 492 100       1081  
389 419         895 return qw(subject predicate object);
390             }
391             }
392              
393 123         2505 use Moo::Role;
394             with 'Attean::API::CanonicalizingBindingIterator';
395 231     231   304 with 'Attean::API::StatementIterator';
396 231         643
397 231 100       6133 return qw(subject predicate object graph);
398 123         4442 }
399             }
400              
401             use Moo::Role;
402             with 'Attean::API::CanonicalizingBindingIterator';
403 50     50   362 with 'Attean::API::StringyItemIterator';
  50         94  
  50         209  
404             my $self = shift;
405             my $graph = shift;
406             return $self->map(
407             sub { $_->does('Attean::API::Quad') ? $_ : $_->as_quad($graph) },
408 30     30 0 4802 'Attean::API::Quad'
409 30         58 );
410 30     88   230 }
  88         277  
411             }
412              
413             use Types::Standard qw(Str ArrayRef);
414 101     101 0 268 use Moo::Role;
415            
416             with 'Attean::API::CanonicalizingBindingIterator';
417             with 'Attean::API::StringyItemIterator';
418             has 'variables' => (is => 'rw', isa => ArrayRef[Str], required => 1);
419 50     50   20275 my $self = shift;
  50         141  
  50         246  
420             my $rhs = shift;
421             my @vars = keys %{ { map { $_ => 1 } (@{ $self->variables }, @{ $rhs->variables }) } };
422            
423             my @rhs = $rhs->elements;
424 267     267 0 592 my @results;
425             while (my $lhs = $self->next) {
426             foreach my $rhs (@rhs) {
427             if (my $j = $lhs->join($rhs)) {
428             push(@results, $j);
429 50     50   17603 }
  50         110  
  50         227  
430             }
431             }
432             return Attean::ListIterator->new( values => \@results, item_type => $self->item_type, variables => \@vars);
433 2     2 0 142 }
434 2         4  
435             with 'Attean::API::ResultOrTermIterator';
436 10 100   10   19 }
437 2         29  
438             use Moo::Role;
439             my $self = shift;
440             my $mapper = Attean::TermMap->canonicalization_map;
441             return $self->map( $mapper );
442             }
443 50     50   20142 with 'Attean::API::CanonicalizingBindingIterator';
  50         126  
  50         276  
444 50     50   25901 with 'Attean::API::StringyItemIterator';
  50         108  
  50         205  
445             }
446              
447             1;
448              
449              
450 5     5 0 12 =back
451 5         6  
452 5         8 =head2 Methods on Roles Supporting Stringification
  5         12  
  13         55  
  5         110  
  5         90  
453              
454 5         25 For iterators over roles that provide an C<as_string> method, extra methods
455 5         11 are provided. These iterators are:
456 5         15  
457 6         12 Attean::API::ResultOrTermIterator
458 9 100       23 Attean::API::StatementIterator
459 7         30 Attean::API::MixedStatementIterator
460             Attean::API::ResultIterator
461             Attean::API::TermIterator
462              
463 5         109 They provide the following methods:
464              
465             =over 4
466              
467             =item C<< uniq >>
468              
469             Returns a new iterator providing unique results (based on the stringified value of the underlying elements).
470 50     50   24486  
  50         123  
  50         209  
471             =back
472 0     0 0    
473 0           =head1 BUGS
474 0            
475             Please report any bugs or feature requests to through the GitHub web interface
476             at L<https://github.com/kasei/attean/issues>.
477              
478             =head1 SEE ALSO
479              
480             L<Attean::API::RepeatableIterator>
481              
482              
483              
484             =head1 AUTHOR
485              
486             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
487              
488             =head1 COPYRIGHT
489              
490             Copyright (c) 2014--2022 Gregory Todd Williams.
491             This program is free software; you can redistribute it and/or modify it under
492             the same terms as Perl itself.
493              
494             =cut