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

NAME

File::AddInc - a reliable shorthand of use lib dirname($FindBin::Bin) for Modulino

SYNOPSIS

Assume you have a Modulino at $DIR/lib/MyApp.pm, and you want to use $DIR/lib/MyApp/Util.pm from it. Then:

    #!/usr/bin/env perl
    package MyApp;

    # This manipulates @INC for you!
    use File::AddInc;

    # So perl can find MyApp/Util.pm from the same module tree correctly.
    use MyApp::Util;

    ...

You can use File::AddInc to add $DIR/lib to @INC.

DESCRIPTION

File::AddInc manipulates @INC for Modulino (a module which is also runnable as a command). If you don't know much about the usefulness of Modulino, See these fine articles [1] [2].

Unfortunately, there is an annoying complexity to write Modulino: @INC manipulation. Generally, it is responsible for top-level scripts (*.pl, *.psgi) to manipulate @INC to be able to load all modules correctly. But in programming with Modulino, the Modulino itself is the top-level. To run such Modulino, you must give -Mlib to perl like below:

   perl -Mlib=$PWD ModX.pm ...

Above is disappointingly long, especially for Perl newbies. Instead, imagine if it can be called as a command file like ./ModX.pm, like the following:

  ./ModX.pm ...

With the above, they can use shell's filename completion and can run it less than a second. To achieve above, you usually need to add following BEGIN {} block to every Modulinos. (Note. You may want to name your Modulino like ModX::SomeCategory::SomeFunc):

  package ModX;
  ...
  BEGIN {
    my ($pack) = __PACKAGE__;
    my $libdir = $FindBin::RealBin;
    my $depth = (split "::", $pack) - 1;
    $libdir = dirname($libdir) while --$depth >= 0;
    require lib;
    lib->import($libdir);
  }

With File::AddInc, you can replace the above block with one line:

  use File::AddInc;

Conceptually, this module locates the root of lib directory through the following steps.

  1. Inspect __FILE__ (using caller()).

  2. Resolve symbolic links.

  3. Trim __PACKAGE__ part from it.

Then adds it to @INC.

Also, File::AddInc can be used to find the library's root directory reliably. FindBin is enough to manipulate @INC but not work well to locate something other than use-ed Perl modules. For example, assume you have a Modulino $DIR/lib/ModY.pm, and it uses some assets under $DIR/assets. You may write ModY with FindBin like following:

  package ModY;
  ...
  use lib (my $app_dir = dirname(FindBin::RealBin));

  our $assets_dir = "$app_dir/assets";

Unfortunately, the above code doesn't work as expected, because FindBin relies on $0 and varies what top-level program uses this ModY. In such a case, we should use __FILE__ instead of $0.

  package ModY;
  ...
  use lib (my $app_dir = dirname(File::Spec->rel2abs(__FILE__)));

  our $assets_dir = "$app_dir/assets";

Unfortunately again, this won't work if ModY.pm is symlinked to somewhere. With File::AddInc, you can rewrite it and can handle symlinks correctly:

  package ModY;
  ...
  use File::AddInc;
  my $app_dir = dirname(File::AddInc->libdir);

  our $assets_dir = "$app_dir/assets";

SUB-PRAGMAS

If you give some arguments to this module, it will treat them as subpragmas. This module invokes corresponding class methods for each subpragmas as the specified order. You can specify any number of subpragmas. If you give no subpragmas, a subpragma -file_inc is assumed. There are three forms of subpragmas in this module. That is -PRAGMA, [PRAGMA => @ARGS] and qw($var).

For example, following code:

  use File::AddInc -file_inc
   , [libdir_var => qw($libdir)]
   , qw($libdir2);

is a shorthand of below:

  BEGIN {
    require File::AddInc;

    my $opts = File::AddInc->Opts->new(caller => [caller]);

    File::AddInc->declare_file_inc($opts);

    File::AddInc->declare_libdir_var($opts, qw($libdir));

    File::AddInc->declare_libdir_var($opts, qw($libdir2));
  }

-file_inc

This finds libdir from caller and add it to @INC by "add_inc_if_necessary". This is the default behavior of this module. In other words,

  use File::AddInc;

is a shorthand form of below:

  use File::AddInc -file_inc;

-local_lib

This also adds $DIR/local/lib/perl5 to @INC (assumes your module is under $DIR/lib). This subpragma is now implemented in "these_libdirs" subpragma. In other words,

  use File::AddInc -local_lib;

is a shorthand form of below:

  use File::AddInc [these_libdirs => '', [dirname => "local/lib/perl5"]];

qw($var)

This finds libdir from caller and set it to given scalar variable. This subpragma is now implemented in "libdir_var" subpragma. In other words,

  use File::AddInc qw($foo);

is a shorthand form of below:

  use File::AddInc [libdir_var => qw($foo)];

[libdir_var => qw($libdir)]

This finds libdir from caller and set it to given scalar variable.

  use File::AddInc [libdir_var => qw($foo)];

is an equivalent of the folloing:

  use File::AddInc ();
  our $foo; BEGIN { $foo = File::AddInc->libdir };

[these_libdirs => @dirSpec]

This finds libdir from caller, generate a list of directories from given @dirSpec and prepend them to @INC by "add_inc_if_necessary".

For example, following code:

  use File::AddInc [these_libdirs => 'etc', '', [dirname => "local/lib/perl5"]];

adds $libdir/etc, $libdir and dirname($libdir)."/local/lib/perl5") to @INC.

Each item of @dirSpec can be one of following two forms:

  • STRING

    In this case, $libdir."/STRING" will be added.

  • [dirname => STRING]

    In this case, dirname($libdir)."/STRING" will be added.

CLASS METHODS

->libdir($PACKNAME, $FILEPATH)

Trims $PACKNAME portion from $FILEPATH. When arguments are omitted, results from caller() is used.

  my $libdir = File::AddInc->libdir('MyApp::Foobar', "/somewhere/lib/MyApp/Foobar.pm");
  # $libdir == "/somewhere/lib"

  my $libdir = File::AddInc->libdir(caller);

  my $libdir = File::AddInc->libdir;

->add_inc_if_necessary(@libdir)

This method prepends @libdir to @INC unless it is already listed in there. Note: this comparison is done through exact match.

MISC

How to inherit and extend

You can inherit this module to implement custom @INC modifier. For example, you can write your own exporter to invoke declare_these_libdirs to give traditional pragma usage like following:

  use MyExporter 'etc', '', 'perl5';

Such MyExporter.pm could be written like folloing:

  package MyExporter;
  use strict;
  use warnings;
  use parent qw/File::AddInc/;
  
  sub import {
    my ($pack, @args) = @_;
  
    my $opts = $pack->Opts->new(caller => [caller]);
  
    $pack->declare_these_libdirs($opts, @args);
  }
  
  1;

Note for MOP4Import users

This module does *NOT* rely on MOP4Import::Declare but designed to work well with it. Actually, this module provides declare_file_inc method. So, you can inherit 'File::AddInc' to reuse this pragma.

  package MyExporter;
  use MOP4Import::Declare -as_base, [parent => 'File::AddInc'];

Then you can use -file_inc pragma like following:

  use MyExporter -file_inc;

CAVEATS

Since this module compares __FILE__ with __PACKAGE__ in a case sensitive manner, it may not work well with modules which rely on case insensitive filesystems.

SEE ALSO

FindBin, lib, rlib, blib

LICENSE

Copyright (C) Kobayasi, Hiroaki.

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

AUTHOR

Kobayasi, Hiroaki <buribullet@gmail.com>