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