The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Plugins::Factory;

use strict;
use warnings;

our $VERSION = '0.01';

use Module::Load;
require Module::Pluggable;

# save all plugin and package
$Plugins::Factory::job_name = {};

# save all code ref
$Plugins::Factory::ref_code = {};

sub import {
    my ($class,%opts) = @_;
    my ($call_pkg) = caller;
    map {
        my $pl_name = $_;
        my $namespace = $opts{$pl_name}->{'plugin_namespace'} if exists $opts{$pl_name}->{'plugin_namespace'};
        next unless (ref $opts{$pl_name} eq 'HASH' and $namespace);
        my $method = $opts{$pl_name}->{'init_method'};
        
        Module::Pluggable->import(search_path => [$namespace]);
        foreach my $pkg (__PACKAGE__->plugins) {            
            load $pkg;
            (my $short_name = $pkg) =~ s/^${namespace}:://;
            push @{$Plugins::Factory::job_name->{$call_pkg}->{$pl_name}},$pkg;
            $Plugins::Factory::ref_code->{$call_pkg}->{$pl_name}->{$short_name} =
            $Plugins::Factory::ref_code->{$call_pkg}->{$pl_name}->{$pkg} =
            ($method and $pkg->can($method)) ? sub {$pkg->$method(@_)} : sub {$pkg};
        }
        
        no strict 'refs';
        no warnings qw(redefine prototype);
        # add function in traget package
        *{"$call_pkg\::$pl_name"} = sub {
                                             my ($class,$name,@init) = @_;                                            
                                             return $Plugins::Factory::job_name->{$call_pkg}->{$pl_name} unless $name;
                                             return $Plugins::Factory::ref_code->{$call_pkg}->{$pl_name}->{$name}->(@init) if exists $Plugins::Factory::ref_code->{$call_pkg}->{$pl_name}->{$name};
        } if exists $Plugins::Factory::ref_code->{$call_pkg}->{$pl_name};        
        use strict 'refs';
        1;
    } keys %opts;
    
}


1;
__END__

use Plugins::Factory
                    model => {
                        plugin_namespace => 'MyProject::SomePath::MyModel',
                        init_method => 'get_instance'
                    },
                    controller => {
                        plugin_namespace => 'MyProject::SomePath::MyController',
                        init_method => 'process'
                    },
                    view => {
                        plugin_namespace => 'MyProject::SomePath::SomeView',
                        init_method => 'render'
                    };
                    

=encoding utf-8

=head1 NAME

B<Plugins::Factory> - simple plugins factory.

=head1 SYNOPSIS
    
- - - - - - - - - - - - - - - - - - - -
    
    package MyProject::MyModel::News;
    # some model
    
    sub new { bless( ref $_[1] eq 'HASH' ? $_[1] : {}, $[0] ) }
    sub get_news {print $_[0]->{'news'}}
    
    1;
    
    - - - - - - - - - - - - - - - - - - - -
    
    package MyProject::MyModel::Rss;
    # some model
    use LWP;
    
    my $singletone;
    
    sub new { 
    	unless ($singletone) {
    		$singletone = LWP::UserAgent->new();
    	}
    	return $singletone;    	 
    }
    sub get_rss {...}
    
    1;
    
    - - - - - - - - - - - - - - - - - - - -

    package MyProject::MyController::Newspaper;
    # some controller
    
    sub init {__PACKAGE__}
    sub process {
    	my ($class,@param) = @_;
    	...
    }
    
    1;
    
    - - - - - - - - - - - - - - - - - - - -
    
    package MyApplication;
    # you app
    use Plugins::Factory
                    model => {
                        plugin_namespace => 'MyProject::MyModel',
                        init_method => 'new'
                    },
                    controller => {
                        plugin_namespace => 'MyProject::MyController',
                        init_method => 'init'
                    };
    
    MyApplication->model('News',{news=>'some news'})->get_news();
    # equvalent MyProject::MyModel::News->new({news=>'some news'})->get_news()
    
    MyApplication->model('MyProject::MyModel::News',{news=>'some news'})->get_news();
    # alternative call
    
    MyApplication->model('Rss')->get_rss(@param);
    # equvalent MyProject::MyModel::Rss->new()->get_rss(@param) 
    
    MyApplication->controller('Newspaper')->process(@param);
    # equvalent MyProject::MyController::Newspaper->init()->process(@param)
    
    $Plugins::Factory::job_name->model();
    # return arrayref with all full-name plugin on serch path MyProject::MyModel::*
    
=head1 DESCRIPTION

Plugins::Factory find and load your plugin and save link to initial method.
Every time, when you call plugins group alias with short (or full) plugin name, Plugins::Factory execute link to initial method. 
This is good for custom application with MVC, when you need in intuitive interface.

=head2 CALL SYNOPSIS

    use Plugins::Factory
                    name_group => {
                        plugin_namespace => 'namespace::to::group::plugin',
                        init_method => 'name_method_to_call_when_you_call_name_group'
                    },...;

alternative
    
    require Plugins::Factory;
    Plugins::Factory->import(
                    name_group => {
                        plugin_namespace => 'namespace::to::group::plugin',
                        init_method => 'name_method_to_call_when_you_call_name_group'
                    },...
    );

=head1 AUTHOR

Ivan Sivirinov

=cut