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