File Coverage

blib/lib/Tie/TextDir.pm
Criterion Covered Total %
statement 107 128 83.5
branch 37 68 54.4
condition 8 12 66.6
subroutine 21 22 95.4
pod n/a
total 173 230 75.2


line stmt bran cond sub pod time code
1             package Tie::TextDir;
2              
3 2     2   146601 use strict;
  2         15  
  2         65  
4 2     2   10 use warnings;
  2         3  
  2         56  
5              
6 2     2   11 use File::Spec;
  2         3  
  2         40  
7 2     2   1631 use File::Temp;
  2         46678  
  2         153  
8 2     2   16 use File::Path;
  2         6  
  2         87  
9 2     2   1029 use File::Find::Iterator;
  2         40120  
  2         138  
10 2     2   21 use File::Basename;
  2         5  
  2         180  
11              
12 2     2   13 use Symbol;
  2         5  
  2         115  
13 2     2   11 use Fcntl qw(:DEFAULT);
  2         5  
  2         685  
14 2     2   23 use Carp;
  2         4  
  2         3058  
15              
16             our $VERSION = '0.07';
17              
18             sub TIEHASH {
19 9 50 33 9   7247 croak "usage: tie(%hash, 'Tie::TextDir', \$path, [mode], [perms], [levels])"
20             unless 2 <= @_ and @_ <= 5;
21            
22 9         29 my ($package, $path, $mode, $perms, $levels) = @_;
23 9   100     31 $mode ||= 'ro';
24 9   50     41 $perms ||= 0775;
25 9   100     31 $levels ||= 0;
26 9 50       50 $levels = 0 if $levels < 0;
27 9         24 my $self = bless {}, $package;
28            
29             # Save levels
30 9         31 $self->{LEVELS} = $levels;
31              
32             # Can we make changes to the database?
33 9 100       31 if ($mode eq 'rw') {
    50          
34 7         19 $self->{MODE} = O_CREAT | O_RDWR;
35             } elsif ($mode eq 'ro') {
36 2         6 $self->{MODE} = O_RDONLY;
37             } else {
38             # Assume $mode is a bitmask of Fcntl flags
39 0         0 $self->{MODE} = $mode;
40             }
41              
42             # Nice-ify $path:
43 9         26 $path =~ s#/$##;
44 9 50 66     176 croak "$path is not a directory" if -e $path and not -d _;
45 9 100       97 unless (-e $path) {
46 4 50       18 croak "$path does not exist" unless $self->{MODE} & O_CREAT;
47 4 50       295 mkdir $path, $perms or croak "Can't create $path: $!";
48             }
49 9         39 $self->{PATH} = $path;
50            
51             # Get an iterator over the directory
52             $self->{FIND} = File::Find::Iterator->create(dir => [$self->{PATH}],
53 9     21   132 filter => sub { -f });
  21         3501  
54            
55 9         1447 return $self;
56             }
57              
58             sub FETCH {
59 15     15   1501 my ($self, $key) = @_;
60 15 50       36 if ( !$self->_key_okay($key) ) {
61 0 0       0 carp "Bad key '$key'" if $^W;
62 0         0 return;
63             }
64              
65 15         48 my $file = File::Spec->catfile($self->{PATH}, $self->_key_to_path($key));
66 15 50       205 return unless -e $file;
67              
68 15         64 local *FH;
69 15 50       543 unless (open( FH, "< $file" )) {
70 0         0 carp "Can't open $file for reading: $!";
71 0         0 return;
72             }
73 15         55 my $value;
74 15         247 sysread FH, $value, ( stat FH )[7];
75 15         149 close FH;
76 15         105 return $value;
77             }
78              
79              
80             sub STORE {
81 8     8   1134 my ($self, $key) = (shift, shift);
82 8         29 my $file = File::Spec->catfile($self->{PATH}, $self->_key_to_path($key));
83 8 50       36 croak "No write access for '$file'" unless $self->{MODE} & O_RDWR;
84              
85 8 50       26 if ( !$self->_key_okay($key) ) {
86 0 0       0 carp "Bad key '$key'" if $^W;
87 0         0 return;
88             }
89              
90             # Use temp file for writing, and then rename to make the update atomic
91 8         39 my ($fh, $tmpname) = File::Temp::tempfile(DIR => $self->{PATH}, CLEANUP => 1);
92 8         2871 print $fh $_[0];
93 8         294 close $fh;
94 8 50       474 rename ($tmpname, $file) or croak ("can't rename temp file $tmpname to $file: $!");
95             }
96              
97              
98             sub DELETE {
99 6     6   46 my ($self, $key) = @_;
100 6         23 my $file = File::Spec->catfile($self->{PATH}, $self->_key_to_path($key));
101 6 50       28 croak "No write access for '$file'" unless $self->{MODE} & O_RDWR;
102            
103 6 50       24 if ( !$self->_key_okay($key) ) {
104 0 0       0 carp "Bad key '$key'" if $^W;
105 0         0 return;
106             }
107            
108 6 50       115 return unless -e $file;
109            
110 6         25 my $return;
111 6 50       29 $return = $self->FETCH($key) if defined wantarray; # Don't bother in void context
112            
113 6 50       408 unlink $file or croak "Couldn't delete $file: $!";
114              
115 6 100       27 if ($self->{LEVELS}) {
116 5         13 my $path = $file;
117 5         16 for (1..$self->{LEVELS}) {
118 9         291 $path = dirname($path);
119 9         370 rmdir $path;
120             }
121             }
122            
123 6         74 return $return;
124             }
125              
126             sub CLEAR {
127 0     0   0 my $self = shift;
128 0 0       0 croak "No write access for '$self->{PATH}'" unless $self->{MODE} & O_RDWR;
129            
130 0         0 $self->{FIND}->first;
131              
132 0         0 my $entry;
133 0         0 while (defined ($entry = $self->{FIND}->next)) {
134 0 0       0 unlink $entry or croak "can't remove $entry: $!";
135              
136 0 0       0 if ($self->{LEVELS}) {
137 0         0 my $path = $entry;
138 0         0 for (1..$self->{LEVELS}) {
139 0         0 $path = dirname($path);
140 0         0 rmdir $path;
141             }
142             }
143             }
144             }
145              
146             sub EXISTS {
147 6     6   19 my ($self, $key) = @_;
148 6 50       16 if ( !$self->_key_okay($key) ) {
149 6 50       22 carp "Bad key '$key'" if $^W;
150 6         29 return;
151             }
152 0         0 return -e File::Spec->catfile($self->{PATH}, $self->_key_to_path($key));
153             }
154              
155              
156             sub FIRSTKEY {
157 7     7   145 my $self = shift;
158              
159 7         35 $self->{FIND}->first;
160 7         1589 my $entry = $self->{FIND}->next;
161 7 100       77 $entry = _path_to_key($entry) if $entry;
162 7         40 return $entry;
163             }
164              
165              
166             sub NEXTKEY {
167 7     7   24 my $entry = shift()->{FIND}->next;
168 7 100       93 $entry = _path_to_key($entry) if $entry;
169 7         31 return $entry;
170             }
171              
172             sub _key_okay {
173 35 100   35   182 return 0 if $_[1] =~ /^\.{0,2}$/;
174 29         83 return 1;
175             }
176              
177             sub _path_to_key {
178 7     7   156 return fileparse($_[0]);
179             }
180              
181             sub _key_to_path {
182 29     29   55 my ($self, $key) = @_;
183 29         52 my $levels = $self->{LEVELS};
184            
185             # try to bail out as soon as we can if no levels is needed
186 29 100       148 return $key unless $levels;
187            
188             # Create the tree structure
189 21         72 my @key = split //, $key;
190 21 100       52 $levels = scalar(@key) if scalar(@key) < $levels;
191 21         58 my $prefix = File::Spec->catfile(map { join("", @key[0..$_]) } (0..$levels-1));
  33         252  
192            
193             # make sure the path exists, so we can create the file.
194 21         131 my $dir = File::Spec->catfile($self->{PATH}, $prefix);
195 21 100       1249 mkpath($dir) unless -d $dir;
196            
197 21         289 return File::Spec->catfile($prefix, $key);
198             }
199              
200             1;
201              
202             __END__