File Coverage

blib/lib/WebDAO/Config.pm
Criterion Covered Total %
statement 112 124 90.3
branch 39 52 75.0
condition 12 20 60.0
subroutine 14 15 93.3
pod 1 6 16.6
total 178 217 82.0


line stmt bran cond sub pod time code
1             package WebDAO::Config;
2              
3             =head1 NAME
4              
5             WebDAO::Config - Configuration file class.
6              
7             =head1 SYNOPSIS
8              
9             use WebDAO::Config;
10             my $conf = new WebDAO::Config:: ( $opt{config} );
11             my $value = $conf->general->{db_name};
12              
13              
14             =head1 DESCRIPTION
15              
16             Configuration file class
17              
18             =head3 Format of INI-FILE
19              
20             Data is organized in sections. Each key/value pair is delimited with an
21             equal (=) sign. Sections are declared on their own lines enclosed in
22             '[' and ']':
23              
24             [BLOCK1]
25             KEY1 ?=VALUE1
26             KEY2 +=VALUE2
27              
28              
29             [BLOCK2]
30             KEY1=VALUE1
31             KEY2=VALUE2
32              
33             #%INCLUDE file.inc%
34              
35             =item B - set value unless it defined before
36              
37             =item B<+=> - add value
38              
39             =item B<=> - set value to key
40              
41             =item B<#%INCLUDE file.inc%> - include config ini file
42              
43             =cut
44              
45 1     1   33050 use strict;
  1         3  
  1         26  
46 1     1   5 use warnings;
  1         2  
  1         28  
47 1     1   522 use WebDAO::Base;
  1         3  
  1         78  
48 1     1   5 use base 'WebDAO::Base';
  1         2  
  1         110  
49 1     1   5 use vars qw($AUTOLOAD);
  1         2  
  1         52  
50 1     1   865 use IO::File;
  1         10210  
  1         192  
