Object-Configure
view release on metacpan or search on metacpan
function to match API specification.
- Automatically preserve coderefs and blessed objects passed to configure().
Config::Abstraction treats unknown scalar values as config file paths,
which corrupts coderef and object references. The configure() function
now automatically stashes these values before processing and restores
them afterward, eliminating the need for users to implement manual
stash-delete-restore patterns in their constructors. The logger
parameter continues to receive special handling for wrapping in
Log::Abstraction. Added comprehensive test suite (t/coderef.t) to
verify preservation of coderefs and blessed objects.
- Fixed _deep_merge() to correctly return overlay value when overlay is not a hash.
Previously returned base value instead of overlay, violating the principle
that overlay should always take precedence. Changed line to
'return $overlay unless ref($overlay) eq "HASH"'.
- Fixed logger='NULL' handling to prevent unwanted logger creation.
When logger parameter was set to 'NULL', code would skip wrapping but fall
through to else clause which created a default Log::Abstraction anyway.
Restructured logic to explicitly check for 'NULL' first and preserve it
without creating any logger. Also optimized to avoid dereferencing
$params->{'logger'} twice by using the already-assigned $logger variable.
- Comprehensive POD documentation updates for all public methods.
Added detailed documentation for configure(), instantiate(),
enable_hot_reload(), disable_hot_reload(), reload_config(),
register_object(), restore_signal_handlers(), and get_signal_handler_info().
### USING A CONFIGURATION FILE
To control behavior at runtime, `Object::Configure` supports loading settings from a configuration file via [Config::Abstraction](https://metacpan.org/pod/Config%3A%3AAbstraction).
A minimal example of a config file (`~/.conf/local.conf`) might look like:
[My__Module]
logger.file = /var/log/mymodule.log
The `configure()` function will read this file,
overlay it onto your default parameters,
and initialize the logger accordingly.
If the file is not readable and no config\_dirs are provided,
the module will throw an error.
To be clear, in this case, inheritance is not followed.
This mechanism allows dynamic tuning of logging behavior (or other parameters you expose) without modifying code.
More details to be written.
lib/Object/Configure.pm view on Meta::CPAN
=head3 USING A CONFIGURATION FILE
To control behavior at runtime, C<Object::Configure> supports loading settings from a configuration file via L<Config::Abstraction>.
A minimal example of a config file (C<~/.conf/local.conf>) might look like:
[My__Module]
logger.file = /var/log/mymodule.log
The C<configure()> function will read this file,
overlay it onto your default parameters,
and initialize the logger accordingly.
If the file is not readable and no config_dirs are provided,
the module will throw an error.
To be clear, in this case, inheritance is not followed.
This mechanism allows dynamic tuning of logging behavior (or other parameters you expose) without modifying code.
More details to be written.
lib/Object/Configure.pm view on Meta::CPAN
# Side: May allocate a new Log::Abstraction instance.
sub _reconfigure_logger
{
my ($obj, $key, $logger_config) = @_;
my $carp_on_warn = $obj->{carp_on_warn} || 0;
$obj->{$key} = _build_logger($logger_config, $carp_on_warn);
return;
}
# Purpose: Right-precedence deep merge of two hash references.
# Scalar/arrayref values in $overlay replace those in $base entirely;
# nested hashrefs are merged recursively.
# Entry: Both args should be hashrefs (or undef/non-ref, handled gracefully).
# Exit: Returns a new hashref; neither input is modified.
sub _deep_merge {
my ($base, $overlay) = @_;
return $overlay unless ref($base) eq 'HASH';
return $overlay unless ref($overlay) eq 'HASH';
my $result = { %$base };
foreach my $key (keys %$overlay) {
if(ref($overlay->{$key}) eq 'HASH' && ref($result->{$key}) eq 'HASH') {
$result->{$key} = _deep_merge($result->{$key}, $overlay->{$key});
} else {
$result->{$key} = $overlay->{$key};
}
}
return $result;
}
# Clean up the watcher child and restore signal state on interpreter exit.
END {
disable_hot_reload();
restore_signal_handlers();
t/extended_tests.t view on Meta::CPAN
ok((grep { $_ eq 'Deep::Root3' } @chain), 'root class present in chain');
};
# ---------------------------------------------------------------------------
# SECTION 10 â _deep_merge(): all branch combinations
# ---------------------------------------------------------------------------
# Access the function directly
*_deep_merge = \&Object::Configure::_deep_merge;
subtest '_deep_merge: base not a hashref returns overlay' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge('scalar_base', { a => 1 });
is_deeply($result, { a => 1 }, 'overlay returned when base is scalar');
};
subtest '_deep_merge: overlay not a hashref returns overlay' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge({ a => 1 }, 'scalar_overlay');
is($result, 'scalar_overlay', 'scalar overlay returned directly');
};
subtest '_deep_merge: overlay undef returned as-is' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge({ a => 1 }, undef);
ok(!defined($result), 'undef overlay returned as undef');
};
subtest '_deep_merge: keys only in base are preserved' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge({ a => 1, b => 2 }, { b => 99 });
is($result->{a}, 1, 'base-only key preserved');
};
subtest '_deep_merge: keys only in overlay are added' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge({ a => 1 }, { z => 42 });
is($result->{z}, 42, 'overlay-only key added');
};
subtest '_deep_merge: overlay scalar overwrites base scalar' => sub {
plan tests => 1;
my $result = Object::Configure::_deep_merge({ k => 'old' }, { k => 'new' });
is($result->{k}, 'new', 'overlay scalar wins');
};
subtest '_deep_merge: nested hashes are recursively merged' => sub {
plan tests => 3;
my $base = { top => { a => 1, b => 2 } };
my $overlay = { top => { b => 99, c => 3 } };
my $result = Object::Configure::_deep_merge($base, $overlay);
is($result->{top}{a}, 1, 'base sub-key preserved');
is($result->{top}{b}, 99, 'overlay sub-key wins');
is($result->{top}{c}, 3, 'overlay-only sub-key added');
};
subtest '_deep_merge: overlay hash over base non-hash replaces' => sub {
plan tests => 1;
# base has scalar, overlay has hash â overlay wins (not recursed)
my $result = Object::Configure::_deep_merge(
{ k => 'scalar' },
{ k => { nested => 1 } }
);
is_deeply($result->{k}, { nested => 1 },
'overlay hash replaces base scalar');
};
subtest '_deep_merge: base hash over overlay non-hash, overlay wins' => sub {
plan tests => 1;
# base has hash, overlay has scalar â scalar overlay wins
my $result = Object::Configure::_deep_merge(
{ k => { deep => 1 } },
{ k => 'flat' }
);
is($result->{k}, 'flat', 'scalar overlay replaces base hash');
};
subtest '_deep_merge: does not mutate either input' => sub {
plan tests => 2;
my $base = { a => 1 };
my $overlay = { b => 2 };
my $result = Object::Configure::_deep_merge($base, $overlay);
ok(!exists $base->{b}, 'base not mutated');
ok(!exists $overlay->{a}, 'overlay not mutated');
};
# ---------------------------------------------------------------------------
# SECTION 11 â register_object(): guard and repeat-call branches
# ---------------------------------------------------------------------------
subtest 'register_object: undef class croaks' => sub {
plan tests => 1;
_reset_globals();
dies_ok { Object::Configure::register_object(undef, bless {}, 'Foo') }
t/function.t view on Meta::CPAN
done_testing();
};
subtest '_deep_merge() - merges two hashes' => sub {
my $base = {
foo => 1,
bar => 2,
nested => { a => 1 }
};
my $overlay = {
bar => 3,
baz => 4,
nested => { b => 2 }
};
my $result = Object::Configure::_deep_merge($base, $overlay);
is($result->{foo}, 1, 'Base value preserved');
is($result->{bar}, 3, 'Overlay overrides base');
is($result->{baz}, 4, 'New value from overlay');
is($result->{nested}{a}, 1, 'Nested base value preserved');
is($result->{nested}{b}, 2, 'Nested overlay value added');
done_testing();
};
subtest '_deep_merge() - handles non-hash inputs' => sub {
my $result1 = Object::Configure::_deep_merge('not_hash', { foo => 1 });
is_deeply($result1, { foo => 1 }, 'Returns overlay when base not hash');
my $result2 = Object::Configure::_deep_merge({ foo => 1 }, 'not_hash');
is($result2, 'not_hash', 'Returns overlay when overlay not hash (overlay takes precedence)');
my $result3 = Object::Configure::_deep_merge('not_hash', 'also_not_hash');
is($result3, 'also_not_hash', 'Returns overlay when neither is hash');
done_testing();
};
subtest 'instantiate() - creates object with configuration' => sub {
{
package Test::Instantiable;
sub new {
my ($class, $params) = @_;
return bless $params, $class;
( run in 1.481 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )