File Coverage

lib/Array/AsHash.pm
Criterion Covered Total %
statement 261 266 98.1
branch 84 94 89.3
condition 16 16 100.0
subroutine 44 44 100.0
pod 31 31 100.0
total 436 451 96.6


line stmt bran cond sub pod time code
1             package Array::AsHash;
2              
3 4     4   97865 use warnings;
  4         10  
  4         173  
4 4     4   29 use strict;
  4         10  
  4         150  
5 4     4   3544 use Clone ();
  4         14128  
  4         125  
6 4     4   49 use Scalar::Util qw(refaddr);
  4         11  
  4         825  
7              
8             our $VERSION = '0.32';
9              
10             my ( $_bool, $_to_string );
11              
12             BEGIN {
13              
14             # these are defined in a BEGIN block because otherwise, overloading
15             # doesn't get them in time.
16             $_bool = sub {
17 39     39   368 my $self = CORE::shift;
18 39         211 return $self->acount;
19 4     4   24 };
20              
21             $_to_string = sub {
22 4     4   26 no warnings 'once';
  4         8  
  4         696  
23 3     3   3058 require Data::Dumper;
24 3         7244 local $Data::Dumper::Indent = 0;
25 3         7 local $Data::Dumper::Terse = 1;
26 3         6 my $self = CORE::shift;
27 3         5 my $string = '';
28 3 50       85 return $string unless $self;
29 3         12 while ( my ( $k, $v ) = $self->each ) {
30              
31 8         17 foreach ( $k, $v ) {
32 16 100       38 $_ = ref $_ ? Data::Dumper::Dumper($_) : $_;
33             }
34 8         163 $string .= "$k\n $v\n";
35             }
36 3         23 return $string;
37 4         114 };
38             }
39              
40 4     4   7308 use overload bool => $_bool, '""' => $_to_string, fallback => 1;
  4         4724  
  4         27  
