File Coverage

blib/lib/Data/Properties.pm
Criterion Covered Total %
statement 252 329 76.6
branch 110 162 67.9
condition 31 60 51.6
subroutine 26 40 65.0
pod 19 22 86.3
total 438 613 71.4


line stmt bran cond sub pod time code
1             #! perl
2              
3             package Data::Properties;
4              
5 14     14   1069067 use strict;
  14         214  
  14         484  
6 14     14   78 use warnings;
  14         26  
  14         913  
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: Fri Jul 16 08:23:14 2021
12             # Update Count : 545
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 = "1.05";
92              
93 14     14   7958 use Text::ParseWords qw(parse_line);
  14         20610  
  14         995  
94 14     14   8043 use File::LoadLines;
  14         201945  
  14         1076  
95 14     14   8121 use String::Interpolate::Named;
  14         44879  
  14         1127  
96 14     14   125 use Carp;
  14         34  
  14         5221  
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 1 1398 if ( ref($_[1]) ) {
114             # IX/Data-Properties.
115 0         0 croak("API Error -- Incompatible Data::Properties version");
116             }
117 24         91 unshift(@_, 0);
118 24         78 &_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 1 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     143 my $class = ref($invocant) || $invocant;
142              
143             # Initialize and bless the new object.
144 24         77 my $self = bless({}, $class);
145              
146             # Default path.
147 24         141 $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         364 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         66 $self->{_in_context} = undef;
171              
172             # Return.
173 24         143 $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 1 0 my ($self, $file, $context) = @_;
199 0         0 $self->_parse_file_internal( $file, $context);
200              
201 0 0       0 if ( $self->{_debug} ) {
202 14     14   10524 use Data::Dumper;
  14         94775  
  14         2477  
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 1 3032 my ($self, $lines, $file, $context) = @_;
222 27         130 $self->_parse_lines_internal( $lines, $file, $context);
223              
224 27 50       102 if ( $self->{_debug} ) {
225 14     14   167 use Data::Dumper;
  14         32  
  14         61420  
226 0         0 $Data::Dumper::Indent = 2;
227 0         0 warn(Data::Dumper->Dump([$self->{_props}],[qw(properties)]), "\n");
228             }
229 27         93 $self;
230             }
231              
232             # Catch some calls that are not in this version of Data::Properties.
233             sub load {
234 0     0 0 0 croak("API Error -- Incompatible Data::Properties version");
235             }
236             sub property_names {
237 0     0 0 0 croak("API Error -- Incompatible Data::Properties version");
238             }
239             sub store {
240 0     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 1 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 1 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 $lines = loadlines($cfg);
290 0         0 $self->parse_lines( $lines, $cfg, $context );
291 0         0 $did++;
292              
293             # We read a file, no need to proceed searching.
294 0         0 last;
295             }
296              
297             # Sanity checks.
298 0 0       0 croak("No properties $file in " . join(":", @$searchpath)) unless $did;
299             }
300              
301             # internal
302              
303             sub _value {
304 1040     1040   3069 my ( $self, $value, $ctx, $noexpand ) = @_;
305              
306             # Single-quoted string.
307 1040 100       2900 if ( $value =~ /^'(.*)'\s*$/ ) {
308 221         451 $value = $1;
309 221         361 $value =~ s/\\\\/\x{fdd0}/g;
310 221         307 $value =~ s/\\'/'/g;
311 221         325 $value =~ s/\x{fdd0}/\\/g;
312 221         456 return $value;
313             }
314              
315 819 50 33     1829 if ( $self->{_raw} && $value =~ /^(null|false|true)$/ ) {
316 0         0 return $value;
317             }
318              
319 819 100   2   1985 if ( lc($value) eq "null" ) {
  2         24  
  2         4  
  2         40  
320 15         37 return;
321             }
322 804 100       59232 if ( lc($value) eq "true" ) {
323 4         9 return 1;
324             }
325 800 100       1452 if ( lc($value) eq "false" ) {
326 23         46 return 0;
327             }
328              
329 777 100       1866 if ( $value =~ /^"(.*)"\s*$/ ) {
330 167         388 $value = $1;
331 167         299 $value =~ s/\\\\/\x{fdd0}/g;
332 167         258 $value =~ s/\\"/"/g;
333 167         237 $value =~ s/\\n/\n/g;
334 167         268 $value =~ s/\\t/\t/g;
335 167         269 $value =~ s/\\([0-7]{1,3})/sprintf("%c",oct($1))/ge;
  6         36  
336 167         240 $value =~ s/\\x([0-9a-f][0-9a-f]?)/sprintf("%c",hex($1))/ge;
  1         9  
337 167         255 $value =~ s/\\x\{([0-9a-f]+)\}/sprintf("%c",hex($1))/ge;
  1         7  
338 167         284 $value =~ s/\x{fdd0}/\\/g;
339 167 100       304 return $value if $noexpand;
340 165         350 return $self->expand($value, $ctx);
341             }
342              
343 610 100       1581 return $value if $noexpand;
344 108         272 $self->expand($value, $ctx);
345             }
346              
347             sub _parse_lines_internal {
348              
349 27     27   75 my ( $self, $lines, $filename, $context ) = @_;
350              
351 27 100       92 my @stack = $context ? ( [$context, undef] ) : ();
352 27         123 my $keypat = qr/[-\w.]+|"[^"]*"|'[^']*'/;
353              
354             # Process its contents.
355 27         59 my $lineno = 0;
356 27         91 while ( @$lines ) {
357 873         1133 $lineno++;
358 873         1399 $_ = shift(@$lines);
359              
360             #### Discard empty lines and comment lines/
361 873 100       2549 next if /^\s*#/;
362 647 100       1963 next unless /\S/;
363              
364             #### Trim.
365 611         1676 s/^\s+//;
366 611         1529 s/\s+$//;
367              
368             #### Controls
369             # include filename (only if at the line start, and not followed by =.
370 611 50 33     1468 if ( /^include\s+((?![=:]).+)/ && !$self->{_noinc} ) {
371 0         0 my $value = $self->_value( $1, $stack[0] );
372 0         0 $self->_parse_file_internal($value, $stack[0]);
373 0         0 next;
374             }
375              
376             #### Settings
377             # key = value
378             # key {
379             # key [
380             # value
381             # ]
382             # }
383              
384             # foo.bar {
385             # foo.bar [
386             # Push a new context.
387 611 100       4369 if ( /^($keypat)\s*([{])$/ ) {
388 48         146 my $c = $self->_value( $1, undef, "noexpand" );
389 48 50       152 my $i = $2 eq '[' ? 0 : undef;
390 48 100       160 @stack = ( [ $c, $i ] ), next unless @stack;
391 35         121 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
392 35         94 next;
393             }
394 563 100       2918 if ( /^($keypat)\s*[:=]\s*([[])$/ ) {
395 9         29 my $c = $self->_value( $1, undef, "noexpand" );
396 9 50       29 my $i = $2 eq '[' ? 0 : undef;
397 9 100       28 @stack = ( [ $c, $i ] ), next unless @stack;
398 8         36 unshift( @stack, [ $stack[0]->[0] . "." . $c, $i ] );
399 8         23 next;
400             }
401              
402             # foo.bar = [ val val ]
403             # foo.bar = [ val
404             # val ]
405             # foo.bar = [ val val
406             # ]
407             # BUT NOT
408             # foo.bar = [
409             # val val ]
410             # Create an array
411             # Add lines, if necessary.
412 554   100     3378 while ( /^($keypat)\s*[=:]\s*\[(.+)$/ && $2 !~ /\]\s*$/ && @$lines ) {
      66        
413 2         8 $_ .= " " . shift(@$lines);
414 2         16 $lineno++;
415             }
416 554 100       2753 if ( /^($keypat)\s*[:=]\s*\[(.*)\]$/ ) {
417 12         41 my $prop = $self->_value( $1, undef, "noexpand" );
418 12 100       48 $prop = $stack[0]->[0] . "." . $prop if @stack;
419 12         33 my $v = $2;
420 12         70 $v =~ s/^\s+//;
421 12         50 $v =~ s/\s+$//;
422 12         24 my $ix = 0;
423 12         62 for my $value ( parse_line( '\s+', 1, $v ) ) {
424 24         1031 $value = $self->_value( $value, $stack[0] );
425 24         969 $self->set_property( $prop . "." . $ix++, $value );
426             }
427 12 100       68 $self->set_property( $prop, undef ) unless $ix;
428 12         81 next;
429             }
430              
431 542 50 66     1318 if ( /^\s*\[(.*)\]$/ && @stack && $stack[0][1] ) {
      66        
432 10         34 my $prop = $stack[0][0] . "." . $stack[0][1]++;
433 10         29 my $v = $1;
434 10         23 $v =~ s/^\s+//;
435 10         30 $v =~ s/\s+$//;
436 10         15 my $ix = 0;
437 10         31 for my $value ( parse_line( '\s+', 1, $v ) ) {
438 32         1221 $value = $self->_value( $value, $stack[0] );
439 32         1473 $self->set_property( $prop . "." . $ix++, $value );
440             }
441 10         36 next;
442             }
443              
444             # {
445             # [
446             # Push a new context while building an array.
447 532 100 100     1729 if ( @stack && defined($stack[0]->[1]) # building array
      100        
448             && /^([{\[])$/ ) {
449 7 50       24 my $i = $1 eq '[' ? 0 : undef;
450 7         33 unshift( @stack, [ $stack[0]->[0] . "." . $stack[0]->[1]++, $i ] );
451 7         17 next;
452             }
453              
454             # }
455             # ]
456             # Pop context.
457 525 100       1353 if ( /^([}\]])$/ ) {
458 64 50 33     330 die("stack underflow at line $lineno")
    50          
459             unless @stack
460             && $1 eq defined($stack[0]->[1]) ? ']' : '}';
461 64         110 shift(@stack);
462 64         170 next;
463             }
464              
465             # foo.bar = blech
466             # foo.bar = "blech"
467             # foo.bar = 'blech'
468             # Simple assignment.
469             # The value is expanded unless single quotes are used.
470 461 100       3189 if ( /^($keypat)\s*[=:]\s*(.*)/ ) {
471 438         1286 my $prop = $self->_value( $1, undef, "noexpand" );
472 438         1044 my $value = $self->_value( $2, $stack[0] );
473              
474             # Make a full name.
475 438 100       10234 $prop = $stack[0]->[0] . "." . $prop if @stack;
476              
477             # Set the property.
478 438         1155 $self->set_property($prop, $value);
479              
480 438         1261 next;
481             }
482              
483             # value(s) (while building an array)
484 23 50 33     88 if ( @stack && defined($stack[0]->[1]) ) {
485              
486 23         84 for my $value ( parse_line( '\s+', 1, $_ ) ) {
487             # Make a full name.
488 39         1609 my $prop = $stack[0]->[0] . "." . $stack[0]->[1]++;
489              
490 39         103 $value = $self->_value( $value, $stack[0] );
491              
492             # Set the property.
493 39         1731 $self->set_property($prop, $value);
494             }
495 23         79 next;
496             }
497              
498             # Error.
499 0         0 croak("?line $lineno: $_\n");
500             }
501              
502             # Sanity checks.
503 27 100       172 croak("Unfinished properties $filename")
    50          
504             if @stack != ($context ? 1 : 0);
505             }
506              
507             =item get_property I [ , I ]
508              
509             Get the value for a given property I.
510              
511             If a context I has been set using C')>,
512             C will first try C<'I.foo.bar'> and then
513             C<'foo.bar'>. C (note the leading period)
514             will only try C<'I.foo.bar'> and raise an exception if no context
515             was set.
516              
517             If no value can be found, I is used.
518              
519             In either case, the resultant value is examined for references to
520             other properties or environment variables. See L below.
521              
522             =cut
523              
524             sub get_property {
525 4     4 1 10 my ($self) = shift;
526 4         15 $self->expand($self->get_property_noexpand(@_));
527             }
528              
529             =item get_property_noexpand I [ , I ]
530              
531             This is like I, but does not do any expansion.
532              
533             =cut
534              
535             sub get_property_noexpand {
536 4     4 1 9 my ($self, $prop, $default) = @_;
537 4         9 $prop = lc($prop);
538 4         8 my $ctx = $self->{_context};
539 4         8 my $context_only;
540 4 50 33     15 if ( ($context_only = $prop =~ s/^\.//) && !$ctx ) {
541 0         0 croak("get_property: no context for $prop");
542             }
543 4 50       11 if ( defined($ctx) ) {
544 0 0       0 $ctx .= "." if $ctx;
545 0 0       0 if ( exists($self->{_props}->{$ctx.$prop}) ) {
546 0         0 $self->{_in_context} = $ctx;
547 0         0 return $self->{_props}->{$ctx.$prop};
548             }
549             }
550 4 50       9 if ( $context_only ) {
551 0         0 $self->{_in_context} = undef;
552 0         0 return $default;
553             }
554 4 50 33     22 if ( defined($self->{_props}->{$prop}) && $self->{_props}->{$prop} ne "") {
555 4         10 $self->{_in_context} = "";
556 4         14 return $self->{_props}->{$prop};
557             }
558 0         0 $self->{_in_context} = undef;
559 0         0 $default;
560             }
561              
562             =item gps I [ , I ]
563              
564             This is like I, but raises an exception if no value
565             could be established.
566              
567             This is probably the best and safest method to use.
568              
569             =cut
570              
571             sub gps {
572 3     3 1 10 my $nargs = @_;
573 3         8 my ($self, $prop, $default) = @_;
574 3         8 my $ret = $self->get_property($prop, $default);
575 3 50 33     131 croak("gps: no value for $prop")
576             unless defined($ret) || $nargs == 3;
577 3         16 $ret;
578             }
579              
580             =item get_property_keys I
581              
582             Returns an array reference with the names of the (sub)keys for the
583             given property. The names are unqualified, e.g., when properties
584             C and C exist, C would
585             return C<['bar', 'blech']>.
586              
587             =cut
588              
589             sub get_property_keys {
590 0     0 1 0 my ($self, $prop) = @_;
591 0 0       0 $prop .= '.' if $prop;
592 0         0 $prop .= '@';
593 0         0 $self->get_property_noexpand($prop);
594             }
595              
596             =item expand I [ , I ]
597              
598             Perform the expansion as described with I.
599              
600             =cut
601              
602             sub expand {
603 762     762 1 1956 my ($self, $ret, $ctx) = (@_, "");
604 762 100       1382 return $ret unless $ret;
605 671 50 0     1238 warn("expand($ret,",$ctx//'',")\n") if $self->{_debug};
606 671         908 my $props = $self->{_props};
607 671         1173 $ret =~ s:^~(/|$):$ENV{HOME}$1:g;
608 671         1278 return $self->_interpolate( $ret, $ctx );
609             }
610              
611             # internal
612              
613             sub _interpolate {
614 671     671   1166 my ( $self, $tpl, $ctx ) = @_;
615 671 100       1303 ( $ctx, my $ix ) = @$ctx if $ctx;
616 671         952 my $props = $self->{_props};
617             return interpolate( { activator => '$',
618             keypattern => qr/\.?\w+[-_\w.]*\??(?::.*)?/,
619             args => sub {
620 14     14   1334 my $key = shift;
621 14 50 0     43 warn("_inter($key,",$ctx//'',")\n") if $self->{_debug};
622             # Establish the value for this key.
623 14         25 my $val = '';
624              
625 14         21 my $default = '';
626 14 100       56 ( $key, $default ) = ( $1, $2 )
627             if $key =~ /^(.*?):(.*)/;
628 14         42 my $checkdef = $key =~ s/\?$//;
629              
630             # If an environment variable exists, take its value.
631 14 100       48 if ( exists($ENV{$key}) ) {
632 1         3 $val = $ENV{$key};
633 1 50       3 $val = defined($val) if $checkdef;
634             }
635             else {
636 13         26 my $orig = $key;
637 13 100       36 $key = $ctx.$key if ord($key) == ord('.');
638             # For properties, the value should be non-empty.
639 13 100 100     112 if ( $checkdef ) {
    100          
640 2         7 $val = defined($props->{lc($key)});
641             }
642             elsif ( defined($props->{lc($key)}) && $props->{lc($key)} ne "" ) {
643 6         18 $val = $props->{lc($key)};
644             }
645             else {
646 5         12 $val = $default;
647             }
648             }
649 14         44 return $val;
650             } },
651 671         5252 $tpl );
652             }
653              
654             =item set_property I, I
655              
656             Set the property to the given value.
657              
658             =cut
659              
660             sub set_property {
661 537     537 1 1136 my ($self, $prop, $value) = @_;
662 537         830 my $props = $self->{_props};
663 537         1830 $props->{lc($prop)} = $value;
664 537         1786 my @prop = split(/\./, $prop, -1);
665 537         1166 while ( @prop ) {
666 1660         2642 my $last = pop(@prop);
667 1660         3465 my $p = lc(join(".", @prop, '@'));
668 1660 100       3301 if ( exists($props->{$p}) ) {
669 510         1862 push(@{$props->{$p}}, $last)
670 1466 100       1841 unless index(join("\0","",@{$props->{$p}},""),
  1466         7659  
671             "\0".$last."\0") >= 0;
672             }
673             else {
674 194         785 $props->{$p} = [ $last ];
675             }
676             }
677             }
678              
679             =item set_properties I => I, ...
680              
681             Add a hash (key/value pairs) of properties to the set of properties.
682              
683             =cut
684              
685             sub set_properties {
686 0     0 1 0 my ($self, %props) = @_;
687 0         0 foreach ( keys(%props) ) {
688 0         0 $self->set_property($_, $props{$_});
689             }
690             }
691              
692             =item set_context I
693              
694             Set the search context. Without argument, clears the current context.
695              
696             =cut
697              
698             sub set_context {
699 0     0 1 0 my ($self, $context) = @_;
700 0         0 $self->{_context} = lc($context);
701 0         0 $self->{_in_context} = undef;
702 0         0 $self;
703             }
704              
705             =item get_context
706              
707             Get the current search context.
708              
709             =cut
710              
711             sub get_context {
712 0     0 1 0 my ($self) = @_;
713 0         0 $self->{_context};
714             }
715              
716             =item result_in_context
717              
718             Get the context status of the last search.
719              
720             Empty means it was found out of context, a string indicates the
721             context in which the result was found, and undef indicates search
722             failure.
723              
724             =cut
725              
726             sub result_in_context {
727 0     0 1 0 my ($self) = @_;
728 0         0 $self->{_in_context};
729             }
730              
731             =item data [ I ]
732              
733             Produces a Perl data structure created from all the properties from a
734             given point in the hierarchy.
735              
736             Note that since Perl hashes do not have an ordering, this information
737             will get lost. Also, properties can not have both a value and a substructure.
738              
739             =cut
740              
741             sub data {
742 13     13 1 182 my ($self, $start) = ( @_, '' );
743 13         42 my $ret = $self->_data_internal($start);
744 13         114 $ret;
745             }
746              
747             sub _data_internal {
748 672     672   1152 my ( $self, $orig ) = @_;
749 672   50     1287 my $cur = $orig // '';
750 672 100       1431 $cur .= "." if $cur ne '';
751 672         1050 my $all = $cur;
752 672         1035 $all .= '@';
753 672 100       2015 if ( my $res = $self->{_props}->{lc($all)} ) {
754 175 100       312 if ( _check_array($res) ) {
755 62         107 my $ret = [];
756 62         115 foreach my $prop ( @$res ) {
757 238         595 $ret->[$prop] = $self->_data_internal($cur.$prop);
758             }
759 62         204 return $ret;
760             }
761             else {
762 113         200 my $ret = {};
763 113         273 foreach my $prop ( @$res ) {
764 421         1073 $ret->{$prop} = $self->_data_internal($cur.$prop);
765             }
766 113         351 return $ret;
767             }
768             }
769             else {
770 497         1296 my $val = $self->{_props}->{lc($orig)};
771 497 100       1042 $val = $self->expand($val) if defined $val;
772 497         16949 return $val;
773             }
774             }
775              
776             sub _check_array {
777 175     175   267 my ( $i ) = @_;
778 175         497 my @i = @$i;
779 175 100       972 return unless "@i" =~ /^[\d ]+$/; # quick
780 62         115 my $ref = 0;
781 62         184 for ( @i) {
782 238 50       432 return unless $_ eq "$ref";
783 238         339 $ref++;
784             }
785 62         159 return 1; # success!
786             }
787              
788             =item dump [ I [ , I ] ]
789              
790             Produces a listing of all properties from a given point in the
791             hierarchy and write it to the I.
792              
793             Without I, returns a string.
794              
795             In general, I should be UTF-8 capable.
796              
797             =item dumpx [ I [ , I ] ]
798              
799             Like dump, but dumps with all values expanded.
800              
801             =cut
802              
803             my $dump_expanded;
804              
805             sub dump {
806 12     12 1 219 my ($self, $start, $fh) = ( @_, '' );
807 12         53 my $ret = $self->_dump_internal($start);
808 12 50       44 print $fh $ret if $fh;
809 12         85 $ret;
810             }
811              
812             sub dumpx {
813 0     0 1 0 my ($self, $start, $fh) = ( @_, '' );
814 0         0 $dump_expanded = 1;
815 0         0 my $ret = $self->dump( $start, $fh );
816 0         0 $dump_expanded = 0;
817 0         0 $ret;
818             }
819              
820             # internal
821              
822             sub _dump_internal {
823 347     347   556 my ($self, $cur) = @_;
824 347 100       687 $cur .= "." if $cur;
825 347         536 my $all = $cur;
826 347         555 $all .= '@';
827 347         475 my $ret = "";
828 347 100       961 if ( my $res = $self->{_props}->{lc($all)} ) {
829 90 100       382 $ret .= "# $all = @$res\n" if @$res > 1;
830 90         232 foreach my $prop ( @$res ) {
831 335         847 my $t = $self->_dump_internal($cur.$prop);
832 335 100 66     1109 $ret .= $t if defined($t) && $t ne '';
833 335         989 my $val = $self->{_props}->{lc($cur.$prop)};
834 335 50       563 $val = $self->expand($val) if $dump_expanded;
835 335 100       774 if ( !defined $val ) {
    100          
836 89 100 66     325 $ret .= "$cur$prop = null\n"
837             unless defined($t) && $t ne '';
838             }
839             elsif ( $val =~ /[\n\t]/ ) {
840 1         11 $val =~ s/(["\\])/\\$1/g;
841 1         7 $val =~ s/\n/\\n/g;
842 1         5 $val =~ s/\t/\\t/g;
843 1         6 $ret .= "$cur$prop = \"$val\"\n";
844             }
845             else {
846 245         396 $val =~ s/(\\\')/\\$1/g;
847 245         753 $ret .= "$cur$prop = '$val'\n";
848             }
849             }
850             }
851 347         628 $ret;
852             }
853              
854             =for later
855              
856             package Tokenizer;
857              
858             sub new {
859             my ( $pkg, $lines ) = @_;
860             bless { _line => "",
861             _token => undef,
862             _lineno => 0,
863             _lines => $lines,
864             } => $pkg;
865             }
866              
867             sub next {
868             my ( $self ) = @_;
869             while ( $self->{_line} !~ /\S/ && @{$self->{_lines} } ) {
870             $self->{_line} = shift(@{ $self->{_lines} });
871             $self->{_lineno}++;
872             $self->{_line} = "" if $self->{_line} =~ /^\s*#/;
873             }
874             return $self->{_token} = undef unless $self->{_line} =~ /\S/;
875              
876             $self->{_line} =~ s/^\s+//;
877              
878             if ( $self->{_line} =~ s/^([\[\]\{\}=:])// ) {
879             return $self->{_token} = $1;
880             }
881              
882             # Double quoted string.
883             if ( $self->{_line} =~ s/^ " ((?>[^\\"]*(?:\\.[^\\"]*)*)) " //xs ) {
884             return $self->{_token} = qq{"$1"};
885             }
886              
887             # Single quoted string.
888             if ( $self->{_line} =~ s/^ ' ((?>[^\\']*(?:\\.[^\\']*)*)) ' //xs ) {
889             return $self->{_token} = qq{'$1'}
890             }
891              
892             $self->{_line} =~ s/^([^\[\]\{\}=:"'\s]+)//;
893             return $self->{_token} = $1;
894             }
895              
896             sub token { $_[0]->{_token } }
897             sub lineno { $_[0]->{_lineno } }
898              
899             =cut
900              
901             ################ Package End ################
902              
903             1;
904              
905             =back
906              
907             =head1 PROPERTY FILES
908              
909             Property files contain definitions for properties. This module uses an
910             augmented version of the properties as used in e.g. Java.
911              
912             In general, each line of the file defines one property.
913              
914             version: 1
915             foo.bar = blech
916             foo.xxx = yyy
917             foo.xxx = "yyy"
918             foo.xxx = 'yyy'
919              
920             The latter three settings for C are equivalent.
921              
922             Whitespace has no significance. A colon C<:> may be used instead of
923             C<=>. Lines that are blank or empty, and lines that start with C<#>
924             are ignored.
925              
926             Property I consist of one or more identifiers (series of
927             letters and digits) separated by periods.
928              
929             Valid values are a plain text (whitespace, but not trailing, allowed),
930             a single-quoted string, or a double-quoted string. Single-quoted
931             strings allow embedded single-quotes by escaping them with a backslash
932             C<\>. Double-quoted strings allow common escapes like C<\n>, C<\t>,
933             C<\7>, C<\x1f> and C<\x{20cd}>.
934              
935             Note that in plain text backslashes are taken literally. The following
936             alternatives yield the same results:
937              
938             foo = a'\nb
939             foo = 'a\'\nb'
940             foo = "a'\\nb"
941              
942             B All values are strings. These three are equivalent:
943              
944             foo = 1
945             foo = "1"
946             foo = '1'
947              
948             and so are these:
949              
950             foo = Hello World!
951             foo = "Hello World!"
952             foo = 'Hello World!'
953              
954             Quotes are required when you want leading and/or trailing whitespace.
955             Also, the value C is special so if you want to use this as a string
956             it needs to be quoted.
957              
958             Single quotes defer expansion, see L below.
959              
960             =head2 Context
961              
962             When several properties with a common prefix must be set, they can be
963             grouped in a I:
964              
965             foo {
966             bar = blech
967             xxx = "yyy"
968             zzz = 'zyzzy'
969             }
970              
971             Contexts may be nested.
972              
973             =head2 Arrays
974              
975             When a property has a number of sub-properties with keys that are
976             consecutive numbers starting at C<0>, it may be considered as an
977             array. This is only relevant when using the data() method to retrieve
978             a Perl data structure from the set of properties.
979              
980             list {
981             0 = aap
982             1 = noot
983             2 = mies
984             }
985              
986             When retrieved using data(), this returns the Perl structure
987              
988             [ "aap", "noot", "mies" ]
989              
990             For convenience, arrays can be input in several more concise ways:
991              
992             list = [ aap noot mies ]
993             list = [ aap
994             noot
995             mies ]
996              
997             The opening bracket must be followed by one or more values. This will
998             currently not work:
999              
1000             list = [
1001             aap
1002             noot
1003             mies ]
1004              
1005             =head2 Includes
1006              
1007             Property files can include other property files:
1008              
1009             include "myprops.prp"
1010              
1011             All properties that are read from the file are entered in the current
1012             context. E.g.,
1013              
1014             foo {
1015             include "myprops.prp"
1016             }
1017              
1018             will enter all the properties from the file with an additional C
1019             prefix.
1020              
1021             =head2 Expansion
1022              
1023             Property values can be anything. The value will be I before
1024             being assigned to the property unless it is placed between single
1025             quotes C<''>.
1026              
1027             Expansion means:
1028              
1029             =over
1030              
1031             =item *
1032              
1033             A tilde C<~> in what looks like a file name will be replaced by the
1034             value of C<${HOME}>.
1035              
1036             =item *
1037              
1038             If the value contains C<${I}>, I is first looked up in the
1039             current environment. If an environment variable I can be found,
1040             its value is substituted.
1041              
1042             If no suitable environment variable exists, I is looked up as a
1043             property and, if it exists and has a non-empty value, this value is
1044             substituted.
1045              
1046             Otherwise, the C<${I}> part is removed.
1047              
1048             Note that if a property is referred as C<${.I}>, I is
1049             looked up in the current context only.
1050              
1051             B Property lookup is case insensitive, B for the
1052             names of environment variables B on Microsoft Windows
1053             where environment variable names are looked up case insensitive.
1054              
1055             =item *
1056              
1057             If the value contains C<${I:I}>, I is looked up as
1058             described above. If, however, no suitable value can be found, I
1059             is substituted.
1060              
1061             =back
1062              
1063             Expansion is delayed if single quotes are used around the value.
1064              
1065             x = 1
1066             a = ${x}
1067             b = "${x}"
1068             c = '${x}'
1069             x = 2
1070              
1071             Now C and C will be C<'1'>, but C will be C<'2'>.
1072              
1073             Substitution is handled by L. See its
1074             documentation for more power.
1075              
1076             In addition, you can test for a property being defined (not null) by
1077             appending a C to its name.
1078              
1079             result = ${x?|${x|value|empty}|null}
1080              
1081             This will yield C if C is not null and not empty, C
1082             if not null and empty, and C if not defined or defined as null.
1083              
1084             =head1 SEE ALSO
1085              
1086             L, L.
1087              
1088             =head1 BUGS
1089              
1090             Although in production for over 25 years, this module is still
1091             slightly experimental and subject to change.
1092              
1093             =head1 AUTHOR
1094              
1095             Johan Vromans, C<< >>
1096              
1097             =head1 SUPPORT AND DOCUMENTATION
1098              
1099             Development of this module takes place on GitHub:
1100             https://github.com/sciurius/perl-Data-Properties.
1101              
1102             You can find documentation for this module with the perldoc command.
1103              
1104             perldoc Data::Properties
1105              
1106             Please report any bugs or feature requests using the issue tracker on
1107             GitHub.
1108              
1109             =head1 ACKNOWLEDGEMENTS
1110              
1111             This module was initially developed in 1994 as part of the Multihouse
1112             MH-Doc (later: MMDS) software suite. Multihouse kindly waived copyrights.
1113              
1114             In 2002 it was revamped as part of the Compuware OptimalJ development
1115             process. Compuware kindly waived copyrights.
1116              
1117             In 2020 it was updated to support arrays and released to the general
1118             public.
1119              
1120             =head1 COPYRIGHT & LICENSE
1121              
1122             Copyright 1994,2002,2020 Johan Vromans, all rights reserved.
1123              
1124             This program is free software; you can redistribute it and/or modify it
1125             under the same terms as Perl itself.
1126              
1127             =cut
1128              
1129             1; # End of Data::Properties