view release on metacpan or search on metacpan
bin/activator.pl view on Meta::CPAN
See L<Activator::Tutorial> for a description of how to configure an Activator project.
=cut
# $config, $args, $project, $action and the current apache pid are globally interesting
my ( $config, $args, $project, $action, $httpd_pid );
try eval {
# Act::Config requires that project be set via an option or be the
# last arg, hence the flag after undef below
$config = Activator::Config->get_config( \@ARGV, undef, 1 );
};
if ( catch my $e ) {
die( "Error while processing command line options: $e" );
}
my $log_level = $config->{log_level} || 'WARN';
if ( $config->{v} || $config->{verbose} ) {
Activator::Log->level( 'INFO' );
}
lib/Activator/Config.pm view on Meta::CPAN
There are times where a script takes the project name as a required
bareword argument. For these cases, require that project be the last
argument, and pass a flag to L</get_config()>.
That is, when your script is called like this:
myscript.pl --options <project>
get the config like this:
Activator::Config->get_config( \@ARGV, undef, 1 );
The second argument to L</get_config()> is the realm, so you pass
C<undef> (unless you know the realm you are looking for) to allow the
command line options and environment variables to take affect.
=head1 ENVIRONMENT VARIABLES
Environment variables can be used to act as a default to command line
options, and/or override any top level configuration file key which is
a scalar. The expected format is C<ACT_CONFIG_[key]>. Note that YAML is
case sensitive, so the environment variables must match. Be especially
wary of command shell senstive characters in your YAML keys (like
C<:~E<gt>E<lt>|>).
lib/Activator/Config.pm view on Meta::CPAN
=head1 METHODS
=cut
sub new {
my ( $pkg ) = @_;
my $self = bless( {
REGISTRY => Activator::Registry->new(),
ARGV_EXTRA => {},
ARGV => undef,
BAREWORDS => undef,
}, $pkg);
$self->_init_StrongSingleton();
return $self;
}
=head2 get_config()
Process command line arguments, environment variables and
configuration files then return a hashref representing the merged
configuration. Recognized configuration items are removed from C<@ARGV>.
Usage:
Activator::Config->get_config( \@ARGV, $realm, $project_is_arg );
C<$realm> is optional (default is 'default'). If undefined, it will be
determined from a command line option or environment variable.
C<$project_is_arg> is optional. Use any true value for this argument
if your script requries the project name as the last bareword
argument.
Examples:
#
# get options for default realm
lib/Activator/Config.pm view on Meta::CPAN
#
# get options for 'some' realm, ignoring --realm and ACT_CONFIG_realm
#
my $config = Activator::Config->get_config( \@ARGV, 'some' );
#
# don't ignore --realm and ACT_CONFIG_realm, use $barewords[-1] (the
# last bareword argument) as the project
#
Activator::Config->get_config( \@ARGV, undef, 1 );
See L</get_args()> for a description of the way command line arguments
are processed.
If called repeatedly, this sub does NOT reprocess C<\@ARGV>. This
allows you to make multiple calls to get a reference to the config for
multiple realms if desired.
=cut
lib/Activator/Config.pm view on Meta::CPAN
# get_args sets $self->{ARGV}
$self->get_args( $argv );
DEBUG( Data::Dumper->Dump( [ $self->{ARGV} ], [ qw/ ARGV / ] ) );
DEBUG( Data::Dumper->Dump( [ $self->{BAREWORDS} ], [ qw /BAREWORDS/ ] ) );
# make sure we can use ENV vars
my $skip_env = $ENV{ACT_CONFIG_skip_env};
$realm ||=
$self->{ARGV}->{realm} ||
( $skip_env ? undef : $ENV{ACT_CONFIG_realm} ) ||
'default';
if ( ref( $realm ) ) {
Activator::Exception::Config->throw( 'realm_specified_more_than_once', Dumper( $realm ) );
}
if ( $realm ne 'default' ) {
Activator::Registry->set_default_realm( $realm );
}
lib/Activator/Config.pm view on Meta::CPAN
}
if ( defined( $self->{ARGV} ) || defined( $self->{BAREWORDS} ) ) {
DEBUG("skipping ARGV reprocessing");
return ( $self->{ARGV}, $self->{BAREWORDS} );
}
DEBUG("got ARGV: ". join(' ', @$argv_raw ));
# use refs to insure that that $self->{ARGV} and
# $self->{BAREWORDS} are defined, so we don't return undef.
my $argv = {};
my $barewords = [];
my $found_terminator = 0;
foreach my $arg ( @$argv_raw ) {
my ( $key, $value ) = $self->_get_arg( $arg );
if ( $found_terminator || !defined( $key ) ) {
DEBUG("'$arg' is a bareword or after the args terminator '--'");
push @$barewords, $arg;
lib/Activator/Config.pm view on Meta::CPAN
}
# save these so we don't have to do it again
$self->{ARGV} = $argv;
$self->{BAREWORDS} = $barewords;
return ( $argv, $barewords );
}
# Helper to split an arg into key/value. Returns ($key, $value), where
# $value is undef if the argument is flag format (--debug), undef if
# it is a bareword ( foo ) and '--' if it is the arguments terminator
# symbol.
#
sub _get_arg {
my ( $self, $arg ) = @_;
if ( $arg !~ /^-(-)?/ ) {
return;
}
lib/Activator/Config.pm view on Meta::CPAN
}
# Merge config files into this objects Activator::Registry object
sub _process_config_files {
my ( $pkg, $realm, $skip_env, $project_is_arg ) = @_;
my $self = &new( @_ );
# figure out what project we are working on
my $project =
$self->{ARGV}->{project} ||
( $project_is_arg ? $self->{BAREWORDS}->[-1] : undef ) ||
( $skip_env ? undef : $ENV{ACT_CONFIG_project} ) ||
Activator::Exception::Config->throw( 'project', 'missing' );
# process these files:
# $ENV{USER}.yml
# <realm>.yml - realm specific settings and defaults
# <project>.yml - project specific settings and defaults
# org.yml - top level organization settings and defaults
# in one of these paths, if set
# --conf_file= : use $self->{ARGV}->{conf_file} (which could be an arrayref )
# ACT_CONFIG_conf_file= : comma separated list of files
my $conf_path = $self->{ARGV}->{conf_path};
if ( ! $conf_path ) {
$conf_path = ( $skip_env ? undef : $ENV{ACT_CONFIG_conf_path} );
if ( !$conf_path ) {
ERROR( "Neither ACT_CONFIG conf_path env var nor --conf_path set");
Activator::Exception::Config->throw( 'conf_path', 'missing' );
}
else {
INFO( "Using ACT_CONFIG_conf_path env var: $conf_path");
}
}
else {
INFO( "Using conf_path argument: $conf_path");
lib/Activator/DB.pm view on Meta::CPAN
or Activator::Exception::DB->throw( 'connection',
'failure',
"_explode couldn't get connection for alias '$self->{cur_alias}'");
my $attr = $args->{attr} || {};
return ( $self, $bind, $attr );
}
# This can never die, so we jump through hoops to return some valid scalar.
# * replace undef values with NULL, since this is how dbi will do it
# * If $bind is of wrong type, don't do substitutions.
# * shift @vals to handle the case of '?' in the bind values
# * @vals? in the regexp is to handle fewer args on the right than the left
# TODO: support attrs in debug
sub _get_sql {
my ( $pkg, $sql, $bind ) = @_;
$sql ||= '';
$bind ||= [];
if ( ref( $bind ) eq 'ARRAY' ) {
lib/Activator/DB.pm view on Meta::CPAN
=head2 getrow
=head2 getrow_arrayref
=head2 getrow_hashref
Prepare and Execute a SQL statement and get a the result of values
back via DBI::fetchrow_array(), DBI::fetchrow_arrayref(),
DBI::fetchrow_hashref() respectively. NOTE: Unlike DBI, these return
empty array/arrayref/hashref (like DBI::fetchall_arrayref does,
instead of undef) when there are no results.
Usage:
my @row = $db->getrow( $sql, $bind, @args )
my $rowref = $db->getrow_arrayref( $sql, $bind, @args )
my $hashref = $db->getrow_hashref( $sql, $bind, @args )
=head2 getall
=head2 getall_arrayrefs
lib/Activator/Dictionary.pm view on Meta::CPAN
L<http://search.cpan.org/dist/Locale-Maketext/lib/Locale/Maketext/TPJ13.pod>
before making a decision as to which localization method your
application needs.
=head1 CONFIGURATION OVERVIEW
'Activator::Registry': # uses Activator::Registry
'Activator::Dictionary':
default_lang: 'en' # default language for get_dict()*
default_realm: 'my_realm' # default realm for lookup()*
fail_mode: [ die ] # die instead of returning undef
for lookup failures*
dict_files: '<path>' # path to definition files**
dict_tables: [ t1, t2 ] # database definition table(s)**
db_alias: 'db' # Activator::DB alias to use***
* optional
** either dict_files OR dict_tables MUST be defined
*** db_alias required when dict_tables defined
=head1 DICTIONARY FILE CONFIGURATION
lib/Activator/Dictionary.pm view on Meta::CPAN
field you are interested in with dot notation:
$dict->lookup( $key_prefix ); # fails
$dict->lookup( "$key_prefix.$col" ); # succeeds
For this reason, it is required that you not use period in the
C<key_prefix> column.
=head2 Failure Mode
Instead of returning undef for non-existent keys, you can configure
this module to fail via one or more of these methods:
die : throws Activator::Exception::Dictionary('key', 'missing')
key : returns the requested key itself
'' : returns empty string
<lang> : return the value for <lang> in the requested realm
<realm> : return the value for <realm>
Examples:
lib/Activator/Dictionary.pm view on Meta::CPAN
fail_mode: [ realm2, die ]
return value for $key in realm2 if it exists
throw Activator::Exception::Dictionary
fail_mode: [ realm2, realm3 ]
return value for $key in realm2 if it exists
return value for $key in realm3 if it exists
return undef (fallback to default failure mode)
fail_mode: [ '' ]
return empty string
=head1 DISABLING LOAD WARNING
When loading dictionary files, you may sometimes see:
[WARN] Couldn't load dictionary from file for <lang>
lib/Activator/Dictionary.pm view on Meta::CPAN
my $dict = Activator::Dictionary->get_dict( $lang );
$dict->lookup( $key, $realm );
$dict->lookup( $key2, $realm );
Static Usage:
Activator::Dictionary->use_lang( $lang );
Activator::Dictionary->lookup( $key, $realm );
Activator::Dictionary->lookup( $key2, $realm );
Returns the value for C<$key> in C<$realm>. Returns C<undef> when the
key does not exist, but you can configure this module to do something
different (see L<Failure Mode> below). If realm does not exist, throws
C<Activator::Exception::Dictionary> no matter the failure mode.
=cut
sub lookup {
my ($pkg, $key, $realm ) = @_;
my $self = &get_dict( $pkg );
my $lang = $self->{cur_lang};
$realm ||= $self->{config}->{default_realm};
if ( !defined( $key ) ) {
Activator::Exception::Dictionary->throw( 'key', 'undefined');
}
if ( !exists( $self->{ $lang }->{ $realm } ) ) {
Activator::Exception::Dictionary->throw( 'realm', 'undefined', $realm);
}
if ( exists( $self->{ $lang }->{ $realm }->{ $key } ) ) {
my $ret = $self->{ $lang }->{ $realm }->{ $key };
DEBUG( "Found key '$key'. value: $ret");
return $ret;
}
# At this point, there was no value for the given key in the given
# realm. Honor configured failure mode.
DEBUG( "Didn't find key '$key'.");
if ( !exists( $self->{config}->{fail_mode} ) ) {
DEBUG( "No fail_mode defined. Returning undef");
return;
}
if ( !defined( $self->{config}->{fail_mode} ) ) {
DEBUG( "No fail_mode defined. Returning undef");
return;
}
my %tried = ( $lang => 1, $realm => 1 );
my @modes = @{ $self->{config}->{fail_mode} };
DEBUG( "Trying modes: ". Dumper( \@modes ) );
foreach my $mode ( @modes ) {
next if $tried{ $mode };
$tried{ $mode } = 1;
DEBUG( "Trying fail_mode '$mode'");
lib/Activator/Dictionary.pm view on Meta::CPAN
if ( !exists( $self->{ $mode }->{ $realm } ) ) {
next;
}
if ( !exists( $self->{ $mode }->{ $realm }->{ $key } ) ) {
next;
}
DEBUG( "Found entry for lang '$mode'");
return $self->{ $mode }->{ $realm }->{ $key };
}
}
DEBUG( "No valid fail_mode found. Returning undef");
return;
}
=head2 get_dict( $lang )
Returns a reference to the Activator::Dictionary object. Sets all
future lookups to use the $lang passed in. If $lang is not passed in,
uses 'Activator::Dictionary' registry value for 'default_lang'. If
$lang cannot be determined, throws Activator::Exception::Dictionary.
lib/Activator/Dictionary.pm view on Meta::CPAN
# first call
if( !exists $self->{config} ) {
$self->_init_config();
}
# first call for $lang
$lang ||= $self->{cur_lang} || $self->{config}->{default_lang};
if ( !$lang ) {
Activator::Exception::Dictionary->throw( 'lang', 'undefined' );
}
if( !exists $self->{ $lang } ) {
try eval {
$self->_init_lang( $lang );
};
if ( catch my $e ) {
Activator::Exception::Dictionary->throw( 'init_lang', 'failed', $e );
}
lib/Activator/Dictionary.pm view on Meta::CPAN
$self->{config}->{default_realm} = $config->{default_realm} || 'default';
$self->{config}->{default_lang} = $config->{default_lang} || 'en';
$self->{config}->{dict_tables} = $config->{dict_tables};
$self->{config}->{dict_files} = $config->{dict_files};
$self->{config}->{db_alias} = $config->{db_alias};
$self->{config}->{fail_mode} = $config->{fail_mode};
if ( !( defined( $self->{config}->{dict_files} ) ||
defined( $self->{config}->{dict_tables} )
) ) {
Activator::Exception::Dictionary->throw( 'tables_or_files', 'undefined' );
}
if ( defined( $self->{config}->{dict_tables} ) &&
!defined( $self->{config}->{db_alias} ) ) {
Activator::Exception::Dictionary->throw( 'db_alias', 'missing' );
}
}
sub _init_lang {
my ($self, $lang) = @_;
lib/Activator/Options.pm view on Meta::CPAN
Constructor: implements singleton. Not very useful. Use L<get_opts()>.
=cut
sub new {
my ( $pkg ) = @_;
my $self = bless( {
REGISTRY => Activator::Registry->new(),
ARGV_EXTRA => {},
ARGV => undef,
BAREWORDS => undef,
}, $pkg);
$self->_init_StrongSingleton();
return $self;
}
=head2 get_opts()
Usage:
lib/Activator/Options.pm view on Meta::CPAN
# get_args sets $self->{ARGV}
$self->get_args( $argv );
DEBUG( Data::Dumper->Dump( [ $self->{ARGV} ], [ qw/ ARGV / ] ) );
DEBUG( Data::Dumper->Dump( [ $self->{BAREWORDS} ], [ qw /BAREWORDS/ ] ) );
# make sure we can use ENV vars
my $skip_env = $ENV{ACT_OPT_skip_env};
$realm ||=
$self->{ARGV}->{realm} ||
( $skip_env ? undef : $ENV{ACT_OPT_realm} ) ||
'default';
# setup or get the merged YAML configuration settings from files
# into the registry
my $opts = $self->{REGISTRY}->get_realm( $realm );
# first call
if ( !keys %$opts ) {
# define valid opts from config files
try eval {
lib/Activator/Options.pm view on Meta::CPAN
}
if ( defined( $self->{ARGV} ) || defined( $self->{BAREWORDS} ) ) {
DEBUG("skipping ARGV reprocessing");
return ( $self->{ARGV}, $self->{BAREWORDS} );
}
DEBUG("got ARGV: ". join(' ', @$argv_raw ));
# use refs to insure that that $self->{ARGV} and
# $self->{BAREWORDS} are defined, so we don't return undef.
my $argv = {};
my $barewords = [];
my $found_terminator = 0;
foreach my $arg ( @$argv_raw ) {
my ( $key, $value ) = $self->_get_arg( $arg );
if ( $found_terminator || !defined( $key ) ) {
DEBUG("'$arg' is a bareword or after the args terminator '--'");
lib/Activator/Options.pm view on Meta::CPAN
}
# save these so we don't have to do it again
$self->{ARGV} = $argv;
$self->{BAREWORDS} = $barewords;
return ( $argv, $barewords );
}
# Helper to split an arg into key/value. Returns ($key, $value), where
# $value is undef if the argument is flag format (--debug), undef if
# it is a bareword ( foo ) and '--' if it is the arguments terminator
# symbol.
#
sub _get_arg {
my ( $self, $arg ) = @_;
if ( $arg !~ /^-(-)?/ ) {
return;
}
lib/Activator/Pager.pm view on Meta::CPAN
Returns:
$self
Sample:
n == highest possible offset
p == highest possbile page
$self = bless( {
next_offset => 5, -- offset of next page ( 0..n ) or undef if you are
on last page
set_size => 5, -- constructor argument
prev_offset => 0, -- offset of previous page ( 0..n ) or undef if you
are on first page
cur_page => 1, -- the current page number of $offset
last_page => 21, -- the last page page for the total passed in ( 1..p )
last_offset => 100,-- the last possible offset based on number pages ( 0..n )
total => 103, -- constructor argument
next_page => 2, -- the next possible page ( 1..p ) or undef if on
last page ( offset == last_offset )
page_size => 5 -- constructor argument
to => 5, -- the last member number of current page ( 1..n+1 )
from => 1, -- the first member number of current page ( offset+1 )
prev_page => 1, -- the previous page ( 1..p ) or undef if on first
page ( offset == 0 )
offset => 0 -- constructor argument
}, Activator::Pager );
NOTE: we need to document the assuption of offset not being $to
=cut
sub new {
lib/Activator/Pager.pm view on Meta::CPAN
# old and crufty
$self->{last_offset} = int($total/$page_size) * $page_size - ( ($total % $page_size > 0) ? 0 : $page_size ); ;
## cur page offset
$self->{cur_page} = int( $offset / $page_size ) + 1;
## prev
if( $offset - $page_size >= 0 ) {
$self->{prev_offset} = $offset - $page_size;
$self->{prev_page} = ( $self->{cur_page} - 1 <= 0 ) ? undef : $self->{cur_page} - 1;
}
else {
$self->{prev_offset} = undef;
$self->{prev_page} = undef;
}
## next
if( $offset + $page_size < $total ) {
$self->{next_offset} = $offset + $page_size;
$self->{next_page} = int( $self->{next_offset}/$page_size ) + 1;
}
else {
$self->{next_offset} = undef;
$self->{next_page} = undef;
}
return $self;
}
=head2 FUTURE WORK
Implement getter functions if anyone wants it. We just access the vars
directly at this time.
lib/Activator/Registry.pm view on Meta::CPAN
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
=head2 new()
Returns a reference to a registry object. This is a singleton, so
repeated calls always return the same ref. This will load the file
specified by C<$ENV{ACT_REG_YAML_FILE}>, then C<$yaml_file>. If
neither are valid YAML files, you will have an object with an empty
registry. If the registry has already been loaded, DOES NOT RELOAD it.
lib/Activator/Registry.pm view on Meta::CPAN
if ( !$registered_something ) {
my $action = 'load';
if ( keys %{ $self->{REGISTRY_BACKUP} } ) {
$self->{REGISTRY} = $self->{REGISTRY_BACKUP};
$action = 'reload';
}
if ( $ENV{ACT_REG_YAML_FILE} || $yaml_file ) {
my $msg = "Registry $action failed." .
'Neither $ENV{ACT_REG_YAML_FILE} ('. ( $ENV{ACT_REG_YAML_FILE} || 'undef' ) .
') nor $yaml_file ('. ( $yaml_file || 'undef' ) .
') are a valid configuration file';
# TODO: figure out how to solve the cyclic dependancy problem.
# That is, Log depends on this file to find it's config, so
# when calling new, we can't be guranteed that log is loaded.
# We need to figure out if Log is loaded, then we can just
# warn for the outlier case where Log is configured to a bad
# filename.
warn( "[WARN] $msg" );
}
lib/Activator/Registry.pm view on Meta::CPAN
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 );
lib/Catalyst/Plugin/SecureCookies.pm view on Meta::CPAN
my $cipher = &_get_cipher( $c->config->{SecureCookies}->{key} );
## calc a csum for the encrypted block
my $ctx = new Digest::SHA1;
$ctx->add( $encoded );
my $this_csum = substr( &_base64_encode_url( $ctx->digest ), 3, 4 );
## compare it
if( $csum ne $this_csum ) { return undef; }
## ok, the csum is good, decrypt
my $encrypted = &_base64_decode_url( $encoded );
# $encrypted = "RandomIV".$encrypted;
my $dec = $cipher->decrypt( $encrypted );
## get the form
my $form_hashref = &_url_decode_hashref( $dec );
return $form_hashref;
t/Config-05.t view on Meta::CPAN
#Activator::Log->level( 'DEBUG' );
my $config;
@ARGV = ();
my $proj_dir = "$ENV{PWD}/t/data/test_project";
push @ARGV, qq(--conf_path="$proj_dir"), 'test';
lives_ok {
$config = Activator::Config->get_config( \@ARGV, undef, 1 );
} 'lives when project is arg';
t/Dictionary-default.t view on Meta::CPAN
$line = $capture->read;
#ok ( $line =~ /$expected_err2/os, 'got second load error');
ok (defined $line, 'got second expected error');
$val = $dict->lookup('fkey1');
ok( $val eq 'fvalue1', 'can lookup known key' );
lives_ok {
$val = $dict->lookup('fkey2');
} "lookup doesn't die when looking up invalid key";
ok( !defined($val), 'unknown key returns undef by default' );
$val = $dict->lookup('fkey3');
ok( !defined($val), 'leading whitspace commented key returns undef');
$val = $dict->lookup('fkey4');
ok( $val eq 'fvalue4 has many words', 'multi-word values work');
$val = $dict->lookup('fkey5');
ok( $val eq 'fvalue5 has trailing whitespace', 'trailing whitespace stripped');
$val = $dict->lookup('fkey6');
ok( $val eq " fvalue6 is quoted ", 'quoted strings preserve whitespace');
t/Exception.t view on Meta::CPAN
use Exception::Class::TryCatch;
use Data::Dumper;
my $err;
try eval {
Activator::Exception->throw( 'MyObj', 'MyCode' );
};
catch $err;
ok( $err, "Can catch $err");
$err = undef;
try eval {
1;
};
ok( !$err, "Catch nothing when no error thrown");
try eval {
Activator::Exception->throw( 'MyObj', 'MyCode', 'MyExtra' );
};
catch $err;
t/Registry-oo.t view on Meta::CPAN
# deep structs maintained
my $deep = $reg->get( 'deep_hash' );
ok( exists ( $deep->{level_1} ), 'deep key level 1 exists' );
ok( exists ( $deep->{level_1}->{level_2} ), 'deep key level 2 exists' );
ok( exists ( $deep->{level_1}->{level_2}->{level_3} ), 'deep key level 3 exists' );
ok( defined ( $deep->{level_1}->{level_2}->{level_3} ), 'deep key level 3 defined' );
ok( $deep->{level_1}->{level_2}->{level_3} eq 'this is level 3', 'deep value match' );
# key does not exist
my $dne_value = $reg->get('dne_value');
ok( !defined( $dne_value ), 'non-existent key returns undef' );
# deep get
my $deep_key = 'deep_hash->level_1->level_2->level_3';
my $deep_val = $reg->get( $deep_key );
ok( $deep_val && $deep_val eq 'this is level 3', 'deep arrow syntax: value match' );
eval {
$deep_val = $reg->get( "${deep_key}->level_4" );
};
ok( defined $@, 'deep get of non-existent key throws exception' );