File Coverage

blib/lib/Config/Properties.pm
Criterion Covered Total %
statement 210 318 66.0
branch 61 158 38.6
condition 17 42 40.4
subroutine 39 55 70.9
pod 19 32 59.3
total 346 605 57.1


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