NAME
Dist::Zilla::Plugin::Hook::Manual - Hook
user manual
VERSION
Version 0.006, released on 2015-08-11 20:12 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 role 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]
. = use autodie ':all';
. = use Path::Tiny;
[Hook::Role]
. = # Code of your inpline plugin:
. = $self->log( 'Starting…' );
. = # …arbitrary Perl code…
. = …
where Role is one of Dist-Zilla
roles, like BeforeBuild
, AfterBuild
, FileGatherer
, MetaProvider
etc. See complete list of supported roles in "DESCRIPTION".
DESCRIPTION
Dist-Zilla-Plugin-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: Plugin Hook::BeforeBuild
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::AfterBuild]
section of dist.ini, prints message "Building v0.001" to the log.
The same for all other Hook
plugins. Only Hook::Init
plugin is a bit special: it implements BUILD
method. It has two features. First: there is no need in consuming a role to implement BUILD
method (and there is no role Dist::Zilla::Role::Init
). Second, and 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. If
Dist::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
.
$plugin
and $dist
variables are defined for conformance with template processing plugins (GenerateFile
, Templates
, TemplateFiles
, MetaResources::Template
, etc.). $arg
is set for convenience: in many cases Dist::Zilla
passes the only argument to the method (which usually is a HashRef
).
Arguments and Return Value
Return value of the 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.
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 fixes which do not require much coding. See "EXAMPLES" section.
Prologue
If dist.ini contains section [Hook/prologue]
, the code from this section is executed as initial part of code of all Hook
s. 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 (by consuming) the Hooker
role. The latter uses ErrorLogger
role internally, but it also available to be used in hooks:
. = $self->log_error( … );
. = $self->abort_if_error( … );
. = $self->abort( … );
Multiple Hooks of the Same Type
Use explicit plugin names if you want to have multiple hooks of the same type, e. g.:
[Hook::AfterRelease/bump version]
. = my $version = Perl::Version->new( $dist->version );
. = $version->inc_alpha;
. = path( '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
NameProvider | provide_name
PrereqSource | register_prereqs
ReleaseStatusProvider | provide_release_status
VersionProvider | provide_version
--------------------- + ----------------------
OPTIONS
.
This is multi-value option, i. e. it may be specified multiple time. Each value is a separate line of Perl code, e. g.:
. = if ( $dist->is_trial ) {
. = $self->log( 'Trial version' );
. = };
Beware of caveats, see "dist.ini Parsing".
EXAMPLES
Examples below are focused on using Hook
, so dist.ini is a primary file in all the examples, and sometimes is only file. Perl 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 META.yml) automatically. All you need is just use 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 ability to specify description. It could be easily fixed with Hook
, though.
dist.ini file:
name = Description
abstract = Hook demo: Set "description" meta info
version = 0.001
[Hook::MetaProvider/description] ; <<<=== Look at this
; MetaProvider's metadata method must return HashRef (or undef).
; Multiple MetaProviders can coexist. 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]
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, (and so typo-prone) and looks ugly. With Hook
I can make it not just 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 = 0.001
[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 Handling
I want the version of my distribution is bumped automatically after each release, and automatically assigned version should be alpha (trial) one.
For example: if I released version "0.001", the version of the next release should be "0.001_01". When I release "0.001_01", the next should be "0.001_02", the next one — "0.001_03" and so on. When I decide it is time to non-trial release, I will reset the version to "0.002" manually, release it, and will have automatically bumped version "0.002_01".
The implementation is built with two plugins: Hook:VersionProvider
and Hook::AfterRelease
. The first one reads version from external file VERSION which contains only version and nothing more (ok, trailing newline is allowed) — it simplifies implementation, because there is no need in parsing dist.ini file. The second plugin bumps the version after release with help from Perl::Version
module.
dist.ini file:
name = VersionHandling
abstract = Hook demo: Bump version after release
[Hook/prologue] ; <<<=== Look at this
. = use Path::Tiny;
. = use Perl::Version;
[Hook::VersionProvider] ; <<<=== Look at this
. = path( 'VERSION' )->slurp =~ s{\s*\z}{}gr;
. = # ^ Strip trailing whitespace, if any.
[GenerateFile/Assa.pm]
filename = lib/Assa.pm
content = package Assa; 1;
[MetaJSON]
[FakeRelease]
[Hook::AfterRelease/bump version] ; <<<=== Look at this
. = my $ver = Perl::Version->new( $dist->version );
. = $ver->inc_alpha;
. = path( 'VERSION' )->spew( $ver );
. = $self->log( [ 'Next release will be %s', "$ver" ] );
VERSION file:
0.001
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 = 0.001
[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]
CAVEATS
dist.ini Parsing
Before code reaches a Hook
plugin, it is parsed by Dist::Zilla
config file reader (probably, 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 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 };
However, 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 rather minimalistic: It provides only one option, and it executes only Perl. All other Run
features can be easily implemented 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;
or
. = if ( $dist->is_trial ) { … };
Checking release build:
. = … if $ENV{ DZIL_RELEASING };
or
. = 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:
Hook
passes arguments provided byDist::Zilla
to the code.Hook
passes return value from the code back toDist::Zilla
.
which 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.
SEE ALSO
- Dist::Zilla
- Dist::Zilla::Plugin::Run
- Dist::Zilla::Role::Hooker
- Dist::Zilla::Role::ErrorLogger
- Dist::Zilla::Plugin::Hook
- Dist::Zilla::Plugin::Hook::ReadMe
AUTHOR
Van de Bugger <van.de.bugger@gmail.com>
COPYRIGHT AND LICENSE
Copyright © 2015 Van de Bugger
This file is part of perl-Dist-Zilla-Plugin-Hook.
perl-Dist-Zilla-Plugin-Hook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
perl-Dist-Zilla-Plugin-Hook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with perl-Dist-Zilla-Plugin-Hook. If not, see <http://www.gnu.org/licenses/>.