51             our $VERSION = '0.4';
52             our $PERL_SINGLE_QUOTE;
53              
54             __PACKAGE__->mk_attr ( __conf=>undef, _path=>undef );
55              
56             sub parse_line {
57 22     22 0 70 my($delimiter, $keep, $line) = @_;
58 22         36 my($word, @pieces);
59              
60 1     1   7 no warnings 'uninitialized'; # we will be testing undef strings
  1         993  
  1         1545  
61              
62 22         300 while (length($line)) {
63             # This pattern is optimised to be stack conservative on older perls.
64             # Do not refactor without being careful and testing it on very long strings.
65             # See Perl bug #42980 for an example of a stack busting input.
66 28 50       631 $line =~ s/^
67             (?:
68             # double quoted string
69             (") # $quote
70             ((?>[^\\"]*(?:\\.[^\\"]*)*))" # $quoted
71             | # --OR--
72             # singe quoted string
73             (') # $quote
74             ((?>[^\\']*(?:\\.[^\\']*)*))' # $quoted
75             | # --OR--
76             # unquoted string
77             ( # $unquoted
78             (?:\\.|[^\\"'])*?
79             )
80             # followed by
81             ( # $delim
82             \Z(?!\n) # EOL
83             | # --OR--
84             (?-x:$delimiter) # delimiter
85             | # --OR--
86             (?!^)(?=["']) # a quote
87             )
88             )//xs or return; # extended layout
89 28 100       412 my ($quote, $quoted, $unquoted, $delim) = (($1 ? ($1,$2) : ($3,$4)), $5, $6);
90              
91              
92 28 50 100     746 return() unless( defined($quote) || length($unquoted) || length($delim));
      66        
93              
94 28 50       71 if ($keep) {
95 0         0 $quoted = "$quote$quoted$quote";
96             }
97             else {
98 28         125 $unquoted =~ s/\\(.)/$1/sg;
99 28 100       89 if (defined $quote) {
100 16 100       60 $quoted =~ s/\\(.)/$1/sg if ($quote eq '"');
101 16 50 33     59 $quoted =~ s/\\([\\'])/$1/g if ( $PERL_SINGLE_QUOTE && $quote eq "'");
102             }
103             }
104 28         76 $word .= substr($line, 0, 0); # leave results tainted
105 28 100       81 $word .= defined $quote ? $quoted : $unquoted;
106            
107 28 100       188 if (length($delim)) {
108 4         12 push(@pieces, $word);
109 4 50       16 push(@pieces, $delim) if ($keep eq 'delimiters');
110 4         11 undef $word;
111             }
112 28 100       161 if (!length($line)) {
113 22         202 push(@pieces, $word);
114             }
115             }
116 22         107 return(@pieces);
117             }
118              
119             #method for convert 'file_name', \*FH, \$string, to hash
120              
121             sub convert_ini2hash {
122 5     5 0 25 my $data = shift;
123              
124             #if we got filename
125 5 100       34 unless ( ref $data ) {
126 1         15 my $fh = new IO::File:: "< $data";
127 1         160 my $res = &convert_ini2hash($fh);
128 1         32 close $fh;
129 1         24 return $res;
130             }
131              
132             #We got file descriptor ?
133 4 50 66     96 if ( ref $data
      33        
      66        
134             and ( UNIVERSAL::isa( $data, 'IO::Handle' ) or ( ref $data ) eq 'GLOB' )
135             or UNIVERSAL::isa( $data, 'Tie::Handle' ) )
136             {
137              
138             #read all data from file descripto to scalar
139 1         2 my $str;
140             {
141 1         3 local $/;
  1         7  
142 1         454888 $str = <$data>;
143             }
144 1         133 return &convert_ini2hash( \$str );
145             }
146 3         14 my %result = ();
147 3         9 my $line_num = 0;
148 3         8 my $section = 'default';
149              
150             #if in param ref to scalar
151 3         203 foreach ( split /(?:\015{1,2}\012|\015|\012)/, $$data ) {
152 36         70 my $line = $_;
153 36         59 $line_num++;
154              
155             # skipping comments and empty lines:
156              
157 36 100       271 $line =~ /^\s*(\n|\#|;)/ and next;
158 31 100       128 $line =~ /\S/ or next;
159              
160 30         62 chomp $line;
161              
162 30         126 $line =~ s/^\s+//g;
163 30         109 $line =~ s/\s+$//g;
164              
165             # parsing the block name:
166 30 100       263 $line =~ /^\s*\[\s*([^\]]+)\s*\]$/ and $section = lc($1), next;
167              
168             # parsing key/value pairs
169             # process ?= and += features
170 22 50       139 if ( $line =~ /^\s*([^=]*\w)\s*([\?\+]?=)\s*(.*)\s*$/ ) {
171 22         133 my $key = lc($1);
172 22         63 my @value = parse_line( '\s*,\s*', 0, $3 );
173 22         62 my $op = $2;
174              
175             #add current key
176 22 100       94 if ( $op =~ /\+=/ ) {
    100          
177 2         5 push @{ $result{$section}->{$key} }, @value;
  2         11  
178 2         8 next;
179             }
180              
181             # skip if already defined key
182             elsif ( $op =~ /\?=/ ) {
183 4 100       22 next if defined $result{$section}->{$key};
184             }
185              
186             # set current value to result hash
187 18         180 $result{$section}->{$key} = \@value;
188 18         89 next;
189             }
190              
191             # if we came this far, the syntax couldn't be validated:
192 0         0 warn "syntax error on line $line_num: '$line'";
193 0         0 return {};
194             }
195              
196             #strip values
197 3         26 while ( my ( $sect_name, $sect_hash ) = each %result ) {
198 6         24 while ( my ( $key, $val ) = each %$sect_hash ) {
199 13 100       55 if ( scalar(@$val) < 2 ) {
200 8         54 $result{$sect_name}->{$key} = shift @$val;
201             }
202             }
203             }
204 3         85 return \%result;
205             }
206              
207             sub get_full_path_for {
208 1     1 0 3 my $root_file = shift;
209              
210             # my $file_to = shift;
211 1         6 my @req_path = @_;
212 1         5 my $req_path = join "/", @req_path;
213 1 50       6 return $req_path if $req_path =~ /^\//;
214 1         5 my @ini_path = split( "/", $root_file );
215              
216             #strip file name
217 1         2 pop @ini_path;
218 1         3 my $path = join "/" => @ini_path, $req_path;
219              
220             # _log1 $self "File $path not exists" unless -e $path;
221 1         8 return $path;
222             }
223              
224             sub process_includes {
225 3     3 0 6 my $file = shift;
226 3   50     28 my $fh = ( new IO::File:: "< $file" ) || die "$file: $!";
227 3         285 my $str = '';
228 3         2287806 while ( defined( my $line = <$fh> ) ) {
229              
230 28 100       190 $str .=
231             $line =~ /#%INCLUDE\s*(.*)\s*%/
232             ? &process_includes( &get_full_path_for( $file, $1 ) )
233             : $line;
234             }
235 3         41 close $fh;
236 3         32 return $str;
237             }
238              
239             sub new {
240 1     1 1 12 my $class = shift;
241 1         2 my $self = {};
242 1         3 my $stat;
243 1         24 bless( $self, $class );
244 1         5 $self->_init(@_);
245 1         12 return $self;
246             }
247              
248             sub _init {
249 1     1   2 my $self = shift;
250 1         2 my $file_path = shift;
251              
252             #process inludes in in data
253 1         4 my $inc = &process_includes($file_path);
254 1         12 $self->__conf( &convert_ini2hash(\$inc) );
255 1         141 $self->_path($file_path);
256 1         4 return 1;
257             }
258              
259             sub get_full_path {
260 0     0 0 0 my $self = shift;
261 0         0 my @req_path = @_;
262 0         0 my $req_path = join "/", @req_path;
263 0 0       0 return $req_path if $req_path =~ /^\//;
264 0         0 my @ini_path = split( "/", $self->_path );
265 0         0 pop @ini_path;
266 0         0 my $path = join "/" => @ini_path, $req_path;
267 0 0       0 _log1 "File $path not exists" unless -e $path;
268 0         0 return $path;
269             }
270              
271             sub AUTOLOAD {
272 1     1   5 my $self = shift;
273 1 50       8 return if $AUTOLOAD =~ /::DESTROY$/;
274 1         23 ( my $auto_sub ) = $AUTOLOAD =~ /.*::(.*)/;
275 1         138 return $self->__conf->{$auto_sub};
276             }
277             1;
278             __END__