=head1 NAME

Templer::Plugin::Factory - A simple plugin class

=cut

=head1 DESCRIPTION

This class implements a singleton within which plugin classes may
be registered and retrieved.

The plugins used by C<templer> are of two forms:

=over 8

=item formatters

These plugins operate upon the text contained in L<Templer::Site::Page> objects
and transform the input into HTML.

=item variable expanders

These plugins, also operating on L<Templer::Site::Page> objects, are allowed
the opportunity to modify, update, replace, or delete the various per-page
variables.

=back

Plugins of each type register themselves by calling the appropriate methods
in this class.

=cut

=head1 LICENSE

This module is free software; you can redistribute it and/or modify it
under the terms of either:

a) the GNU General Public License as published by the Free Software
Foundation; either version 2, or (at your option) any later version,
or

b) the Perl "Artistic License".

=cut

=head1 AUTHOR

Steve Kemp <steve@steve.org.uk>

=cut

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2012-2015 Steve Kemp <steve@steve.org.uk>.

This library is free software. You can modify and or distribute it under
the same terms as Perl itself.

=cut

=head1 METHODS

=cut


use strict;
use warnings;


package Templer::Plugin::Factory;


my $singleton;


#
#  Look for plugins beneath the `Templer::Plugin` namespace.
#
use Module::Pluggable
  search_path => ['Templer::Plugin'],
  require     => 1;


#
# Invoking `plugins` ensures that we've actually loaded our plugins
# by the time this module is loaded.
#
my @plugins = plugins();




=head2 new

Constructor.

This class is a singleton, so this method will either construct
an instance of this class or return the global instance.

=cut

sub new
{
    my $class = shift;
    $singleton ||= bless {}, $class;
}



=head2 load_plugins

This method loads "*.pm" from the given directory.

=cut

sub load_plugins
{
    my ( $self, $directory ) = (@_);
    return unless ( -d $directory );

    foreach my $file ( sort( glob( $directory . "/*.pm" ) ) )
    {
        require $file;
    }
}


=head2 init

For each loaded plugin invoke the "init" method, if it exists.

=cut

sub init
{
    my ( $self, $site ) = (@_);

    foreach my $plugin ( @{ $self->{ 'plugins' } } )
    {
        if ( UNIVERSAL::can( $plugin, "init" ) )
        {
            $plugin->init($site);
        }
    }
}


=head2 register_formatter

This method should be called by all formatting plugins to register
themselves.  The two arguments are the name of the input-format,
and the class-name which may be instantiated to process that kind
of input.

L<Templer::Plugin::Textile> and L<Templer::Plugin::Markdown> are
example classes.

=cut

sub register_formatter
{
    my ( $self, $name, $obj ) = (@_);

    die "No name" unless ($name);
    $name = lc($name);
    $self->{ 'formatters' }{ $name } = $obj;
}



=head2 register_filter

This method should be called by all template filter plugins to register
themselves.  The two arguments are the name of the template-filter,
and the class-name which may be instantiated to process that kind
of input.

L<Templer::Plugin::Dollar> and L<Templer::Plugin::Strict> are
example classes.

=cut

sub register_filter
{
    my ( $self, $name, $obj ) = (@_);

    die "No name" unless ($name);
    $name = lc($name);
    $self->{ 'filters' }{ $name } = $obj;
}



=head2 register_plugin

This method should be called by all variable-expanding plugins to register
themselves.  The expected argument is the class-name which may be instantiated
to expand variables.

L<Templer::Plugin::ShellCommand>, L<Templer::Plugin::FileGlob>, and
L<Templer::Plugin::FileContents> are examples of such plugins.

NOTE: The plugin is instantiated immediately, and kept alive for the duration
of a templer-run.

=cut

sub register_plugin
{
    my ( $self, $obj ) = (@_);

    push( @{ $self->{ 'plugins' } }, $obj->new() );
}



=head2 expand_variables

Expand variables via all loaded plugins.

=cut

sub expand_variables
{
    my ( $self, $site, $page, $data ) = (@_);

    my $out;

    foreach my $plugin ( @{ $self->{ 'plugins' } } )
    {
        if ( UNIVERSAL::can( $plugin, "expand_variables" ) )
        {
            my %in = %$data;
            $out = $plugin->expand_variables( $site, $page, \%in );
            $data = \%$out;
        }
    }
    return ($data);
}



=head2 cleanup

For each loaded plugin invoke the "cleanup" method, if it exists.

This can be useful if you wish a plugin to generate a site-map, or similar.

=cut

sub cleanup
{
    my ($self) = (@_);

    foreach my $plugin ( @{ $self->{ 'plugins' } } )
    {
        if ( UNIVERSAL::can( $plugin, "cleanup" ) )
        {
            $plugin->cleanup();
        }
    }
}



=head2 formatter

Return a new instance of the formatter class with the given name.

C<undef> is returned if no such plugin is registered.

=cut

sub formatter
{
    my ( $self, $name ) = (@_);

    die "No name" unless ($name);
    $name = lc($name);

    #
    #  Lookup the formatter by name, if it is found
    # then instantiate the clsee.
    #
    my $obj = $self->{ 'formatters' }{ $name } || undef;
    $obj = $obj->new() if ($obj);

    return ($obj);
}



=head2 formatters

Return the names of each registered formatter-plugin, this is only
used by the test-suite.

=cut

sub formatters
{
    my ($self) = (@_);

    keys( %{ $self->{ 'formatters' } } );
}



=head2 filter

Return a new instance of the filter class with the given name.

C<undef> is returned if no such plugin is registered.

=cut

sub filter
{
    my ( $self, $name ) = (@_);

    die "No name" unless ($name);
    $name = lc($name);

    #
    #  Lookup the filter by name, if it is found
    # then instantiate the class.
    #
    my $obj = $self->{ 'filters' }{ $name } || undef;
    $obj = $obj->new() if ($obj);

    return ($obj);
}



=head2 filters

Return the names of each registered filter-plugin, this is only
used by the test-suite.

=cut

sub filters
{
    my ($self) = (@_);

    keys( %{ $self->{ 'filters' } } );
}



1;