Coverage Summary

# File Coverage

lib/List/Objects/WithUtils.pm
Criterion | Covered | Total | %
--- | --- | --- | ---
statement | 66 | 67 | 98.5
branch | 21 | 24 | 87.5
condition | 10 | 12 | 83.3
subroutine | 11 | 11 | 100.0
total | 108 | 114 | 94.7

line | stmt | bran | cond | sub | code
--- | --- | --- | --- | --- | ---
1 |  |  |  |  | package List::Objects::WithUtils;
2 | 101 |  |  | 101 | use Carp;
| 101 |  |  |  |
| 101 |  |  |  |
3 | 101 |  |  | 101 | use strictures 1;
| 101 |  |  |  |
| 101 |  |  |  |
4 |  |  |  |  |
5 |  |  |  |  | our %ImportMap = (
6 |  |  |  |  | array => 'Array',
7 |  |  |  |  | immarray => 'Array::Immutable',
8 |  |  |  |  | array_of => 'Array::Typed',
9 |  |  |  |  | immarray_of => 'Array::Immutable::Typed',
10 |  |  |  |  | hash => 'Hash',
11 |  |  |  |  | immhash => 'Hash::Immutable',
12 |  |  |  |  | hash_of => 'Hash::Typed',
13 |  |  |  |  | immhash_of => 'Hash::Immutable::Typed',
14 |  |  |  |  | );
15 |  |  |  |  |
16 |  |  |  |  | our @DefaultImport = keys %ImportMap;
17 |  |  |  |  |
18 |  |  |  |  | sub import {
19 | 109 |  |  | 109 | my ($class, @funcs) = @_;
20 |  |  |  |  |
21 | 109 |  |  |  | my $pkg;
22 | 109 | 100 |  |  | if (ref $funcs[0]) {
23 | 16 | 100 |  |  | croak 'Expected a list of imports or a HASH but got '.$funcs[0]
24 |  |  |  |  | unless ref $funcs[0] eq 'HASH';
25 | 15 |  |  |  | my %opts = %{ $funcs[0] };
| 15 |  |  |  |
26 | 15 | 50 |  |  | @funcs = @{ $opts{import} || [ 'all' ] };
| 15 |  |  |  |
27 | 15 |  | 66 |  | $pkg = $opts{to} || caller;
28 |  |  |  |  | }
29 |  |  |  |  |
30 | 108 | 100 |  |  | @funcs = @DefaultImport unless @funcs;
31 | 108 |  |  |  | my %fmap = map {;
32 | 177 | 100 |  |  | lc( substr($_, 0, 1) eq ':' ? substr($_, 1) : $_ ) => 1
33 |  |  |  |  | } @funcs;
34 |  |  |  |  |
35 | 103 | 100 | 100 |  | if (defined $fmap{all} || defined $fmap{-all}) {
|  | 100 | 66 |  |
36 | 8 |  |  |  | @funcs = (
37 |  |  |  |  | @DefaultImport,
38 |  |  |  |  | 'autobox'
39 |  |  |  |  | )
40 |  |  |  |  | } elsif (defined $fmap{functions} || defined $fmap{funcs}) {
41 |  |  |  |  | # Legacy import tag, tested but not documented
42 | 8 |  |  |  | @funcs = @DefaultImport
43 |  |  |  |  | }
44 |  |  |  |  |
45 | 101 |  |  |  | my @mods;
46 | 101 |  |  |  | for my $function (@funcs) {
47 | 185 | 100 | 100 |  | if ($function eq 'autobox' || $function eq '-autobox') {
48 | 11 |  |  |  | require List::Objects::WithUtils::Autobox;
49 | 11 |  |  |  | List::Objects::WithUtils::Autobox::import($class);
50 |  |  |  |  | next
51 | 11 |  |  |  | }
52 | 177 | 100 |  |  | if (my $thismod = $ImportMap{$function}) {
53 | 176 |  |  |  | push @mods, 'List::Objects::WithUtils::'.$thismod;
54 |  |  |  |  | next
55 | 176 |  |  |  | }
56 | 4 |  |  |  | croak "Unknown import parameter '$function'"
57 |  |  |  |  | }
58 |  |  |  |  |
59 | 98 | 100 |  |  | $pkg = caller unless defined $pkg;
60 | 98 |  |  |  | my @failed;
61 | 95 |  |  |  | for my $mod (@mods) {
62 | 172 |  |  |  | my $c = "package $pkg; use $mod;";
63 | 172 |  |  | 97 | local $@; eval $c;
| 172 |  |  | 25 |
| 87 |  |  | 14 |
| 85 |  |  | 9 |
| 88 |  |  | 7 |
| 16 |  |  | 7 |
| 39 |  |  | 5 |
| 15 |  |  | 5 |
| 4 |  |  |  |
| 4 |  |  |  |
| 26 |  |  |  |
| 26 |  |  |  |
| 26 |  |  |  |
| 2 |  |  |  |
| 5 |  |  |  |
| 5 |  |  |  |
| 5 |  |  |  |
| 26 |  |  |  |
| 26 |  |  |  |
| 26 |  |  |  |
| 26 |  |  |  |
| 2 |  |  |  |
| 2 |  |  |  |
| 5 |  |  |  |
| 2 |  |  |  |
| 5 |  |  |  |
64 | 172 | 50 |  |  | if ($@) { carp $@; push @failed, $mod }
| 0 |  |  |  |
| 3 |  |  |  |
65 |  |  |  |  | }
66 |  |  |  |  |
67 | 98 | 50 |  |  | if (@failed) {
68 | 10 |  |  |  | croak 'Failed to import ' . join ', ', @failed
69 |  |  |  |  | }
70 |  |  |  |  |
71 |  |  |  |  | 1
72 | 98 |  |  |  | }
73 |  |  |  |  |
74 |  |  |  |  | print
75 |  |  |  |  | qq[ * rjbs is patching PAUSE.\n<rjbs> (to reject anything from peregrin)\n]
76 |  |  |  |  | unless caller;
77 |  |  |  |  |
78 |  |  |  |  | 1;
79 |  |  |  |  |
80 |  |  |  |  | =pod
81 |  |  |  |  |
82 |  |  |  |  | =for Pod::Coverage import
83 |  |  |  |  |
84 |  |  |  |  | =head1 NAME
85 |  |  |  |  |
86 |  |  |  |  | List::Objects::WithUtils - List objects, kitchen sink included
87 |  |  |  |  |
88 |  |  |  |  | =head1 SYNOPSIS
89 |  |  |  |  |
90 |  |  |  |  | ## A small sample; consult the description, below, for links to
91 |  |  |  |  | ## extended documentation
92 |  |  |  |  |
93 |  |  |  |  | # Import all object constructor functions:
94 |  |  |  |  | # array immarray array_of immarray_of
95 |  |  |  |  | # hash immhash hash_of immhash_of
96 |  |  |  |  | use List::Objects::WithUtils;
97 |  |  |  |  |
98 |  |  |  |  | # Import all of the above plus autoboxing:
99 |  |  |  |  | use List::Objects::WithUtils ':all';
100 |  |  |  |  | # Same, but via the 'Lowu' shortcut:
101 |  |  |  |  | use Lowu;
102 |  |  |  |  |
103 |  |  |  |  | # Most methods returning lists return new objects; chaining is easy:
104 |  |  |  |  | array(qw/ aa Ab bb Bc bc /)
105 |  |  |  |  | ->grep(sub { /^b/i })
106 |  |  |  |  | ->map(sub { uc })
107 |  |  |  |  | ->uniq
108 |  |  |  |  | ->all; # ( 'BB', 'BC' )
109 |  |  |  |  |
110 |  |  |  |  | # Useful utilities from other list modules are available:
111 |  |  |  |  | my $wanted = array(
112 |  |  |  |  | +{ id => '400', user => 'bob' },
113 |  |  |  |  | +{ id => '600', user => 'suzy' },
114 |  |  |  |  | +{ id => '700', user => 'fred' },
115 |  |  |  |  | )->first(sub { $_->{id} > 500 });
116 |  |  |  |  |
117 |  |  |  |  | my $itr = array( 1 .. 7 )->natatime(3);
118 |  |  |  |  | while ( my @nextset = $itr->() ) {
119 |  |  |  |  | ...
120 |  |  |  |  | }
121 |  |  |  |  |
122 |  |  |  |  | my $meshed = array(qw/ a b c d /)
123 |  |  |  |  | ->mesh( array(1 .. 4) )
124 |  |  |  |  | ->all; # ( 'a', 1, 'b', 2, 'c', 3, 'd', 4 )
125 |  |  |  |  |
126 |  |  |  |  | my ($evens, $odds) = array( 1 .. 20 )
127 |  |  |  |  | ->part(sub { $_[0] & 1 })
128 |  |  |  |  | ->all;
129 |  |  |  |  |
130 |  |  |  |  | my $sorted = array(
131 |  |  |  |  | +{ name => 'bob', acct => 1 },
132 |  |  |  |  | +{ name => 'fred', acct => 2 },
133 |  |  |  |  | +{ name => 'suzy', acct => 3 },
134 |  |  |  |  | )->sort_by(sub { $_->{name} });
135 |  |  |  |  |
136 |  |  |  |  | # array() objects are mutable:
137 |  |  |  |  | my $mutable = array(qw/ foo bar baz /);
138 |  |  |  |  | $mutable->insert(1, 'quux');
139 |  |  |  |  | $mutable->delete(2);
140 |  |  |  |  |
141 |  |  |  |  | # ... or use immarray() immutable arrays:
142 |  |  |  |  | my $static = immarray( qw/ foo bar baz / );
143 |  |  |  |  | $static->set(0, 'quux'); # dies
144 |  |  |  |  | $static->[0] = 'quux'; # dies
145 |  |  |  |  | push @$static, 'quux'; # dies
146 |  |  |  |  |
147 |  |  |  |  | # Construct a hash:
148 |  |  |  |  | my $hash = hash( foo => 'bar', snacks => 'cake' );
149 |  |  |  |  |
150 |  |  |  |  | # You can set multiple keys in one call:
151 |  |  |  |  | $hash->set( foobar => 'baz', pie => 'cherry' );
152 |  |  |  |  |
153 |  |  |  |  | # ... which is useful for merging in another (plain) hash:
154 |  |  |  |  | my %foo = ( pie => 'pumpkin', snacks => 'cheese' );
155 |  |  |  |  | $hash->set( %foo );
156 |  |  |  |  |
157 |  |  |  |  | # ... or another hash object:
158 |  |  |  |  | my $second = hash( pie => 'key lime' );
159 |  |  |  |  | $hash->set( $second->export );
160 |  |  |  |  |
161 |  |  |  |  | # Retrieve one value as a simple scalar:
162 |  |  |  |  | my $snacks = $hash->get('snacks');
163 |  |  |  |  |
164 |  |  |  |  | # ... or retrieve multiple values as an array-type object:
165 |  |  |  |  | my $vals = $hash->get('foo', 'foobar');
166 |  |  |  |  |
167 |  |  |  |  | # Take a hash slice of keys, return a new hash object
168 |  |  |  |  | # consisting of the retrieved key/value pairs:
169 |  |  |  |  | my $slice = $hash->sliced('foo', 'pie');
170 |  |  |  |  |
171 |  |  |  |  | # Hashes can be inflated to objects:
172 |  |  |  |  | my $obj = $hash->inflate;
173 |  |  |  |  | $snacks = $obj->snacks;
174 |  |  |  |  |
175 |  |  |  |  | # Chained method examples; methods that return multiple values
176 |  |  |  |  | # typically return new array-type objects:
177 |  |  |  |  | my @match_keys = $hash->keys->grep(sub { m/foo/ })->all;
178 |  |  |  |  | my @match_vals = $hash->values->grep(sub { m/bar/ })->all;
179 |  |  |  |  |
180 |  |  |  |  | my @sorted_pairs = hash( foo => 2, bar => 3, baz => 1)
181 |  |  |  |  | ->kv
182 |  |  |  |  | ->sort_by(sub { $_->[1] })
183 |  |  |  |  | ->all; # ( [ baz => 1 ], [ foo => 2 ], [ bar => 3 ] )
184 |  |  |  |  |
185 |  |  |  |  | # Perl6-inspired Junctions:
186 |  |  |  |  | if ( $hash->keys->any_items eq 'snacks' ) {
187 |  |  |  |  | ...
188 |  |  |  |  | }
189 |  |  |  |  | if ( $hash->values->all_items > 10 ) {
190 |  |  |  |  | ...
191 |  |  |  |  | }
192 |  |  |  |  |
193 |  |  |  |  | # Type-checking arrays via Type::Tiny:
194 |  |  |  |  | use List::Objects::WithUtils 'array_of';
195 |  |  |  |  | use Types::Standard -all;
196 |  |  |  |  | my $int_arr = array_of Int() => 1 .. 10;
197 |  |  |  |  |
198 |  |  |  |  | # Type-checking hashes:
199 |  |  |  |  | use List::Objects::WithUtils 'hash_of';
200 |  |  |  |  | use Types::Standard -all;
201 |  |  |  |  | my $int_hash = hash_of Int() => (foo => 1, bar => 2);
202 |  |  |  |  |
203 |  |  |  |  | # Native list types can be autoboxed:
204 |  |  |  |  | use List::Objects::WithUtils 'autobox';
205 |  |  |  |  | my $foo = [ qw/foo baz bar foo quux/ ]->uniq->sort;
206 |  |  |  |  | my $bar = +{ a => 1, b => 2, c => 3 }->values->sort;
207 |  |  |  |  |
208 |  |  |  |  | # Autoboxing is lexically scoped like normal:
209 |  |  |  |  | { no List::Objects::WithUtils::Autobox;
210 |  |  |  |  | [ 1 .. 10 ]->shuffle; # dies
211 |  |  |  |  | }
212 |  |  |  |  |
213 |  |  |  |  |
214 |  |  |  |  | =head1 DESCRIPTION
215 |  |  |  |  |
216 |  |  |  |  | A set of roles and classes defining an object-oriented interface to Perl
217 |  |  |  |  | hashes and arrays with useful utility methods, junctions, type-checking
218 |  |  |  |  | ability, and optional autoboxing.
219 |  |  |  |  |
220 |  |  |  |  | Originally derived from L<Data::Perl>.
221 |  |  |  |  |
222 |  |  |  |  | =head2 Arrays
223 |  |  |  |  |
224 |  |  |  |  | B<array> (L<List::Objects::WithUtils::Array>) provides basic mutable
225 |  |  |  |  | ARRAY-type objects. Behavior is defined by
226 |  |  |  |  | L<List::Objects::WithUtils::Role::Array>; look there for documentation on
227 |  |  |  |  | available methods.
228 |  |  |  |  |
229 |  |  |  |  | B<immarray> is imported from L<List::Objects::WithUtils::Array::Immutable> and
230 |  |  |  |  | operates much like an B<array>, except methods that mutate the list are not
231 |  |  |  |  | available; using immutable arrays promotes safer functional patterns.
232 |  |  |  |  |
233 |  |  |  |  | B<array_of> provides L<Type::Tiny>-compatible type-checking array objects
234 |  |  |  |  | that can coerce and check their values as they are added; see
235 |  |  |  |  | L<List::Objects::WithUtils::Array::Typed>.
236 |  |  |  |  |
237 |  |  |  |  | B<immarray_of> provides immutable type-checking arrays; see
238 |  |  |  |  | L<List::Objects::WithUtils::Array::Immutable::Typed>.
239 |  |  |  |  |
240 |  |  |  |  | =head2 Hashes
241 |  |  |  |  |
242 |  |  |  |  | B<hash> is the basic mutable HASH-type object imported from
243 |  |  |  |  | L<List::Objects::WithUtils::Hash>; see
244 |  |  |  |  | L<List::Objects::WithUtils::Role::Hash> for documentation.
245 |  |  |  |  |
246 |  |  |  |  | B<immhash> provides immutable (restricted) hashes; see
247 |  |  |  |  | L<List::Objects::WithUtils::Hash::Immutable>.
248 |  |  |  |  |
249 |  |  |  |  | B<hash_of> provides L<Type::Tiny>-compatible type-checking hash
250 |  |  |  |  | objects; see L<List::Objects::WithUtils::Hash::Typed>.
251 |  |  |  |  |
252 |  |  |  |  | B<immhash_of> provides immutable type-checking hashes; see
253 |  |  |  |  | L<List::Objects::WithUtils::Hash::Immutable::Typed>.
254 |  |  |  |  |
255 |  |  |  |  | =head2 Importing
256 |  |  |  |  |
257 |  |  |  |  | A bare import list (C<use List::Objects::WithUtils;>) will import all of the
258 |  |  |  |  | object constructor functions described above; they can also be selectively
259 |  |  |  |  | imported, e.g.:
260 |  |  |  |  |
261 |  |  |  |  | use List::Objects::WithUtils 'array_of', 'hash_of';
262 |  |  |  |  |
263 |  |  |  |  | Importing B<autobox> lexically enables L<List::Objects::WithUtils::Autobox>,
264 |  |  |  |  | providing methods for native ARRAY and HASH types.
265 |  |  |  |  |
266 |  |  |  |  | Importing B<all> or B<:all> will import all of the object constructors and
267 |  |  |  |  | additionally turn B<autobox> on; C<use Lowu;> is a shortcut for importing
268 |  |  |  |  | B<all>.
269 |  |  |  |  |
270 |  |  |  |  | =head2 Subclassing
271 |  |  |  |  |
272 |  |  |  |  | The importer for this package is somewhat flexible; a subclass can override
273 |  |  |  |  | import to pass import tags and a target package by feeding this package's
274 |  |  |  |  | C<import()> a HASH:
275 |  |  |  |  |
276 |  |  |  |  | # Subclass and import to target packages (see Lowu.pm f.ex):
277 |  |  |  |  | package My::Defaults;
278 |  |  |  |  | use parent 'List::Objects::WithUtils';
279 |  |  |  |  | sub import {
280 |  |  |  |  | my ($class, @params) = @_;
281 |  |  |  |  | $class->SUPER::import(
282 |  |  |  |  | +{
283 |  |  |  |  | import => [ 'autobox', 'array', 'hash' ],
284 |  |  |  |  | to => scalar(caller)
285 |  |  |  |  | }
286 |  |  |  |  | )
287 |  |  |  |  | }
288 |  |  |  |  |
289 |  |  |  |  | Functionality is mostly defined by Roles.
290 |  |  |  |  | For example, it's easy to create your own array class with new methods:
291 |  |  |  |  |
292 |  |  |  |  | package My::Array::Object;
293 |  |  |  |  | use Role::Tiny::With;
294 |  |  |  |  | with 'List::Objects::WithUtils::Role::Array',
295 |  |  |  |  | 'List::Objects::WithUtils::Role::Array::WithJunctions';
296 |  |  |  |  |
297 |  |  |  |  | # An easy way to add your own functional interface:
298 |  |  |  |  | use Exporter 'import'; our @EXPORT = 'my_array';
299 |  |  |  |  | sub my_array { __PACKAGE__->new(@_) }
300 |  |  |  |  |
301 |  |  |  |  | # ... add/override methods ...
302 |  |  |  |  |
303 |  |  |  |  | ... in which case you may want to also define your own hash subclass that
304 |  |  |  |  | overrides C<array_type> to produce your preferred arrays:
305 |  |  |  |  |
306 |  |  |  |  | package My::Hash::Object;
307 |  |  |  |  | use Role::Tiny::With;
308 |  |  |  |  | with 'List::Objects::WithUtils::Role::Hash';
309 |  |  |  |  |
310 |  |  |  |  | use Exporter 'import'; our @EXPORT = 'my_hash';
311 |  |  |  |  | sub my_hash { __PACKAGE__->new(@_) }
312 |  |  |  |  |
313 |  |  |  |  | sub array_type { 'My::Array::Object' }
314 |  |  |  |  |
315 |  |  |  |  | # ...
316 |  |  |  |  |
317 |  |  |  |  | =head1 SEE ALSO
318 |  |  |  |  |
319 |  |  |  |  | L<List::Objects::WithUtils::Role::Array> for documentation on the basic set of
320 |  |  |  |  | C<array()> methods.
321 |  |  |  |  |
322 |  |  |  |  | L<List::Objects::WithUtils::Role::Array::WithJunctions> for documentation on C<array()>
323 |  |  |  |  | junction-returning methods.
324 |  |  |  |  |
325 |  |  |  |  | L<List::Objects::WithUtils::Role::Hash> for documentation regarding C<hash()>
326 |  |  |  |  | methods.
327 |  |  |  |  |
328 |  |  |  |  | L<List::Objects::WithUtils::Array::Immutable> for more on C<immarray()>
329 |  |  |  |  | immutable arrays.
330 |  |  |  |  |
331 |  |  |  |  | L<List::Objects::WithUtils::Array::Typed> for more on C<array_of()>
332 |  |  |  |  | type-checking arrays.
333 |  |  |  |  |
334 |  |  |  |  | L<List::Objects::WithUtils::Array::Immutable::Typed> for more on
335 |  |  |  |  | C<immarray_of()> immutable type-checking arrays.
336 |  |  |  |  |
337 |  |  |  |  | L<List::Objects::WithUtils::Hash::Typed> for more on C<hash_of()>
338 |  |  |  |  | type-checking hashes.
339 |  |  |  |  |
340 |  |  |  |  | L<List::Objects::WithUtils::Hash::Immutable::Typed> for more on
341 |  |  |  |  | C<immhash_of()> immutable type-checking hashes.
342 |  |  |  |  |
343 |  |  |  |  | L<List::Objects::WithUtils::Autobox> for details on autoboxing.
344 |  |  |  |  |
345 |  |  |  |  | The L<Lowu> module for a convenient importer shortcut.
346 |  |  |  |  |
347 |  |  |  |  | L<List::Objects::Types> for relevant L<Type::Tiny> types.
348 |  |  |  |  |
349 |  |  |  |  | L<MoopsX::ListObjects> for integration with L<Moops> class-building sugar.
350 |  |  |  |  |
351 |  |  |  |  | =head1 AUTHOR
352 |  |  |  |  |
353 |  |  |  |  | Jon Portnoy <avenj@cobaltirc.org>
354 |  |  |  |  |
355 |  |  |  |  | Licensed under the same terms as Perl.
356 |  |  |  |  |
357 |  |  |  |  | The original Array and Hash roles were derived from L<Data::Perl> by Matthew
358 |  |  |  |  | Phillips (CPAN: MATTP), haarg, and others.
359 |  |  |  |  |
360 |  |  |  |  | Immutable array objects were inspired by L<Const::Fast> by Leon Timmermans
361 |  |  |  |  | (CPAN: LEONT)
362 |  |  |  |  |
363 |  |  |  |  | Junctions are adapted from L<Perl6::Junction> by Carl Franks (CPAN: CFRANKS)
364 |  |  |  |  |
365 |  |  |  |  | Most of the type-checking code and other useful additions were contributed by
366 |  |  |  |  | Toby Inkster (CPAN: TOBYINK)
367 |  |  |  |  |
368 |  |  |  |  | Much of this code simply wraps other widely-used modules, including:
369 |  |  |  |  |
370 |  |  |  |  | L<List::Util>
371 |  |  |  |  |
372 |  |  |  |  | L<List::MoreUtils>
373 |  |  |  |  |
374 |  |  |  |  | L<List::UtilsBy>
375 |  |  |  |  |
376 |  |  |  |  | =cut
377 |  |  |  |  |