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.05 2017/02/06 10:36:37 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   77128 use 5.006;
  15         34  
21              
22 15     15   51 use strict;
  15         15  
  15         236  
23 15     15   42 use warnings;
  15         15  
  15         318  
24 15     15   44 use vars qw($VERSION);
  15         13  
  15         1116  
25              
26             ($VERSION) = ( q$Revision: 3.05 $ =~ /(\d+(?:\.(\d+))+)/sm );
27              
28 15     15   4866 use Parse::PlainConfig::Constants qw(:all);
  15         60  
  15         2263  
29 15     15   6394 use Text::ParseWords;
  15         15586  
  15         763  
30 15     15   5911 use Text::Tabs;
  15         8643  
  15         1232  
31 15     15   70 use Carp;
  15         15  
  15         667  
32 15     15   55 use Fcntl qw(:flock);
  15         16  
  15         1184  
33 15     15   3455 use Paranoid;
  15         4753  
  15         538  
34 15     15   5384 use Paranoid::Data;
  15         38677  
  15         699  
35 15     15   80 use Paranoid::Debug;
  15         15  
  15         593  
36 15     15   7301 use Paranoid::Filesystem;
  15         444190  
  15         1593  
37 15     15   125 use Paranoid::Input;
  15         21  
  15         720  
38 15     15   64 use Paranoid::IO qw(:all);
  15         18  
  15         2282  
39 15     15   7308 use Paranoid::IO::Line;
  15         41261  
  15         44601  
