File Coverage

blib/lib/Parse/PlainConfig/Legacy.pm
Criterion Covered Total %
statement 422 458 92.1
branch 188 246 76.4
condition 46 61 75.4
subroutine 32 33 96.9
pod 13 13 100.0
total 701 811 86.4


line stmt bran cond sub pod time code
1             # Parse::PlainConfig::Legacy -- Parsing Engine Legacy for Parse::PlainConfig
2             #
3             # (c) 2002 - 2016, Arthur Corliss ,
4             #
5             # $Id: lib/Parse/PlainConfig/Legacy.pm, 3.04 2017/02/06 02:58:13 acorliss Exp $
6             #
7             # This software is licensed under the same terms as Perl, itself.
8             # Please see http://dev.perl.org/licenses/ for more information.
9             #
10             #####################################################################
11              
12             #####################################################################
13             #
14             # Environment definitions
15             #
16             #####################################################################
17              
18             package Parse::PlainConfig::Legacy;
19              
20 15     15   77447 use 5.006;
  15         34  
21              
22 15     15   57 use strict;
  15         16  
  15         247  
23 15     15   50 use warnings;
  15         15  
  15         335  
24 15     15   46 use vars qw($VERSION);
  15         18  
  15         1129  
25              
26             ($VERSION) = ( q$Revision: 3.04 $ =~ /(\d+(?:\.(\d+))+)/sm );
27              
28 15     15   5150 use Parse::PlainConfig::Constants qw(:all);
  15         31  
  15         2205  
29 15     15   7845 use Text::ParseWords;
  15         18102  
  15         825  
30 15     15   6444 use Text::Tabs;
  15         9614  
  15         1353  
31 15     15   73 use Carp;
  15         19  
  15         793  
32 15     15   62 use Fcntl qw(:flock);
  15         15  
  15         1381  
33 15     15   4415 use Paranoid;
  15         5466  
  15         881  
34 15     15   5762 use Paranoid::Data;
  15         41585  
  15         786  
35 15     15   84 use Paranoid::Debug;
  15         18  
  15         639  
36 15     15   7781 use Paranoid::Filesystem;
  15         457711  
  15         1471  
37 15     15   132 use Paranoid::Input;
  15         20  
  15         681  
38 15     15   63 use Paranoid::IO qw(:all);
  15         18  
  15         2415  
39 15     15   8134 use Paranoid::IO::Line;
  15         40225  
  15         45396  
