Dist::Zilla::Plugin::Hook::Manual(3pm)

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( 'StartingX' );
        . = # Xarbitrary Perl codeX
        . = X

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 ]. If "Dist::Zilla" does not provide argument, the variable will be set to "undef".

$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( X );

  "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( X );
        . = $self->abort_if_error( X );
        . = $self->abort( X );

  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 errX 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,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 X 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, butX this is errX 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( X );

Making errors in external commands fatal:

    . = use autodie ':all';
    . = system( X );

Making errors in Perl code non-fatal:

    . = use Try::Tiny;
    . = try { X };

Checking trial status:

    . = if ( $dist->is_trial ) { X };

Checking release build:

    . = if ( $ENV{ DZIL_RELEASING } ) { X };

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( X, $dist->abstract, X, $dist->license->name, X );

BTW, there are two minor (at the first look) "Hook" features:

1.  Arguments provided by "Dist::Zilla" are passes to the hook.

2.  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 X 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. <https://github.com/plicease/Dist-Zilla-Plugin-Test-Version/issues/5> 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};}}

ErrX 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 X "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) X 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.