File Coverage

blib/lib/Hash/MultiValue.pm
Criterion Covered Total %
statement 168 178 94.3
branch 24 26 92.3
condition 4 6 66.6
subroutine 29 31 93.5
pod 17 23 73.9
total 242 264 91.6


line stmt bran cond sub pod time code
1             package Hash::MultiValue;
2              
3 8     8   128333 use strict;
  8         14  
  8         306  
4 8     8   28 no warnings 'void';
  8         8  
  8         184  
5 8     8   150 use 5.006_002;
  8         21  
  8         330  
6             our $VERSION = '0.16';
7              
8 8     8   33 use Carp ();
  8         9  
  8         153  
9 8     8   26 use Scalar::Util qw(refaddr);
  8         8  
  8         857  
10              
11             # there does not seem to be a relevant RT or perldelta entry for this
12 8     8   41 use constant _SPLICE_SAME_ARRAY_SEGFAULT => $] < '5.008007';
  8         14  
  8         1508  
13              
14             my %keys;
15             my %values;
16             my %registry;
17              
18             BEGIN {
19 8     8   38 require Config;
20 8   33     122 my $needs_registry = ($^O eq 'Win32' || $Config::Config{useithreads});
21 8 50       25 if ($needs_registry) {
22             *CLONE = sub {
23 0         0 foreach my $oldaddr (keys %registry) {
24 0         0 my $this = refaddr $registry{$oldaddr};
25 0         0 $keys{$this} = delete $keys{$oldaddr};
26 0         0 $values{$this} = delete $values{$oldaddr};
27 0         0 Scalar::Util::weaken($registry{$this} = delete $registry{$oldaddr});
28             }
29 0         0 };
30             }
31 8         10680 *NEEDS_REGISTRY = sub () { $needs_registry };
  0         0  
32             }
33              
34             if (defined &UNIVERSAL::ref::import) {
35             UNIVERSAL::ref->import;
36             }
37              
38 0     0 0 0 sub ref { 'HASH' }
39              
40             sub create {
41 7     7 0 11 my $class = shift;
42 7         17 my $self = bless {}, $class;
43 7         24 my $this = refaddr $self;
44 7         22 $keys{$this} = [];
45 7         13 $values{$this} = [];
46 7         9 Scalar::Util::weaken($registry{$this} = $self) if NEEDS_REGISTRY;
47 7         14 $self;
48             }
49              
50             sub new {
51 6     6 1 65 my $class = shift;
52 6         23 my $self = $class->create;
53 6         14 unshift @_, $self;
54 6         8 &{ $self->can('merge_flat') };
  6         70  
55             }
56              
57             sub from_mixed {
58 1     1 1 13 my $class = shift;
59 1         4 my $self = $class->create;
60 1         2 unshift @_, $self;
61 1         1 &{ $self->can('merge_mixed') };
  1         9  
62             }
63              
64             sub DESTROY {
65 9     9   3325 my $this = refaddr shift;
66 9         29 delete $keys{$this};
67 9         468 delete $values{$this};
68 9         142 delete $registry{$this} if NEEDS_REGISTRY;
69             }
70              
71             sub get {
72 0     0 1 0 my($self, $key) = @_;
73 0         0 $self->{$key};
74             }
75              
76             sub get_all {
77 4     4 1 1940 my($self, $key) = @_;
78 4         12 my $this = refaddr $self;
79 4         6 my $k = $keys{$this};
80 4         31 (@{$values{$this}}[grep { $key eq $k->[$_] } 0 .. $#$k]);
  4         13  
  16         25  
81             }
82              
83             sub get_one {
84 2     2 1 3 my ($self, $key) = @_;
85 2         4 my @v = $self->get_all($key);
86 2 100       8 return $v[0] if @v == 1;
87 1 50       3 Carp::croak "Key not found: $key" if not @v;
88 1         142 Carp::croak "Multiple values match: $key";
89             }
90              
91             sub set {
92 6     6 1 12 my $self = shift;
93 6         9 my $key = shift;
94              
95 6         12 my $this = refaddr $self;
96 6         10 my $k = $keys{$this};
97 6         5 my $v = $values{$this};
98              
99 6         16 my @idx = grep { $key eq $k->[$_] } 0 .. $#$k;
  29         40  
100              
101 6         9 my $added = @_ - @idx;
102 6 100       22 if ($added > 0) {
    100          
103 1         29 my $start = $#$k + 1;
104 1         4 push @$k, ($key) x $added;
105 1         2 push @idx, $start .. $#$k;
106             }
107             elsif ($added < 0) {
108 4         11 my ($start, @drop, @keep) = splice @idx, $added;
109 4         12 for my $i ($start+1 .. $#$k) {
110 10 100 100     34 if (@drop and $i == $drop[0]) {
111 4         5 shift @drop;
112 4         5 next;
113             }
114 6         8 push @keep, $i;
115             }
116              
117             splice @$_, $start, 0+@$_, ( _SPLICE_SAME_ARRAY_SEGFAULT
118             ? @{[ @$_[@keep] ]} # force different source array
119             : @$_[@keep]
120 4         24 ) for $k, $v;
121             }
122              
123 6 100       20 if (@_) {
124 3         7 @$v[@idx] = @_;
125 3         6 $self->{$key} = $_[-1];
126             }
127             else {
128 3         7 delete $self->{$key};
129             }
130              
131 6         9 $self;
132             }
133              
134             sub add {
135 4     4 1 1747 my $self = shift;
136 4         6 my $key = shift;
137 4         11 $self->merge_mixed( $key => \@_ );
138 4         7 $self;
139             }
140              
141             sub merge_flat {
142 6     6 0 10 my $self = shift;
143 6         13 my $this = refaddr $self;
144 6         12 my $k = $keys{$this};
145 6         10 my $v = $values{$this};
146 6 100       23 push @{ $_ & 1 ? $v : $k }, $_[$_] for 0 .. $#_;
  46         84  
147 6         17 @{$self}{@$k} = @$v;
  6         33  
148 6         23 $self;
149             }
150              
151             sub merge_mixed {
152 5     5 0 9 my $self = shift;
153 5         11 my $this = refaddr $self;
154 5         6 my $k = $keys{$this};
155 5         8 my $v = $values{$this};
156              
157 5         4 my $hash;
158 5 100       13 $hash = shift if @_ == 1;
159              
160 5 100       44 while ( my ($key, $value) = @_ ? splice @_, 0, 2 : each %$hash ) {
161 7 100       19 if ( CORE::ref($value) eq 'ARRAY' ) {
162 5 100       14 next if not @$value;
163 4         447 push @$k, ($key) x @$value;
164 4         8 push @$v, @$value;
165             }
166             else {
167 2         3 push @$k, $key;
168 2         4 push @$v, $value;
169             }
170 6         29 $self->{$key} = $v->[-1];
171             }
172              
173 5         10 $self;
174             }
175              
176             sub remove {
177 2     2 1 7 my ($self, $key) = @_;
178 2         7 $self->set($key);
179 2         3 $self;
180             }
181              
182             sub clear {
183 1     1 1 2 my $self = shift;
184 1         2 %$self = ();
185 1         3 my $this = refaddr $self;
186 1         3 $keys{$this} = [];
187 1         2 $values{$this} = [];
188 1         2 $self;
189             }
190              
191             sub clone {
192 1     1 1 840 my $self = shift;
193 1         5 CORE::ref($self)->new($self->flatten);
194             }
195              
196             sub keys {
197 3     3 1 1386 my $self = shift;
198 3         3 return @{$keys{refaddr $self}};
  3         38  
199             }
200              
201             sub values {
202 1     1 1 2 my $self = shift;
203 1         1 return @{$values{refaddr $self}};
  1         9  
204             }
205              
206             sub flatten {
207 11     11 1 30 my $self = shift;
208 11         19 my $this = refaddr $self;
209 11         15 my $k = $keys{$this};
210 11         10 my $v = $values{$this};
211 11         22 map { $k->[$_], $v->[$_] } 0 .. $#$k;
  44         587  
212             }
213              
214             sub each {
215 2     2 1 2901 my ($self, $code) = @_;
216 2         386 my $this = refaddr $self;
217 2         6 my $k = $keys{$this};
218 2         2 my $v = $values{$this};
219 2         5 for (0 .. $#$k) {
220 8         24 $code->($k->[$_], $v->[$_]);
221             }
222 2         8 return $self;
223             }
224              
225             sub as_hashref {
226 1     1 1 6 my $self = shift;
227 1         4 my %hash = %$self;
228 1         3 \%hash;
229             }
230              
231             sub as_hashref_mixed {
232 1     1 1 2546 my $self = shift;
233 1         5 my $this = refaddr $self;
234 1         2 my $k = $keys{$this};
235 1         2 my $v = $values{$this};
236              
237 1         1 my %hash;
238 1         4 push @{$hash{$k->[$_]}}, $v->[$_] for 0 .. $#$k;
  4         23  
239 1         4 for (CORE::values %hash) {
240 3 100       8 $_ = $_->[0] if 1 == @$_;
241             }
242              
243 1         3 \%hash;
244             }
245              
246             *mixed = \&as_hashref_mixed;
247              
248             sub as_hashref_multi {
249 1     1 1 1912 my $self = shift;
250 1         3 my $this = refaddr $self;
251 1         2 my $k = $keys{$this};
252 1         1 my $v = $values{$this};
253              
254 1         3 my %hash;
255 1         3 push @{$hash{$k->[$_]}}, $v->[$_] for 0 .. $#$k;
  4         9  
256              
257 1         3 \%hash;
258             }
259              
260             *multi = \&as_hashref_multi;
261              
262             sub STORABLE_freeze {
263 2     2 0 39 my $self = shift;
264 2         6 my $this = refaddr $self;
265 2         104 return '', $keys{$this}, $values{$this};
266             }
267              
268             sub STORABLE_thaw {
269 2     2 0 25 my $self = shift;
270 2         2 my ($is_cloning, $serialised, $k, $v) = @_;
271 2         4 my $this = refaddr $self;
272 2         4 $keys {$this} = $k;
273 2         2 $values{$this} = $v;
274 2         3 @{$self}{@$k} = @$v;
  2         6  
275 2         9 return $self;
276             }
277              
278             1;
279             __END__