File Coverage

blib/lib/Tie/ConfigFile.pm
Criterion Covered Total %
statement 91 101 90.1
branch 18 24 75.0
condition n/a
subroutine 15 17 88.2
pod n/a
total 124 142 87.3


line stmt bran cond sub pod time code
1             package Tie::ConfigFile;
2              
3 1     1   39814 use strict;
  1         2  
  1         29  
4 1     1   5 use warnings;
  1         1  
  1         25  
5              
6 1     1   454 use IO::File;
  1         911  
  1         100  
7 1     1   5 use Carp;
  1         1  
  1         812  
8              
9             our $VERSION = '1.00';
10              
11             sub __init {
12 2     2   3 my $self = shift;
13              
14 2 100       25 $self->{_fh} = IO::File->new(
    50          
    50          
15             $self->{filename},
16             ($self->{readonly} ? O_RDONLY : O_RDWR) |
17             ($self->{create_file} ? O_CREAT : 0)
18             ) or croak sprintf('Unable to open file "%s".', $self->{filename});
19              
20             # we utf-8, baby
21 2         181 $self->{_fh}->binmode(':utf8');
22              
23             # init cache
24 2         21 $self->{_cache} = {};
25              
26 2         4 return 1;
27             }
28              
29             sub __read {
30 2     2   3 my $self = shift;
31              
32             # separator character
33 2         2 my $SEP = '=';
34             # comment character
35 2         5 my $COMM = ';';
36              
37 2         4 my $cache = $self->{_cache};
38              
39 2         2 my $i = 0;
40 2         70 while (defined(my $line = $self->{_fh}->getline)) {
41 14         286 chomp $line;
42              
43             # is it comment?
44 14 100       111 if ($line =~ /^\s*$COMM/) {
    100          
    50          
45 4         3 push @{ $cache->{structure} },
  4         11  
46             {
47             type => 'comment',
48             value => $line,
49             };
50             }
51             # is it just whitespace?
52             elsif ($line =~ /^\s*$/) {
53 2         7 push @{ $cache->{structure} },
  2         7  
54             {
55             type => 'whitespace',
56             value => $line,
57             }
58             }
59             # is it key-value pair?
60             elsif ($line =~ /$SEP/) {
61 8         49 my($key, $value) = split /\s*$SEP\s*/, $line, 2;
62              
63 8         8 push @{ $cache->{structure} },
  8         24  
64             {
65             type => 'value',
66             value => $line,
67             };
68              
69             # these are needed for fast retrieval of values
70 8         17 $cache->{values}{$key} = $value;
71 8         15 $cache->{key_to_struct}{$key} = $cache->{structure}->[$i];
72             }
73             # we can't parse it
74             else {
75 0         0 croak sprintf(
76             "Found garbage at <%s>, line %d.",
77             $self->{filename}, $i + 1
78             );
79             }
80              
81 14         224 $i++;
82             }
83              
84             return
85 2         44 }
86              
87             sub __write {
88 4     4   6 my $self = shift;
89              
90 4         7 my $cache = $self->{_cache};
91              
92             # clear the file before we write to it.
93 4         27 $self->{_fh}->truncate(0);
94 4         233 $self->{_fh}->seek(0, 0);
95              
96 4         27 for my $item (@{ $cache->{structure} }) {
  4         13  
97 31 100       140 next if $item->{deleted};
98              
99 28         70 $self->{_fh}->print($item->{value}, "\n");
100             }
101              
102             # Due to flushing, writes are reliable even if user forgets to untie hash.
103 4         137 $self->{_fh}->flush;
104              
105 4         11 return;
106             }
107              
108             sub TIEHASH {
109 2     2   669 my($class, %args) = @_;
110              
111 2         10 my $self = {
112             filename => undef,
113             readonly => 1,
114             create_file => 0,
115             %args
116             };
117              
118 2 50       9 unless (defined $self->{filename}) {
119 0         0 croak 'Filename is required.';
120             }
121              
122 2         3 bless $self, $class;
123              
124 2         8 $self->__init;
125 2         5 $self->__read;
126              
127 2         13 return $self;
128             }
129              
130             sub FETCH {
131 5     5   78 my($self, $key) = @_;
132              
133 5         10 my $cache = $self->{_cache};
134              
135 5         21 return $cache->{values}{$key};
136             }
137              
138             sub STORE {
139 3     3   376 my($self, $key, $value) = @_;
140              
141 3         4 my $cache = $self->{_cache};
142              
143 3 100       12 if ($self->{readonly}) {
144 1         201 croak 'STORE is not allowed on read-only config file.';
145             }
146              
147 2 100       9 if (exists $self->{_cache}{values}{$key}) {
148             # key already exist, just update it
149 1         4 $cache->{values}{$key} = $value;
150 1         7 $cache->{key_to_struct}{$key}{value} = "$key=$value",
151             }
152             else {
153             # create new key
154 1         2 push @{ $cache->{structure} },
  1         7  
155             {
156             type => 'value',
157             value => "$key=$value",
158             };
159             # index of element we just pushed
160 1         2 my $index = $#{ $cache->{structure} };
  1         3  
161              
162 1         3 $cache->{values}{$key} = $value;
163 1         8 $cache->{key_to_struct}{$key} = $cache->{structure}->[$index],
164             }
165              
166 2         25 $self->__write;
167              
168 2         6 return;
169             }
170              
171             sub DELETE {
172 2     2   752 my($self, $key) = @_;
173              
174 2         5 my $cache = $self->{_cache};
175              
176 2 50       10 if ($self->{readonly}) {
177 0         0 croak 'DELETE is not allowed on read-only config file.';
178             }
179              
180 2         6 $cache->{key_to_struct}{$key}{deleted} = 1;
181 2         5 delete $cache->{key_to_struct}{$key};
182 2         5 delete $cache->{values}{$key};
183              
184 2         6 $self->__write;
185              
186 2         9 return;
187             }
188              
189             sub CLEAR {
190 0     0   0 my $self = shift;
191              
192 0         0 %{$self->{_cache}} = ();
  0         0  
193 0         0 $self->__write;
194              
195 0         0 return;
196             }
197              
198             sub EXISTS {
199 0     0   0 my($self, $key) = @_;
200              
201             # Empty keys have '' value, so if value is undefined, key doesn't exist.
202 0         0 return exists $self->{_cache}{values}{$key};
203             }
204              
205             sub FIRSTKEY {
206 1     1   10 my $self = shift;
207              
208 1         2 return (each %{ $self->{_cache}{values} } )
  1         9  
209             }
210              
211             sub NEXTKEY {
212 3     3   5 my $self = shift;
213              
214 3         3 return (each %{ $self->{_cache}{values} } )
  3         22  
215             }
216              
217             sub UNTIE {
218 1     1   284 my($self, $count) = @_;
219              
220 1 50       5 carp "untie attempted while $count inner references still exist" if $count;
221              
222             return
223 1         5 }
224              
225             sub DESTROY {
226 2     2   4 my $self = shift;
227              
228 2         17 $self->{_fh}->close;
229              
230             return
231 2         161 }
232              
233             'thank you based god';
234              
235             __END__