Config-Fast
view release on metacpan or search on metacpan
lib/Config/Fast.pm view on Meta::CPAN
package Config::Fast;
=head1 NAME
Config::Fast - extremely fast configuration file parser
=head1 SYNOPSIS
# default config format is a space-separated file
company "Supercool, Inc."
support nobody@nowhere.com
# and then in Perl
use Config::Fast;
%cf = fastconfig;
print "Thanks for visiting $cf{company}!\n";
print "Please contact $cf{support} for support.\n";
=cut
use Carp;
use strict;
use Exporter;
use base 'Exporter';
our @EXPORT = qw(fastconfig);
our $VERSION = do { my @r=(q$Revision: 1.7 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
our %READCONF = ();
#
# Global settings - can override with $Config::Fast::PARAM = 'value';
#
our $DELIM = '\s+'; # default delimiter
our $KEEPCASE = 0; # preserve MixedCase variables?
our $ENVCAPS = 1; # setenv ALLCAPS variables?
our $ARRAYS = 0; # set var[0] as array elements?
our @DEFINE = (); # predefines of key=val
our %CONVERT = ( # convert these values appropriately
'true|on|yes' => 1,
'false|off|no' => 0,
);
# Aliases for prettiness
*Arrays = \$ARRAYS;
*Define = \@DEFINE;
*Delim = \$DELIM;
*Convert = \%CONVERT;
*EnvCaps = \$ENVCAPS;
*KeepCase = \$KEEPCASE;
#
# Internal variables; are overridable, but undocumented
#
our $MTIME = '_mtime';
our $ALLCAPS = '_allcaps';
our $SOURCE = '_source';
sub fastconfig (;$$) {
my $file = shift;
my $delim = shift || $DELIM;
# auto file detection
unless ($file) {
require File::Basename;
my $dir = File::Basename::dirname($ENV{SCRIPT_NAME} || $0); # mod_perl usage
my $prog = File::Basename::basename($ENV{SCRIPT_NAME} || $0);
require File::Spec;
$file = File::Spec->catfile($dir, '..', 'etc', "$prog.conf")
}
# Allow $file, \$file, or \@file
my @file;
my $mtime = 0;
if (my $ref = ref $file) {
if ($ref eq 'SCALAR') {
@file = $$file;
$READCONF{$file}{$SOURCE} = 'scalar';
} elsif ($ref eq 'ARRAY') {
@file = @$file;
$READCONF{$file}{$SOURCE} = 'array';
} else {
croak "fastconfig: Invalid data type '$ref' for file arg";
}
} else {
# Flat file; open if newer than cache
croak "fastconfig: Invalid configuration file '$file'"
unless -f $file && -r _;
$mtime = -M _;
if (! $READCONF{$file}{$MTIME} || $mtime < $READCONF{$file}{$MTIME}) {
open CF, "<$file" or croak "fastconfig: Can't open $file: $!";
@file = <CF>;
close CF;
$READCONF{$file}{$SOURCE} = 'file';
}
}
if (@file) {
$READCONF{$file}{$ALLCAPS} ||= [];
# Generate unique package name to isolate vars
my $srcpkg = join '::', __PACKAGE__, 'Parser' . time() . $$;
eval "{ package $srcpkg; " . <<'EndOfParser';
#
# We now parse variables by eval'ing them inline. This gets us
# the same quoting conventions Perl uses implicitly.
#
no strict;
use Carp;
# Predefine anything in @DEFINE by unshifting onto @file (kludge)
my @lines = @Config::Fast::DEFINE;
for (@file) {
next if /^\s*$/ || /^\s*#/; chomp;
push @lines, [split /$delim/, $_, 2];
}
for (@lines) {
my($key, $val) = @$_;
# See if our var is ALLCAPS to setenv it
my $env = $key =~ /^[A-Z0-9_]+(\[\d+\])?$/ ? $key : undef;
$val =~ s/^\s*(["']?)(.*)\1\s*$/$2/g;
my $q = $1 || '"'; # save quote
unless ($q eq "'") {
$val =~ s/([^a-zA-Z0-9_\$\\'"])/\\$1/g # escape nasty (sneaky?) chars
}
$val = qq{$q$val$q}; # add quotes back in
# Now check for "on/off" or "true/false"
for my $pat (keys %Config::Fast::CONVERT) {
$val = $Config::Fast::CONVERT{$pat} if $val =~ /^($pat)$/i;
}
# Convert MixedCaseGook to $mixedcasegook?
my $pkey = $Config::Fast::KEEPCASE ? $key : lc($key);
# Can only allow substitutions on RegularKeys, not weird+val:stuff
my $tkey = $key =~ /^[a-zA-Z]\w*$/ ? $key : 'junk';
my $ekey;
if ($Config::Fast::ARRAYS && $pkey =~ s/\[(\d+)\]$//) {
$ekey = q($Config::Fast::READCONF{$file}{$pkey}[$1] = ${$tkey}[$1] = );
} else {
$ekey = q($Config::Fast::READCONF{$file}{$pkey} = $$tkey = );
}
eval $ekey . '$tmp = ' . $val;
warn "fastconfig: Parse error:\$$key = $val: $@" if $@;
# Push it as an env var if so requested
if ($Config::Fast::ENVCAPS && $env) {
push @{$Config::Fast::READCONF{$file}{$Config::Fast::ALLCAPS}},
[ $env => $tmp ];
}
}
$Config::Fast::READCONF{$file}{$Config::Fast::MTIME} = $mtime;
} # eval block
EndOfParser
} else {
$READCONF{$file}{$SOURCE} = 'cache';
}
# ALLCAPS vars go into env, do this each time so that
# calls to fastconfig() always reset the environment.
for (@{$READCONF{$file}{$ALLCAPS}}) {
$ENV{$_->[0]} = $_->[1];
}
if (wantarray) {
return %{$READCONF{$file}};
} else {
# import vars into main namespace
no strict 'refs';
while (my($k,$v) = each %{$READCONF{$file}}) {
next if $k =~ /^_/ || $k =~ /\W/;
eval {
*{"main::$k"} = \$v;
};
croak "fastconfig: Could not import variable '$k': $@" if $@;
}
( run in 0.604 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )