David
David

Reputation: 142

Perl - create hash of arrays from conf file with Config::General

I'm using Config::General to read from a conf file that has data in the following format:

<hash>
    <array1>
        val1
        val2
    </array1>
    <array2>
        val1
        val2
    </array2>
</hash>

While it is possible to access the array values as hash keys, it's not a particularly elegant solution.

for my $array (keys $config{'hash'}) {
    for my $value (keys $config{'hash'}{$array}) {
      #do stuff
    }
}

Is there a way this can be changed for the data to be properly accessed as a hash of arrays, rather than resorting to this method that's somewhat of a hack? Here is the resulting structure I'm looking for:

%hash = (
    array1 => [ 'value1', 'value2' ],
    array2 => [ 'value1', 'value2' ]
);

Upvotes: 1

Views: 1844

Answers (3)

Gerd
Gerd

Reputation: 2613

Attention!!! If you set

        my $conf = new Config::General(
            -ConfigFile            => $config_file,
            -MergeDuplicateOptions => 1
    );   

the solution above not work!

Upvotes: 0

Miller
Miller

Reputation: 35208

Config::General is designed to use key value pairs. It does not provide a way to specify a list like you're trying.

One thing you can do though is supply multiple key/value pairs for the same key though, and those will be merged into an array.

use strict;
use warnings;

use Config::General;

my $conf = Config::General->new(
    -String      => do {local $/; <DATA>},
);
my %config = $conf->getall;

use Data::Dump;
dd \%config;

__DATA__
<hash>
    array1 = val1
    array1 = val2
    array2 = val1
    array2 = val2
</hash>

Outputs:

{
  hash => { array1 => ["val1", "val2"], array2 => ["val1", "val2"] },
}

The only issue with this method is that you will have to add logic to determine if a key is an array reference or a scalar before working it. For example, the following is how you could create your own loop to go through this config:

for my $block (keys %config) {
    for my $key (keys %{$config{$block}}) {
        if (ref $config{$block}{$key}) {
            print "$block -> $key -> @{$config{$block}{$key}}\n";
        } else {
            print "$block -> $key -> $config{$block}{$key}\n";
        }
    }
}

Outputs:

hash -> array1 -> val1 val2
hash -> array2 -> val1 val2

Alternative using YAML

If you'd like a human readable config file that supports arrays, I'd recommend looking into YAML.

The following is your config file redone in YAML format, loaded into a hash of arrays, and then outputted using Data::Dump:

use strict;
use warnings;

use YAML;

my $conf = Load(do {local $/; <DATA>});

use Data::Dump;
dd $conf;

__DATA__
hash:
  array1:
    - val1
    - val2
  array2:
    - val1
    - val2
  array3:
    - val1

Outputs:

{
  hash => {
    array1 => ["val1", "val2"],
    array2 => ["val1", "val2"],
    array3 => ["val1"],
  },
}

Upvotes: 3

ThisSuitIsBlackNot
ThisSuitIsBlackNot

Reputation: 24083

Config::General works with key/value pairs. Your config file contains no key/value pairs, so Config::General treats val1 and val2 as keys and assigns them a value of undef. The resulting data structure looks like this:

{ 
  hash => {
    array1 => { val1 => undef, val2 => undef },
    array2 => { val1 => undef, val2 => undef },
  },
}

If your config file format is fixed, you have no choice but to hack the resulting data structure yourself. You are (ab)using Config::General for something it wasn't intended for.

You can create arrays if you change the file format to the following:

foo.cfg

<hash>
    array1 val1
    array1 val2
    array2 val1
    array2 val2
</hash>

code

use strict;
use warnings;

use Config::General;
use Data::Dump;

my $conf = Config::General->new(
    -ConfigFile => 'foo.cfg'
);
my %config = $conf->getall;

dd \%config;

Output

{ 
  hash => { array1 => ["val1", "val2"], array2 => ["val1", "val2"] },
}

Upvotes: 3

Related Questions