File Coverage

blib/lib/Parse/PerlConfig.pm
Criterion Covered Total %
statement 144 195 73.8
branch 53 88 60.2
condition 1 3 33.3
subroutine 16 20 80.0
pod 0 1 0.0
total 214 307 69.7


line stmt bran cond sub pod time code
1             # Parse::PerlConfig - parse a configuration file written in Perl
2              
3             # Copyright (C) 1999 Michael Fowler, all rights reserved
4              
5             # This program is free software; you can redistribute it and/or modify it
6             # under the same terms as Perl itself.
7              
8              
9             package Parse::PerlConfig;
10              
11             require Exporter;
12 7     7   21679 use Fcntl qw(O_RDONLY);
  7         16  
  7         435  
13 7     7   42 use strict;
  7         13  
  7         301  
14             use vars (
15 7         10369 qw($VERSION @ISA @EXPORT_OK),
16             qw($saved_dollar_slash), # used in _do_file()
17 7     7   39 );
  7         17  
18              
19              
20             @ISA = qw(Exporter);
21              
22             @EXPORT_OK = qw(parse);
23              
24             $VERSION = '0.05';
25              
26              
27             my %thing_str2key = (
28             '$' => 'SCALAR',
29             '@' => 'ARRAY',
30             '%' => 'HASH',
31             '&' => 'CODE',
32             '*' => 'GLOB',
33             'i' => 'IO',
34             );
35              
36              
37             my %thing_key2str;
38              
39             @thing_key2str{values %thing_str2key} = keys(%thing_str2key);
40              
41              
42              
43             sub parse {
44 7     7 0 43483 local *FILE;
45 7         43 my $subname = (caller(0))[3];
46              
47 7         250 my %args = (
48             Namespace_Base => __PACKAGE__ . '::ConfigFile',
49              
50             Thing_Order => '$@%&i*',
51             Taint_Clean => 0,
52              
53             Warn_default => 'noop',
54             Warn_preparse => 'default',
55             Warn_eval => 'default',
56              
57             Error_default => 'warn',
58             Error_argument => 'default',
59             Error_file_is_dir => 'default',
60             Error_failed_open => 'default',
61             Error_eval => 'default',
62             Error_unknown_thing => 'default',
63             Error_unknown_handler => 'default',
64             Error_invalid_lexical => 'default',
65             Error_invalid_namespace => 'default',
66             );
67              
68              
69 7 50       59 if (ref($_[0]) eq 'HASH') { # first argument is a hash..
    50          
    50          
70 0         0 %args = (%args, %{+ shift }, @_); # ..dereference it
  0         0  
71              
72             } elsif (ref($_[0]) eq 'ARRAY') { # first argument is an array..
73 0         0 %args = (%args, @{+ shift }, @_); # ..dereference it
  0         0  
74              
75             } elsif (@_) {
76 7         118 %args = (%args, @_);
77             }
78              
79              
80 7         80 my $def_errsub = _errsub($args{'Error_default'});
81 7         20 my $def_warnsub = _errsub($args{ 'Warn_default'});
82              
83 7         21 my(%errsubs, %warnsubs);
84 7         23 foreach my $handler (qw(
85             argument
86             file_is_dir
87             failed_open
88             eval
89             unknown_thing
90             unknown_handler
91             invalid_namespace
92             )) {
93 49         114 $errsubs{$handler} = _errsub($args{"Error_$handler"}, $def_errsub);
94             }
95              
96              
97 7         35 foreach my $handler (qw(preparse eval)) {
98 14         38 $warnsubs{$handler} = _errsub($args{"Warn_$handler"}, $def_warnsub);
99             }
100              
101              
102             # This allows us to pass around %args, rather than each hash necessary.
103 7         19 $args{'_errsubs'} = \%errsubs;
104 7         16 $args{'_warnsubs'} = \%warnsubs;
105              
106              
107              
108 7         11 my @files;
109 7 50       29 push(@files, $args{File}) if defined($args{File});
110              
111 7 50       39 if (ref($args{Files}) eq 'ARRAY') {
    50          
112 0         0 push(@files, @{ $args{Files} });
  0         0  
113              
114             } elsif (defined $args{Files}) {
115 0         0 push(@files, $args{Files});
116             }
117              
118 7 50       19 unless (@files) {
119 0         0 $errsubs{'argument'}->(
120             "Files or File argument required in call to $subname."
121             );
122 0         0 return;
123             }
124              
125              
126 7         14 my @handlers;
127 7 100       24 push(@handlers, $args{Handler}) if defined($args{Handler});
128              
129 7 100       34 if (ref($args{Handlers}) eq 'ARRAY') {
    50          
130 1         3 push(@handlers, @{ $args{Handlers} });
  1         3  
131              
132             } elsif (defined $args{Handlers}) {
133 0         0 push(@handlers, $args{Handlers});
134             }
135              
136              
137 7         13 my %lexicals;
138 7 100       30 if (ref $args{Lexicals} eq 'HASH') {
    50          
139 1         4 %lexicals = %{ $args{Lexicals} };
  1         5  
140              
141             } elsif (defined $args{Lexicals}) {
142 0         0 $errsubs{'argument'}->(
143             "Lexicals argument must be a hashref in call to $subname."
144             );
145             }
146              
147              
148 7         39 my @def_thing_order = _thingstr_to_array(\%args, $args{'Thing_Order'});
149              
150 7         28 my %custom_symbols;
151 7 50       25 if (ref($args{Symbols}) eq 'HASH') {
152 0         0 while (my($sym, $order) = each(%{$args{Symbols}})) {
  0         0  
153 0         0 $custom_symbols{$sym} = [ _thingstr_to_array(\%args, $order) ];
154             }
155             }
156              
157              
158 7         31 my $lexicals_string = _construct_lexicals_string(\%args, \%lexicals);
159              
160              
161              
162             # Having checked all of our arguments, we run through our files.
163 7         15 my %parsed_symbols;
164 7         18 FILE: foreach my $file (@files) {
165              
166 7 50       481 $errsubs{'file_is_dir'}->("Config file \"$file\" is a directory.")
167             if -d $file;
168              
169 7         40 $warnsubs{'preparse'}->("Preparing to parse config file \"$file\".");
170              
171              
172              
173              
174 7 50       416 unless (sysopen FILE, $file, O_RDONLY) {
175 0         0 $errsubs{'failed_open'}->(
176             "Unable to open config file \"$file\": \l$!."
177             );
178 0         0 return;
179             }
180              
181              
182 7 50       26 if ($args{'Taint_Clean'}) {
183 0         0 require IO::Handle;
184 0         0 FILE->untaint;
185             }
186              
187              
188 7         665 my $namespace = _construct_namespace(\%args, $file);
189              
190 7 50       25 unless (_valid_namespace($namespace)) {
191 0         0 $errsubs{'invalid_namespace'}->(
192             "Namespace \"$namespace\" is invalid."
193             );
194 0         0 return;
195             }
196            
197              
198             {
199 7         12 my %parse_perl_config = (
  7         42  
200             Parse_Args => \%args,
201             Filename => $file,
202             Namespace => $namespace,
203             Error => undef,
204             );
205              
206 7         20 my $eval_warn = $warnsubs{'eval'};
207              
208 7     0   80 local $SIG{__WARN__} = sub { $eval_warn->(join "", @_) };
  0         0  
209 7         31 _do_file(\*FILE, $namespace, \%parse_perl_config, $lexicals_string);
210              
211              
212 7         28 my $error;
213 7 50       151 if (defined($error = $parse_perl_config{Error})) {
    50          
214 0         0 $errsubs{'eval'}->(
215             "Configuration file raised an error: $error."
216             );
217 0         0 next FILE;
218              
219             } elsif ($@) {
220 0         0 $error = $@;
221 0         0 1 while chomp($error);
222              
223 0         0 $errsubs{'eval'}->("Error in configuration eval: $error.");
224 0         0 next FILE;
225             }
226             }
227              
228              
229             _parse_symbols(
230 7         51 Namespace => $namespace,
231             Thing_Order => \@def_thing_order,
232             Symbols => \%custom_symbols,
233             Hash => \%parsed_symbols,
234             );
235             }
236              
237              
238 7         41 _dispatch_handlers(\%args, \@handlers, \%parsed_symbols);
239              
240              
241 7         25770 return \%parsed_symbols;
242             }
243              
244              
245              
246              
247             # _parse_symbols
248             # Namespace - namespace to parse symbols from
249             # Symbols - hashref of symbols with specific thing ordering
250             # Thing_Order - default thing order, arrayref
251             # Hash - a hashref into which parsed symbols are placed
252             #
253             # This an internal function used by parse() to do the actual parsing of
254             # symbols from a namespace. This function has the potential to be a public
255             # one, if sanity checking on arguments is added.
256              
257             sub _parse_symbols {
258 7     7   40 my %args = @_;
259              
260 7         18 my $namespace = $args{'Namespace'};
261 7         14 my %custom_symbols = %{ $args{'Symbols'} };
  7         23  
262 7         13 my @def_thing_order = @{ $args{'Thing_Order'} };
  7         29  
263 7         16 my $parsed_symbols = $args{'Hash'};
264              
265              
266 7     7   67 no strict 'refs';
  7         10  
  7         2786  
267 7         14 while (my($symbol, $glob) = each(%{"$namespace\::"})) {
  210         1484  
268 203         252 my @thing_order;
269              
270 203 50       540 if (exists $custom_symbols{$symbol}) {
271 0         0 @thing_order = @{ $custom_symbols{$symbol} };
  0         0  
272             } else {
273 203         597 @thing_order = @def_thing_order;
274             }
275              
276              
277 203         229 my $value;
278 203         354 foreach my $thing (@thing_order) {
279 308 100       928 if ($thing eq 'SCALAR') {
    100          
280             # Special case for scalars; we always get a scalar
281             # reference, even if the underlying scalar is undefined.
282 170 100       329 if (defined ${ *$glob{SCALAR} }) {
  170         697  
283 87         102 $value = ${ *$glob{SCALAR} };
  87         446  
284 87         187 last;
285             }
286              
287             } elsif (defined *$glob{$thing}) {
288 76         187 $value = *$glob{$thing};
289 76         152 last;
290             }
291             }
292              
293              
294 203 100       698 $$parsed_symbols{$symbol} = $value if defined($value);
295              
296              
297             # In order to prevent various warnings, and the symbols from still
298             # being there (even though the symbol table isn't), we undef each
299             # glob as we go.
300 203         234 undef(*{"$namespace\::$symbol"});
  203         2688  
301             }
302              
303              
304 7         47 return;
305             }
306              
307              
308              
309              
310             sub _dispatch_handlers {
311 7     7   22 my($args, $handlers, $parsed_symbols) = (shift, shift, shift);
312              
313 7         20 foreach my $handler (@$handlers) {
314 4 100       21 if (ref $handler eq 'CODE') {
    50          
315 2         15 $handler->($parsed_symbols);
316              
317             } elsif (ref $handler eq 'HASH') {
318 2         68 @$handler{keys %$parsed_symbols} = values %$parsed_symbols
319              
320             } else {
321 0         0 $$args{'_errsubs'}{'unknown_handler'}->(
322             'Unknown handler type "' . ref($handler) . '"'
323             );
324             }
325             }
326             }
327              
328              
329              
330              
331             # _do_file
332             #
333             #
334             # Reads the given filename using sysopen and eval, in the specified
335             # namespace. The hash %parse_perl_config is set with the specified
336             # hashref.
337             #
338             # The reason this subroutine exists is to keep the lexical space as clean
339             # as possible, while still allowing some lexicals through. Were this
340             # functionality inlined with the rest of parse(), a configuration file
341             # would have access to parse()'s lexicals. To keep things even cleaner
342             # local() is used rather than my(). Obviously the latter is preferable,
343             # but in this case, would cause problems
344              
345             sub _do_file {
346             # Arguments are accessed through @_ indexing, rather than shifting, to
347             # keep the lexical space as clean as possible.
348              
349 7     7   14 local *FILE = $_[0] ;
350 7         43 my %parse_perl_config = %{+ $_[2] };
  7         35  
351              
352              
353             # We go to some lengths to be able to slurp the file, while
354             # still keeping $/ intact.
355              
356 7         20 local $saved_dollar_slash = $/;
357 7         23 local $/;
358              
359 7     7   41 no strict;
  7         12  
  7         15806  
360 7         5311 eval '$/ = $saved_dollar_slash;' .
361             "package $_[1];" .
362             $_[3] . # lexical definitions
363            
364             ;
365             }
366              
367              
368              
369              
370             sub _construct_lexicals_string {
371 7     7   19 my($args, $lexicals) = (shift, shift);
372              
373 7 100       27 return '' unless %$lexicals;
374              
375 1         11 require Data::Dumper;
376              
377 1         3 my $inv_lex_errsub = $$args{'_errsubs'}{'invalid_lexical'};
378              
379 1         2 my $lexicals_string = '';
380 1         18 LEXICAL: while (my($key, $value) = each(%$lexicals)) {
381              
382 3 50 33     197 if ($key !~ /^([^_\W][\w\d]*|\w[\w\d]+)$/) {
    50          
    50          
383 0         0 $inv_lex_errsub->(
384             "Lexical name \"$key\" is invalid, must be a valid " .
385             "identifier."
386             );
387              
388 0         0 next LEXICAL;
389              
390             } elsif (ref($value) eq 'CODE') {
391 0         0 $inv_lex_errsub->(
392             "Lexical \"$key\" value is invalid, code references " .
393             "are not allowed."
394             );
395              
396 0         0 next LEXICAL;
397              
398             } elsif ($key eq 'parse_perl_config' && ref($value) eq 'HASH') {
399 0         0 $inv_lex_errsub->(
400             "Cannot have a hash lexical named \"parse_perl_config\"."
401             );
402              
403 0         0 next LEXICAL;
404             }
405              
406 3         23 $lexicals_string .= 'my ' . Data::Dumper->Dump([$value], ["*$key"]);
407             }
408              
409              
410 1         55 $lexicals_string;
411             }
412              
413              
414              
415              
416              
417             sub _construct_namespace {
418 7     7   19 my($args, $file) = (shift, shift);
419              
420 7         9 my $namespace;
421 7 50       24 if (defined $$args{'Namespace'}) {
422 0         0 $namespace = $$args{'Namespace'};
423              
424             } else {
425 7         33 $namespace = "$$args{'Namespace_Base'}::" . _encode_namespace($file);
426             }
427              
428              
429 7 50       25 if ($$args{'Taint_Clean'}) {
430             # We've already filtered the namespace, but perl doesn't know
431             # that; fake it.
432 0         0 ($namespace) = ($namespace =~ /(.*)/);
433             }
434              
435              
436 7         21 return $namespace;
437             }
438              
439              
440              
441             sub _valid_namespace {
442 7     7   15 my $namespace = shift;
443              
444 7         31 foreach my $ns_ele (split /::/, $namespace) {
445 28 50       121 return 0 unless $ns_ele =~ /^[_A-Za-z][_A-Za-z0-9]*/;
446             }
447              
448 7         28 return 1;
449             }
450              
451              
452              
453             sub _encode_namespace {
454 7     7   14 my $namespace = shift;
455              
456 7         10 my @namespace;
457 7         26 foreach my $ns_ele (split /::/, $namespace) {
458             # ^A-Za-z0-9 (as opposed to [\W\D]) is spelled out explicitly
459             # because package names are not (yet?) locale-friendly.
460 7         61 $ns_ele =~
461             s{
462             (
463             (?:^[^A-Za-z]) # first character must not be a number
464             |
465             [^A-Za-z0-9] # any further characters can be
466             )
467             }{
468 85         355 sprintf("_%2x", ord $1)
469             }egx
470             ;
471              
472              
473 7         26 push(@namespace, $ns_ele);
474             }
475              
476              
477 7         36 return join("::", @namespace);
478             }
479              
480              
481              
482              
483              
484             # _thingstr_to_array
485             #
486             # Translates a thing string ($%@*i&) into the associated glob keys:
487             # $ SCALAR
488             # @ ARRAY
489             # % HASH
490             # * GLOB
491             # i IO
492             # & CODE
493             #
494             #
495             # Returns the keys as a list, in the same order. The specified coderef is
496             # an error function used to report when an unknown character is
497             # encountered.
498             #
499             # If the string is actually an array reference, the array is dereferenced,
500             # checked for invalid keys, and returned.
501              
502             sub _thingstr_to_array {
503 7     7   15 my($args, $string) = (shift, shift);
504              
505 7         42 my $errsub = $$args{'_errsubs'}{'unknown_thing'};
506              
507 7 50       22 if (ref($string) eq 'ARRAY') {
508 0         0 my @filtered;
509              
510 0         0 foreach my $thing (@$string) {
511 0         0 $thing = uc($thing);
512 0 0       0 if (!exists $thing_key2str{$thing}) {
513 0         0 $errsub->("Unknown thing key \"$thing\".");
514 0         0 next;
515             }
516              
517 0         0 push(@filtered, $thing);
518             }
519              
520 0         0 return @filtered;
521             }
522              
523              
524 7         31 my @keys;
525 7         64 foreach my $c (split //, $string) {
526 28 50       76 unless (defined $thing_str2key{$c}) {
527 0         0 $errsub->("Undefined thing string \"$c\".");
528 0         0 next;
529             }
530              
531 28         59 push(@keys, $thing_str2key{$c});
532             }
533              
534 7         29 return @keys;
535             }
536              
537              
538              
539              
540 0     0   0 sub _fwarn { CORE::warn(shift() . "\n") }
541 0 0   0   0 sub _warn { CORE::warn(shift() . "\n") if $^W }
542 0     0   0 sub _die { CORE::die (shift() . "\n") }
543 7     7   12 sub _noop { }
544              
545             # _errsub []
546             # Responsible for parsing the "default", "noop", "warn", "fwarn", and
547             # "die" strings, and returning an appropriate code reference.
548              
549             sub _errsub {
550 77     77   164 my($spec, $default) = (shift, shift);
551 77 50       225 $spec = lc($spec) unless ref($spec);
552              
553 77 50       159 (ref $spec eq 'CODE' ) && return $spec;
554 77 100       152 ( $spec eq 'warn' ) && return \&_warn;
555 70 50       124 ( $spec eq 'fwarn') && return \&_fwarn;
556 70 50       123 ( $spec eq 'die' ) && return \&_die;
557 70 100       126 ( $spec eq 'noop' ) && return \&_noop;
558              
559             # catch anything that falls through
560 63 50       229 return (ref $default eq 'CODE') ? $default : \&_warn;
561             }
562              
563              
564              
565              
566             1;
567              
568              
569             __END__