Commit | Line | Data |
d6512b50 |
1 | package DOM::Tiny::Collection; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | use Carp 'croak'; |
d6512b50 |
6 | use List::Util; |
7 | use Scalar::Util 'blessed'; |
8 | |
927f1351 |
9 | our $VERSION = '0.001'; |
10 | |
8152d579 |
11 | sub new { |
12 | my $class = shift; |
13 | return bless [@_], ref $class || $class; |
14 | } |
15 | |
16 | sub TO_JSON { [@{shift()}] } |
17 | |
d6512b50 |
18 | sub compact { |
19 | my $self = shift; |
20 | return $self->new(grep { defined && (ref || length) } @$self); |
21 | } |
22 | |
23 | sub each { |
24 | my ($self, $cb) = @_; |
25 | return @$self unless $cb; |
26 | my $i = 1; |
27 | $_->$cb($i++) for @$self; |
28 | return $self; |
29 | } |
30 | |
31 | sub first { |
32 | my ($self, $cb) = (shift, shift); |
33 | return $self->[0] unless $cb; |
34 | return List::Util::first { $_ =~ $cb } @$self if ref $cb eq 'Regexp'; |
35 | return List::Util::first { $_->$cb(@_) } @$self; |
36 | } |
37 | |
38 | sub flatten { $_[0]->new(_flatten(@{$_[0]})) } |
39 | |
40 | sub grep { |
41 | my ($self, $cb) = (shift, shift); |
42 | return $self->new(grep { $_ =~ $cb } @$self) if ref $cb eq 'Regexp'; |
43 | return $self->new(grep { $_->$cb(@_) } @$self); |
44 | } |
45 | |
46 | sub join { |
91880340 |
47 | join $_[1] // '', map {"$_"} @{$_[0]}; |
d6512b50 |
48 | } |
49 | |
50 | sub last { shift->[-1] } |
51 | |
52 | sub map { |
53 | my ($self, $cb) = (shift, shift); |
54 | return $self->new(map { $_->$cb(@_) } @$self); |
55 | } |
56 | |
d6512b50 |
57 | sub reduce { |
58 | my $self = shift; |
59 | @_ = (@_, @$self); |
60 | goto &List::Util::reduce; |
61 | } |
62 | |
63 | sub reverse { $_[0]->new(reverse @{$_[0]}) } |
64 | |
65 | sub shuffle { $_[0]->new(List::Util::shuffle @{$_[0]}) } |
66 | |
67 | sub size { scalar @{$_[0]} } |
68 | |
69 | sub slice { |
70 | my $self = shift; |
71 | return $self->new(@$self[@_]); |
72 | } |
73 | |
74 | sub sort { |
75 | my ($self, $cb) = @_; |
76 | |
77 | return $self->new(sort @$self) unless $cb; |
78 | |
79 | my $caller = caller; |
80 | no strict 'refs'; |
81 | my @sorted = sort { |
82 | local (*{"${caller}::a"}, *{"${caller}::b"}) = (\$a, \$b); |
83 | $a->$cb($b); |
84 | } @$self; |
85 | return $self->new(@sorted); |
86 | } |
87 | |
88 | sub tap { |
89 | my ($self, $cb) = (shift, shift); |
90 | $_->$cb(@_) for $self; |
91 | return $self; |
92 | } |
93 | |
94 | sub to_array { [@{shift()}] } |
95 | |
96 | sub uniq { |
97 | my ($self, $cb) = (shift, shift); |
98 | my %seen; |
99 | return $self->new(grep { !$seen{$_->$cb(@_)}++ } @$self) if $cb; |
100 | return $self->new(grep { !$seen{$_}++ } @$self); |
101 | } |
102 | |
103 | sub _flatten { |
104 | map { _ref($_) ? _flatten(@$_) : $_ } @_; |
105 | } |
106 | |
107 | sub _ref { ref $_[0] eq 'ARRAY' || blessed $_[0] && $_[0]->isa(__PACKAGE__) } |
108 | |
109 | 1; |
110 | |
111 | =encoding utf8 |
112 | |
113 | =head1 NAME |
114 | |
115 | DOM::Tiny::Collection - Collection |
116 | |
117 | =head1 SYNOPSIS |
118 | |
8152d579 |
119 | use DOM::Tiny::Collection; |
d6512b50 |
120 | |
121 | # Manipulate collection |
8152d579 |
122 | my $collection = DOM::Tiny::Collection->new(qw(just works)); |
d6512b50 |
123 | unshift @$collection, 'it'; |
124 | say $collection->join("\n"); |
125 | |
126 | # Chain methods |
127 | $collection->map(sub { ucfirst })->shuffle->each(sub { |
128 | my ($word, $num) = @_; |
129 | say "$num: $word"; |
130 | }); |
131 | |
d6512b50 |
132 | =head1 DESCRIPTION |
133 | |
8152d579 |
134 | L<DOM::Tiny::Collection> is an array-based container for collections of |
c67ce516 |
135 | L<DOM::Tiny> nodes or other items based on L<Mojo::Collection>. |
d6512b50 |
136 | |
137 | # Access array directly to manipulate collection |
8152d579 |
138 | my $collection = DOM::Tiny::Collection->new(1 .. 25); |
d6512b50 |
139 | $collection->[23] += 100; |
140 | say for @$collection; |
141 | |
d6512b50 |
142 | =head1 METHODS |
143 | |
8152d579 |
144 | L<DOM::Tiny::Collection> implements the following methods. |
145 | |
146 | =head2 new |
147 | |
148 | my $collection = DOM::Tiny::Collection->new(1, 2, 3); |
149 | |
150 | Construct a new array-based L<DOM::Tiny::Collection> object. |
d6512b50 |
151 | |
152 | =head2 TO_JSON |
153 | |
154 | my $array = $collection->TO_JSON; |
155 | |
156 | Alias for L</"to_array">. |
157 | |
158 | =head2 compact |
159 | |
160 | my $new = $collection->compact; |
161 | |
162 | Create a new collection with all elements that are defined and not an empty |
163 | string. |
164 | |
165 | # "0, 1, 2, 3" |
8152d579 |
166 | DOM::Tiny::Collection->new(0, 1, undef, 2, '', 3)->compact->join(', '); |
d6512b50 |
167 | |
168 | =head2 each |
169 | |
170 | my @elements = $collection->each; |
171 | $collection = $collection->each(sub {...}); |
172 | |
173 | Evaluate callback for each element in collection or return all elements as a |
174 | list if none has been provided. The element will be the first argument passed |
175 | to the callback and is also available as C<$_>. |
176 | |
177 | # Make a numbered list |
178 | $collection->each(sub { |
179 | my ($e, $num) = @_; |
180 | say "$num: $e"; |
181 | }); |
182 | |
183 | =head2 first |
184 | |
185 | my $first = $collection->first; |
186 | my $first = $collection->first(qr/foo/); |
187 | my $first = $collection->first(sub {...}); |
188 | my $first = $collection->first($method); |
189 | my $first = $collection->first($method, @args); |
190 | |
191 | Evaluate regular expression/callback for, or call method on, each element in |
192 | collection and return the first one that matched the regular expression, or for |
193 | which the callback/method returned true. The element will be the first argument |
194 | passed to the callback and is also available as C<$_>. |
195 | |
196 | # Longer version |
197 | my $first = $collection->first(sub { $_->$method(@args) }); |
198 | |
8152d579 |
199 | # Find first value that contains the word "dom" |
200 | my $interesting = $collection->first(qr/dom/i); |
d6512b50 |
201 | |
202 | # Find first value that is greater than 5 |
203 | my $greater = $collection->first(sub { $_ > 5 }); |
204 | |
205 | =head2 flatten |
206 | |
207 | my $new = $collection->flatten; |
208 | |
209 | Flatten nested collections/arrays recursively and create a new collection with |
210 | all elements. |
211 | |
212 | # "1, 2, 3, 4, 5, 6, 7" |
8152d579 |
213 | DOM::Tiny::Collection->new(1, [2, [3, 4], 5, [6]], 7)->flatten->join(', '); |
d6512b50 |
214 | |
215 | =head2 grep |
216 | |
217 | my $new = $collection->grep(qr/foo/); |
218 | my $new = $collection->grep(sub {...}); |
219 | my $new = $collection->grep($method); |
220 | my $new = $collection->grep($method, @args); |
221 | |
222 | Evaluate regular expression/callback for, or call method on, each element in |
223 | collection and create a new collection with all elements that matched the |
224 | regular expression, or for which the callback/method returned true. The element |
225 | will be the first argument passed to the callback and is also available as |
226 | C<$_>. |
227 | |
228 | # Longer version |
229 | my $new = $collection->grep(sub { $_->$method(@args) }); |
230 | |
8152d579 |
231 | # Find all values that contain the word "dom" |
232 | my $interesting = $collection->grep(qr/dom/i); |
d6512b50 |
233 | |
234 | # Find all values that are greater than 5 |
235 | my $greater = $collection->grep(sub { $_ > 5 }); |
236 | |
237 | =head2 join |
238 | |
239 | my $stream = $collection->join; |
240 | my $stream = $collection->join("\n"); |
241 | |
242 | Turn collection into string. |
243 | |
244 | # Join all values with commas |
245 | $collection->join(', ')->say; |
246 | |
247 | =head2 last |
248 | |
249 | my $last = $collection->last; |
250 | |
251 | Return the last element in collection. |
252 | |
253 | =head2 map |
254 | |
255 | my $new = $collection->map(sub {...}); |
256 | my $new = $collection->map($method); |
257 | my $new = $collection->map($method, @args); |
258 | |
259 | Evaluate callback for, or call method on, each element in collection and create |
260 | a new collection from the results. The element will be the first argument |
261 | passed to the callback and is also available as C<$_>. |
262 | |
263 | # Longer version |
264 | my $new = $collection->map(sub { $_->$method(@args) }); |
265 | |
8152d579 |
266 | # Append the word "dom" to all values |
267 | my $domified = $collection->map(sub { $_ . 'dom' }); |
d6512b50 |
268 | |
269 | =head2 reduce |
270 | |
271 | my $result = $collection->reduce(sub {...}); |
272 | my $result = $collection->reduce(sub {...}, $initial); |
273 | |
274 | Reduce elements in collection with callback, the first element will be used as |
275 | initial value if none has been provided. |
276 | |
277 | # Calculate the sum of all values |
278 | my $sum = $collection->reduce(sub { $a + $b }); |
279 | |
280 | # Count how often each value occurs in collection |
281 | my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {}); |
282 | |
283 | =head2 reverse |
284 | |
285 | my $new = $collection->reverse; |
286 | |
287 | Create a new collection with all elements in reverse order. |
288 | |
289 | =head2 slice |
290 | |
291 | my $new = $collection->slice(4 .. 7); |
292 | |
293 | Create a new collection with all selected elements. |
294 | |
295 | # "B C E" |
8152d579 |
296 | DOM::Tiny::Collection->new('A', 'B', 'C', 'D', 'E')->slice(1, 2, 4)->join(' '); |
d6512b50 |
297 | |
298 | =head2 shuffle |
299 | |
300 | my $new = $collection->shuffle; |
301 | |
302 | Create a new collection with all elements in random order. |
303 | |
304 | =head2 size |
305 | |
306 | my $size = $collection->size; |
307 | |
308 | Number of elements in collection. |
309 | |
310 | =head2 sort |
311 | |
312 | my $new = $collection->sort; |
313 | my $new = $collection->sort(sub {...}); |
314 | |
315 | Sort elements based on return value of callback and create a new collection |
316 | from the results. |
317 | |
318 | # Sort values case-insensitive |
319 | my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) }); |
320 | |
321 | =head2 tap |
322 | |
323 | $collection = $collection->tap(sub {...}); |
324 | |
e99ef07d |
325 | Equivalent to L<Mojo::Base/"tap">. |
d6512b50 |
326 | |
327 | =head2 to_array |
328 | |
329 | my $array = $collection->to_array; |
330 | |
331 | Turn collection into array reference. |
332 | |
333 | =head2 uniq |
334 | |
335 | my $new = $collection->uniq; |
336 | my $new = $collection->uniq(sub {...}); |
337 | my $new = $collection->uniq($method); |
338 | my $new = $collection->uniq($method, @args); |
339 | |
340 | Create a new collection without duplicate elements, using the string |
341 | representation of either the elements or the return value of the |
342 | callback/method. |
343 | |
344 | # Longer version |
345 | my $new = $collection->uniq(sub { $_->$method(@args) }); |
346 | |
347 | # "foo bar baz" |
8152d579 |
348 | DOM::Tiny::Collection->new('foo', 'bar', 'bar', 'baz')->uniq->join(' '); |
d6512b50 |
349 | |
350 | # "[[1, 2], [2, 1]]" |
8152d579 |
351 | DOM::Tiny::Collection->new([1, 2], [2, 1], [3, 2])->uniq(sub{ $_->[1] })->to_array; |
d6512b50 |
352 | |
353 | =head1 BUGS |
354 | |
355 | Report any issues on the public bugtracker. |
356 | |
357 | =head1 AUTHOR |
358 | |
359 | Dan Book <dbook@cpan.org> |
360 | |
361 | =head1 COPYRIGHT AND LICENSE |
362 | |
363 | This software is Copyright (c) 2015 by Dan Book. |
364 | |
365 | This is free software, licensed under: |
366 | |
367 | The Artistic License 2.0 (GPL Compatible) |
368 | |
369 | =head1 SEE ALSO |
370 | |
371 | L<Mojo::Collection> |