Skip to content

Commit

Permalink
Merge branch 'feature/find-plugin'
Browse files Browse the repository at this point in the history
  • Loading branch information
xsawyerx committed Dec 17, 2016
2 parents 253b930 + 0147b23 commit a171120
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 4 deletions.
5 changes: 5 additions & 0 deletions Changes
@@ -1,5 +1,10 @@
{{$NEXT}}

[ ENHANCEMENTS ]
* You can now call '$self->find_plugin(...)' within a plugin
in order to find a plugin, in order to use its DSL in your
custom plugin. (Sawyer X)

[ DOCUMENTATION ]
* GH #1282: Typo in Cookbook. (Kurt Edmiston)
* GH #1214: Update Migration document. (Sawyer X)
Expand Down
8 changes: 8 additions & 0 deletions lib/Dancer2/Core/App.pm
Expand Up @@ -10,6 +10,7 @@ use Safe::Isa;
use Sub::Quote;
use File::Spec;
use Module::Runtime 'use_module';
use List::Util ();

use Plack::App::File;
use Plack::Middleware::FixMissingBodyInRedirect;
Expand Down Expand Up @@ -471,6 +472,13 @@ has destroyed_session => (
clearer => 'clear_destroyed_session',
);

sub find_plugin {
my ( $self, $name ) = @_;
my $plugin = List::Util::first { ref($_) eq $name } @{ $self->plugins };
$plugin or return;
return $plugin;
}

sub destroy_session {
my $self = shift;

Expand Down
32 changes: 28 additions & 4 deletions lib/Dancer2/Plugin.pm
Expand Up @@ -83,6 +83,11 @@ sub execute_plugin_hook {
$self->app->execute_hook( $full_name, @args );
}

sub find_plugin {
my ( $self, $name ) = @_;
return $self->app->find_plugin($name);
}

# both functions are there for D2::Core::Role::Hookable
# back-compatibility. Aren't used
sub supported_hooks { [] }
Expand Down Expand Up @@ -201,8 +206,8 @@ sub _exporter_app {

return unless $app->can('with_plugin');

my $plugin = $app->with_plugin( "+" . $class );
$global->{plugin} = $plugin;
my $plugin = $app->with_plugin( '+' . $class );
$global->{'plugin'} = $plugin;

return unless $class->can('keywords');

Expand Down Expand Up @@ -945,7 +950,6 @@ To use Dancer's DSL in your plugin:
$self->dsl->debug( “Hi! I’m logging from your plugin!” );
See L<Dancer2::Manual/"DSL KEYWORDS"> for a full list of Dancer2 DSL.
=head2 Using the plugin within the app
Expand All @@ -961,7 +965,6 @@ don't want anything imported via empty parentheses when C<use>ing the module:
use Dancer2::Plugin::Polite ();
=head2 Plugins using plugins
It's easy to use plugins from within a plugin:
Expand All @@ -979,6 +982,27 @@ This does not export C<smiley()> into your application - it is only available
from within your plugin. However, from the example above, you can wrap
DSL from other plugins and make it available from your plugin.
=head2 Utilizing other plugins
You can use the C<find_plugin> to locate other plugins loaded by the user,
in order to use them, or their information, directly:
# MyApp.pm
use Dancer2;
use Dancer2::Plugin::Foo;
use Dancer2::Plugin::Bar;
# Dancer2::Plugin::Bar;
...
sub my_keyword {
my $self = shift;
my $foo = $self->find_plugin('Dancer2::Plugin::Foo')
or $self->dsl->send_error('Could not find Foo');
return $foo->foo_keyword(...);
}
=head2 Hooks
New plugin hooks are declared via C<plugin_hooks>.
Expand Down
104 changes: 104 additions & 0 deletions t/plugin2/find_plugin.t
@@ -0,0 +1,104 @@
use strict;
use warnings;
use Test::More 'tests' => 3;
use Plack::Test;
use HTTP::Request::Common;

{
package Dancer2::Plugin::Foo;
use Dancer2::Plugin;

BEGIN {
has 'foo_message' => (
'is' => 'ro',
'default' => sub {'foo'},
);

plugin_keywords('foo_message');
}
}

{
package Dancer2::Plugin::Bar;
use Dancer2::Plugin;

BEGIN {
has 'bar_message' => (
'is' => 'ro',
'lazy' => 1,
'default' => sub {
my $self = shift;
::isa_ok( $self, 'Dancer2::Plugin::Bar' );

my $foo = $self->find_plugin('Dancer2::Plugin::Foo')
or Carp::croak('Cannot find Dancer2::Plugin::Foo');

::isa_ok( $foo, 'Dancer2::Plugin::Foo' );
::can_ok( $foo, 'foo_message' );
return $foo->foo_message . ':bar';
}
);

plugin_keywords('bar_message');
}
}

{
package AppWithFoo;
use Dancer2;
use Dancer2::Plugin::Foo;
get '/' => sub { return foo_message() };
}

{
package AppWithBar;
use Dancer2;
use Dancer2::Plugin::Bar;
set 'logger' => 'Capture';
get '/' => sub { return bar_message() };
}

{
package AppWithFooAndBar;
use Dancer2;
use Dancer2::Plugin::Foo;
use Dancer2::Plugin::Bar;
get '/' => sub { return bar_message() };
}

subtest 'Baseline' => sub {
my $test = Plack::Test->create( AppWithFoo->to_app );
my $res = $test->request( GET '/' );
ok( $res->is_success, 'Successful response' );
is( $res->content, 'foo', 'Foo plugin works correctly' );
};

subtest 'When parent plugin not available' => sub {
my $test = Plack::Test->create( AppWithBar->to_app );
my $res = $test->request( GET '/' );

ok( !$res->is_success, 'Response failed' );

my $trap = AppWithBar::app->config()->{'logger'};
isa_ok( $trap, 'Dancer2::Logger::Capture' );

my $trapper = $trap->trapper;
my $logs = $trapper->read;
isa_ok( $logs, 'ARRAY', 'Found logs' );
is( scalar @{$logs}, 1, 'One log message' );

my $message = $logs->[0];
is( $message->{'level'}, 'error' );
like(
$message->{'message'},
qr{\QRoute exception: Cannot find Dancer2::Plugin::Foo\E},
'Correct error',
);
};

subtest 'When both parent and child plugins available' => sub {
my $test = Plack::Test->create( AppWithFooAndBar->to_app );
my $res = $test->request( GET '/' );
ok( $res->is_success, 'Successful response' );
is( $res->content, 'foo:bar', 'Bar plugin found Foo and worked' );
};

0 comments on commit a171120

Please sign in to comment.