NAME
Dist::Zilla::Plugin::Hook::Manual - Hook
plugin user manual
VERSION
Version v0.8.4, released on 2018-03-15 21:44 UTC.
WHAT?
Dist-Zilla-Plugin-Hook
(or just Hook
) is a set of Dist-Zilla
plugins. Every plugin executes Perl code inlined into dist.ini at particular stage of build process.
This is Hook
user manual. Read this if you want to write Dist::Zilla
plugin directly in dist.ini.
If you are going to hack or extend Dist-Zilla-Plugin-Hook
, read the Dist::Zilla::Role::Hooker
module documentation. General topics like getting source, building, installing, bug reporting and some others are covered in the README.
SYNOPSIS
In your dist.ini:
[Hook/prologue]
. = # Code to be executed before every hook.
. = use autodie ':all';
. = use Path::Tiny;
[Hook::Role]
. = # Code of your inline plugin:
. = $self->log( 'Starting…' );
. = # …arbitrary Perl code…
. = …
where Role is one of Hook
submodules/Dist::Zilla
roles, like BeforeBuild
, AfterBuild
, FileGatherer
, MetaProvider
etc. See complete list of supported roles in "List of Modules".
DESCRIPTION
Hook
is a set of plugins, like Hook::BeforeBuild
and Hook::AfterBuild
.
Every Hook
plugin (except Hook::Init
, which is a bit special, see below) consumes a role with the same name, and implements the method required by the consumed role. When Dist::Zilla
invokes the method, it executes the code specified in the plugin's section of dist.ini.
An example: Hook::BeforeBuild
plugin consumes BeforeBuild
role, this role requires before_build
method, which is implemented by the plugin. When Dist::Zilla
invokes Hook::BeforeBuild
's before_build
method, it executes the code from the plugin's section of dist.ini, e. g.:
name = Assa
version = 0.001
[Hook::BeforeBuild]
. = $self->log( [ "Building v%s", $dist->version ] );
...
and Perl code inlined into [Hook::BeforeBuild]
section of dist.ini prints message "Building v0.001" to the log. Such inlined Perl code is called "hook" below.
The same for all other Hook
plugins. Only Hook::Init
plugin is a bit special: it implements BUILD
method. It has two subsequences. First: there is no need in consuming a role to implement BUILD
method (and actually there is no role Dist::Zilla::Role::Init
). Second, more important: BUILD
method is called at very early stage of the build, immediately after reading [Hook::Init]
section of dist.ini. This is useful in some circumstances.
Predefined Variables
Inlined Perl code can use following predefined variables:
@_
-
Arguments of the method, as provided by
Dist::Zilla
. Self-reference is already shifted to$self
(but the first argument is not)! $arg
-
The first argument of the method, the same as
$_[ 0 ]
. IfDist::Zilla
does not provide argument, the variable will be set toundef
. $plugin
$self
-
Reference to the plugin object executing the code.
$dist
$zilla
-
Reference to
Dist::Zilla
object, the same as$self->zilla
.
$self
and $zilla
are "standard" variables frequently used in plugin source code. $plugin
and $dist
variables are defined for conformance with template processing plugins (GenerateFile
, Templates
, TemplateFiles
, MetaResources::Template
, etc.). $arg
is defined for convenience: in many cases Dist::Zilla
passes the only argument to the method (which usually is a HashRef
).
Arguments and Return Value
As described in the previous section, arguments provided by Dist::Zilla
are passed to the hook (through $self
, $arg
, and @_
variables).
Return value of the hook code is not ignored but passed back to Dist::Zilla
. Dist::Zilla
, in turn, often ignores it, but sometimes return value is important, for example, for "provider" plugins: Hook::VersionProvider
, Hook::MetaProvider
, etc. See documentation on corresponding roles (e. g. Dist::Zilla::Role::VersionProvider, Dist::Zilla::Role::MetaProvider, etc) for description of expected method result.
Passing arguments and return values actually means you can write your own Dist::Zilla
plugin which code is not in an external .pm
file but inlined directly to dist.ini. Of course, such approach is limited. For example, "inline plugin" cannot define attributes and methods. Anyway the approach is quite convenient for small hacks and prototyping which do not require much coding. See "EXAMPLES" section.
Prologue
If dist.ini contains section [Hook/prologue]
, the code from this section is executed before every hook. All the predefined variables are available in prologue code too.
Prologue may be used for loading frequently used modules, or for debugging:
[Hook/prologue]
. = use autodie ':all';
. = use Path::Tiny;
. = use IPC::System::Simple qw{ capture };
. = $self->log_debug( 'begins' );
[Hook::BeforeBuild]
. = # No need in "use autodie" because
. = # it is specified in prologue.
. = system( … );
ErrorLogger
Role
Every Hook
plugin executes Perl code with help from the Hooker
role. The latter uses ErrorLogger
role internally. As a side effect, ErrorLogger
methods are also available to use in hooks:
[Hook::Role]
. = $self->log_error( … );
. = $self->abort_if_error( … );
. = $self->abort( … );
Multiple Hooks of the Same Role
Use explicit plugin names if you want to have multiple hooks of the same role, e. g.:
[Hook::AfterRelease/bump version]
. = my $version = Perl::Version->new( $dist->version );
. = $version->inc_alpha;
. = path( $dist->root )->child( 'VERSION' )->spew( $version );
[Hook::AfterRelease/post-release commit]
. = system( qw{ hg commit -m Post-release VERSION Changes } );
[Hook::AfterRelease/push]
. = system( qw{ hg push } );
[Hook::AfterRelease/clean]
. = $zilla->clean();
List of Modules
This is the complete list of Hook
modules/roles and methods:
--------------------- + ----------------------
Module/Role | Method
--------------------- + ----------------------
AfterBuild | after_build
AfterMint | after_mint
AfterRelease | after_release
BeforeArchive | before_archive
BeforeBuild | before_build
BeforeMint | before_mint
BeforeRelease | before_release
FileGatherer | gather_files
FileMunger | munge_files
FilePruner | prune_files
Init | BUILD
InstallTool | setup_installer
LicenseProvider | provide_license
MetaProvider | metadata
ModuleMaker | make_module
NameProvider | provide_name
PrereqSource | register_prereqs
ReleaseStatusProvider | provide_release_status
Releaser | release
VersionProvider | provide_version
--------------------- + ----------------------
OPTIONS
.
Yes, the only option recognized by Hook
modules is .
(dot).
This is multi-value option, i. e. it may be specified multiple time. Each value is a distinct line of Perl code, e. g.:
. = if ( $dist->is_trial ) {
. = $self->log( 'Building trial version' );
. = };
Beware of caveats, see "dist.ini Parsing".
CAVEATS
dist.ini Parsing
Before code reaches a Hook
plugin, it is parsed by Dist::Zilla
config file reader (probably, by Config::INI::Reader
). Config file reader seems to strip leading and trailing spaces from each value, and treat semicolon preceded by a space as a comment starter. Usually it is not a problem, just put semicolon immediately after statement:
. = foo(); bar(); # Ok
. = foo() ; bar() ; # NOT OK: bar will not be called.
Note that semicolon starts a dist.ini comment even within Perl string:
. = $str = "one; two"; # Ok
. = $str = "one ; two"; # NOT OK
And be careful with multi-line strings:
. = $str = "first line
. = indented line"; # Leading spaces will be lost.
WHY?
There is Dist::Zilla::Plugin::Run
on CPAN which allows to run Perl code from within dist.ini, why I wrote one more? Let us consider two examples.
The first one executes external commands:
$cat dist.ini
name = RunShell
abstract = RunShell demo
version = 0.001_001
[Run::BeforeBuild]
run = echo "1. begin"
run_if_release = echo "2. release"
run_no_release = echo "3. not release"
run_if_trial = echo "4. trial"
run_no_trial = echo "5. not trial"
run = echo "6. end"
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[FakeRelease]
$ dzil build
[Run::BeforeBuild] executing: echo "1. begin"
[Run::BeforeBuild] 1. begin
[Run::BeforeBuild] executing: echo "6. end"
[Run::BeforeBuild] 6. end
[Run::BeforeBuild] executing: echo "5. not trial"
[Run::BeforeBuild] 5. not trial
[Run::BeforeBuild] executing: echo "3. not release"
[Run::BeforeBuild] 3. not release
[DZ] beginning to build RunShell
[DZ] writing RunShell in RunShell-0.001_001
[DZ] building archive with Archive::Tar::Wrapper
[DZ] writing archive to RunShell-0.001_001-TRIAL.tar.gz
[DZ] built in RunShell-0.001_001
Execution order is err… non-linear. Of course there is an explanation why command were executed in this particular order, but when you are looking at dist.ini it is not obvious. (It is also unclear why Run
consider the build is not trial, but it may be just a bug.)
Another example executes Perl code:
$cat dist.ini
name = RunPerl
abstract = RunPerl demo
version = 0.001_001
[Run::BeforeBuild]
eval = my $self = shift( @_ );
eval = my $dist = $self->zilla;
eval = $self->log( [ '%s v%s', $dist->name, $dist->version ] );
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[FakeRelease]
$ dzil build
[Run::BeforeBuild] evaluating: my $self = shift( @_ );
[Run::BeforeBuild] my $dist = $self->zilla;
[Run::BeforeBuild] $self->log( [ '0.001_001 v', $dist->name, $dist->version ] );
[Run::BeforeBuild] 0.001_001 v
[DZ] beginning to build RunPerl
[DZ] writing RunPerl in RunPerl-0.001_001
[DZ] building archive with Archive::Tar::Wrapper
[DZ] writing archive to RunPerl-0.001_001-TRIAL.tar.gz
[DZ] built in RunPerl-0.001_001
Look at the last message from Run::BeforeBuild
plugin. Surprising? Where is the distribution name? Why is the character "v" printed after version number? Ah! %s
is a special conversion specifier which was replaced with "something retained for backward compatibility". There is a bunch of other conversion specifiers: %a
, %d
, %n
,%p
, %t
, %v
, %x
,… That effectively means I cannot use printf-like functions and hashes, because every percent will be replaced with something or cause error "unknown conversion".
Ok, I can. There is (undocumented!) method to avoid it — every percent sign should be doubled:
eval = $self->log( [ '%%s v%%s', $dist->name, $dist->version ] );
or
eval = my %%meta = %%{ $dist->distmeta };
It is simple, but… this is err… not quite Perl. I cannot just cut-n-paste code from a plugin to dist.ini and back.
Let me cite a part of "Philosophy" section of the great Text::Template
module:
When people make a template module like this one, they almost always start by inventing a special syntax for substitutions. For example, they build it so that a string like %%VAR%%
is replaced with the value of $VAR
. Then they realize the need extra formatting, so they put in some special syntax for formatting. Then they need a loop, so they invent a loop syntax. Pretty soon they have a new little template language.
This approach has two problems: First, their little language is crippled. If you need to do something the author hasn't thought of, you lose. Second: Who wants to learn another language? You already know Perl, so why not use it?
Look: Run
plugin introduced a bunch of dist.ini options: run_if_trial
, run_no_trial
(BTW, why not run_if_not_trial
?), run_if_release
, run_no_release
, eval
, censor_commands
, fatal_errors
, quiet
; a bunch of "conversion specifiers": %a
, %d
, %n
, %p
, %v
, %t
, %x
, %s
; and bunch of poorly documented rules. It's "a little crippled language", isn't it?
Compared to Run
, Hook
is designed to be minimalistic: It provides only one option, and it executes only Perl. Of course, when writing a hook you have to keep in mind many rules, but these are well documented Perl rules and (not so well) Dist::Zilla
rules, not rules introduced by Hook
.
All Run
features can be easily implemented with hooks in Perl, for example:
Running external commands:
. = system( … );
Making errors in external commands fatal:
. = use autodie ':all';
. = system( … );
Making errors in Perl code non-fatal:
. = use Try::Tiny;
. = try { … };
Checking trial status:
. = if ( $dist->is_trial ) { … };
Checking release build:
. = if ( $ENV{ DZIL_RELEASING } ) { … };
The code is a little bit longer than Run
counterparts, but it is well-known full-featured Perl.
What if you need to pass to an external command something the Run
authors have not thought of? For example, abstract or licence name. There are no conversion specifiers for it, so you lose. But with Hook
it is trivial:
. = system( …, $dist->abstract, …, $dist->license->name, … );
BTW, there are two minor (at the first look) Hook
features:
Arguments provided by
Dist::Zilla
are passes to the hook.Hook return value is passed back to
Dist::Zilla
.
These bring a new quality: with Hook
you can write inline plugins. For example, a plugin which reads distribution version from an external file:
[Hook::VersionProvider]
. = use Path::Tiny; path( 'VERSION' )->slurp;
(Actually, every hook is an inline plugin.) See more in "EXAMPLES" in Dist::Zilla::Plugin::Hook::Manual.
EXAMPLES
Examples below are focused on using Hook
, so dist.ini is a primary file in all the examples, and sometimes is the only file of an example. Example module contains single line package Assa; 1;
and generated on-the-fly with GenerateFile
plugin.
Description Meta Resource
Distribution meta information contains such items as name, version, abstract and many others. All named items are written to META.json (and maybe to META.yml) automatically, all you need is just using MetaJSON
and/or MetaYAML
plugin(s) in your dist.ini file.
Meta information may also include description — a longer, more complete description of the distribution. However, Dist::Zilla
does not provide option to specify description. It could be easily fixed with Hook
, though.
dist.ini file:
name = Description
abstract = Hook demo: Set "description" meta info
version = v0.0.1
[Hook::MetaProvider/description] ; <<<=== Look at this
; MetaProvider's metadata method must return HashRef (or undef).
; Multiple MetaProviders are allowed. Metainfo received from
; all providers will be merged by Dist::Zilla. This
; MetaProvider provides only description.
; See Dist::Zilla::Role::MetaProvider.
. = { description =>
. = "This is not short one-line abstract,
. = but more detailed description,
. = which spans several lines."
. = }
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[MetaJSON]
[FakeRelease]
Test::Version
adaptive strictness
Test::Version
is a great plugin. It creates a test which checks modules in distribution: every module must have $VERSION
variable defined, and its value must be a valid version string. There are two notion of "validity": lax and strict. (See "Regular Expressions for Version Parsing" in version::Internals for definitions of lax and strict).
I want to use strict check:
[Test::Version]
is_strict = 1
Unfortunately, this does not work for trial releases: any trial release definitely fails the test, because strict check does not allow underscore in version string. Thus, before every trial release I have to reset is_strict
option to zero, and return it back to one after release. This is boring and error-prone. I want to have "adaptive strictness": use lax check in case of trial release and strict check otherwise.
Test::Version
maintainer Graham Ollis said: This is a good idea! I'll see if I can implement it. However, implementation may take some time. With a little help from Hook
, I can easily get achieve adaptive strictness right now.
dist.ini file:
name = AdaptiveTestVersion
abstract = Hook demo: Test::Version adaptive strictness
version = 0.001
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[Test::Version] ; <<<=== Look at this
is_strict = 0
[Hook::BeforeBuild/AdaptiveStrictness] ; <<<=== Look at this
. = my $tv = $zilla->plugin_named( 'Test::Version' );
. = $tv->{ is_strict } = $dist->is_trial ? '0' : '1';
[MetaJSON]
[FakeRelease]
Template Variables
In a distribution, I have to duplicate the same pieces of information again and again. For example, bug report email and web URLs should be written in [MetaResources]
section of dist.ini and in the documentation, like BUGS.pod.
With a help from Templates
plugin I can eliminate duplication. If BUGS.pod is a template, I can use email and web URLs defined in dist.ini, e. g.:
{{$dist->distmeta->{resources}->{bugtracker}->{mailto};}}
Err… This works but requires a lot of typing (so it is typo-prone), and looks ugly. With Hook
I can make it not only working, but also elegant. [Hook::Init]
section defines few variables in MY
package, which can be used in various templates, including documentation and meta resources.
dist.ini file:
name = TemplateVariables
abstract = Hook demo: Define variables for later use in templates.
version = v0.0.1
[Hook::Init/my vars] ; <<<=== Look at this
. = package MY;
. = our $name = $dist->name;
. = our $bt_mail = "mailto:bug-$name\@bt.example.org";
. = our $bt_web = "https://bt.example.org/display.html?name=$name";
; BTW, Hook::BeforeBuild cannot be used here: it works too late,
; MetaResources::Template will not see the variables.
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[GatherDir]
[PruneCruft]
[FileFinder::ByName/BUGS.pod] ; <<<=== Look at this
file = BUGS.pod
[Templates] ; <<<=== Look at this
templates = BUGS.pod
[MetaResources::Template] ; <<<=== Look at this
bugtracker.mailto = {{$MY::bt_mail}}
bugtracker.web = {{$MY::bt_web}}
license = {{$dist->license->url}}
[MetaJSON]
[FakeRelease]
BUGS.pod file:
=head2 Bugs
The quickest way to report a bug in C<{{$MY::name}}>
is by sending email to {{$MY::bt_mail}}.
Bug tracker can be used via
L<web interface|{{$MY::bt_web}}>.
Version Bumping
I want the version of my distribution is bumped automatically after each release, and automatically assigned version should be trial.
For example: If I released version v0.0.1
, the version of the next release should be v0.0.1.1
(see Version::Dotted::Semantic
). When I release v0.0.1.1
, the next should be v0.0.1.2
, the next one — v0.0.1.3
and so on. When I decide it is time to non-trial release, I will set the version to v0.0.2
manually, release it, and will have automatically bumped version v0.0.2.1
for the next release.
This is implemented with three plugins: Hook:VersionProvider
, Hook::ReleaseStatusProvider
, and Hook::AfterRelease
. The first one reads version from external file VERSION which contains only version and nothing more (ok, trailing whitespace is allowed) — it simplifies implementation, because there is no need in parsing dist.ini file. The second plugin lets Dist::Zilla
know release status (trial or stable). The third plugin bumps the version after release, and writes bumped version back to the VERSION file.
dist.ini file:
name = VersionBumping
abstract = Hook demo: Bump version after release
[Hook/prologue] ; <<<=== Look at this
. = use Version::Dotted::Semantic 'qv';
[Hook::VersionProvider] ; <<<=== Look at this
. = $zilla->root->child( 'VERSION' )->slurp =~ s{\s*\z}{}r;
[Hook::ReleaseStatusProvider] ; <<<=== Look at this
. = qv( $zilla->version )->is_trial ? "testing" : "stable";
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[MetaJSON]
[FakeRelease]
[Hook::AfterRelease/bump version] ; <<<=== Look at this
. = my $ver = qv( $dist->version )->bump( 3 );
. = $zilla->root->child( 'VERSION' )->spew( $ver );
. = $self->log( [ 'The next release will be %s', "$ver" ] );
VERSION file:
v0.0.1
Unwanted Dependencies
Data::Printer
is a great module, I often use it for debugging. However, sometimes I forget to remove
use DDP;
from the code and make a release with this unwanted dependency. Hook::BeforeRelease
checks the distribution does not have unwanted dependencies. If it does, release will be aborted.
dist.ini file:
name = UnwantedDependencies
abstract = Hook demo: Check the distro does not have unwanted dependencies
version = v0.0.1
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; use DDP; 1;
[AutoPrereqs]
[MetaJSON]
[Hook::BeforeRelease/unwanted deps] ; <<<=== Look at this
. = my @modules = qw{ DDP Data::Printer }; # Unwanted modules.
. = my $prereqs = $dist->distmeta->{ prereqs };
. = for my $m ( @modules ) {
. = for my $s ( qw{ configure develop runtime test } ) {
. = if ( exists( $prereqs->{ $s }->{ requires }->{ $m } ) ) {
. = $self->log_error( [ '%s found in %s prereqs', $m, $s ] );
. = };
. = };
. = };
. = $self->abort_if_error( 'unwanted dependencies found' );
[FakeRelease]
Let [=inc::Foo]
work in Perl v5.26+.
Starting from Perl v26.0, .
is not included into @INC
anymore. This breaks Dist::Zilla
syntax for plugins which are located in the distribution source tree, e. g.:
[=inc::Foo]
Being run with Perl v5.26+, dzil
complains:
Required plugin inc::Foo isn't installed.
Dist::Zilla::Plugin::lib
was created specially for workarounding this issue. (I said "workarounding" not "solving" because Dist::Zilla::Plugin::lib
does not help dzil authordeps --missing
to work.)
The same effect can be achieved with Hook::Init
one-liner.
dist.ini file:
name = NoDotInInc
abstract = Hook demo: Let [=inc::Foo] work in Perl v5.26+.
version = v0.0.1
[Hook::Init] ; <<<=== Look at this
. = use lib $zilla->root->absolute->stringify;
[=inc::Foo]
[MetaJSON]
[FakeRelease]
SEE ALSO
- Dist::Zilla
- Dist::Zilla::Plugin::Run
- Dist::Zilla::Role::Hooker
- Dist::Zilla::Role::ErrorLogger
- Dist::Zilla::Plugin::Hook
AUTHOR
Van de Bugger <van.de.bugger@gmail.com>
COPYRIGHT AND LICENSE
Copyright (C) 2015, 2016, 2018 Van de Bugger
License GPLv3+: The GNU General Public License version 3 or later <http://www.gnu.org/licenses/gpl-3.0.txt>.
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.