App-Env

 view release on metacpan or  search on metacpan

script/appexec  view on Meta::CPAN

#!perl

use v5.10;
use strict;
use warnings;

# ABSTRACT: execute a command under a specified environment
# PODNAME: appexec

use Getopt::Long qw( :config require_order );

use File::Basename;
use File::Spec::Functions qw( file_name_is_absolute );
use File::Which;
use App::Env;
use List::Util 1.33 'any';
use App::Env::_Util;

our $VERSION = '1.05';
my $prog = basename( $0, '.pl' );

my %ShellMap = (
    ksh  => 'korn',
    bash => 'bash',
    tcsh => 'tc',
    sh   => 'bourne',
    csh  => 'c',
);

# program options; see parse_args();
my %opt;

eval { main() } // do {
    say STDERR "# $prog: $_" foreach split /\n/, $@;    ## no critic(InputOutput::RequireCheckedSyscalls)
    exit 1;
};

exit 0;

sub main {
    parse_args();

    help( 1 ) if $opt{help};
    help( 2 ) if $opt{usage};

    return ( print "$prog $VERSION\n" )
      if $opt{version};

    die "please specify an environment\n"
      unless defined $opt{env};

    if ( $opt{clear} ) {
        ## no critic( Variables::RequireLocalizedPunctuationVars )
        %ENV = map { $_ => $ENV{$_} }
          grep { exists $ENV{$_} } qw[ HOME LOGNAME SHELL ];
    }

    my @envs = split( /,/, $opt{env} );

    # if more than one environment, sort out possible environment specific appopts
    my %appopts;
    @appopts{@envs} = map { {} } 1 .. @envs;

    if ( @envs > 1 ) {

        for my $k ( keys %{ $opt{appopts} } ) {
            my ( $env, $key ) = $k =~ /^([^:]*):(.*)$/;

            die( "appopts ($key) not specific to one of the specified environments" )
              unless exists $appopts{$env};

            $appopts{$env}{$key} = $opt{appopts}->{$k};
        }
    }

    else {
        $appopts{ $envs[0] } = $opt{appopts};
    }

    my $env = eval {
        App::Env->new(
            ( map { [ $_ => { AppOpts => $appopts{$_} } ] } @envs ),
            { ( defined $opt{site} ? ( Site => $opt{site} ) : () ), } );
    } // die( "error setting up environment `$opt{env}': $@\n" );

    $env->setenv( $_ ) for @{ $opt{delete} };
    $env->setenv( $_, $opt{define}{$_} ) for keys %{ $opt{define} };

    dumpenv( $env, $opt{dumpenv}, $opt{dumpvar} ) if $opt{dumpenv};

    if ( @ARGV ) {
        say join( q{ }, @ARGV )    ## no critic(InputOutput::RequireCheckedSyscalls)
          if $opt{verbose};

        %ENV = %$env;              ## no critic( Variables::RequireLocalizedPunctuationVars )

        die( "$ARGV[0] does not exist, is not executable, or is not in PATH\n" )
          unless ( file_name_is_absolute( $ARGV[0] ) && -e $ARGV[0] )
          || defined which( $ARGV[0] );

        exec { $ARGV[0] } @ARGV
          or die( "can't exec $ARGV[0]: not in path?\n" );
    }

    return !!1;
}

sub _is_valid_env_name {
    my $name = shift;
    return $name !~ /\P{IsWord}/ && substr( $name, 0, 1 ) =~ /\P{IsDigit}/;
}

sub dumpenv {
    my ( $env, $fmt, $vars ) = @_;

    $vars = [ keys %$env ] unless @$vars;

    ## no critic (InputOutput::RequireCheckedSyscalls)
    ## no critic (ControlStructures::ProhibitCascadingIfElse)
    if ( $fmt eq 'raw' ) {
        say "$_=",
          (
            length $env->{$_}
            ? App::Env::_Util::shell_escape( $env->{$_} )
            : q{}
          ) for @$vars;
    }

    elsif ( $fmt eq 'unquoted' ) {
        say "$_=$env->{$_}" for @$vars;
    }

    elsif ( $fmt eq 'values' ) {
        say $env->{$_} for @$vars;
    }

    elsif ( $fmt eq 'json' ) {
        require JSON::PP;
        say JSON::PP::encode_json( { map { $_ => $env->{$_} } @$vars } );
    }

    elsif ( $fmt eq 'delta-json' ) {
        my ( $delete, $add ) = delta( $env );
        require JSON::PP;
        say JSON::PP::encode_json( {
            delete => $delete,
            add    => { map { $_ => $env->{$_} } @$add },
        } );
    }

    elsif ( $fmt eq 'delta-args' ) {
        my ( $delete, $add ) = delta( $env );
        say join q{ }, ( map { "-X $_" } @$delete ),
          ( map { "-D $_=" . App::Env::_Util::shell_escape( $env->{$_} ) } @$add );
    }

    else {
        require Shell::Guess;

        if ( $fmt eq 'auto' ) {
            $fmt = Shell::Guess->running_shell;
        }
        else {
            die( "unknown dump format: $fmt\n" )
              unless my $mth = Shell::Guess->can( ( $ShellMap{$fmt} // $fmt ) . '_shell' );
            $fmt = Shell::Guess->$mth;
        }
        require Shell::Config::Generate;
        my $config    = Shell::Config::Generate->new;
        my $extracted = $env->env( $vars, { AllowIllegalVariableNames => !!0 } );
        $config->set( $_, $extracted->{$_} ) for keys %$extracted;
        print $config->generate( $fmt );
    }

    return;
}

sub delta {
    my ( $env ) = @_;
    my @delete = grep { !exists $env->{$_} } keys %ENV;

    my @add
      = grep { !exists $ENV{$_} || exists $ENV{$_} && exists $env->{$_} && $ENV{$_} ne $env->{$_} }
      keys %$env;
    return ( \@delete, \@add );
}

sub parse_args {

    %opt = (
        appopts => {},
        clear   => 0,
        define  => {},
        delete  => [],
        dumpvar => [],
        verbose => 0,
        version => 0,
        usage   => 0,
        help    => 0,
    );

    eval {
        local $SIG{__WARN__} = sub { die $_[0] };

        Getopt::Long::Configure( 'no_ignore_case' );

        GetOptions(
            \%opt,
            qw/
              env=s
              appopts|o=s%
              define|D=s%
              delete|X=s@



( run in 0.313 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )