Reputation: 25475
I'm using Config::General to load setup data for a script. As part of it, I'd like for the script to do some find and replace on the values in config data. Since the values could be nested multiple levels down, it seems like the best approach would be to use a recursive function to do the substitution. I've come up with the following which seems to work fine:
#!/usr/bin/perl -w
use strict;
use Config::General;
use YAML::XS;
### Load in the config data and move it into the hash.
my $configObj = new Config::General(-ConfigFile => \*DATA);
my %config_hash = $configObj->getall;
### define the year to use as the replacement
my $current_year = (localtime())[5] + 1900;
### Call the function to update the data.
recursive_hash_replacement(\%config_hash, $current_year);
sub recursive_hash_replacement {
### Pull in the hash ref.
my $tmp_hash_ref = shift;
my $replacment_year = shift;
### Loop through all the keys in the hash ref.
for my $tmp_hash_key (keys %{$tmp_hash_ref}) {
### If the value is another hash ref, call the function recursively.
if(ref $tmp_hash_ref->{$tmp_hash_key} eq ref {}) {
recursive_hash_replacement($tmp_hash_ref->{$tmp_hash_key}, $replacment_year);
}
### otherwise, just update the value.
else {
$tmp_hash_ref->{$tmp_hash_key} =~ s{!YEAR!}{$replacment_year}g;
}
}
}
### Show the output with the updated values.
print Dump \%config_hash;
### Define the config data locally for testing.
__DATA__
key1 = /path/with/no/update
key2 = /path/with/new/!YEAR!/update
<level1>
<level2>
key3 = /another/!YEAR!/update
</level2>
</level1>
Is there a better way to do this? And, more importantly, are there any gotchas lurking in this code that are waiting to bite me?
Upvotes: 0
Views: 653
Reputation: 1849
It's worth taking a look at Data::Visitor on CPAN. It's what Config::JFDI uses behind the scenes to do its substitutions.
Upvotes: 0
Reputation: 40152
You can simplify a few things by working on the values of the hash rather than the keys. You also want to test for other types of references, because blindly running a regex on other ref types probably won't be doing what you wanted.
sub recursive_hash_replacement {
### unpack multiple args with list assignment:
my ($hash, $year) = @_;
### Loop through all the values in the hash ref.
for my $value (values %$hash) {
### If the value is another hash ref, call the function recursively.
if (ref $value) {
if (ref $value eq 'HASH') {
recursive_hash_replacement($value, $year);
}
else {
# handle other reftypes, or skip them, or throw an error
die "non hash reference: $value"
}
}
### otherwise, just update the value.
else {
$value =~ s{!YEAR!}{$year}g;
}
}
}
Finally, while running with strict
is good, running with strict
and warnings
is better, and will catch many more potential errors. Finally, you can sometimes run into issues with the indirect object syntax, so use standard method calls for constructors:
Config::General->new(-ConfigFile => \*DATA);
or if you like the indirect object syntax, make it unambiguous by adding ::
to the end of the package name:
new Config::General:: -ConfigFile => \*DATA;
Upvotes: 2