File Coverage

blib/lib/Tie/RegexpHash.pm
Criterion Covered Total %
statement 94 117 80.3
branch 23 36 63.8
condition 6 6 100.0
subroutine 16 18 88.8
pod 6 8 75.0
total 145 185 78.3


line stmt bran cond sub pod time code
1             package Tie::RegexpHash;
2              
3             require 5.005;
4 2     2   1788 use strict;
  2         5  
  2         80  
5              
6 2     2   11 use vars qw( $VERSION @ISA );
  2         4  
  2         123  
7              
8             $VERSION = '0.16';
9              
10 2     2   20 use Carp;
  2         3  
  2         136  
11 2     2   1066 use Data::Dumper;
  2         6324  
  2         2108  
12              
13             # This is what stringified qrs seem to look like.
14             # It captures flags in $1 and pattern in $2
15             my $SERIALIZE_RE;
16              
17             if ($] < 5.13.6) {
18             $SERIALIZE_RE = qr/^\(\?([ismx]{0,4})-[ismx]*:(.*)\)$/;
19             }
20             else {
21             $SERIALIZE_RE = qr/^\(\?\^([ismx]{0,4}(?:-[ismx]{1,4})?):(.*)\)$/;
22             }
23              
24             # This is what the serialized version looks like.
25             # It also captures flags in $1 and pattern in $2
26             my $DESERIALIZE_RE = qr/^([ismx]{0,4}):(.*)$/;
27              
28             # Creates a new 'Tie::RegexpHash' object. We use an underlying array rather
29             # than a hash because we want to search through the hash keys in the order
30             # that they were added.
31             #
32             # See the _find() and add() routines for more details.
33             sub new {
34 2     2 1 8900 my ($class) = @_;
35              
36 2         12 my $self = {
37             KEYS => [ ], # array of Regexp keys
38             VALUES => [ ], # array of corresponding values
39             COUNT => 0, # the number of hash/key pairs (is this necessary?)
40             };
41              
42 2         9 bless $self, $class;
43             }
44              
45             # Embed any modifiers used with qr// in the pattern.
46             sub _convert_key {
47 9     9   13 my ($key) = shift;
48              
49 9         148 my ($flags,$pat) = ($key =~ $SERIALIZE_RE);
50 9 100       110 ($key = qr/(?$flags:$pat)/) if $flags;
51 9         21 return $key;
52             }
53            
54             # Sequentially goes through the hash keys for Regexps which match the given
55             # key and returns the index. If the hash is empty, or a matching key was not
56             # found, returns undef.
57             sub _find {
58 19     19   27 my ($self, $key) = @_;
59              
60 19 100       60 unless ($self->{COUNT}) {
61 3         7 return;
62             }
63              
64 16 100       37 if (ref($key) eq 'Regexp') {
65 4         6 my $i = 0;
66 4         9 $key = _convert_key($key);
67 4   100     30 while (($i < $self->{COUNT}) and ($key ne $self->{KEYS}->[ $i ])) {
68 2         10 $i++;
69             }
70              
71 4 100       11 if ($i == $self->{COUNT}) {
72 1         3 return;
73             }
74             else {
75 3         7 return $i;
76             }
77             }
78             else {
79 12         16 my $i = 0;
80 12   100     224 while (($i < $self->{COUNT}) and ($key !~ m/$self->{KEYS}->[ $i ]/)) {
81 3         19 $i++;
82             }
83              
84 12 100       31 if ($i == $self->{COUNT}) {
85 1         3 return;
86             }
87             else {
88 11         24 return $i;
89             }
90             }
91             }
92              
93             # If a key exists the value will be replaced. (If the Regexps are not the same
94             # but match, a warning is displayed.) If the key is new, then a new key/value
95             # pair is added.
96             sub add {
97 6     6 1 1283 my ($self, $key, $value) = @_;
98              
99 6 100       31 ($key = _convert_key($key)) if (ref($key) eq 'Regexp');
100              
101 6         17 my $index = _find $self, $key;
102 6 100       18 if (defined($index)) {
103 1 50       5 if ($key ne $self->{KEYS}->[ $index ]) {
104 0         0 carp "\'$key\' is not the same as \'",
105             $self->{KEYS}->[$index], "\'";
106             }
107 1         5 $self->{VALUES}->[ $index ] = $value;
108             }
109             else {
110 5         9 $index = $self->{COUNT}++;
111              
112 5 100       29 ($key = qr/$key/) unless (ref($key) eq 'Regexp');
113              
114 5         11 $self->{KEYS}->[ $index ] = $key;
115 5         24 $self->{VALUES}->[ $index ] = $value;
116             }
117             }
118              
119              
120             # Does a key exist or does it match any Regexp keys?
121             sub match_exists {
122 0     0 1 0 my ($self, $key) = @_;
123 0         0 return defined( _find $self, $key );
124             }
125              
126             # Returns the value of a key or any matches to Regexp keys.
127             sub match {
128 12     12 1 1404 my ($self, $key) = @_;
129              
130 12         25 my $index = _find $self, $key;
131              
132 12 50       26 if (defined($index)) {
133 12         77 return $self->{VALUES}->[ $index ];
134             }
135             else {
136 0         0 return;
137             }
138             }
139              
140             # Removes a key or Regexp key and associated value from the hash. If the key
141             # is not the same as the Regexp, a warning is displayed.
142             sub remove {
143 0     0 1 0 my ($self, $key) = @_;
144              
145 0 0       0 ($key = _convert_key($key)) if (ref($key) eq 'Regexp');
146              
147 0         0 my $index = _find $self, $key;
148              
149 0 0       0 if (defined($index)) {
150 0 0       0 if ($key ne $self->{KEYS}->[ $index ]) {
151 0         0 carp "'`$key\' is not the same as '`",
152             $self->{KEYS}->[$index], "\'";
153             }
154              
155 0         0 my $value = $self->{VALUES}->[ $index ];
156 0         0 splice @{ $self->{KEYS} }, $index, 1;
  0         0  
157 0         0 splice @{ $self->{VALUES} }, $index, 1;
  0         0  
158 0         0 $self->{COUNT}--;
159 0         0 return $value;
160             }
161             else {
162 0         0 carp "Cannot delete a nonexistent key: \`$key\'";
163 0         0 return;
164             }
165             }
166              
167             # Clears the hash.
168             sub clear {
169 1     1 1 2 my ($self) = @_;
170              
171 1         2 $self->{KEYS} = [ ];
172 1         4 $self->{VALUES} = [ ];
173 1         8 $self->{COUNT} = 0;
174              
175             }
176              
177             BEGIN {
178             # make aliases...
179 2     2   22 no strict;
  2         10  
  2         204  
180 2     2   7 *TIEHASH = \ &new;
181 2         5 *STORE = \ &add;
182 2         4 *EXISTS = \ &match_exists;
183 2         5 *FETCH = \ &match;
184 2         10 *DELETE = \ &remove;
185 2         924 *CLEAR = \ &clear;
186             }
187              
188             # Returns the first key
189             sub FIRSTKEY {
190 1     1   10 my ($self) = @_;
191              
192 1 50       4 unless ($self->{COUNT}) {
193 0         0 return;
194             }
195              
196 1         7 return $self->{KEYS}->[0];
197              
198             }
199              
200             # Returns the next key
201             sub NEXTKEY {
202 1     1   2 my ($self, $lastkey) = @_;
203              
204 1 50       4 unless ($self->{COUNT}) {
205 0         0 return;
206             }
207              
208 1         3 my $index = _find $self, $lastkey;
209              
210 1 50       4 unless (defined($index)) {
211 0         0 confess "Invalid \$lastkey";
212             }
213              
214 1         1 $index++;
215              
216 1 50       4 if ($index == $self->{COUNT}) {
217 1         6 return;
218             }
219             else {
220 0         0 return $self->{KEYS}->[ $index ];
221             }
222             }
223              
224             # serialize object
225             sub STORABLE_freeze {
226 2     2 0 550 my ($self, $cloning) = @_;
227              
228 2         4 my @keystrings;
229              
230             {
231 2         3 local *_;
  2         4  
232 2         5 @keystrings = map { join(':', ($_ =~ $SERIALIZE_RE)); } @{$self->{KEYS}};
  2         22  
  2         5  
233             }
234            
235 2         10 my $sref = {
236             KEYSTRINGS => \@keystrings,
237             VALUES => $self->{VALUES},
238             COUNT => $self->{COUNT},
239             };
240              
241 2         274 return (0,$sref);
242             }
243              
244             # deserialize
245             sub STORABLE_thaw {
246 2     2 0 463 my($self, $cloning, $serialized, $sref) = @_;
247              
248 2         7 $self->{KEYS} = [ ];
249 2         5 $self->{VALUES} = $sref->{VALUES};
250 2         31 $self->{COUNT} = $sref->{COUNT};
251              
252             {
253 2         4 local *_;
  2         5  
254 2         21 @{$self->{KEYS}} = map {
  2         15  
255 2         6 my ($flags,$pat) = ($_ =~ $DESERIALIZE_RE);
256 2 50       7 $pat = ($flags) ? "(?$flags:$pat)" : $pat;
257 2         36 qr/$pat/;
258 2         4 } @{$sref->{KEYSTRINGS}};
259             }
260             }
261              
262             1;
263             __END__