File Coverage

blib/lib/Tie/Array/AsHash.pm
Criterion Covered Total %
statement 72 80 90.0
branch 17 28 60.7
condition 1 3 33.3
subroutine 16 17 94.1
pod n/a
total 106 128 82.8


line stmt bran cond sub pod time code
1             package Tie::Array::AsHash;
2              
3 9     9   218397 use strict;
  9         22  
  9         349  
4 9     9   48 use warnings;
  9         17  
  9         264  
5              
6             # use warnings;
7 9     9   49 use Carp qw/croak/;
  9         21  
  9         652  
8              
9 9     9   8680 use Tie::Hash ();
  9         9156  
  9         199  
10 9     9   8499 use Params::Util qw(_REGEX _STRING);
  9         72289  
  9         889  
11              
12 9     9   91 use base qw(Tie::StdHash);
  9         18  
  9         17065  
13              
14             our $VERSION = '0.200';
15              
16             my $usage = 'usage: tie %hash, \'Tie::Array::AsHash\', array => \@array, '
17             . "split => ':' [, join => '#', 'Tie::File option' => value, ... ]\n";
18              
19             sub TIEHASH
20             {
21 8 50   8   200 croak( $usage ) unless ( scalar(@_) % 2 );
22              
23 8         49 my ( $obj, %opts ) = @_;
24              
25             # set array to use
26 8 50       48 my $array = delete( $opts{array} ) or croak( $usage );
27              
28             # set delimiter and croak if none was supplied
29 8 50       59 my $split = delete( $opts{split} ) or croak( $usage );
30              
31             # set join, an optional argument
32 8         24 my $join = delete( $opts{join} );
33              
34             # if split's value is a regex and join isn't specified, croak
35 8 50 33     92 croak( "Tie::Array::AsHash error: no 'join' option specified and 'split' option is a regular expression\n",
36             $usage )
37             if ( _REGEX($split) and not _STRING($join) );
38              
39             # the rest of the options can feed right into Tie::File
40             # Tie::File can worry about checking the arguments for validity, etc.
41             #tie my @file, 'Tie::File', $filename, %opts or return;
42              
43 8         294 my $self = bless(
44             {
45             split => $split,
46             join => $join,
47             array => $array,
48             splitrx => qr/^(.*?)$split/s,
49             },
50             $obj
51             );
52              
53 8         86 return $self;
54             }
55              
56             sub FETCH
57             {
58 14     14   36 my ( $self, $key ) = @_;
59 14         177 my $fetchrx = qr/^$key$self->{split}(.*)/s;
60              
61             # find the key and get corresponding value
62 14         24 foreach my $line ( @{ $self->{array} } )
  14         33  
63             {
64 51 100       274 return $1 if ( $line =~ $fetchrx );
65             }
66              
67 0         0 return;
68             }
69              
70             sub STORE
71             {
72 1     1   4 my ( $self, $key, $val ) = @_;
73 1         14 my $existsrc = qr/^$key$self->{split}/s;
74              
75             # look for $key in the file and replace value if $key is found
76 1         2 foreach my $line ( @{ $self->{array} } )
  1         4  
77             {
78 6 50       28 if ( $line =~ $existsrc ) # found the key? good. replace the entire line with the correct key, delim, and values
79             {
80              
81             # Marco Poleggi supplied a patch that changed exists
82             # to defined in the next line of code. Thanks Macro!
83 0 0       0 my $rc = $line = $key . ( defined( $self->{join} ) ? $self->{join} : $self->{split} ) . $val;
84 0         0 return $val;
85             }
86             }
87              
88             # if key doesn't exist in the file, append to end of file
89 1 50       3 push( @{ $self->{array} }, $key . ( defined( $self->{join} ) ? $self->{join} : $self->{split} ) . $val );
  1         7  
90              
91 1         6 return $val;
92             }
93              
94             sub DELETE
95             {
96 1     1   3 my ( $self, $key ) = @_;
97 1         15 my $fetchrx = qr/^$key$self->{split}(.*)/s;
98              
99             # first, look for the key in the file
100             # next, delete the line in the file
101             # finally, return the value, which might not contain anything
102             # perl's builtin delete() returns the deleted value, so emulate the behavior
103              
104 1         2 for my $i ( 0 .. $#{ $self->{array} } )
  1         4  
105             {
106 2 100       16 if ( $self->{array}->[$i] =~ $fetchrx )
107             {
108 1         1 splice( @{ $self->{array} }, $i, 1 ); # remove entry from file
  1         4  
109 1         8 return $1;
110             }
111             }
112              
113 0         0 return;
114             }
115              
116 0     0   0 sub CLEAR { @{ $_[0]->{array} } = (); return; }
  0         0  
  0         0  
117              
118             sub EXISTS
119             {
120 1     1   3 my ( $self, $key ) = @_;
121 1         16 my $existsrc = qr/^$key$self->{split}/s;
122              
123 1         2 foreach my $line ( @{ $self->{array} } )
  1         4  
124             {
125 2 100       21 return 1 if ( $line =~ $existsrc );
126             }
127              
128 0         0 return 0;
129             }
130              
131             sub FIRSTKEY
132             {
133 3     3   10 my ($self) = @_;
134              
135             # deal with empty files
136 3 50       19 return unless ( exists( $self->{array}->[0] ) );
137              
138 3         29 my ($val) = $self->{array}->[0] =~ $self->{splitrx};
139              
140             # reset index for NEXTKEY
141 3         13 $self->{index} = 0;
142              
143 3 50       28 return defined($val) ? $val : $self->NEXTKEY();
144             }
145              
146             sub NEXTKEY
147             {
148 18     18   51 my ($self) = @_;
149              
150             # keep track of what line of the file we are on
151             # and the end of the file
152 18 50       28 return if ( $self->{index} >= scalar( @{ $self->{array} } ) );
  18         49  
153              
154 18         22 my $val;
155 18         49 while( !defined( $val ) )
156             {
157 18 100       39 last if ( ++$self->{index} >= scalar( @{ $self->{array} } ) );
  18         880  
158 15         111 ($val) = $self->{array}->[ $self->{index} ] =~ $self->{splitrx};
159             }
160              
161 18         69 return $val;
162             }
163              
164             sub SCALAR
165             {
166 3     3   9 my ($self) = @_;
167              
168             # can't think of any other good use for scalar %hash besides this
169 3         5 return scalar( @{ $self->{array} } );
  3         15  
170             }
171              
172             sub UNTIE
173             {
174 8     8   40 my $self = shift;
175              
176 8         855 delete $self->{array};
177             }
178              
179             sub DESTROY
180             {
181 8     8   3984 UNTIE(@_);
182             }
183              
184             =head1 NAME
185              
186             Tie::Array::AsHash - tie arrays as hashes by splitting lines on separator
187              
188             =head1 SYNOPSIS
189              
190             use Tie::Array::AsHash;
191              
192             my $t = tie my %hash, 'Tie::Array::AsHash', array => \@array, split => ':'
193             or die "Problem tying %hash: $!";
194              
195             print $hash{foo}; # access hash value via key name
196             $hash{foo} = "bar"; # assign new value
197             my @keys = keys %hash; # get the keys
198             my @values = values %hash; # ... and values
199             exists $hash{perl}; # check for existence
200             delete $hash{baz}; # delete line from file
201             $hash{newkey} = "perl"; # entered at end of file
202             while (($key,$val) = each %hash) # iterate through hash
203             %hash = (); # empty file
204              
205             untie %hash; # all done
206              
207             Here is sample text that would work with the above code when contained in a
208             file:
209              
210             foo:baz
211             key:val
212             baz:whatever
213              
214             =head1 DESCRIPTION
215              
216             C uses some practical extracting code so arrays can be tied
217             to hashes.
218              
219             The module was initially written by Chris Angell for
220             managing htpasswd-format password files.
221              
222             =head1 SYNOPSIS
223              
224             use Tie::Array::AsHash;
225             tie %hash, 'Tie::Array::AsHash', array => \@array, split => ':'
226             or die "Problem tying %hash: $!";
227              
228             (use %hash like a regular ol' hash)
229              
230             untie %hash; # changes saved to disk
231              
232             Easy enough eh?
233              
234             New key/value pairs are appended to the end of the file, C removes lines
235             from the file, C and C work as expected, and so on.
236              
237             C will not die or exit if there is a problem tying a
238             file, so make sure to check the return value and check C<$!> as the examples do.
239              
240             =head2 OPTIONS
241              
242             The only argument C requires is the "split" option, besides
243             a filename. The split option's value is the delimiter that exists in the file
244             between the key and value portions of the line. It may be a regular
245             expression, and if so, the "join" option must be used to tell
246             C what to stick between the key and value when writing
247             to the file. Otherwise, the module dies with an error message.
248              
249             tie %hash, 'Tie::Array::AsHash', array => \@array, split => qr(\s+), join => ' '
250             or die "Problem tying %hash: $!";
251              
252             Obviously no one wants lines like "key(?-xism:\s+)val" in their files.
253              
254             All other options are passed directly to C, so read its
255             documentation for more information.
256              
257             =head1 CAVEATS
258              
259             When C, C, or C is used on the hash, the values are
260             returned in the same order as the data exists in the file, from top to
261             bottom, though this behavior should not be relied on and is subject to change
262             at any time (but probably never will).
263              
264             C doesn't force keys to be unique. If there are multiple
265             keys, the first key in the file, starting at the top, is used. However, when
266             C, C, or C is used on the hash, every key/value combination
267             is returned, including duplicates, triplicates, etc.
268              
269             Keys can't contain the split character. Look at the perl code that
270             C is comprised of to see why (look at the regexes). Using
271             a regex for the split value may be one way around this issue.
272              
273             C hasn't been optimized much. Maybe it doesn't need to be.
274             Optimization could add overhead. Maybe there can be options to turn on and off
275             various types of optimization?
276              
277             =head1 EXAMPLES
278              
279             =head2 changepass.pl
280              
281             C changes password file entries when the lines are of
282             "user:encryptedpass" format. It can also add users.
283              
284             #!/usr/bin/perl -w
285              
286             use strict;
287             use warnings;
288              
289             use Tie::Array::AsHash;
290              
291             die "Usage: $0 user password" unless @ARGV == 2;
292             my ($user, $newpass) = @ARGV;
293              
294             tie my @userlist, 'Tie::File', '/pwdb/users.txt';
295             tie my %users, 'Tie::Array::AsHash', array => \@userlist, split => ':'
296             or die "Problem tying %hash: $!";
297              
298             # username isn't in the password file? see if the admin wants it added
299             unless (exists $users{$user})
300             {
301             print "User '$user' not found in db. Add as a new user? (y/n)\n";
302             chomp(my $y_or_n = );
303             set_pw($user, $newpass) if $y_or_n =~ /^[yY]/;
304             }
305             else
306             {
307             set_pw($user, $newpass);
308             print "Done.\n";
309             }
310              
311             sub set_pw { $users{$_[0]} = crypt($_[1], "AA") }
312              
313             =head2 Using the join option
314              
315             Here's code that would allow the delimiter to be ':' or '#' but prefers '#':
316              
317             tie my %hash, 'Tie::Array::AsHash', array => \@array, split => qr/[:#]/, join => "#" or die $!;
318              
319             Say you want to be sure no ':' delimiters exist in the file:
320              
321             while (my ($key, $val) = each %hash)
322             {
323             $hash{$key} = $val;
324             }
325              
326             =head1 TODO
327              
328             =over 4
329              
330             =item *
331              
332             add supoort for comments and/or commented lines
333              
334             =over 8
335              
336             =item + RfC
337              
338             new parameters: C regex, comment_join =E ' #'>>
339             similar to split/join parameters?
340              
341             =back
342              
343             =back
344              
345             =head1 AUTHOR
346              
347             Chris Angell , Jens Rehsack
348              
349             Feel free to email me with suggestions, fixes, etc.
350              
351             Thanks to Mark Jason Dominus for authoring the superb Tie::File module.
352              
353             =head1 COPYRIGHT
354              
355             Copyright (C) 2004, Chris Angell, 2008-2013, Jens Rehsack. All Rights Reserved.
356              
357             This library is free software; you can redistribute it and/or modify
358             it under the same terms as Perl itself, including any version of Perl 5.
359              
360             =head1 SEE ALSO
361              
362             perl(1), perltie(1), Tie::File(3pm), Tie::File::AsHash(3pm)
363              
364             =cut
365              
366             # vim:ts=4
367              
368             1;