File Coverage

blib/lib/LINQ/DSL.pm
Criterion Covered Total %
statement 55 212 25.9
branch 13 186 6.9
condition 1 3 33.3
subroutine 18 61 29.5
pod 54 54 100.0
total 141 516 27.3


line stmt bran cond sub pod time code
1 1     1   120971 use 5.006;
  1         8  
2 1     1   6 use strict;
  1         2  
  1         20  
3 1     1   4 use warnings;
  1         1  
  1         51  
4              
5              
6             our $AUTHORITY = 'cpan:TOBYINK';
7             our $VERSION = '0.003';
8              
9             use LINQ ();
10 1     1   383 use LINQ::Util -all;
  1         3  
  1         29  
11 1     1   404  
  1         2  
  1         8  
12             use Exporter::Shiny;
13 1     1   528  
  1         2  
  1         18  
14             our %EXPORT_TAGS = (
15             essential => [qw/
16             Linq From Where Select Join GroupBy ForEach DefaultIfEmpty HashSmush
17             /],
18             join => [qw/
19             LeftJoin RightJoin InnerJoin OuterJoin GroupJoin
20             /],
21             sort => [qw/
22             OrderBy OrderByDescending Reverse
23             /],
24             filter => [qw/
25             Distinct Take TakeWhile Skip SkipWhile
26             /],
27             native => [qw/
28             ToList ToArray ToDictionary ToIterator
29             /],
30             aggregate => [qw/
31             Min Max Sum Average Aggregate Any All Contains Count SequenceEqual
32             /],
33             aggregate_safe => [qw/
34             Min Max Sum Average Aggregate Contains Count SequenceEqual
35             /],
36             field => [qw/
37             SelectX WhereX field fields check_fields
38             /],
39             type => [qw/
40             Cast OfType AutoObject
41             /],
42             combine => [qw/
43             Concat Union Intersect Except Zip
44             /],
45             get => [qw/
46             First FirstOrDefault Last LastOrDefault
47             Single SingleOrDefault ElementAt ElementAtOrDefault
48             /],
49             default => [qw/
50             :essential :sort :join :filter :native :aggregate :field :type :combine :get
51             /],
52             default_safe => [qw/
53             :essential :sort :join :filter :native :aggregate_safe :field :type :combine :get
54             /],
55             );
56              
57             our @EXPORT = map {
58             /^:(.+)$/ ? @{ $EXPORT_TAGS{$1} } : $_;
59             } @{ $EXPORT_TAGS{'default'} };
60              
61             our @EXPORT_OK = @EXPORT;
62              
63             our $Result;
64             our $Final;
65              
66             my $definition = shift;
67             local $Result;
68 1     1 1 106 local $Final = 0;
69 1         3 $definition->();
70 1         2 return @$Result if $Final eq 'LIST';
71 1         3 return $Result;
72 1 50       7 }
73 0         0  
74             {
75             my $ao;
76             require Types::Standard;
77             require Object::Adhoc;
78             $ao ||= Types::Standard::Object()->plus_coercions(
79 1     1 1 628 Types::Standard::HashRef(), \&Object::Adhoc::object,
80 1         73266 );
81 1   33     10 }
82             }
83              
84             my $source = pop;
85             my $type = @_ ? shift : 'source';
86            
87             if ( $type eq 'source' ) {
88 1     1 1 5 $Result = LINQ::LINQ( $source );
89 1 50       4 }
90             elsif ( $type eq 'range' ) {
91 1 50       4 $Result = LINQ::Range( @$source );
    0          
    0          
92 1         4 }
93             elsif ( $type eq 'repeat' ) {
94             $Result = LINQ::Repeat( @$source );
95 0         0 }
96             else {
97             die 'unknown source';
98 0         0 }
99             return;
100             }
101 0         0  
102             die if $Final;
103 1         3 die unless $Result;
104             $Result = $Result->where( @_ );
105             }
106              
107 0 0   0 1 0 @_ = scalar LINQ::Util::check_fields( @_ );
108 0 0       0 goto \&Select;
109 0         0 }
110              
111             die if $Final;
112             die unless $Result;
113 0     0 1 0 $Result = $Result->select( @_ );
114 0         0 }
115              
116             @_ = scalar LINQ::Util::fields( @_ );
117             goto \&Select;
118 1 50   1 1 4 }
119 1 50       7  
120 1         6 die if $Final;
121             die unless $Result;
122             my $other = LINQ::LINQ( shift );
123             $Result = $Result->join( $other, @_ );
124 1     1 1 9 }
125 1         23  
126             splice( @_, 1, 0, -left );
127             goto \&Join;
128             }
129 1 50   1 1 4  
130 1 50       8 splice( @_, 1, 0, -right );
131 1         5 goto \&Join;
132 1         6 }
133              
134             splice( @_, 1, 0, -inner );
135             goto \&Join;
136 1     1 1 4 }
137 1         5  
138             splice( @_, 1, 0, -outer );
139             goto \&Join;
140             }
141 0     0 1 0  
142 0         0 die if $Final;
143             die unless $Result;
144             my $other = LINQ::LINQ( shift );
145             $Result = $Result->group_join( $other, @_ );
146 0     0 1 0 }
147 0         0  
148             die if $Final;
149             die unless $Result;
150             $Result = $Result->group_by( @_ );
151 0     0 1 0 }
152 0         0  
153             die if $Final;
154             die unless $Result;
155             $Result = $Result->distinct( @_ );
156 0 0   0 1 0 }
157 0 0       0  
158 0         0 die if $Final;
159 0         0 die unless $Result;
160             $Result = $Result->order_by( @_ );
161             }
162              
163 0 0   0 1 0 die if $Final;
164 0 0       0 die unless $Result;
165 0         0 $Result = $Result->order_by_descending( @_ );
166             }
167              
168             die if $Final;
169 0 0   0 1 0 die unless $Result;
170 0 0       0 $Result = $Result->take( @_ );
171 0         0 }
172              
173             die if $Final;
174             die unless $Result;
175 1 50   1 1 4 $Result = $Result->take_while( @_ );
176 1 50       5 }
177 1         6  
178             die if $Final;
179             die unless $Result;
180             $Result = $Result->skip( @_ );
181 0 0   0 1 0 }
182 0 0       0  
183 0         0 die if $Final;
184             die unless $Result;
185             $Result = $Result->skip_while( @_ );
186             }
187 0 0   0 1 0  
188 0 0       0 die if $Final;
189 0         0 die unless $Result;
190             my $other = LINQ::LINQ( shift );
191             $Result = $Result->concat( $other, @_ );
192             }
193 0 0   0 1 0  
194 0 0       0 die if $Final;
195 0         0 die unless $Result;
196             $Result = $Result->reverse();
197             }
198              
199 0 0   0 1 0 die if $Final;
200 0 0       0 die unless $Result;
201 0         0 my $other = LINQ::LINQ( shift );
202             $Result = $Result->zip( $other, @_ );
203             }
204              
205 0 0   0 1 0 die if $Final;
206 0 0       0 die unless $Result;
207 0         0 $Result = $Result->default_if_empty( @_ );
208             }
209              
210             die if $Final;
211 0 0   0 1 0 die unless $Result;
212 0 0       0 $Result = $Result->cast( @_ );
213 0         0 }
214 0         0  
215             die if $Final;
216             die unless $Result;
217             $Result = $Result->of_type( @_ );
218 0 0   0 1 0 }
219 0 0       0  
220 0         0 die if $Final; ++$Final;
221             die unless $Result;
222             $Result = $Result->min( @_ );
223             }
224 0 0   0 1 0  
225 0 0       0 die if $Final; ++$Final;
226 0         0 die unless $Result;
227 0         0 $Result = $Result->max( @_ );
228             }
229              
230             die if $Final; ++$Final;
231 0 0   0 1 0 die unless $Result;
232 0 0       0 $Result = $Result->sum( @_ );
233 0         0 }
234              
235             die if $Final; ++$Final;
236             die unless $Result;
237 1 50   1 1 1033 $Result = $Result->average( @_ );
238 1 50       4 }
239 1         8  
240             die if $Final; ++$Final;
241             die unless $Result;
242             $Result = $Result->aggregate( @_ );
243 0 0   0 1 0 }
244 0 0       0  
245 0         0 die if $Final; ++$Final;
246             die unless $Result;
247             $Result = $Result->first( @_ );
248             }
249 0 0   0 1 0  
  0         0  
