The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Dancer::Plugin;
use strict;
use warnings;
use Carp;

use base 'Exporter';
use Dancer::Config 'setting';
use Dancer::Hook;
use Dancer::Factory::Hook;
use Dancer::Exception qw(:all);

use base 'Exporter';
use vars qw(@EXPORT);

@EXPORT = qw(
  add_hook
  register
  register_plugin
  plugin_setting
  register_hook
  execute_hooks
  execute_hook
  plugin_args
);

sub register($&);

my $_keywords = {};

sub add_hook { Dancer::Hook->new(@_) }

sub plugin_args { (undef, @_) }

sub plugin_setting {
    my $plugin_orig_name = caller();
    (my $plugin_name = $plugin_orig_name) =~ s/Dancer::Plugin:://;

    return setting('plugins')->{$plugin_name} ||= {};
}

sub register_hook {
    Dancer::Factory::Hook->instance->install_hooks(@_);
}

sub execute_hooks {
    Dancer::Deprecation->deprecated(reason => "use 'execute_hook'",
                                    version => '1.3098',
                                    fatal => 0);
    Dancer::Factory::Hook->instance->execute_hooks(@_);
}

sub execute_hook {
    Dancer::Factory::Hook->instance->execute_hooks(@_);
}

sub register($&) {
    my ($keyword, $code) = @_;
    my $plugin_name = caller();

    $keyword =~ /^[a-zA-Z_]+[a-zA-Z0-9_]*$/
      or raise core_plugin => "You can't use '$keyword', it is an invalid name"
        . " (it should match ^[a-zA-Z_]+[a-zA-Z0-9_]*$ )";

    if (
        grep { $_ eq $keyword } 
        map  { s/^(?:\$|%|&|@|\*)//; $_ } 
        (@Dancer::EXPORT, @Dancer::EXPORT_OK)
    ) {
        raise core_plugin => "You can't use '$keyword', this is a reserved keyword";
    }
    while (my ($plugin, $keywords) = each %$_keywords) {
        if (grep { $_->[0] eq $keyword } @$keywords) {
            raise core_plugin => "You can't use $keyword, "
                . "this is a keyword reserved by $plugin";
        }
    }

    $_keywords->{$plugin_name} ||= [];
    push @{$_keywords->{$plugin_name}}, [$keyword => $code];
}

sub register_plugin {
    my ($application) = shift || caller(1);
    my ($plugin) = caller();

    my @symbols = set_plugin_symbols($plugin);
    {
        no strict 'refs';
        # tried to use unshift, but it yields an undef warning on $plugin (perl v5.12.1)
        @{"${plugin}::ISA"} = ('Exporter', 'Dancer::Plugin', @{"${plugin}::ISA"});
        push @{"${plugin}::EXPORT"}, @symbols;
    }
    return 1;
}

sub set_plugin_symbols {
    my ($plugin) = @_;

    for my $keyword (@{$_keywords->{$plugin}}) {
        my ($name, $code) = @$keyword;
        {
            no strict 'refs';
            *{"${plugin}::${name}"} = $code;
        }
    }
    return map { $_->[0] } @{$_keywords->{$plugin}};
}

1;
__END__

=pod

=head1 NAME

Dancer::Plugin - helper for writing Dancer plugins

=head1 DESCRIPTION

Create plugins for Dancer

=head1 SYNOPSIS

  package Dancer::Plugin::LinkBlocker;
  use Dancer ':syntax';
  use Dancer::Plugin;

  register block_links_from => sub {
    my $conf = plugin_setting();
    my $re = join ('|', @{$conf->{hosts}});
    before sub {
        if (request->referer && request->referer =~ /$re/) {
            status 403 || $conf->{http_code};
        }
    };
  };

  register_plugin;
  1;

And in your application:

    package My::Webapp;
    
    use Dancer ':syntax';
    use Dancer::Plugin::LinkBlocker;

    block_links_from; # this is exported by the plugin

=head1 PLUGINS

You can extend Dancer by writing your own Plugin.

A plugin is a module that exports a bunch of symbols to the current namespace
(the caller will see all the symbols defined via C<register>).

Note that you have to C<use> the plugin wherever you want to use its symbols.
For instance, if you have Webapp::App1 and Webapp::App2, both loaded from your
main application, they both need to C<use FooPlugin> if they want to use the
symbols exported by C<FooPlugin>.

=head2 METHODS

=over 4

=item B<register>

Lets you define a keyword that will be exported by the plugin.

    register my_symbol_to_export => sub {
        # ... some code 
    };

=item B<register_plugin>

A Dancer plugin must end with this statement. This lets the plugin register all
the symbols define with C<register> as exported symbols (via the L<Exporter>
module).

A Dancer plugin inherits from Dancer::Plugin and Exporter transparently.

=item B<register_hook>

Allows a plugin to declare a list of supported hooks. Any hook declared like so
can be executed by the plugin with C<execute_hooks>.

    register_hook 'foo'; 
    register_hook 'foo', 'bar', 'baz'; 

=item B<execute_hooks>

Allows a plugin to execute the hooks attached at the given position

    execute_hooks 'some_hook';

The hook must have been registered by the plugin first, with C<register_hook>.

=item B<plugin_setting>

Configuration for plugin should be structured like this in the config.yml of
the application:

  plugins:
    plugin_name:
      key: value

If C<plugin_setting> is called inside a plugin, the appropriate configuration 
will be returned. The C<plugin_name> should be the name of the package, or, 
if the plugin name is under the B<Dancer::Plugin::> namespace (which is
recommended), the remaining part of the plugin name. 

Enclose the remaining part in quotes if it contains ::, e.g.
for B<Dancer::Plugin::Foo::Bar>, use:

  plugins:
    "Foo::Bar":
      key: value


=item B<plugin_args>

To easy migration and interoperability between Dancer 1 and Dancer 2
you can use this method to obtain the arguments or parameters passed
to a plugin-defined keyword. Although not relevant for Dancer 1 only,
or Dancer 2 only, plugins, it is useful for universal plugins.

  register foo => sub {
     my ($self, @args) = plugin_args(@_);
     ...
  }

Note that Dancer 1 will return undef as the object reference.

=back

=head1 AUTHORS

This module has been written by Alexis Sukrieh and others.

=head1 LICENSE

This module is free software and is published under the same
terms as Perl itself.

=cut