use strict;

package Probe::Perl;
{
  $Probe::Perl::VERSION = '0.03';
}

# TODO: cache values derived from launching an external perl process
# TODO: docs refer to Config.pm and $self->{config}


use Config;
use File::Spec;

sub new {
  my $class = shift;
  my $data  = shift || {};
  return bless( $data, $class );
}

sub config {
  my ($self, $key) = (shift, shift);
  if (@_) {
    unless (ref $self) {
      die "Can't set config values via $self->config().  Use $self->new() to create a local view";
    }
    $self->{$key} = shift;
  }
  return ref($self) && exists $self->{$key} ? $self->{$key} : $Config{$key};
}

sub config_revert {
  my $self = shift;
  die "Can't use config_revert() as a class method" unless ref($self);
  
  delete $self->{$_} foreach @_;
}

sub perl_version {
  my $self = shift;
  # Check the current perl interpreter
  # It's much more convenient to use $] here than $^V, but 'man
  # perlvar' says I'm not supposed to.  Bloody tyrant.
  return $^V ? $self->perl_version_to_float(sprintf( "%vd", $^V )) : $];
}

sub perl_version_to_float {
  my ($self, $version) = @_;
  $version =~ s/\./../;  # Double up the first dot so the output has one dot remaining
  $version =~ s/\.(\d+)/sprintf( '%03d', $1 )/eg;
  return $version;
}

sub _backticks {
  my $perl = shift;
  return unless -e $perl;

  my $fh;
  eval {open $fh, '-|', $perl, @_ or die $!};
  if (!$@) {
    return <$fh> if wantarray;
    my $tmp = do {local $/=undef; <$fh>};
    return $tmp;
  }

  # Quoting only happens on the path to perl - I control the rest of
  # the args and they don't need quoting.
  if ($^O eq 'MSWin32') {
    $perl = qq{"$perl"} if $perl =~ m{^[\w\\]+$};
  } else {
    $perl =~ s{([^\w\\])}{\\$1}g;
  }

  return `$perl @_`;
}

sub perl_is_same {
  my ($self, $perl) = @_;
  return _backticks($perl, qw(-MConfig=myconfig -e print -e myconfig)) eq Config->myconfig;
}

sub find_perl_interpreter {
  my $self = shift;

  return $^X if File::Spec->file_name_is_absolute($^X);

  my $exe = $self->config('exe_ext');

  my $thisperl = $^X;
  if ($self->os_type eq 'VMS') {
    # VMS might have a file version at the end
    $thisperl .= $exe unless $thisperl =~ m/$exe(;\d+)?$/i;
  } elsif (defined $exe) {
    $thisperl .= $exe unless $thisperl =~ m/$exe$/i;
  }

  foreach my $perl ( $self->config('perlpath'),
		     map( File::Spec->catfile($_, $thisperl),
			  File::Spec->path() )
		   ) {
    return $perl if -f $perl and $self->perl_is_same($perl);
  }
  return;
}

# Determine the default @INC for this Perl
sub perl_inc {
  my $self = shift;

  local $ENV{PERL5LIB};  # this is not considered part of the default.

  my $perl = $self->find_perl_interpreter();

  my @inc = _backticks($perl, qw(-l -e print -e for -e @INC));
  chomp @inc;

  return @inc;
}


{
  my %OSTYPES = qw(
		   aix       Unix
		   bsdos     Unix
		   dgux      Unix
		   dynixptx  Unix
		   freebsd   Unix
		   linux     Unix
		   hpux      Unix
		   irix      Unix
		   darwin    Unix
		   machten   Unix
		   next      Unix
		   openbsd   Unix
		   netbsd    Unix
		   dec_osf   Unix
		   svr4      Unix
		   svr5      Unix
		   sco_sv    Unix
		   unicos    Unix
		   unicosmk  Unix
		   solaris   Unix
		   sunos     Unix
		   cygwin    Unix
		   os2       Unix

		   dos       Windows
		   MSWin32   Windows

		   os390     EBCDIC
		   os400     EBCDIC
		   posix-bc  EBCDIC
		   vmesa     EBCDIC

		   MacOS     MacOS
		   VMS       VMS
		   VOS       VOS
		   riscos    RiscOS
		   amigaos   Amiga
		   mpeix     MPEiX
		  );


  sub os_type {
    my $class  = shift;
    return $OSTYPES{shift || $^O};
  }
}