250 0 0       0 die if $Final; ++$Final;
251 0         0 die unless $Result;
252             $Result = $Result->first_or_default( @_ );
253             }
254              
255 0 0   0 1 0 die if $Final; ++$Final;
  0         0  
256 0 0       0 die unless $Result;
257 0         0 $Result = $Result->last( @_ );
258             }
259              
260             die if $Final; ++$Final;
261 0 0   0 1 0 die unless $Result;
  0         0  
262 0 0       0 $Result = $Result->last_or_default( @_ );
263 0         0 }
264              
265             die if $Final; ++$Final;
266             die unless $Result;
267 0 0   0 1 0 $Result = $Result->single( @_ );
  0         0  
268 0 0       0 }
269 0         0  
270             die if $Final; ++$Final;
271             die unless $Result;
272             $Result = $Result->single_or_default( @_ );
273 0 0   0 1 0 }
  0         0  
274 0 0       0  
275 0         0 die if $Final; ++$Final;
276             die unless $Result;
277             $Result = $Result->element_at( @_ );
278             }
279 0 0   0 1 0  
  0         0  
280 0 0       0 die if $Final; ++$Final;
281 0         0 die unless $Result;
282             $Result = $Result->element_at_or_default( @_ );
283             }
284              
285 0 0   0 1 0 die if $Final; ++$Final;
  0         0  
