—#============================================================= -*-Perl-*-
#
# Template::Plugins
#
# DESCRIPTION
# Plugin provider which handles the loading of plugin modules and
# instantiation of plugin objects.
#
# AUTHORS
# Andy Wardley <abw@wardley.org>
#
# COPYRIGHT
# Copyright (C) 1996-2006 Andy Wardley. All Rights Reserved.
# Copyright (C) 1998-2000 Canon Research Centre Europe Ltd.
#
# This module is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# REVISION
# $Id$
#
#============================================================================
package
Template::Plugins;
use
strict;
use
warnings;
use
Template::Constants;
our
$VERSION
=
'3.009'
;
our
$DEBUG
= 0
unless
defined
$DEBUG
;
our
$PLUGIN_BASE
=
'Template::Plugin'
;
our
$STD_PLUGINS
= {
'assert'
=>
'Template::Plugin::Assert'
,
'cgi'
=>
'Template::Plugin::CGI'
,
'datafile'
=>
'Template::Plugin::Datafile'
,
'date'
=>
'Template::Plugin::Date'
,
'debug'
=>
'Template::Plugin::Debug'
,
'directory'
=>
'Template::Plugin::Directory'
,
'dbi'
=>
'Template::Plugin::DBI'
,
'dumper'
=>
'Template::Plugin::Dumper'
,
'file'
=>
'Template::Plugin::File'
,
'format'
=>
'Template::Plugin::Format'
,
'html'
=>
'Template::Plugin::HTML'
,
'image'
=>
'Template::Plugin::Image'
,
'iterator'
=>
'Template::Plugin::Iterator'
,
'latex'
=>
'Template::Plugin::Latex'
,
'pod'
=>
'Template::Plugin::Pod'
,
'scalar'
=>
'Template::Plugin::Scalar'
,
'table'
=>
'Template::Plugin::Table'
,
'url'
=>
'Template::Plugin::URL'
,
'view'
=>
'Template::Plugin::View'
,
'wrap'
=>
'Template::Plugin::Wrap'
,
'xml'
=>
'Template::Plugin::XML'
,
'xmlstyle'
=>
'Template::Plugin::XML::Style'
,
};
#========================================================================
# -- PUBLIC METHODS --
#========================================================================
#------------------------------------------------------------------------
# fetch($name, \@args, $context)
#
# General purpose method for requesting instantiation of a plugin
# object. The name of the plugin is passed as the first parameter.
# The internal FACTORY lookup table is consulted to retrieve the
# appropriate factory object or class name. If undefined, the _load()
# method is called to attempt to load the module and return a factory
# class/object which is then cached for subsequent use. A reference
# to the calling context should be passed as the third parameter.
# This is passed to the _load() class method. The new() method is
# then called against the factory class name or prototype object to
# instantiate a new plugin object, passing any arguments specified by
# list reference as the second parameter. e.g. where $factory is the
# class name 'MyClass', the new() method is called as a class method,
# $factory->new(...), equivalent to MyClass->new(...) . Where
# $factory is a prototype object, the new() method is called as an
# object method, $myobject->new(...). This latter approach allows
# plugins to act as Singletons, cache shared data, etc.
#
# Returns a reference to a plugin, (undef, STATUS_DECLINE) to decline
# the request or ($error, STATUS_ERROR) on error.
#------------------------------------------------------------------------
sub
fetch {
my
(
$self
,
$name
,
$args
,
$context
) =
@_
;
my
(
$factory
,
$plugin
,
$error
);
$self
->debug(
"fetch($name, "
,
defined
$args
? (
'[ '
,
join
(
', '
,
@$args
),
' ]'
) :
'<no args>'
,
', '
,
defined
$context
?
$context
:
'<no context>'
,
')'
)
if
$self
->{ DEBUG };
# NOTE:
# the $context ref gets passed as the first parameter to all regular
# plugins, but not to those loaded via LOAD_PERL; to hack around
# this until we have a better implementation, we pass the $args
# reference to _load() and let it unshift the first args in the
# LOAD_PERL case
$args
||= [ ];
unshift
@$args
,
$context
;
$factory
=
$self
->{ FACTORY }->{
$name
} ||=
do
{
(
$factory
,
$error
) =
$self
->_load(
$name
,
$context
);
return
(
$factory
,
$error
)
if
$error
;
## RETURN
$factory
;
};
# call the new() method on the factory object or class name
eval
{
if
(
ref
$factory
eq
'CODE'
) {
defined
(
$plugin
=
&$factory
(
@$args
) )
||
die
"$name plugin failed\n"
;
}
else
{
defined
(
$plugin
=
$factory
->new(
@$args
) )
||
die
"$name plugin failed: "
,
$factory
->error(),
"\n"
;
}
};
if
(
$error
= $@) {
# chomp $error;
return
$self
->{ TOLERANT }
? (
undef
, Template::Constants::STATUS_DECLINED)
: (
$error
, Template::Constants::STATUS_ERROR);
}
return
$plugin
;
}
#========================================================================
# -- PRIVATE METHODS --
#========================================================================
#------------------------------------------------------------------------
# _init(\%config)
#
# Private initialisation method.
#------------------------------------------------------------------------
sub
_init {
my
(
$self
,
$params
) =
@_
;
my
(
$pbase
,
$plugins
,
$factory
) =
@$params
{
qw( PLUGIN_BASE PLUGINS PLUGIN_FACTORY )
};
$plugins
||= { };
# update PLUGIN_BASE to an array ref if necessary
$pbase
= [ ]
unless
defined
$pbase
;
$pbase
= [
$pbase
]
unless
ref
(
$pbase
) eq
'ARRAY'
;
# add default plugin base (Template::Plugin) if set
push
(
@$pbase
,
$PLUGIN_BASE
)
if
$PLUGIN_BASE
;
$self
->{ PLUGIN_BASE } =
$pbase
;
$self
->{ PLUGINS } = {
%$STD_PLUGINS
,
%$plugins
};
$self
->{ TOLERANT } =
$params
->{ TOLERANT } || 0;
$self
->{ LOAD_PERL } =
$params
->{ LOAD_PERL } || 0;
$self
->{ FACTORY } =
$factory
|| { };
$self
->{ DEBUG } = (
$params
->{ DEBUG } || 0 )
& Template::Constants::DEBUG_PLUGINS;
return
$self
;
}
#------------------------------------------------------------------------
# _load($name, $context)
#
# Private method which attempts to load a plugin module and determine the
# correct factory name or object by calling the load() class method in
# the loaded module.
#------------------------------------------------------------------------
sub
_load {
my
(
$self
,
$name
,
$context
) =
@_
;
my
(
$factory
,
$module
,
$base
,
$pkg
,
$file
,
$ok
,
$error
);
if
(
$module
=
$self
->{ PLUGINS }->{
$name
} ||
$self
->{ PLUGINS }->{
lc
$name
}) {
# plugin module name is explicitly stated in PLUGIN_NAME
$pkg
=
$module
;
(
$file
=
$module
) =~ s|::|/|g;
$file
=~ s|::|/|g;
$self
->debug(
"loading $module.pm (PLUGIN_NAME)"
)
if
$self
->{ DEBUG };
$ok
=
eval
{
require
"$file.pm"
};
$error
= $@;
}
else
{
# try each of the PLUGIN_BASE values to build module name
(
$module
=
$name
) =~ s/\./::/g;
foreach
$base
(@{
$self
->{ PLUGIN_BASE } }) {
$pkg
=
$base
.
'::'
.
$module
;
(
$file
=
$pkg
) =~ s|::|/|g;
$self
->debug(
"loading $file.pm (PLUGIN_BASE)"
)
if
$self
->{ DEBUG };
$ok
=
eval
{
require
"$file.pm"
};
last
unless
$@;
$error
.=
"$@\n"
unless
($@ =~ /^Can\'t locate
$file
\.pm/);
}
}
if
(
$ok
) {
$self
->debug(
"calling $pkg->load()"
)
if
$self
->{ DEBUG };
$factory
=
eval
{
$pkg
->load(
$context
) };
$error
=
''
;
if
($@ || !
$factory
) {
$error
= $@ ||
'load() returned a false value'
;
}
}
elsif
(
$self
->{ LOAD_PERL }) {
# fallback - is it a regular Perl module?
(
$file
=
$module
) =~ s|::|/|g;
eval
{
require
"$file.pm"
};
if
($@) {
$error
= $@;
}
else
{
# this is a regular Perl module so the new() constructor
# isn't expecting a $context reference as the first argument;
# so we construct a closure which removes it before calling
# $module->new(@_);
$factory
=
sub
{
shift
;
$module
->new(
@_
);
};
$error
=
''
;
}
}
if
(
$factory
) {
$self
->debug(
"$name => $factory"
)
if
$self
->{ DEBUG };
return
$factory
;
}
elsif
(
$error
) {
return
$self
->{ TOLERANT }
? (
undef
, Template::Constants::STATUS_DECLINED)
: (
$error
, Template::Constants::STATUS_ERROR);
}
else
{
return
(
undef
, Template::Constants::STATUS_DECLINED);
}
}
#------------------------------------------------------------------------
# _dump()
#
# Debug method which constructs and returns text representing the current
# state of the object.
#------------------------------------------------------------------------
sub
_dump {
my
$self
=
shift
;
my
$output
=
"[Template::Plugins] {\n"
;
my
$format
=
" %-16s => %s\n"
;
my
$key
;
foreach
$key
(
qw( TOLERANT LOAD_PERL )
) {
$output
.=
sprintf
(
$format
,
$key
,
$self
->{
$key
});
}
local
$" =
', '
;
my
$fkeys
=
join
(
", "
,
keys
%{
$self
->{ FACTORY }});
my
$plugins
=
$self
->{ PLUGINS };
$plugins
=
join
(
''
,
map
{
sprintf
(
" $format"
,
$_
,
$plugins
->{
$_
});
}
keys
%$plugins
);
$plugins
=
"{\n$plugins }"
;
$output
.=
sprintf
(
$format
,
'PLUGIN_BASE'
,
"[ @{ $self->{ PLUGIN_BASE } } ]"
);
$output
.=
sprintf
(
$format
,
'PLUGINS'
,
$plugins
);
$output
.=
sprintf
(
$format
,
'FACTORY'
,
$fkeys
);
$output
.=
'}'
;
return
$output
;
}
1;
__END__
=head1 NAME
Template::Plugins - Plugin provider module
=head1 SYNOPSIS
use Template::Plugins;
$plugin_provider = Template::Plugins->new(\%options);
($plugin, $error) = $plugin_provider->fetch($name, @args);
=head1 DESCRIPTION
The C<Template::Plugins> module defines a provider class which can be used
to load and instantiate Template Toolkit plugin modules.
=head1 METHODS
=head2 new(\%params)
Constructor method which instantiates and returns a reference to a
C<Template::Plugins> object. A reference to a hash array of configuration
items may be passed as a parameter. These are described below.
Note that the L<Template> front-end module creates a C<Template::Plugins>
provider, passing all configuration items. Thus, the examples shown
below in the form:
$plugprov = Template::Plugins->new({
PLUGIN_BASE => 'MyTemplate::Plugin',
LOAD_PERL => 1,
...
});
can also be used via the L<Template> module as:
$ttengine = Template->new({
PLUGIN_BASE => 'MyTemplate::Plugin',
LOAD_PERL => 1,
...
});
as well as the more explicit form of:
$plugprov = Template::Plugins->new({
PLUGIN_BASE => 'MyTemplate::Plugin',
LOAD_PERL => 1,
...
});
$ttengine = Template->new({
LOAD_PLUGINS => [ $plugprov ],
});
=head2 fetch($name, @args)
Called to request that a plugin of a given name be provided. The relevant
module is first loaded (if necessary) and the
L<load()|Template::Plugin#load()> class method called to return the factory
class name (usually the same package name) or a factory object (a prototype).
The L<new()|Template::Plugin#new()> method is then called as a class or object
method against the factory, passing all remaining parameters.
Returns a reference to a new plugin object or C<($error, STATUS_ERROR)>
on error. May also return C<(undef, STATUS_DECLINED)> to decline to
serve the request. If C<TOLERANT> is set then all errors will be
returned as declines.
=head1 CONFIGURATION OPTIONS
The following list summarises the configuration options that can be provided
to the C<Template::Plugins> L<new()> constructor. Please consult
L<Template::Manual::Config> for further details and examples of each
configuration option in use.
=head2 PLUGINS
The L<PLUGINS|Template::Manual::Config#PLUGINS> option can be used to provide
a reference to a hash array that maps plugin names to Perl module names.
my $plugins = Template::Plugins->new({
PLUGINS => {
cgi => 'MyOrg::Template::Plugin::CGI',
foo => 'MyOrg::Template::Plugin::Foo',
bar => 'MyOrg::Template::Plugin::Bar',
},
});
=head2 PLUGIN_BASE
If a plugin is not defined in the L<PLUGINS|Template::Manual::Config#PLUGINS>
hash then the L<PLUGIN_BASE|Template::Manual::Config#PLUGIN_BASE> is used to
attempt to construct a correct Perl module name which can be successfully
loaded.
# single value PLUGIN_BASE
my $plugins = Template::Plugins->new({
PLUGIN_BASE => 'MyOrg::Template::Plugin',
});
# multiple value PLUGIN_BASE
my $plugins = Template::Plugins->new({
PLUGIN_BASE => [ 'MyOrg::Template::Plugin',
'YourOrg::Template::Plugin' ],
});
=head2 LOAD_PERL
The L<LOAD_PERL|Template::Manual::Config#LOAD_PERL> option can be set to allow
you to load regular Perl modules (i.e. those that don't reside in the
C<Template::Plugin> or another user-defined namespace) as plugins.
If a plugin cannot be loaded using the
L<PLUGINS|Template::Manual::Config#PLUGINS> or
L<PLUGIN_BASE|Template::Manual::Config#PLUGIN_BASE> approaches then,
if the L<LOAD_PERL|Template::Manual::Config#LOAD_PERL> is set, the
provider will make a final attempt to load the module without prepending any
prefix to the module path.
Unlike regular plugins, modules loaded using L<LOAD_PERL|Template::Manual::Config#LOAD_PERL>
do not receive a L<Template::Context> reference as the first argument to the
C<new()> constructor method.
=head2 TOLERANT
The L<TOLERANT|Template::Manual::Config#TOLERANT> flag can be set to indicate
that the C<Template::Plugins> module should ignore any errors encountered while
loading a plugin and instead return C<STATUS_DECLINED>.
=head2 DEBUG
The L<DEBUG|Template::Manual::Config#DEBUG> option can be used to enable
debugging messages for the C<Template::Plugins> module by setting it to
include the C<DEBUG_PLUGINS> value.
use Template::Constants qw( :debug );
my $template = Template->new({
DEBUG => DEBUG_FILTERS | DEBUG_PLUGINS,
});
=head1 TEMPLATE TOOLKIT PLUGINS
Please see L<Template::Manual::Plugins> For a complete list of all the plugin
modules distributed with the Template Toolkit.
=head1 AUTHOR
Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
=head1 COPYRIGHT
Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=head1 SEE ALSO
L<Template::Manual::Plugins>, L<Template::Plugin>, L<Template::Context>, L<Template>.
=cut
# Local Variables:
# mode: perl
# perl-indent-level: 4
# indent-tabs-mode: nil
# End:
#
# vim: expandtab shiftwidth=4: