NAME

Require::HookChain - Chainable require hooks

VERSION

This document describes version 0.015 of Require::HookChain (from Perl distribution Require-HookChain), released on 2023-07-23.

SYNOPSIS

Say you want to create a require hook to prepend some code to the module source code that is loaded. In your hook source, in Require/HookChain/munge/prepend.pm:

package Require::HookChain::munge::prepend;

sub new {

    # our hook accepts one argument: preamble (the string to be prepended)
    my ($class, $preamble) = @_;

    bless { preamble => $preamble }, $class;
}

sub Require::HookChain::munge::prepend::INC {

    # instead a filename like a reguler hook, an hook's INC is called by
    # Require::HookChain's main INC and will be passed $r stash
    my ($self, $r) = @_;

    # safety, in case we are not called by Require::HookChain
    return () unless ref $r;

    my $src = $r->src;

    # we only munge source code, when source code has not been loaded by other
    # hooks, we decline.
    return unless defined $src;

    $src = "$self->{preamble};\n$src";
    $r->src($src);
}

1;

In a code to use this hook:

use Require::HookChain -end=>1, 'munge::prepend' => 'use strict';
use Foo::Bar; # Foo/Bar.pm will be loaded with added 'use strict;' at the start

Install other hooks, but put it at the end of @INC instead of at the beginning:

use Require::HookChain -end=>1, 'munge::append' => 'some code';
use Require::HookChain 'log::stderr'; # log each loading of module to stderr

DESCRIPTION

This module lets you create chainable require hooks. As one already understands, Perl lets you put a coderef or object in @INC. In the case of object, its INC method will be called by Perl:

package My::INCHandler;
sub new { ... }
sub My::INCHandler::INC {
    my ($self, $filename) = @_;
    ...
}

The method is passed itself then filename (which is what is passed to require()) and is expected to return nothing or a list of up to four values: a scalar reference containing source code, filehandle, reference to subroutine, optional state for subroutine (more information can be read from the perlfunc manpage). As soon as the first hook in @INC returns non-empty value then the search for source code is stopped.

With Require::HookChain, you can put multiple hooks in @INC that all get executed. When use'd, Require::HookChain will install its own hook at the beginning of @INC which will search for source code in @INC as well as execute INC method of all the other hooks which are instances of Require::HookChain::* class. Instead of filename, the method is passed a Require::HookChain::r object ($r). The method can do things on $r, for example retrieve source code via $r->src or modify source code via $r->src($new_content). After the method returns, the next Require::HookChain::* hook is executed, and so on. The final source code will be retrieved from $r->src and returned for Perl.

This lets one chainable hook munge the result of the previous chainable hook.

To create your own chainable require hook, see example in "SYNOPSIS". First you create a module under the Require::HookChain::* namespace, then create a constructor as well as INC handler.

Import options

Options must be specified at the beginning, before specifying

  • -end

    Bool. If set to true, then hooks will be put at the end of @INC instead of at the beginning (after Require::HookChain's own hook). Regardless, Require::HookChain's own hook will be put at the beginning to allow executing all the other hooks.

  • -debug

    Bool. If set to true, then debug messages will be printed to stderr.

Hook ordering

The order of execution of hooks by Require::HookChain is by their order in @INC, so you should set the ordering yourself by way of the (reverse) ordering of use Require::HookChain statements. Each time you do this:

use Require::HookChain 'hook1';

then Require::HookChain will (re)install its own hook to the beginning of @INC, then insert hook1 as the second element in @INC. Then when you load another hook:

use Require::HookChain 'hook2';

then Require::HookChain will (re)install its own hook to the beginning of @INC, then insert hook2 as the second element in @INC, while hook1 will be at the third element of @INC. So the order of hook execution will be: hook2, hook1. When another hook, hook3, is loaded afterwards, the order of execution will be hook3, hook2, hook1.

Some hooks should be loaded at the end of other hooks (and sources), e.g. debug::dump_source::stderr, so you should install such hooks using something like:

use Require::HookChain -end=>1, 'hook4';

in which case Require::HookChain will again (re)install its own hook to the beginning of @INC, then insert hook4 as the last element in @INC. The order of execution of hooks will then be: hook3, hook2, hook1, hook4. If you install another hook at the end:

use Require::HookChain -end=>1, 'hook5';

then the order of execution of hooks will then be: hook3, hook2, hook1, hook4, hook5.

Subnamespace organization

  • Require::HookChain::debug::

    Hooks that do debugging-related stuffs. See also: log:: subnamespace, timestamp:: subnamespace.

  • Require::HookChain::log::

    Hooks that add logging to module loading process. See also: debug:: subnamespace.

  • Require::HookChain::munge::

    Hooks that modify source code.

  • Require::HookChain::postcheck::

    Hooks that perform checks after the source code is loaded (eval-ed). See also precheck:: subnamespace.

  • Require::HookChain::precheck::

    Hooks that perform checks before the source code is loaded (eval-ed). See also postcheck:: subnamespace.

  • Require::HookChain::source::

    Hooks that allow loading module source from alternative sources.

  • Require::HookChain::test::

    Testing-related, particularly testing the Require::HookCHain hook module itself.

  • Require::HookChain::timestamp::

    Hooks that add timestamps during module loading process.

Require::HookChain::r OBJECT

Methods

filename

Usage:

my $filename = $r->filename;

src

Usage:

my $src = $r->src;
$r->src($new_src);

Get or set source code content. Will return undef if source code has not been found or set.

FAQ

Loading a hook does nothing!

Make sure you use a hook this way:

use Require::HookChain 'hookname'; # correct

instead of:

use Require::HookChain::hookname; # INCORRECT, this does not install the hook to @INC

The order of execution of hooks is incorrect!

You control the ordering by putting the hooks in @INC in your preferred order. See "Hook ordering" for more details.

HOMEPAGE

Please visit the project's homepage at https://metacpan.org/release/Require-HookChain.

SOURCE

Source repository is at https://github.com/perlancar/perl-Require-HookChain.

SEE ALSO

RHC for convenience of using on the command-line or one-liners.

Require::Hook is an older framework and is superseded by Require::HookChain.q

AUTHOR

perlancar <perlancar@cpan.org>

CONTRIBUTING

To contribute, you can send patches by email/via RT, or send pull requests on GitHub.

Most of the time, you don't need to build the distribution yourself. You can simply modify the code, then test via:

% prove -l

If you want to build the distribution (e.g. to try to install it locally on your system), you can install Dist::Zilla, Dist::Zilla::PluginBundle::Author::PERLANCAR, Pod::Weaver::PluginBundle::Author::PERLANCAR, and sometimes one or two other Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond that are considered a bug and can be reported to me.

COPYRIGHT AND LICENSE

This software is copyright (c) 2023, 2022, 2020, 2017 by perlancar <perlancar@cpan.org>.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

BUGS

Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Require-HookChain

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.