File Coverage

blib/lib/Config/Natural.pm
Criterion Covered Total %
statement 323 341 94.7
branch 118 164 71.9
condition 20 53 37.7
subroutine 33 33 100.0
pod 19 19 100.0
total 513 610 84.1


line stmt bran cond sub pod time code
1             package Config::Natural;
2              
3 19     19   303074 use strict;
  19         52  
  19         966  
4 19     19   108 use Carp qw(carp croak);
  19         37  
  19         1158  
5 19     19   181 use File::Spec;
  19         38  
  19         415  
6 19     19   19674 use FileHandle;
  19         301153  
  19         128  
7              
8 19     19   8942 { no strict;
  19         45  
  19         5800  
9             $VERSION = '1.01';
10             }
11              
12             # class option
13             my %options = (
14             quiet => 0,
15             );
16              
17              
18             #
19             # new()
20             # ---
21             sub new {
22 28     28 1 2512 my $class = shift;
23 28         684 my $self = bless {
24             options => {
25             'comment_line_symbol' => '#',
26             'affectation_symbol' => '=',
27             'append_symbol' => '+',
28             'multiline_begin_symbol' => '-',
29             'multiline_end_symbol' => '.',
30             'array_begin_symbol' => '(',
31             'array_end_symbol' => ')',
32             'list_begin_symbol' => '{',
33             'list_end_symbol' => '}',
34             'include_symbol' => 'include',
35             'case_sensitive' => 1,
36             'auto_create_surrounding_list' => 0,
37             'read_hidden_files' => 0,
38             'strip_indentation' => 0,
39             },
40             state => { },
41             param => { },
42             handlers => { },
43             prefilter => 0,
44             filter => 0,
45             }, $class;
46            
47 28 100       139 if(ref $_[0] eq 'HASH') {
48 4         8 my $opts = shift;
49 4         16 for my $option (keys %$opts) {
50 10 100       27 $options{$option} = $opts->{$option} if exists $options{$option};
51 10         33 $self->{'options'}{$option} = $opts->{$option};
52 10 100       105 $self->filter($opts->{$option}) if $option eq 'filter';
53 10 50       29 $self->prefilter($opts->{$option}) if $option eq 'prefilter';
54             }
55             }
56 28 100       119 $self->read_source(shift) if @_;
57 28         91 return $self;
58             }
59              
60              
61             #
62             # AUTOLOAD()
63             # --------
64             sub AUTOLOAD {
65 19     19   179 no strict;
  19         37  
  19         84396  
66 209     209   3403 my $self = $_[0];
67 209   33     1810 my $type = ref $self || croak "I am not an object, so don't call me that way.";
68 209         1400 my $name = $AUTOLOAD;
69 209         968 $name =~ s/.*:://;
70            
71 209 50 33     1790 carp "Unknown option '$name'"
72             unless exists $options{$name} or defined $self->{options}{$name};
73              
74 209         841 my $code = q{
75             sub {
76             my $self = shift;
77             my $value = $self->{options}{METHOD};
78             $self->{options}{METHOD} = shift if @_;
79             return $value
80             }
81             };
82 209         987 $code =~ s/METHOD/$name/g;
83              
84 209 50   467   21845 *$AUTOLOAD = eval $code;
  467 50       851  
  467 50       837  
  467 50       1013  
  467 50       2581  
  184 100       284  
  184 50       640  
  184 50       406  
  184 100       934  
  219 50       323  
  219 50       422  
  219         510  
  219         978  
  111         185  
  111         275  
  111         277  
  111         366  
  138         211  
  138         294  
  138         308  
  138         547  
  314         516  
  314         603  
  314         1200  
  314         1608  
  203         306  
  203         454  
  203         513  
  203         1182  
  197         340  
  197         402  
  197         430  
  197         1001  
  130         296  
  130         273  
  130         888  
  130         386  
  121         186  
  121         273  
  121         286  
  121         333  
  240         392  
  240         478  
  240         515  
  240         850  
85 209         6156 goto &$AUTOLOAD;
86             }
87              
88              
89             #
90             # DESTROY()
91             # -------
92             sub DESTROY {
93 28     28   14018 my $self = shift;
94 28         117 $self->clear_params;
95 28         133 $self->delete_all;
96             }
97              
98              
99             #
100             # options()
101             # -------
102             sub options {
103 15     15 1 257 my $self = shift;
104 15         92 my $args = _parse_args(@_);
105 15         49 my @ret_list = ();
106            
107 15         37 for my $arg (@{$args->{'get'}}) {
  15         68  
108 0 0 0     0 carp "Class option '$arg' does not exist" and next
      0        
109             unless exists $options{$arg} or $options{'quiet'};
110 0         0 push @ret_list, $options{$arg};
111             }
112            
113 15         39 for my $arg (keys %{$args->{'set'}}) {
  15         92  
114 15 50 0     105 carp "Class option '$arg' does not exist" and next
      33        
115             unless exists $options{$arg} or $options{'quiet'};
116 15         66 $options{$arg} = $args->{'set'}{$arg};
117             }
118            
119 15 50       114 return wantarray ? @ret_list : $ret_list[0];
120             }
121              
122              
123             #
124             # _read_dir()
125             # ---------
126             # Recursively walk through the given directory and read
127             # all the files encountered
128             #
129             sub _read_dir {
130 20     20   35 my $self = shift;
131 20         34 my $dir = shift;
132            
133 20 50       285 return $self->read_source($dir) if -f $dir;
134            
135 20 50       711 opendir(DIR, $dir) or croak "Can't read directory '$dir': $!";
136 20         497 my @list = grep {!/^\.\.?$/} readdir(DIR); # remove . and ..
  130         356  
137 20 100       484 @list = grep {!/^\./} @list unless $self->read_hidden_files;
  81         210  
138 20         361 closedir(DIR);
139            
140 20         47 for my $file (@list) {
141 81         1134 my $path = File::Spec->catfile($dir, $file);
142              
143 81 100       1708 if(-d $path) {
144 10         64 $self->_read_dir($path)
145             } else {
146 71         292 $self->read_source($path)
147             }
148             }
149             }
150              
151              
152             #
153             # read_source()
154             # -----------
155             # Read the data from the given file or filehandle
156             #
157             sub read_source {
158 120     120 1 1694 my $self = shift;
159 120         166 my $file = shift;
160 120         188 local $_;
161            
162             # go to recursive mode if the argument is a directory
163 120 100       2209 if(-d $file) {
164 10         43 unshift @_, $self, $file;
165 10         53 goto &_read_dir
166             }
167            
168             # ... else open the file
169 110 50       284 my $fh = _file_or_handle($file) or croak "Can't open file '$file': $!";
170            
171             # keep local copy of the properties we'll use
172 110         4307 my $comment = $self->comment_line_symbol;
173 110         2610 my $aff_sym = $self->affectation_symbol;
174 110         2662 my $app_sym = $self->append_symbol;
175 110         2589 my $multiline = $self->multiline_begin_symbol;
176 110         2514 my $multi_end = $self->multiline_end_symbol;
177 110         8095 my $array = $self->array_begin_symbol;
178 110         2706 my $array_end = $self->array_end_symbol;
179 110         2460 my $list = $self->list_begin_symbol;
180 110         2406 my $list_end = $self->list_end_symbol;
181 110         2354 my $include = $self->include_symbol;
182 110         197 my $state = $self->{'state'};
183            
184             # store the name of the last opened file
185 110         208 $state->{'filename'} = $file;
186            
187 110         2019 while(<$fh>) {
188             ## execute the prefilter if present
189 747 100       1594 $self->{'prefilter'} and $_ = &{$self->{'prefilter'}}($self, $_);
  20         53  
190            
191 747 100       2949 next if /^\s*$/; # skip empty lines
192 661 100       2669 next if /^\s*$comment/; # skip comments
193 645         998 chomp;
194            
195             ## include statement
196 645 100       2694 if(/^\s*\Q${include}\E\s+(\S+)\s*$/) {
197 20         128 my $included = $1;
198 20         280 my @path = File::Spec->splitdir($state->{'filename'});
199 20         47 pop @path; # remove the current file name from the path
200 20         158 $included = File::Spec->catdir(@path, $included);
201 20         248 $self->read_source($included);
202             next
203 20         272 }
204            
205             ## begin of a new list
206 625 100       2547 if(/^\s*(\S+)\s*\Q${list}\E\s*$/) {
207 99         117 push @{$state->{lists_names}}, $1;
  99         315  
208 99         128 push @{$state->{lists_stacks}}, {};
  99         203  
209             next
210 99         624 }
211            
212             ## end of the current list
213 526 100       1969 if(/^\s*\Q${list_end}\E\s*$/) {
214 99         169 my $lists_stacks = $state->{'lists_stacks'};
215 99         105 my $curlistname = pop @{$state->{'lists_names'}};
  99         202  
216 99         161 my $curlistref = pop @$lists_stacks;
217            
218 99 100       205 if(@$lists_stacks) {
219 68         77 push @{ $$lists_stacks[-1] ->{ $curlistname } }, $curlistref
  68         186  
220             } else {
221 31         40 push @{$self->{'param'}{$curlistname}}, $curlistref
  31         105  
222             }
223            
224             next
225 99         2160 }
226            
227             ## parameter affectation
228 427         2871 my($field,$value) = (/^\s*(\S+)\s*\Q${app_sym}\E?\s*\Q${aff_sym}\E\s*(.*)$/);
229            
230             ## detect append mode
231 427         1778 my $append = /^\s*(\S+)\s*\Q${app_sym}\E\s*\Q${aff_sym}\E\s*(.*)$/;
232 427 100       470 my $prev_value = ${$state->{'lists_names'}}[-1] ?
  427         1475  
233             $state->{'lists_stacks'}[-1]{$field} : $self->{'param'}{$field};
234            
235             ## multiline case
236 427 100       1751 if($value =~ /^\s*\Q${multiline}\E\s*$/) {
237 17         38 $value = '';
238 17 50 0     51 $value = $prev_value . $value and $append = 0 if $append;
239 17         43 $_ = <$fh>;
240            
241 17 50       375 $self->strip_indentation and my($indent) = (/^(\s*)/);
242            
243 17         337 while(not /^\s*\Q${multi_end}\E\s*$/) {
244 52 50       123 $indent and s/^$indent//;
245 52         96 $value .= $_;
246 52         111 $_ = <$fh>;
247 52 100       336 last if eof($fh)
248             }
249             }
250            
251             ## array case
252 427 100       12084 if($value =~ /^\s*\Q${array}\E\s*$/) {
253 25         37 $value = '';
254 25         57 $_ = <$fh>;
255            
256 25         298 while(not /^\s*\Q${array_end}\E\s*$/) {
257 103         343 s/^\s*//;
258 103         169 $value .= $_;
259 103         158 $_ = <$fh>;
260 103 100       528 last if eof($fh)
261             }
262            
263             $append ?
264 25 100 50     390 ( push @$prev_value, split($/, $value) and $append = 0 )
265             : $self->param({ $field => [ split $/, $value ] });
266             next
267 25         186 }
268            
269             ## create a surrounding list if the parameter already exists
270 402 50       15583 if($self->auto_create_surrounding_list) {
271 0         0 my $surrlist = "${field}s";
272 0         0 my $root_param = $self->{'param'};
273 0         0 my $curlistref = ${$self->{'state'}{'lists_stacks'}}[-1];
  0         0  
274            
275 0 0       0 if($curlistref) {
276 0         0 $root_param = $curlistref
277             }
278            
279             ## the surrounding list doesn't already exist
280 0 0 0     0 if(exists $root_param->{$field} and not exists $root_param->{$surrlist}) {
281 0         0 $root_param->{$surrlist} = [ { $field => $root_param->{$field} } ];
282 0         0 delete $root_param->{$field};
283             }
284            
285             ## add the new parameter to the list
286 0 0       0 if(exists $root_param->{$surrlist}) {
287 0         0 push @{$root_param->{$surrlist}}, { $field => $value };
  0         0  
288             next
289 0         0 }
290             }
291            
292             ## add the new value to the object parameters
293 402 100       820 $value = $prev_value . $value if $append;
294 402         1803 $self->param({ $field => $value });
295             }
296             }
297              
298              
299             #
300             # _file_or_handle()
301             # ---------------
302             sub _file_or_handle {
303 112     112   177 my $file = shift;
304            
305 112 100       851 unless(ref $file) {
306 106   100     1064 my $mode = shift || 'r';
307 106         671 my $fh = new FileHandle $file, $mode;
308 106         10379 return $fh
309             }
310            
311 6         55 return $file
312             }
313              
314              
315             #
316             # param()
317             # -----
318             sub param {
319 762     762 1 6213 my $self = shift;
320 762 100       1547 return $self->all_parameters unless @_;
321            
322 753         1383 my $args = _parse_args(@_);
323            
324 753         1146 my @retlist = (); # return list
325            
326             ## get the value of the desired parameters
327 753         927 for my $arg (@{$args->{'get'}}) {
  753         1676  
328 139 50 0     406 carp "Parameter '$arg' does not exist" and next
      66        
329             if not exists $self->{'param'}{_case_($self, $arg)} and not $options{'quiet'};
330            
331 139         347 push @retlist, $self->{'param'}{_case_($self, $arg)}
332             }
333            
334             ## set the named parameters to new values
335 753         888 my $param;
336 753         748 my $current_list = ${$self->{'state'}{'lists_names'}}[-1];
  753         1575  
337 753         868 my @arg_list = keys %{$args->{'set'}};
  753         2030  
338            
339 753 100       1348 if($current_list) {
340 274         276 $param = ${$self->{'state'}{'lists_stacks'}}[-1];
  274         535  
341            
342             } else {
343 479         801 $param = $self->{'param'};
344             }
345            
346 753         1225 for my $arg (@arg_list) {
347 633         970 my $value = $args->{'set'}{$arg};
348            
349             ## use the filter if present
350 92         220 $self->{'filter'} and
351 633 100       1462 $value = &{$self->{'filter'}}($self, $value);
352            
353             ## use the handler if present
354 633 100       1816 $self->{'handlers'}{$arg} and
355             $value = $self->exec_handler($arg, $value);
356            
357 633         1242 $param->{_case_($self, $arg)} = $value
358             }
359            
360 753 100       5811 return wantarray ? @retlist : $retlist[0]
361             }
362              
363              
364             #
365             # _case_()
366             # ------
367             # Check for the case
368             #
369             sub _case_ {
370 1279     1279   2186 my $self = shift;
371 1279         1653 my $param = shift;
372 1279 100       37864 return ($self->case_sensitive ? $param : lc $param)
373             }
374              
375              
376             #
377             # _parse_args()
378             # -----------
379             sub _parse_args {
380 773     773   2766 my %args = ( get => [], set => {} );
381            
382 773         2194 while(my $arg = shift) {
383 783 100       1882 if(my $ref_type = ref $arg) {
384            
385             ## setting multiples parameters using a hashref
386 622 50       1126 if($ref_type eq 'HASH') {
387 622         691 local $_;
388 622         1704 for (keys %$arg) {
389 626 50       3989 $args{'set'}{$_} = $arg->{$_} if $_
390             }
391            
392             } else {
393 0 0       0 carp "Bad ref $ref_type; ignoring it" unless $options{'quiet'};
394             next
395 0         0 }
396            
397             } else {
398             ## setting a parameter to a new value
399 161 100       409 if(substr($arg, 0, 1) eq '-') {
400 22         61 $arg = substr($arg, 1);
401 22         43 my $val = shift;
402 22 0 0     85 carp "Undefined value for parameter '$arg'" and next
      33        
403             if not defined $val and not $options{'quiet'};
404 22 50       175 $args{'set'}{$arg} = $val if $arg
405            
406             ## getting the value of a parameter
407             } else {
408 139         202 push @{$args{'get'}}, $arg
  139         596  
409             }
410             }
411             }
412            
413 773         1752 return \%args
414             }
415              
416              
417             #
418             # prefilter()
419             # ---------
420             # Set a new prefilter.
421             #
422             sub prefilter {
423 1     1 1 8 my $self = shift;
424 1         1 my $code = shift;
425 1 50       5 croak "Not a CODEREF" unless ref $code eq 'CODE';
426 1         3 $self->{'prefilter'} = $code;
427             }
428              
429              
430             #
431             # filter()
432             # ------
433             # Set a new filter.
434             #
435             sub filter {
436 3     3 1 13 my $self = shift;
437 3         6 my $code = shift;
438 3 50       10 croak "Not a CODEREF" unless ref $code eq 'CODE';
439 3         13 $self->{'filter'} = $code;
440             }
441              
442              
443             #
444             # set_handler()
445             # -----------
446             # Set a new handler for a parameter
447             #
448             sub set_handler {
449 2     2 1 10 my $self = shift;
450 2         3 my $param = shift;
451 2         3 my $code = shift;
452 2         11 $self->{'handlers'}{$param} = $code;
453             }
454              
455              
456             #
457             # delete_handler()
458             # --------------
459             # Delete the handler of the given parameter
460             #
461             sub delete_handler {
462 1     1 1 2 my $self = shift;
463 1         1 my $param = shift;
464 1         7 delete $self->{'handlers'}{$param};
465             }
466              
467              
468             #
469             # has_handler()
470             # -----------
471             # Check if the given parameter has a handler
472             #
473             sub has_handler {
474 3     3 1 10 my $self = shift;
475 3         4 my $param = shift;
476 3         16 return exists $self->{'handlers'}{$param}
477             }
478              
479              
480             #
481             # exec_handler()
482             # ------------
483             # Execute the handler of a parameter
484             #
485             sub exec_handler {
486 5     5 1 6 my $self = shift;
487 5         6 my $param = shift;
488 5         5 my $value = shift;
489 5         5 return &{$self->{'handlers'}{$param}}($param, $value)
  5         19  
490             }
491              
492              
493             #
494             # all_parameters()
495             # --------------
496             # Return the list of all the parameters at the root level
497             #
498             sub all_parameters {
499 68     68 1 155 my $self = shift;
500 68         108 return keys %{$self->{'param'}}
  68         425  
501             }
502              
503              
504             #
505             # param_tree()
506             # --------------
507             # Return the hash tree of all parameters
508             #
509             sub param_tree {
510 1     1 1 9 my $self = shift;
511 1         2 my $tree = {};
512 1         4 $tree->{$_} = $self->param($_) for($self->param());
513 1         4 return $tree;
514             }
515              
516              
517             #
518             # value_of()
519             # --------
520             # Return the value of the specified parameter
521             #
522             sub value_of {
523 24     24 1 11832 my $self = shift;
524 24         62 my $param_path = shift;
525            
526             # handle simple cases simply...
527 24 100       91 return $self->{'param'}{$param_path} if $self->{'param'}{$param_path};
528            
529             # handle more complex cases nicely.
530 23         207 my @path = split '/', $param_path;
531 23 50       66 not $path[0] and shift @path;
532            
533 23         974 my($name,$index) = ( (shift @path) =~ /^([^[]+)(?:\[([+-]?\d+|\*)\])?$/ );
534 23   100     68 my $node = $self->param($name); $index ||= 0;
  23         89  
535 23 100       59 return $node if $index eq '*';
536            
537 22 50       51 if(ref $node) {
538 22         45 $node = $node->[int($index)];
539 22         32 for my $p (@path) {
540 40         212 ($name,$index) = ( ($p) =~ /^([^[]+)(?:\[([+-]?\d+|\*)\])?$/ );
541 40   100     86 $node = $node->{$name}; $index ||= 0;
  40         115  
542 40 100 100     180 ref $node and $index ne '*' and $node = $node->[int($index)];
543             }
544             }
545            
546 22         115 return $node
547             }
548              
549              
550             #
551             # delete()
552             # ------
553             # Delete the given parameters
554             #
555             sub delete {
556 30     30 1 230 my $self = shift;
557            
558 30         193 for my $param (@_) {
559 184 0 0     2229 carp "Parameter '$param' does not exist" and next
      33        
560             if not exists $self->{'param'}{_case_($self, $param)} and not $options{'quiet'};
561 184         592 delete $self->{'param'}{_case_($self, $param)}
562             }
563             }
564              
565              
566             #
567             # delete_all()
568             # ----------
569             sub delete_all {
570 29     29 1 218 my $self = shift;
571 29         81 $self->delete($self->all_parameters)
572             }
573              
574              
575             #
576             # clear()
577             # -----
578             sub clear {
579 2     2 1 3 my $self = shift;
580 2         5 for my $param (@_) {
581 5         17 $self->param({$param => ''})
582             }
583             }
584              
585              
586             #
587             # clear_params()
588             # ------------
589             sub clear_params {
590 29     29 1 118 my $self = shift;
591 29         102 for my $param ($self->all_parameters) {
592 184         590 $self->param({$param => ''})
593             }
594             }
595              
596              
597             #
598             # dump_param()
599             # ----------
600             sub dump_param {
601 5     5 1 656 my $self = shift;
602 5         14 my $args = _parse_args(@_);
603 5   50     31 my $prefix = $args->{'set'}{'prefix'} || '';
604 5   50     24 my $suffix = $args->{'set'}{'suffix'} || '';
605 5   50     27 my $nospace = $args->{'set'}{'nospace'} || 0;
606              
607 5         18 return _dump_tree($self, $self->{'param'}, 0,
608             prefix => $prefix, suffix => $suffix, nospace => $nospace)
609             }
610              
611              
612             #
613             # _dump_tree()
614             # ----------
615             sub _dump_tree {
616 50     50   65 my $self = shift;
617 50         55 my $tree = shift;
618 50         52 my $level = shift;
619 50         179 my %state = @_;
620 50         63 my $str = '';
621 50 50       94 my $sp = $state{'nospace'} ? '' : ' ';
622            
623 50 100       119 if(ref $tree eq 'HASH') {
    50          
624            
625             # add the list name and symbol
626 32 100       735 $state{'list_name'} and
627             $str .= join '',
628             $/, $sp x (($level-1)*2), $state{'list_name'}, $sp,
629             $self->list_begin_symbol, $/;
630            
631 32         125 for my $param (sort keys %$tree) {
632 108 100       205 if(ref($tree->{$param})) {
633 18         89 $str .= _dump_tree($self, $tree->{$param}, $level+1, %state, list_name => $param)
634            
635             } else {
636             ## multi-line value?
637 90 100       300 my $multiline = 1 if $tree->{$param} =~ /\n|\r/;
638            
639 90 100       2189 $str .= join '',
    100          
640             $sp x ($level*2),
641             $state{'prefix'}, $param, $sp, $self->affectation_symbol, $sp,
642             ($multiline ? $self->multiline_begin_symbol . $/ : ''),
643             $tree->{$param},
644             ($multiline ? $self->multiline_end_symbol . $/ : ''),
645             $state{'suffix'}, $/;
646             }
647             }
648            
649             # add the list end symbol
650 32 100       699 $state{'list_name'} and
651             $str .= join '',
652             $sp x (($level-1)*2), $self->list_end_symbol, $/;
653            
654             } elsif(ref $tree eq 'ARRAY') {
655 18 100       39 if(ref $tree->[0]) {
656 12         19 for my $list (@$tree) { $str .= _dump_tree($self, $list, $level, %state) }
  27         82  
657             } else {
658 24         212 $str .= join '',
659             $sp x ($level*2),
660             $state{'prefix'}, $state{'list_name'}, $sp,
661             $self-> affectation_symbol, $sp, $self->array_begin_symbol, $/,
662 6         154 (map {($sp x (($level+1)*2)) . $_ . $/} @$tree),
663             $sp x ($level*2), $self->array_end_symbol, $/
664             }
665            
666             } else {
667 0         0 warn "unexpected reference type ", ref($tree)
668             }
669            
670 50         389 return $str
671             }
672              
673              
674             #
675             # write_source()
676             # ------------
677             # Write the current state of the object to a file
678             #
679             sub write_source {
680 2     2 1 794 my $self = shift;
681            
682             # use the last filename given to read_source() if no arg
683 2 50       6 push @_, $self->{'state'}{'filename'} unless @_;
684            
685 2         3 my $file = shift;
686 2         5 my $fh = _file_or_handle($file, 'w');
687 2 50       8 print $fh $self->dump_param(@_) or croak "Error while writing to '$file': $!";
688             }
689              
690              
691             1;
692              
693             __END__