286 0 0       0 die unless $Result;
287 0         0 $Result = $Result->any( @_ );
288             }
289              
290             die if $Final; ++$Final;
291 0 0   0 1 0 die unless $Result;
  0         0  
292 0 0       0 $Result = $Result->all( @_ );
293 0         0 }
294              
295             die if $Final; ++$Final;
296             die unless $Result;
297 0 0   0 1 0 $Result = $Result->contains( @_ );
  0         0  
298 0 0       0 }
299 0         0  
300             die if $Final; ++$Final;
301             die unless $Result;
302             $Result = $Result->count();
303 0 0   0 1 0 }
  0         0  
304 0 0       0  
305 0         0 die if $Final; $Final = 'LIST';
306             die unless $Result;
307             $Result = [ $Result->to_list() ];
308             }
309 0 0   0 1 0  
  0         0  
310 0 0       0 die if $Final; ++$Final;
311 0         0 die unless $Result;
312             $Result = $Result->to_array();
313             }
314              
315 0 0   0 1 0 die if $Final; ++$Final;
  0         0  
316 0 0       0 die unless $Result;
317 0         0 $Result = $Result->to_dictionary( @_ );
318             }
319              
320             die if $Final; ++$Final;
321 0 0   0 1 0 die unless $Result;
  0         0  
322 0 0       0 $Result = $Result->to_iterator();
323 0         0 }
324              
325             die if $Final;
326             die unless $Result;
327 0 0   0 1 0 $Result->foreach( @_ );
  0         0  
328 0 0       0 }
329 0         0  
330             die if $Final;
331             die unless $Result;
332             my $other = LINQ::LINQ( shift );
333 0 0   0 1 0 $Result = $Result->union( $other, @_ );
  0         0  
334 0 0       0 }
335 0         0  
336             die if $Final;
337             die unless $Result;
338             my $other = LINQ::LINQ( shift );
339 0 0   0 1 0 $Result = $Result->intersect( $other, @_ );
  0         0  
340 0 0       0 }
341 0         0  
342             die if $Final;
343             die unless $Result;
344             my $other = LINQ::LINQ( shift );
345 0 0   0 1 0 $Result = $Result->except( $other, @_ );
  0         0  
346 0 0       0 }
347 0         0  
348             die if $Final; ++$Final;
349             die unless $Result;
350             my $other = LINQ::LINQ( shift );
351 1 50   1 1 9 $Result = $Result->sequence_equal( $other, @_ );
  1         3  
352 1 50       3 }
353 1         4  
354             require Object::Adhoc;
355             return sub {
356             my %smushed = map %$_, grep defined, reverse @_;
357 0 0   0 1 0 Object::Adhoc::object(\%smushed);
  0         0  
358 0 0       0 }
359 0         0 }
360              
361             1;
362              
363 0 0   0 1 0  
  0         0  
364 0 0       0 =pod
365 0         0  
366             =encoding utf-8
367              
368             =head1 NAME
369 0 0   0 1 0  
  0         0  
