NAME
lib::relative::to
DESCRIPTION
Add paths 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' and 't/lib' directories 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_configuration
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.