The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Data::Phrasebook::Generic;
use strict;
use warnings FATAL => 'all';
use Data::Phrasebook::Loader;
use base qw( Data::Phrasebook::Debug );
use Carp qw( croak );

use vars qw($VERSION);
$VERSION = '0.33';

=head1 NAME

Data::Phrasebook::Generic - Base class for Phrasebook Models

=head1 SYNOPSIS

    use Data::Phrasebook;

    my $q = Data::Phrasebook->new(
        class  => 'Fnerk',
        loader => 'XML',
        file   => 'phrases.xml',
        dict   => 'English',
    );

=head1 DESCRIPTION

This module provides a base class for phrasebook implementations.

=head1 CONSTRUCTOR

=head2 new

C<new> takes an optional hash of arguments. Each value in the hash
is given as an argument to a method of the same name as the key.

This constructor should B<never> need to be called directly as
Phrasebook creation should go through the L<Data::Phrasebook> factory.

Subclasses should provide at least an accessor method to retrieve values for
a named key. Further methods can be overloaded, but must retain a standard
API to the overloaded method.

All, or at least I<most>, phrasebook implementations should inherit from
B<this> class.

=cut

sub new {
    my $class = shift;
    my %hash = @_;
	$hash{loader} ||= 'Text';

    if($class->debug) {
		$class->store(3,"$class->new IN");
		$class->store(4,"$class->new args=[".$class->dumper(\%hash)."]");
	}

	my $self = bless {}, $class;

    # set default delimiters, in case custom delimiters
    # are provided in the hash
    $self->{delimiters} = qr{ :(\w+) }x;

    foreach (keys %hash) {
        $self->$_($hash{$_});
    }

    return $self;
}

=head1 METHODS

=head2 loader

Set, or get, the loader class. Uses a default if none have been
specified. See L<Data::Phrasebook::Loader>.

=head2 unload

Called by the file() and dict() methods when a fresh file or dictionary is
specified, and reloading is required.

=head2 loaded

Accessor to determine whether the current dictionary has been loaded

=head2 file

A description of a file that is passed to the loader. In most cases,
this is a file. A loader that gets its data from a database could
conceivably have this as a hash like thus:

   $q->file( {
       dsn => "dbi:SQLite:dbname=bookdb",
       table => 'phrases',
   } );

That is, which loader you use determines what your C<file> looks like.

The default loader takes just an ordinary filename.

=head2 dict

Accessor to store the dictionary to be used.

=cut

sub loader {
    my $self = shift;
    my $load = @_ ? shift : defined $self->{loader} ? $self->{loader} : 'Text';
	$self->{loader} = $load;
}
sub unload {
    my $self = shift;
    $self->{loaded} = undef;
    $self->{'loaded-data'} = undef;
    return;
}

sub loaded {
    my $self = shift;
    my $load = @_ ? $self->{loaded} = shift : $self->{loaded} ;

	# ensure we know what loader class we are getting
	$self->loader($load->class)  if($load);
	return $load;
}
sub file {
    my $self = shift;
    if(@_) {
        my $file = shift;
        if(!$self->{file} || $file ne $self->{file}) {
            $self->unload();
            $self->{file} = $file;
        }
    }
    return $self->{file};
}

sub dict {
    my $self = shift;

    if(@_) {
        my $list1 = "@_";
        my $list2 = $self->{dict} ? "@{$self->{dict}}" : '';

        if($list1 ne $list2) {
            $self->unload();
            $self->{dict} = (ref $_[0] ? $_[0] : [@_]);
        }
    }

    return($self->{dict} ? @{$self->{dict}}     : ()    )   if(wantarray);
    return($self->{dict} ? $self->{dict}->[0]   : undef );
}

=head2 dicts

Having instantiated the C<Data::Phrasebook> object class, and using the C<file>
attribute as a directory path, the object can return a list of the current
dictionaries available (provided the plugin supports it) as:

  my $pb = Data::Phrasebook->new(
    loader => 'Text',
    file   => '/tmp/phrasebooks',
  );

  my @dicts = $pb->dicts;

or

  my @dicts = $pb->dicts( $path );

=cut

