Config-Interactive
view release on metacpan or search on metacpan
lib/Config/Interactive.pm view on Meta::CPAN
package Config::Interactive;
use strict;
use warnings;
use 5.006_001;
=head1 NAME
Config::Interactive - config module with support for interpolation, XML fragments and interactive UI
=head1 VERSION
Version 0.04
=cut
our $VERSION = '0.04';
=head1 DESCRIPTION
This module opens a config file and parses it's contents for you. The I<new()> method
accepts several parameters. The method 'parse' returns a hash reference
which contains all options and it's associated values of your config file as well as comments above.
If the dialog mode is set then at the moment of parsing user will be prompted to enter different value and
if validation pattern for this particular key was defined then it will be validated and user could be asked to
enter different value if it failed.
The format of config files supported by L<Config::Interactive> is
C<< <name>=<value> >> pairs or XML fragments (by L<XML::Simple>, namespaces are not supported) and comments are any line which starts with #.
Comments inside of XML fragments will pop-up on top of the related fragment. It will interpolate any perl variable
which looks as C< ${?[A-Za-z]\w+}? >.
Please not that interpolation works for XML fragments as well, BUT interpolated varialbles MUST be defined
by C<key=value> definition and NOT inside of other XML fragment!
The order of appearance of such variables in the config file is not important, means you can use C<$bar> variable anywhere in the config file but
set it to something on the last line (or even skip setting it at all , then it will be undef).
It stores internally config file contents as hash ref where data structure is:
Please note that array ref is used to store XML text elements and scalar for attributes.
( 'key1' => {'comment' => "#some comment\n#more comments\n",
'value' => 'Value1',
'order' => '1',
},
'key2' => {'comment' => "#some comment\n#more comments\n",
'value' => 'Value2',
'order' => '2'
},
'XMLRootKey' => {'comment' => "#some comment\n#more comments\n",
'order' => '3',
'value' => {
'xmlAttribute1' => 'attribute_value',
'subXmlKey1' => ['sub_xml_value1'],
'subXmlKey2' => ['sub_xml_value2'],
'subXmlKey3'=> ['sub_xml_value3'],
}
}
)
The normalized ( flat hash with only key=value pairs ) view of the config could be obtained by getNormalizedData() call.
All tree- like options will be flatted as key1_subkey1_subsubkey1. So the structure above will be converted into:
('key1' => 'Value1',
'key2' => 'Value2',
'XMLRootKey_xmlAttribute1' => 'attribute_value',
'XMLRootKey_subXmlKey1' => 'sub_xml_value1' ,
'XMLRootKey_subXmlKey2' => 'sub_xml_value2',
'XMLRootKey_subXmlKey3'=> 'sub_xml_value3' , )
the case of the key will be preserved.
=head1 SYNOPSIS
Provides a convenient way for loading config values from a given file and
returns it as a hash structure, allows interpolation for the simple perl scalars C<( $xxxx ${xxx} )>
Also, it can run interactive session with user, use predefined prompts, use validation patterns
and store back into the file, preserving the order of original comments.
Motivation behind this module was inspired by L<Config::General> module which was missing required
functionality (preservation of the comments order and positioining, prompts and validation for
command line based UI ). Basically, this is I<Yet-Another-Config-Module> with list of features found to be useful.
use Config::Interactive;
# define prompts for the keys
my %CONF_prompts = ('username' => 'your favorite username ',
'password'=> 'most secure password ever ',
);
my %validkeys = ('username' => ' your favorite username ',
'password'=> ' most secure password ever ',
);
lib/Config/Interactive.pm view on Meta::CPAN
}
else {
print OUTF $comment . $key . $self->{delimiter} . "$value\n";
carp( $comment . $key . $self->{delimiter} . $value )
if $self->{debug};
}
}
close OUTF;
}
=head2 parse()
Parse config file, return hash ref ( optional)
Accepts filename as argument
Possible ways to call B<parse()>:
$config_hashref = $conf->parse("my.conf"); # parse my.conf file, if -file was defined at the object creation time, then this will overwrite -file option
$config_hashref = $conf->parse();
This method returns a a hash ref.
=cut
sub parse {
my ( $self, $filen ) = @_;
my $file_to_open = ( defined $filen && -e $filen ) ? $filen : $self->{file};
open INF, "<$file_to_open"
or croak(" Failed to open config file: $file_to_open");
print("File $file_to_open opened for parsing ") if $self->{debug};
my %config = ();
my $comment = undef;
my $order = 1;
my $xml_start = undef;
my $xml_config = undef;
my $pattern = '^([\w\.\-]+)\s*\\' . $self->{delimiter} . '\s*(.+)';
# parsing every line from the config file, removing extra spaces
while (<INF>) {
chomp;
s/^\s+?//;
if (m/^\#/xsm) {
$comment .= "$_\n";
}
else {
s/\s+$//g;
# if not inside of XML and if this is start of XML
if ( !$xml_start && m/^\<\s*([\w\-]+)\b?[^\>]*\>/xsm ) {
$xml_start = $1;
$xml_config .= $_;
}
# elsif inside of XML
elsif ($xml_start) {
if (m/^\<\/\s*($xml_start)\s*\>/xsm) {
$xml_config .= $_;
my $xml_cf = XMLin( $xml_config, KeyAttr => {}, ForceArray => 1 );
$config{$xml_start}{value} = $self->_parseXML($xml_cf);
carp " Parsed XML fragment: " . Dumper $config{$xml_start}{value} if $self->{debug};
if ($comment) {
$config{$xml_start}{comment} = $comment;
$comment = '';
}
$config{$xml_start}{order} = $order++;
$xml_start = undef;
}
else {
$xml_config .= $_;
}
}
# elsif outside of XML, key=value
elsif (m/$pattern/o) {
my $key = $1;
my $value = $2;
$config{$key}{value} = $self->_processKey( $key, $value );
$config{$key}{order} = $order++;
if ($comment) {
$config{$key}{comment} = $comment;
$comment = '';
}
}
else {
print(" ... Just a pattern:$pattern a string: $_")
if $self->{debug};
}
}
}
close INF;
print(" interpolating...\n") if $self->{debug};
# interpolate all values
$self->{data} = $self->_interpolate( \%config );
print( " Config data: \n" . Dumper $self->{data} ) if $self->{debug};
return $self->{data};
}
#
# interpolate all values, in case of XML fragments the name of the interpolated variable
# MUST be set by key=value definition and not by the element from other XML block
#
#
sub _interpolate {
my ( $self, $config, $scalars, $xml_root ) = @_;
my @keys = $xml_root ? keys %{ $config->{value} } : keys %{$config};
# interpolate all values
foreach my $key (@keys) {
### go for recursion in case of XML fragment
if ( !$xml_root ) {
$self->_interpolate( $config->{$key}, $config, $key )
if ref( $config->{$key}{value} ) eq 'HASH';
### interpolate if its simple key=value definition
my @sub_keys =
$config->{$key}{value} =~ /[^\\]?\$\{?([a-zA-Z]+(?:\w+)?)\}?/xsmg;
foreach my $sub_key (@sub_keys) {
print(
" CHECK " . $config->{$key}{value} . " -> $sub_key \n" )
if $self->{debug};
if ( $sub_key && $config->{"$sub_key"} ) {
my $subst = $config->{"$sub_key"}{value};
$config->{$key}{pre} =
$config->{$key}{pre}
? $config->{$key}{pre}
: $config->{$key}{value};
$config->{$key}{value} =~ s/\$\{?$sub_key\}?/$subst/xsmg;
carp( " interpolated "
. $config->{$key}{value}
. " -> $sub_key -> $subst \n" )
if $self->{debug};
}
}
}
else {
## XML keys located under the value key and its single size array in case of element and just scalar for attr
my $xml_value =
ref( $config->{value}{$key} ) eq 'ARRAY'
? $config->{value}{$key}->[0]
: $config->{value}{$key};
my @sub_keys =
$xml_value =~ /[^\\]?\$\{?([a-zA-Z]+(?:\w+)?)\}?/xsmg;
foreach my $sub_key (@sub_keys) {
print( " CHECK " . $xml_value . " -> $sub_key \n" )
if $self->{debug};
if ( $sub_key && $scalars->{"$sub_key"} ) {
my $subst = $scalars->{"$sub_key"}{value};
if ( ref( $config->{value}{$key} ) eq 'ARRAY' ) {
$config->{pre}{$key}->[0] =
$config->{pre}{$key}->[0]
? $config->{pre}{$key}->[0]
: $config->{value}{$key}->[0];
$config->{value}{$key}->[0] =~
s/\$\{?$sub_key\}?/$subst/xsmg;
}
else {
$config->{pre}{$key} =
$config->{pre}{$key}
? $config->{pre}{$key}
: $config->{value}{$key};
$config->{value}{$key} =~
s/\$\{?$sub_key\}?/$subst/xsmg;
}
carp( " interpolated "
. $xml_value
. " -> $sub_key -> $subst \n" )
if $self->{debug};
( run in 0.817 second using v1.01-cache-2.11-cpan-364913b4093 )