Activator
view release on metacpan or search on metacpan
lib/Activator/Dictionary.pm view on Meta::CPAN
'Activator::Dictionary':
dict_tables: [ table1, table2 ]
db_alias: 'Activator::DB alias to use'
Note that you can use dict_files and dict_tables in any combination.
=head1 RESERVED WORDS FOR REALMS
When naming realms, follow these guidelines:
=over
=item *
Use more than 2 characters, to not confuse realms with languages.
=item *
Do not use the word C<config> for a realm
=back
TODO: enforce this guidance programatically
=head1 LOOKUP FEATURES
=head2 Using a Default Realm and/or Language
In some applications, it is inconvenient to have to pass the realm as
an argument for every lookup call when there is one common realm that
is nearly always used. You can define a default language and/or realm
as such:
'Activator::Registry':
'Activator::Dictionary':
default_lang: 'en' # optional
default_realm: 'my_realm' # optional
Not passing the C<$lang> or C<$realm> arguments will then use the registry
key(s):
my $dict = Activator::Dictionary->get_dict(); # sets lang to en
$dict->lookup( $key ); # returns 'my_realm' value
=head2 Database Dictionary Lookups
When using database dictionary definitions, you must define the target
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:
$db->lookup( $key, $realm1 ); # value does not exist
fail_mode: [ realm2, de, key ]
return value for $key in realm2 if it exists
return value for $key in realm1 in german if it exists
return $key
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>
If you are using files for one language, and the DB for another, this
could get really annoying since you KNOW THIS TO BE TRUE. The
workaround is to set the log level for this message an alternate level
of FATAL, ERROR, WARN, INFO, DEBUG, or TRACE. For example:
$dict->{LOG_LEVEL_FOR_FILE_LOAD} = 'INFO';
=head1 METHODS
=head2 lookup($key, $realm)
OO Usage:
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'");
if ( $mode eq 'die' ) {
DEBUG( "die means throw exception");
Activator::Exception::Dictionary->throw('key', 'missing');
}
if ( $mode eq '' ) {
DEBUG( "returning empty string");
return '';
}
if ( $mode eq 'key' ) {
DEBUG( "returning key '$key'");
return $key;
}
# check realms
if ( grep /^$mode$/, keys( %{ $self->{ $lang } } ) ) {
if ( !exists( $self->{ $lang }->{ $mode }->{ $key } ) ) {
next;
}
DEBUG( "Found entry for realm '$mode'");
return $self->{ $lang }->{ $mode }->{ $key };
}
# check langs
if ( grep /^$mode$/, keys( %$self ) ) {
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.
=cut
sub get_dict {
my ($pkg, $lang ) = @_;
my $self = &new( @_ );
# 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 );
}
}
$self->{cur_lang} = $lang;
return $self;
}
=head2 new( $lang )
Creates a dictionary object. Not very useful, as all it does is create
an uninitialized instance of an Activator::Dictionary object.
=cut
# Contstructor. Implements singleton.
sub new {
my ( $pkg, $lang ) = @_;
my $self = bless( { LOG_LEVEL_FOR_FILE_LOAD => 'WARN' }, $pkg);
$self->_init_StrongSingleton();
return $self;
}
sub _init_config {
my ($self) = @_;
# old config format
my $config = Activator::Registry->get('Activator::Dictionary');
if ( !$config ) {
# new format
$config = Activator::Registry->get('Activator->Dictionary');
}
$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) = @_;
my $processed = 0;
# import all the realms for this language from the db
if ( defined( $self->{config}->{dict_tables} ) ) {
my ( $sql, $rows, $row, $col, $realm, $key );
foreach my $table ( @{ $self->{config}->{dict_tables} } ) {
$sql = "SELECT * FROM $table WHERE lang = ?";
try eval {
$rows = Activator::DB->getall_hashrefs( $sql, [ $lang ], connect => 'def' );
};
if ( catch my $e ) {
Activator::Exception::Dictionary->throw( 'dict_tables',
'misconfigured',
"Activator::Dictionary caught: \n$e" );
}
foreach $row ( @$rows ) {
foreach $col ( keys %$row ) {
if ( $col !~/_id$|realm|lang|key_prefix|last_modified/ ) {
$realm = $row->{realm};
$key = "$row->{key_prefix}.$col";
if ( exists( $self->{ $lang }->{ $realm }->{ $key } ) ) {
local $Log::Log4perl::caller_depth;
$Log::Log4perl::caller_depth += 3;
WARN( "dictionary table $table redefines value for realm '$realm' key_prefix '$row->{key_prefix}' column '$col'");
}
$self->{ $lang }->{ $realm }->{ $key } =
$row->{ $col };
}
}
}
$processed = 1;
}
}
# import all the realms for this lang from files
if ( defined( $self->{config}->{dict_files} ) ) {
my $dir_loc = $self->{config}->{dict_files};
$dir_loc =~ s|/$||;
$dir_loc .= "/$lang";
if (!opendir( DIR, $dir_loc ) ) {
local $Log::Log4perl::caller_depth;
$Log::Log4perl::caller_depth += 3;
# This message could be annoying in some situations, so
# allow changing the log level for just this one.
my $msg = "Couldn't load dictionary from file for $lang from $dir_loc";
my $level = $self->{LOG_LEVEL_FOR_FILE_LOAD};
if ( $level =~ /FATAL|ERROR|WARN|INFO|DEBUG|TRACE/ ) {
no strict 'refs';
&$level( $msg );
}
else {
WARN( $msg );
}
}
else {
my @files = grep { /^[^\.]/ && -f "$dir_loc/$_" } readdir(DIR);
closedir DIR;
my ($file, $realm, $key, $value);
foreach $file ( @files ) {
if ( $file !~ /.dict$/ ) {
WARN("Non-dictionary file '$file' found in lang dir $dir_loc");
next;
}
open DICT, "<$dir_loc/$file" ||
Activator::Exception::Dictionary->throw('dict_file',
'unreadable',
"$dir_loc/$file" );
$file =~ /(.+)\.dict$/;
$realm = $1;
while (<DICT>) {
chomp;
next if /^\s*$/;
next if /^\s*#/;
s/^\s+//;
s/\s+$//;
($key, $value) = split /\s+/, $_, 2;
$value =~ s/("$)//;
if ( $1 ) {
$value =~ s/^"//;
}
$self->{ $lang }->{ $realm }->{ $key } = $value;
}
close DICT;
$processed = 1;
}
}
}
return $processed;
}
=head1 SEE ALSO
L<Activator::Log>, L<Activator::Exception>, L<Activator::DB>,
L<Exception::Class::TryCatch>, L<Class::StrongSingleton>
=head1 AUTHOR
Karim A. Nassar
=head1 COPYRIGHT
Copyright (c) 2007 Karim A. Nassar <karim.nassar@acm.org>
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the Perl README file.
=cut
1;
( run in 0.702 second using v1.01-cache-2.11-cpan-39bf76dae61 )