The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

lib::relative::to

DESCRIPTION

Add a path to @INC that is relative to something else

SYNOPSIS

Both of these will look up through the parent directories of the file that contains this code until it finds the root of a git repository, then add the 'lib' directory in that repository's root to @INC.

    use lib::relative::to
        GitRepository => qw(lib t/lib);

    use lib::relative::to
        ParentContaining => '.git/config' => qw(lib t/lib);

WHY?

I used to work with someone (hi Sam!) who would chdir all over the place while working on our product, and expected to be able to run tests no matter where he was in our repository.

Normal people, of course, stay in the repository root and invoke their tests thus:

    prove t/wibble/boing/frobnicate.t

and if that test file wanted to be able to load modules stored in a lib directory alongside t and from t/lib it would just say:

    use lib qw(t/lib lib);

But because of Sam, who liked to do this:

    cd t/wibble/boing
    prove frobnicate.t

We instead had to have nonsense like this:

    use lib::abs qw(../../../lib ../../lib);

which is just plain hideous. Not only is it ugly, it's hard to read (it's not immediately clear which directories are being included) and it's hard to write - did I get the right number of ../../? Did I remember to update the Morse code when I moved a file? Who knows! Hence the GitRepository plugin. And because I wanted to support Mercurial (see the HgRepository plugin) as well, I abstracted out most of the functionality.

Of course, I used to work with Sam, so this is too late to save my sanity, but writing it at least made me feel better.

METHODS

import

Takes numerous arguments, the first of which is the name of a plugin, the rest are arguments to that plugin. It will load the plugin (or die if it can't) and then pass the rest of the arguments to the plugin.

In general the argument list takes the form:

plugin_name
plugin_configuration
list_of_directories

and the plugin will use the plugin_argument to add list_of_directories to @INC. In the "SYNOPSIS" above you can see that ParentContaining and GitRepository are plugins, that .git/config is plugin configuration (the GitRepository plugin takes no configuration), and that in both cases we want to add lib and t/lib to @INC.

parent_dir

Class method, takes a file or directory name as its argument, returns the directory containing that object.

WRITING PLUGINS

You are encouraged to write your own plugins. I would appreciate, but do not require, that you tell me about your plugins.

You can upload your own plugins to the CPAN, or you can send them to me and I will include them in this distribution. The best way of sending them to me is via a Github pull request, but any other way of getting the files to me works. If you want your code to be included in this distribution you must include tests and appropriate fixtures.

NAMING

Plugin names must take the form lib::relative::to::YourPluginName.

The lib::relative::to::ZZZ::* namespace is reserved.

FUNCTIONS

Your plugin must implement a class method called _find, which will be called when your plugin has been loaded, and will have the remainder of the argument list passed to it. That is to say that when your plugin is invoked thus:

    use lib::relative::to YourPluginName => qw(foo bar baz);

your _find method will be called thus:

    lib::relative::to::YourPluginName->_find(qw(foo bar baz));

NB that your import method, if any, will not be called.

Your _find method should return a list of absolute paths to be added to @INC. You will probably find Cwd::abs_path and File::Spec useful. Both modules will have already been loaded so you don't need to use them yourself. You may also want to use /parent_dir - you can get access to it either by inheriting from lib::relative::to or by calling it directly:

    my $directory = lib::relative::to->parent_dir(...);

CONTEXT

$lib::relative::to::called_from will contain the absolute name of the file from which your plugin was invoked.

INHERITANCE

The most useful class to inherit from is probably going to be the ParentContaining plugin. Indeed, that is what the GitRepository and HgRepository plugins do. The source for the HgRepository plugin reads, in its entirety:

    package lib::relative::to::HgRepository;
    
    use strict;
    use warnings;
     
    use parent 'lib::relative::to::ParentContaining';
    
    sub _find {
        my($class, @args) = @_;
        $class->SUPER::_find('.hg/store', @args);
    }
    1;

BUGS

I only have access to Unix machines for development and debugging. There may be bugs lurking that affect users of exotic platforms like Amiga, Windows, and VMS. I welcome patches, preferably in the form of a pull request. Ideally any patches will be accompanied by tests, and those tests will either skip or pass on Unix.

AUTHOR, COPYRIGHT and LICENCE

Copyright 2020 David Cantrell <david@cantrell.org.uk>.

This software is free-as-in-speech as well as free-as-in-beer, and may be used, distributed, and modified under the terms of either the GNU General Public Licence version 2 or the Artistic Licence. It's up to you which one you use. The full text of the licences can be found in the files GPL2.txt and ARTISTIC.txt, respectively.

CONSPIRACY

This software is also free-as-in-mason.