package above;

use strict;
use warnings;

use Cwd qw(getcwd);
use File::Spec qw();

our $VERSION = '0.03'; # No BumpVersion

sub import {
    my $package = shift;
    for (@_) {
        use_package($_);
    }
}

our %used_libs;
BEGIN {
    %used_libs = ($ENV{PERL_USED_ABOVE} ? (map { $_ => 1 } split(":", $ENV{PERL_USED_ABOVE})) : ());
    for my $path (keys %used_libs) {
        my $error = do {
            local $@;
            eval "use lib '$path';";
            $@;
        };
        die "Failed to use library path '$path' from the environment PERL_USED_ABOVE?: $error" if $error;
    }
};

sub _caller_use {
    my ($caller, $class) = @_;
    my $error = do {
        local $@;
        eval "package $caller; use $class";
        $@;
    };
    die $error if $error;
}

sub _dev {
    my $path = shift;
    return (stat($path))[0];
}

sub use_package {
    my $class  = shift;
    my $caller = (caller(1))[0];
    my $module = File::Spec->join(split(/::/, $class)) . '.pm';

    ## paths already found in %used_above have
    ## higher priority than paths based on cwd
    for my $path (keys %used_libs) {
        if (-e File::Spec->join($path, $module)) {
            _caller_use($caller, $class);
            return;
        }
    }

    my $xdev = $ENV{ABOVE_DISCOVERY_ACROSS_FILESYSTEM};
    my $cwd = getcwd();
    unless ($cwd) {
        die "cwd failed: $!";
    }
    my $dev = _dev($cwd);
    my $abort_crawl = sub {
        my @parts = @_;
        return 1 if (@parts == 0); # nothing left to try
        return 1 if (@parts == 1 && $parts[0] eq ''); # hit root dir
        my $path = File::Spec->join(@parts);
        return !($xdev || _dev($path) == $dev); # crossed device
    };
    my $found_module_at = sub {
        my $path = shift;
        return (-e File::Spec->join($path, $module));
    };

    my @parts = File::Spec->splitdir($cwd);
    my $path;
    do {
        $path = File::Spec->join(@parts);
        pop @parts;
    } until ($found_module_at->($path) || $abort_crawl->(@parts));

    if ($found_module_at->($path)) {
        while ($path =~ s:/[^/]+/\.\./:/:) { 1 } # simplify
        unless ($used_libs{$path}) {
            print STDERR "Using libraries at $path\n" unless $ENV{PERL_ABOVE_QUIET} or $ENV{COMP_LINE};
            my $error = do {
                local $@;
                eval "use lib '$path';";
                $@;
            };
            die $error if $error;
            $used_libs{$path} = 1;
            my $env_value = join(":", sort keys %used_libs);
            $ENV{PERL_USED_ABOVE} = $env_value;
        }
    }

    _caller_use($caller, $class);
};

1;

=pod

=head1 NAME

above - auto "use lib" when a module is in the tree of the PWD

=head1 SYNOPSIS

use above "My::Module";

=head1 DESCRIPTION

Used by the command-line wrappers for Command modules which are developer tools.

Do NOT use this in modules, or user applications.

Uses a module as though the cwd and each of its parent directories were at the beginnig of @INC.
If found in that path, the parent directory is kept as though by "use lib".

Set ABOVE_DISCOVERY_ACROSS_FILESYSTEM shell variable to true value to crawl past device boundaries.

=head1 EXAMPLES

# given
/home/me/perlsrc/My/Module.pm

# in
/home/me/perlsrc/My/Module/Some/Path/

# in myapp.pl:
use above "My::Module";

# does this ..if run anywhere under /home/me/perlsrc:
use lib '/home/me/perlsrc/'
use My::Module;

=head1 AUTHOR

Scott Smith
Nathaniel Nutter

=cut