File Coverage

script/appexec
Criterion Covered Total %
statement 88 127 69.2
branch 23 58 39.6
condition 6 30 20.0
subroutine 14 18 77.7
pod n/a
total 131 233 56.2


line stmt bran cond sub pod time code
1             #!perl
2              
3 1     1   5747 use v5.10;
  1         4  
4 1     1   6 use strict;
  1         1  
  1         40  
5 1     1   5 use warnings;
  1         1  
  1         60  
6              
7             # ABSTRACT: execute a command under a specified environment
8             # PODNAME: appexec
9              
10 1     1   850 use Getopt::Long qw( :config require_order );
  1         19043  
  1         7  
11              
12 1     1   243 use File::Basename;
  1         2  
  1         124  
13 1     1   567 use File::Spec::Functions qw( file_name_is_absolute );
  1         956  
  1         88  
14 1     1   563 use File::Which;
  1         1675  
  1         67  
15 1     1   711 use App::Env;
  1         5  
  1         5  
16 1     1   7 use List::Util 1.33 'any';
  1         20  
  1         82  
17 1     1   6 use App::Env::_Util;
  1         2  
  1         3926  
18              
19 1         265317 our $VERSION = '1.05';
20 1         126 my $prog = basename( $0, '.pl' );
21              
22 1         12 my %ShellMap = (
23             ksh => 'korn',
24             bash => 'bash',
25             tcsh => 'tc',
26             sh => 'bourne',
27             csh => 'c',
28             );
29              
30             # program options; see parse_args();
31 1         3 my %opt;
32              
33 1   50     1 eval { main() } // do {
  1         6  