41              
42             my $_actual_key = sub {
43             my ( $self, $key ) = @_;
44             if ( ref $key ) {
45             my $new_key = $self->{curr_key_of}{ refaddr $key};
46             return refaddr $key unless defined $new_key;
47             $key = $new_key;
48             }
49             return $key;
50             };
51              
52             # private because it doesn't match expectations. The "index" of a
53             # non-existent key is one greater than the current list
54             my $_index = sub {
55             my ( $self, $key ) = @_;
56             my $index =
57             $self->exists($key)
58             ? $self->{index_of}{$key}
59             : scalar @{ $self->{array_for} }; # automatically one greater
60             return $index;
61             };
62              
63             my $_croak = sub {
64             my ( $proto, $message ) = @_;
65             require Carp;
66             Carp::croak($message);
67             };
68              
69             my $_validate_kv_pairs = sub {
70             my ( $self, $arg_for ) = @_;
71             my $sub = $arg_for->{sub} || ( caller(1) )[3];
72              
73             if ( @{ $arg_for->{pairs} } % 2 ) {
74             $self->$_croak("Arguments to $sub must be an even-sized list");
75             }
76             };
77              
78             sub new {
79 35     35 1 17179 my $class = shift;
80 35         120 return $class->_initialize(@_);
81             }
82              
83             sub _initialize {
84 35     35   58 my ( $class, $arg_ref ) = @_;
85 35         95 my $self = bless {} => $class;
86 35         223 $self->{array_for} = [];
87 35 100       123 return $self unless $arg_ref;
88 25   100     78 my $array = $arg_ref->{array} || [];
89 25         62 $self->{is_strict} = $arg_ref->{strict};
90 25 100       217 $array = Clone::clone($array) if $arg_ref->{clone};
91              
92 25 100       86 unless ( 'ARRAY' eq ref $array ) {
93 1         5 $class->$_croak('Argument to new() must be an array reference');
94             }
95 24 100       83 if ( @$array % 2 ) {
96 1         4 $class->$_croak('Uneven number of keys in array');
97             }
98              
99 23         44 $self->{array_for} = $array;
100 23         86 foreach ( my $i = 0; $i < @$array; $i += 2 ) {
101 56         79 my $key = $array->[$i];
102 56         156 $self->{index_of}{$key} = $i;
103 56 100       192 if ( ref $key ) {
104 4         964 my $old_address = refaddr $arg_ref->{array}[$i];
105 4         11 my $curr_key = "$key";
106 4         29 $self->{curr_key_of}{$old_address} = $curr_key;
107             }
108             }
109 23         81 return $self;
110             }
111              
112             sub get {
113 83     83 1 879 my ( $self, @keys ) = @_;
114 83         90 my @get;
115 83         136 foreach my $key (@keys) {
116 86         185 $key = $self->$_actual_key($key);
117 86 50       186 next unless defined $key;
118 86         178 my $exists = $self->exists($key);
119 86 100 100     264 if ( $self->{is_strict} && !$exists ) {
120 1         4 $self->$_croak("Cannot get non-existent key ($key)");
121             }
122 85 100       182 if ($exists) {
    100          
123 72         163 CORE::push @get, $self->{array_for}[ $self->$_index($key) + 1 ];
124             }
125             elsif ( @keys > 1 ) {
126 1         3 CORE::push @get, undef;
127             }
128             else {
129 12         63 return;
130             }
131             }
132             return wantarray ? @get
133 70 100       419 : @keys > 1 ? \@get
    100          
134             : $get[0];
135             }
136              
137             my $_insert = sub {
138             my ( $self, $key, $label, $index ) = splice @_, 0, 4;
139              
140             $self->$_validate_kv_pairs(
141             { pairs => \@_, sub => "Array::AsHash::insert_$label" } );
142             $key = $self->$_actual_key($key);
143              
144             unless ( $self->exists($key) ) {
145             $self->$_croak("Cannot insert $label non-existent key ($key)");
146             }
147             foreach ( my $i = 0; $i < @_; $i += 2 ) {
148             my $new_key = $_[$i];
149             if ( $self->exists($new_key) ) {
150             $self->$_croak("Cannot insert duplicate key ($new_key)");
151             }
152             $self->{index_of}{$new_key} = $index + $i;
153             }
154              
155             my @tail = splice @{ $self->{array_for} }, $index;
156             push @{ $self->{array_for} }, @_, @tail;
157             my %seen = @_;
158             foreach my $curr_key ( CORE::keys %{ $self->{index_of} } ) {
159             if ( $self->{index_of}{$curr_key} >= $index
160             && !exists $seen{$curr_key} )
161             {
162             $self->{index_of}{$curr_key} += @_;
163             }
164             }
165             return $self;
166             };
167              
168             sub strict {
169 4     4 1 1004 my $self = shift;
170 4 100       15 return $self->{is_strict} unless @_;
171 2         3 $self->{is_strict} = !!shift;
172 2         6 return $self;
173             }
174              
175             sub clone {
176 1     1 1 343 my $self = CORE::shift;
177 1         5 return ( ref $self )->new(
178             { array => scalar $self->get_array,
179             clone => 1,
180             }
181             );
182             }
183              
184             sub unshift {
185 3     3 1 537 my ( $self, @kv_pairs ) = @_;
186 3         14 $self->$_validate_kv_pairs( { pairs => \@kv_pairs } );
187 3         7 foreach my $curr_key ( CORE::keys %{ $self->{index_of} } ) {
  3         13  
188 2         8 $self->{index_of}{$curr_key} += @kv_pairs;
189             }
190 3         15 for ( my $i = 0; $i < @kv_pairs; $i += 2 ) {
191 5         12 my ( $key, $value ) = @kv_pairs[ $i, $i + 1 ];
192 5 50       19 if ( $self->exists($key) ) {
193 0         0 $self->$_croak("Cannot unshift an existing key ($key)");
194             }
195 5         21 $self->{index_of}{$key} = $i;
196             }
197 3         6 unshift @{ $self->{array_for} }, @kv_pairs;
  3         19  
198             }
199              
200             sub push {
201 6     6 1 562 my ( $self, @kv_pairs ) = @_;
202 6         31 $self->$_validate_kv_pairs( { pairs => \@kv_pairs } );
203 6         23 my @array = $self->get_array;
204 6         23 for ( my $i = 0; $i < @kv_pairs; $i += 2 ) {
205 13         28 my ( $key, $value ) = @kv_pairs[ $i, $i + 1 ];
206 13 50       32 if ( $self->exists($key) ) {
207 0         0 $self->$_croak("Cannot push an existing key ($key)");
208             }
209 13         67 $self->{index_of}{$key} = @array + $i;
210             }
211 6         7 CORE::push @{ $self->{array_for} }, @kv_pairs;
  6         34  
212             }
213              
214             sub pop {
215 3     3 1 597 my $self = shift;
216 3 100       11 return unless $self;
217 2         4 my ( $key, $value ) = splice @{ $self->{array_for} }, -2;
  2         7  
218 2         5 delete $self->{index_of}{$key};
219 2 100       11 return wantarray ? ( $key, $value ) : [ $key, $value ];
220             }
221              
222             sub shift {
223 3     3 1 535 my $self = CORE::shift;
224 3 100       8 return unless $self;
225 2         4 foreach my $curr_key ( CORE::keys %{ $self->{index_of} } ) {
  2         8  
226 3         8 $self->{index_of}{$curr_key} -= 2;
227             }
228 2         5 my ( $key, $value ) = splice @{ $self->{array_for} }, 0, 2;
  2         6  
229 2         6 delete $self->{index_of}{$key};
230 2 100       10 return wantarray ? ( $key, $value ) : [ $key, $value ];
231             }
232              
233             sub hcount {
234 4     4 1 11 my $self = CORE::shift;
235 4         10 my $count = $self->acount;
236 4         21 return $count / 2;
237             }
238              
239             sub acount {
240 60     60 1 889 my $self = CORE::shift;
241 60         137 my @array = $self->get_array;
242 60         283 return scalar @array;
243             }
244              
245             sub hindex {
246 2     2 1 4 my $self = CORE::shift;
247 2         5 my $index = $self->aindex(CORE::shift);
248 2 100       13 return defined $index ? $index / 2 : ();
249             }
250              
251             sub aindex {
252 7     7 1 14 my $self = CORE::shift;
253 7         17 my $key = $self->$_actual_key(CORE::shift);
254 7 100       35 return unless $self->exists($key);
255 5         14 return $self->$_index($key);
256             }
257              
258             sub keys {
259 21     21 1 705 my $self = CORE::shift;
260 21         55 my @array = $self->get_array;
261 21         36 my @keys;
262 21         69 for ( my $i = 0; $i < @array; $i += 2 ) {
263 50         148 CORE::push @keys, $array[$i];
264             }
265 21 100       151 return wantarray ? @keys : \@keys;
266             }
267              
268             sub values {
269 19     19 1 2497 my $self = CORE::shift;
270 19         47 my @array = $self->get_array;
271 19         33 my @values;
272 19         68 for ( my $i = 1; $i < @array; $i += 2 ) {
273 44         227 CORE::push @values, $array[$i];
274             }
275 19 100       128 return wantarray ? @values : \@values;
276             }
277              
278             sub first {
279 11     11 1 365 my $self = CORE::shift;
280 11         19 my $index = $self->{current_index_for};
281 11   100     113 return defined $index && 2 == $index;
282             }
283              
284             sub last {
285 11     11 1 20 my $self = CORE::shift;
286 11         18 my $index = $self->{current_index_for};
287 11   100     57 return defined $index && $self->acount == $index;
288             }
289              
290             sub each {
291 27     27 1 6335 my $self = CORE::shift;
292              
293             my $each = sub {
294 33   100 33   129 my $index = $self->{current_index_for} || 0;
295 33         84 my @array = $self->get_array;
296 33 100       95 if ( $index >= @array ) {
297 6         23 $self->reset_each;
298 6         40 return;
299             }
300 27         73 my ( $key, $value ) = @array[ $index, $index + 1 ];
301 4     4   8810 no warnings 'uninitialized';
  4         10  
  4         553  
302 27         49 $self->{current_index_for} += 2;
303 27         180 return ( $key, $value );
304 27         116 };
305              
306 27 100       57 if (wantarray) {
307 24         45 return $each->();
308             }
309             else {
310 3         1054 require Array::AsHash::Iterator;
311 3         31 return Array::AsHash::Iterator->new(
312             { parent => $self,
313             iterator => $each,
314             }
315             );
316             }
317             }
318             {
319 4     4   21 no warnings 'once';
  4         8  
  4         6141  
320             *kv = \&each;
321             }
322              
323 9     9 1 1356 sub reset_each { CORE::shift->{current_index_for} = undef }
324              
325             sub insert_before {
326 4     4 1 1974 my $self = CORE::shift;
327 4         8 my $key = CORE::shift;
328 4         11 my $index = $self->$_index($key);
329 4         13 $self->$_insert( $key, 'before', $index, @_ );
330             }
331              
332             sub insert_after {
333 6     6 1 2358 my $self = CORE::shift;
334 6         10 my $key = CORE::shift;
335 6         15 my $index = $self->$_index($key) + 2;
336 6         23 $self->$_insert( $key, 'after', $index, @_ );
337             }
338              
339             sub key_at {
340 7     7 1 469 my $self = CORE::shift;
341 7         9 my @keys;
342 7         16 foreach my $index ( my @copy = @_ ) { # prevent aliasing
343 9         12 $index *= 2;
344 9         26 CORE::push @keys => $self->{array_for}[$index];
345             }
346             return wantarray ? @keys
347 7 50       46 : 1 == @_ ? $keys[0]
    100          
348             : \@keys;
349             }
350              
351             sub value_at {
352 8     8 1 19 my $self = CORE::shift;
353 8         9 my @values;
354 8         19 foreach my $index ( my @copy = @_ ) { # prevent aliasing
355 12         17 $index = $index * 2 + 1;
356 12         30 CORE::push @values => $self->{array_for}[$index];
357             }
358             return wantarray ? @values
359 8 50       49 : 1 == @_ ? $values[0]
    100          
360             : \@values;
361             }
362              
363             sub delete {
364 14     14 1 1112 my $self = CORE::shift;
365 14         22 my $num_args = @_;
366 14         35 my $key = $self->$_actual_key(CORE::shift);
367 14         20 my @value;
368              
369 14 100       32 if ( $self->exists($key) ) {
    100          
370 11         25 my $index = $self->$_index($key);
371 11         29 delete $self->{index_of}{$key};
372 11         16 my ( undef, $value ) = splice @{ $self->{array_for} }, $index, 2;
  11         39  
373 11         23 CORE::push @value, $value;
374 11         15 foreach my $curr_key ( CORE::keys %{ $self->{index_of} } ) {
  11         35  
375 17 100       52 if ( $self->{index_of}{$curr_key} >= $index ) {
376 12         35 $self->{index_of}{$curr_key} -= 2;
377             }
378             }
379             }
380             elsif ( $self->{is_strict} ) {
381 1         4 $self->$_croak("Cannot delete non-existent key ($key)");
382             }
383 13 100       39 if (@_) {
384 3         12 CORE::push @value, $self->delete(@_);
385             }
386             return wantarray ? @value
387 13 100       88 : $num_args > 1 ? \@value
    100          
388             : $value[0];
389             }
390              
391             sub clear {
392 1     1 1 6 my $self = CORE::shift;
393 1         3 for my $spec (qw) {
394 3         10 $self->{$spec} = undef;
395             }
396 1         3 @{ $self->{array_for} } = ();
  1         3  
397 1         5 return $self;
398             }
399              
400             sub exists {
401 339     339 1 3672 my ( $self, $key ) = @_;
402 339         516 $key = $self->$_actual_key($key);
403 339 50       689 return unless defined $key;
404              
405 339         1297 return exists $self->{index_of}{$key};
406             }
407              
408             sub rename {
409 3     3 1 772 my ( $self, @pairs ) = @_;
410 3         13 $self->$_validate_kv_pairs( { pairs => \@pairs } );
411              
412 2         11 foreach ( my $i = 0; $i < @pairs; $i += 2 ) {
413 3         7 my ( $old, $new ) = @pairs[ $i, $i + 1 ];
414 3 50       9 unless ( $self->exists($old) ) {
415 0         0 $self->$_croak("Cannot rename non-existent key ($old)");
416             }
417 3 50       9 unless ( defined $new ) {
418 0         0 $self->$_croak("Cannot rename ($old) to an undefined value");
419             }
420 3 50       7 if ( $self->exists($new) ) {
421 0         0 $self->$_croak(
422             "Cannot rename ($old) to an key which already exists ($new)"
423             );
424             }
425 3         8 my $index = delete $self->{index_of}{$old};
426 3         14 $self->{index_of}{$new} = $index;
427 3         13 $self->{array_for}[$index] = $new;
428             }
429 2         10 return $self;
430             }
431              
432             sub get_pairs {
433 11     11 1 5873 my ( $self, @keys ) = @_;
434              
435 11         14 my @pairs;
436 11         20 foreach my $key (@keys) {
437 20 100       38 if ( $self->exists($key) ) {
    100          
438 17         35 CORE::push @pairs, $key, $self->get($key);
439             }
440             elsif ( $self->{is_strict} ) {
441 1         5 $self->$_croak("Cannot get pair for non-existent key ($key)");
442             }
443             }
444 10 100       46 return wantarray ? @pairs : \@pairs;
445             }
446              
447             sub default {
448 3     3 1 330 my ( $self, @pairs ) = @_;
449 3         12 $self->$_validate_kv_pairs( { pairs => \@pairs } );
450              
451 3         14 for ( my $i = 0; $i < @pairs; $i += 2 ) {
452 5         8 my ( $k, $v ) = @pairs[ $i, $i + 1 ];
453 5 100       11 next if $self->exists($k);
454 4         11 $self->put( $k, $v );
455             }
456 3         8 return $self;
457             }
458              
459             sub add {
460 2     2 1 825 my ( $self, @pairs ) = @_;
461 2         9 $self->$_validate_kv_pairs( { pairs => \@pairs } );
462              
463 2         7 for ( my $i = 0; $i < @pairs; $i += 2 ) {
464 2         4 my ( $key, $value ) = @pairs[ $i, $i + 1 ];
465 2         5 $key = $self->$_actual_key($key);
466 2 100       3 if ( $self->exists($key) ) {
467 1         5 $self->$_croak("Cannot add existing key ($key)");
468             }
469 1         3 my $index = $self->$_index($key);
470 1         3 $self->{index_of}{$key} = $index;
471 1         2 $self->{array_for}[$index] = $key;
472 1         4 $self->{array_for}[ $index + 1 ] = $value;
473             }
474 1         3 return $self;
475             }
476              
477             sub put {
478 18     18 1 2828 my ( $self, @pairs ) = @_;
479 18         75 $self->$_validate_kv_pairs( { pairs => \@pairs } );
480              
481 18         69 for ( my $i = 0; $i < @pairs; $i += 2 ) {
482 20         53 my ( $key, $value ) = @pairs[ $i, $i + 1 ];
483 20         42 $key = $self->$_actual_key($key);
484 20 100 100     67 if ( !$self->exists($key) && $self->{is_strict} ) {
485 2         6 $self->$_croak("Cannot put a non-existent key ($key)");
486             }
487 18         47 my $index = $self->$_index($key);
488 18         42 $self->{index_of}{$key} = $index;
489 18         37 $self->{array_for}[$index] = $key;
490 18         71 $self->{array_for}[ $index + 1 ] = $value;
491             }
492 16         59 return $self;
493             }
494              
495             sub get_array {
496 150     150 1 194 my $self = CORE::shift;
497             return wantarray
498 150 100       329 ? @{ $self->{array_for} }
  143         637  
499             : $self->{array_for};
500             }
501              
502             1;
503             __END__