File Coverage

blib/lib/Config/Wild.pm
Criterion Covered Total %
statement 99 176 56.2
branch 40 90 44.4
condition 2 12 16.6
subroutine 13 22 59.0
pod 10 11 90.9
total 164 311 52.7


line stmt bran cond sub pod time code
1             # --8<--8<--8<--8<--
2             #
3             # Copyright (C) 1998-2011 Smithsonian Astrophysical Observatory
4             #
5             # This file is part of Config-Wild
6             #
7             # Config-Wild is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 3 of the License, or (at
10             # your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19             #
20             # -->8-->8-->8-->8--
21              
22             package Config::Wild;
23              
24 1     1   30466 use strict;
  1         3  
  1         38  
25 1     1   6 use warnings;
  1         1  
  1         32  
26              
27 1     1   5 use Carp qw( carp croak );
  1         5  
  1         160  
28 1     1   1157 use FileHandle;
  1         18043  
  1         6  
29 1     1   426 use Cwd qw[ getcwd ];
  1         3  
  1         3019  
30              
31             our $VERSION = '1.5';
32              
33              
34             sub new {
35 7     7 1 8811 my $this = shift;
36 7   33     33 my $class = ref( $this ) || $this;
37              
38 7 100       21 my $attr = ref $_[-1] eq 'HASH' ? pop @_ : {};
39              
40 7         52 my $self = {
41             wild => [], # regular expression keywords
42             abs => {}, # absolute keywords
43             attr => {
44             UNDEF => undef, # function to call from value when
45             # keyword not defined
46             PrintError => 0, # warn() on error
47             dir => '.',
48             %$attr,
49             },
50              
51             };
52              
53 7         17 bless $self, $class;
54              
55 7         34 my $file = shift;
56              
57 7 50       18 if ( $file ) {
58 7 50       18 $self->load( $file ) or return undef;
59             }
60              
61 7         25 $self;
62             }
63              
64             sub load {
65 7     7 1 11 my ( $self, $file ) = @_;
66 7         9 my ( $keyword, $value );
67              
68 7 50       15 unless ( $file ) {
69 0         0 $self->_errmsg( 'load: no file specified' );
70 0         0 return undef;
71             }
72              
73 7         10 my %files = ();
74 7         25 my @files = ( { file => $file, pos => 0 } );
75              
76 7         64 my $cwd = getcwd;
77 7 50       84 chdir( $self->{attr}{dir} ) or do {
78 0         0 $self->_errmsg(
79             "load: couldn't change directory to $self->{attr}{dir}" );
80 0         0 return undef;
81             };
82              
83 7         11 my $ret = eval {
84              
85             loop:
86 7         16 while ( @files ) {
87 15         22 my $file = $files[0]->{file};
88 15         23 my $pos = $files[0]->{pos};
89              
90             # if EOF on last file, don't bother with it
91 15 50       35 next if $files[0]->{pos} == -1;
92              
93 15 50       70 my $fh = new FileHandle $file or do {
94 0         0 $self->_errmsg( "load: error opening file `$file'" );
95 0         0 return;
96             };
97              
98 15         1106 seek( $fh, $files[0]->{pos}, 0 );
99              
100             # loop through file
101 15         17 my $line = 0;
102 15         1039 while ( <$fh> ) {
103 37         62 $files[0]->{pos} = tell;
104 37         38 $line++;
105              
106             # ignore comment lines or empty lines
107 37 100       254 next if /^\s*\#|^\s*$/;
108              
109 30         35 chomp;
110              
111 30 100       70 if ( /^\s*%include\s+(.*)/ ) {
112 4 50       15 if ( CORE::exists $files{$1} ) {
113 0         0 $self->_errmsg(
114             "load: infinite loop trying to read $1" );
115 0         0 return undef;
116             }
117 4         9 $files{$1}++;
118 4         19 unshift @files, { file => $1, pos => 0 };
119 4         15 $fh->close;
120 4         67 redo loop;
121             }
122              
123 26 50       54 $self->_parsepair( $_ ) or do {
124 0         0 $self->_errmsg( "load: $file: can't parse line $line" );
125 0         0 return;
126             }
127              
128             }
129              
130             }
131             continue {
132 11         39 shift @files;
133             }
134              
135              
136 7         14 return 1;
137             };
138              
139 7 50       116 chdir( $cwd ) or do {
140 0         0 $self->_errmsg( "load: error restoring directory to $cwd" );
141 0         0 return undef;
142             };
143              
144              
145 7         26 return $ret;
146             }
147              
148             sub load_cmd {
149 0     0 1 0 my ( $self, $argv, $attr ) = @_;
150 0         0 my $keyword;
151              
152 0 0       0 $attr = {} unless defined $attr;
153              
154 0         0 foreach ( @$argv ) {
155 0 0 0     0 if ( $$attr{Exists}
      0        
156             && ( $keyword = ( $self->_splitpair( $_ ) )[0] )
157             && !$self->_exists( $keyword ) )
158             {
159 0         0 $self->_errmsg( "load_cmd: keyword `$keyword' doesn't exist" );
160 0         0 return undef;
161             }
162              
163 0 0       0 $self->_parsepair( $_ ) or do {
164 0         0 $self->_errmsg( "load_cmd: can't parse line $_" );
165 0         0 return undef;
166             }
167             }
168              
169 0         0 1;
170             }
171              
172              
173             sub set {
174 26     26 1 32 my ( $self, $keyword, $value ) = @_;
175              
176 26 50 33     110 die unless defined( $keyword ) and defined( $value );
177             # so, is it a regular expression or not?
178 26 100       47 if ( $keyword =~ /\{/ ) {
179             # quote all characters outside of curly brackets.
180 4 100       18 $keyword = join(
181             '',
182             map {
183 2         12 substr( $_, 0, 1 ) ne '{'
184             ? quotemeta( $_ )
185             : substr( $_, 1, -1 )
186             } $keyword =~ /( [^{}]+ | {[^\}]*} )/gx
187             );
188              
189 2         5 unshift @{ $self->{wild} }, [ $keyword, $value ];
  2         8  
190             }
191             else {
192 24         72 $self->{abs}->{$keyword} = $value;
193             }
194             }
195              
196             # for backwards compatibility
197             sub value {
198 0     0 0 0 goto &get;
199             }
200              
201             sub get {
202 19     19 1 51 my ( $self, $keyword ) = @_;
203              
204 19 50       43 unless ( $keyword ) {
205 0         0 $self->_errmsg( 'value: no keyword specified' );
206 0         0 return undef;
207             }
208              
209 19 100       90 return $self->_expand( $self->{abs}->{$keyword} )
210             if CORE::exists( $self->{abs}->{$keyword} );
211              
212 2         3 foreach ( @{ $self->{wild} } ) {
  2         6  
213 3 100       57 return $self->_expand( $_->[1] ) if $keyword =~ /$_->[0]/;
214             }
215              
216 0 0       0 return $self->{attr}{UNDEF}->( $keyword )
217             if defined $self->{attr}{UNDEF};
218              
219 0         0 undef;
220             }
221              
222             sub getbool {
223              
224 7     7 1 8444 require Lingua::Boolean::Tiny;
225              
226 7         50330 my $self = shift;
227              
228 7         18 return Lingua::Boolean::Tiny::boolean( $self->get( @_ ) );
229             }
230              
231             sub delete {
232 0     0 1 0 my ( $self, $keyword ) = @_;
233              
234 0 0       0 unless ( $keyword ) {
235 0         0 $self->_errmsg( 'delete: no keyword specified' );
236 0         0 return undef;
237             }
238              
239 0 0       0 if ( CORE::exists $self->{abs}->{$keyword} ) {
240 0         0 delete $self->{abs}->{$keyword};
241             }
242             else {
243 0         0 $self->{wild} = grep( $_->[0] ne $keyword, @{ $self->{wild} } );
  0         0  
244             }
245 0         0 1;
246             }
247              
248             sub exists {
249 0     0 1 0 my ( $self, $keyword ) = @_;
250              
251 0 0       0 unless ( $keyword ) {
252 0         0 $self->_errmsg( 'exists: no keyword specified' );
253 0         0 return undef;
254             }
255              
256 0         0 return $self->_exists( $keyword );
257             }
258              
259             sub _exists {
260 0     0   0 my ( $self, $keyword ) = @_;
261              
262 0 0       0 return 1 if CORE::exists( $self->{abs}->{$keyword} );
263              
264 0         0 foreach ( @{ $self->{wild} } ) {
  0         0  
265 0 0       0 return 1 if $keyword =~ /$_->[0]/;
266             }
267              
268 0         0 undef;
269              
270             }
271              
272              
273             sub set_attr {
274 0     0 1 0 my ( $self, $attr ) = @_;
275 0         0 my ( $key, $value );
276              
277 0         0 while ( ( $key, $value ) = each %{$attr} ) {
  0         0  
278 0 0       0 unless ( CORE::exists $self->{attr}{$key} ) {
279 0         0 $self->_errmsg( "set_attr: unknown attribute: `$key'" );
280 0         0 return undef;
281             }
282 0         0 $self->{attr}{$key} = $value;
283             }
284              
285             }
286              
287              
288              
289             sub errmsg {
290 0     0 1 0 my $self = shift;
291 0         0 return $self->{errmsg};
292             }
293              
294             sub _errmsg {
295 0     0   0 my ( $self, $errmsg ) = @_;
296              
297 0         0 $self->{errmsg} = __PACKAGE__ . ': ' . $errmsg;
298 0 0       0 if ( $self->{attr}{PrintError} ) {
299 0 0       0 if ( ref( $self->{attr}{PrintError} ) eq 'CODE' ) {
300 0         0 $self->{attr}{PrintError}->( $errmsg );
301             }
302             else {
303 0         0 warn $errmsg, "\n";
304             }
305             }
306             }
307              
308              
309             #========================================================================
310             #
311             # AUTOLOAD
312             #
313             # Autoload function called whenever an unresolved object method is
314             # called. If the method name relates to a defined VARIABLE, we patch
315             # in $self->get() and $self->set() to magically update the varaiable
316             # (if a parameter is supplied) and return the previous value.
317             #
318             # Thus the function can be used in the folowing ways:
319             # $cfg->variable(123); # set a new value
320             # $foo = $cfg->variable(); # get the current value
321             #
322             # Returns the current value of the variable, taken before any new value
323             # is set. Prints a warning if the variable isn't defined (i.e. doesn't
324             # exist rather than exists with an undef value) and returns undef.
325             #
326             #========================================================================
327              
328             our $AUTOLOAD;
329             sub AUTOLOAD {
330 1     1   12 my $self = shift;
331 1         2 my $keyword;
332 1         1 my ( $oldval, $newval );
333              
334              
335             # splat the leading package name
336 1         6 ( $keyword = $AUTOLOAD ) =~ s/.*:://;
337              
338             # ignore destructor
339 1 50       4 $keyword eq 'DESTROY' && return;
340              
341 1 50       5 if ( CORE::exists( $self->{abs}->{$keyword} ) ) {
342 1         17 $oldval = $self->_expand( $self->{abs}->{$keyword} );
343             }
344             else {
345 0         0 my $found = 0;
346 0         0 foreach ( @{ $self->{wild} } ) {
  0         0  
347 0 0       0 $oldval = $self->_expand( $_->[1] ), $found++, last
348             if $keyword =~ /$_->[0]/;
349             }
350 0 0       0 if ( !$found ) {
351 0 0       0 return $self->{attr}{UNDEF}->( $keyword )
352             if defined( $self->{attr}{UNDEF} );
353              
354 0         0 $self->{errmsg} = __PACKAGE__ . ": $keyword doesn't exist";
355 0         0 return undef;
356             }
357             }
358              
359             # set a new value if a parameter was supplied
360 1 50       3 $self->set( $keyword, $newval )
361             if defined( $newval = shift );
362              
363             # return old value
364 1         6 return $oldval;
365             }
366              
367             sub _expand {
368 20     20   33 my ( $self, $value ) = @_;
369              
370 20         25 my $stop = 0;
371 20         40 until ( $stop ) {
372 30         29 $stop = 1;
373              
374             # expand ${VAR} as environment variables
375 30 50       67 $value =~ s/\$\{(\w+)\}/defined $ENV{$1} ? $ENV{$1} : ''/ge
  2 100       13  
376             and $stop = 0;
377              
378             # expand $(VAR) as a ConfigWild variable
379 30 100       72 $value =~ s{\$\((\w+)\)} {
380 6 50       38 defined $self->{abs}->{$1} ? $self->{abs}->{$1} : '';
381             }gex
382             and $stop = 0;
383              
384             # expand any unparenthesised/braced variables,
385             # e.g. "$var", as ConfigWild vars or environment variables.
386             # leave untouched if not
387 30 100       99 $value =~ s{\$(\w+)} {
388 2 50       21 defined $self->{abs}->{$1} ? $self->{abs}->{$1} :
    100          
389             defined $ENV{$1} ? $ENV{$1} :
390             "\$$1"
391             }gex
392             and $stop = 0;
393             }
394             # return the value
395 20         93 $value;
396             }
397              
398             sub _splitpair {
399 0     0   0 my ( $self, $pair ) = @_;
400 0         0 my ( $keyword, $value );
401              
402 0         0 $pair =~ s/^\s+//;
403 0         0 $pair =~ s/\s+$//;
404              
405 0 0       0 return 2 != ( ( $keyword, $value ) = $pair =~ /([^=\s]*)\s*=\s*(.*)/ )
406             ? ()
407             : ( $keyword, $value );
408             }
409              
410             sub _parsepair {
411 26     26   38 my ( $self, $pair ) = @_;
412              
413 26         24 my ( $keyword, $value );
414              
415 26         46 $pair =~ s/^\s+//;
416 26         61 $pair =~ s/\s+$//;
417              
418             return undef
419 26 50       148 if 2 != ( ( $keyword, $value ) = $pair =~ /([^=\s]*)\s*=\s*(.*)/ );
420              
421 26         62 $self->set( $keyword, $value );
422 26         175 1;
423             }
424              
425              
426             1;
427             __END__