34 0         0 say STDERR "# $prog: $_" foreach split /\n/, $@; ## no critic(InputOutput::RequireCheckedSyscalls)
35 0         0 exit 1;
36             };
37              
38 1         0 exit 0;
39              
40             sub main {
41 1     1   5 parse_args();
42              
43 1 50       4 help( 1 ) if $opt{help};
44 1 50       4 help( 2 ) if $opt{usage};
45              
46             return ( print "$prog $VERSION\n" )
47 1 50       4 if $opt{version};
48              
49             die "please specify an environment\n"
50 1 50       4 unless defined $opt{env};
51              
52 1 50       5 if ( $opt{clear} ) {
53             ## no critic( Variables::RequireLocalizedPunctuationVars )
54 1         27 %ENV = map { $_ => $ENV{$_} }
55 1         2 grep { exists $ENV{$_} } qw[ HOME LOGNAME SHELL ];
  3         9  
56             }
57              
58 1         5 my @envs = split( /,/, $opt{env} );
59              
60             # if more than one environment, sort out possible environment specific appopts
61 1         26 my %appopts;
62 1         4 @appopts{@envs} = map { {} } 1 .. @envs;
  1         5  
63              
64 1 50       4 if ( @envs > 1 ) {
65              
66 0         0 for my $k ( keys %{ $opt{appopts} } ) {
  0         0  
67 0         0 my ( $env, $key ) = $k =~ /^([^:]*):(.*)$/;
68              
69             die( "appopts ($key) not specific to one of the specified environments" )
70 0 0       0 unless exists $appopts{$env};
71              
72 0         0 $appopts{$env}{$key} = $opt{appopts}->{$k};
73             }
74             }
75              
76             else {
77 1         4 $appopts{ $envs[0] } = $opt{appopts};
78             }
79              
80 1   50     2 my $env = eval {
81             App::Env->new(
82 1         33 ( map { [ $_ => { AppOpts => $appopts{$_} } ] } @envs ),
83 1 50       2 { ( defined $opt{site} ? ( Site => $opt{site} ) : () ), } );
84             } // die( "error setting up environment `$opt{env}': $@\n" );
85              
86 1         4 $env->setenv( $_ ) for @{ $opt{delete} };
  1         4  
87 1         2 $env->setenv( $_, $opt{define}{$_} ) for keys %{ $opt{define} };
  1         8  
88              
89 1 50       8 dumpenv( $env, $opt{dumpenv}, $opt{dumpvar} ) if $opt{dumpenv};
90              
91 1 50       4 if ( @ARGV ) {
92             say join( q{ }, @ARGV ) ## no critic(InputOutput::RequireCheckedSyscalls)
93 0 0       0 if $opt{verbose};
94              
95 0         0 %ENV = %$env; ## no critic( Variables::RequireLocalizedPunctuationVars )
96              
97 0 0 0     0 die( "$ARGV[0] does not exist, is not executable, or is not in PATH\n" )
      0        
98             unless ( file_name_is_absolute( $ARGV[0] ) && -e $ARGV[0] )
99             || defined which( $ARGV[0] );
100              
101 0 0       0 exec { $ARGV[0] } @ARGV
  0         0  
102             or die( "can't exec $ARGV[0]: not in path?\n" );
103             }
104              
105 1         12 return !!1;
106             }
107              
108             sub _is_valid_env_name {
109 0     0   0 my $name = shift;
110 0   0     0 return $name !~ /\P{IsWord}/ && substr( $name, 0, 1 ) =~ /\P{IsDigit}/;
111             }
112              
113             sub dumpenv {
114 1     1   3 my ( $env, $fmt, $vars ) = @_;
115              
116 1 50       5 $vars = [ keys %$env ] unless @$vars;
117              
118             ## no critic (InputOutput::RequireCheckedSyscalls)
119             ## no critic (ControlStructures::ProhibitCascadingIfElse)
120 1 50       12 if ( $fmt eq 'raw' ) {
    50          
    50          
    50          
    50          
    50          
121             say "$_=",
122             (
123             length $env->{$_}
124             ? App::Env::_Util::shell_escape( $env->{$_} )
125             : q{}
126 0 0       0 ) for @$vars;
127             }
128              
129             elsif ( $fmt eq 'unquoted' ) {
130 0         0 say "$_=$env->{$_}" for @$vars;
131             }
132              
133             elsif ( $fmt eq 'values' ) {
134 0         0 say $env->{$_} for @$vars;
135             }
136              
137             elsif ( $fmt eq 'json' ) {
138 0         0 require JSON::PP;
139 0         0 say JSON::PP::encode_json( { map { $_ => $env->{$_} } @$vars } );
  0         0  
140             }
141              
142             elsif ( $fmt eq 'delta-json' ) {
143 0         0 my ( $delete, $add ) = delta( $env );
144 0         0 require JSON::PP;
145             say JSON::PP::encode_json( {
146             delete => $delete,
147 0         0 add => { map { $_ => $env->{$_} } @$add },
  0         0  
148             } );
149             }
150              
151             elsif ( $fmt eq 'delta-args' ) {
152 0         0 my ( $delete, $add ) = delta( $env );
153 0         0 say join q{ }, ( map { "-X $_" } @$delete ),
154 0         0 ( map { "-D $_=" . App::Env::_Util::shell_escape( $env->{$_} ) } @$add );
  0         0  
155             }
156              
157             else {
158 1         705 require Shell::Guess;
159              
160 1 50       3034 if ( $fmt eq 'auto' ) {
161 0         0 $fmt = Shell::Guess->running_shell;
162             }
163             else {
164             die( "unknown dump format: $fmt\n" )
165 1 50 33     58 unless my $mth = Shell::Guess->can( ( $ShellMap{$fmt} // $fmt ) . '_shell' );
166 1         4 $fmt = Shell::Guess->$mth;
167             }
168 1         668 require Shell::Config::Generate;
169 1         4316 my $config = Shell::Config::Generate->new;
170 1         15 my $extracted = $env->env( $vars, { AllowIllegalVariableNames => !!0 } );
171 1         8 $config->set( $_, $extracted->{$_} ) for keys %$extracted;
172 1         49 print $config->generate( $fmt );
173             }
174              
175 1         556 return;
176             }
177              
178             sub delta {
179 0     0   0 my ( $env ) = @_;
180 0         0 my @delete = grep { !exists $env->{$_} } keys %ENV;
  0         0  
181              
182             my @add
183 0 0 0     0 = grep { !exists $ENV{$_} || exists $ENV{$_} && exists $env->{$_} && $ENV{$_} ne $env->{$_} }
  0   0     0  
184             keys %$env;
185 0         0 return ( \@delete, \@add );
186             }
187              
188             sub parse_args {
189              
190 1     1   11 %opt = (
191             appopts => {},
192             clear => 0,
193             define => {},
194             delete => [],
195             dumpvar => [],
196             verbose => 0,
197             version => 0,
198             usage => 0,
199             help => 0,
200             );
201              
202 1   50     2 eval {
203 1     0   9 local $SIG{__WARN__} = sub { die $_[0] };
  0         0  
204              
205 1         8 Getopt::Long::Configure( 'no_ignore_case' );
206              
207 1         53 GetOptions(
208             \%opt,
209             qw/
210             env=s
211             appopts|o=s%
212             define|D=s%
213             delete|X=s@
214             usage
215             help
216             clear|c
217             dumpenv|d=s
218             dumpvar|V=s@
219             site=s
220             verbose
221             version
222             /,
223             );
224 1         2501 1;
225             } // die $@;
226              
227 1 50 33     15 return if $opt{version} || $opt{help} || $opt{usage};
      33        
228              
229 1         4 my @notset = grep { !defined $opt{$_} } keys %opt;
  11         20  
230 1 50       5 die( 'parameters `', join( q{`, `}, @notset ), "' are not set\n" )
231             if @notset;
232              
233             # ensure that the dumpenv option is correct
234 1 50       4 if ( exists $opt{dumpenv} ) {
235             die( "unsupported dumpenv format: $opt{dumpenv}\n" )
236 1 50   4   11 unless any { $opt{dumpenv} eq $_ } keys %ShellMap,
  4         10  
237             qw( auto delta-args delta-json json raw unquoted values );
238             }
239              
240             # if --env wasn't specified, the first argument is the application
241             # name
242 1 50       7 $opt{env} = shift( @ARGV ) unless defined $opt{env};
243             }
244              
245              
246             sub help {
247 0     0     my ( $verbose ) = @_;
248              
249 0           require Pod::Usage;
250 0           Pod::Usage::pod2usage( { -exitval => 0, -verbose => $verbose } );
251             }
252              
253             #
254             # This file is part of App-Env
255             #
256             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
257             #
258             # This is free software, licensed under:
259             #
260             # The GNU General Public License, Version 3, June 2007
261             #
262              
263             __END__