40              
41             #####################################################################
42             #
43             # Module code follows
44             #
45             #####################################################################
46              
47             {
48             my $ERROR = '';
49              
50             sub ERROR : lvalue {
51 32     32 1 178 $ERROR;
52             }
53             }
54              
55             sub new {
56              
57             # Purpose: Creates a new object
58             # Returns: Object reference if successful, undef if not
59             # Usage: $obj = Parse::PlainConfig->new(%PARAMS);
60              
61 19     19 1 1850 my $class = shift;
62 19         266 my $self = {
63             CONF => {},
64             ORDER => [],
65             FILE => undef,
66             PARAM_DELIM => ':',
67             LIST_DELIM => ',',
68             HASH_DELIM => '=>',
69             AUTOPURGE => 0,
70             COERCE => {},
71             DEFAULTS => {},
72             SMART_PARSER => 0,
73             PADDING => 2,
74             MAX_BYTES => PPC_DEF_SIZE,
75             MTIME => 0,
76             };
77 19         54 my %args = @_;
78 19         29 my ( $k, $v, $rv );
79              
80 19         109 pdebug( 'entering', PPCDLEVEL1 );
81 19         360 pIn();
82              
83 19         126 bless $self, $class;
84              
85             # Assign all the arguments
86 19         30 $rv = 1;
87 19   50     134 while ( $rv && scalar keys %args ) {
88 12         14 $k = shift @{ [ keys %args ] };
  12         38  
89 12         26 $v = $args{$k};
90 12         23 delete $args{$k};
91 12 50       40 $rv = 0 unless $self->property( $k, $v );
92             }
93              
94 19 50       59 $self = undef unless $rv;
95              
96 19         64 pOut();
97 19         98 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $self );
98              
99 19         452 return $self;
100             }
101              
102             sub property {
103              
104             # Purpose: Gets/sets object property value
105             # Returns: Value of property in Get mode, true/false in set mode
106             # Usage: $value = $obj->property($name);
107             # Usage: $rv = $obj->property($name, $value);
108              
109 58     58 1 3830 my $self = shift;
110 58         101 my @args = @_;
111 58         71 my $arg = $_[0];
112 58         61 my $val = $_[1];
113 58 100       116 my $ival = defined $val ? $val : 'undef';
114 58         50 my $rv = 1;
115 58         57 my ( $k, $v );
116              
117             croak 'Mandatory first argument must be a valid property name'
118 58 100 33     400 unless defined $arg and exists $$self{$arg};
119              
120 57         132 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $arg, $ival );
121 57         1298 pIn();
122              
123 57 100       355 pdebug( 'method is in ' . ( scalar @args == 2 ? 'set' : 'get' ) . ' mode',
124             PPCDLEVEL1 );
125 57         507 $arg = uc $arg;
126              
127             # Validate arguments & value
128 57 100       125 if ( scalar @args == 2 ) {
129              
130 39 100 66     362 if ( $arg eq 'ORDER' ) {
    100 100        
    100          
131              
132             # ORDER must be a list reference
133 2 100       7 unless ( ref $val eq 'ARRAY' ) {
134 1         1 $rv = 0;
135 1         2 Parse::PlainConfig::Legacy::ERROR =
136             pdebug( '%s\'s value must be a list reference',
137             PPCDLEVEL1, $arg );
138             }
139              
140             } elsif ( $arg eq 'CONF' or $arg eq 'COERCE' or $arg eq 'DEFAULTS' ) {
141              
142             # CONF, COERCE, and DEFAULTS must be a hash reference
143 7 100       38 unless ( ref $val eq 'HASH' ) {
144 1         2 $rv = 0;
145 1         3 Parse::PlainConfig::Legacy::ERROR =
146             pdebug( '%s\'s value must be a hash reference',
147             PPCDLEVEL1, $arg );
148             }
149              
150 7 100       20 if ($rv) {
151              
152 6 100       30 if ( $arg eq 'COERCE' ) {
    50          
153              
154             # Validate each key/value pair in COERCE
155 3         12 foreach ( keys %$val ) {
156 6 50       16 $ival = defined $$val{$_} ? $$val{$_} : 'undef';
157 6 100 100     38 unless ( $ival eq 'string'
      100        
158             or $ival eq 'list'
159             or $ival eq 'hash' ) {
160 1         2 Parse::PlainConfig::Legacy::ERROR = pdebug(
161             'coerced data type (%s: %s) not a string, list, or hash',
162             PPCDLEVEL1, $_, $ival
163             );
164 1         3 $rv = 0;
165             }
166             }
167             } elsif ( $arg eq 'DEFAULTS' ) {
168              
169             # Copy over the defaults into CONF (not overriding
170             # existing values)
171 3         4 while ( ( $k, $v ) = each %{ $$self{DEFAULTS} } ) {
  3         16  
172             $$self{CONF}{$k} = { 'Value' => $v }
173 0 0       0 unless exists $$self{CONF}{$k};
174             }
175             }
176             }
177              
178             # TODO: Validate properties like PADDING that have a concrete
179             # TODO: list of valid values?
180              
181             } elsif ( ref $val ne '' ) {
182              
183             # Everything else should be a scalar value
184 1         3 $rv = 0;
185 1         3 Parse::PlainConfig::Legacy::ERROR =
186             pdebug( '%s\'s value must be a scalar value',
187             PPCDLEVEL1, $arg );
188             }
189             }
190              
191             # Set the value if all's kosher
192 57 100       106 if ($rv) {
193 53 100       90 if ( scalar @args == 2 ) {
194              
195             # Assign the value
196 35 100       110 if ( ref $val eq 'ARRAY' ) {
    100          
197              
198             # Copy array contents in
199 1         3 $$self{$arg} = [@$val];
200              
201             } elsif ( ref $val eq 'HASH' ) {
202              
203             # Copy hash contents in
204 5         21 $$self{$arg} = {%$val};
205              
206             } else {
207              
208             # Assign the scalar value
209 29         51 $$self{$arg} = $val;
210             }
211             } else {
212              
213             # Copy the value
214 18 100 100     70 if ( defined $$self{$arg} and ref $$self{$arg} ne '' ) {
215             $rv =
216             ref $$self{$arg} eq 'ARRAY' ? []
217 2 50       11 : ref $$self{$arg} eq 'HASH' ? {}
    100          
218             : undef;
219 2 50       4 if ( defined $rv ) {
220 2 50       9 unless ( deepCopy( $$self{$arg}, $rv ) ) {
221 0         0 Parse::PlainConfig::Legacy::ERROR =
222             pdebug( 'failed to copy data from %s: %s',
223             PPCDLEVEL1, Paranoid::ERROR, $arg );
224             }
225             } else {
226             Parse::PlainConfig::Legacy::ERROR =
227             pdebug( 'I don\'t know how to copy %s (%s)',
228 0         0 PPCDLEVEL1, $$self{$arg}, $arg );
229             }
230             } else {
231 16         60 $rv = $$self{$arg};
232             }
233             }
234             }
235              
236 57         250 pOut();
237 57         268 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
238              
239 57         1079 return $rv;
240             }
241              
242             sub purge {
243              
244             # Purpose: Performs a manual purge of internal data
245             # Returns: True
246             # Usage: $obj->purge;
247              
248 9     9 1 412 my $self = shift;
249 9         10 my ( $k, $v );
250              
251 9         25 pdebug( 'entering', PPCDLEVEL1 );
252 9         119 pIn();
253              
254             # First, purge all existing values
255 9         35 delete @{ $$self{CONF} }{ keys %{ $$self{CONF} } };
  9         76  
  9         36  
256              
257             # Second, apply default values
258 9         20 while ( ( $k, $v ) = each %{ $$self{DEFAULTS} } ) {
  12         46  
259 3         5 $$self{CONF}{$k} = { 'Value' => $v };
260             }
261              
262 9         21 pOut();
263 9         38 pdebug( 'leaving w/rv: 1', PPCDLEVEL1 );
264              
265 9         77 return 1;
266             }
267              
268             sub read {
269              
270             # Purpose: Reads either the passed filename or an internally recorded one
271             # Returns: True or false depending on success of read & parse
272             # Usage: $rv = $obj->read;
273             # Usage: $rv = $obj->read($filename);
274              
275 26     26 1 417 my $self = shift;
276 26   66     97 my $file = shift || $$self{FILE};
277 26         35 my $rv = 0;
278 26         117 my $oldSize = PIOMAXFSIZE;
279 26         78 my ( $line, @lines );
280              
281 26 50       72 croak 'Optional first argument must be a defined filename or the FILE '
282             . 'property must be set'
283             unless defined $file;
284              
285 26         76 pdebug( 'entering w/(%s)', PPCDLEVEL1, $file );
286 26         460 pIn();
287              
288             # Reset the error string and update the internal filename
289 26         201 Parse::PlainConfig::Legacy::ERROR = '';
290 26         46 $$self{FILE} = $file;
291              
292             # Temporarily set the specified size limit
293 26         70 PIOMAXFSIZE = $$self{MAX_BYTES};
294              
295             # Store the file's current mtime
296 26         641 $$self{MTIME} = ( stat $file )[MTIME];
297              
298 26 50       139 if ( detaint( $file, 'filename' ) ) {
299 26 100       3817 if ( slurp( $file, @lines, 1 ) ) {
300              
301             # Empty the current config hash and key order
302 25 100       37838 $self->purge if $$self{AUTOPURGE};
303              
304             # Parse the rc file's lines
305 25         125 $rv = $self->_parse(@lines);
306              
307             } else {
308 1         1335 Parse::PlainConfig::Legacy::ERROR =
309             pdebug( Paranoid::ERROR, PPCDLEVEL1 );
310             }
311             } else {
312 0         0 Parse::PlainConfig::Legacy::ERROR =
313             pdebug( 'Filename failed detaint check', PPCDLEVEL1 );
314             }
315              
316             # Restore old size limit
317 26         111 PIOMAXFSIZE = $oldSize;
318              
319 26         90 pOut();
320 26         165 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
321              
322             # Return the result code
323 26         570 return $rv;
324             }
325              
326             sub readIfNewer ($) {
327              
328             # Purpose: Performs a file read/parse if the file is newer than last read
329             # Returns: 1 if read/parse was successful, 2 if file is the same age, 0
330             # on any errors
331             # Usage: $rv = $obj->readIfNewer;
332              
333 3     3 1 4001371 my $self = shift;
334 3         16 my $file = $$self{FILE};
335 3         8 my $omtime = $$self{MTIME};
336 3         7 my $rv = 0;
337 3         7 my $mtime;
338              
339 3 50       86 croak 'The FILE property must be set' unless defined $file;
340              
341 3         24 pdebug( 'entering w/(%s)', PPCDLEVEL1, $file );
342 3         221 pIn();
343              
344             # Try to read the file
345 3 100 66     168 if ( -e $file && -r _ ) {
346              
347             # File exists and appears to be readable, get the mtime
348 2         11 $mtime = ( stat _ )[MTIME];
349 2         10 pdebug( 'current mtime: %s last: %s', PPCDLEVEL2, $mtime, $omtime );
350              
351             # Read the file if it's newer, or return 2
352 2 100       82 $rv = $mtime > $omtime ? $self->read : 2;
353              
354             } else {
355              
356             # Report errors
357 1         7 Parse::PlainConfig::Legacy::ERROR =
358             pdebug( 'file (%s) does not exist or is not readable',
359             PPCDLEVEL1, $file );
360             }
361              
362 3         12 pOut();
363 3         24 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
364              
365             # Return the result code
366 3         80 return $rv;
367             }
368              
369             sub write {
370              
371             # Purpose: Writes the file to disk
372             # Returns: True/False depending on success of write
373             # Usage: $rv = $obj->write;
374             # Usage: $rv = $obj->write($filename);
375              
376 5     5 1 89 my $self = shift;
377 5   66     29 my $file = shift || $$self{FILE};
378 5         8 my $padding = shift;
379 5         12 my $conf = $$self{CONF};
380 5         10 my $order = $$self{ORDER};
381 5         12 my $coerce = $$self{COERCE};
382 5         17 my $smart = $$self{SMART_PARSER};
383 5         10 my $paramDelim = $$self{PARAM_DELIM};
384 5         12 my $hashDelim = $$self{HASH_DELIM};
385 5         9 my $listDelim = $$self{LIST_DELIM};
386 5         7 my $rv = 0;
387 5         10 my $tw = DEFAULT_TW;
388 5         88 my $delimRegex = qr/(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
389 5         12 my ( @forder, $type, $param, $value, $description, $entry, $out );
390 0         0 my ( $tmp, $tvalue, $lines, $fh );
391              
392             # TODO: Implement non-blocking flock support
393             # TODO: Store read padding and/or use PADDING property value
394              
395 5 50       20 croak 'Optional first argument must be a defined filename or the FILE '
396             . 'property must be set'
397             unless defined $file;
398              
399 5 50       16 $padding = 2 unless defined $padding;
400 5 100       17 $tw -= 2 unless $smart;
401              
402 5         20 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $file, $padding );
403 5         147 pIn();
404              
405             # Pad the delimiter as specified
406 5 50       59 $paramDelim =
    50          
    50          
407             $padding == 0 ? $paramDelim
408             : $padding == 1 ? " $paramDelim"
409             : $padding == 2 ? "$paramDelim "
410             : " $paramDelim ";
411 5         17 pdebug( 'PARAM_DELIM w/padding is \'%s\'', PPCDLEVEL2, $paramDelim );
412              
413             # Create a list of parameters for output
414 5         116 @forder = @$order;
415 5         59 foreach $tmp ( sort keys %$conf ) {
416 51 100       867 push @forder, $tmp
417             unless grep /^\Q$tmp\E$/sm, @forder;
418             }
419 5         27 pdebug( "order of params to be written:\n\t%s", PPCDLEVEL2, @forder );
420              
421             # Compose the new output
422 5         210 $out = '';
423 5         13 foreach $param (@forder) {
424              
425             # Determine the datatype
426 51 50       123 $value = exists $$conf{$param} ? $$conf{$param}{Value} : '';
427             $description =
428 51 50       83 exists $$conf{$param} ? $$conf{$param}{Description} : '';
429             $type =
430 51 100       155 exists $$coerce{$param} ? $$coerce{$param}
    100          
    100          
431             : ref $value eq 'HASH' ? 'hash'
432             : ref $value eq 'ARRAY' ? 'list'
433             : 'string';
434 51         101 pdebug( 'adding %s param (%s)', PPCDLEVEL2, $type, $param );
435              
436             # Append the comments
437 51         1064 $out .= $description;
438 51 50       179 $out .= "\n" unless $out =~ /\n$/sm;
439              
440             # Start the new entry with the parameter name and delimiter
441 51         53 $entry = "$param$paramDelim";
442              
443             # Append the value, taking into consideration the smart parser
444             # and coercion settings
445 51 100       97 if ( $type eq 'string' ) {
    100          
446              
447             # String type
448 29         39 $tvalue = $value;
449 29 100 66     86 unless ( $smart && exists $$coerce{$param} ) {
450 19         28 $tvalue =~ s/"/\\"/smg;
451 19 100       113 $tvalue = "\"$tvalue\"" if $tvalue =~ /$delimRegex/sm;
452             }
453 29         38 $lines = "$entry$tvalue";
454              
455             } elsif ( $type eq 'list' ) {
456              
457             # List type
458 17         41 $tvalue = [@$value];
459 17         37 foreach (@$tvalue) {
460 54         69 s/"/\\"/smg;
461 54 100 66     116 if ( $smart && exists $$coerce{$param} ) {
462 20 100       61 $_ = "\"$_\"" if /\Q$listDelim\E/sm;
463             } else {
464 34 100       163 $_ = "\"$_\"" if /$delimRegex/sm;
465             }
466             }
467 17         48 $lines = $entry . join " $listDelim ", @$tvalue;
468              
469             } else {
470              
471             # Hash type
472 5         31 $tvalue = {%$value};
473 5         22 foreach ( keys %$tvalue ) {
474 20         17 $tmp = $_;
475 20         25 $tmp =~ s/"/\\"/smg;
476 20 50       68 $tmp = "\"$tmp\"" if /$delimRegex/sm;
477 20 50       33 if ( $tmp ne $_ ) {
478 0         0 $$tvalue{$tmp} = $$tvalue{$_};
479 0         0 delete $$tvalue{$_};
480             }
481 20         27 $$tvalue{$tmp} =~ s/"/\\"/smg;
482             $$tvalue{$tmp} = "\"$$tvalue{$tmp}\""
483 20 100       81 if $$tvalue{$tmp} =~ /$delimRegex/sm;
484             }
485             $lines = $entry
486             . join " $listDelim ",
487 5         28 map {"$_ $hashDelim $$tvalue{$_}"} sort keys %$tvalue;
  20         50  
488             }
489              
490             # wrap the output to the column width and append to the output
491 51 100       117 $out .= _wrap( '', "\t", $tw, ( $smart ? "\n" : "\\\n" ), $lines );
492 51 50       211 $out .= "\n" unless $out =~ /\n$/sm;
493             }
494              
495             # Write the file
496 5 50       49 if ( detaint( $file, 'filename' ) ) {
497 5 50       1247 if ( open $fh, '>', $file ) {
498              
499             # Write the file
500 5         37 flock $fh, LOCK_EX;
501 5 50       54 if ( print $fh $out ) {
502 5         9 $rv = 1;
503             } else {
504 0         0 Parse::PlainConfig::Legacy::ERROR = $!;
505             }
506 5         221 flock $fh, LOCK_UN;
507 5         187 close $fh;
508              
509             # Store the new mtime on successful writes
510 5 50       88 $$self{MTIME} = ( stat $file )[MTIME] if $rv;
511              
512             } else {
513              
514             # Report the errors
515 0         0 Parse::PlainConfig::Legacy::ERROR =
516             pdebug( 'error writing file: %s', PPCDLEVEL1, $! );
517             }
518             } else {
519              
520             # Detainting filename failed
521 0         0 Parse::PlainConfig::Legacy::ERROR =
522             pdebug( 'illegal characters in filename: %s', PPCDLEVEL1, $file );
523             }
524              
525 5         23 pOut();
526 5         31 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
527              
528 5         190 return $rv;
529             }
530              
531             sub parameters {
532              
533             # Purpose: Returns a list of all parsed parameters
534             # Returns: List of parameter names with configure values
535             # Usage: @params = $obj->parameters;
536              
537 11     11 1 417 my $self = shift;
538 11         17 my @parameters = keys %{ $$self{CONF} };
  11         55  
539              
540 11         46 pdebug( 'called method -- rv: %s', PPCDLEVEL1, @parameters );
541              
542 11         385 return @parameters;
543             }
544              
545             sub parameter {
546              
547             # Purpose: Gets/sets named parameter
548             # Returns: True/false in set mode, Parameter value in get mode
549             # Usage: $rv = $obj->parameter($name);
550             # Usage: $rv = $obj->parameter($name, $value);
551              
552 77     77 1 3003107 my $self = shift;
553 77         136 my @args = @_;
554 77         83 my $param = $args[0];
555 77         69 my $value = $args[1];
556 77 100       128 my $ivalue = defined $value ? $value : 'undef';
557 77         82 my $conf = $$self{CONF};
558 77         80 my $listDelim = $$self{LIST_DELIM};
559 77         62 my $hashDelim = $$self{HASH_DELIM};
560 77         73 my $paramDelim = $$self{PARAM_DELIM};
561             my $coerceType =
562             exists $$self{COERCE}{$param}
563 77 100       139 ? $$self{COERCE}{$param}
564             : 'undef';
565 77         71 my $defaults = $$self{DEFAULTS};
566 77         67 my $rv = 1;
567 77         477 my $delimRegex = qr/(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
568 77         65 my ( $finalValue, @elements );
569              
570             # TODO: Consider storing a list/hash padding value as well, for use
571             # TODO: in coercion to string.
572              
573 77 50       132 croak 'Mandatory firest argument must be a defined parameter name'
574             unless defined $param;
575              
576 77         144 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $param, $ivalue );
577 77         1558 pIn();
578              
579 77 100       331 if ( scalar @args == 2 ) {
580 37         63 pdebug( 'method in set mode', PPCDLEVEL1 );
581              
582             # Create a blank record if it hasn't been defined yet
583             $$conf{$param} = {
584             Value => '',
585             Description => '',
586             }
587 37 100       351 unless exists $$conf{$param};
588              
589             # Start processing value assignment
590 37 100       73 if ( $coerceType ne 'undef' ) {
591 32         51 pdebug( 'coercing into %s', PPCDLEVEL2, $coerceType );
592              
593             # Parameter has a specific data type to be coerced into
594 32 100 100     636 if ( $coerceType eq 'string' && ref $value ne '' ) {
    100 100        
    100 100        
595              
596             # Coerce values into strings
597 3 100       13 if ( ref $value eq 'ARRAY' ) {
    50          
598              
599             # Convert lists into a string using the list delimiter
600 2         7 foreach (@$value) {
601 7         11 s/"/\\"/smg;
602 7 50       85 $_ = "\"$_\"" if /\Q$listDelim\E/sm;
603             }
604 2         11 $finalValue = join " $listDelim ", @$value;
605              
606             } elsif ( ref $value eq 'HASH' ) {
607              
608             # Convert hashes into a string using the hash & list
609             # delimiters
610 1         8 foreach ( sort keys %$value ) {
611 2         2 $ivalue = $_;
612 2         3 $ivalue =~ s/"/\\"/smg;
613 2 50       24 $ivalue = "\"$ivalue\""
614             if /(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
615 2 50       6 $$value{$_} = '' unless defined $$value{$_};
616             $$value{$_} = "\"$$value{$_}\""
617 2 50       16 if $$value{$_} =~
618             /(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
619             push @elements,
620             join " $hashDelim ", $_,
621 2 50       8 ( defined $$value{$_} ? $$value{$_} : '' );
622             }
623 1         3 $finalValue = join " $listDelim ", @elements;
624              
625             } else {
626              
627             # Try to stringify everything else
628 0         0 $finalValue = "$value";
629             }
630              
631             } elsif ( $coerceType eq 'list' && ref $value ne 'ARRAY' ) {
632              
633             # Coerce value into a list
634 3 100       12 if ( ref $value eq 'HASH' ) {
    50          
635              
636             # Convert hashes into a list
637 2         2 $finalValue = [];
638 2         15 foreach ( sort keys %$value ) {
639 4         7 push @$finalValue, $_, $$value{$_};
640             }
641              
642             } elsif ( ref $value eq '' ) {
643              
644             # Convert strings into a list
645 1         8 $self->_parse(
646             split /\n/sm,
647             "$$conf{$param}{Description}\n"
648             . "$param $paramDelim $value"
649             );
650 1         2 $finalValue = $$conf{$param}{Value};
651              
652             } else {
653              
654             # Stringify everything else and put it into an array
655 0         0 $finalValue = ["$value"];
656             }
657              
658             } elsif ( $coerceType eq 'hash' && ref $value ne 'HASH' ) {
659              
660             # Coerce value into a hash
661 3 100       20 if ( ref $value eq 'ARRAY' ) {
    50          
662              
663             # Convert a list into a hash using every two elements
664             # as a key/value pair
665 1 50       10 push @$value, ''
666             unless int( scalar @$value / 2 ) ==
667             scalar @$value / 2;
668 1         4 $finalValue = {@$value};
669              
670             } elsif ( ref $value eq '' ) {
671              
672             # Convert strings into a hash
673 2         17 $self->_parse(
674             split /\n/sm,
675             "$$conf{$param}{Description}\n"
676             . "$param $paramDelim $value"
677             );
678 2         6 $finalValue = $$conf{$param}{Value};
679              
680             } else {
681              
682             # Stringify everything else and put the value into the
683             # hash key
684 0         0 $finalValue = { "$value" => '' };
685             }
686              
687             } else {
688              
689             # No coercion is necessary
690 23         24 $finalValue = $value;
691             }
692              
693             } else {
694 5         16 pdebug( 'no coercion to do', PPCDLEVEL2 );
695 5         45 $finalValue = $value;
696             }
697 37         53 $$conf{$param}{Value} = $finalValue;
698              
699             } else {
700 40         62 pdebug( 'method in retrieve mode', PPCDLEVEL1 );
701             $rv =
702             exists $$conf{$param} ? $$conf{$param}{Value}
703 40 50       355 : exists $$defaults{$param} ? $$defaults{$param}
    100          
704             : undef;
705             }
706              
707 77         120 pOut();
708 77         282 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
709              
710 77 100       1409 return ref $rv eq 'HASH' ? (%$rv) : ref $rv eq 'ARRAY' ? (@$rv) : $rv;
    100          
711             }
712              
713             sub coerce {
714              
715             # Purpose: Assigns the passed list to a data type and attempts to
716             # coerce each existing value into that data type.
717             # Returns: True or false.
718             # Usage: $rv = $obj->coerce($type, @fields);
719              
720 21     21 1 406 my $self = shift;
721 21         24 my $type = shift;
722 21 50       41 my $itype = defined $type ? $type : 'undef';
723 21         38 my @params = @_;
724 21         22 my $rv = 1;
725              
726 21 50 100     102 croak 'Mandatory first argument must be "string", "list", or "hash"'
      66        
727             unless $itype eq 'string'
728             or $itype eq 'list'
729             or $itype eq 'hash';
730 21 50       41 croak 'Remaining arguments must be defined parameter names'
731             unless @params;
732              
733 21         54 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $type, @params );
734 21         438 pIn();
735              
736 21         85 foreach (@params) {
737 45 50       58 if (defined) {
738              
739             # Mark the parameter
740 45         63 $$self{COERCE}{$_} = $type;
741             $self->parameter( $_, $$self{CONF}{$_}{Value} )
742 45 100       112 if exists $$self{CONF}{$_};
743             } else {
744              
745             # Report undefined parameter names
746 0         0 Parse::PlainConfig::Legacy::ERROR =
747             pdebug( 'passed undefined parameter names to coerce',
748             PPCDLEVEL1 );
749 0         0 $rv = 0;
750             }
751             }
752              
753 21         39 pOut();
754 21         82 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
755              
756 21         364 return $rv;
757             }
758              
759             sub describe {
760              
761             # Purpose: Assigns descriptive comments to specific parameters
762             # Returns: True
763             # Usage: $obj->describe(%descriptions);
764              
765 0     0 1 0 my $self = shift;
766 0         0 my $conf = $$self{CONF};
767 0         0 my $coerce = $$self{COERCE};
768 0         0 my %new = (@_);
769              
770 0         0 pdebug( 'entering', PPCDLEVEL1 );
771 0         0 pIn();
772              
773             # TODO: Consider allowing comment tags to be specified
774              
775             # TODO: Consider line splitting and comment tag prepending where
776             # TODO: it's not already done.
777              
778 0         0 foreach ( keys %new ) {
779 0         0 pdebug( '%s is described as \'%s\'', PPCDLEVEL1, $_, $new{$_} );
780 0 0       0 unless ( exists $$conf{$_} ) {
781 0         0 $$conf{$_} = {};
782 0 0       0 if ( exists $$coerce{$_} ) {
783             $$conf{$_}{Value} =
784             $$coerce{$_} eq 'list' ? []
785 0 0       0 : $$coerce{$_} eq 'hash' ? {}
    0          
786             : '';
787             } else {
788 0         0 $$conf{$_}{Value} = '';
789             }
790             }
791 0         0 $$conf{$_}{Description} = $new{$_};
792             }
793              
794 0         0 pOut();
795 0         0 pdebug( 'leaving w/rv: 1', PPCDLEVEL1 );
796              
797 0         0 return 1;
798             }
799              
800             sub order {
801              
802             # Purpose: Gets/sets order of parameters in file
803             # Returns: Ordered list of named parameters
804             # Usage: @params = $obj->order;
805             # Usage: @params = $obj->order(@newOrder);
806              
807 2     2 1 10 my $self = shift;
808 2         3 my $order = $$self{ORDER};
809 2         5 my @new = (@_);
810              
811 2         3 pdebug( 'entering w/(%s)', PPCDLEVEL1, @new );
812              
813 2 100       37 @$order = (@new) if scalar @new;
814              
815 2         4 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, @$order );
816              
817 2         48 return @$order;
818             }
819              
820             sub _parse {
821              
822             # Purpose: Parses the passed list of lines and extracts comments,
823             # fields, and values and storing everything into the CONF
824             # hash
825             # Returns: True or false
826             # Usage: $rv = $obj->_parse(@lines);
827              
828 28     28   38 my $self = shift;
829 28         49 my $conf = $$self{CONF};
830 28         45 my $order = $$self{ORDER};
831 28         40 my $smart = $$self{SMART_PARSER};
832 28         43 my $tagDelim = $$self{PARAM_DELIM};
833 28         39 my $hashDelim = $$self{HASH_DELIM};
834 28         40 my $listDelim = $$self{LIST_DELIM};
835 28         124 my @lines = @_;
836 28         81 my $rv = 1;
837 28         30 my ( $i, $line, $comment, $entry, $field, $value );
838 0         0 my ( $indentation, $data, $saveEntry );
839              
840             # Make sure some of the properties are sane
841             croak 'LIST_DELIM and HASH_DELIM cannot be the same character sequence!'
842 28 50       85 unless $$self{LIST_DELIM} ne $$self{HASH_DELIM};
843              
844 28         62 pdebug( 'entering', PPCDLEVEL2 );
845 28         265 pIn();
846              
847             # Flatten lines using an explicit backslash
848 28         167 for ( $i = 0; $i <= $#lines; $i++ ) {
849              
850             # Let's disable uninitialized warnings since there's a few
851             # places here we really don't care
852 15     15   112 no warnings 'uninitialized';
  15         23  
  15         15811  
853              
854 685 100       1410 if ( $lines[$i] =~ /\\\s*$/sm ) {
855 174         310 pdebug( 'joining lines %s & %s', PPCDLEVEL2, $i + 1, $i + 2 );
856              
857             # Lop off the trailing whitespace and backslash, preserving
858             # only one space on the assumption that if it's there it's a
859             # natural word break.
860 174         4448 $lines[$i] =~ s/(\s)?\s*\\\s*$/$1/sm;
861              
862             # Concatenate the following line (if there is one) after stripping
863             # off preceding whitespace
864 174 50       307 if ( $i < $#lines ) {
865 174         393 $lines[ $i + 1 ] =~ s/^\s+//sm;
866 174         233 $lines[$i] .= $lines[ $i + 1 ];
867 174         174 splice @lines, $i + 1, 1;
868 174         289 --$i;
869             }
870             }
871             }
872              
873             $saveEntry = sub {
874              
875             # Saves the extracted data into the conf hash and resets
876             # the vars.
877              
878 244     244   203 my ($type);
879              
880 244         1577 ( $field, $value ) =
881             ( $entry =~ /^\s*([^$tagDelim]+?)\s*\Q$tagDelim\E\s*(.*)$/sm );
882 244         478 pdebug( "saving data:\n\t(%s: %s)", PPCDLEVEL2, $field, $value );
883              
884 244 100       4325 if ( exists $$self{COERCE}{$field} ) {
885              
886             # Get the field data type from COERCE
887 27         38 $type = $$self{COERCE}{$field};
888              
889             } else {
890              
891             # Otherwise, try to autodetect data type
892 217 100       1064 $type =
    100          
893             scalar quotewords( qr/\s*\Q$hashDelim\E\s*/sm, 0, $value ) > 1
894             ? 'hash'
895             : scalar quotewords( qr/\s*\Q$listDelim\E\s*/sm, 0, $value ) >
896             1 ? 'list'
897             : 'scalar';
898             }
899 244         47845 pdebug( 'detected type of %s is %s', PPCDLEVEL2, $field, $type );
900              
901             # For all data types we should strip leading/trailing whitespace.
902             # If they really want it they should quote it.
903 244 100       6565 $value =~ s/^\s+|\s+$//smg unless $type eq 'scalar';
904              
905             # We'll apply quotewords to scalar values only if the smart parser is
906             # not being used or if we're not coercing all values into scalar for
907             # this field.
908             #
909             # I hate having to do this but I was an idiot in the previous versions
910             # and this is necessary for backwards compatibility.
911 244 100       557 if ( $type eq 'scalar' ) {
    100          
    100          
912             $value = join '',
913             quotewords( qr/\s*\Q$listDelim\E\s*/sm, 0, $value )
914             unless $smart
915             && exists $$self{COERCE}{$field}
916 112 50 66     848 && $$self{COERCE}{$field} eq 'scalar';
      33        
917             } elsif ( $type eq 'hash' ) {
918 30         359 $value = {
919             quotewords(
920             qr/\s*(?:\Q$hashDelim\E|\Q$listDelim\E)\s*/sm, 0,
921             $value
922             ) };
923             } elsif ( $type eq 'list' ) {
924 88         552 $value = [ quotewords( qr/\s*\Q$listDelim\E\s*/sm, 0, $value ) ];
925             }
926              
927             # Create the parameter record
928 244         18415 $$conf{$field} = {};
929 244         414 $$conf{$field}{Value} = $value;
930 244         295 $$conf{$field}{Description} = $comment;
931 244 100       3573 push @$order, $field unless grep /^\Q$field\E$/sm, @$order;
932 244         659 $comment = $entry = '';
933 28         174 };
934              
935             # Process lines
936 28         43 $comment = $entry = '';
937 28         181 while ( defined( $line = shift @lines ) ) {
938              
939 511 100       1696 if ( $line =~ /^\s*(?:#.*)?$/sm ) {
940              
941             # Grab comments and blank lines
942 208         357 pdebug( "comment/blank line:\n\t%s", PPCDLEVEL3, $line );
943              
944             # First save previous entries if $entry has content
945 208 100 50     3452 &$saveEntry() and $i = 0 if length $entry;
946              
947             # Save the comments
948 208 100       654 $comment = length($comment) > 0 ? "$comment$line\n" : "$line\n";
949              
950             } else {
951              
952             # Grab configuration lines
953              
954             # If this is the first line of a new entry and there's no
955             # PARAM_DELIM skip the line -- something must be wrong.
956             #
957             # TODO: Error out/raise exception
958 303 50 66     1374 unless ( length $entry || $line =~ /\Q$tagDelim\E/sm ) {
959 0         0 pdebug( "skipping spurious text:\n\t%s", PPCDLEVEL3, $line );
960 0         0 next;
961             }
962              
963             # Grab indentation characters and line content
964 303         819 ( $indentation, $data ) = ( $line =~ /^(\s*)(.+)$/sm );
965 303         564 pdebug( "data line:\n\t%s", PPCDLEVEL3, $data );
966              
967 303 100       4839 if ($smart) {
968              
969             # Smart parsing is enabled
970              
971 121 100       135 if ( length $entry ) {
972              
973             # There's current content
974              
975 96 100       112 if ( length($indentation) > $i ) {
976              
977             # If new indentation is greater than original
978             # indentation we concatenate the lines as a
979             # continuation
980 59         126 $entry .= $data;
981              
982             } else {
983              
984             # Otherwise we treat this a a new entry, so we save
985             # the old and store the current
986 37         45 &$saveEntry();
987 37         85 ( $i, $entry ) = ( length($indentation), $data );
988             }
989              
990             } else {
991              
992             # No current content, so just store the current data and
993             # continue processing
994 25         109 ( $i, $entry ) = ( length($indentation), $data );
995             }
996              
997             } else {
998              
999             # Smart parsing is disabled, so treat every line as a new
1000             # entry
1001 182         203 $entry = $data;
1002 182         233 &$saveEntry();
1003             }
1004             }
1005             }
1006 28 100       96 &$saveEntry() if length $entry;
1007              
1008 28         78 pOut();
1009 28         146 pdebug( 'leaving w/rv: %s', PPCDLEVEL2, $rv );
1010              
1011 28         772 return $rv;
1012             }
1013              
1014             sub _wrap {
1015              
1016             # Purpose: Parses the passed line of test and inserts indentation and
1017             # line breaks as needed
1018             # Returns: Formated string
1019             # Usage: $out = $obj->_wrap($fIndent, $sIndent, $textWidth,
1020             # $lineBreak, $paragraph);
1021              
1022 51     51   85 my $firstIndent = shift;
1023 51         47 my $subIndent = shift;
1024 51         37 my $textWidth = shift;
1025 51         43 my $lineBreak = shift;
1026 51         54 my $paragraph = shift;
1027 51         42 my ( @lines, $segment, $output );
1028              
1029 51         94 pdebug( "entering w/(%s)(%s)(%s)(%s):\n\t%s",
1030             PPCDLEVEL2, $firstIndent, $subIndent, $textWidth, $lineBreak,
1031             $paragraph );
1032 51         1433 pIn();
1033              
1034             # Expand tabs in everything -- sorry everyone
1035 51         245 ($firstIndent) = expand($firstIndent);
1036 51         436 ($subIndent) = expand($subIndent);
1037 51         1009 $paragraph = expand("$firstIndent$paragraph");
1038              
1039 51         508 $lines[0] = '';
1040 51         133 while ( length($paragraph) > 0 ) {
1041              
1042             # Get the next string segment (splitting on whitespace)
1043 614         1345 ($segment) = ( $paragraph =~ /^(\s*\S+\s?)/sm );
1044              
1045 614 100       876 if ( length $segment <= $textWidth - length $lines[-1] ) {
    100          
1046              
1047             # The segment will fit appended to the current line,
1048             # concatenate it
1049 576         517 $lines[-1] .= $segment;
1050              
1051             } elsif ( length $segment <= $textWidth - length $subIndent ) {
1052              
1053             # The segment will fit into the next line, add it
1054 23         27 $lines[-1] .= $lineBreak;
1055 23         38 push @lines, "$subIndent$segment";
1056              
1057             } else {
1058              
1059             # Else, split on the text width
1060 15 50       42 $segment =
1061             $#lines == 0
1062             ? substr $paragraph, 0, $textWidth
1063             : substr $paragraph, 0, $textWidth - length $subIndent;
1064 15 50       41 if ( length $segment > $textWidth - length $lines[-1] ) {
1065 15         24 $lines[-1] .= $lineBreak;
1066 15 50       31 push @lines,
1067             ( $#lines == 0 ? $segment : "$subIndent$segment" );
1068             } else {
1069 0         0 $lines[-1] .= $segment;
1070             }
1071             }
1072 614         387 $paragraph =~ s/^.{@{[length($segment)]}}//sm;
  614         5631  
1073             }
1074 51         61 $lines[-1] .= "\n";
1075              
1076 51         87 $output = join '', @lines;
1077              
1078 51         103 pOut();
1079 51         222 pdebug( "leaving w/rv:\n%s", PPCDLEVEL2, $output );
1080              
1081 51         1116 return $output;
1082             }
1083              
1084             sub hasParameter {
1085              
1086             # Purpose: Checks to see if the specified parameter exists as a
1087             # configuration parameter
1088             # Returns: True or false
1089             # Usage: $rv = $obj->hasParameter($name);
1090              
1091 3     3 1 42 my $self = shift;
1092 3         3 my $param = shift;
1093 3         2 my $rv = 0;
1094 3         3 my @params = ( keys %{ $self->{CONF} }, keys %{ $self->{DEFAULTS} }, );
  3         7  
  3         9  
1095              
1096 3 50       6 croak 'Mandatory first parameter must be a defined parameter name'
1097             unless defined $param;
1098              
1099 3         7 pdebug( 'entering w/(%s)', PPCDLEVEL1, $param );
1100 3         44 pIn();
1101              
1102 3         56 $rv = scalar grep /^\Q$param\E$/sm, @params;
1103              
1104 3         5 pOut();
1105 3         10 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
1106              
1107 3         46 return $rv;
1108             }
1109              
1110             1;
1111              
1112             __END__