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