File Coverage

blib/lib/ChordPro/Config/Properties.pm
Criterion Covered Total %
statement 253 331 76.4
branch 111 164 67.6
condition 31 60 51.6
subroutine 26 40 65.0
pod n/a
total 421 595 70.7


line stmt bran cond sub pod time code
1             #! perl
2              
3             package Data::Properties;
4              
5 13     13   1410800 use strict;
  13         165  
  13         394  
6 13     13   75 use warnings;
  13         35  
  13         726  
7              
8             # Author : Johan Vromans
9             # Created On : Mon Mar 4 11:51:54 2002
10             # Last Modified By: Johan Vromans
11             # Last Modified On: Mon Dec 6 10:53:33 2021
12             # Update Count : 557
13             # Status : Unknown, Use with caution!
14              
15             =head1 NAME
16              
17             Data::Properties -- Flexible properties handling
18              
19             =head1 SUMMARY
20              
21             use Data::Properties;
22              
23             my $cfg = new Data::Properties;
24              
25             # Preset a property.
26             $cfg->set_property("config.version", "1.23");
27              
28             # Parse a properties file.
29             $cfg->parse_file("config.prp");
30              
31             # Get a property value
32             $version = $cfg->get_property("config.version");
33             # Same, but with a default value.
34             $version = $cfg->get_property("config.version", "1.23");
35              
36             # Get the list of subkeys for a property, and process them.
37             my $aref = $cfg->get_property_keys("item.list");
38             foreach my $item ( @$aref ) {
39             if ( $cfg->get_property("item.list.$item") ) {
40             ....
41             }
42             }
43              
44             =head1 DESCRIPTION
45              
46             The property mechanism is modelled after the Java implementation of
47             properties.
48              
49             In general, a property is a string value that is associated with a
50             key. A key is a series of names (identifiers) separated with periods.
51             Names are treated case insensitive. Unlike in Java, the properties are
52             really hierarchically organized. This means that for a given property
53             you can fetch the list of its subkeys, and so on. Moreover, the list
54             of subkeys is returned in the order the properties were defined.
55              
56             Data::Properties can also be used to define data structures, just like
57             JSON but with much less quotes.
58              
59             Property lookup can use a preset property context. If a context I
60             has been set using C')>,
61             C will first try C<'I.foo.bar'> and
62             then C<'foo.bar'>. C (note the leading
63             period) will only try C<'I.foo.bar'> and raise an exception if
64             no context was set.
65              
66             Design goals:
67              
68             =over
69              
70             =item *
71              
72             properties must be hierarchical of unlimited depth;
73              
74             =item *
75              
76             manual editing of the property files (hence unambiguous syntax and lay out);
77              
78             =item *
79              
80             it must be possible to locate all subkeys of a property in the
81             order they appear in the property file(s);
82              
83             =item *
84              
85             lightweight so shell scripts can use it to query properties.
86              
87             =back
88              
89             =cut
90              
91             our $VERSION = "2.001";
92              
93 13     13   6671 use Text::ParseWords qw(parse_line);
  13         19522  
  13         783  
94 13     13   6658 use File::LoadLines;
  13         176558  
  13         837  
95 13     13   6635 use String::Interpolate::Named;
  13         39866  
  13         781  
96 13     13   100 use Carp;
  13         30  
  13         4860  
