Question

I have a php-ini-file which I need to parse and read with Config::IniFiles. In this ini-file are sections which inherit their settings from other sections. The structure looks like this:

[section1]
...
...
[section2:section1]
...
...
[section3:section2]
...
...

And so on.

Now, if I want to read the value for a key from section3, the object returns undef, since it only know the section section3:section2. As you've maybe already expected, this is not the desired behavior for inheritance. In my case I would like the object to try and read the value first from section3, if it doesn't find it there, try section2 and then section1. Is there a way to achieve this with this module, or do I have to write a wrapper-module and implement this functionality by myself?

Was it helpful?

Solution

I can find no Perl module that implements config inheritance.

But it isn't that complicated to write a simple filter that works out the inheritance. Given a HoH, we can resolve the inheritance like below, if we see each entry in the config as a delta:

use Algorithm::C3;  # must be installed from CPAN

sub resolve_inheritance {
  my $input = shift;
  my %output;

  # hash of arrays where the arrays contain all parents
  my %child_parent_relations =
    map { my ($c, @p) = split /\s*:\s*/; $c => [\@p, $input->{$_}] } keys %$input;
  my $get_parents = sub { @{ $child_parent_relations{shift()}[0] } };
  my $get_data    = sub {    $child_parent_relations{shift()}[1] };

  # prepare stuff for C3 resolution
  my $resolution_cache = {};
  my $resolve = sub {
    my $child = shift;
    Algorithm::C3::merge($child, $get_parents, $resolution_cache);
  };

  # now we go through all childs, and build temporary hashes from the C3 linearization.
  for my $child (keys %child_parent_relations) {
    my @linearization = $resolve->($child);
    my %temp;
    for my $delta_name (reverse @linearization) {
      my $delta = $get_data->($delta_name);
      @temp{keys %$delta} = values %$delta;
    }
    # save the data in the output:
    $output{$child} = \%temp;
  }

  return \%output;
}

Test:

my $input = {
  's1'       => { A => 1 },
  's2:s1'    => { A => 2, B => 2 },
  's3:s1'    => { B => 3, C => 3 },
  's4:s3:s2' => { }
};
my $expected = {
  's1' => { A => 1 },
  's2' => { A => 2, B => 2 },
  's3' => { A => 1, B => 3, C => 3 },
  's4' => { A => 2, B => 3, C => 3 },
};
use Test::More tests => 1;
is_deeply resolve_inheritance($input), $expected, 'C3 resolution';

As you can see, this gives the :-operator right associativity (combines right-to-left).

If you want depth-first resolution, i.e.:

my $expected = {
  's1' => { A => 1 },
  's2' => { A => 2, B => 2 },
  's3' => { A => 1, B => 3, C => 3 },
  's4' => { A => 1, B => 3, C => 3 },  # s3 completely overwrites s2
};

then you get a different result. This can be done by first inheriting each parent, and then combining only the immediate parents, but not the whole hierarchy. In cases of single inheritance, the result of depth-first linearization and C3 resolution is equivalent.

To support depth-first resolution, we swap out the $resolve function in above code, and change it to

my $resolve; $resolve = sub {
  my $child = shift;
  return $child, map { $resolve->($_) } $get_parents->($child);
};

This is the minimal change, but of course this could be made more efficient by keeping only the leftmost occurrence of each parent:

use List::MoreUtils 'uniq';

my $resolve; $resolve = sub {
  my $child = shift;
  return uniq $child, map { $resolve->($_) } $get_parents->($child);
};

If the data structure used for input could remember the order of sections, then this depth-first solution would get even easier. However, hashes are unordered.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top