1package Dancer2::Plugin;
2# ABSTRACT: base class for Dancer2 plugins
3$Dancer2::Plugin::VERSION = '0.301004';
4use strict;
5use warnings;
6
7use Moo;
8use Carp;
9use List::Util qw/ reduce /;
10use Module::Runtime 'require_module';
11use Attribute::Handlers;
12use Scalar::Util;
13use Ref::Util qw<is_arrayref is_coderef>;
14
15our $CUR_PLUGIN;
16
17extends 'Exporter::Tiny';
18
19with 'Dancer2::Core::Role::Hookable';
20
21has app => (
22    is       => 'ro',
23    weak_ref => 1,
24    required => 1,
25);
26
27has config => (
28    is => 'ro',
29    lazy => 1,
30    default => sub {
31        my $self = shift;
32        my $config = $self->app->config;
33        my $package = ref $self; # TODO
34        $package =~ s/Dancer2::Plugin:://;
35        $config->{plugins}{$package} || {};
36    },
37);
38
39my $_keywords = {};
40sub keywords { $_keywords }
41
42my $REF_ADDR_REGEX = qr{
43    [A-Za-z0-9\:\_]+
44    =HASH
45    \(
46        ([0-9a-fx]+)
47    \)
48}x;
49my %instances;
50
51# backwards compatibility
52our $_keywords_by_plugin = {};
53
54has '+hooks' => (
55    default => sub {
56        my $plugin = shift;
57        my $name = 'plugin.' . lc ref $plugin;
58        $name =~ s/Dancer2::Plugin:://i;
59        $name =~ s/::/_/g;
60
61        +{
62            map { join( '.', $name, $_ ) => [] }
63                @{ $plugin->ClassHooks }
64        };
65    },
66);
67
68sub add_hooks {
69    my $class = shift;
70    push @{ $class->ClassHooks }, @_;
71}
72
73sub execute_plugin_hook {
74    my ( $self, $name, @args ) = @_;
75    my $plugin_class = ref $self;
76
77    $self->isa('Dancer2::Plugin')
78        or croak "Cannot call plugin hook ($name) from outside plugin";
79    $plugin_class =~ s/^Dancer2::Plugin:://; # short names
80
81    my $full_name = 'plugin.' . lc($plugin_class) . ".$name";
82    $full_name =~ s/::/_/g;
83
84    $self->app->execute_hook( $full_name, @args );
85}
86
87sub find_plugin {
88    my ( $self, $name ) = @_;
89    return $self->app->find_plugin($name);
90}
91
92# both functions are there for D2::Core::Role::Hookable
93# back-compatibility. Aren't used
94sub supported_hooks { [] }
95sub hook_aliases    { $_[0]->{'hook_aliases'} ||= {} }
96
97### has() STUFF  ########################################
98
99# our wrapping around Moo::has, done to be able to intercept
100# both 'from_config' and 'plugin_keyword'
101sub _p2_has {
102    my $class = shift;
103    $class->_p2_has_from_config( $class->_p2_has_keyword( @_ ) );
104};
105
106sub _p2_has_from_config {
107    my( $class, $name, %args ) = @_;
108
109    my $config_name = delete $args{'from_config'}
110        or return ( $name, %args );
111
112    $args{lazy} = 1;
113
114    if ( is_coderef($config_name) ) {
115        $args{default} ||= $config_name;
116        $config_name = 1;
117    }
118
119    $config_name = $name if $config_name eq '1';
120    my $orig_default = $args{default} || sub{};
121    $args{default} = sub {
122        my $plugin = shift;
123        my $value = reduce { eval { $a->{$b} } } $plugin->config, split /\./, $config_name;
124        return defined $value ? $value: $orig_default->($plugin);
125    };
126
127    return $name => %args;
128}
129
130sub _p2_has_keyword {
131    my( $class, $name, %args ) = @_;
132
133    if( my $keyword = delete $args{plugin_keyword} ) {
134
135        $keyword = $name if $keyword eq '1';
136
137        $class->keywords->{$_} = sub { (shift)->$name(@_) }
138            for ref $keyword ? @$keyword : $keyword;
139    }
140
141    return $name => %args;
142}
143
144### ATTRIBUTE HANDLER STUFF ########################################
145
146# :PluginKeyword shenanigans
147
148sub PluginKeyword :ATTR(CODE,BEGIN) {
149    my( $class, $sym_ref, $code, undef, $args ) = @_;
150
151    # importing at BEGIN stage doesn't work with 5.10 :-(
152    return unless ref $sym_ref;
153
154    my $func_name = *{$sym_ref}{NAME};
155
156    $args = join '', @$args if is_arrayref($args);
157
158    for my $name ( split ' ', $args || $func_name ) {
159        $class->keywords->{$name} = $code;
160    }
161
162}
163
164## EXPORT STUFF ##############################################################
165
166# this @EXPORT will only be taken
167# into account when we do a 'use Dancer2::Plugin'
168# I.e., it'll only do its magic for the
169# plugins themselves, not when they are
170# called
171our @EXPORT = qw/ :plugin /;
172
173# compatibility - it will be removed soon!
174my $no_dsl = {};
175my $exported_app = {};
176sub _exporter_expand_tag {
177    my( $class, $name, $args, $global ) = @_;
178
179    my $caller = $global->{into};
180
181    $name eq 'no_dsl' and $no_dsl->{$caller} = 1;
182    # no_dsl check here is for compatibility only
183    # it will be removed soon!
184    return _exporter_plugin($caller)
185        if $name eq 'plugin' or $name eq 'no_dsl';
186
187    return _exporter_app($class,$caller,$global)
188        if $name eq 'app' and $caller->can('app') and !$no_dsl->{$class};
189
190    return;
191
192}
193
194# plugin has been called within a D2 app. Modify
195# the app and export keywords
196sub _exporter_app {
197    my( $class, $caller, $global ) = @_;
198
199    $exported_app->{$caller} = 1;
200
201    # The check for ->dsl->app is to handle plugins as well.
202    # Otherwise you can only import from a plugin to an app,
203    # but with this, you can import to anything
204    # that has a DSL with an app, which translates to "also plugins"
205    my $app = eval("${caller}::app()") || eval { $caller->dsl->app } ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
206        or return; ## no critic
207
208    return unless $app->can('with_plugin');
209
210    my $plugin = $app->with_plugin( '+' . $class );
211    $global->{'plugin'} = $plugin;
212
213    return unless $class->can('keywords');
214
215    # Add our hooks to the app, so they're recognized
216    # this is for compatibility so you can call execute_hook()
217    # without the fully qualified plugin name.
218    # The reason we need to do this here instead of when adding a
219    # hook is because we need to register in the app, and only now it
220    # exists.
221    # This adds a caveat that two plugins cannot register
222    # the same hook name, but that will be deprecated anyway.
223    {;
224        foreach my $hook ( @{ $plugin->ClassHooks } ) {
225            my $full_name = 'plugin.' . lc($class) . ".$hook";
226            $full_name =~ s/Dancer2::Plugin:://i;
227            $full_name =~ s/::/_/g;
228
229            # this adds it to the plugin
230            $plugin->hook_aliases->{$hook} = $full_name;
231
232            # this adds it to the app
233            $plugin->app->hook_aliases->{$hook} = $full_name;
234
235            # copy the hooks from the plugin to the app
236            # this is in case they were created at import time
237            # rather than after
238            @{ $plugin->app->hooks }{ keys %{ $plugin->hooks } } =
239                values %{ $plugin->hooks };
240        }
241    }
242
243    {
244        # get the reference
245        my ($plugin_addr) = "$plugin" =~ $REF_ADDR_REGEX;
246
247        $instances{$plugin_addr}{'config'} = sub { $plugin->config };
248        $instances{$plugin_addr}{'app'}    = $plugin->app;
249
250        Scalar::Util::weaken( $instances{$plugin_addr}{'app'} );
251
252        ## no critic
253        no strict 'refs';
254
255        # we used a forward declaration
256        # so the compiled form "plugin_setting;" can be overridden
257        # with this implementation,
258        # which works on runtime ("plugin_setting()")
259        # we can't use can() here because the forward declaration will
260        # create a CODE stub
261        no warnings 'redefine';
262        *{"${class}::plugin_setting"} = sub {
263            my ($plugin_addr) = "$CUR_PLUGIN" =~ $REF_ADDR_REGEX;
264
265            $plugin_addr
266                or Carp::croak('Can\'t find originating plugin');
267
268            # we need to do this because plugins might call "set"
269            # in order to change plugin configuration but it doesn't
270            # change the plugin object, it changes the app object
271            # so we merge them.
272            my $name = ref $CUR_PLUGIN;
273            $name =~ s/^Dancer2::Plugin:://g;
274
275            my $plugin_inst       = $instances{$plugin_addr};
276            my $plugin_config     = $plugin_inst->{'config'}->();
277            my $app_plugin_config = $plugin_inst->{'app'}->config->{'plugins'}{$name};
278
279            return { %{ $plugin_config || {} }, %{ $app_plugin_config || {} } };
280        };
281
282        # FIXME:
283        # why doesn't this work? it's like it's already defined somewhere
284        # but i'm not sure where. seems like AUTOLOAD runs it.
285        #$class->can('execute_hook') or
286            *{"${class}::execute_hook"}   = sub {
287                # this can also be called by App.pm itself
288                # if the plugin is a
289                # "candidate" for a hook
290                # See: App.pm "execute_hook" method, "around" modifier
291                if ( $_[0]->isa('Dancer2::Plugin') ) {
292                    # this means it's probably our hook, we need to verify it
293                    my ( $plugin_self, $hook_name, @args ) = @_;
294
295                    my $plugin_class = lc $class;
296                    $plugin_class =~ s/^dancer2::plugin:://;
297                    $plugin_class =~ s{::}{_}g;
298
299                    # you're either calling it with the full qualifier or not
300                    # if not, use the execute_plugin_hook instead
301                    if ( $plugin->hooks->{"plugin.$plugin_class.$hook_name"} ) {
302                        Carp::carp("Please use fully qualified hook name or "
303                                 . "the method execute_plugin_hook");
304                        $hook_name = "plugin.$plugin_class.$hook_name";
305                    }
306
307                    $hook_name =~ /^plugin\.$plugin_class/
308                        or Carp::croak('Unknown plugin called through other plugin');
309
310                    # now we can't really use the app to execute it because
311                    # the "around" modifier is the one calling us to begin
312                    # with, so we need to call it directly ourselves
313                    # this is okay because the modifier is there only to
314                    # call candidates, like us (this is in fact how and
315                    # why we were called)
316                    $_->( $plugin_self, @args )
317                        for @{ $plugin->hooks->{$hook_name} };
318
319                    return;
320                }
321
322                return $plugin->app->execute_hook(@_);
323        };
324    }
325
326    local $CUR_PLUGIN = $plugin;
327    $_->($plugin) for @{ $plugin->_DANCER2_IMPORT_TIME_SUBS() };
328
329    map { [ $_ =>  {plugin => $plugin}  ] } keys %{ $plugin->keywords };
330}
331
332# turns the caller namespace into
333# a D2P2 class, with exported keywords
334sub _exporter_plugin {
335    my $caller = shift;
336    require_module('Dancer2::Core::DSL');
337    my $keywords_list = join ' ', keys %{ Dancer2::Core::DSL->dsl_keywords };
338
339    eval <<"END"; ## no critic
340        {
341            package $caller;
342            use Moo;
343            use Carp ();
344            use Attribute::Handlers;
345
346            extends 'Dancer2::Plugin';
347
348            our \@EXPORT = ( ':app' );
349
350            around has => sub {
351                my( \$orig, \$name, \%args ) = \@_;
352
353                if (ref \$name eq 'ARRAY'
354                        && exists \$args{'plugin_keyword'}
355                        && ref \$args{'plugin_keyword'} eq 'ARRAY') {
356
357                    Carp::croak('Setting "plugin_keyword" to an array is disallowed'
358                        . ' when defining multiple attributes simultaneously');
359                }
360
361                \$orig->( ${caller}->_p2_has( \$_, \%args) )
362                    for ref \$name ? @\$name : \$name;
363            };
364
365            sub PluginKeyword :ATTR(CODE,BEGIN) {
366                goto &Dancer2::Plugin::PluginKeyword;
367            }
368
369            sub execute_plugin_hook {
370                goto &Dancer2::Plugin::execute_plugin_hook;
371            }
372
373            my \$_keywords = {};
374            sub keywords { \$_keywords }
375
376            my \$_ClassHooks = [];
377            sub ClassHooks { \$_ClassHooks }
378
379            # this is important as it'll do the keywords mapping between the
380            # plugin and the app
381            sub register_plugin { Dancer2::Plugin::register_plugin(\@_) }
382
383            sub register {
384                my ( \$keyword, \$sub ) = \@_;
385                \$_keywords->{\$keyword} = \$sub;
386
387                \$keyword =~ /^[a-zA-Z_]+[a-zA-Z0-9_]*\$/
388                  or Carp::croak(
389                    "You can't use '\$keyword', it is an invalid name"
390                  . " (it should match ^[a-zA-Z_]+[a-zA-Z0-9_]*\\\$ )");
391
392
393                grep +( \$keyword eq \$_ ), qw<$keywords_list>
394                    and Carp::croak("You can't use '\$keyword', this is a reserved keyword");
395
396                \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword}
397                    and Carp::croak("You can't use \$keyword, "
398                      . "this is a keyword reserved by "
399                      . \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword});
400
401                \$Dancer2::Plugin::_keywords_by_plugin->{\$keyword} = "$caller";
402
403                # Exporter::Tiny doesn't seem to generate the subs
404                # in the caller properly, so we have to do it manually
405                {
406                    no strict 'refs';
407                    *{"${caller}::\$keyword"} = \$sub;
408                }
409            }
410
411            my \@_DANCER2_IMPORT_TIME_SUBS;
412            sub _DANCER2_IMPORT_TIME_SUBS {\\\@_DANCER2_IMPORT_TIME_SUBS}
413            sub on_plugin_import (&) {
414                push \@_DANCER2_IMPORT_TIME_SUBS, \$_[0];
415            }
416
417            sub register_hook { goto &plugin_hooks }
418
419            sub plugin_setting {};
420
421            sub plugin_args {
422                Carp::carp "Plugin DSL method 'plugin_args' is deprecated. "
423                         . "Use '\\\@_' instead'.\n";
424
425                \@_;
426            }
427        }
428END
429
430    $no_dsl->{$caller} or eval <<"END"; ## no critic
431        {
432            package $caller;
433
434            # FIXME: AUTOLOAD might pick up on this
435            sub dancer_app {
436                Carp::carp "Plugin DSL method 'dancer_app' is deprecated. "
437                         . "Use '\\\$self->app' instead'.\n";
438
439                \$_[0]->app;
440            }
441
442            # FIXME: AUTOLOAD might pick up on this
443            sub request {
444                Carp::carp "Plugin DSL method 'request' is deprecated. "
445                         . "Use '\\\$self->app->request' instead'.\n";
446
447                \$_[0]->app->request;
448            }
449
450            # FIXME: AUTOLOAD might pick up on this
451            sub var {
452                Carp::carp "Plugin DSL method 'var' is deprecated. "
453                         . "Use '\\\$self->app->request->var' instead'.\n";
454
455                shift->app->request->var(\@_);
456            }
457
458            # FIXME: AUTOLOAD might pick up on this
459            sub hook {
460                Carp::carp "Plugin DSL method 'hook' is deprecated. "
461                         . "Use '\\\$self->app->add_hook' instead'.\n";
462
463                shift->app->add_hook(
464                    Dancer2::Core::Hook->new( name => shift, code => shift ) );
465            }
466
467        }
468END
469
470    die $@ if $@;
471
472    my $app_dsl_cb = _find_consumer();
473
474    if ( $app_dsl_cb ) {
475        my $dsl = $app_dsl_cb->();
476
477        {
478            ## no critic qw(TestingAndDebugging::ProhibitNoWarnings)
479            no strict 'refs';
480            no warnings 'redefine';
481            *{"${caller}::dsl"} = sub {$dsl};
482        }
483    }
484
485    return map { [ $_ => { class => $caller } ] }
486               qw/ plugin_keywords plugin_hooks /;
487}
488
489sub _find_consumer {
490    my $class;
491
492    ## no critic qw(ControlStructures::ProhibitCStyleForLoops)
493    for ( my $i = 1; my $caller = caller($i); $i++ ) {
494        $class = $caller->can('dsl')
495            and last;
496    }
497
498    # If you use a Dancer2 plugin outside a Dancer App, this fails.
499    # It also breaks a bunch of the tests. -- SX
500    #$class
501    #    or croak('Could not find Dancer2 app');
502
503    return $class;
504};
505
506# This has to be called for now at the end of every plugin package, in order to
507# map the keywords of the associated app to the plugin, so that these keywords
508# can be called from within the plugin code. This function is deprecated, as
509# it's tied to the old plugin system. It's kept here for backcompat reason, but
510# should go away with the old plugin system.
511sub register_plugin {
512
513    my $plugin_module = caller(1);
514
515    # if you ask yourself why we do the injection in the plugin
516    # module namespace every time the plugin is used, and not only
517    # once, it's because it can be used by different app that could
518    # have a different DSL with a different list of keywords.
519
520    my $_DANCER2_IMPORT_TIME_SUBS = $plugin_module->_DANCER2_IMPORT_TIME_SUBS;
521    unshift(@$_DANCER2_IMPORT_TIME_SUBS, sub {
522                my $app_dsl_cb = _find_consumer();
523
524                # Here we want to verify that "register_plugin" compat keyword
525                # was in fact only called from an app.
526                $app_dsl_cb
527                      or Carp::croak(
528                        'I could not find a Dancer App for this plugin');
529
530                my $dsl = $app_dsl_cb->();
531
532                foreach my $keyword ( keys %{ $dsl->dsl_keywords} ) {
533                    # if not yet defined, inject the keyword in the plugin
534                    # namespace, but make sure the code will always get the
535                    # coderef from the right associated app, because one plugin
536                    # can be used by multiple apps. Note that we remove the
537                    # first parameter (plugin instance) from what we pass to
538                    # the keyword implementation of the App
539                    no strict 'refs';
540                    $plugin_module->can($keyword)
541                      or *{"${plugin_module}::$keyword"} = sub {
542                          my $coderef = shift()->app->name->can($keyword);
543                          $coderef->(@_);
544                      };
545                }
546            });
547}
548
549sub _exporter_expand_sub {
550    my( $plugin, $name, $args, $global ) = @_;
551    my $class = $args->{class};
552
553    return _exported_plugin_keywords($plugin,$class)
554        if $name eq 'plugin_keywords';
555
556    return _exported_plugin_hooks($class)
557        if $name eq 'plugin_hooks';
558
559    $exported_app->{ $global->{'into'} }
560        or Carp::croak('Specific subroutines cannot be exported from plugin');
561
562    # otherwise, we're exporting a keyword
563
564    my $p = $args->{plugin};
565    my $sub = $p->keywords->{$name};
566    return $name => sub(@) {
567        # localize the plugin so we can get it later
568        local $CUR_PLUGIN = $p;
569        $sub->($p,@_);
570    }
571}
572
573# "There's a good reason for this, I swear!"
574#    -- Sawyer X
575# basically, if someone adds a hook to the app directly
576# that needs to access a DSL that needs the current object
577# (such as "plugin_setting"),
578# that object needs to be available
579# So:
580# we override App's "add_hook" to provide a register a
581# different hook callback, that closes over the plugin when
582# it's available, relocalizes it when the callback runs and
583# after localizing it, calls the original hook callback
584{
585    ## no critic;
586    no strict 'refs';
587    no warnings 'redefine';
588    my $orig_cb = Dancer2::Core::App->can('add_hook');
589    $orig_cb and *{'Dancer2::Core::App::add_hook'} = sub {
590        my ( $app, $hook ) = @_;
591
592        my $hook_code = Scalar::Util::blessed($hook) ? $hook->code : $hook->{code};
593        my $plugin    = $CUR_PLUGIN;
594
595        $hook->{'code'} = sub {
596            local $CUR_PLUGIN = $plugin;
597            $hook_code->(@_);
598        };
599
600        $orig_cb->(@_);
601    };
602}
603
604
605# define the exported 'plugin_keywords'
606sub _exported_plugin_keywords{
607    my( $plugin, $class ) = @_;
608
609    return plugin_keywords => sub(@) {
610        while( my $name = shift @_ ) {
611            ## no critic
612            my $sub = is_coderef($_[0])
613                ? shift @_
614                : eval '\&'.$class."::" . ( ref $name ? $name->[0] : $name );
615            $class->keywords->{$_} = $sub for ref $name ? @$name : $name;
616        }
617    }
618}
619
620sub _exported_plugin_hooks {
621    my $class = shift;
622    return plugin_hooks => sub (@) { $class->add_hooks(@_) }
623}
624
6251;
626
627__END__
628
629=pod
630
631=encoding UTF-8
632
633=head1 NAME
634
635Dancer2::Plugin - base class for Dancer2 plugins
636
637=head1 VERSION
638
639version 0.301004
640
641=head1 SYNOPSIS
642
643The plugin itself:
644
645    package Dancer2::Plugin::Polite;
646
647    use strict;
648    use warnings;
649
650    use Dancer2::Plugin;
651
652    has smiley => (
653        is => 'ro',
654        default => sub {
655            $_[0]->config->{smiley} || ':-)'
656        }
657    );
658
659    plugin_keywords 'add_smileys';
660
661    sub BUILD {
662        my $plugin = shift;
663
664        $plugin->app->add_hook( Dancer2::Core::Hook->new(
665            name => 'after',
666            code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
667        ));
668
669        $plugin->app->add_route(
670            method => 'get',
671            regexp => '/goodbye',
672            code   => sub {
673                my $app = shift;
674                'farewell, ' . $app->request->params->{name};
675            },
676        );
677
678    }
679
680    sub add_smileys {
681        my( $plugin, $text ) = @_;
682
683        $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
684
685        return $text;
686    }
687
688    1;
689
690then to load into the app:
691
692    package MyApp;
693
694    use strict;
695    use warnings;
696
697    use Dancer2;
698
699    BEGIN { # would usually be in config.yml
700        set plugins => {
701            Polite => {
702                smiley => '8-D',
703            },
704        };
705    }
706
707    use Dancer2::Plugin::Polite;
708
709    get '/' => sub {
710        add_smileys( 'make me a sandwich.' );
711    };
712
713    1;
714
715=head1 DESCRIPTION
716
717=head2 Writing the plugin
718
719=head3 C<use Dancer2::Plugin>
720
721The plugin must begin with
722
723    use Dancer2::Plugin;
724
725which will turn the package into a L<Moo> class that inherits from L<Dancer2::Plugin>. The base class provides the plugin with
726two attributes: C<app>, which is populated with the Dancer2 app object for which
727the plugin is being initialized for, and C<config> which holds the plugin
728section of the application configuration.
729
730=head3 Modifying the app at building time
731
732If the plugin needs to tinker with the application -- add routes or hooks, for example --
733it can do so within its C<BUILD()> function.
734
735    sub BUILD {
736        my $plugin = shift;
737
738        $plugin->app->add_route( ... );
739    }
740
741=head3 Adding keywords
742
743=head4 Via C<plugin_keywords>
744
745Keywords that the plugin wishes to export to the Dancer2 app can be defined via the C<plugin_keywords> keyword:
746
747    plugin_keywords qw/
748        add_smileys
749        add_sad_kitten
750    /;
751
752Each of the keyword will resolve to the class method of the same name. When invoked as keyword, it'll be passed
753the plugin object as its first argument.
754
755    sub add_smileys {
756        my( $plugin, $text ) = @_;
757
758        return join ' ', $text, $plugin->smiley;
759    }
760
761    # and then in the app
762
763    get '/' => sub {
764        add_smileys( "Hi there!" );
765    };
766
767You can also pass the functions directly to C<plugin_keywords>.
768
769    plugin_keywords
770        add_smileys => sub {
771            my( $plugin, $text ) = @_;
772
773            $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
774
775            return $text;
776        },
777        add_sad_kitten => sub { ... };
778
779Or a mix of both styles. We're easy that way:
780
781    plugin_keywords
782        add_smileys => sub {
783            my( $plugin, $text ) = @_;
784
785            $text =~ s/ (?<= \. ) / $plugin->smiley /xeg;
786
787            return $text;
788        },
789        'add_sad_kitten';
790
791    sub add_sad_kitten {
792        ...;
793    }
794
795If you want several keywords to be synonyms calling the same
796function, you can list them in an arrayref. The first
797function of the list is taken to be the "real" method to
798link to the keywords.
799
800    plugin_keywords [qw/ add_smileys add_happy_face /];
801
802    sub add_smileys { ... }
803
804Calls to C<plugin_keywords> are cumulative.
805
806=head4 Via the C<:PluginKeyword> function attribute
807
808For perl 5.12 and higher, keywords can also be defined by adding the C<:PluginKeyword> attribute
809to the function you wish to export.
810
811For Perl 5.10, the export triggered by the sub attribute comes too late in the
812game, and the keywords won't be exported in the application namespace.
813
814    sub foo :PluginKeyword { ... }
815
816    sub bar :PluginKeyword( baz quux ) { ... }
817
818    # equivalent to
819
820    sub foo { ... }
821    sub bar { ... }
822
823    plugin_keywords 'foo', [ qw/ baz quux / ] => \&bar;
824
825=head4 For an attribute
826
827You can also turn an attribute of the plugin into a keyword.
828
829    has foo => (
830        is => 'ro',
831        plugin_keyword => 1,  # keyword will be 'foo'
832    );
833
834    has bar => (
835        is => 'ro',
836        plugin_keyword => 'quux',  # keyword will be 'quux'
837    );
838
839    has baz => (
840        is => 'ro',
841        plugin_keyword => [ 'baz', 'bazz' ],  # keywords will be 'baz' and 'bazz'
842    );
843
844=head3 Accessing the plugin configuration
845
846The plugin configuration is available via the C<config()> method.
847
848    sub BUILD {
849        my $plugin = shift;
850
851        if ( $plugin->config->{feeling_polite} ) {
852            $plugin->app->add_hook( Dancer2::Core::Hook->new(
853                name => 'after',
854                code => sub { $_[0]->content( $_[0]->content . " ... please?" ) }
855            ));
856        }
857    }
858
859=head3 Getting default values from config file
860
861Since initializing a plugin with either a default or a value passed via the configuration file,
862like
863
864    has smiley => (
865        is => 'ro',
866        default => sub {
867            $_[0]->config->{smiley} || ':-)'
868        }
869    );
870
871C<Dancer2::Plugin> allows for a C<from_config> key in the attribute definition.
872Its value is the plugin configuration key that will be used to initialize the attribute.
873
874If it's given the value C<1>, the name of the attribute will be taken as the configuration key.
875
876Nested hash keys can also be referred to using a dot notation.
877
878If the plugin configuration has no value for the given key, the attribute default, if specified, will be honored.
879
880If the key is given a coderef as value, it's considered to be a C<default> value combo:
881
882    has foo => (
883        is => 'ro',
884        from_config => sub { 'my default' },
885    );
886
887
888    # equivalent to
889    has foo => (
890        is => 'ro',
891        from_config => 'foo',
892        default => sub { 'my default' },
893    );
894
895For example:
896
897    # in config.yml
898
899    plugins:
900        Polite:
901            smiley: ':-)'
902            greeting:
903                casual: Hi!
904                formal: How do you do?
905
906
907    # in the plugin
908
909    has smiley => (             # will be ':-)'
910        is          => 'ro',
911        from_config => 1,
912        default     => sub { ':-(' },
913    );
914
915    has casual_greeting => (    # will be 'Hi!'
916        is          => 'ro',
917        from_config => 'greeting.casual',
918    );
919
920    has apology => (            # will be 'sorry'
921        is          => 'ro',
922        from_config => 'apology',
923        default     => sub { 'sorry' },
924    )
925
926    has closing => (            # will be 'See ya!'
927        is => 'ro',
928        from_config => sub { 'See ya!' },
929    );
930
931=head3 Config becomes immutable
932
933The plugin's C<config> attribute is loaded lazily on the first call to
934C<config>. After this first call C<config> becomes immutable so you cannot
935do the following in a test:
936
937    use Dancer2;
938    use Dancer2::Plugin::FooBar;
939
940    set plugins => {
941        FooBar => {
942            wibble => 1,  # this is OK
943        },
944    };
945
946    flibble(45);          # plugin keyword called which causes config read
947
948    set plugins => {
949        FooBar => {
950            wibble => 0,  # this will NOT change plugin config
951        },
952    };
953
954=head3 Accessing the parent Dancer app
955
956If the plugin is instantiated within a Dancer app, it'll be
957accessible via the method C<app()>.
958
959    sub BUILD {
960        my $plugin = shift;
961
962        $plugin->app->add_route( ... );
963    }
964
965To use Dancer's DSL in your plugin:
966
967    $self->dsl->debug( “Hi! I’m logging from your plugin!” );
968
969See L<Dancer2::Manual/"DSL KEYWORDS"> for a full list of Dancer2 DSL.
970
971=head2 Using the plugin within the app
972
973A plugin is loaded via
974
975    use Dancer2::Plugin::Polite;
976
977The plugin will assume that it's loading within a Dancer module and will
978automatically register itself against its C<app()> and export its keywords
979to the local namespace. If you don't want this to happen, specify that you
980don't want anything imported via empty parentheses when C<use>ing the module:
981
982    use Dancer2::Plugin::Polite ();
983
984=head2 Plugins using plugins
985
986It's easy to use plugins from within a plugin:
987
988    package Dancer2::Plugin::SourPuss;
989
990    use Dancer2::Plugin;
991    use Dancer2::Plugin::Polite;
992
993    sub my_keyword { my $smiley = smiley(); }
994
995    1;
996
997This does not export C<smiley()> into your application - it is only available
998from within your plugin. However, from the example above, you can wrap
999DSL from other plugins and make it available from your plugin.
1000
1001=head2 Utilizing other plugins
1002
1003You can use the C<find_plugin> to locate other plugins loaded by the user,
1004in order to use them, or their information, directly:
1005
1006    # MyApp.pm
1007    use Dancer2;
1008    use Dancer2::Plugin::Foo;
1009    use Dancer2::Plugin::Bar;
1010
1011    # Dancer2::Plugin::Bar;
1012    ...
1013
1014    sub my_keyword {
1015        my $self = shift;
1016        my $foo  = $self->find_plugin('Dancer2::Plugin::Foo')
1017            or $self->dsl->send_error('Could not find Foo');
1018
1019        return $foo->foo_keyword(...);
1020    }
1021
1022=head2 Hooks
1023
1024New plugin hooks are declared via C<plugin_hooks>.
1025
1026    plugin_hooks 'my_hook', 'my_other_hook';
1027
1028Hooks are prefixed with C<plugin.plugin_name>. So the plugin
1029C<my_hook> coming from the plugin C<Dancer2::Plugin::MyPlugin> will have the hook name
1030C<plugin.myplugin.my_hook>.
1031
1032Hooks are executed within the plugin by calling them via the associated I<app>.
1033
1034    $plugin->execute_plugin_hook( 'my_hook' );
1035
1036You can also call any other hook if you provide the full name using the
1037C<execute_hook> method:
1038
1039    $plugin->app->execute_hook( 'core.app.route_exception' );
1040
1041Or using their alias:
1042
1043    $plugin->app->execute_hook( 'on_route_exception' );
1044
1045B<Note:> If your plugin consumes a plugin that declares any hooks, those hooks
1046are added to your application, even though DSL is not.
1047
1048=head2 Writing Test Gotchas
1049
1050=head3 Constructor for Dancer2::Plugin::Foo has been inlined and cannot be updated
1051
1052You'll usually get this one because you are defining both the plugin and app
1053in your test file, and the runtime creation of Moo's attributes happens after
1054the compile-time import voodoo dance.
1055
1056To get around this nightmare, wrap your plugin definition in a C<BEGIN> block.
1057
1058    BEGIN {
1059        package Dancer2::Plugin::Foo;
1060
1061        use Dancer2::Plugin;
1062
1063            has bar => (
1064                is => 'ro',
1065                from_config => 1,
1066            );
1067
1068            plugin_keywords qw/ bar /;
1069
1070    }
1071
1072    {
1073        package MyApp;
1074
1075        use Dancer2;
1076        use Dancer2::Plugin::Foo;
1077
1078        bar();
1079    }
1080
1081=head3 You cannot overwrite a locally defined method (bar) with a reader
1082
1083If you set an object attribute of your plugin to be a keyword as well, you need
1084to call C<plugin_keywords> after the attribute definition.
1085
1086    package Dancer2::Plugin::Foo;
1087
1088    use Dancer2::Plugin;
1089
1090    has bar => (
1091        is => 'ro',
1092    );
1093
1094    plugin_keywords 'bar';
1095
1096=head1 AUTHOR
1097
1098Dancer Core Developers
1099
1100=head1 COPYRIGHT AND LICENSE
1101
1102This software is copyright (c) 2021 by Alexis Sukrieh.
1103
1104This is free software; you can redistribute it and/or modify it under
1105the same terms as the Perl 5 programming language system itself.
1106
1107=cut
1108