File Coverage

blib/lib/Env/Bash.pm
Criterion Covered Total %
statement 115 200 57.5
branch 35 118 29.6
condition 11 41 26.8
subroutine 21 30 70.0
pod 8 8 100.0
total 190 397 47.8


line stmt bran cond sub pod time code
1             package Env::Bash;
2              
3 4     4   56571 use 5.008;
  4         17  
  4         156  
4 4     4   23 use strict;
  4         8  
  4         153  
5 4     4   30 use warnings;
  4         11  
  4         129  
6              
7 4     4   12522 use Data::Dumper;
  4         112079  
  4         26430  
8              
9             require Exporter;
10              
11             our @ISA = qw(Exporter);
12              
13             our @EXPORT = qw( get_env_var get_env_keys );
14              
15             our $HAVEBASH = 1;
16              
17             our $VERSION = '0.04';
18             $VERSION = eval $VERSION;
19              
20             =pod
21              
22             =head1 NAME
23              
24             Env::Bash - Perl extension for accessing _all_ bash environment variables.
25              
26             =head1 SYNOPSIS
27              
28             use Env::Bash;
29              
30             Standard interface:
31              
32             my @var = get_env_var( "SORCERER_MIRRORS",
33             Source => "/etc/sorcery/config", );
34             print "SORCERER_MIRRORS via get_env_var:\n",
35             join( "\n", @var ), "\ncount = ", scalar @var, "\n";
36            
37             @var = Env::Bash::SORCERER_MIRRORS
38             ( Source => "/etc/sorcery/config", );
39             print "SORCERER_MIRRORS via name:\n",
40             join( "\n", @var ), "\ncount = ", scalar @var, "\n";
41            
42             my @keys = get_env_keys( Source => "/etc/sorcery/config",
43             SourceOnly => 1, );
44             print "first 10 keys:\n", map { " $_\n" } @keys[0..9];
45              
46             =cut
47              
48             # -------------------------
49             # Implementation - AUTOLOAD
50             # -------------------------
51              
52             sub AUTOLOAD {
53 1     1   104 my $name = our $AUTOLOAD;
54 1 50       17 return if $name =~ /DESTROY$/;
55 1         29 $name =~ s/^.*:://;
56 1 50       24 return unless $name =~ /^[_A-Z][_A-Z0-9]*$/;
57 1 50 33     49 $_[0] && ref $_[0] && $_[0]->isa( 'Env::Bash' ) ?
58             shift->get( $name, @_ ) :
59             _get_env_var( $name, @_ );
60             }
61              
62             # -------------------------
63             # Implementation - exported
64             # -------------------------
65              
66             sub get_env_var
67             {
68 1     1 1 3007 _get_env_var( @_ );
69             }
70              
71             sub get_env_keys
72             {
73 0     0 1 0 _get_env_keys( @_ );
74             }
75              
76             =pod
77              
78             Object oriented interface:
79              
80             my $be = Env::Bash->new( Source => "/etc/sorcery/config",
81             Keys => 1, );
82             my @var = $be->get( "SORCERER_MIRRORS" );
83             print "SORCERER_MIRRORS via get:\n",
84             join( "\n", @var ), "\ncount = ", scalar @var, "\n";
85            
86             @var = $be->SORCERER_MIRRORS;
87             print "SORCERER_MIRRORS via name:\n",
88             join( "\n", @var ), "\ncount = ", scalar @var, "\n";
89            
90             $be = Env::Bash->new( Keys => 1,);
91             @var = $be->HOSTTYPE;
92             print "HOSTTYPE via name:\n",
93             join( "\n", @var ), "\ncount = ", scalar @var, "\n";
94            
95             if( $be->exists( 'BASH_VERSINFO' ) ) {
96             print "BASH_VERSINFO =>\n ",
97             join( "\n ", $be->BASH_VERSINFO ), "\n";
98             }
99            
100             my %options = $be->options( [], Keys => 1 );
101              
102             =cut
103              
104             # -------------------------
105             # Implementation - oo i/f
106             # -------------------------
107              
108             sub new
109             {
110 1     1 1 283 my( $invocant, @options ) = @_;
111 1   33     12 my $class = ref( $invocant ) || $invocant;
112 1         4 my $s = { options => {}, };
113 1         5 bless $s, $class;
114 1         4 _have_bash();
115 1         40 $s->options( @options );
116 1 50       48 $s->keys() if $s->{options}{Keys};
117 1         39 $s;
118             }
119              
120             sub get
121             {
122 1     1 1 4 my( $s, $name, @options ) = @_;
123 1         5 my %options = $s->options( @options );
124 1         10 _get_env_var( $name, %options );
125             }
126              
127             sub exists
128             {
129 0     0 1 0 my( $s, $key ) = @_;
130 0 0       0 unless( $s->{keys} ) {
131 0         0 $s->{options}{Keys} = 1;
132 0         0 $s->keys();
133             }
134 0         0 grep /^$key$/, @{$s->{keys}};
  0         0  
135             }
136              
137             sub keys
138             {
139 1     1 1 4 my( $s, @options ) = @_;
140 1         6 $s->options( @options );
141 1 50 33     29 if( exists $s->{keys} && @{$s->{keys}} ) {
  0         0  
142 0 0       0 return unless defined wantarray;
143 0 0       0 return wantarray ? @{$s->{keys}} : $s->{keys};
  0         0  
144             }
145 1         5 my @keys = _get_env_keys( %{$s->{options}} );
  1         8  
146 1         21 $s->{keys} = [ @keys ];
147 1 50       17 return unless defined wantarray;
148 0 0       0 wantarray ? @keys : \@keys;
149             }
150              
151             sub reload_keys
152             {
153 0     0 1 0 my( $s, @options ) = @_;
154 0         0 delete $s->{keys};
155 0         0 $s->keys( @options );
156             }
157              
158             sub options
159             {
160 4     4 1 24 my $s = shift;
161 4         34 my %options = _options( @_ );
162 4 50       28 unless( %options ) {
163 4 100       26 return unless defined wantarray;
164 1 50       4 return wantarray ? %{$s->{options}} : $s->{options};
  1         9  
165             }
166 0         0 $s->{options} = { %{$s->{options}}, %options };
  0         0  
167 0 0       0 return unless defined wantarray;
168 0 0       0 return wantarray ? %{$s->{options}} : $s->{options};
  0         0  
169             }
170              
171             =pod
172              
173             Tie HASH interface:
174              
175             my %env = ();
176             tie %env, "Env::Bash", Source => "/etc/sorcery/config", ForceArray => 1;
177            
178             my $var = $env{SORCERER_MIRRORS};
179             print "SORCERER_MIRRORS via tied hash:\n",
180             join( "\n", @$var ), "\ncount = ", scalar @$var, "\n";
181            
182             $var = $env{HOSTTYPE};
183             print "HOSTTYPE via tied hash:\n",
184             join( "\n", @$var ), "\ncount = ", scalar @$var, "\n";
185            
186             while( my( $key, $value ) = each %env ) {
187             print "$key =>\n ", join( "\n ", @$value ), "\n";
188             }
189              
190             =cut
191              
192             # -------------------------
193             # Implementation - tie hash
194             # -------------------------
195              
196             sub TIEHASH
197             {
198 1     1   225 my( $invocant, @options ) = @_;
199 1   33     9 my $class = ref( $invocant ) || $invocant;
200 1         4 my $s = { options => {}, };
201 1         4 bless $s, $class;
202 1         5 _have_bash();
203 1         26 $s->options( @options );
204 1         29 $s->keys();
205 1         33 $s;
206             }
207              
208             sub FETCH
209             {
210 1     1   54 my( $s, $key ) = @_;
211 1 50       5 return undef unless $s->EXISTS( $key );
212 1         3 _get_env_var( $key, %{$s->{options}} );
  1         36  
213             }
214              
215             sub STORE
216             {
217 0     0   0 Carp::croak( "Tied hash is read-only\n" );
218             }
219              
220             sub DELETE
221             {
222 0     0   0 Carp::croak( "Tied hash is read-only\n" );
223             }
224              
225             sub CLEAR
226             {
227 0     0   0 Carp::croak( "Tied hash is read-only\n" );
228             }
229              
230             sub EXISTS
231             {
232 1     1   4 my( $s, $key ) = @_;
233 1         2 grep /^$key$/, @{$s->{keys}};
  1         76  
234             }
235              
236             sub FIRSTKEY
237             {
238 0     0   0 my $s = shift;
239 0         0 $s->{keys}[0];
240             }
241              
242             sub NEXTKEY
243             {
244 0     0   0 my( $s, $prevkey ) = @_;
245 0         0 my $idx = 0;
246 0 0       0 return $s->FIRSTKEY() unless $prevkey;
247 0         0 for( ; $idx < @{$s->{keys}}; $idx++ ) {
  0         0  
248 0 0       0 last if $s->{keys}[$idx] eq $prevkey;
249             }
250 0         0 $s->{keys}[++$idx];
251             }
252              
253             # -------------------------
254             # 'Private' subs
255             # ( denoted by leading '_' )
256             # -------------------------
257              
258             sub _get_env_var
259             {
260 3 50   3   20 return unless defined wantarray;
261 3         10 my $name = shift;
262 3 50       22 return undef unless $name;
263              
264 3         20 my @ret = ();
265 3         72 my %options = _options( @_ );
266 3 50       15 if( _have_bash() ) {
267 3         86 my @script =
268             (
269             _sources( %options ),
270             _script_contents( $name ),
271             );
272 3         31 my $script = join ";", @script;
273 3 50       29 print STDERR "script:\n$script\n" if $options{Debug};
274            
275 3         44 my $result = _execute_script( $script, %options );
276              
277 0         0 my $href = _load_contents( $result, %options );
278 0 0       0 @ret = $href->{$name} ? @{$href->{$name}} : () ;
  0         0  
279             } else {
280 0   0     0 push @ret, $ENV{$name} || '';
281             }
282 0 0       0 if( $options{ForceArray} ) {
283 0 0       0 return wantarray ? @ret : \@ret;
284             }
285 0 0       0 wantarray ? @ret : ( defined $ret[0] ? $ret[0] : '' );
    0          
286             }
287              
288             sub _get_env_keys
289             {
290 1     1   4 my %options = _options( @_ );
291 1         6 my $bash = _have_bash();
292 1         23 my @keys = ();
293 1 50       30 if( $bash ) {
294 1         22 my @sources = _sources( %options );
295 1 50       21 my $script = "#!$bash\n" .
296             ( @sources ? join( ';', @sources ).';' : '' ) .
297             'set';
298 1         12 my $result = _execute_script( $script, %options );
299 1         22 my %hkeys = _select_keys( $result, %options );
300 1 50 33     14 if( @sources && $options{SourceOnly} ) {
301 0         0 $script = "#!$bash\nset";
302 0         0 $result = _execute_script( $script, %options );
303 0         0 my %bhkeys = _select_keys( $result, %options );
304 0         0 map { delete $hkeys{$_} } CORE::keys %bhkeys;
  0         0  
305 0         0 delete $hkeys{PIPESTATUS}; # magically appears when a script is run
306             }
307 1         35 @keys = sort( CORE::keys %hkeys );
308             } else {
309 0         0 @keys = sort( CORE::keys %ENV );
310             }
311 1 50       16 return unless defined wantarray;
312 1 50       21 wantarray ? @keys : \@keys;
313             }
314              
315             sub _select_keys
316             {
317 1     1   6 my $result = shift;
318 1         11 my %options = _options( @_ );
319 1         2 my %hkeys = ();
320 1         23 pos( $result ) = 0;
321 1         51 while( $result =~ /(.*?)=(?:'.*?'\n|\(.*?\)\n|.*?\n)/sg ) {
322 28         59 my $name = $1;
323 28 50       51 next unless $name;
324 28 50       54 next if $name eq 'BASH_EXECUTION_STRING';
325 28 50       57 if( $options{SelectRegex} ) {
326 0 0       0 next unless $name =~ /$options{SelectRegex}/;
327             }
328 28         150 $hkeys{$name} = 1;
329             }
330 1         33 %hkeys;
331             }
332              
333             sub _have_bash
334             {
335 6 50   6   28 return '' unless $HAVEBASH;
336 6         9 my $bash;
337 6         18 $HAVEBASH = 1;
338 6         16 $bash = $ENV{SHELL};
339 6 0 33     34 return $bash if $bash && -f $bash && -x _;
      33        
340 6 50       243964 return 'bash' if system( 'bash', '-c', '' ) == 0;
341 0         0 $bash = $ENV{BASH};
342 0 0 0     0 return $bash if $bash && -f $bash && -x _;
      0        
343 0 0       0 warn "No bash executable found, running as \$ENV{...}\n" if $HAVEBASH;
344 0         0 $HAVEBASH = 0;
345 0         0 '';
346             }
347              
348             sub _sources
349             {
350 4     4   61 my %options = _options( @_ );
351 0         0 my @srcs =
352 0         0 map { split /;/, $_ }
353             $options{Source} ?
354             ( ref $options{Source} && ref $options{Source} eq 'ARRAY' ?
355 4 0 0     53 @{$options{Source}} : $options{Source} ) : ();
    50          
356 4 50       4697 return () unless @srcs;
357 0         0 my @sources = ();
358 0         0 for my $source( @srcs ) {
359 0 0       0 next unless $source;
360 0         0 $source =~ s/^\. //;
361 0 0       0 next unless $source;
362 0 0       0 unless( -f $source ) {
363 0         0 warn "Source '$source' not found. Ignored.\n";
364 0         0 next;
365             }
366 0 0       0 unless( -x _ ) {
367 0         0 warn "Source '$source' not executable. Ignored.\n";
368 0         0 next;
369             }
370 0         0 my $fh;
371 0 0       0 unless( open( $fh, $source ) ) {
372 0         0 warn "Source '$source' open error: $!. Ignored.\n";
373 0         0 next;
374             }
375 0         0 close $fh;
376 0         0 push @sources, ". $source";
377             }
378 0         0 @sources;
379             }
380              
381             sub _script_contents
382             {
383 3     3   30 my( $name ) = @_;
384             (
385 3         77 "for element in \$(seq 0 \$((\${#${name}[@]} - 1)))",
386             "do echo \"<<8774$name>>\${${name}[\$element]}<<4587>>\"",
387             "done",
388             );
389             }
390              
391             sub _execute_script
392             {
393 4     4   158 my $script = shift;
394 4         22 my %options = _options( @_ );
395 4 50       21 print STDERR "script:\n$script\n" if $options{Debug};
396 4         35 my $result = eval { `$script 2>&1` };
  4         73397  
397 4 100 66     4300 Carp::croak
398             ( "Oops: internal bash script error or your shell is not bash:\n".
399             $result ) if $? || $@;
400 1 50       19 print STDERR "script output:\n$result\n" if $options{Debug};
401 1         24 $result;
402             }
403              
404             sub _load_contents
405             {
406 0     0   0 my $data = shift;
407 0         0 my %options = _options( @_ );
408 0         0 my $content = {};
409 0         0 pos( $data ) = 0;
410 0         0 while( $data =~ /<<8774(.+?)>>(.*?|)<<4587>>/sg ) {
411 0         0 push @{$content->{$1}}, $2;
  0         0  
412             }
413 0 0       0 print STDERR "content: ", Dumper( $content ) if $options{Debug};
414 0         0 $content;
415             }
416              
417             sub _options
418             {
419 17     17   52 my %options;
420 17 50 33     188 if( $_[0] && ref $_[0] && ref $_[0] eq 'ARRAY' ) {
      33        
421 0         0 shift; %options = ( @_, ForceArray => 1, );
  0         0  
422             } else {
423 17         56 %options = @_;
424             }
425 17 50       79 unless( %options ) {
426 17 50       65 return unless defined wantarray;
427 17 50       75 return wantarray ? () : [];
428             }
429 0 0         return unless defined wantarray;
430 0 0         return wantarray ? %options : \%options;
431             }
432              
433             1;
434              
435             __END__