File Coverage

blib/lib/EnvDir.pm
Criterion Covered Total %
statement 106 115 92.1
branch 24 38 63.1
condition 8 15 53.3
subroutine 21 22 95.4
pod 0 2 0.0
total 159 192 82.8


line stmt bran cond sub pod time code
1             package EnvDir;
2 6     6   177237 use 5.008005;
  6         24  
  6         265  
3 6     6   34 use strict;
  6         15  
  6         244  
4 6     6   43 use warnings;
  6         13  
  6         298  
5 6     6   40 use Carp ();
  6         8  
  6         151  
6 6     6   33 use File::Spec;
  6         10  
  6         161  
7 6     6   8445 use Storable ();
  6         35836  
  6         464  
8              
9             our $VERSION = "0.08";
10             our $DEFAULT_ENVDIR = File::Spec->catdir( File::Spec->curdir, 'env' );
11              
12 6     6   134 use constant MARK_DELETE => '__MARK_DELETE__';
  6         11  
  6         2568  
13              
14             sub new {
15 5     5 0 24 my $class = shift;
16 5         13 my %args = @_;
17 5         79 bless {
18             clean => 0,
19             depth => 0,
20             cache => [],
21             stack => [],
22 5         18 map { $_ => $args{$_} } qw(clean)
23             }, $class;
24             }
25              
26             my $GLOBAL_INSTANCE;
27             my @GLOBAL_GUARD;
28             sub _instance {
29 6     6   12 my $class = shift;
30 6   66     40 $GLOBAL_INSTANCE ||= $class->new;
31 6         19 $GLOBAL_INSTANCE;
32             }
33              
34             sub import {
35 6     6   62 my $class = shift;
36 6         19 my @args = @_;
37              
38 6         14 my $autoload = 0;
39 6         9 my $dir = 0;
40 6         13 my $clean = 0;
41 6         14 my $self;
42              
43 6         39 while ( defined( my $arg = shift @args ) ) {
44 6 100       30 if ( $arg eq '-autoload' ) {
    100          
    50          
45 2         10 $self = $class->_instance;
46 2         5 $autoload = 1;
47              
48 2         3 $dir = shift @args;
49 2 50 33     23 if ( $dir and $dir eq '-clean' ) {
50 0         0 push @_, $dir;
51 0         0 $dir = $DEFAULT_ENVDIR;
52             }
53             }
54             elsif ( $arg eq 'envdir' ) {
55 2         42 my $package = (caller)[0];
56 6     6   39 no strict 'refs';
  6         15  
  6         18078  
57 2         5 *{"$package\::envdir"} = \&envdir;
  2         147  
58             }
59             elsif ( $arg eq '-clean' ) {
60 2         9 $self = $class->_instance;
61 2         9 $self->{clean} = 1;
62             }
63             }
64              
65 6 100       8085 if ($autoload) {
66 2         5 push @GLOBAL_GUARD, $self->envdir($dir);
67             }
68             }
69              
70             sub envdir {
71 6     6 0 2466 my ( $self, $envdir ) = @_;
72              
73 6 100 66     52 unless ( ref $self and ref $self eq 'EnvDir' ) {
74 2         5 $envdir = $self;
75 2         18 $self = EnvDir->_instance;
76             }
77              
78 6         11 $self->{depth} = scalar @{ $self->{stack} };
  6         25  
79 6   33     21 $envdir ||= $DEFAULT_ENVDIR;
80              
81 6         14 my $depth = $self->{depth};
82              
83             # from cache
84 6         11 my @keys = keys %{ $self->{cache}->[$depth] };
  6         49  
85 6 50       23 if ( scalar @keys ) {
86 0         0 $self->_push_stack;
87 0 0       0 $self->_clean_env if $self->{clean};
88 0         0 $self->_update_env;
89              
90 0 0   0   0 return EnvDir::Guard->new( sub { $self->_revert if $self } );
  0         0  
91             }
92              
93             # from dir
94 6 50       799 opendir my $dh, $envdir or Carp::croak "Cannot open $envdir: $!";
95              
96 6         277 for my $key ( grep !/^\./, readdir($dh) ) {
97 14         223 my $path = File::Spec->catfile( $envdir, $key );
98 14 50       1157 next if -d $path;
99 14 100       228 if ( -s $path == 0 ) {
100 4         26 $self->{cache}->[$depth]->{ uc $key } = MARK_DELETE;
101             }
102             else {
103 10         37 my $value = $self->_slurp($path);
104 10         57 $self->{cache}->[$depth]->{ uc $key } = $value;
105             }
106             }
107              
108 6         27 $self->_push_stack;
109 6 100       708 $self->_clean_env if $self->{clean};
110 6         34 $self->_update_env;
111              
112 6 50       107 closedir $dh or Carp::carp "Cannot close $envdir: $!";
113              
114 6 50   4   55 return EnvDir::Guard->new( sub { $self->_revert if $self } );
  4         28  
115             }
116              
117             sub _push_stack {
118 6     6   16 my $self = shift;
119 6         11 push @{ $self->{stack} }, Storable::freeze( \%ENV );
  6         48  
120             }
121              
122             sub _pop_stack {
123 4     4   7 my $self = shift;
124 4         6 my $ENV = pop @{ $self->{stack} };
  4         13  
125 4         11 %{ Storable::thaw($ENV) };
  4         18  
126             }
127              
128             sub _revert {
129 4     4   8 my $self = shift;
130 4         14 %ENV = $self->_pop_stack;
131             }
132              
133             sub _clean_env {
134 2     2   5 my $self = shift;
135 2         32 %ENV = ();
136 2         12 $ENV{PATH} = '/bin:/usr/bin'; # the same as envdir(8)
137             }
138              
139             sub _update_env {
140 6     6   12 my $self = shift;
141 6         17 my $new_env = $self->{cache}->[ $self->{depth} ];
142 6         22 for ( keys %$new_env ) {
143 14         25 my $value = $new_env->{$_};
144 14 100 66     82 if ( $value and $value eq MARK_DELETE ) {
145 4         28 delete $ENV{$_};
146             }
147             else {
148 10         56 $ENV{$_} = $value;
149             }
150             }
151             }
152              
153             sub _slurp {
154 10     10   18 my $self = shift;
155 10         18 my $path = shift;
156 10 50       1076 if ( open my $fh, '<', $path ) {
157 10         248 my $value = <$fh>; # read first line only.
158 10 50       42 chomp $value if defined $value;
159 10 50       150 close $fh or Carp::carp "Cannot close $path: $!";
160 10         72 return $value;
161             }
162             else {
163 0         0 Carp::carp "Cannot open $path: $!";
164 0         0 return;
165             }
166             }
167              
168             package EnvDir::Guard;
169              
170             sub new {
171 6     6   32 my ( $class, $handler ) = @_;
172 6         4059 bless $handler, $class;
173             }
174              
175             sub DESTROY {
176 4     4   4395 my $self = shift;
177 4         31 $self->();
178             }
179              
180             1;
181             __END__