97              
98             my $DEBUG = 1;
99              
100             ################ Constructors ################
101              
102             =over
103              
104             =item new
105              
106             I is the standard constructor. I doesn't require any
107             arguments, but you can pass it a list of initial properties to store
108             in the resultant properties object.
109              
110             =cut
111              
112             sub new {
113 24 50   24   1754 if ( ref($_[1]) ) {
114             # IX/Data-Properties.
115 0         0 croak("API Error -- Incompatible Data::Properties version");
116             }
117 24         83 unshift(@_, 0);
118 24         72 &_constructor;
119             }
120              
121             =item clone
122              
123             I is like I, but it takes an existing properties object as
124             its invocant and returns a new object with the contents copied.
125              
126             B This is not a deep copy, so take care.
127              
128             =cut
129              
130             sub clone {
131 0     0   0 unshift(@_, 1);
132 0         0 &_constructor;
133             }
134              
135             # Internal construction helper.
136             sub _constructor {
137             # Get caller and initial attributes.
138 24     24   79 my ($cloning, $invocant, %atts) = @_;
139              
140             # If the invocant is an object, get its class.
141 24   33     132 my $class = ref($invocant) || $invocant;
142              
143             # Initialize and bless the new object.
144 24         70 my $self = bless({}, $class);
145              
146             # Default path.
147 24         127 $self->{_path} = [ "." ];
148              
149             # Initialize.
150 24 50       105 $self->{_props} = $cloning ? {%{$invocant->{_props}}} : {};
  0         0  
151              
152             # Fill in initial attribute values.
153 24         328 while ( my ($k, $v) = each(%atts) ) {
154 0 0       0 if ( $k eq "_context" ) {
    0          
    0          
    0          
155 0         0 $self->{_context} = $v;
156             }
157             elsif ( $k eq "_debug" ) {
158 0         0 $self->{_debug} = 1;
159             }
160             elsif ( $k eq "_noinc" ) {
161 0         0 $self->{_noinc} = 1;
162             }
163             elsif ( $k eq "_raw" ) {
164 0         0 $self->{_raw} = 1;
165             }
166             else {
167 0         0 $self->set_property($k, $v);
168             }
169             }
170 24         65 $self->{_in_context} = undef;
171              
172             # Return.
173 24         134 $self;
174             }
175              
176             ################ Methods ################
177              
178             =item parse_file I [ , I ]
179              
180             I reads a properties file and adds the contents to the
181             properties object.
182              
183             I is the name of the properties file. This file is searched in
184             all elements of the current search path (see L">) unless
185             the name starts with a slash.
186              
187             I can be used to designate an initial context where all
188             properties from the file will be subkeys of.
189              
190             For the detailed format of properties files see L.
191              
192             Reading the file is handled by L. See its
193             documentation for more power.
194              
195             =cut
196              
197             sub parse_file {
198 0     0   0 my ($self, $file, $context) = @_;
199 0         0 $self->_parse_file_internal( $file, $context);
200              
201 0 0       0 if ( $self->{_debug} ) {
202 13     13   9412 use Data::Dumper;
  13         85468  
  13         1894  
203 0         0 $Data::Dumper::Indent = 2;
204 0         0 warn(Data::Dumper->Dump([$self->{_props}],[qw(properties)]), "\n");
205             }
206 0         0 $self;
207             }
208              
209             =item parse_lines I [ , I [ , I ] ]
210              
211             As I, but processes an array of lines.
212              
213             I is used for diagnostic purposes only.
214              
215             I can be used to designate an initial context where all
216             properties from the file will be subkeys of.
217              
218             =cut
219              
220             sub parse_lines {
221 27     27   3150 my ($self, $lines, $file, $context) = @_;
222 27         114 $self->_parse_lines_internal( $lines, $file, $context);
223              
224 27 50       112 if ( $self->{_debug} ) {
225 13     13   112 use Data::Dumper;
  13         29  
  13         56790  
226 0         0 $Data::Dumper::Indent = 2;
227 0         0 warn(Data::Dumper->Dump([$self->{_props}],[qw(properties)]), "\n");
228             }
229 27         110 $self;
230             }
231              
232             # Catch some calls that are not in this version of Data::Properties.
233             sub load {
234 0     0   0 croak("API Error -- Incompatible Data::Properties version");
235             }
236             sub property_names {
237 0     0   0 croak("API Error -- Incompatible Data::Properties version");
238             }
239             sub store {
240 0     0   0 croak("API Error -- Incompatible Data::Properties version");
241             }
242              
243             =item set_path I
244              
245             Sets a search path for file lookup.
246              
247             I must be reference to an array of paths.
248              
249             Default I is C<[ '.' ]> (current directory).
250              
251             =item get_path
252              
253             Gets the current search path for file lookup.
254              
255             =cut
256              
257             sub set_path {
258 0     0   0 my ( $self ) = shift;
259 0         0 my $path = shift;
260 0 0 0     0 if ( @_ > 0 || !UNIVERSAL::isa($path,'ARRAY') ) {
261 0         0 $path = [ $path, @_ ];
262             }
263 0         0 $self->{_path} = $path;
264             }
265              
266             sub get_path {
267 0     0   0 my ( $self ) = @_;
268 0         0 $self->{_path};
269             }
270              
271             # internal
272              
273             sub _parse_file_internal {
274              
275 0     0   0 my ($self, $file, $context) = @_;
276 0         0 my $did = 0;
277 0         0 my $searchpath = $self->{_path};
278 0 0       0 $searchpath = [ '' ] unless $searchpath;
279              
280 0         0 foreach ( @$searchpath ) {
281 0         0 my $path = $_;
282 0 0       0 $path .= "/" unless $path eq '';
283              
284             # Fetch one.
285 0         0 my $cfg = $file;
286 0 0       0 $cfg = $path . $file unless $file =~ m:^/:;
287 0 0       0 next unless -e $cfg;
288              
289 0         0 my $opt = { strip => qr/[ \t]*\\(?:\r\n|\n|\r)[ \t]*/ };
290 0         0 my $lines = loadlines( $cfg, $opt );
291 0         0 $self->parse_lines( $lines, $cfg, $context );
292 0         0 $did++;
293              
294             # We read a file, no need to proceed searching.
295 0         0 last;
296             }
297              
298             # Sanity checks.
299 0 0       0 croak("No properties $file in " . join(":", @$searchpath)) unless $did;
300             }
301              
302             # internal
303              
304             sub _value {
305 1040     1040   2915 my ( $self, $value, $ctx, $noexpand ) = @_;
306              
307             # Single-quoted string.
308 1040 100       2815 if ( $value =~ /^'(.*)'\s*$/ ) {
309 221         448 $value = $1;
310 221         390 $value =~ s/\\\\/\x{fdd0}/g;
311 221         338 $value =~ s/\\'/'/g;
312 221         335 $value =~ s/\x{fdd0}/\\/g;
313 221         443 return $value;
314             }
315              
316 819 50 33     1928 if ( $self->{_raw} && $value =~ /^(null|false|true)$/ ) {
317 0         0 return $value;
318             }
319              
320 819 100   2   1916 if ( lc($value) eq "null" ) {
  2         17  
  2         13  
  2         34  
321 15         33 return;
322             }
323 804 100       54741 if ( lc($value) eq "true" ) {
324 4         7 return 1;
325             }
326 800 100       1601 if ( lc($value) eq "false" ) {
327 23         43 return 0;
328             }
329              
330 777 100       1906 if ( $value =~ /^"(.*)"\s*$/ ) {
331 167         372 $value = $1;
332 167         335 $value =~ s/\\\\/\x{fdd0}/g;
333 167         287 $value =~ s/\\"/"/g;
334 167         256 $value =~ s/\\n/\n/g;
335 167         262 $value =~ s/\\t/\t/g;
336 167         277 $value =~ s/\\([0-7]{1,3})/sprintf("%c",oct($1))/ge;
  6         34  
337 167         254 $value =~ s/\\x([0-9a-f][0-9a-f]?)/sprintf("%c",hex($1))/ge;
  1         6  
338 167         258 $value =~ s/\\x\{([0-9a-f]+)\}/sprintf("%c",hex($1))/ge;
  1         6  
339 167         278 $value =~ s/\x{fdd0}/\\/g;
340 167 100       323 return $value if $noexpand;
341 165         361 return $self->expand($value, $ctx);
342             }
343              
344 610 100       1604 return $value if $noexpand;
345 108         307 $self->expand($value, $ctx);
346             }
347              
348             sub _parse_lines_internal {
349              
350 27     27   73 my ( $self, $lines, $filename, $context ) = @_;
351              
352 27 100       105 my @stack = $context ? ( [$context, undef] ) : ();
353 27         117 my $keypat = qr/[-\w.]+|"[^"]*"|'[^']*'/;
354              
355             # Process its contents.
356 27         98 my $lineno = 0;
357 27         82 while ( @$lines ) {
358 873         1267 $lineno++;
359 873         1519 $_ = shift(@$lines);
360              
361             #### Discard empty lines and comment lines/
362 873 100       2358 next if /^\s*#/;
363 647 100       1970 next unless /\S/;
364              
365             #### Trim.
366 611         1779 s/^\s+//;
367 611         1532 s/\s+$//;
368              
369             #### Controls
370             # include filename (only if at the line start, and not followed by =.
371 611 50 33     1427 if ( /^include\s+((?![=:]).+)/ && !$self->{_noinc} ) {
372 0         0 my $value = $self->_value( $1, $stack[0] );
373 0         0 $self->_parse_file_internal($value, $stack[0]);
374 0         0 next;
375             }
376              
377             #### Settings
378             # key = value
379             # key {
380             # key [
381             # value
382             # ]
383             # }
384              
385             # foo.bar {
386             # foo.bar [
387             # Push a new context.
388 611 100       4104 if ( /^($keypat)\s*([{])$/ ) {
389 48         150 my $c = $self->_value( $1, undef, "noexpand" );
390 48 50       156 my $i = $2 eq '[' ? 0 : undef;
391 48 100       151 @stack = ( [ $c, $i ] ), next unless @stack;
392 35         146 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
393 35         97 next;
394             }
395 563 100       2940 if ( /^($keypat)\s*[:=]\s*([[])$/ ) {
396 9         43 my $c = $self->_value( $1, undef, "noexpand" );
397 9 50       36 my $i = $2 eq '[' ? 0 : undef;
398 9 100       30 @stack = ( [ $c, $i ] ), next unless @stack;
399 8         31 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
400 8         23 next;
401             }
402              
403             # foo.bar = [ val val ]
404             # foo.bar = [ val
405             # val ]
406             # foo.bar = [ val val
407             # ]
408             # BUT NOT
409             # foo.bar = [
410             # val val ]
411             # Create an array
412             # Add lines, if necessary.
413 554   100     3137 while ( /^($keypat)\s*[=:]\s*\[(.+)$/ && $2 !~ /\]\s*$/ && @$lines ) {
      66        
414 2         9 $_ .= " " . shift(@$lines);
415 2         16 $lineno++;
416             }
417 554 100       2842 if ( /^($keypat)\s*[:=]\s*\[(.*)\]$/ ) {
418 12         38 my $prop = $self->_value( $1, undef, "noexpand" );
419 12 100       64 $prop = $stack[0]->[0] . "." . $prop if @stack;
420 12         35 my $v = $2;
421 12         47 $v =~ s/^\s+//;
422 12         55 $v =~ s/\s+$//;
423 12         27 my $ix = 0;
424 12         43 for my $value ( parse_line( '\s+', 1, $v ) ) {
425 24         971 $value = $self->_value( $value, $stack[0] );
426 24         1503 $self->set_property( $prop . "." . $ix++, $value );
427             }
428 12 100       103 $self->set_property( $prop, undef ) unless $ix;
429 12         40 next;
430             }
431              
432 542 50 66     1358 if ( /^\s*\[(.*)\]$/ && @stack && $stack[0][1] ) {
      66        
433 10         41 my $prop = $stack[0][0] . "." . $stack[0][1]++;
434 10         28 my $v = $1;
435 10         23 $v =~ s/^\s+//;
436 10         27 $v =~ s/\s+$//;
437 10         21 my $ix = 0;
438 10         42 for my $value ( parse_line( '\s+', 1, $v ) ) {
439 32         1223 $value = $self->_value( $value, $stack[0] );
440 32         2250 $self->set_property( $prop . "." . $ix++, $value );
441             }
442 10         34 next;
443             }
444              
445             # {
446             # [
447             # Push a new context while building an array.
448 532 100 100     1653 if ( @stack && defined($stack[0]->[1]) # building array
      100        
449             && /^([{\[])$/ ) {
450 7 50       39 my $i = $1 eq '[' ? 0 : undef;
451 7         51 unshift( @stack, [ $stack[0]->[0] . "." . $stack[0]->[1]++, $i ] );
452 7         48 next;
453             }
454              
455             # }
456             # ]
457             # Pop context.
458 525 100       1330 if ( /^([}\]])$/ ) {
459 64 50 33     326 die("stack underflow at line $lineno")
    50          
460             unless @stack
461             && ( $1 eq defined($stack[0]->[1]) ? ']' : '}' );
462 64         120 shift(@stack);
463 64         172 next;
464             }
465              
466             # foo.bar = blech
467             # foo.bar = "blech"
468             # foo.bar = 'blech'
469             # Simple assignment.
470             # The value is expanded unless single quotes are used.
471 461 100       3156 if ( /^($keypat)\s*[=:]\s*(.*)/ ) {
472 438 50       1206 die("Brace is illegal as a value (use quotes to bypass)\n")
473             if $2 eq '{';
474 438         1032 my $prop = $self->_value( $1, undef, "noexpand" );
475 438         1023 my $value = $self->_value( $2, $stack[0] );
476              
477             # Make a full name.
478 438 100       15821 $prop = $stack[0]->[0] . "." . $prop if @stack;
479              
480             # Set the property.
481 438         1264 $self->set_property($prop, $value);
482              
483 438         1350 next;
484             }
485              
486             # value(s) (while building an array)
487 23 50 33     97 if ( @stack && defined($stack[0]->[1]) ) {
488              
489 23         76 for my $value ( parse_line( '\s+', 1, $_ ) ) {
490             # Make a full name.
491 39         1669 my $prop = $stack[0]->[0] . "." . $stack[0]->[1]++;
492              
493 39         100 $value = $self->_value( $value, $stack[0] );
494              
495             # Set the property.
496 39         2713 $self->set_property($prop, $value);
497             }
498 23         69 next;
499             }
500              
501             # Error.
502 0         0 croak("?line $lineno: $_\n");
503             }
504              
505             # Sanity checks.
506 27 100       171 croak("Unfinished properties $filename")
    50          
507             if @stack != ($context ? 1 : 0);
508             }
509              
510             =item get_property I [ , I ]
511              
512             Get the value for a given property I.
513              
514             If a context I has been set using C')>,
515             C will first try C<'I.foo.bar'> and then
516             C<'foo.bar'>. C (note the leading period)
517             will only try C<'I.foo.bar'> and raise an exception if no context
518             was set.
519              
520             If no value can be found, I is used.
521              
522             In either case, the resultant value is examined for references to
523             other properties or environment variables. See L below.
524              
525             =cut
526              
527             sub get_property {
528 4     4   12 my ($self) = shift;
529 4         13 $self->expand($self->get_property_noexpand(@_));
530             }
531              
532             =item get_property_noexpand I [ , I ]
533              
534             This is like I, but does not do any expansion.
535              
536             =cut
537              
538             sub get_property_noexpand {
539 4     4   11 my ($self, $prop, $default) = @_;
540 4         8 $prop = lc($prop);
541 4         11 my $ctx = $self->{_context};
542 4         6 my $context_only;
543 4 50 33     18 if ( ($context_only = $prop =~ s/^\.//) && !$ctx ) {
544 0         0 croak("get_property: no context for $prop");
545             }
546 4 50       12 if ( defined($ctx) ) {
547 0 0       0 $ctx .= "." if $ctx;
548 0 0       0 if ( exists($self->{_props}->{$ctx.$prop}) ) {
549 0         0 $self->{_in_context} = $ctx;
550 0         0 return $self->{_props}->{$ctx.$prop};
551             }
552             }
553 4 50       11 if ( $context_only ) {
554 0         0 $self->{_in_context} = undef;
555 0         0 return $default;
556             }
557 4 50 33     21 if ( defined($self->{_props}->{$prop}) && $self->{_props}->{$prop} ne "") {
558 4         10 $self->{_in_context} = "";
559 4         16 return $self->{_props}->{$prop};
560             }
561 0         0 $self->{_in_context} = undef;
562 0         0 $default;
563             }
564              
565             =item gps I [ , I ]
566              
567             This is like I, but raises an exception if no value
568             could be established.
569              
570             This is probably the best and safest method to use.
571              
572             =cut
573              
574             sub gps {
575 3     3   13 my $nargs = @_;
576 3         10 my ($self, $prop, $default) = @_;
577 3         9 my $ret = $self->get_property($prop, $default);
578 3 50 33     212 croak("gps: no value for $prop")
579             unless defined($ret) || $nargs == 3;
580 3         16 $ret;
581             }
582              
583             =item get_property_keys I
584              
585             Returns an array reference with the names of the (sub)keys for the
586             given property. The names are unqualified, e.g., when properties
587             C and C exist, C would
588             return C<['bar', 'blech']>.
589              
590             =cut
591              
592             sub get_property_keys {
593 0     0   0 my ($self, $prop) = @_;
594 0 0       0 $prop .= '.' if $prop;
595 0         0 $prop .= '@';
596 0         0 $self->get_property_noexpand($prop);
597             }
598              
599             =item expand I [ , I ]
600              
601             Perform the expansion as described with I.
602              
603             =cut
604              
605             sub expand {
606 762     762   1548 my ($self, $ret, $ctx) = (@_, "");
607 762 100       1501 return $ret unless $ret;
608 671 50 0     1303 warn("expand($ret,",$ctx//'',")\n") if $self->{_debug};
609 671         978 my $props = $self->{_props};
610 671         1147 $ret =~ s:^~(/|$):$ENV{HOME}$1:g;
611 671         1246 return $self->_interpolate( $ret, $ctx );
612             }
613              
614             # internal
615              
616             sub _interpolate {
617 671     671   1233 my ( $self, $tpl, $ctx ) = @_;
618 671 100       1354 ( $ctx, my $ix ) = @$ctx if $ctx;
619 671         1068 my $props = $self->{_props};
620             return interpolate( { activator => '$',
621             keypattern => qr/\.?\w+[-_\w.]*\??(?::.*)?/,
622             args => sub {
623 14     14   1385 my $key = shift;
624 14 50 0     39 warn("_inter($key,",$ctx//'',")\n") if $self->{_debug};
625             # Establish the value for this key.
626 14         24 my $val = '';
627              
628 14         23 my $default = '';
629 14 100       53 ( $key, $default ) = ( $1, $2 )
630             if $key =~ /^(.*?):(.*)/;
631 14         48 my $checkdef = $key =~ s/\?$//;
632              
633             # If an environment variable exists, take its value.
634 14 100       49 if ( exists($ENV{$key}) ) {
635 1         14 $val = $ENV{$key};
636 1 50       5 $val = defined($val) if $checkdef;
637             }
638             else {
639 13         22 my $orig = $key;
640 13 100       37 $key = $ctx.$key if ord($key) == ord('.');
641             # For properties, the value should be non-empty.
642 13 100 100     93 if ( $checkdef ) {
    100          
643 2         7 $val = defined($props->{lc($key)});
644             }
645             elsif ( defined($props->{lc($key)}) && $props->{lc($key)} ne "" ) {
646 6         19 $val = $props->{lc($key)};
647             }
648             else {
649 5         11 $val = $default;
650             }
651             }
652 14         40 return $val;
653             } },
654 671         5055 $tpl );
655             }
656              
657             =item set_property I, I
658              
659             Set the property to the given value.
660              
661             =cut
662              
663             sub set_property {
664 537     537   1119 my ($self, $prop, $value) = @_;
665 537         925 my $props = $self->{_props};
666 537         1959 $props->{lc($prop)} = $value;
667 537         1803 my @prop = split(/\./, $prop, -1);
668 537         1215 while ( @prop ) {
669 1660         2777 my $last = pop(@prop);
670 1660         3621 my $p = lc(join(".", @prop, '@'));
671 1660 100       3445 if ( exists($props->{$p}) ) {
672 510         1932 push(@{$props->{$p}}, $last)
673 1466 100       2137 unless index(join("\0","",@{$props->{$p}},""),
  1466         7417  
674             "\0".$last."\0") >= 0;
675             }
676             else {
677 194         839 $props->{$p} = [ $last ];
678             }
679             }
680             }
681              
682             =item set_properties I => I, ...
683              
684             Add a hash (key/value pairs) of properties to the set of properties.
685              
686             =cut
687              
688             sub set_properties {
689 0     0   0 my ($self, %props) = @_;
690 0         0 foreach ( keys(%props) ) {
691 0         0 $self->set_property($_, $props{$_});
692             }
693             }
694              
695             =item set_context I
696              
697             Set the search context. Without argument, clears the current context.
698              
699             =cut
700              
701             sub set_context {
702 0     0   0 my ($self, $context) = @_;
703 0         0 $self->{_context} = lc($context);
704 0         0 $self->{_in_context} = undef;
705 0         0 $self;
706             }
707              
708             =item get_context
709              
710             Get the current search context.
711              
712             =cut
713              
714             sub get_context {
715 0     0   0 my ($self) = @_;
716 0         0 $self->{_context};
717             }
718              
719             =item result_in_context
720              
721             Get the context status of the last search.
722              
723             Empty means it was found out of context, a string indicates the
724             context in which the result was found, and undef indicates search
725             failure.
726              
727             =cut
728              
729             sub result_in_context {
730 0     0   0 my ($self) = @_;
731 0         0 $self->{_in_context};
732             }
733              
734             =item data [ I ]
735              
736             Produces a Perl data structure created from all the properties from a
737             given point in the hierarchy.
738              
739             Note that since Perl hashes do not have an ordering, this information
740             will get lost. Also, properties can not have both a value and a substructure.
741              
742             =cut
743              
744             sub data {
745 13     13   160 my ($self, $start) = ( @_, '' );
746 13         38 my $ret = $self->_data_internal($start);
747 13         151 $ret;
748             }
749              
750             sub _data_internal {
751 672     672   1220 my ( $self, $orig ) = @_;
752 672   50     1353 my $cur = $orig // '';
753 672 100       1722 $cur .= "." if $cur ne '';
754 672         1110 my $all = $cur;
755 672         1015 $all .= '@';
756 672 100       1936 if ( my $res = $self->{_props}->{lc($all)} ) {
757 175 100       324 if ( _check_array($res) ) {
758 62         113 my $ret = [];
759 62         108 foreach my $prop ( @$res ) {
760 238         601 $ret->[$prop] = $self->_data_internal($cur.$prop);
761             }
762 62         203 return $ret;
763             }
764             else {
765 113         223 my $ret = {};
766 113         207 foreach my $prop ( @$res ) {
767 421         1080 $ret->{$prop} = $self->_data_internal($cur.$prop);
768             }
769 113         376 return $ret;
770             }
771             }
772             else {
773 497         1315 my $val = $self->{_props}->{lc($orig)};
774 497 100       1085 $val = $self->expand($val) if defined $val;
775 497         28360 return $val;
776             }
777             }
778              
779             sub _check_array {
780 175     175   273 my ( $i ) = @_;
781 175         632 my @i = @$i;
782 175 100       900 return unless "@i" =~ /^[\d ]+$/; # quick
783 62         129 my $ref = 0;
784 62         117 for ( @i) {
785 238 50       441 return unless $_ eq "$ref";
786 238         368 $ref++;
787             }
788 62         148 return 1; # success!
789             }
790              
791             =item dump [ I [ , I ] ]
792              
793             Produces a listing of all properties from a given point in the
794             hierarchy and write it to the I.
795              
796             Without I, returns a string.
797              
798             In general, I should be UTF-8 capable.
799              
800             =item dumpx [ I [ , I ] ]
801              
802             Like dump, but dumps with all values expanded.
803              
804             =cut
805              
806             my $dump_expanded;
807              
808             sub dump {
809 12     12   125 my ($self, $start, $fh) = ( @_, '' );
810 12         44 my $ret = $self->_dump_internal($start);
811 12 50       47 print $fh $ret if $fh;
812 12         76 $ret;
813             }
814              
815             sub dumpx {
816 0     0   0 my ($self, $start, $fh) = ( @_, '' );
817 0         0 $dump_expanded = 1;
818 0         0 my $ret = $self->dump( $start, $fh );
819 0         0 $dump_expanded = 0;
820 0         0 $ret;
821             }
822              
823             # internal
824              
825             sub _dump_internal {
826 347     347   605 my ($self, $cur) = @_;
827 347 100       734 $cur .= "." if $cur;
828 347         548 my $all = $cur;
829 347         555 $all .= '@';
830 347         509 my $ret = "";
831 347 100       927 if ( my $res = $self->{_props}->{lc($all)} ) {
832 90 100       385 $ret .= "# $all = @$res\n" if @$res > 1;
833 90         177 foreach my $prop ( @$res ) {
834 335         880 my $t = $self->_dump_internal($cur.$prop);
835 335 100 66     1089 $ret .= $t if defined($t) && $t ne '';
836 335         910 my $val = $self->{_props}->{lc($cur.$prop)};
837 335 50       591 $val = $self->expand($val) if $dump_expanded;
838 335 100       780 if ( !defined $val ) {
    100          
839 89 100 66     362 $ret .= "$cur$prop = null\n"
840             unless defined($t) && $t ne '';
841             }
842             elsif ( $val =~ /[\n\t]/ ) {
843 1         14 $val =~ s/(["\\])/\\$1/g;
844 1         6 $val =~ s/\n/\\n/g;
845 1         5 $val =~ s/\t/\\t/g;
846 1         5 $ret .= "$cur$prop = \"$val\"\n";
847             }
848             else {
849 245         415 $val =~ s/(\\\')/\\$1/g;
850 245         746 $ret .= "$cur$prop = '$val'\n";
851             }
852             }
853             }
854 347         686 $ret;
855             }
856              
857             =for later
858              
859             package Tokenizer;
860              
861             sub new {
862             my ( $pkg, $lines ) = @_;
863             bless { _line => "",
864             _token => undef,
865             _lineno => 0,
866             _lines => $lines,
867             } => $pkg;
868             }
869              
870             sub next {
871             my ( $self ) = @_;
872             while ( $self->{_line} !~ /\S/ && @{$self->{_lines} } ) {
873             $self->{_line} = shift(@{ $self->{_lines} });
874             $self->{_lineno}++;
875             $self->{_line} = "" if $self->{_line} =~ /^\s*#/;
876             }
877             return $self->{_token} = undef unless $self->{_line} =~ /\S/;
878              
879             $self->{_line} =~ s/^\s+//;
880              
881             if ( $self->{_line} =~ s/^([\[\]\{\}=:])// ) {
882             return $self->{_token} = $1;
883             }
884              
885             # Double quoted string.
886             if ( $self->{_line} =~ s/^ " ((?>[^\\"]*(?:\\.[^\\"]*)*)) " //xs ) {
887             return $self->{_token} = qq{"$1"};
888             }
889              
890             # Single quoted string.
891             if ( $self->{_line} =~ s/^ ' ((?>[^\\']*(?:\\.[^\\']*)*)) ' //xs ) {
892             return $self->{_token} = qq{'$1'}
893             }
894              
895             $self->{_line} =~ s/^([^\[\]\{\}=:"'\s]+)//;
896             return $self->{_token} = $1;
897             }
898              
899             sub token { $_[0]->{_token } }
900             sub lineno { $_[0]->{_lineno } }
901              
902             =cut
903              
904             ################ Package End ################
905              
906             1;
907              
908             =back
909              
910             =head1 PROPERTY FILES
911              
912             Property files contain definitions for properties. This module uses an
913             augmented version of the properties as used in e.g. Java.
914              
915             In general, each line of the file defines one property.
916              
917             version: 1
918             foo.bar = blech
919             foo.xxx = yyy
920             foo.xxx = "yyy"
921             foo.xxx = 'yyy'
922              
923             The latter three settings for C are equivalent.
924              
925             Whitespace has no significance. A colon C<:> may be used instead of
926             C<=>. Lines that are blank or empty, and lines that start with C<#>
927             are ignored.
928              
929             Property I consist of one or more identifiers (series of
930             letters and digits) separated by periods.
931              
932             Valid values are a plain text (whitespace, but not trailing, allowed),
933             a single-quoted string, or a double-quoted string. Single-quoted
934             strings allow embedded single-quotes by escaping them with a backslash
935             C<\>. Double-quoted strings allow common escapes like C<\n>, C<\t>,
936             C<\7>, C<\x1f> and C<\x{20cd}>.
937              
938             Note that in plain text backslashes are taken literally. The following
939             alternatives yield the same results:
940              
941             foo = a'\nb
942             foo = 'a\'\nb'
943             foo = "a'\\nb"
944              
945             B All values are strings. These three are equivalent:
946              
947             foo = 1
948             foo = "1"
949             foo = '1'
950              
951             and so are these:
952              
953             foo = Hello World!
954             foo = "Hello World!"
955             foo = 'Hello World!'
956              
957             Quotes are required when you want leading and/or trailing whitespace.
958             Also, the value C is special so if you want to use this as a string
959             it needs to be quoted.
960              
961             Single quotes defer expansion, see L below.
962              
963             =head2 Context
964              
965             When several properties with a common prefix must be set, they can be
966             grouped in a I:
967              
968             foo {
969             bar = blech
970             xxx = "yyy"
971             zzz = 'zyzzy'
972             }
973              
974             Contexts may be nested.
975              
976             =head2 Arrays
977              
978             When a property has a number of sub-properties with keys that are
979             consecutive numbers starting at C<0>, it may be considered as an
980             array. This is only relevant when using the data() method to retrieve
981             a Perl data structure from the set of properties.
982              
983             list {
984             0 = aap
985             1 = noot
986             2 = mies
987             }
988              
989             When retrieved using data(), this returns the Perl structure
990              
991             [ "aap", "noot", "mies" ]
992              
993             For convenience, arrays can be input in several more concise ways:
994              
995             list = [ aap noot mies ]
996             list = [ aap
997             noot
998             mies ]
999              
1000             The opening bracket must be followed by one or more values. This will
1001             currently not work:
1002              
1003             list = [
1004             aap
1005             noot
1006             mies ]
1007              
1008             =head2 Includes
1009              
1010             Property files can include other property files:
1011              
1012             include "myprops.prp"
1013              
1014             All properties that are read from the file are entered in the current
1015             context. E.g.,
1016              
1017             foo {
1018             include "myprops.prp"
1019             }
1020              
1021             will enter all the properties from the file with an additional C
1022             prefix.
1023              
1024             =head2 Expansion
1025              
1026             Property values can be anything. The value will be I before
1027             being assigned to the property unless it is placed between single
1028             quotes C<''>.
1029              
1030             Expansion means:
1031              
1032             =over
1033              
1034             =item *
1035              
1036             A tilde C<~> in what looks like a file name will be replaced by the
1037             value of C<${HOME}>.
1038              
1039             =item *
1040              
1041             If the value contains C<${I}>, I is first looked up in the
1042             current environment. If an environment variable I can be found,
1043             its value is substituted.
1044              
1045             If no suitable environment variable exists, I is looked up as a
1046             property and, if it exists and has a non-empty value, this value is
1047             substituted.
1048              
1049             Otherwise, the C<${I}> part is removed.
1050              
1051             Note that if a property is referred as C<${.I}>, I is
1052             looked up in the current context only.
1053              
1054             B Property lookup is case insensitive, B for the
1055             names of environment variables B on Microsoft Windows
1056             where environment variable names are looked up case insensitive.
1057              
1058             =item *
1059              
1060             If the value contains C<${I:I}>, I is looked up as
1061             described above. If, however, no suitable value can be found, I
1062             is substituted.
1063              
1064             =back
1065              
1066             Expansion is delayed if single quotes are used around the value.
1067              
1068             x = 1
1069             a = ${x}
1070             b = "${x}"
1071             c = '${x}'
1072             x = 2
1073              
1074             Now C and C will be C<'1'>, but C will be C<'2'>.
1075              
1076             Substitution is handled by L. See its
1077             documentation for more power.
1078              
1079             In addition, you can test for a property being defined (not null) by
1080             appending a C to its name.
1081              
1082             result = ${x?|${x|value|empty}|null}
1083              
1084             This will yield C if C is not null and not empty, C
1085             if not null and empty, and C if not defined or defined as null.
1086              
1087             =head1 SEE ALSO
1088              
1089             L, L.
1090              
1091             =head1 BUGS
1092              
1093             Although in production for over 25 years, this module is still
1094             slightly experimental and subject to change.
1095              
1096             =head1 AUTHOR
1097              
1098             Johan Vromans, C<< >>
1099              
1100             =head1 SUPPORT AND DOCUMENTATION
1101              
1102             Development of this module takes place on GitHub:
1103             https://github.com/sciurius/perl-Data-Properties.
1104              
1105             You can find documentation for this module with the perldoc command.
1106              
1107             perldoc Data::Properties
1108              
1109             Please report any bugs or feature requests using the issue tracker on
1110             GitHub.
1111              
1112             =head1 ACKNOWLEDGEMENTS
1113              
1114             This module was initially developed in 1994 as part of the Multihouse
1115             MH-Doc (later: MMDS) software suite. Multihouse kindly waived copyrights.
1116              
1117             In 2002 it was revamped as part of the Compuware OptimalJ development
1118             process. Compuware kindly waived copyrights.
1119              
1120             In 2020 it was updated to support arrays and released to the general
1121             public.
1122              
1123             =head1 COPYRIGHT & LICENSE
1124              
1125             Copyright 1994,2002,2020 Johan Vromans, all rights reserved.
1126              
1127             This program is free software; you can redistribute it and/or modify it
1128             under the same terms as Perl itself.
1129              
1130             =cut
1131              
1132             1; # End of Data::Properties