1;

__END__


=head1 NAME

Probe::Perl - Information about the currently running perl

=head1 VERSION

version 0.03

=head1 SYNOPSIS

 use Probe::Perl;
 $p = Probe::Perl->new();
 
 # Version of this perl as a floating point number
 $ver = $p->perl_version();
 $ver = Probe::Perl->perl_version();
 
 # Convert a multi-dotted string to a floating point number
 $ver = $p->perl_version_to_float($ver);
 $ver = Probe::Perl->perl_version_to_float($ver);
 
 # Check if the given perl is the same as the one currently running
 $bool = $p->perl_is_same($perl_path);
 $bool = Probe::Perl->perl_is_same($perl_path);
 
 # Find a path to the currently-running perl
 $path = $p->find_perl_interpreter();
 $path = Probe::Perl->find_perl_interpreter();
 
 # Get @INC before run-time additions
 @paths = $p->perl_inc();
 @paths = Probe::Perl->perl_inc();
 
 # Get the general type of operating system
 $type = $p->os_type();
 $type = Probe::Perl->os_type();
 
 # Access Config.pm values
 $val = $p->config('foo');
 $val = Probe::Perl->config('foo');
 $p->config('foo' => 'bar');  # Set locally
 $p->config_revert('foo');  # Revert

=head1 DESCRIPTION

This module provides methods for obtaining information about the
currently running perl interpreter.  It originally began life as code
in the C<Module::Build> project, but has been externalized here for
general use.

=head1 METHODS

=over 4

=item new()

Creates a new Probe::Perl object and returns it.  Most methods in
the Probe::Perl packages are available as class methods, so you
don't always need to create a new object.  But if you want to create a
mutable view of the C<Config.pm> data, it's necessary to create an
object to store the values in.

=item config( $key [, $value] )

Returns the C<Config.pm> value associated with C<$key>.  If C<$value>
is also specified, then the value is set to C<$value> for this view of
the data.  In this case, C<config()> must be called as an object
method, not a class method.

=item config_revert( $key )

Removes any user-assigned value in this view of the C<Config.pm> data.

=item find_perl_interpreter( )

Returns the absolute path of this perl interpreter.  This is actually
sort of a tricky thing to discover sometimes - in these cases we use
C<perl_is_same()> to verify.

=item perl_version( )

Returns the version of this perl interpreter as a perl-styled version
number using C<perl_version_to_float()>.  Uses C<$^V> if your perl is
recent enough, otherwise uses C<$]>.

=item perl_version_to_float( $version )

Formats C<$version> as a perl-styled version number like C<5.008001>.

=item perl_is_same( $perl )

Given the name of a perl interpreter, this method determines if it has
the same configuration as the one represented by the current perl
instance.  Usually this means it's exactly the same

=item perl_inc( )

Returns a list of directories in this perl's C<@INC> path, I<before>
any entries from C<use lib>, C<$ENV{PERL5LIB}>, or C<-I> switches are
added.

=item os_type( [$osname] )

Returns a generic OS type (e.g. "Unix", "Windows", "MacOS") for the
given OS name. If no OS name is given it uses the value in $^O, which
is the same as $Config{osname}.

=back

=head1 AUTHOR

Randy W. Sims <randys@thepierianspring.org>

Based partly on code from the Module::Build project, by Ken Williams
<kwilliams@cpan.org> and others.

=head1 COPYRIGHT

Copyright 2005 Ken Williams and Randy Sims.  All rights reserved.

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

=cut