370 0 0       0 LINQ::DSL - alternative syntax for LINQ
371 0         0  
372             =head2 ABSTRACT
373              
374             use LINQ::DSL ':default_safe';
375 0 0   0 1 0
376 0 0       0 my @people = (
377 0         0 { name => "Alice", dept => 8 },
378             { name => "Bob", dept => 7, this_will => 'be ignored' },
379             { name => "Carol", dept => 7 },
380             { name => "Dave", dept => 8 },
381 0 0   0 1 0 { name => "Eve", dept => 1 },
382 0 0       0 );
383 0         0
384 0         0 my @depts = (
385             { dept_name => 'Accounts', id => 1 },
386             { dept_name => 'IT', id => 7 },
387             { dept_name => 'Marketing', id => 8 },
388 0 0   0 1 0 );
389 0 0       0
390 0         0 my $collection = Linq {
391 0         0 From \@people;
392             SelectX 'name', 'dept';
393             LeftJoin \@depts, field('dept'), field('id'), HashSmush;
394             OrderBy -string, field('name');
395 0 0   0 1 0 Cast AutoObject;
396 0 0       0 };
397 0         0
398 0         0 $collection->foreach( sub {
399             printf "%s from %s\n", $_->name, $_->dept_name;
400             } );
401              
402 0 0   0 1 0 =head1 DESCRIPTION
  0         0  
