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   109902 use strict;
  7         11  
  7         219  
4 7     7   25 use warnings;
  7         8  
  7         252  
5              
6             our $VERSION = '1.79';
7              
8 7     7   3442 use IO::Handle;
  7         33385  
  7         305  
9 7     7   36 use Carp;
  7         8  
  7         311  
10 7     7   26 use PerlIO qw();
  7         8  
  7         74  
11 7     7   2949 use Errno qw();
  7         6012  
  7         144  
12              
13             {
14 7     7   30 no warnings;
  7         7  
  7         15970  
15             sub _t_key ($) {
16 70     70   54 my $k=shift;
17 70 50 33     278 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   2 my $v=shift;
35 2 50 33     14 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       33 defined ($f) or
43             croak "invalid file '$f'";
44             }
45              
46             sub _t_order ($) {
47 9     9   13 my $o = shift;
48 9 50       67 $o =~ /^(?:keep|alpha|none)$/ or
49             croak "invalid order";
50             }
51              
52             sub _t_encoding ($) {
53 8     8   13 my $e = shift;
54 8 50       38 $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 218 my $class = shift;
66 8         10 my $defaults;
67 8 50       26 $defaults = shift if @_ & 1;
68 8         19 my %opts = @_;
69 8 50       37 $defaults = delete $opts{defaults} unless defined $defaults;
70 8         13 my $format = delete $opts{format};
71 8 50       25 $format = '%s=%s' unless defined $format;
72 8         12 my $wrap = delete $opts{wrap};
73 8 50       19 $wrap = 1 unless defined $wrap;
74 8         12 my $order = delete $opts{order};
75 8 50       20 $order = 'keep' unless defined $order;
76 8         27 _t_order($order);
77 8         12 my $file = delete $opts{file};
78 8   100     40 my $encoding = delete $opts{encoding} || 'latin1';
79 8         20 _t_encoding($encoding);
80 8         11 my $eol_re = delete $opts{eol_re};
81 8 50       47 $eol_re = qr/\r\n|\n|\r/ unless defined $eol_re;
82 8         217 my $line_re = qr/^(.*?)(?:$eol_re)/s;
83              
84 8 50       33 %opts and croak "invalid option(s) '" . join("', '", keys %opts) . "'";
85              
86 8 50       111 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         98 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         22 bless $self, $class;
110              
111 8 50       24 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         36 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 391 my ($self, $key, $recurse) = @_;
135 1         5 _t_key $key;
136              
137 1 50       3 if (exists $self->{properties}{$key}) {
138 1         3 delete $self->{properties}{$key};
139 1         1 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 6 my ($self, $key, $value)=@_;
149 2         3 _t_key $key;
150 2         3 _t_value $value;
151              
152 2 50       3 defined(wantarray) and
153             carp "warning: setProperty doesn't return the old value anymore";
154              
155 2   66     6 $self->{property_line_numbers}{$key} ||= ++$self->{last_line_number};
156 2         3 $self->{properties}{$key} = $value;
157             }
158              
159             sub _properties {
160 4     4   3 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         4 return %{ $self->{properties} }
  4         54  
166             }
167              
168             # properties() - return a flated hash with all the properties
169             sub properties {
170 1     1 1 3 my $self = shift;
171 1         3 my %p = $self->_properties;
172 1         7 map { $_ => $p{$_} } $self->_sort_keys(keys %p);
  13         22  
173             }
174              
175              
176              
177             # getProperties() - Return a hashref of all of the properties
178 1     1 1 5 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 34 my ($self, $validator) = @_;
210 2         6 _t_validator $validator;
211 2         9 $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 1 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 2063 my $self = shift;
237 1 50       6 $self->setOrder(@_) if @_;
238 1         1 $self->{order};
239             }
240              
241             # load() - Load the properties from a filehandle
242             sub load {
243 8     8 1 40 my ($self, $file) = @_;
244 8         20 _t_file $file;
245              
246             # check whether it is a real file handle
247 8         8 my $fn = do {
248 8         9 local $@;
249 8         9 eval { fileno($file) }
  8         41  
250             };
251 8 50 33     36 if (defined $fn and $fn >0) {
252 8 100       93 unless (grep /^(?:encoding|utf8)\b/, PerlIO::get_layers($file)) {
253 7 50   6   196 binmode $file, ":encoding($self->{encoding})"
  6         42  
  6         5  
  6         33  
254             or croak "Unable to set file encoding layer: $!";
255             }
256             }
257 8         49014 $self->{properties} = {};
258 8         22 $self->{property_line_numbers} = {};
259 8         68 my $ln = $file->input_line_number;
260 8 50       173 $self->{last_line_number} = ($ln > 0 ? $ln : 0);
261 8         16 $self->{buffer_in} = '';
262 8         26 1 while $self->process_line($file);
263 5         18 $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 45 $_[0]=~s{([\t\n\r\\"' =:])}{
278 14   66     39 "\\".($esc{$1}||$1) }ge;
279 21         27 $_[0]=~s{([^\x20-\x7e])}{sprintf "\\u%04x", ord $1}ge;
  0         0  
280 21         25 $_[0]=~s/^ /\\ /;
281 21         30 $_[0]=~s/^([#!])/\\$1/;
282 21         29 $_[0]=~s/(?
283             }
284              
285             sub escape_value {
286 21     21 0 41 $_[0]=~s{([\t\n\r\\])}{
287 13   66     37 "\\".($esc{$1}||$1) }ge;
288 21         34 $_[0]=~s{([^\x20-\x7e])}{sprintf "\\u%04x", ord $1}ge;
  0         0  
289 21         31 $_[0]=~s/^ /\\ /;
290             }
291              
292             sub unescape {
293 116     116 0 256 $_[0]=~s/\\([tnr\\"' =:#!])|\\u([\da-fA-F]{4})/
294 155 100 66     587 defined $1 ? $unesc{$1}||$1 : chr hex $2 /ge;
295             }
296              
297             sub read_line {
298 124     124 0 90 my ($self, $file) = @_;
299 124         115 my $bin = \$self->{buffer_in};
300 124         121 my $line_re = $self->{line_re};
301 124         77 while (1) {
302 132 100       886 if ($$bin =~ s/$line_re//) {
303 119         108 $self->{last_line_number}++;
304 119         244 return $1;
305             }
306             else {
307 13         254 my $bytes = read($file, $$bin, 8192, length $$bin);
308 13 50 0     82 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       15 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 94 my ($self, $file) = @_;
332 100         116 my $line = $self->read_line($file);
333 100 100       165 defined $line or return undef;
334              
335             # remove utf8 byte order mark
336 95         72 my $ln = $self->{last_line_number};
337 95 100       137 $line =~ s/$bomre// if $ln < 2;
338              
339             # ignore comments
340 95 100       265 $line =~ /^\s*(\#|\!|$)/ and return 1;
341              
342             # handle continuation lines
343 59         40 my @lines;
344 59   66     210 while ($line =~ /(\\+)$/ and length($1) & 1) {
345 24         77 $line =~ s/\\$//;
346 24         34 push @lines, $line;
347 24         39 $line = $self->read_line($file);
348 24 50       46 $line = '' unless defined $line;
349 24         136 $line =~ s/^\s+//;
350             }
351 59 100       96 $line = join('', @lines, $line) if @lines;
352              
353 59 100       395 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         84 unescape $key;
365 58         72 unescape $value;
366              
367 58         95 $self->validate($key, $value);
368              
369 56         126 $self->{property_line_numbers}{$key} = $ln;
370 56         75 $self->{properties}{$key} = $value;
371              
372 56         186 return 1;
373             }
374              
375             sub validate {
376 58     58 0 51 my $self=shift;
377 58         61 my $validator = $self->{validator};
378 58 100       101 if (defined $validator) {
379 11 100       10 &{$validator}(@_, $self) or $self->fail("invalid value '$_[1]' for '$_[0]'");
  11         19  
380             }
381             }
382              
383              
384             # line_number() - number for the last line read from the configuration file
385 3     3 0 23 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         12 die "$error at line ".$self->line_number()."\n";
392             }
393              
394             sub _sort_keys {
395 6     6   13 my $self = shift;
396 6         9 my $order = $self->{order};
397 6 100       17 if ($order eq 'keep') {
398 5         6 my $sk = $self->{property_line_numbers};
399 7     7   45 no warnings 'uninitialized';
  7         9  
  7         808  
400 5         24 return sort { $sk->{$a} <=> $sk->{$b} } @_;
  140         157  
401             }
402 1 50       4 if ($order eq 'alpha') {
403 1         10 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         6 _t_file $file;
413              
414 3         3 my $wrap;
415 3 50       11 if ($self->{wrap}) {
416 3         4 eval {
417 7     7   33 no warnings;
  7         9  
  7         5898  
418 3         888 require Text::Wrap;
419 3         3754 $wrap=($Text::Wrap::VERSION >= 2001.0929);
420             };
421 3 50       10 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       8 local($Text::Wrap::separator)=" \\\n" if $wrap;
427 3 50       9 local($Text::Wrap::unexpand)=undef if $wrap;
428 3 50       6 local($Text::Wrap::huge)='overflow' if $wrap;
429 3 50       15 local($Text::Wrap::break)=qr/(?
430              
431 3         3 foreach ($self->_sort_keys(keys %{$self->{properties}})) {
  3         24  
432 21         31498 my $key=$_;
433 21         45 my $value=$self->{properties}{$key};
434 21         29 escape_key $key;
435 21         28 escape_value $value;
436              
437 21 50       28 if ($wrap) {
438 21         89 $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 471 my ($self, $file, $header) = @_;
454 3         6 _t_file($file);
455              
456 3 50       7 if (defined $header) {
457 3         7 $header=~s/\n/# \n/sg;
458 3         18 print $file "# $header\n#\n";
459             }
460 3         197 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 9725 my $self = shift;
566 67         58 my $key = shift;
567 67         87 _t_key $key;
568              
569 67 50       160 if (exists $self->{properties}{$key}) {
    0          
570 67         186 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 19 my $self = shift;
598 2         4 my %p = $self->_properties;
599 2         9 $self->_sort_keys(keys %p);
600             }
601              
602              
603             1;
604             __END__