File Coverage

blib/lib/Config/Properties.pm
Criterion Covered Total %
statement 207 314 65.9
branch 58 154 37.6
condition 19 44 43.1
subroutine 39 55 70.9
pod 19 32 59.3
total 342 599 57.1


line stmt bran cond sub pod time code
1             package Config::Properties;
2              
3 7     7   98777 use strict;
  7         11  
  7         235  
4 7     7   27 use warnings;
  7         8  
  7         260  
5              
6             our $VERSION = '1.78';
7              
8 7     7   3447 use IO::Handle;
  7         36006  
  7         348  
9 7     7   43 use Carp;
  7         9  
  7         325  
10 7     7   29 use PerlIO qw();
  7         8  
  7         88  
11 7     7   3516 use Errno qw();
  7         6813  
  7         146  
12              
13             {
14 7     7   30 no warnings;
  7         9  
  7         18506  
15             sub _t_key ($) {
16 70     70   60 my $k=shift;
17 70 50 33     353 defined($k) && length($k)
18             or croak "invalid property key '$k'";
19             }
20              
21             sub _t_value ($) {
22 2     2   2 my $v=shift;
23 2 50       4 defined $v
24             or croak "undef is not a valid value for a property";
25             }
26              
27             sub _t_format ($) {
28 0     0   0 my $f=shift;
29 0 0 0     0 defined ($f) && $f=~/\%s.*\%s/
30             or croak "invalid format '%f'";
31             }
32              
33             sub _t_validator ($) {
34 2     2   3 my $v=shift;
35 2 50 33     16 defined($v) &&
36             UNIVERSAL::isa($v, 'CODE') or
37             croak "invalid property validator '$v'";
38             }
39              
40             sub _t_file ($) {
41 14     14   22 my $f=shift;
42 14 50       36 defined ($f) or
43             croak "invalid file '$f'";
44             }
45              
46             sub _t_order ($) {
47 9     9   13 my $o = shift;
48 9 50       65 $o =~ /^(?:keep|alpha|none)$/ or
49             croak "invalid order";
50             }
51              
52             sub _t_encoding ($) {
53 8     8   11 my $e = shift;
54 8 50       43 $e =~ /^[\w\-]+$/ or
55             croak "invalid encoding '$e'";
56             }
57             }
58              
59             # new() - Constructor
60             #
61             # The constructor can take one optional argument "$defaultProperties"
62             # which is an instance of Config::Properties to be used as defaults
63             # for this object.
64             sub new {
65 8     8 1 265 my $class = shift;
66 8         12 my $defaults;
67 8 50       30 $defaults = shift if @_ & 1;
68 8         21 my %opts = @_;
69 8 50       36 $defaults = delete $opts{defaults} unless defined $defaults;
70 8         16 my $format = delete $opts{format};
71 8 50       23 $format = '%s=%s' unless defined $format;
72 8         11 my $wrap = delete $opts{wrap};
73 8 50       20 $wrap = 1 unless defined $wrap;
74 8         14 my $order = delete $opts{order};
75 8 50       19 $order = 'keep' unless defined $order;
76 8         25 _t_order($order);
77 8         11 my $file = delete $opts{file};
78 8   100     39 my $encoding = delete $opts{encoding} || 'latin1';
79 8         21 _t_encoding($encoding);
80 8         10 my $eol_re = delete $opts{eol_re};
81 8 50       45 $eol_re = qr/\r\n|\n|\r/ unless defined $eol_re;
82 8         193 my $line_re = qr/^(.*?)(?:$eol_re)/s;
83              
84 8 50       32 %opts and croak "invalid option(s) '" . join("', '", keys %opts) . "'";
85              
86 8 50       128 if (defined $defaults) {
87 0 0       0 if (ref $defaults eq 'HASH') {
    0          
88 0         0 my $d = Config::Properties->new;
89 0         0 while (my ($k, $v) = each %$defaults) {
90 0         0 $d->setProperty($k, $v);
91             }
92 0         0 $defaults = $d;
93             }
94             elsif (!$defaults->isa('Config::Properties')) {
95 0         0 croak die "defaults parameter is not a Config::Properties object or a hash"
96             }
97             }
98              
99 8         79 my $self = { defaults => $defaults,
100             format => $format,
101             wrap => $wrap,
102             order => $order,
103             properties => {},
104             last_line_number => 0,
105             property_line_numbers => {},
106             file => $file,
107             encoding => $encoding,
108             line_re => $line_re };
109 8         30 bless $self, $class;
110              
111 8 50       29 if (defined $file) {
112 0 0       0 open my $fh, '<', $file or croak "unable to open file '$file': $!";
113 0         0 $self->load($fh);
114 0 0       0 close $fh or croak "unable to load file '$file': $!";
115             }
116 8         39 return $self;
117             }
118              
119             # set property only if its going to change the property value.
120             #
121             sub changeProperty {
122 0     0 1 0 my ($self, $key, $new, @defaults) = @_;
123 0         0 _t_key $key;
124 0         0 _t_value $new;
125 0         0 my $old=$self->getProperty($key, @defaults);
126 0 0 0     0 if (!defined $old or $old ne $new) {
127 0         0 $self->setProperty($key, $new);
128 0         0 return 1;
129             }
130 0         0 return 0;
131             }
132              
133             sub deleteProperty {
134 1     1 1 599 my ($self, $key, $recurse) = @_;
135 1         6 _t_key $key;
136              
137 1 50       4 if (exists $self->{properties}{$key}) {
138 1         2 delete $self->{properties}{$key};
139 1         2 delete $self->{property_line_numbers}{$key};
140             }
141              
142 1 50 33     3 $self->{defaults}->deleteProperty($key, 1)
143             if ($recurse and $self->{defaults});
144             }
145              
146             # setProperty() - Set the value for a specific property
147             sub setProperty {
148 2     2 1 8 my ($self, $key, $value)=@_;
149 2         1 _t_key $key;
150 2         3 _t_value $value;
151              
152 2 50       4 defined(wantarray) and
153             carp "warning: setProperty doesn't return the old value anymore";
154              
155 2   66     7 $self->{property_line_numbers}{$key} ||= ++$self->{last_line_number};
156 2         4 $self->{properties}{$key} = $value;
157             }
158              
159             sub _properties {
160 4     4   5 my $self=shift;
161 4 50       12 if (defined ($self->{defaults})) {
162 0         0 my %p=($self->{defaults}->_properties, %{$self->{properties}});
  0         0  
163 0         0 return %p;
164             }
165 4         5 return %{ $self->{properties} }
  4         61  
166             }
167              
168             # properties() - return a flated hash with all the properties
169             sub properties {
170 1     1 1 2 my $self = shift;
171 1         2 my %p = $self->_properties;
172 1         6 map { $_ => $p{$_} } $self->_sort_keys(keys %p);
  13         21  
173             }
174              
175              
176              
177             # getProperties() - Return a hashref of all of the properties
178 1     1 1 3 sub getProperties { return { shift->_properties }; }
179              
180              
181             # getFormat() - Return the output format for the properties
182 0     0 1 0 sub getFormat { shift->{format} }
183              
184              
185             # setFormat() - Set the output format for the properties
186             sub setFormat {
187 0     0 1 0 my ($self, $format) = @_;
188 0 0       0 defined $format or $format='%s=%s';
189 0         0 _t_format $format;
190 0         0 $self->{format} = $format;
191             }
192              
193             # format() - Alias for get/setFormat();
194             sub format {
195 0     0 1 0 my $self = shift;
196 0 0       0 if (@_) {
197 0         0 return $self->setFormat(@_)
198             }
199 0         0 $self->getFormat();
200             }
201              
202              
203             # setValidator(\&validator) - Set sub to be called to validate
204             # property/value pairs. It is called
205             # &validator($property, $value, $config) being $config
206             # the Config::Properties object. $property and $key
207             # can be modified by the validator via $_[0] and $_[1]
208             sub setValidator {
209 2     2 0 43 my ($self, $validator) = @_;
210 2         7 _t_validator $validator;
211 2         13 $self->{validator} = $validator;
212             }
213              
214              
215             # getValidator() - Return the current validator sub
216 0     0 0 0 sub getValidator { shift->{validator} }
217              
218             # validator() - Alias for get/setValidator();
219             sub validator {
220 0     0 0 0 my $self=shift;
221 0 0       0 if (@_) {
222 0         0 return $self->setValidator(@_)
223             }
224             $self->getValidator
225 0         0 }
226              
227             sub setOrder {
228 1     1 0 3 my ($self, $order) = @_;
229 1         3 _t_order $order;
230 1         2 $self->{order} = $order
231             }
232              
233 0     0 0 0 sub getOrder { shift->{order} }
234              
235             sub order {
236 1     1 1 1588 my $self = shift;
237 1 50       7 $self->setOrder(@_) if @_;
238 1         2 $self->{order};
239             }
240              
241             # load() - Load the properties from a filehandle
242             sub load {
243 8     8 1 45 my ($self, $file) = @_;
244 8         22 _t_file $file;
245              
246             # check whether it is a real file handle
247 8         10 my $fn = do {
248 8         9 local $@;
249 8         10 eval { fileno($file) }
  8         38  
250             };
251 8 50 33     41 if (defined $fn and $fn >0) {
252 8 100       88 unless (grep /^(?:encoding|utf8)\b/, PerlIO::get_layers($file)) {
253 7 50   6   190 binmode $file, ":encoding($self->{encoding})"
  6         36  
  6         4  
  6         34  
254             or croak "Unable to set file encoding layer: $!";
255             }
256             }
257 8         55889 $self->{properties} = {};
258 8         22 $self->{property_line_numbers} = {};
259 8         63 my $ln = $file->input_line_number;
260 8 50       193 $self->{last_line_number} = ($ln > 0 ? $ln : 0);
261 8         18 $self->{buffer_in} = '';
262 8         29 1 while $self->process_line($file);
263 5         15 $self->{last_line_number};
264             }
265              
266              
267             # escape_key(string), escape_value(string), unescape(string) -
268             # subroutines to convert escaped characters to their
269             # real counterparts back and forward.
270              
271             my %esc = ( "\n" => 'n',
272             "\r" => 'r',
273             "\t" => 't' );
274             my %unesc = reverse %esc;
275              
276             sub escape_key {
277 21     21 0 57 $_[0]=~s{([\t\n\r\\"' =:])}{
278 14   66     75 "\\".($esc{$1}||$1) }ge;
279 21         36 $_[0]=~s{([^\x20-\x7e])}{sprintf "\\u%04x", ord $1}ge;
  0         0  
280 21         32 $_[0]=~s/^ /\\ /;
281 21         41 $_[0]=~s/^([#!])/\\$1/;
282 21         35 $_[0]=~s/(?
283             }
284              
285             sub escape_value {
286 21     21 0 54 $_[0]=~s{([\t\n\r\\])}{
287 13   66     64 "\\".($esc{$1}||$1) }ge;
288 21         42 $_[0]=~s{([^\x20-\x7e])}{sprintf "\\u%04x", ord $1}ge;
  0         0  
289 21         39 $_[0]=~s/^ /\\ /;
290             }
291              
292             sub unescape {
293 116     116 0 285 $_[0]=~s/\\([tnr\\"' =:#!])|\\u([\da-fA-F]{4})/
294 155 100 66     678 defined $1 ? $unesc{$1}||$1 : chr hex $2 /ge;
295             }
296              
297             sub read_line {
298 124     124 0 110 my ($self, $file) = @_;
299 124         131 my $bin = \$self->{buffer_in};
300 124         107 my $line_re = $self->{line_re};
301 124         89 while (1) {
302 132 100       973 if ($$bin =~ s/$line_re//) {
303 119         113 $self->{last_line_number}++;
304 119         334 return $1;
305             }
306             else {
307 13         243 my $bytes = read($file, $$bin, 8192, length $$bin);
308 13 50 0     88 last unless $bytes or (not defined $bytes and
      33        
      66        
309             ($! == Errno::EGAIN() or
310             $! == Errno::EWOULDBLOCK() or
311             $! == Errno::EINTR()));
312             }
313             }
314              
315 5 50       11 if (length $$bin) {
316 0         0 $self->{last_line_number}++;
317 0         0 my $line = $$bin;
318 0         0 $$bin = '';
319 0         0 return $line
320             }
321 5         9 undef;
322             }
323              
324              
325             # process_line() - read and parse a line from the properties file.
326              
327             # this is to workaround a bug in perl 5.6.0 related to unicode
328             my $bomre = eval(q< qr/^\\x{FEFF}/ >) || qr//;
329              
330             sub process_line {
331 100     100 0 125 my ($self, $file) = @_;
332 100         124 my $line = $self->read_line($file);
333 100 100       184 defined $line or return undef;
334              
335             # remove utf8 byte order mark
336 95         90 my $ln = $self->{last_line_number};
337 95 100       160 $line =~ s/$bomre// if $ln < 2;
338              
339             # ignore comments
340 95 100       305 $line =~ /^\s*(\#|\!|$)/ and return 1;
341              
342             # handle continuation lines
343 59         49 my @lines;
344 59   66     226 while ($line =~ /(\\+)$/ and length($1) & 1) {
345 24         82 $line =~ s/\\$//;
346 24         35 push @lines, $line;
347 24         41 $line = $self->read_line($file);
348 24 50       48 $line = '' unless defined $line;
349 24         167 $line =~ s/^\s+//;
350             }
351 59 100       102 $line = join('', @lines, $line) if @lines;
352              
353 59 100       401 my ($key, $value) = $line =~ /^
354             \s*
355             ((?:[^\s:=\\]|\\.)+)
356             \s*
357             [:=\s]
358             \s*
359             (.*)
360             $
361             /x
362             or $self->fail("invalid property line '$line'");
363              
364 58         105 unescape $key;
365 58         83 unescape $value;
366              
367 58         104 $self->validate($key, $value);
368              
369 56         128 $self->{property_line_numbers}{$key} = $ln;
370 56         83 $self->{properties}{$key} = $value;
371              
372 56         160 return 1;
373             }
374              
375             sub validate {
376 58     58 0 52 my $self=shift;
377 58         63 my $validator = $self->{validator};
378 58 100       100 if (defined $validator) {
379 11 100       9 &{$validator}(@_, $self) or $self->fail("invalid value '$_[1]' for '$_[0]'");
  11         20  
380             }
381             }
382              
383              
384             # line_number() - number for the last line read from the configuration file
385 3     3 0 21 sub line_number { shift->{last_line_number} }
386              
387              
388             # fail(error) - report errors in the configuration file while reading.
389             sub fail {
390 3     3 0 15 my ($self, $error) = @_;
391 3         13 die "$error at line ".$self->line_number()."\n";
392             }
393              
394             sub _sort_keys {
395 6     6   11 my $self = shift;
396 6         12 my $order = $self->{order};
397 6 100       16 if ($order eq 'keep') {
398 5         9 my $sk = $self->{property_line_numbers};
399 7     7   54 no warnings 'uninitialized';
  7         11  
  7         859  
400 5         19 return sort { $sk->{$a} <=> $sk->{$b} } @_;
  145         211  
401             }
402 1 50       3 if ($order eq 'alpha') {
403 1         8 return sort @_;
404             }
405 0         0 return @_;
406             }
407              
408             # _save() - Utility function that performs the actual saving of
409             # the properties file to a filehandle.
410             sub _save {
411 3     3   5 my ($self, $file) = @_;
412 3         8 _t_file $file;
413              
414 3         5 my $wrap;
415 3 50       10 if ($self->{wrap}) {
416 3         4 eval {
417 7     7   43 no warnings;
  7         10  
  7         6999  
418 3         2194 require Text::Wrap;
419 3         3706 $wrap=($Text::Wrap::VERSION >= 2001.0929);
420             };
421 3 50       44 unless ($wrap) {
422 0         0 carp "Text::Wrap module is to old, version 2001.0929 or newer required: long lines will not be wrapped"
423             }
424             }
425              
426 3 50       10 local($Text::Wrap::separator)=" \\\n" if $wrap;
427 3 50       8 local($Text::Wrap::unexpand)=undef if $wrap;
428 3 50       6 local($Text::Wrap::huge)='overflow' if $wrap;
429 3 50       14 local($Text::Wrap::break)=qr/(?
430              
431 3         5 foreach ($self->_sort_keys(keys %{$self->{properties}})) {
  3         21  
432 21         34585 my $key=$_;
433 21         93 my $value=$self->{properties}{$key};
434 21         40 escape_key $key;
435 21         31 escape_value $value;
436              
437 21 50       34 if ($wrap) {
438 21         113 $file->print( Text::Wrap::wrap( "",
439             " ",
440             sprintf( $self->{'format'},
441             $key, $value ) ),
442             "\n" );
443             }
444             else {
445 0         0 $file->print(sprintf( $self->{'format'}, $key, $value ), "\n")
446             }
447             }
448             }
449              
450              
451             # save() - Save the properties to a filehandle with the given header.
452             sub save {
453 3     3 1 375 my ($self, $file, $header) = @_;
454 3         10 _t_file($file);
455              
456 3 50       8 if (defined $header) {
457 3         8 $header=~s/\n/# \n/sg;
458 3         22 print $file "# $header\n#\n";
459             }
460 3         203 print $file '# ' . localtime() . "\n\n";
461 3         13 $self->_save( $file );
462             }
463              
464             sub saveToString {
465 0     0 1 0 my $self = shift;
466 0         0 my $str = '';
467 0 0       0 open my $fh, '>', \$str
468             or die "unable to open string ref as file";
469 0         0 $self->save($fh, @_);
470 0 0       0 close $fh
471             or die "unable to write to in memory file";
472 0         0 return $str;
473             }
474              
475             sub _split_to_tree {
476 0     0   0 my ($self, $tree, $re, $start) = @_;
477 0 0       0 if (defined $self->{defaults}) {
478 0         0 $self->{defaults}->_split_to_tree($tree, $re, $start);
479             }
480 0         0 for my $key (keys %{$self->{properties}}) {
  0         0  
481 0         0 my $ekey = $key;
482              
483 0 0       0 if (defined $start) {
484 0 0       0 $ekey =~ s/$start// or next;
485             }
486              
487 0         0 my @parts = split $re, $ekey;
488 0 0       0 @parts = '' unless @parts;
489 0         0 my $t = $tree;
490 0         0 while (@parts) {
491 0         0 my $part = shift @parts;
492 0         0 my $old = $t->{$part};
493              
494 0 0       0 if (@parts) {
495 0 0       0 if (defined $old) {
496 0 0       0 if (ref $old) {
497 0         0 $t = $old;
498             }
499             else {
500 0         0 $t = $t->{$part} = { '' => $old };
501             }
502             }
503             else {
504 0         0 $t = $t->{$part} = {};
505             }
506             }
507             else {
508 0         0 my $value = $self->{properties}{$key};
509 0 0       0 if (ref $old) {
510 0         0 $old->{''} = $value;
511             }
512             else {
513 0         0 $t->{$part} = $value;
514             }
515             }
516             }
517             }
518             }
519              
520             sub splitToTree {
521 0     0 1 0 my ($self, $re, $start) = @_;
522 0 0       0 $re = qr/\./ unless defined $re;
523 0 0       0 $re = qr/$re/ unless ref $re;
524 0 0       0 if (defined $start) {
525 0         0 $start = quotemeta $start;
526 0         0 $start = qr/^$start$re/
527             }
528 0         0 my $tree = {};
529 0         0 $self->_split_to_tree($tree, $re, $start);
530 0         0 $tree;
531             }
532              
533             sub _unsplit_from_tree {
534 0     0   0 my ($self, $method, $tree, $sep, @start) = @_;
535 0 0       0 $sep = '.' unless defined $sep;
536 0         0 my $ref = ref $tree;
537 0 0       0 if ($ref eq 'HASH') {
    0          
    0          
538 0         0 for my $key (keys %$tree) {
539 0 0       0 $self->_unsplit_from_tree($method, $tree->{$key}, $sep,
540             @start, ($key ne '' ? $key : ()))
541             }
542             }
543             elsif ($ref eq 'ARRAY') {
544 0         0 for my $key (0..$#$tree) {
545 0         0 $self->_unsplit_from_tree($method, $tree->[$key], $sep, @start, $key)
546             }
547             }
548             elsif ($ref) {
549 0         0 croak "unexpected object '$ref' found inside tree"
550             }
551             else {
552 0         0 $self->$method(join($sep, @start), $tree)
553             }
554             }
555              
556 0     0 1 0 sub setFromTree { shift->_unsplit_from_tree(setProperty => @_) }
557 0     0 1 0 sub changeFromTree { shift->_unsplit_from_tree(changeProperty => @_) }
558              
559             # store() - Synonym for save()
560             *store = \&save;
561              
562             # getProperty() - Return the value of a property key. Returns the default
563             # for that key (if there is one) if no value exists for that key.
564             sub getProperty {
565 67     67 1 11479 my $self = shift;
566 67         69 my $key = shift;
567 67         95 _t_key $key;
568              
569 67 50       134 if (exists $self->{properties}{$key}) {
    0          
570 67         211 return $self->{properties}{$key}
571             }
572             elsif (defined $self->{defaults}) {
573 0         0 return $self->{defaults}->getProperty($key, @_);
574             }
575 0         0 for (@_) {
576 0 0       0 return $_ if defined $_
577             }
578             undef
579 0         0 }
580              
581             sub requireProperty {
582 0     0 1 0 my $this = shift;
583 0         0 my $prop = $this->getProperty(@_);
584 0 0       0 defined $prop
585             or die "required property '$_[0]' not found on configuration file\n";
586 0         0 return $prop;
587             }
588              
589             sub _property_line_number {
590 0     0   0 my ($self, $key)=@_;
591 0         0 $self->{property_line_numbers}{$key}
592             }
593              
594              
595             # propertyName() - Returns an array of the keys of the Properties
596             sub propertyNames {
597 2     2 1 18 my $self = shift;
598 2         7 my %p = $self->_properties;
599 2         14 $self->_sort_keys(keys %p);
600             }
601              
602              
603             1;
604             __END__