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   1409984 use strict;
  13         179  
  13         392  
6 13     13   93 use warnings;
  13         23  
  13         1253  
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   6788 use Text::ParseWords qw(parse_line);
  13         18596  
  13         800  
94 13     13   6749 use File::LoadLines;
  13         175669  
  13         850  
95 13     13   6631 use String::Interpolate::Named;
  13         39025  
  13         841  
96 13     13   99 use Carp;
  13         27  
  13         4828  
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   1789 if ( ref($_[1]) ) {
114             # IX/Data-Properties.
115 0         0 croak("API Error -- Incompatible Data::Properties version");
116             }
117 24         116 unshift(@_, 0);
118 24         77 &_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   90 my ($cloning, $invocant, %atts) = @_;
139              
140             # If the invocant is an object, get its class.
141 24   33     135 my $class = ref($invocant) || $invocant;
142              
143             # Initialize and bless the new object.
144 24         67 my $self = bless({}, $class);
145              
146             # Default path.
147 24         139 $self->{_path} = [ "." ];
148              
149             # Initialize.
150 24 50       96 $self->{_props} = $cloning ? {%{$invocant->{_props}}} : {};
  0         0  
151              
152             # Fill in initial attribute values.
153 24         308 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         63 $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   9238 use Data::Dumper;
  13         85438  
  13         1811  
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   3111 my ($self, $lines, $file, $context) = @_;
222 27         116 $self->_parse_lines_internal( $lines, $file, $context);
223              
224 27 50       97 if ( $self->{_debug} ) {
225 13     13   116 use Data::Dumper;
  13         37  
  13         55619  
226 0         0 $Data::Dumper::Indent = 2;
227 0         0 warn(Data::Dumper->Dump([$self->{_props}],[qw(properties)]), "\n");
228             }
229 27         114 $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   2796 my ( $self, $value, $ctx, $noexpand ) = @_;
306              
307             # Single-quoted string.
308 1040 100       3005 if ( $value =~ /^'(.*)'\s*$/ ) {
309 221         442 $value = $1;
310 221         362 $value =~ s/\\\\/\x{fdd0}/g;
311 221         310 $value =~ s/\\'/'/g;
312 221         330 $value =~ s/\x{fdd0}/\\/g;
313 221         454 return $value;
314             }
315              
316 819 50 33     1825 if ( $self->{_raw} && $value =~ /^(null|false|true)$/ ) {
317 0         0 return $value;
318             }
319              
320 819 100   2   1874 if ( lc($value) eq "null" ) {
  2         15  
  2         4  
  2         29  
321 15         32 return;
322             }
323 804 100       54552 if ( lc($value) eq "true" ) {
324 4         7 return 1;
325             }
326 800 100       1537 if ( lc($value) eq "false" ) {
327 23         47 return 0;
328             }
329              
330 777 100       1835 if ( $value =~ /^"(.*)"\s*$/ ) {
331 167         389 $value = $1;
332 167         317 $value =~ s/\\\\/\x{fdd0}/g;
333 167         262 $value =~ s/\\"/"/g;
334 167         253 $value =~ s/\\n/\n/g;
335 167         256 $value =~ s/\\t/\t/g;
336 167         273 $value =~ s/\\([0-7]{1,3})/sprintf("%c",oct($1))/ge;
  6         38  
337 167         258 $value =~ s/\\x([0-9a-f][0-9a-f]?)/sprintf("%c",hex($1))/ge;
  1         6  
338 167         251 $value =~ s/\\x\{([0-9a-f]+)\}/sprintf("%c",hex($1))/ge;
  1         7  
339 167         268 $value =~ s/\x{fdd0}/\\/g;
340 167 100       306 return $value if $noexpand;
341 165         365 return $self->expand($value, $ctx);
342             }
343              
344 610 100       1571 return $value if $noexpand;
345 108         299 $self->expand($value, $ctx);
346             }
347              
348             sub _parse_lines_internal {
349              
350 27     27   72 my ( $self, $lines, $filename, $context ) = @_;
351              
352 27 100       96 my @stack = $context ? ( [$context, undef] ) : ();
353 27         113 my $keypat = qr/[-\w.]+|"[^"]*"|'[^']*'/;
354              
355             # Process its contents.
356 27         56 my $lineno = 0;
357 27         84 while ( @$lines ) {
358 873         1257 $lineno++;
359 873         1491 $_ = shift(@$lines);
360              
361             #### Discard empty lines and comment lines/
362 873 100       2392 next if /^\s*#/;
363 647 100       1835 next unless /\S/;
364              
365             #### Trim.
366 611         1757 s/^\s+//;
367 611         1608 s/\s+$//;
368              
369             #### Controls
370             # include filename (only if at the line start, and not followed by =.
371 611 50 33     1458 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       4178 if ( /^($keypat)\s*([{])$/ ) {
389 48         143 my $c = $self->_value( $1, undef, "noexpand" );
390 48 50       137 my $i = $2 eq '[' ? 0 : undef;
391 48 100       146 @stack = ( [ $c, $i ] ), next unless @stack;
392 35         113 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
393 35         92 next;
394             }
395 563 100       2906 if ( /^($keypat)\s*[:=]\s*([[])$/ ) {
396 9         33 my $c = $self->_value( $1, undef, "noexpand" );
397 9 50       34 my $i = $2 eq '[' ? 0 : undef;
398 9 100       26 @stack = ( [ $c, $i ] ), next unless @stack;
399 8         32 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
400 8         24 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     3148 while ( /^($keypat)\s*[=:]\s*\[(.+)$/ && $2 !~ /\]\s*$/ && @$lines ) {
      66        
414 2         5 $_ .= " " . shift(@$lines);
415 2         24 $lineno++;
416             }
417 554 100       2839 if ( /^($keypat)\s*[:=]\s*\[(.*)\]$/ ) {
418 12         41 my $prop = $self->_value( $1, undef, "noexpand" );
419 12 100       62 $prop = $stack[0]->[0] . "." . $prop if @stack;
420 12         31 my $v = $2;
421 12         44 $v =~ s/^\s+//;
422 12         49 $v =~ s/\s+$//;
423 12         24 my $ix = 0;
424 12         43 for my $value ( parse_line( '\s+', 1, $v ) ) {
425 24         895 $value = $self->_value( $value, $stack[0] );
426 24         1583 $self->set_property( $prop . "." . $ix++, $value );
427             }
428 12 100       110 $self->set_property( $prop, undef ) unless $ix;
429 12         40 next;
430             }
431              
432 542 50 66     1366 if ( /^\s*\[(.*)\]$/ && @stack && $stack[0][1] ) {
      66        
433 10         31 my $prop = $stack[0][0] . "." . $stack[0][1]++;
434 10         28 my $v = $1;
435 10         22 $v =~ s/^\s+//;
436 10         25 $v =~ s/\s+$//;
437 10         18 my $ix = 0;
438 10         51 for my $value ( parse_line( '\s+', 1, $v ) ) {
439 32         1275 $value = $self->_value( $value, $stack[0] );
440 32         2185 $self->set_property( $prop . "." . $ix++, $value );
441             }
442 10         40 next;
443             }
444              
445             # {
446             # [
447             # Push a new context while building an array.
448 532 100 100     1659 if ( @stack && defined($stack[0]->[1]) # building array
      100        
449             && /^([{\[])$/ ) {
450 7 50       34 my $i = $1 eq '[' ? 0 : undef;
451 7         29 unshift( @stack, [ $stack[0]->[0] . "." . $stack[0]->[1]++, $i ] );
452 7         23 next;
453             }
454              
455             # }
456             # ]
457             # Pop context.
458 525 100       1370 if ( /^([}\]])$/ ) {
459 64 50 33     315 die("stack underflow at line $lineno")
    50          
460             unless @stack
461             && ( $1 eq defined($stack[0]->[1]) ? ']' : '}' );
462 64         114 shift(@stack);
463 64         165 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       3166 if ( /^($keypat)\s*[=:]\s*(.*)/ ) {
472 438 50       1194 die("Brace is illegal as a value (use quotes to bypass)\n")
473             if $2 eq '{';
474 438         1026 my $prop = $self->_value( $1, undef, "noexpand" );
475 438         1069 my $value = $self->_value( $2, $stack[0] );
476              
477             # Make a full name.
478 438 100       15279 $prop = $stack[0]->[0] . "." . $prop if @stack;
479              
480             # Set the property.
481 438         1200 $self->set_property($prop, $value);
482              
483 438         1263 next;
484             }
485              
486             # value(s) (while building an array)
487 23 50 33     84 if ( @stack && defined($stack[0]->[1]) ) {
488              
489 23         76 for my $value ( parse_line( '\s+', 1, $_ ) ) {
490             # Make a full name.
491 39         1646 my $prop = $stack[0]->[0] . "." . $stack[0]->[1]++;
492              
493 39         102 $value = $self->_value( $value, $stack[0] );
494              
495             # Set the property.
496 39         2749 $self->set_property($prop, $value);
497             }
498 23         71 next;
499             }
500              
501             # Error.
502 0         0 croak("?line $lineno: $_\n");
503             }
504              
505             # Sanity checks.
506 27 100       163 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         15 $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   10 my ($self, $prop, $default) = @_;
540 4         11 $prop = lc($prop);
541 4         7 my $ctx = $self->{_context};
542 4         8 my $context_only;
543 4 50 33     23 if ( ($context_only = $prop =~ s/^\.//) && !$ctx ) {
544 0         0 croak("get_property: no context for $prop");
545             }
546 4 50       11 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       9 if ( $context_only ) {
554 0         0 $self->{_in_context} = undef;
555 0         0 return $default;
556             }
557 4 50 33     25 if ( defined($self->{_props}->{$prop}) && $self->{_props}->{$prop} ne "") {
558 4         9 $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   11 my $nargs = @_;
576 3         7 my ($self, $prop, $default) = @_;
577 3         7 my $ret = $self->get_property($prop, $default);
578 3 50 33     216 croak("gps: no value for $prop")
579             unless defined($ret) || $nargs == 3;
580 3         14 $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   1582 my ($self, $ret, $ctx) = (@_, "");
607 762 100       1496 return $ret unless $ret;
608 671 50 0     1302 warn("expand($ret,",$ctx//'',")\n") if $self->{_debug};
609 671         1019 my $props = $self->{_props};
610 671         1164 $ret =~ s:^~(/|$):$ENV{HOME}$1:g;
611 671         1294 return $self->_interpolate( $ret, $ctx );
612             }
613              
614             # internal
615              
616             sub _interpolate {
617 671     671   1292 my ( $self, $tpl, $ctx ) = @_;
618 671 100       1350 ( $ctx, my $ix ) = @$ctx if $ctx;
619 671         1087 my $props = $self->{_props};
620             return interpolate( { activator => '$',
621             keypattern => qr/\.?\w+[-_\w.]*\??(?::.*)?/,
622             args => sub {
623 14     14   1413 my $key = shift;
624 14 50 0     38 warn("_inter($key,",$ctx//'',")\n") if $self->{_debug};
625             # Establish the value for this key.
626 14         28 my $val = '';
627              
628 14         23 my $default = '';
629 14 100       50 ( $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       47 if ( exists($ENV{$key}) ) {
635 1         3 $val = $ENV{$key};
636 1 50       9 $val = defined($val) if $checkdef;
637             }
638             else {
639 13         23 my $orig = $key;
640 13 100       38 $key = $ctx.$key if ord($key) == ord('.');
641             # For properties, the value should be non-empty.
642 13 100 100     82 if ( $checkdef ) {
    100          
643 2         7 $val = defined($props->{lc($key)});
644             }
645             elsif ( defined($props->{lc($key)}) && $props->{lc($key)} ne "" ) {
646 6         21 $val = $props->{lc($key)};
647             }
648             else {
649 5         13 $val = $default;
650             }
651             }
652 14         42 return $val;
653             } },
654 671         5016 $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   1108 my ($self, $prop, $value) = @_;
665 537         847 my $props = $self->{_props};
666 537         3517 $props->{lc($prop)} = $value;
667 537         1833 my @prop = split(/\./, $prop, -1);
668 537         1206 while ( @prop ) {
669 1660         2793 my $last = pop(@prop);
670 1660         3477 my $p = lc(join(".", @prop, '@'));
671 1660 100       3358 if ( exists($props->{$p}) ) {
672 510         1979 push(@{$props->{$p}}, $last)
673 1466 100       1981 unless index(join("\0","",@{$props->{$p}},""),
  1466         7359  
674             "\0".$last."\0") >= 0;
675             }
676             else {
677 194         697 $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   182 my ($self, $start) = ( @_, '' );
746 13         38 my $ret = $self->_data_internal($start);
747 13         100 $ret;
748             }
749              
750             sub _data_internal {
751 672     672   1229 my ( $self, $orig ) = @_;
752 672   50     1501 my $cur = $orig // '';
753 672 100       1501 $cur .= "." if $cur ne '';
754 672         1182 my $all = $cur;
755 672         1036 $all .= '@';
756 672 100       2076 if ( my $res = $self->{_props}->{lc($all)} ) {
757 175 100       313 if ( _check_array($res) ) {
758 62         112 my $ret = [];
759 62         113 foreach my $prop ( @$res ) {
760 238         647 $ret->[$prop] = $self->_data_internal($cur.$prop);
761             }
762 62         220 return $ret;
763             }
764             else {
765 113         212 my $ret = {};
766 113         242 foreach my $prop ( @$res ) {
767 421         1124 $ret->{$prop} = $self->_data_internal($cur.$prop);
768             }
769 113         350 return $ret;
770             }
771             }
772             else {
773 497         1445 my $val = $self->{_props}->{lc($orig)};
774 497 100       1178 $val = $self->expand($val) if defined $val;
775 497         30443 return $val;
776             }
777             }
778              
779             sub _check_array {
780 175     175   292 my ( $i ) = @_;
781 175         516 my @i = @$i;
782 175 100       986 return unless "@i" =~ /^[\d ]+$/; # quick
783 62         120 my $ref = 0;
784 62         122 for ( @i) {
785 238 50       464 return unless $_ eq "$ref";
786 238         353 $ref++;
787             }
788 62         155 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   113 my ($self, $start, $fh) = ( @_, '' );
810 12         43 my $ret = $self->_dump_internal($start);
811 12 50       45 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   610 my ($self, $cur) = @_;
827 347 100       688 $cur .= "." if $cur;
828 347         553 my $all = $cur;
829 347         512 $all .= '@';
830 347         500 my $ret = "";
831 347 100       975 if ( my $res = $self->{_props}->{lc($all)} ) {
832 90 100       339 $ret .= "# $all = @$res\n" if @$res > 1;
833 90         204 foreach my $prop ( @$res ) {
834 335         939 my $t = $self->_dump_internal($cur.$prop);
835 335 100 66     1087 $ret .= $t if defined($t) && $t ne '';
836 335         856 my $val = $self->{_props}->{lc($cur.$prop)};
837 335 50       565 $val = $self->expand($val) if $dump_expanded;
838 335 100       781 if ( !defined $val ) {
    100          
839 89 100 66     349 $ret .= "$cur$prop = null\n"
840             unless defined($t) && $t ne '';
841             }
842             elsif ( $val =~ /[\n\t]/ ) {
843 1         29 $val =~ s/(["\\])/\\$1/g;
844 1         8 $val =~ s/\n/\\n/g;
845 1         6 $val =~ s/\t/\\t/g;
846 1         6 $ret .= "$cur$prop = \"$val\"\n";
847             }
848             else {
849 245         398 $val =~ s/(\\\')/\\$1/g;
850 245         734 $ret .= "$cur$prop = '$val'\n";
851             }
852             }
853             }
854 347         653 $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