403 0 0       0  
404 0         0 This module allows you to create and manipulate L<LINQ::Collection>
405 0         0 objects using functions instead of chained method calls. The result
406             is a fairly SQL-like syntax.
407              
408             C<< Linq {...} >> returns a L<LINQ::Collection> unless the block
409 1     1 1 456 includes an aggregating keyword (which must be the final statement
410             in the block). An aggregating keyword will cause C<Linq> to return
411 5     5   31 the result of that keyword, which is usually a scalar, except in
412 5         14 the case of the keyword C<ToList>.
413              
414 1         7556 =head2 C<< :essential >>
415              
416             These can be imported using C<< use LINQ::DSL ':essential' >>.
417              
418             =over
419              
420             =item C<< Linq { BLOCK } >>
421              
422             =item C<< From \@array >>
423              
424             =item C<< From sub { ITERATOR } >>
425              
426             =item C<< From range => [ $start, $end ] >>
427              
428             =item C<< From repeat => [ $value, $count ] >>
429              
430             =item C<< Where { CONDITION } >>
431              
432             =item C<< Select { EXPRESSION } >>
433              
434             =item C<< Join $collection, $hints, $leftexpr, $rightexpr, $joinexpr >>
435              
436             =item C<< GroupBy { EXPRESSION } >>
437              
438             =item C<< ForEach { BLOCK } >>
439              
440             =item C<< DefaultIfEmpty $value >>
441              
442             =back
443              
444             Additionally, C<:essential> includes C<< HashSmush( $href1, $href2 ) >>.
445             This combines multiple hashrefs into a single hashref and then converts
446             that to a blessed object using L<Object::Adhoc>. If the hashrefs contain
447             overlapping keys, the first one "wins".
448              
449             =begin Pod::Coverage
450              
451             =item C<HashSmush>
452              
453             =end Pod::Coverage
454              
455             =head2 C<< :join >>
456              
457             These can be imported using C<< use LINQ::DSL ':join' >>.
458              
459             =over
460              
461             =item C<< LeftJoin $collection, $leftexpr, $rightexpr, $joinexpr >>
462              
463             =item C<< RightJoin $collection, $leftexpr, $rightexpr, $joinexpr >>
464              
465             =item C<< InnerJoin $collection, $leftexpr, $rightexpr, $joinexpr >>
466              
467             =item C<< OuterJoin $collection, $leftexpr, $rightexpr, $joinexpr >>
468              
469             =item C<< GroupJoin $collection, $hints, $leftexpr, $rightexpr, $joinexpr >>
470              
471             =back
472              
473             =head2 C<< :sort >>
474              
475             These can be imported using C<< use LINQ::DSL ':sort' >>.
476              
477             =over
478              
479             =item C<< OrderBy $hints, sub { EXPRESSION } >>
480              
481             =item C<< OrderByDescending $hints, sub { EXPRESSION } >>
482              
483             =item C<< Reverse >>
484              
485             =back
486              
487             =head2 C<< :filter >>
488              
489             These can be imported using C<< use LINQ::DSL ':filter' >>.
490              
491             =over
492              
493             =item C<< Distinct { EXPRESSION } >>
494              
495             =item C<< Take $count >>
496              
497             =item C<< TakeWhile { EXPRESSION } >>
498              
499             =item C<< Skip $count >>
500              
501             =item C<< SkipWhile { EXPRESSION } >>
502              
503             =back
504              
505             =head2 C<< :native >>
506              
507             These can be imported using C<< use LINQ::DSL ':native' >>.
508             These keywords are aggregating keywords!
509              
510             =over
511              
512             =item C<< ToList >>
513              
514             =item C<< ToArray >>
515              
516             =item C<< ToDictionary { KEY EXPRESSION } >>
517              
518             =item C<< ToIterator >>
519              
520             =back
521              
522             =head2 C<< :aggregate >>
523              
524             These can be imported using C<< use LINQ::DSL ':aggregate' >>.
525             These keywords are aggregating keywords!
526              
527             =over
528              
529             =item C<< Min { EXPRESSION } >>
530              
531             =item C<< Max { EXPRESSION } >>
532              
533             =item C<< Sum { EXPRESSION } >>
534              
535             =item C<< Average { EXPRESSION } >>
536              
537             =item C<< Aggregate { EXPRESSION } >>
538              
539             =item C<< Any { TRUTH EXPRESSION } >>
540              
541             =item C<< All { TRUTH EXPRESSION } >>
542              
543             =item C<< Contains $item, sub { COMPARATOR } >>
544              
545             =item C<< Count >>
546              
547             =item C<< SequenceEqual $other_collection >>
548              
549             =back
550              
551             C<Any> and C<All> are very generic-sounding keywords, and C<Any> conflicts
552             with the function of the same name from L<Types::Standard>, so
553             C<< use LINQ::DSL ':aggregate_safe' >> can be used to avoid importing
554             those functions.
555              
556             =head2 C<< :field >>
557              
558             These can be imported using C<< use LINQ::DSL ':field' >>.
559              
560             =over
561              
562             =item C<< SelectX @fields >>
563              
564             =item C<< WhereX @checks >>
565              
566             =item C<< field $name >>
567              
568             =item C<< fields @fields >>
569              
570             =item C<< check_fields @checks >>
571              
572             =back
573              
574             See L<LINQ::Util>.
575             C<SelectX> combines C<Select> and C<fields>.
576             C<WhereX> combines C<Where> and C<check_fields>.
577              
578             =head2 C<< :type >>
579              
580             These can be imported using C<< use LINQ::DSL ':type' >>.
581              
582             =over
583              
584             =item C<< Cast $type >>
585              
586             =item C<< Cast AutoObject >>
587              
588             =item C<< OfType $type >>
589              
590             =back
591              
592             =head2 C<< :combine >>
593              
594             These can be imported using C<< use LINQ::DSL ':combine' >>.
595              
596             =over
597              
598             =item C<< Concat $collection >>
599              
600             =item C<< Union $collection, sub { COMPARATOR } >>
601              
602             =item C<< Intersect $collection, sub { COMPARATOR } >>
603              
604             =item C<< Except $collection, sub { COMPARATOR } >>
605              
606             =item C<< Zip $collection, sub { EXPRESSION } >>
607              
608             =back
609              
610             =head2 C<< :get >>
611              
612             These can be imported using C<< use LINQ::DSL ':get' >>.
613             These keywords are aggregating keywords!
614              
615             =over
616              
617             =item C<< First { TRUTH EXPRESSION } >>
618              
619             =item C<< FirstOrDefault { TRUTH EXPRESSION } $default >>
620              
621             =item C<< Last { TRUTH EXPRESSION } >>
622              
623             =item C<< LastOrDefault { TRUTH EXPRESSION } $default >>
624              
625             =item C<< Single { TRUTH EXPRESSION } >>
626              
627             =item C<< SingleOrDefault { TRUTH EXPRESSION } $default >>
628              
629             =item C<< ElementAt $index >>
630              
631             =item C<< ElementAtOrDefault $index, $default >>
632              
633             =back
634              
635             =head1 BUGS
636              
637             Please report any bugs to
638             L<http://rt.cpan.org/Dist/Display.html?Queue=LINQ>.
639              
640             =head1 SEE ALSO
641              
642             L<LINQ::Collection>, L<LINQ::Utils>.
643              
644             =head1 AUTHOR
645              
646             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
647              
648             =head1 COPYRIGHT AND LICENCE
649              
650             This software is copyright (c) 2022 by Toby Inkster.
651              
652             This is free software; you can redistribute it and/or modify it under
653             the same terms as the Perl 5 programming language system itself.
654              
655             =head1 DISCLAIMER OF WARRANTIES
656              
657             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
658             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
659             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.