Activator
view release on metacpan or search on metacpan
lib/Activator/Registry.pm view on Meta::CPAN
package Activator::Registry;
use YAML::Syck;
use base 'Class::StrongSingleton';
use Activator::Log qw( :levels );
use Data::Dumper;
use Hash::Merge;
use Activator::Exception;
use Exception::Class::TryCatch;
=head1 NAME
Activator::Registry - provide a registry based on YAML file(s)
=head1 SYNOPSIS
use Activator::Registry;
#### register $value to $key in realm $realm
Activator::Registry->register( $key, $value, $realm );
#### register $value to $key in default realm
Activator::Registry->register( $key, $value );
#### get value for $key from $realm
Activator::Registry->get( $key, $realm );
#### get value for $key from default realm
Activator::Registry->get( $key );
#### get a deep value for $key from default realm
#### this form throws exception for invalid keys
$key = 'top->deep->deeper';
try eval {
Activator::Registry->get( $key );
}
#### register YAML file into realm
Activator::Registry->register_file( $file, $realm );
#### register hash into realm
Activator::Registry->register_hash( $mode, $hashref, $realm );
#### use ${} syntax in your registry for variables
Activator::Registry->replace_in_realm( 'default', $replacements_hashref );
=head1 DESCRIPTION
This module provides global access to a registry of key-value pairs.
It is implemented as a singleton, so you can use this Object Oriented
or staticly with arrow notation. It supports getting and setting of
deeply nested objects. Setting can be done via YAML configuration
files.
=head1 CONFIGURATION FILES
Configuration files are YAML files.
=head2 Registry Within Another Configuration File
You can have a registry be a stand alone file, or live within a
configuration file used for other purposes. If you wish your registry
to be only a subset of a larger YAML file, put the desired hierarchy
in a top level key C<Activator::Registry>. If that key exists, only
that part of the YAML file will be registered.
=head2 Default Configuration File
Often, your project will have a central configuration file that you
always want to use. In these cases set the environment variable
C<ACT_REG_YAML_FILE>. All calls to L</new()>, L</load()> and
L</reload()> will register this file first, then any files passed as
arguments to those subroutines.
If you are utilizing this module from apache, this directive must be
in your httpd configuration:
SetEnv ACT_REG_YAML_FILE '/path/to/config.yml'
If you are using this module from a script, you need to ensure that
the environment is properly set. This my require that you utilize a
BEGIN block BEFORE the C<use> statement of any module that C<use>s
C<Activator::Registry> itself:
BEGIN{
$ENV{ACT_REG_YAML_FILE} ||= '/path/to/reg.yml'
}
Otherwise, you will get weirdness when all of your expected registry
keys are undef...
=head1 METHODS
lib/Activator/Registry.pm view on Meta::CPAN
sub _deep_register {
my ($self, $keys, $value, $setref) = @_;
my $curkey = shift @$keys;
if ( @$keys == 0 ) {
$setref->{ $curkey } = $value;
}
else {
$self->_deep_register( $keys, $value, $setref->{ $curkey });
}
}
=head2 register_file( $file, $realm)
Register the contents of the C<'Activator::Registry':> heirarchy from
within a YAML file, then merge it into the existing registry for the
default realm, or optionally C<$realm>.
=cut
sub register_file {
my ( $pkg, $file, $realm ) = @_;
my $self = $pkg->new();
$realm ||= $reg->{DEFAULT_REALM};
my $config = YAML::Syck::LoadFile( $file );
# In pre 1.0 versions of this module, it was a top level key of
# 'Activator::Registry' was required to allow registries to live
# within other yml files. In common usage, this is not the normal
# case. Here we support both.
if ( $config->{'Activator::Registry'} ) {
$self->register_hash( 'left', $config->{'Activator::Registry'}, $realm );
}
else {
$self->register_hash( 'left', $config, $realm );
}
}
=head2 register_hash( $mode, $right, $realm)
Set registry keys in C<$realm> from C<$right> hash using C<$mode>,
which can either be C<left> or C<right>. C<left> will only set keys
that do not exist, and C<right> will set or override all C<$right>
values into C<$realm>'s registry.
=cut
sub register_hash {
my ( $pkg, $mode, $right, $realm ) = @_;
if ( $mode eq 'left' ) {
Hash::Merge::set_behavior( 'LEFT_PRECEDENT' );
}
elsif ( $mode eq 'right' ) {
Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' );
}
else {
# TODO: consider using custom precedence
#Hash::Merge::specify_behavior( $pkg->{SAFE_LEFT_PRECEDENCE} );
Activator::Exception::Registry->throw( 'mode', 'invalid' );
}
my $reg = $pkg->new();
$realm ||= $reg->{DEFAULT_REALM};
if ( !exists( $reg->{REGISTRY}->{ $realm } ) ) {
$reg->{REGISTRY}->{ $realm } = {};
}
my $merged = {};
try eval {
$merged = Hash::Merge::merge( $reg->{REGISTRY}->{ $realm }, $right );
};
# catch
if ( catch my $e ) {
Activator::Exception::Registry->throw( 'merge', 'failure', $e );
}
elsif( keys %$merged ) {
$reg->{REGISTRY}->{ $realm } = $merged;
}
}
=head2 get( $key, $realm )
Get the value for C<$key> within C<$realm>. If C<$realm> not defined
returns the value from the default realm. C<$key> can refer to a
deeply nested element. Returns undef if the key does not exist, or you
try to seek into an array. Some examples:
With a YAML config that produces:
deep_list:
level_1:
- level_2_a
- level_2_b
key: value
You will get this behavior:
Activator::Registry->get( 'key' ); # returns 'value'
Activator::Registry->get( 'deep_list' ); # returns hashref
Activator::Registry->get( 'deep_lost' ); # returns undef
Activator::Registry->get( 'deep_list->level_1' ); # returns arrayref
Activator::Registry->get( 'deep_list->level_1->level_2_a' ); # returns undef
Activator::Registry->get( 'deep_list->level_one' ); # returns undef
=cut
sub get {
my ($pkg, $key, $realm) = @_;
my $self = $pkg->new();
$realm ||= $self->{DEFAULT_REALM};
my @keys = split( '->', $key );
if ( @keys > 1 ) {
my $retval;
try eval {
$retval = $self->_deep_get( \@keys, $realm, $self->{REGISTRY}->{ $realm } );
};
if ( catch my $e ) {
return;
}
return $retval;
}
return $self->{REGISTRY}->{ $realm }->{ $key };
}
sub _deep_get {
my ($pkg, $keys, $realm, $reg_ref) = @_;
my $key = shift @$keys;
if ( @$keys == 0 ) {
if ( exists( $reg_ref->{ $key } ) ) {
return $reg_ref->{ $key };
}
else {
Activator::Exception::Registry->throw( 'key', 'invalid', $key );
}
}
if ( exists( $reg_ref->{ $key } ) ) {
return $pkg->_deep_get( $keys, $realm, $reg_ref->{ $key } );
}
else {
Activator::Exception::Registry->throw( 'key', 'invalid', $key );
}
}
=head2 get_realm( $realm )
Return a reference to hashref for an entire C<$realm>.
=cut
sub get_realm {
my ($pkg, $realm) = @_;
my $self = $pkg->new();
$realm ||= $self->{DEFAULT_REALM};
return $self->{REGISTRY}->{ $realm };
}
=head2 set_default_realm( $realm )
Use C<$realm> instead of 'default' for default realm calls.
=cut
sub set_default_realm {
my ($pkg, $realm) = @_;
my $self = $pkg->new();
$self->{DEFAULT_REALM} = $realm;
}
=head2 replace_in_realm( $realm, $replacements )
Replace variables matching C<${}> notation with the values in
C<$replacements>. C<$realm> must be specified. Use C<'default'> for
the default realm. Keys that refer to other keys in the realm are
processed AFTER the passed in C<$replacements> are processed.
=cut
sub replace_in_realm {
my ($pkg, $realm, $replacements) = @_;
my $self = $pkg->new();
my $reg = $self->get_realm( $realm );
if ( !keys %$reg ) {
Activator::Exception::Registry->throw( 'realm', 'invalid', $realm );
}
TRACE("replacing (realm '$realm') ". Dumper($reg) . "\n ---- with ----\n". Dumper($replacements));
$self->replace_in_hashref( $reg, $replacements );
$self->replace_in_hashref( $reg, $reg );
TRACE("Done replacing. End result: ". Dumper($reg));
}
=head2 replace_in_hashref( $hashref, $replacements )
Replace withing the values of C<$hashref> keys, variables matching
C<${}> notation with the values in C<$replacements>.
=cut
sub replace_in_hashref {
my ( $pkg, $hashref, $replacements ) = @_;
foreach my $key ( keys %$hashref ) {
# if key is a hash, recurse
if ( UNIVERSAL::isa( $hashref->{ $key }, 'HASH')) {
$pkg->replace_in_hashref( $hashref->{ $key }, $replacements );
}
# if key is an array, do replacements for each item
elsif ( UNIVERSAL::isa( $hashref->{ $key }, 'ARRAY')) {
for( my $i = 0; $i < @{ $hashref->{ $key } }; $i++ ) {
@{ $hashref->{ $key }}[ $i ] =
$pkg->do_replacements( @{ $hashref->{ $key }}[ $i ],
$replacements,
0 );
}
}
# if key is a string just do the replacment for the string
else {
$hashref->{ $key } =
$pkg->do_replacements( $hashref->{ $key },
$replacements,
0 );
}
}
}
=head2 do_replacements ( $string, $replacements )
Helper subroutine to allow recursive replacements of C<${}> notation
with values in C<$replacements>. Returns the new value.
=cut
sub do_replacements {
my ( $pkg, $string, $replacements, $depth ) = @_;
my ( $replacement_str, $num_replaced ) = $pkg->get_replaced_string( $string, $replacements );
if ( $num_replaced > 0 && $replacement_str =~ /\$\{[^\}]+\}/ ) {
$replacement_str = $pkg->do_replacements( $replacement_str, $replacements, $depth+1 );
}
( run in 0.564 second using v1.01-cache-2.11-cpan-39bf76dae61 )