File Coverage

blib/lib/Config/Savelogs.pm
Criterion Covered Total %
statement 221 234 94.4
branch 67 98 68.3
condition 4 10 40.0
subroutine 23 23 100.0
pod 13 13 100.0
total 328 378 86.7


line stmt bran cond sub pod time code
1             package Config::Savelogs;
2              
3 1     1   55302 use 5.008001;
  1         4  
  1         41  
4 1     1   5 use strict;
  1         2  
  1         32  
5 1     1   5 use warnings;
  1         6  
  1         30  
6 1     1   5 use Carp 'carp';
  1         2  
  1         49  
7 1     1   1327 use Storable ();
  1         626316  
  1         3147  
8              
9             our $VERSION = '0.11';
10              
11             ## note this: savelogs configuration files are UNORDERED. In that
12             ## spirit, we don't preserve ordering of any fields. When we
13             ## pretty-print, we put bare directives first and then groups last.
14              
15             ## class members
16             my %file = ();
17             my %directs = ();
18             my %dirty = ();
19              
20             ## other
21             my %array_type = ( apachelogexclude => [],
22             apachehost => [],
23             log => [],
24             nolog => [], );
25              
26             my %normal = ( apacheconf => 'ApacheConf',
27             apachehost => 'ApacheHost',
28             logfile => 'LogFile',
29             loglevel => 'LogLevel',
30             size => 'Size',
31             touch => 'Touch',
32             chown => 'Chown',
33             chmod => 'Chmod',
34             period => 'Period',
35             count => 'Count',
36             hourly => 'Hourly',
37             postmovehook => 'PostMoveHook',
38             apachelogexclude => 'ApacheLogExclude',
39             apachelog => 'ApacheLog',
40             clobber => 'Clobber',
41             filter => 'Filter',
42             ext => 'Ext',
43             datefmt => 'DateFmt',
44             process => 'Process',
45             archive => 'Archive',
46             nolog => 'NoLog',
47             log => 'Log',
48             disabled => 'Disabled',
49             );
50              
51             sub new {
52 11     11 1 21401 my $class = shift;
53 11         32 my $file = shift;
54              
55 11         49 my $self = bless \(my $ref), $class;
56              
57 11         59 $file {$self} = '';
58 11         41 $directs {$self} = {};
59 11         39 $dirty {$self} = {};
60              
61 11 100       33 if( $file ) {
62 9         39 $self->file($file);
63 9 50       209 $self->read() if -f $file;
64             }
65              
66 11         50 return $self;
67             }
68              
69             sub read {
70 9     9 1 13 my $self = shift;
71 9 50       18 $self->file(@_)
72             or return;
73              
74 9         21 $directs {$self} = {}; ## reset
75              
76             open my $fh, "<", $file{$self}
77 9 50       360 or do {
78 0         0 carp "Couldn't read file '" . $file{$self} . "': $!\n";
79 0         0 return;
80             };
81              
82 9         137 while( my $line = <$fh> ) {
83 100         146 chomp $line;
84 100 100       209 next unless $line;
85 67 100       172 next if $line =~ /^\s*\#/; ## skip comments
86              
87             ## parse a group [ARRAYREF]
88 57 100       179 if( $line =~ /^\s*/i ) {
89 31         68 my $group = $self->_parse_group($fh);
90 31   100     133 $directs{$self}->{groups} ||= [];
91 31         34 push @{ $directs{$self}->{groups} }, $group;
  31         73  
92 31         1271 next;
93             }
94              
95 26         53 my $data = _parse_line($line);
96              
97             ## got a {Directive => Value} pair
98 26 50       1197 if( ref($data) ) {
99 26         72 my ($directive, $value) = each %$data;
100 26         46 $directive = lc($directive); ## normalize
101              
102 26 50       59 if( exists $array_type{$directive} ) {
103 0   0     0 $directs{$self}->{$directive} ||= [];
104 0         0 push @{ $directs{$self}->{$directive} }, $value;
  0         0  
105             }
106             else {
107 26         171 $directs{$self}->{$directive} = $value;
108             }
109             }
110              
111 26         95 next;
112             }
113              
114 9         103 close $fh;
115              
116             ## make a deep copy here of %directs
117 9         535 $dirty{$self} = Storable::dclone($directs{$self});
118              
119 9         38 return 1;
120             }
121              
122             sub set {
123 6     6 1 2108 my $self = shift;
124              
125 6         19 while( @_ ) {
126 9         14 my $directive = shift;
127 9         19 my $value = shift;
128              
129             ## overwrite existing data
130 9         27 $directive = lc($directive); ## normalize
131 9 50       22 if( exists $array_type{$directive} ) {
132 0         0 $directs{$self}->{$directive} = [ $value ];
133             }
134             else {
135 9         46 $directs{$self}->{$directive} = $value;
136             }
137             }
138             }
139              
140             sub add_group {
141 3     3 1 6371 my $self = shift;
142 3         11 my %args = @_;
143 3         5 push @{ $directs{$self}->{groups} }, $self->_fix_group(\%args);
  3         18  
144             }
145              
146             sub _fix_group {
147 3     3   5 my $self = shift;
148 3         4 my $group = shift;
149              
150 3         9 for my $key ( %$group ) {
151 18 100       43 next unless exists $array_type{lc($key)};
152 3 100       10 next if ref($group->{$key});
153 2         7 $group->{$key} = [ $group->{$key} ];
154             }
155              
156 3         13 return $group;
157             }
158              
159             sub remove_group {
160 3     3 1 497 my $self = shift;
161 3         37 my %args = @_;
162              
163 3 50       20 my $match = delete $args{match}
164             or return;
165              
166 3         10 my @removed = ();
167              
168             ## find first matching group
169 3         8 my $groups = $directs{$self}->{groups};
170 3         22 GROUP: for my $i ( 0..$#$groups ) {
171 8         14 my $group = $groups->[$i];
172              
173 8         30 MATCH: for my $mkey ( keys %$match ) {
174 8         15 my $gkey = lc($mkey);
175 8 50       17 next GROUP unless exists $group->{$gkey};
176 8 50       20 if( ref($group->{$gkey}) ) {
177 8         18 for my $value (@{ $group->{$gkey} }) {
  8         13  
178 11 100       31 last MATCH if $value eq $match->{$mkey};
179             }
180 5         14 next GROUP;
181             }
182             else {
183 0 0       0 next GROUP unless $group->{$gkey} eq $match->{$mkey};
184             }
185             }
186              
187 3         6 push @removed, $groups->[$i];
188 3         6 $groups->[$i] = undef;
189             }
190              
191 3         11 @$groups = grep { defined } @$groups;
  8         17  
192              
193 3         13 return @removed;
194             }
195              
196             sub find_group {
197 9     9 1 14 my $self = shift;
198 9         19 my %args = @_;
199              
200 9 50       31 my $match = delete $args{match}
201             or return;
202              
203 9         23 my $groups = $directs{$self}->{groups};
204 9         10 my $find_group;
205 9         22 GROUP: for my $group ( @$groups ) {
206 26         53 MATCH: for my $mkey ( keys %$match ) {
207 26         36 my $gkey = lc($mkey); ## normalize
208 26 100       57 next GROUP unless exists $group->{$gkey};
209              
210             DO_MATCH: {
211 21 100       23 if( ref($group->{$gkey}) ) {
  21         42  
212 10         11 for my $value ( @{ $group->{$gkey} } ) {
  10         19  
213 15 100       41 last DO_MATCH if $value eq $match->{$mkey};
214             }
215 7         19 next GROUP;
216             }
217             else {
218 11 100       43 next GROUP unless $group->{$gkey} eq $match->{$mkey};
219             }
220             }
221              
222 8         12 $find_group = $group;
223 8         10 last GROUP;
224             }
225             }
226              
227 9         36 return $find_group;
228             }
229              
230             ## FIXME: make work with Log or other multiple directives
231             sub add_to_group {
232 2     2 1 7 my $self = shift;
233 2         8 my %args = @_;
234              
235 2 50       12 my $match = delete $args{match}
236             or return;
237              
238 2         5 my $host = delete $args{apachehost};
239 2 50       12 unless( ref($host) ) {
240 2         6 $host = [ $host ];
241             }
242              
243 2         2 my $found;
244 2 50       8 if( my $group = $self->find_group(match => $match) ) {
245 2         6 my $hosts = $group->{apachehost};
246 2         16 $group->{apachehost} = [ sort (@$hosts, @$host) ];
247 2         5 $found = 1;
248             }
249              
250 2         6 return $found;
251             }
252              
253             ## FIXME: make work with Log or other multiple directives
254             sub remove_from_group {
255 3     3 1 600 my $self = shift;
256 3         11 my %args = @_;
257              
258 3 50       13 my $match = delete $args{match}
259             or return;
260              
261 3         7 my $host = delete $args{apachehost};
262 3 100       9 unless( ref($host) ) {
263 2         5 $host = [ $host ];
264             }
265              
266 3         5 my %host = ();
267 3         11 @host{@$host} = (1) x @$host;
268              
269 3 50       9 if( my $group = $self->find_group(match => $match) ) {
270 3         4 my $hosts = $group->{apachehost};
271 3         7 $group->{apachehost} = [ sort grep { ! $host{$_} } @$hosts ];
  6         22  
272             }
273              
274 3         9 return 1;
275             }
276              
277             sub data {
278 16     16 1 39 my $self = shift;
279 16         40 my $groups = $directs{$self}->{groups};
280 16         23 my $changed = 0;
281              
282 16         38 GROUPS: for my $group ( @$groups ) {
283 46         45 my $valid_group = 0;
284 46         146 for my $key ( sort keys %$group ) {
285 46         62 my $val = $group->{$key};
286 46 50       92 next unless ref($val);
287              
288 46         59 for my $lval ( @$val ) {
289 45 50 33     118 $valid_group = 1 if lc($key) eq 'apachehost' or lc($key) eq 'log';
290 45 50       156 next GROUPS if $valid_group;
291             }
292              
293             ## we have an invalid group here
294 1         3 undef $group;
295 1         2 $changed = 1;
296 1         3 next GROUPS;
297             }
298             }
299              
300 16 100       35 if( $changed ) {
301 1         3 @$groups = grep { defined $_ } @$groups;
  3         7  
302 1         4 $directs{$self}->{groups} = $groups;
303             }
304              
305 16         95 return $directs{$self};
306             }
307              
308             sub file {
309 28     28 1 40 my $self = shift;
310              
311 28 100       61 if( @_ ) {
312 12         28 $file{$self} = shift;
313             }
314              
315 28         97 return $file{$self};
316             }
317              
318             sub is_dirty {
319 3     3 1 4 my $self = shift;
320              
321 3         6 local $Storable::canonical = 1;
322              
323 3         14 my $cmp1 = Storable::freeze($directs{$self});
324 3         118 my $cmp2 = Storable::freeze($dirty{$self});
325              
326 3         92 return $cmp1 ne $cmp2;
327             }
328              
329             sub revert {
330 1     1 1 3 my $self = shift;
331 1         35 $directs{$self} = Storable::dclone($dirty{$self});
332             }
333              
334             sub write {
335 10     10 1 1770 my $self = shift;
336 10 50       29 $self->file(@_)
337             or return;
338              
339             open my $fh, ">", $file{$self}
340 10 50       1171 or do {
341 0         0 carp "Couldn't write file '" . $file{$self} . "': $!\n";
342 0         0 return;
343             };
344              
345 10         26 my %config = %{ $self->data };
  10         42  
346 10         28 my $groups = delete $config{groups};
347              
348 10         28 for my $key ( keys %config ) {
349 28 50       67 my $directive = ($normal{$key} ? $normal{$key} : $key);
350              
351 28 50       47 if( ref($config{$key}) ) {
352 0         0 for my $value ( @{$config{$key}} ) {
  0         0  
353 0         0 print $fh "$directive\t$value\n";
354             }
355             }
356             else {
357 28         170 print $fh "$directive\t$config{$key}\n";
358             }
359             }
360              
361 10 50       41 _write_groups($fh, $groups) if $groups;
362              
363 10         2664 close $fh;
364              
365 10         561 $dirty{$self} = Storable::dclone($directs{$self});
366              
367 10         147 return 1;
368             }
369              
370             sub _write_groups {
371 10     10   14 my $fh = shift;
372 10         11 my $groups = shift;
373 10         21 my $str = '';
374              
375             ## FIXME: sort by apachehost, then log directive
376 10         25 GROUP: for my $group ( @$groups ) {
377 35         36 my $tstr .= "\n";
378 35         54 $tstr .= "\n";
379 35         84 for my $key ( sort keys %$group ) {
380 88         132 my $val = $group->{$key};
381 88 100       134 my $tab = ( length($key) > 5 ? "\t" : "\t\t" );
382 88   33     196 my $nkey = $normal{lc($key)} || $key;
383              
384 88 100       146 if( ref($val) ) {
385 35         46 for my $lval ( @$val ) {
386 61         158 $tstr .= " $nkey${tab}$lval\n";
387             }
388             }
389             else {
390 53         127 $tstr .= " $nkey${tab}$val\n";
391             }
392             }
393 35         50 $tstr .= "\n";
394              
395 35         55 $str .= $tstr;
396             }
397              
398 10 50       28 print $fh $str if $str;
399              
400 10         15 return 1;
401             }
402              
403             sub _parse_line {
404 125     125   184 my $line = shift;
405              
406 125 50       542 if( my($directive, $value) = $line =~ /^\s*(\S+)\s*(.*)/ ) {
407 125         539 $value =~ s/\s*$//;
408 125         416 return { $directive => $value };
409             }
410              
411 0         0 return $line; ## something we don't recognize
412             }
413              
414             sub _parse_group {
415 31     31   36 my $self = shift;
416 31         27 my $fh = shift;
417 31         49 my %group = ();
418              
419 31         81 while( my $line = <$fh> ) {
420 130         142 chomp $line;
421 130 50       214 next unless $line;
422 130 50       361 next if $line =~ /^\s*\#/; ## skip comments
423              
424 130 100       410 if( $line =~ m{\s*}i ) {
425 31         44 last;
426             }
427              
428 99         154 my $data = _parse_line($line);
429 99 50       222 if( ref($data) ) {
430 99         216 my($key, $val) = each %$data;
431 99         153 $key = lc($key); ## normalize
432              
433 99 100       173 if( exists $array_type{$key} ) {
434 53 100       135 $group{$key} = []
435             unless exists $group{$key};
436 53         53 push @{$group{$key}}, $val;
  53         239  
437             }
438              
439             ## overwrite previous entry if multiple
440             else {
441 46         206 $group{$key} = $val;
442             }
443             }
444             }
445              
446 31         83 return \%group;
447             }
448              
449             sub DESTROY {
450 11     11   620 my $self = $_[0];
451              
452 11         41 delete $file {$self};
453 11         62 delete $directs {$self};
454              
455 11         54 my $super = $self->can("SUPER::DESTROY");
456 11 50       245 goto &$super if $super;
457             }
458              
459             1;
460             __END__