File Coverage

blib/lib/Config/Simple/Conf.pm
Criterion Covered Total %
statement 46 214 21.5
branch 9 114 7.8
condition 2 29 6.9
subroutine 9 14 64.2
pod 7 7 100.0
total 73 378 19.3


line stmt bran cond sub pod time code
1             package Config::Simple::Conf;
2             # Copyright 2016-2021 (C) Colin Faber
3             # Licensed under the terms of perl itself.
4              
5 2     2   69483 use strict;
  2         18  
  2         57  
6 2     2   10 use Exporter;
  2         4  
  2         71  
7 2     2   11 use base 'Exporter';
  2         4  
  2         6059  
8              
9             our $VERSION = "2.007";
10              
11             our @ARGV_CLEAN;
12             our @EXPORT = qw(@ARGV_CLEAN);
13              
14             # Revision - cvs automagically updated
15             our $REVISION = '$Id: Conf.pm,v 1.20 2019/11/02 22:24:21 cfaber Exp $';
16              
17             # Ruckus Global Version Number
18              
19             =head1 NAME
20              
21             Config::Simple::Conf - A fast and lightweight configuration file handler
22              
23             =head1 DESCRIPTION
24              
25             The idea behind Config::Simple::Conf came from various INI style parsers I've used in the past. In general these have worked well with the exception of lack of complex configuration handling.
26              
27             Config::Simple for example fails to account for common cases which are extremely useful in any configuration file. These include useful handling of duplicate keys (currently Config::Simple blows them away without any notice), and second, internal macros.
28              
29             In many of my usage cases I want something like your standard .INI file format, with the above mentioned exceptions.
30              
31             # Define a configuration section
32             [core section]
33              
34             # Define an entry in core section
35             path = /root/to/my/stuff
36              
37             # Define a new configuration file section
38             [section name]
39              
40             # Define an entry list and use the value from another section to complete
41             # the configuration
42             path = [core section:path]/abc
43             path = [core section:path]/xyz
44              
45             Such a configuration would allow me to do two things, establish a core path argument, which is then used in other sections, and have a section with multiple duplicate entires as a list.
46              
47             An example of the code here would look something like:
48              
49             #!/usr/bin/perl
50              
51             use strict;
52             use Config::Simple::Conf;
53              
54             my $conf = Config::Simple::Conf->new('/path/to/my.conf');
55              
56             print "My root is: " . $conf->value('core section', 'path') . "\n";
57             print "My section paths are:\n";
58              
59             for($conf->value('section name', 'path')){
60             print "\t$_\n";
61             }
62              
63             With the resulting output looking something like:
64              
65             My root is: /root/to/my/stuff
66             My section paths are:
67             /root/to/my/stuff/abc
68             /root/to/my/stuff/xyz
69              
70             =head1 SYNOPSIS
71              
72             use Config::Simple::Conf;
73              
74             my $conf = Config::Simple::Conf->new('/etc/Something/Example.conf');
75              
76             print $conf->value('global', 'example_key');
77              
78             =head1 HANDLING COMMAND LINE ARGUMENTS
79              
80             Command line arguments are processed automatically when detected within the B<@ARGV> list. The values of these arguments are represented in the special B section. Command line arguments can be in either a single or double (B<->) hash value such as B<--key> or B<-k>
81              
82             A value can be assigned to each argument as well by either placing the value after the B<--key value> or by using an (B<=>) equals sign B<--key=value>. Multiple duplicate keys can be used to generate a list.
83              
84             Values may also be macros, so a value could be sourced from a configuration file. An example of this might be B<--color [colors:red]> with the configuration file:
85              
86             [colors]
87             red = 255,0,0
88             green = 0,255,0
89             blue = 0,0,255
90              
91             =head2 @ARGV_CLEAN
92              
93             An @ARGV like list, cleaned of any arguments which have been captured by the parser.
94              
95             =head1 CONFIG FILE FORMAT
96              
97             Configuration files are defined as ascii text, with comments lines starting with a pound symbol B<#>, sections, keys, and values. Values may be macro entries referencing other configuration keys.
98              
99             =head2 SECTION
100              
101             A section is defined as a single line entry with double square brakets B<[section]>:
102              
103             # Define a section
104             [section]
105              
106             =head2 KEYS
107              
108             Keys are defined within a B
as lines with B type entry
109              
110             # Define a value for keyname in section [section]
111             [section]
112             keyname = value
113              
114             =head2 USING A MACRO
115              
116             Macros are defined as square brakets with a B entry between them. These are automatically resolved to other configuration sections and keys and that keys value is utilized.
117              
118             # Define a value based on a macro
119             [section2]
120             key = [section:keyname]
121             =head2 NOTE
122              
123             Macros may B utilize list entries of duplicate macro keys.
124              
125             =head2 SPECIAL MACROS
126              
127             Currently there are two special macros which perform useful tasks
128              
129             =over
130              
131             =item include
132              
133             The B key name allows you to include another configuration file with additional configuration information
134              
135             =item die
136              
137             The B key will result in the program dying at that spot with an error dumped to STDERR.
138              
139             =back
140              
141             =head2 EXAMPLES
142              
143             # Include another configuration file
144             include = /some/config.cfg
145              
146             # Die right here so user changes things
147             die
148              
149             Additionally see the examples/ directory within this libraries distrobution for more configuration file examples
150              
151             =head1 METHODS
152              
153             =head2 new()
154              
155             Config::Simple::Conf->new(FILE, CFHASH)
156              
157             Generate / Regenerate the configuration hash reference based on on standard Ruckus configuration files and options.
158              
159             FILE - The configuratino file to process, if
160             undefined @ARGV will be processed for
161             arguments.
162              
163             CFHASH - An existing configuraiton hash generated
164             by Config::Simple::Conf in which data should be appended
165             to.
166              
167             Returns a hash reference with two types of values:
168              
169             A standard string "abc", and array reference ["a","b","c"]. In cases of unique keys data is stored as a string. In cases were there are multiple duplicate keys data is stored in an array reference.
170              
171             Keys may make use of other keys values with in the key value.
172              
173             Example:
174             [example]
175             # sets [example:abc] to '123'
176             abc = 123
177              
178             # sets [efg] to '123'
179             efg = [example:abc]
180              
181             # sets [example:list] to [1, 2, 3]
182             list = 1
183             list = 2
184             list = 3
185              
186             When making use of other key's values (as explainded in the example above) the embedded key '[abc]' MUST be unique. Using embedded keys in a listing context is not allowed and will result in an fatal error.
187              
188             In some cases configuration files may need to include other configuration files. The way this is done is via a speical key called 'include'. The same file will be automatically execluded if it's detected multiple times.
189              
190             =cut
191              
192             sub new {
193 1     1 1 87 shift;
194 1         4 my $a = _cliargs(@_);
195 1         4 my $c = _fileargs(@_);
196              
197 1 50       3 if(ref($c) ne 'HASH'){
198 1         2 $c = {};
199             }
200              
201 1         3 $c->{argv} = $a;
202              
203 1         4 my $self = bless _stage2($c);
204             }
205              
206             =head2 argv()
207              
208             Returns the @ARGV_CLEAN list
209              
210             =cut
211              
212 0     0 1 0 sub argv { return @ARGV_CLEAN; }
213              
214             =head2 value(SECTION, KEY)
215              
216             Retrieve a configuration value or list from B
for B keyname.
217              
218             By rule, entries outside of a section are 'global', entries within the CLI arguments list are in section 'argv'
219              
220             =cut
221              
222             sub value {
223 2     2 1 11 my ($self, $sec, $key) = @_;
224 2         7 $sec = lc $sec;
225 2 50       6 $key = ($sec eq 'argv' ? $key : lc $key);
226              
227 2 50       13 if(ref($self->{$sec}->{$key}) eq 'ARRAY'){
228 0         0 return (@{ $self->{$sec}->{$key} });
  0         0  
229             } else {
230 2         9 return $self->{$sec}->{$key};
231             }
232             }
233              
234             =head2 islist(SECTION, KEY)
235              
236             Return true if the B
's B is a list of entries
237              
238             =cut
239              
240             sub islist {
241 0     0 1 0 my ($self, $sec, $key) = @_;
242 0         0 $sec = lc $sec;
243 0         0 $key = lc $key;
244              
245 0 0       0 if(ref($self->{$sec}->{$key}) eq 'ARRAY'){
246 0         0 return 1;
247             } else {
248 0         0 return;
249             }
250             }
251              
252             =head2 sections()
253              
254             Return a list of available sections
255              
256             =cut
257              
258             sub sections {
259 0     0 1 0 my ($self) = @_;
260 0         0 return sort keys %{ $self };
  0         0  
261             }
262              
263             =head2 keys(SECTION)
264              
265             Return the keys for a given section
266              
267             =cut
268              
269             sub keys {
270 0     0 1 0 my ($self, $sec) = @_;
271 0         0 $sec = lc $sec;
272              
273 0 0       0 if(ref($self->{$sec}) eq 'HASH'){
274 0         0 return sort keys %{ $self->{$sec} };
  0         0  
275             } else {
276 0         0 return;
277             }
278             }
279              
280             =head2 set(SECTION, KEY, VALUE)
281              
282             Update or create a new config entry for B
, with the B containing B
283              
284             Returns true on success
285              
286             =cut
287              
288             sub set {
289 1     1 1 7 my ($self, $sec, $key, $val) = @_;
290              
291 1 50 33     11 if(defined $sec && defined $key && defined $val){
      33        
292 1         4 $self->{ lc $sec }->{ $key } = $val;
293 1         3 $self->{ lc $sec }->{ $key };
294              
295 1         3 return 1;
296             }
297              
298 0         0 return;
299             }
300              
301             # parse the arguments string and configuration files
302             sub _fileargs {
303 1 50   1   4 shift @_ if(ref($_[0]) eq 'Config::Simple::Conf');
304              
305 1         3 my ($tfn, $conf, $head, $fn) = @_;
306              
307             # This file isn't allowed to be included any more.
308 1 50       4 $Config::Simple::Conf::INC{$tfn} = 1 if $tfn;
309              
310 1         2 my ($hd, $line);
311              
312 1 50       4 if($tfn){
313             # process the raw configuration file
314 0 0       0 open(my $fh, $tfn) || &_die("ERROR: Unable to read configuration file: $tfn $!." . ($fn ? ' included from: ' . join('-> ', @{$fn}) : undef));
  0 0       0  
315 0         0 for (<$fh>){
316 0         0 $line++;
317 0         0 chomp;
318              
319 0 0       0 if(/^\s*?\[([^\]]+)\]\s*?$/){
    0          
320 0         0 $hd = lc $1;
321 0         0 next;
322             } elsif(!$hd){
323 0   0     0 $hd = $head || 'global';
324             }
325              
326 0         0 s/^\s+//g;
327 0         0 s/\s+$//g;
328              
329             # Skip blank lines and comments
330 0 0 0     0 next if(!$_ || /^#/);
331              
332             # We use argv for our arguments string, and global for our global arguments, and thus a sections named argv or global are disallowed.
333 0 0       0 if($hd eq 'argv'){
334 0 0       0 &_die("ERROR: section name [argv] is invalid and disallowed: $tfn\:$line" . ($fn ? ' included from: ' . join(' -> ', @{$fn}) : undef));
  0         0  
335             }
336              
337              
338 0         0 my ($k, $v) = split(/=/, $_, 2);
339              
340 0         0 $k =~ s/^\s+//g;
341 0         0 $k =~ s/\s+$//g;
342 0         0 $v =~ s/^\s+//g;
343 0         0 $v =~ s/\s+$//g;
344              
345 0         0 $k = lc $k;
346              
347 0 0       0 if($k eq 'die'){
    0          
348 0 0       0 &_die("ERROR: $tfn died on line $line: $v in section [$hd] in $tfn" . ($fn ? ' included from: ' . join('-> ', @{$fn}) : undef));
  0         0  
349             } elsif(!$k){
350 0 0       0 next if !$k;
351             }
352              
353 0 0 0     0 if(exists $conf->{$hd}->{$k} && $conf->{$hd}->{$k} ne "\x18"){
354 0 0       0 if(ref($conf->{$hd}->{$k}) eq 'ARRAY'){
355 0         0 push @{ $conf->{$hd}->{$k} }, $v;
  0         0  
356             } else {
357 0         0 my $tmp = $conf->{$hd}->{$k};
358 0         0 delete $conf->{$hd}->{$k};
359 0         0 @{ $conf->{$hd}->{$k} } = ($tmp, $v);
  0         0  
360             }
361             } else {
362 0         0 $conf->{$hd}->{$k} = $v;
363             }
364             }
365 0         0 close($fh);
366              
367             # Process stage2 here, to ensure that includes are correct.
368 0         0 $conf = _stage2($conf);
369              
370             # Load up includes.
371 0 0       0 if(ref($conf) =~ /^(HASH|Config::Simple::Conf)$/){
372 0         0 my @inc;
373              
374 0         0 for my $sec (keys %{ $conf }){
  0         0  
375 0 0       0 if($conf->{$sec}->{include}){
376 0         0 my @files;
377 0 0       0 if(ref($conf->{$sec}->{include}) eq 'ARRAY'){
378 0         0 @files = @{ $conf->{$sec}->{include} };
  0         0  
379             } else {
380 0         0 @files = ($conf->{$sec}->{include});
381             }
382              
383 0         0 delete $conf->{$sec}->{include};
384              
385 0         0 for my $file (@files){
386 0         0 @{$fn} = ($tfn, $file);
  0         0  
387 0 0       0 if(!$Config::Simple::Conf::INC{$file}){
388 0         0 $Config::Simple::Conf::INC{$file} = 1;
389 0         0 push @inc, $file;
390 0         0 $conf = &_fileargs($file, $conf, $hd, $fn);
391             } else {
392 0 0       0 &_die("ERROR: configuration file $file is included twice!" . ($fn ? ' included from: ' . join('-> ', @{$fn}) : undef));
  0         0  
393             }
394             }
395             }
396             }
397             }
398              
399 0         0 return $conf;
400             }
401             }
402              
403             # Process ARGV
404             sub _cliargs {
405 1     1   3 my (@argv, $conf, $last_key);
406 1         6 for(my $i = 0; $i < @ARGV; $i++){
407 0         0 $ARGV[$i] =~ /(.+)/s; # Untaint everything from the user.
408 0         0 $_ = $1;
409              
410 0 0       0 if(/^--$/){
    0          
    0          
411 0         0 last;
412             } elsif(/^--?([^=]+)=(.+)$/){
413 0         0 my ($k, $v) = ($1, $2);
414 0         0 $k =~ s/^\s+//g;
415 0         0 $k =~ s/\s+$//g;
416 0         0 $v =~ s/^\s+//g;
417 0         0 $v =~ s/\s+$//g;
418              
419 0 0 0     0 if(exists $conf->{$k} && $conf->{$k} ne "\x18"){
420 0 0       0 if(ref($conf->{$k}) eq 'ARRAY'){
421 0         0 push @{ $conf->{$k} }, $v;
  0         0  
422             } else {
423 0         0 my $tmp = $conf->{$k};
424 0         0 delete $conf->{$k};
425 0         0 @{ $conf->{$k} } = ($tmp, $v);
  0         0  
426             }
427             } else {
428 0         0 $conf->{$k} = $v;
429             }
430              
431 0         0 undef $last_key;
432             } elsif(/^--?([A-Za-z0-9_.-]+)$/){
433 0         0 my $k = $1;
434 0         0 $last_key = $k;
435 0 0       0 if(!exists $conf->{$k}){
436 0         0 $conf->{$k} = "\x18";
437             }
438             } else {
439 0 0       0 if($last_key){
440 0 0 0     0 if(exists $conf->{$last_key} && $conf->{$last_key} ne "\x18"){
441 0 0       0 if(ref($conf->{$last_key}) eq 'ARRAY'){
442 0         0 push @{ $conf->{$last_key} }, $ARGV[$i];
  0         0  
443             } else {
444 0         0 my $tmp = $conf->{$last_key};
445 0         0 delete $conf->{$last_key};
446 0         0 push @{ $conf->{$last_key} }, $tmp, $ARGV[$i];
  0         0  
447             }
448             } else {
449 0         0 $conf->{$last_key} = $ARGV[$i];
450             }
451              
452 0         0 undef $last_key;
453             } else {
454 0         0 push @argv, $ARGV[$i];
455             }
456             }
457             }
458              
459 1 50       5 if(ref($conf) =~ /^(HASH|Config::Simple::Conf)$/){
460 0         0 for my $key (keys %{ $conf }){
  0         0  
461 0 0       0 if($conf->{$key} eq "\x18"){
462 0         0 $conf->{$key} = 1;
463             }
464             }
465             } else {
466 1         2 $conf = {};
467             }
468              
469             # Make sure to freshen up the @ARGV_CLEAN list
470 1         4 @ARGV_CLEAN = (@argv);
471              
472 1         2 return $conf;
473             }
474              
475              
476             # This routine reprocesses the configuration hash, and replaces any [section:name], or [name] variables with their setting.
477             sub _stage2 {
478 1     1   2 my ($conf) = @_;
479 1         3 while(1){
480 1         1 my $try;
481             # This provides us with access to configuration options with in configuration options
482 1         2 for my $sec(keys %{ $conf }){
  1         4  
483 1         2 for my $key (keys %{ $conf->{$sec} }){
  1         18  
484 0         0 my @vals;
485 0 0       0 if(ref($conf->{$sec}->{$key}) eq 'ARRAY'){
486 0         0 for(my $i = 0; $i < @{ $conf->{$sec}->{$key} }; $i++){
  0         0  
487 0 0       0 if($conf->{$sec}->{$key}->[$i] =~ /\[([^:]+)\:([^\]]+)]/){
    0          
488 0         0 my ($aa, $ab) = ($1, $2);
489              
490 0 0       0 if($sec ne 'argv'){
491 0         0 $aa = lc $aa;
492 0         0 $ab = lc $ab;
493             }
494              
495 0 0       0 if(ref($conf->{$aa}->{$ab}) eq 'ARRAY'){
    0          
496 0         0 &_die("ERROR: the embedded key [$aa:$ab] in section [$sec] must be unique: " . join("; ", @{ $conf->{$aa}->{$ab} }));
  0         0  
497             } elsif($conf->{$aa}->{$ab}){
498 0         0 my ($raa, $rab) = ($aa, $ab);
499 0         0 $raa =~ s/(\W)/\\$1/g;
500 0         0 $rab =~ s/(\W)/\\$1/g;
501              
502 0         0 $conf->{$sec}->{$key}->[$i] =~ s/\[$raa:$rab\]/$conf->{$aa}->{$ab}/gi;
503 0         0 $try++;
504             }
505             } elsif($conf->{$sec}->{$key}->[$i] =~ /\[([^\]]+)]/){
506 0         0 my $aa = $1;
507              
508 0 0       0 $aa = lc $aa if($sec ne 'argv');
509              
510 0 0       0 if(ref($conf->{global}->{$aa}) eq 'ARRAY'){
    0          
511 0         0 &_die("ERROR: the embedded global key [$aa] in section [$sec] must be unique: " . join("; ", @{ $conf->{global}->{$aa} }));
  0         0  
512             } elsif($conf->{global}->{$aa}){
513 0         0 my $raa = $aa;
514 0         0 $raa =~ s/(\W)/\\$1/g;
515              
516 0         0 $conf->{$sec}->{$key}->[$i] =~ s/\[$raa]/$conf->{global}->{$aa}/gi;
517 0         0 $try++;
518             }
519             }
520             }
521             } else {
522 0 0 0     0 if(!ref($conf->{$sec}->{$key}) && $conf->{$sec}->{$key} =~ /\[([^:]+):([^\]]+)\]/){
    0 0        
523 0         0 my ($aa, $ab) = ($1, $2);
524              
525 0 0       0 if($sec ne 'argv'){
526 0         0 $aa = lc $aa;
527 0         0 $ab = lc $ab;
528             }
529              
530 0 0       0 if(ref($conf->{$aa}->{$ab}) eq 'ARRAY'){
    0          
531 0         0 &_die("ERROR: the embedded key [$aa:$ab] in section [$sec] must be unique: " . join("; ", @{ $conf->{$aa}->{$ab} }));
  0         0  
532             } elsif($conf->{$aa}->{$ab}){
533 0         0 my ($raa, $rab) = ($aa, $ab);
534 0         0 $raa =~ s/(\W)/\\$1/g;
535 0         0 $rab =~ s/(\W)/\\$1/g;
536              
537 0         0 $conf->{$sec}->{$key} =~ s/\[$raa:$rab\]/$conf->{$aa}->{$ab}/gi;
538 0         0 $try++;
539             }
540             } elsif(!ref($conf->{$sec}->{$key}) && $conf->{$sec}->{$key} =~ /\[([^\]]+)\]/){
541 0         0 my $aa = $1;
542              
543 0 0       0 $aa = lc $aa if($sec ne 'argv');
544              
545 0 0       0 if(ref($conf->{global}->{$aa}) eq 'ARRAY'){
    0          
546 0         0 &_die("ERROR: the embedded key [$aa] in section [$sec] must be unique: " . join("; ", @{ $conf->{global}->{$aa} }));
  0         0  
547             } elsif($conf->{global}->{$aa}){
548 0         0 my $raa = $aa;
549 0         0 $raa =~ s/(\W)/\\$1/g;
550              
551 0         0 $conf->{$sec}->{$key} =~ s/\[$raa\]/$conf->{global}->{$aa}/gi;
552 0         0 $try++;
553             }
554             }
555             }
556             }
557             }
558              
559 1 50       6 last if !$try;
560             }
561              
562 1         3 return $conf;
563             }
564              
565             # Internal failure handler
566             sub _die {
567             # detect if we're in a web enviroment
568 0 0 0 0     if($ENV{'QUERY_STRING'} || $ENV{'REQUEST_METHOD'}){
569 0           print "Content-type: text/plain\n\nConfiguration ERROR: $_[0]";
570 0           exit(64);
571             } else {
572 0           print STDERR "$_[0]\n";
573 0           exit(64);
574             }
575             }
576              
577             =head1 EXPORTS
578              
579             @ARGV_CLEAN is exported automatically containing any additional arguments from the @ARGV list which have been cleaned of options (--help etc.)
580              
581             =head1 AUTHOR
582              
583             Colin Faber
584              
585             =head1 LICENSE AND COPYTIGHT
586              
587             Copyright 2016-2021 (C) Colin Faber
588              
589             This library is licensed under the Perl Artistic license and may be freely used and distributed under the terms of Perl itself.