.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Dancer2::Plugin 3pm" .TH Dancer2::Plugin 3pm "2023-02-10" "perl v5.36.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Dancer2::Plugin \- base class for Dancer2 plugins .SH "VERSION" .IX Header "VERSION" version 0.400001 .SH "SYNOPSIS" .IX Header "SYNOPSIS" The plugin itself: .PP .Vb 1 \& package Dancer2::Plugin::Polite; \& \& use strict; \& use warnings; \& \& use Dancer2::Plugin; \& \& has smiley => ( \& is => \*(Aqro\*(Aq, \& default => sub { \& $_[0]\->config\->{smiley} || \*(Aq:\-)\*(Aq \& } \& ); \& \& plugin_keywords \*(Aqadd_smileys\*(Aq; \& \& sub BUILD { \& my $plugin = shift; \& \& $plugin\->app\->add_hook( Dancer2::Core::Hook\->new( \& name => \*(Aqafter\*(Aq, \& code => sub { $_[0]\->content( $_[0]\->content . " ... please?" ) } \& )); \& \& $plugin\->app\->add_route( \& method => \*(Aqget\*(Aq, \& regexp => \*(Aq/goodbye\*(Aq, \& code => sub { \& my $app = shift; \& \*(Aqfarewell, \*(Aq . $app\->request\->params\->{name}; \& }, \& ); \& \& } \& \& sub add_smileys { \& my( $plugin, $text ) = @_; \& \& $text =~ s/ (?<= \e. ) / $plugin\->smiley /xeg; \& \& return $text; \& } \& \& 1; .Ve .PP then to load into the app: .PP .Vb 1 \& package MyApp; \& \& use strict; \& use warnings; \& \& use Dancer2; \& \& BEGIN { # would usually be in config.yml \& set plugins => { \& Polite => { \& smiley => \*(Aq8\-D\*(Aq, \& }, \& }; \& } \& \& use Dancer2::Plugin::Polite; \& \& get \*(Aq/\*(Aq => sub { \& add_smileys( \*(Aqmake me a sandwich.\*(Aq ); \& }; \& \& 1; .Ve .SH "DESCRIPTION" .IX Header "DESCRIPTION" .SS "Writing the plugin" .IX Subsection "Writing the plugin" \fI\f(CI\*(C`use Dancer2::Plugin\*(C'\fI\fR .IX Subsection "use Dancer2::Plugin" .PP The plugin must begin with .PP .Vb 1 \& use Dancer2::Plugin; .Ve .PP which will turn the package into a Moo class that inherits from Dancer2::Plugin. The base class provides the plugin with two attributes: \f(CW\*(C`app\*(C'\fR, which is populated with the Dancer2 app object for which the plugin is being initialized for, and \f(CW\*(C`config\*(C'\fR which holds the plugin section of the application configuration. .PP \fIModifying the app at building time\fR .IX Subsection "Modifying the app at building time" .PP If the plugin needs to tinker with the application \*(-- add routes or hooks, for example \*(-- it can do so within its \f(CW\*(C`BUILD()\*(C'\fR function. .PP .Vb 2 \& sub BUILD { \& my $plugin = shift; \& \& $plugin\->app\->add_route( ... ); \& } .Ve .PP \fIAdding keywords\fR .IX Subsection "Adding keywords" .PP Via \f(CW\*(C`plugin_keywords\*(C'\fR .IX Subsection "Via plugin_keywords" .PP Keywords that the plugin wishes to export to the Dancer2 app can be defined via the \f(CW\*(C`plugin_keywords\*(C'\fR keyword: .PP .Vb 4 \& plugin_keywords qw/ \& add_smileys \& add_sad_kitten \& /; .Ve .PP Each of the keyword will resolve to the class method of the same name. When invoked as keyword, it'll be passed the plugin object as its first argument. .PP .Vb 2 \& sub add_smileys { \& my( $plugin, $text ) = @_; \& \& return join \*(Aq \*(Aq, $text, $plugin\->smiley; \& } \& \& # and then in the app \& \& get \*(Aq/\*(Aq => sub { \& add_smileys( "Hi there!" ); \& }; .Ve .PP You can also pass the functions directly to \f(CW\*(C`plugin_keywords\*(C'\fR. .PP .Vb 3 \& plugin_keywords \& add_smileys => sub { \& my( $plugin, $text ) = @_; \& \& $text =~ s/ (?<= \e. ) / $plugin\->smiley /xeg; \& \& return $text; \& }, \& add_sad_kitten => sub { ... }; .Ve .PP Or a mix of both styles. We're easy that way: .PP .Vb 3 \& plugin_keywords \& add_smileys => sub { \& my( $plugin, $text ) = @_; \& \& $text =~ s/ (?<= \e. ) / $plugin\->smiley /xeg; \& \& return $text; \& }, \& \*(Aqadd_sad_kitten\*(Aq; \& \& sub add_sad_kitten { \& ...; \& } .Ve .PP If you want several keywords to be synonyms calling the same function, you can list them in an arrayref. The first function of the list is taken to be the \*(L"real\*(R" method to link to the keywords. .PP .Vb 1 \& plugin_keywords [qw/ add_smileys add_happy_face /]; \& \& sub add_smileys { ... } .Ve .PP Calls to \f(CW\*(C`plugin_keywords\*(C'\fR are cumulative. .PP Via the \f(CW\*(C`:PluginKeyword\*(C'\fR function attribute .IX Subsection "Via the :PluginKeyword function attribute" .PP For perl 5.12 and higher, keywords can also be defined by adding the \f(CW\*(C`:PluginKeyword\*(C'\fR attribute to the function you wish to export. .PP For Perl 5.10, the export triggered by the sub attribute comes too late in the game, and the keywords won't be exported in the application namespace. .PP .Vb 1 \& sub foo :PluginKeyword { ... } \& \& sub bar :PluginKeyword( baz quux ) { ... } \& \& # equivalent to \& \& sub foo { ... } \& sub bar { ... } \& \& plugin_keywords \*(Aqfoo\*(Aq, [ qw/ baz quux / ] => \e&bar; .Ve .PP For an attribute .IX Subsection "For an attribute" .PP You can also turn an attribute of the plugin into a keyword. .PP .Vb 4 \& has foo => ( \& is => \*(Aqro\*(Aq, \& plugin_keyword => 1, # keyword will be \*(Aqfoo\*(Aq \& ); \& \& has bar => ( \& is => \*(Aqro\*(Aq, \& plugin_keyword => \*(Aqquux\*(Aq, # keyword will be \*(Aqquux\*(Aq \& ); \& \& has baz => ( \& is => \*(Aqro\*(Aq, \& plugin_keyword => [ \*(Aqbaz\*(Aq, \*(Aqbazz\*(Aq ], # keywords will be \*(Aqbaz\*(Aq and \*(Aqbazz\*(Aq \& ); .Ve .PP \fIAccessing the plugin configuration\fR .IX Subsection "Accessing the plugin configuration" .PP The plugin configuration is available via the \f(CW\*(C`config()\*(C'\fR method. .PP .Vb 2 \& sub BUILD { \& my $plugin = shift; \& \& if ( $plugin\->config\->{feeling_polite} ) { \& $plugin\->app\->add_hook( Dancer2::Core::Hook\->new( \& name => \*(Aqafter\*(Aq, \& code => sub { $_[0]\->content( $_[0]\->content . " ... please?" ) } \& )); \& } \& } .Ve .PP \fIGetting default values from config file\fR .IX Subsection "Getting default values from config file" .PP Since initializing a plugin with either a default or a value passed via the configuration file, like .PP .Vb 6 \& has smiley => ( \& is => \*(Aqro\*(Aq, \& default => sub { \& $_[0]\->config\->{smiley} || \*(Aq:\-)\*(Aq \& } \& ); .Ve .PP \&\f(CW\*(C`Dancer2::Plugin\*(C'\fR allows for a \f(CW\*(C`from_config\*(C'\fR key in the attribute definition. Its value is the plugin configuration key that will be used to initialize the attribute. .PP If it's given the value \f(CW1\fR, the name of the attribute will be taken as the configuration key. .PP Nested hash keys can also be referred to using a dot notation. .PP If the plugin configuration has no value for the given key, the attribute default, if specified, will be honored. .PP If the key is given a coderef as value, it's considered to be a \f(CW\*(C`default\*(C'\fR value combo: .PP .Vb 4 \& has foo => ( \& is => \*(Aqro\*(Aq, \& from_config => sub { \*(Aqmy default\*(Aq }, \& ); \& \& \& # equivalent to \& has foo => ( \& is => \*(Aqro\*(Aq, \& from_config => \*(Aqfoo\*(Aq, \& default => sub { \*(Aqmy default\*(Aq }, \& ); .Ve .PP For example: .PP .Vb 1 \& # in config.yml \& \& plugins: \& Polite: \& smiley: \*(Aq:\-)\*(Aq \& greeting: \& casual: Hi! \& formal: How do you do? \& \& \& # in the plugin \& \& has smiley => ( # will be \*(Aq:\-)\*(Aq \& is => \*(Aqro\*(Aq, \& from_config => 1, \& default => sub { \*(Aq:\-(\*(Aq }, \& ); \& \& has casual_greeting => ( # will be \*(AqHi!\*(Aq \& is => \*(Aqro\*(Aq, \& from_config => \*(Aqgreeting.casual\*(Aq, \& ); \& \& has apology => ( # will be \*(Aqsorry\*(Aq \& is => \*(Aqro\*(Aq, \& from_config => \*(Aqapology\*(Aq, \& default => sub { \*(Aqsorry\*(Aq }, \& ) \& \& has closing => ( # will be \*(AqSee ya!\*(Aq \& is => \*(Aqro\*(Aq, \& from_config => sub { \*(AqSee ya!\*(Aq }, \& ); .Ve .PP \fIConfig becomes immutable\fR .IX Subsection "Config becomes immutable" .PP The plugin's \f(CW\*(C`config\*(C'\fR attribute is loaded lazily on the first call to \&\f(CW\*(C`config\*(C'\fR. After this first call \f(CW\*(C`config\*(C'\fR becomes immutable so you cannot do the following in a test: .PP .Vb 2 \& use Dancer2; \& use Dancer2::Plugin::FooBar; \& \& set plugins => { \& FooBar => { \& wibble => 1, # this is OK \& }, \& }; \& \& flibble(45); # plugin keyword called which causes config read \& \& set plugins => { \& FooBar => { \& wibble => 0, # this will NOT change plugin config \& }, \& }; .Ve .PP \fIAccessing the parent Dancer app\fR .IX Subsection "Accessing the parent Dancer app" .PP If the plugin is instantiated within a Dancer app, it'll be accessible via the method \f(CW\*(C`app()\*(C'\fR. .PP .Vb 2 \& sub BUILD { \& my $plugin = shift; \& \& $plugin\->app\->add_route( ... ); \& } .Ve .PP To use Dancer's \s-1DSL\s0 in your plugin: .PP .Vb 1 \& $self\->dsl\->debug( “Hi! I’m logging from your plugin!” ); .Ve .PP See \*(L"\s-1DSL KEYWORDS\*(R"\s0 in Dancer2::Manual for a full list of Dancer2 \s-1DSL.\s0 .SS "Using the plugin within the app" .IX Subsection "Using the plugin within the app" A plugin is loaded via .PP .Vb 1 \& use Dancer2::Plugin::Polite; .Ve .PP The plugin will assume that it's loading within a Dancer module and will automatically register itself against its \f(CW\*(C`app()\*(C'\fR and export its keywords to the local namespace. If you don't want this to happen, specify that you don't want anything imported via empty parentheses when \f(CW\*(C`use\*(C'\fRing the module: .PP .Vb 1 \& use Dancer2::Plugin::Polite (); .Ve .SS "Plugins using plugins" .IX Subsection "Plugins using plugins" It's easy to use plugins from within a plugin: .PP .Vb 1 \& package Dancer2::Plugin::SourPuss; \& \& use Dancer2::Plugin; \& use Dancer2::Plugin::Polite; \& \& sub my_keyword { my $smiley = smiley(); } \& \& 1; .Ve .PP This does not export \f(CW\*(C`smiley()\*(C'\fR into your application \- it is only available from within your plugin. However, from the example above, you can wrap \&\s-1DSL\s0 from other plugins and make it available from your plugin. .SS "Utilizing other plugins" .IX Subsection "Utilizing other plugins" You can use the \f(CW\*(C`find_plugin\*(C'\fR to locate other plugins loaded by the user, in order to use them, or their information, directly: .PP .Vb 4 \& # 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(\*(AqDancer2::Plugin::Foo\*(Aq) \& or $self\->dsl\->send_error(\*(AqCould not find Foo\*(Aq); \& \& return $foo\->foo_keyword(...); \& } .Ve .SS "Hooks" .IX Subsection "Hooks" New plugin hooks are declared via \f(CW\*(C`plugin_hooks\*(C'\fR. .PP .Vb 1 \& plugin_hooks \*(Aqmy_hook\*(Aq, \*(Aqmy_other_hook\*(Aq; .Ve .PP Hooks are prefixed with \f(CW\*(C`plugin.plugin_name\*(C'\fR. So the plugin \&\f(CW\*(C`my_hook\*(C'\fR coming from the plugin \f(CW\*(C`Dancer2::Plugin::MyPlugin\*(C'\fR will have the hook name \&\f(CW\*(C`plugin.myplugin.my_hook\*(C'\fR. .PP Hooks are executed within the plugin by calling them via the associated \fIapp\fR. .PP .Vb 1 \& $plugin\->execute_plugin_hook( \*(Aqmy_hook\*(Aq ); .Ve .PP You can also call any other hook if you provide the full name using the \&\f(CW\*(C`execute_hook\*(C'\fR method: .PP .Vb 1 \& $plugin\->app\->execute_hook( \*(Aqcore.app.route_exception\*(Aq ); .Ve .PP Or using their alias: .PP .Vb 1 \& $plugin\->app\->execute_hook( \*(Aqon_route_exception\*(Aq ); .Ve .PP \&\fBNote:\fR If your plugin consumes a plugin that declares any hooks, those hooks are added to your application, even though \s-1DSL\s0 is not. .SS "Writing Test Gotchas" .IX Subsection "Writing Test Gotchas" \fIConstructor for Dancer2::Plugin::Foo has been inlined and cannot be updated\fR .IX Subsection "Constructor for Dancer2::Plugin::Foo has been inlined and cannot be updated" .PP You'll usually get this one because you are defining both the plugin and app in your test file, and the runtime creation of Moo's attributes happens after the compile-time import voodoo dance. .PP To get around this nightmare, wrap your plugin definition in a \f(CW\*(C`BEGIN\*(C'\fR block. .PP .Vb 2 \& BEGIN { \& package Dancer2::Plugin::Foo; \& \& use Dancer2::Plugin; \& \& has bar => ( \& is => \*(Aqro\*(Aq, \& from_config => 1, \& ); \& \& plugin_keywords qw/ bar /; \& \& } \& \& { \& package MyApp; \& \& use Dancer2; \& use Dancer2::Plugin::Foo; \& \& bar(); \& } .Ve .PP \fIYou cannot overwrite a locally defined method (bar) with a reader\fR .IX Subsection "You cannot overwrite a locally defined method (bar) with a reader" .PP If you set an object attribute of your plugin to be a keyword as well, you need to call \f(CW\*(C`plugin_keywords\*(C'\fR after the attribute definition. .PP .Vb 1 \& package Dancer2::Plugin::Foo; \& \& use Dancer2::Plugin; \& \& has bar => ( \& is => \*(Aqro\*(Aq, \& ); \& \& plugin_keywords \*(Aqbar\*(Aq; .Ve .SH "AUTHOR" .IX Header "AUTHOR" Dancer Core Developers .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is copyright (c) 2023 by Alexis Sukrieh. .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.