- # Go through all new targets and resolve template references.
- foreach (keys %targets) {
- # Start with resolving the base templates
- my @breadcrumbs = ();
- while (my @base_templates =
- $table{$_}->{base_templates} ?
- @{$table{$_}->{base_templates}} : ()) {
- my %new_template_data = ();
- my %new_base_templates = ();
- foreach my $base_template (@base_templates) {
- if (grep { $_ eq $base_template } @breadcrumbs) {
- die "Base template loop! target backtrace:\n "
- ,$base_template,"\n "
- ,join("\n ",
- map { "[ ".join(", ", @{$_})." ]" } @breadcrumbs)
- ,"\n";
+ # Local function to resolve inheritance
+ my $resolve_inheritance;
+ $resolve_inheritance =
+ sub {
+ my $target = shift;
+ my @breadcrumbs = @_;
+
+ if (grep { $_ eq $target } @breadcrumbs) {
+ die "inherit_from loop! target backtrace:\n "
+ ,$target,"\n ",join("\n ", @breadcrumbs),"\n";
+ }
+
+ # Recurse through all inheritances. They will be resolved on
+ # the fly, so when this operation is done, they will all just
+ # be a bunch of attributes with string values.
+ # What we get here, though, are keys with references to lists
+ # of the combined values of them all. We will deal with lists
+ # after this stage is done.
+ my %combined_inheritance = ();
+ if ($table{$target}->{inherit_from}) {
+ foreach (@{$table{$target}->{inherit_from}}) {
+ my %inherited_config =
+ $resolve_inheritance->($_, $target, @breadcrumbs);
+
+ # 'template' is a marker that's considered private to
+ # the config that had it.
+ delete $inherited_config{template};
+
+ map {
+ if (!$combined_inheritance{$_}) {
+ $combined_inheritance{$_} = [];
+ }
+ push @{$combined_inheritance{$_}}, $inherited_config{$_};
+ } keys %inherited_config;
+ }
+ }
+
+ # We won't need inherit_from in this target any more, since
+ # we've resolved all the inheritances that lead to this
+ delete $table{$target}->{inherit_from};
+
+ # Now is the time to deal with those lists. Here's the place
+ # to decide what shall be done with those lists, all based on
+ # the values of the target we're currently dealing with.
+ # - If a value is a coderef, it will be executed with the list
+ # of inherited values as arguments.
+ # - If the corresponding key doesn't have a value at all or is
+ # the emoty string, the inherited value list will be run
+ # through the default combiner (below), and the result
+ # becomes this target's value.
+ # - Otherwise, this target's value is assumed to be a string
+ # that will simply override the inherited list of values.
+ my $default_combiner = sub { join(' ',@_) };
+
+ my %all_keys =
+ map { $_ => 1 } (keys %combined_inheritance,
+ keys %{$table{$target}});
+ foreach (sort keys %all_keys) {
+
+ # Current target doesn't have a value for the current key?
+ # Assign it the default combiner, the rest of this loop
+ # body will handle it just like any other coderef.
+ if (!exists $table{$target}->{$_}) {
+ $table{$target}->{$_} = $default_combiner;