sub dicts {
    my $self = shift;

    my $loader = $self->loaded;
    unless($loader) {
        $self->store(4,"->dicts loader=[".($self->loader)."]")	if($self->debug);
        $loader = Data::Phrasebook::Loader->new(
            'class' => $self->loader,
            'parent' => $self,
        );
        $self->loader($loader->class);  # so we know what we've got
    }

    # just in case it doesn't use D::P::Loader::Base
    croak("dicts() unsupported in plugin")
        unless($loader->can("dicts"));

    return $loader->dicts(@_);
}

=head2 keywords

Having instantiated the C<Data::Phrasebook> object class, using the C<dict>
attribute as required, the object can return a list of the current keywords
available (provided the plugin supports it) as:

  my $pb = Data::Phrasebook->new(
    loader => 'Text',
    file   => '/tmp/phrasebooks',
    dict   => 'TEST',
  );

  my @keywords = $pb->keywords;

or

  my @keywords = $pb->keywords( $dict );

Note the list will be a combination of the default and any named dictionary.
However, not all Loader plugins may support the second usage.

=cut

sub keywords {
    my $self = shift;

    my $loader = $self->loaded;
    if(!defined $loader) {
        $self->store(4,"->keywords loader=[".($self->loader)."]")	if($self->debug);
        $loader = Data::Phrasebook::Loader->new(
            'class' => $self->loader,
            'parent' => $self,
        );
        $self->loader($loader->class);  # so we know what we've got
    }

    # just in case it doesn't use D::P::Loader::Base
    croak("keywords() unsupported in plugin")
        unless($loader->can("keywords"));

    return $loader->keywords(@_);
}

=head2 data

Loads the data source, if not already loaded, and returns the data block
associated with the given key.

    my $data = $self->data($key);

This is typically only used internally by implementations, not the end user.

=cut

sub data {
    my $self = shift;
    my $id = shift;

    if($self->debug) {
		$self->store(3,"->data IN");
		$self->store(4,"->data id=[$id]");
	}

    return  unless($id);

    my $loader = $self->loaded;
    if(!defined $loader) {
        if($self->debug) {
            $self->store(4,"->data loader=[".($self->loader)."]");
            $self->store(4,"->data file=[".($self->file||'undef')."]");
            $self->store(4,"->data dict=[".($self->dict||'undef')."]");
        }
        $loader = Data::Phrasebook::Loader->new(
            'class' => $self->loader,
            'parent' => $self,
        );
        $self->loader($loader->class);  # so we know what we've got
        $loader->load( $self->file, $self->dict );
        $self->loaded($loader);
    }

    return $self->{'loaded-data'}->{$id} ||= do { $loader->get( $id ) };
}

=head2 delimiters

Returns or sets the current delimiters for substitution variables. Must be a
regular expression with at least one capture group.

The example below shows the default ':variable' style regex.

   $q->delimiters( qr{ :(\w+) }x );

The example below shows a Template Toolkit style regex.

   $q->delimiters( qr{ \[% \s* (\w+) \s* %\] }x );

In addition to the delimiter pattern, an optional setting to suppress missing
value errors can be passed after the pattern. If set to a true value, will 
turn any unmatched delimiter patterns to an empty string.

=cut

sub delimiters {
    my $self = shift;
    if(@_) {
        $self->{delimiters} = shift;
        $self->{blank_args} = shift || 0;
    }
        
    return $self->{delimiters};
}

1;

__END__

=head1 SEE ALSO

L<Data::Phrasebook>,
L<Data::Phrasebook::Loader>.

=head1 SUPPORT

Please see the README file.

=head1 AUTHOR

  Original author: Iain Campbell Truskett (16.07.1979 - 29.12.2003)
  Maintainer: Barbie <barbie@cpan.org> since January 2004.
  for Miss Barbell Productions <http://www.missbarbell.co.uk>.

=head1 COPYRIGHT AND LICENSE

  Copyright (C) 2003 Iain Truskett.
  Copyright (C) 2004-2012 Barbie for Miss Barbell Productions.

  This module is free software; you can redistribute it and/or
  modify it under the Artistic License v2.

=cut