40              
41             #####################################################################
42             #
43             # Module code follows
44             #
45             #####################################################################
46              
47             {
48             my $ERROR = '';
49              
50             sub ERROR : lvalue {
51 32     32 1 204 $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 1207 my $class = shift;
62 19         233 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         53 my %args = @_;
78 19         25 my ( $k, $v, $rv );
79              
80 19         94 pdebug( 'entering', PPCDLEVEL1 );
81 19         344 pIn();
82              
83 19         115 bless $self, $class;
84              
85             # Assign all the arguments
86 19         27 $rv = 1;
87 19   50     122 while ( $rv && scalar keys %args ) {
88 12         13 $k = shift @{ [ keys %args ] };
  12         34  
89 12         21 $v = $args{$k};
90 12         20 delete $args{$k};
91 12 50       34 $rv = 0 unless $self->property( $k, $v );
92             }
93              
94 19 50       60 $self = undef unless $rv;
95              
96 19         52 pOut();
97 19         115 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $self );
98              
99 19         450 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 4499 my $self = shift;
110 58         107 my @args = @_;
111 58         67 my $arg = $_[0];
112 58         58 my $val = $_[1];
113 58 100       123 my $ival = defined $val ? $val : 'undef';
114 58         63 my $rv = 1;
115 58         59 my ( $k, $v );
116              
117             croak 'Mandatory first argument must be a valid property name'
118 58 100 33     438 unless defined $arg and exists $$self{$arg};
119              
120 57         146 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $arg, $ival );
121 57         1548 pIn();
122              
123 57 100       390 pdebug( 'method is in ' . ( scalar @args == 2 ? 'set' : 'get' ) . ' mode',
124             PPCDLEVEL1 );
125 57         581 $arg = uc $arg;
126              
127             # Validate arguments & value
128 57 100       128 if ( scalar @args == 2 ) {
129              
130 39 100 66     365 if ( $arg eq 'ORDER' ) {
    100 100        
    100          
131              
132             # ORDER must be a list reference
133 2 100       8 unless ( ref $val eq 'ARRAY' ) {
134 1         3 $rv = 0;
135 1         4 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       27 unless ( ref $val eq 'HASH' ) {
144 1         2 $rv = 0;
145 1         6 Parse::PlainConfig::Legacy::ERROR =
146             pdebug( '%s\'s value must be a hash reference',
147             PPCDLEVEL1, $arg );
148             }
149              
150 7 100       18 if ($rv) {
151              
152 6 100       28 if ( $arg eq 'COERCE' ) {
    50          
153              
154             # Validate each key/value pair in COERCE
155 3         11 foreach ( keys %$val ) {
156 6 50       14 $ival = defined $$val{$_} ? $$val{$_} : 'undef';
157 6 100 100     40 unless ( $ival eq 'string'
      100        
158             or $ival eq 'list'
159             or $ival eq 'hash' ) {
160 1         5 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         15  
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         2 $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       94 if ( scalar @args == 2 ) {
194              
195             # Assign the value
196 35 100       105 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         22 $$self{$arg} = {%$val};
205              
206             } else {
207              
208             # Assign the scalar value
209 29         53 $$self{$arg} = $val;
210             }
211             } else {
212              
213             # Copy the value
214 18 100 100     90 if ( defined $$self{$arg} and ref $$self{$arg} ne '' ) {
215             $rv =
216             ref $$self{$arg} eq 'ARRAY' ? []
217 2 50       10 : ref $$self{$arg} eq 'HASH' ? {}
    100          
218             : undef;
219 2 50       4 if ( defined $rv ) {
220 2 50       10 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         63 $rv = $$self{$arg};
232             }
233             }
234             }
235              
236 57         281 pOut();
237 57         282 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
238              
239 57         1361 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 263 my $self = shift;
249 9         13 my ( $k, $v );
250              
251 9         22 pdebug( 'entering', PPCDLEVEL1 );
252 9         93 pIn();
253              
254             # First, purge all existing values
255 9         34 delete @{ $$self{CONF} }{ keys %{ $$self{CONF} } };
  9         68  
  9         32  
256              
257             # Second, apply default values
258 9         17 while ( ( $k, $v ) = each %{ $$self{DEFAULTS} } ) {
  12         46  
259 3         5 $$self{CONF}{$k} = { 'Value' => $v };
260             }
261              
262 9         18 pOut();
263 9         43 pdebug( 'leaving w/rv: 1', PPCDLEVEL1 );
264              
265 9         84 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 164 my $self = shift;
276 26   66     93 my $file = shift || $$self{FILE};
277 26         33 my $rv = 0;
278 26         116 my $oldSize = PIOMAXFSIZE;
279 26         59 my ( $line, @lines );
280              
281 26 50       71 croak 'Optional first argument must be a defined filename or the FILE '
282             . 'property must be set'
283             unless defined $file;
284              
285 26         78 pdebug( 'entering w/(%s)', PPCDLEVEL1, $file );
286 26         447 pIn();
287              
288             # Reset the error string and update the internal filename
289 26         175 Parse::PlainConfig::Legacy::ERROR = '';
290 26         51 $$self{FILE} = $file;
291              
292             # Temporarily set the specified size limit
293 26         66 PIOMAXFSIZE = $$self{MAX_BYTES};
294              
295             # Store the file's current mtime
296 26         563 $$self{MTIME} = ( stat $file )[MTIME];
297              
298 26 50       136 if ( detaint( $file, 'filename' ) ) {
299 26 100       3707 if ( slurp( $file, @lines, 1 ) ) {
300              
301             # Empty the current config hash and key order
302 25 100       35745 $self->purge if $$self{AUTOPURGE};
303              
304             # Parse the rc file's lines
305 25         119 $rv = $self->_parse(@lines);
306              
307             } else {
308 1         1251 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         95 PIOMAXFSIZE = $oldSize;
318              
319 26         83 pOut();
320 26         108 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
321              
322             # Return the result code
323 26         459 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 4000899 my $self = shift;
334 3         15 my $file = $$self{FILE};
335 3         7 my $omtime = $$self{MTIME};
336 3         5 my $rv = 0;
337 3         4 my $mtime;
338              
339 3 50       18 croak 'The FILE property must be set' unless defined $file;
340              
341 3         18 pdebug( 'entering w/(%s)', PPCDLEVEL1, $file );
342 3         204 pIn();
343              
344             # Try to read the file
345 3 100 66     139 if ( -e $file && -r _ ) {
346              
347             # File exists and appears to be readable, get the mtime
348 2         9 $mtime = ( stat _ )[MTIME];
349 2         8 pdebug( 'current mtime: %s last: %s', PPCDLEVEL2, $mtime, $omtime );
350              
351             # Read the file if it's newer, or return 2
352 2 100       53 $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         10 pOut();
363 3         15 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
364              
365             # Return the result code
366 3         48 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 55 my $self = shift;
377 5   66     20 my $file = shift || $$self{FILE};
378 5         5 my $padding = shift;
379 5         9 my $conf = $$self{CONF};
380 5         8 my $order = $$self{ORDER};
381 5         8 my $coerce = $$self{COERCE};
382 5         14 my $smart = $$self{SMART_PARSER};
383 5         7 my $paramDelim = $$self{PARAM_DELIM};
384 5         10 my $hashDelim = $$self{HASH_DELIM};
385 5         6 my $listDelim = $$self{LIST_DELIM};
386 5         6 my $rv = 0;
387 5         5 my $tw = DEFAULT_TW;
388 5         62 my $delimRegex = qr/(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
389 5         8 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       16 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       11 $padding = 2 unless defined $padding;
400 5 100       16 $tw -= 2 unless $smart;
401              
402 5         12 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $file, $padding );
403 5         97 pIn();
404              
405             # Pad the delimiter as specified
406 5 50       39 $paramDelim =
    50          
    50          
407             $padding == 0 ? $paramDelim
408             : $padding == 1 ? " $paramDelim"
409             : $padding == 2 ? "$paramDelim "
410             : " $paramDelim ";
411 5         13 pdebug( 'PARAM_DELIM w/padding is \'%s\'', PPCDLEVEL2, $paramDelim );
412              
413             # Create a list of parameters for output
414 5         79 @forder = @$order;
415 5         48 foreach $tmp ( sort keys %$conf ) {
416 51 100       595 push @forder, $tmp
417             unless grep /^\Q$tmp\E$/sm, @forder;
418             }
419 5         18 pdebug( "order of params to be written:\n\t%s", PPCDLEVEL2, @forder );
420              
421             # Compose the new output
422 5         163 $out = '';
423 5         10 foreach $param (@forder) {
424              
425             # Determine the datatype
426 51 50       113 $value = exists $$conf{$param} ? $$conf{$param}{Value} : '';
427             $description =
428 51 50       80 exists $$conf{$param} ? $$conf{$param}{Description} : '';
429             $type =
430 51 100       110 exists $$coerce{$param} ? $$coerce{$param}
    100          
    100          
431             : ref $value eq 'HASH' ? 'hash'
432             : ref $value eq 'ARRAY' ? 'list'
433             : 'string';
434 51         75 pdebug( 'adding %s param (%s)', PPCDLEVEL2, $type, $param );
435              
436             # Append the comments
437 51         833 $out .= $description;
438 51 50       141 $out .= "\n" unless $out =~ /\n$/sm;
439              
440             # Start the new entry with the parameter name and delimiter
441 51         44 $entry = "$param$paramDelim";
442              
443             # Append the value, taking into consideration the smart parser
444             # and coercion settings
445 51 100       88 if ( $type eq 'string' ) {
    100          
446              
447             # String type
448 29         26 $tvalue = $value;
449 29 100 66     78 unless ( $smart && exists $$coerce{$param} ) {
450 19         23 $tvalue =~ s/"/\\"/smg;
451 19 100       83 $tvalue = "\"$tvalue\"" if $tvalue =~ /$delimRegex/sm;
452             }
453 29         26 $lines = "$entry$tvalue";
454              
455             } elsif ( $type eq 'list' ) {
456              
457             # List type
458 17         32 $tvalue = [@$value];
459 17         31 foreach (@$tvalue) {
460 54         59 s/"/\\"/smg;
461 54 100 66     107 if ( $smart && exists $$coerce{$param} ) {
462 20 100       57 $_ = "\"$_\"" if /\Q$listDelim\E/sm;
463             } else {
464 34 100       115 $_ = "\"$_\"" if /$delimRegex/sm;
465             }
466             }
467 17         39 $lines = $entry . join " $listDelim ", @$tvalue;
468              
469             } else {
470              
471             # Hash type
472 5         22 $tvalue = {%$value};
473 5         17 foreach ( keys %$tvalue ) {
474 20         15 $tmp = $_;
475 20         23 $tmp =~ s/"/\\"/smg;
476 20 50       53 $tmp = "\"$tmp\"" if /$delimRegex/sm;
477 20 50       28 if ( $tmp ne $_ ) {
478 0         0 $$tvalue{$tmp} = $$tvalue{$_};
479 0         0 delete $$tvalue{$_};
480             }
481 20         23 $$tvalue{$tmp} =~ s/"/\\"/smg;
482             $$tvalue{$tmp} = "\"$$tvalue{$tmp}\""
483 20 100       71 if $$tvalue{$tmp} =~ /$delimRegex/sm;
484             }
485             $lines = $entry
486             . join " $listDelim ",
487 5         23 map {"$_ $hashDelim $$tvalue{$_}"} sort keys %$tvalue;
  20         43  
488             }
489              
490             # wrap the output to the column width and append to the output
491 51 100       132 $out .= _wrap( '', "\t", $tw, ( $smart ? "\n" : "\\\n" ), $lines );
492 51 50       163 $out .= "\n" unless $out =~ /\n$/sm;
493             }
494              
495             # Write the file
496 5 50       28 if ( detaint( $file, 'filename' ) ) {
497 5 50       1083 if ( open $fh, '>', $file ) {
498              
499             # Write the file
500 5         31 flock $fh, LOCK_EX;
501 5 50       52 if ( print $fh $out ) {
502 5         6 $rv = 1;
503             } else {
504 0         0 Parse::PlainConfig::Legacy::ERROR = $!;
505             }
506 5         196 flock $fh, LOCK_UN;
507 5         139 close $fh;
508              
509             # Store the new mtime on successful writes
510 5 50       70 $$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         19 pOut();
526 5         25 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
527              
528 5         126 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 88 my $self = shift;
538 11         13 my @parameters = keys %{ $$self{CONF} };
  11         57  
539              
540 11         35 pdebug( 'called method -- rv: %s', PPCDLEVEL1, @parameters );
541              
542 11         400 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 3001192 my $self = shift;
553 77         110 my @args = @_;
554 77         71 my $param = $args[0];
555 77         64 my $value = $args[1];
556 77 100       110 my $ivalue = defined $value ? $value : 'undef';
557 77         76 my $conf = $$self{CONF};
558 77         67 my $listDelim = $$self{LIST_DELIM};
559 77         73 my $hashDelim = $$self{HASH_DELIM};
560 77         59 my $paramDelim = $$self{PARAM_DELIM};
561             my $coerceType =
562             exists $$self{COERCE}{$param}
563 77 100       120 ? $$self{COERCE}{$param}
564             : 'undef';
565 77         63 my $defaults = $$self{DEFAULTS};
566 77         63 my $rv = 1;
567 77         409 my $delimRegex = qr/(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
568 77         74 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       119 croak 'Mandatory firest argument must be a defined parameter name'
574             unless defined $param;
575              
576 77         130 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $param, $ivalue );
577 77         1407 pIn();
578              
579 77 100       295 if ( scalar @args == 2 ) {
580 37         58 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       330 unless exists $$conf{$param};
588              
589             # Start processing value assignment
590 37 100       64 if ( $coerceType ne 'undef' ) {
591 32         46 pdebug( 'coercing into %s', PPCDLEVEL2, $coerceType );
592              
593             # Parameter has a specific data type to be coerced into
594 32 100 100     590 if ( $coerceType eq 'string' && ref $value ne '' ) {
    100 100        
    100 100        
595              
596             # Coerce values into strings
597 3 100       11 if ( ref $value eq 'ARRAY' ) {
    50          
598              
599             # Convert lists into a string using the list delimiter
600 2         4 foreach (@$value) {
601 7         8 s/"/\\"/smg;
602 7 50       33 $_ = "\"$_\"" if /\Q$listDelim\E/sm;
603             }
604 2         7 $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         6 foreach ( sort keys %$value ) {
611 2         4 $ivalue = $_;
612 2         2 $ivalue =~ s/"/\\"/smg;
613 2 50       23 $ivalue = "\"$ivalue\""
614             if /(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
615 2 50       4 $$value{$_} = '' unless defined $$value{$_};
616             $$value{$_} = "\"$$value{$_}\""
617 2 50       15 if $$value{$_} =~
618             /(?:\Q$hashDelim\E|\Q$listDelim\E)/sm;
619             push @elements,
620             join " $hashDelim ", $_,
621 2 50       9 ( 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       10 if ( ref $value eq 'HASH' ) {
    50          
635              
636             # Convert hashes into a list
637 2         1 $finalValue = [];
638 2         11 foreach ( sort keys %$value ) {
639 4         6 push @$finalValue, $_, $$value{$_};
640             }
641              
642             } elsif ( ref $value eq '' ) {
643              
644             # Convert strings into a list
645 1         6 $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       18 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       8 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         14 $self->_parse(
674             split /\n/sm,
675             "$$conf{$param}{Description}\n"
676             . "$param $paramDelim $value"
677             );
678 2         4 $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         21 $finalValue = $value;
691             }
692              
693             } else {
694 5         14 pdebug( 'no coercion to do', PPCDLEVEL2 );
695 5         40 $finalValue = $value;
696             }
697 37         46 $$conf{$param}{Value} = $finalValue;
698              
699             } else {
700 40         55 pdebug( 'method in retrieve mode', PPCDLEVEL1 );
701             $rv =
702             exists $$conf{$param} ? $$conf{$param}{Value}
703 40 50       337 : exists $$defaults{$param} ? $$defaults{$param}
    100          
704             : undef;
705             }
706              
707 77         115 pOut();
708 77         266 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
709              
710 77 100       1295 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 130 my $self = shift;
721 21         22 my $type = shift;
722 21 50       39 my $itype = defined $type ? $type : 'undef';
723 21         32 my @params = @_;
724 21         18 my $rv = 1;
725              
726 21 50 100     95 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       38 croak 'Remaining arguments must be defined parameter names'
731             unless @params;
732              
733 21         41 pdebug( 'entering w/(%s)(%s)', PPCDLEVEL1, $type, @params );
734 21         407 pIn();
735              
736 21         76 foreach (@params) {
737 45 50       54 if (defined) {
738              
739             # Mark the parameter
740 45         59 $$self{COERCE}{$_} = $type;
741             $self->parameter( $_, $$self{CONF}{$_}{Value} )
742 45 100       104 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         33 pOut();
754 21         82 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
755              
756 21         290 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         3 my @new = (@_);
810              
811 2         5 pdebug( 'entering w/(%s)', PPCDLEVEL1, @new );
812              
813 2 100       42 @$order = (@new) if scalar @new;
814              
815 2         5 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   37 my $self = shift;
829 28         50 my $conf = $$self{CONF};
830 28         41 my $order = $$self{ORDER};
831 28         34 my $smart = $$self{SMART_PARSER};
832 28         38 my $tagDelim = $$self{PARAM_DELIM};
833 28         35 my $hashDelim = $$self{HASH_DELIM};
834 28         34 my $listDelim = $$self{LIST_DELIM};
835 28         116 my @lines = @_;
836 28         75 my $rv = 1;
837 28         31 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       74 unless $$self{LIST_DELIM} ne $$self{HASH_DELIM};
843              
844 28         62 pdebug( 'entering', PPCDLEVEL2 );
845 28         257 pIn();
846              
847             # Flatten lines using an explicit backslash
848 28         175 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   109 no warnings 'uninitialized';
  15         24  
  15         16100  
853              
854 685 100       1362 if ( $lines[$i] =~ /\\\s*$/sm ) {
855 174         279 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         4139 $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       295 if ( $i < $#lines ) {
865 174         345 $lines[ $i + 1 ] =~ s/^\s+//sm;
866 174         279 $lines[$i] .= $lines[ $i + 1 ];
867 174         165 splice @lines, $i + 1, 1;
868 174         291 --$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   180 my ($type);
879              
880 244         1504 ( $field, $value ) =
881             ( $entry =~ /^\s*([^$tagDelim]+?)\s*\Q$tagDelim\E\s*(.*)$/sm );
882 244         469 pdebug( "saving data:\n\t(%s: %s)", PPCDLEVEL2, $field, $value );
883              
884 244 100       4178 if ( exists $$self{COERCE}{$field} ) {
885              
886             # Get the field data type from COERCE
887 27         32 $type = $$self{COERCE}{$field};
888              
889             } else {
890              
891             # Otherwise, try to autodetect data type
892 217 100       1024 $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         45065 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       6071 $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       522 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     663 && $$self{COERCE}{$field} eq 'scalar';
      33        
917             } elsif ( $type eq 'hash' ) {
918 30         322 $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         475 $value = [ quotewords( qr/\s*\Q$listDelim\E\s*/sm, 0, $value ) ];
925             }
926              
927             # Create the parameter record
928 244         17038 $$conf{$field} = {};
929 244         381 $$conf{$field}{Value} = $value;
930 244         279 $$conf{$field}{Description} = $comment;
931 244 100       3310 push @$order, $field unless grep /^\Q$field\E$/sm, @$order;
932 244         616 $comment = $entry = '';
933 28         194 };
934              
935             # Process lines
936 28         44 $comment = $entry = '';
937 28         177 while ( defined( $line = shift @lines ) ) {
938              
939 511 100       1521 if ( $line =~ /^\s*(?:#.*)?$/sm ) {
940              
941             # Grab comments and blank lines
942 208         340 pdebug( "comment/blank line:\n\t%s", PPCDLEVEL3, $line );
943              
944             # First save previous entries if $entry has content
945 208 100 50     3256 &$saveEntry() and $i = 0 if length $entry;
946              
947             # Save the comments
948 208 100       633 $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     1221 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         820 ( $indentation, $data ) = ( $line =~ /^(\s*)(.+)$/sm );
965 303         544 pdebug( "data line:\n\t%s", PPCDLEVEL3, $data );
966              
967 303 100       4668 if ($smart) {
968              
969             # Smart parsing is enabled
970              
971 121 100       138 if ( length $entry ) {
972              
973             # There's current content
974              
975 96 100       113 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         128 $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         44 &$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         54 ( $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         156 $entry = $data;
1002 182         201 &$saveEntry();
1003             }
1004             }
1005             }
1006 28 100       87 &$saveEntry() if length $entry;
1007              
1008 28         97 pOut();
1009 28         130 pdebug( 'leaving w/rv: %s', PPCDLEVEL2, $rv );
1010              
1011 28         736 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   44 my $firstIndent = shift;
1023 51         38 my $subIndent = shift;
1024 51         35 my $textWidth = shift;
1025 51         35 my $lineBreak = shift;
1026 51         47 my $paragraph = shift;
1027 51         35 my ( @lines, $segment, $output );
1028              
1029 51         72 pdebug( "entering w/(%s)(%s)(%s)(%s):\n\t%s",
1030             PPCDLEVEL2, $firstIndent, $subIndent, $textWidth, $lineBreak,
1031             $paragraph );
1032 51         1193 pIn();
1033              
1034             # Expand tabs in everything -- sorry everyone
1035 51         227 ($firstIndent) = expand($firstIndent);
1036 51         365 ($subIndent) = expand($subIndent);
1037 51         780 $paragraph = expand("$firstIndent$paragraph");
1038              
1039 51         400 $lines[0] = '';
1040 51         91 while ( length($paragraph) > 0 ) {
1041              
1042             # Get the next string segment (splitting on whitespace)
1043 614         1009 ($segment) = ( $paragraph =~ /^(\s*\S+\s?)/sm );
1044              
1045 614 100       715 if ( length $segment <= $textWidth - length $lines[-1] ) {
    100          
1046              
1047             # The segment will fit appended to the current line,
1048             # concatenate it
1049 576         415 $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         16 $lines[-1] .= $lineBreak;
1055 23         26 push @lines, "$subIndent$segment";
1056              
1057             } else {
1058              
1059             # Else, split on the text width
1060 15 50       31 $segment =
1061             $#lines == 0
1062             ? substr $paragraph, 0, $textWidth
1063             : substr $paragraph, 0, $textWidth - length $subIndent;
1064 15 50       38 if ( length $segment > $textWidth - length $lines[-1] ) {
1065 15         18 $lines[-1] .= $lineBreak;
1066 15 50       28 push @lines,
1067             ( $#lines == 0 ? $segment : "$subIndent$segment" );
1068             } else {
1069 0         0 $lines[-1] .= $segment;
1070             }
1071             }
1072 614         344 $paragraph =~ s/^.{@{[length($segment)]}}//sm;
  614         3843  
1073             }
1074 51         56 $lines[-1] .= "\n";
1075              
1076 51         64 $output = join '', @lines;
1077              
1078 51         86 pOut();
1079 51         190 pdebug( "leaving w/rv:\n%s", PPCDLEVEL2, $output );
1080              
1081 51         880 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 38 my $self = shift;
1092 3         4 my $param = shift;
1093 3         3 my $rv = 0;
1094 3         2 my @params = ( keys %{ $self->{CONF} }, keys %{ $self->{DEFAULTS} }, );
  3         7  
  3         8  
1095              
1096 3 50       7 croak 'Mandatory first parameter must be a defined parameter name'
1097             unless defined $param;
1098              
1099 3         5 pdebug( 'entering w/(%s)', PPCDLEVEL1, $param );
1100 3         45 pIn();
1101              
1102 3         54 $rv = scalar grep /^\Q$param\E$/sm, @params;
1103              
1104 3         4 pOut();
1105 3         12 pdebug( 'leaving w/rv: %s', PPCDLEVEL1, $rv );
1106              
1107 3         45 return $rv;
1108             }
1109              
1110             1;
1111              
1112             __END__