#!/usr/bin/perl 

use strict;
use warnings;

our $VERSION = '1.02';

# Removing this dependency
# use Text::Trim qw( trim );
use List::Util qw( max );
use Module::Util qw( :all );

use Pod::Usage;
use Getopt::Long qw( :config posix_default bundling );

=head1 NAME

pm_which - find installed modules

=head1 SYNOPSIS

    pm_which [ options ] module(s)

    Returns the path to the given module(s)

=head2 OPTIONS
  
    -q, --quiet     Just print paths
    -p, --paths     Just convert the module name into a relative path
    -a, --all       Print all paths, not just the first one found
    -n, --namespace Print all modules in the given namespace
    -m              Only print module names, not paths
    -V              Show module version
    -I libpath      Add a path to search (like perl -I)
    -d, --dump      Dump paths that would be searched (@INC by default)
    -h, --help      Print this message
    -v, --version   Print version information
    -               Read modules from stdin, one per line
  
=cut

our($quiet, $all, $namespace, $name_only, $paths, $dump, $stdin, $version);
our @search = @INC;

GetOptions(
    'q|quiet'       => \$quiet,
    'a|all'         => \$all,
    'p|paths'       => \$paths,
    'd|dump'        => \$dump,
    'n|namespace'   => \$namespace,
    'm'             => \$name_only,
    'V'             => \$version,
    ''              => \$stdin,
    'h|help'        => sub { pod2usage(-exitval => 0) },
    'I=s'           => sub { unshift @search, $_[1] },
    'v|version'     => \&version,
) or pod2usage( -exitval => 1 );

if ($dump) {
    print join("\n", @search), "\n";
    exit 0;
}

require ExtUtils::MakeMaker if $version;

our @modules = @ARGV;

# Also read module names from STDIN if we have '-' switch

# Removing Text::Trim dependency
# push @modules, trim <STDIN> if $stdin;

if ($stdin) {
    my @from_stdin = <STDIN>;

    for (@from_stdin) {
        s/\A\s*//;
        s/\s*\z//;
    }

    push @modules, @from_stdin;
}

pod2usage( -exitval => 1, -message => 'No modules selected')
    unless @modules;

if ($namespace) {
    my @found;

    for my $ns (@modules) {
        push @found, $ns if find_installed($ns, @search);
        push @found, find_in_namespace($ns, @search);
    }

    @modules = @found;
}

# We can just print and exit if we're just interested in module names.
# However, if we also want versions, we have to get the path(s) anyway.
if ($name_only and not $version) {
    print map { "$_\n" } @modules;
    exit 0;
}

# Find the maximum length of module names
my $width = max map { length } @modules;
my $exit = 0;

MODULE:
for my $module (@modules) {
    unless (is_valid_module_name($module)) {
        # Maybe the module is actually a path:
        my $new = path_to_module($module)
               || fs_path_to_module($module);

        if ($new) {
            $module = $new;
        }
        else {
            $exit = 2;
            warn "'$module' is not a valid module name\n";
            next MODULE;
        }
    }

    if ($paths) {
        print module_path $module, "\n";
        next MODULE;
    }

    my @paths = $all ? all_installed($module, @search)
                     : find_installed($module, @search)
                     ;

    my $prefix = '';
    unless ($quiet or @modules == 1 or $name_only) {
        # print the module name as well as the path
        $prefix = sprintf("%-${width}s - ", $module);
    }

    if (@paths) {
        for my $path (@paths) {
            if ($version) {
                my $version = eval { MM->parse_version($path) };
                if ($@) {
                    warn "$0: Error finding version for '$module': $@\n";
                    $exit = 2;
                }

                # We might not want to display the path
                $path = $module if $name_only;

                $path .= defined $version ? " [ $version ]" : '';
            }

            print $prefix, $path, "\n";
        }
    }
    else {
        $exit = 2;
        print $prefix, "not found\n" unless $quiet;
    }
}

exit $exit;

sub version {
    my $path = module_is_loaded('Module::Util');
    print "pm_which $VERSION\n",
          "Using Module::Util $Module::Util::VERSION at $path\n";
    exit 0;
}

__END__

=head1 DESCRIPTION

This tool reports the locations of installed perl modules.

By default it lists the location of each specified module that would be loaded
by require.

=head1 OPTION DETAILS

=head2 quiet

Under quiet mode, module names are suppressed and missing modules are not
reported.

Normal output:

    $ pm_which Module::One Module::Two Missing::Module
    Module::One     - /path/to/Module/One.pm
    Module::Two     - /path/to/Module/Two.pm
    Missing::Module - not found

Under --quiet:

    $ pm_which -q Module::One Module::Two Missing::Module
    /path/to/Module/One.pm
    /path/to/Module/Two.pm

=head2 paths

In "paths" mode, each module is simply converted into a relative file path. This
is possible even when the module is not installed.

    $ pm_which -p Missing::Module
    Missing/Module.pm

=head2 all

When the "all" switch is specified, all installed modules will be reported, not
just the first one. This is useful for determining when there is a module
installed in multiple locations.

    $ pm_which -a MyModule
    /path/to/MyModule.pm
    /home/me/perl/MyModule.pm

=head2 namespace

Arguments are taken as namespaces to search under.

    $ pm_which -n MyModule
    MyModule            - /path/to/MyModule.pm
    MyModule::Foo       - /path/to/MyModule/Foo.pm
    MyModule::Foo::Bar  - /path/to/MyModule/Foo/Bar.pm

=head2 -m

Disables printing of module paths. This is only really useful in conjunction with --namespace.

    $ pm_which -nm MyModule
    MyModule
    MyModule::Foo
    MyModule::Foo::Bar

=head2 -V

Prints the version of each module, according to L<ExtUtils::MakeMaker>.

    $ pm_which -V MyModule
    MyModule - /path/to/MyModule.pm [ 1.00 ]

    $ pm_which -Vnm MyModule
    MyModule [ 1.00 ]
    MyModule::Foo [ 0.01 ]
    MyModule::Foo::Bar [ undef ]

=head2 dump

Dumps the paths that would be searched and exits. This is @INC modified by any
-I switches.

    $ pm_which --dump
    /usr/lib/perl5/site_perl/5.8.6
    /usr/lib/perl5/vendor_perl/5.8.6
    ...

    $ pm_which -I lib --dump -I blib/lib
    lib
    blib/lib
    /usr/lib/perl5/site_perl/5.8.6
    ...

=head2 version

Prints the version number of the script, plus the version and path of
Module::Util that was loaded.

=head1 EXIT CODES

=over

=item * 0 - Everything was OK

=item * 1 - Initialisation failed (bad switches?)

=item * 2 - Some modules were not installed

=back

=head1 SEE ALSO

This utility comes with L<Module::Util>.

=head1 AUTHOR

Matt Lawrence E<lt>mattlaw@cpan.orgE<gt>

=cut

vim: ts=8 sts=4 sw=4 sr et
