File Coverage

blib/lib/Env/Bash.pm
Criterion Covered Total %
statement 111 194 57.2
branch 31 114 27.1
condition 18 57 31.5
subroutine 21 30 70.0
pod 8 8 100.0
total 189 403 46.9


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