File Coverage

blib/lib/App/Env.pm
Criterion Covered Total %
statement 364 374 97.3
branch 156 186 83.8
condition 45 56 80.3
subroutine 55 58 94.8
pod 17 17 100.0
total 637 691 92.1


line stmt bran cond sub pod time code
1             package App::Env;
2              
3             # ABSTRACT: manage application specific environments
4              
5 25     25   3765093 use 5.00800;
  25         171  
6 25     25   119 use strict;
  25         38  
  25         502  
7 25     25   119 use warnings;
  25         37  
  25         570  
8              
9 25     25   96 use Scalar::Util;
  25         40  
  25         891  
10 25     25   12778 use Storable ();
  25         66887  
  25         562  
11              
12 25     25   10869 use Params::Validate ();
  25         186214  
  25         597  
13              
14             # need to distinguish between a non-existent module
15             # and one which has compile errors.
16 25     25   8941 use Module::Find qw( );
  25         30209  
  25         944  
17              
18             our $VERSION = '1.00';
19              
20             use overload
21 25         165 '%{}' => '_envhash',
22             '""' => 'str',
23 25     25   143 fallback => 1;
  25         41  
24              
25             #-------------------------------------------------------
26              
27             sub _croak {
28 6     6   97 require Carp;
29 6         809 goto &Carp::croak;
30             }
31              
32             my %existsModule;
33              
34             sub _loadModuleList {
35 35     35   85 %existsModule = ();
36              
37 35         90 for my $path ( Module::Find::findallmod( 'App::Env' ) ) {
38             # greedy match picks up full part of path
39 367         78409 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
40              
41             # store lowercased module
42 367         920 $existsModule{ $base . '::' . lc $app } = $path;
43             }
44              
45 35         70 return;
46             }
47              
48             sub _existsModule {
49 195     195   357 my ( $path ) = @_;
50              
51             # reconstruct path with lowercased application name.
52             # greedy match picks up full part of path
53 195         1043 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
54 195         505 $path = $base . '::' . lc $app;
55              
56             # (re)load cache if we can't find the module in the list
57             _loadModuleList
58 195 100       496 unless $existsModule{$path};
59              
60             # really check
61 195         546 return $existsModule{$path};
62             }
63              
64             #-------------------------------------------------------
65              
66             # allow site specific site definition
67 25         36 use constant APP_ENV_SITE => do {
68 25 100 100     124 if ( !exists $ENV{APP_ENV_SITE} && _existsModule( 'App::Env::Site' ) ) {
69 21         41 eval { require App::Env::Site };
  21         6479  
70 21 0       405 _croak( ref $@ ? $@ : "Error loading App::Env::Site: $@\n" ) if $@;
    50          
71             }
72              
73             # only use the environment variable if defined and not empty.
74             defined $ENV{APP_ENV_SITE}
75 25 100 100     79054 && length $ENV{APP_ENV_SITE} ? $ENV{APP_ENV_SITE} : undef;
76 25     25   7658 };
  25         45  
77              
78             # if $alt_site is non-empty, return it.
79             # if $alt_site is empty, return ().
80             # if $alt_site is undefined, return APP_ENV_SITE
81             sub _App_Env_Site {
82 108     108   179 my $site = shift;
83              
84             # _croak( "Environment variable APP_ENV_SITE is only obeyed at the time that ${ \__PACKAGE__ } is loaded" )
85             # if ( defined( APP_ENV_SITE ) xor defined $ENV{APP_ENV_SITE} )
86             # || ( defined( APP_ENV_SITE ) && defined $ENV{APP_ENV_SITE} && APP_ENV_SITE ne $ENV{APP_ENV_SITE} );
87              
88             return
89 108 100       308 defined $site
    100          
90             ? length( $site )
91             ? $site
92             : ()
93             : APP_ENV_SITE;
94             }
95              
96             #-------------------------------------------------------
97              
98             # Options
99             my %SharedOptions = (
100             Force => { default => 0 },
101             Cache => { default => 1 },
102             Site => { default => undef },
103             CacheID => { default => undef },
104             Temp => { default => 0 },
105             SysFatal => { default => 0, type => Params::Validate::BOOLEAN },
106             );
107              
108             my %ApplicationOptions = (
109             AppOpts => { default => {}, type => Params::Validate::HASHREF },
110             %SharedOptions,
111             );
112              
113             my %CloneOptions = %{
114             Storable::dclone(
115             { map { $_ => $SharedOptions{$_} } qw[ CacheID Cache SysFatal ] } ) };
116             $CloneOptions{Cache}{default} = 0;
117              
118             my %TempOptions = %{
119             Storable::dclone(
120             { map { $_ => $SharedOptions{$_} } qw[ SysFatal Temp ] } ) };
121              
122             # options for whom defaults may be changed. The values
123             # in %OptionDefaults are references to the same hashes as in
124             # ApplicationOptions & SharedOptions, so modifying them will
125             # modify the others.
126             my @OptionDefaults = qw( Force Cache Site SysFatal );
127             my %OptionDefaults;
128             @OptionDefaults{@OptionDefaults} = @ApplicationOptions{@OptionDefaults};
129              
130             # environment cache
131             our %EnvCache;
132              
133             #-------------------------------------------------------
134             #-------------------------------------------------------
135              
136              
137             # import one or more environments. this may be called in the following
138             # contexts:
139             #
140             # * as a class method, i.e.
141             # use App:Env qw( application )
142             # App:Env->import( $application )
143             #
144             # * as a class function (just so as not to confuse folks
145             # App::Env::import( $application )
146             #
147             # * as an object method
148             # $env->import
149              
150             sub import {
151              
152 42     42   12866 my $this = $_[0];
153              
154             # object method?
155 42 100 66     280 if ( Scalar::Util::blessed $this && $this->isa( __PACKAGE__ ) ) {
156 11         25 my $self = shift;
157 11 50       28 _croak( __PACKAGE__, "->import: too many arguments\n" )
158             if @_;
159              
160 11         15 while ( my ( $key, $value ) = each %{$self} ) {
  307         386  
161 296         641 $ENV{$key} = $value;
162             }
163             }
164              
165             else {
166              
167             # if class method, get rid of class in argument list
168 31 100 66     191 shift if !ref $this && $this eq __PACKAGE__;
169              
170             # if no arguments, nothing to do. "use App::Env;" will cause this.
171 31 100       25083 return unless @_;
172              
173             # if the only argument is a hash, it sets defaults
174 14 100 100     70 if ( @_ == 1 && 'HASH' eq ref $_[0] ) {
175 2         11 config( @_ );
176 2         1642 return;
177             }
178              
179 12         52 App::Env->new( @_ )->import;
180             }
181             }
182              
183              
184             # class method
185             # retrieve a cached environment.
186             sub retrieve {
187              
188 4     4 1 328 my ( $cacheid ) = @_;
189 4         5 my $self;
190              
191 4 100       13 if ( defined $EnvCache{$cacheid} ) {
192 2         7 $self = __PACKAGE__->new();
193 2         7 $self->_var( app => $EnvCache{$cacheid} );
194             }
195              
196 4         10 return $self;
197             }
198              
199             #-------------------------------------------------------
200              
201             sub config {
202 2     2 1 64 my %default = Params::Validate::validate( @_, \%OptionDefaults );
203 2         19 $OptionDefaults{$_}{default} = $default{$_} for keys %default;
204 2         6 return;
205             }
206              
207             #-------------------------------------------------------
208              
209             sub new {
210 77     77 1 37186 my $class = shift;
211              
212 77 100       293 my $opts = 'HASH' eq ref $_[-1] ? pop : {};
213              
214             # %{} is overloaded, so an extra reference is required to avoid
215             # an infinite loop when doing things like $self->{}. instead,
216             # use $$self->{}
217 77         172 my $self = bless \{}, $class;
218              
219 77 100       317 $self->_load_envs( @_, $opts ) if @_;
220              
221 76         228 return $self;
222             }
223              
224             #-------------------------------------------------------
225              
226             sub clone {
227 2     2 1 460 my $self = shift;
228              
229 2         28 my %nopt = Params::Validate::validate( @_, \%CloneOptions );
230              
231 2         226 my $clone = Storable::dclone( $self );
232 2         5 delete ${$clone}->{id};
  2         5  
233              
234             # create new cache id
235             $clone->_app->mk_cacheid(
236             CacheID => defined $nopt{CacheID}
237             ? $nopt{CacheID}
238 2 50       6 : $self->lobject_id
239             );
240              
241 2         3 my %opt = ( %{ $clone->_opt }, %nopt );
  2         6  
242 2         7 $clone->_opt( \%opt );
243              
244 2         6 $clone->cache( $opt{Cache} );
245              
246 2         6 return $clone;
247             }
248              
249             #-------------------------------------------------------
250              
251             sub _load_envs {
252 75     75   104 my $self = shift;
253 75         171 my @opts = ( pop );
254 75         164 my @apps = @_;
255              
256             # most of the following logic is for the case where multiple applications
257             # are being loaded in one call. Checking caching requires that we generate
258             # a cacheid from the applications' cacheids.
259              
260             # if import is called as import( [$app, \%opts], \%shared_opts ),
261             # this is equivalent to import( $app, { %shared_opts, %opts } ),
262             # but we still validate %shared_opts as SharedOptions, just to be
263             # precise.
264              
265             # if there's a single application passed as a scalar (rather than
266             # an array containing the app name and options), treat @opts as
267             # ApplicationOptions, else SharedOptions
268              
269 75 100 66     1933 my %opts = Params::Validate::validate( @opts,
270             @apps == 1 && !ref( $apps[0] )
271             ? \%ApplicationOptions
272             : \%SharedOptions );
273              
274              
275 75 100       386 $opts{Cache} = 0 if $opts{Temp};
276              
277             # iterate through the applications to ensure that any application specific
278             # options are valid and to form a basis for a multi-application
279             # cacheid to check for cacheing.
280 75         114 my @cacheids;
281             my @Apps;
282 75         154 for my $app ( @apps ) {
283             # initialize the application specific opts from the shared opts
284 81         259 my %app_opt = %opts;
285              
286             # special filtering of options if this is part of a multi-app
287             # merge
288 81 100       213 if ( @apps > 1 ) {
289             # don't use the shared CacheID option
290 12         19 delete $app_opt{CacheID};
291              
292             # don't cache individual apps in a merged environment,
293             # as the cached environments will be polluted.
294 12         14 delete $app_opt{Cache};
295              
296             # ignore a Force option. This will be turned on later;
297             # if set now it will prevent proper error checking
298 12         14 delete $app_opt{Force};
299             }
300              
301             # handle application specific options.
302 81 100       186 if ( 'ARRAY' eq ref( $app ) ) {
303 1         2 ( $app, my $opts ) = @$app;
304 1 50       2 _croak( "$app: application options must be a hashref\n" )
305             unless 'HASH' eq ref $opts;
306              
307 1         4 %app_opt = ( %app_opt, %$opts );
308              
309 1 50       2 if ( @apps > 1 ) {
310 1         2 for my $iopt ( qw( Cache Force ) ) {
311 2 50       4 if ( exists $app_opt{$iopt} ) {
312 0         0 _croak(
313             "$app: do not specify the $iopt option for individual applications in a merge\n"
314             );
315 0         0 delete $app_opt{$iopt};
316             }
317             }
318             }
319             }
320              
321             # set forced options for apps in multi-app merges, otherwise
322             # the defaults will be set by the call to validate below.
323 81 100       151 if ( @apps > 1 ) {
324 12         15 $app_opt{Force} = 1;
325 12         15 $app_opt{Cache} = 0;
326             }
327              
328             # validate possible application options and get default
329             # values. Params::Validate wants a real array
330 81         225 my ( @opts ) = %app_opt;
331              
332             # return an environment object, but don't load it. we need the
333             # module name to create a cacheid for the merged environment.
334             # don't load now to prevent unnecessary loading of uncached
335             # environments if later it turns out this is a cached
336             # multi-application environment
337 81         1918 %app_opt
338             = ( Params::Validate::validate( @opts, \%ApplicationOptions ) );
339 81         478 my $appo = App::Env::_app->new(
340             pid => $self->lobject_id,
341             app => $app,
342             NoLoad => 1,
343             opt => \%app_opt
344             );
345 80         159 push @cacheids, $appo->cacheid;
346 80         269 push @Apps, $appo;
347             }
348              
349              
350             # create a cacheid for the multi-app environment
351 74   33     338 my $cacheid = $opts{CacheId} || join( $;, @cacheids );
352 74         95 my $App;
353              
354             # use cache if possible
355 74 100 100     382 if ( !$opts{Force} && exists $EnvCache{$cacheid} ) {
    100          
356             # if this is a temporary object and a cached version exists,
357             # clone it and assign a new cache id.
358 17 100       38 if ( $opts{Temp} ) {
359 2         114 $App = Storable::dclone( $EnvCache{$cacheid} );
360              
361             # should really call $self->cacheid here, but $self
362             # doesn't have an app attached to it yet so that'll fail.
363 2         7 $App->cacheid( $self->lobject_id );
364              
365             # update Temp compatible options
366             $App->_opt(
367 2         2 { %{ $App->_opt }, map { $_ => $opts{$_} } keys %TempOptions }
  2         4  
  4         11  
368             );
369             }
370              
371             else {
372 15         22 $App = $EnvCache{$cacheid};
373             }
374             }
375              
376             # not cached; is this really just a single application?
377             elsif ( @Apps == 1 ) {
378 52         97 $App = shift @Apps;
379 52         179 $App->load;
380             }
381              
382             # ok, create new environment by iteration through the apps
383             else {
384             # we don't want to merge environments, as apps may
385             # modify a variable that we don't know how to merge.
386             # PATH is easy, but we have no idea what might be in
387             # others. so, let the apps handle it.
388              
389             # apps get loaded in the current environment.
390 5         218 local %ENV = %ENV;
391              
392 5         19 my @modules;
393 5         11 foreach my $app ( @Apps ) {
394 10         20 push @modules, $app->module;
395              
396             # embrace new merged environment
397 10         12 %ENV = %{ $app->load };
  10         15  
398             }
399              
400 5         74 $App = App::Env::_app->new(
401             ref => $self,
402             env => {%ENV},
403             module => join( $;, @modules ),
404             cacheid => $cacheid,
405             opt => \%opts,
406             );
407              
408 5 50       123 if ( $opts{Cache} ) { $App->cache; }
  5         19  
409             }
410              
411             # record the final things we need to know.
412 74         220 $self->_var( app => $App );
413             }
414              
415              
416             #-------------------------------------------------------
417              
418             # simple accessors to reduce confusion because of double reference in $self
419              
420             sub _var {
421 835     835   910 my $self = shift;
422 835         986 my $var = shift;
423              
424 835 100       1279 ${$self}->{$var} = shift if @_;
  152         365  
425              
426 835         829 return ${$self}->{$var};
  835         9338  
427             }
428              
429 0     0 1 0 sub module { $_[0]->_app->module }
430 6     6 1 21 sub cacheid { $_[0]->_app->cacheid }
431 0     0   0 sub _cacheid { my $self = shift; $self->app->cacheid( @_ ) }
  0         0  
432 22     22   383 sub _opt { my $self = shift; $self->_app->_opt( @_ ) }
  22         49  
433 585     585   849 sub _app { $_[0]->_var( 'app' ) }
434 552     552   13156 sub _envhash { $_[0]->_app->{ENV} }
435              
436             # would rather use Object::ID but it uses Hash::FieldHash which
437             # (through no fault of its own:
438             # http://rt.cpan.org/Ticket/Display.html?id=58030 ) stringify's the
439             # passed reference on pre 5.10 perls, which causes problems.
440              
441             # stolen as much as possible from Object::ID to keep the interface the same
442             {
443             my $Last_ID = "a";
444              
445              
446              
447              
448              
449              
450              
451              
452              
453              
454              
455             sub lobject_id {
456 87     87 1 119 my $self = shift;
457              
458 87 100       180 return $self->_var( 'id' ) if defined $self->_var( 'id' );
459 76         209 return $self->_var( 'id', ++$Last_ID );
460             }
461             }
462              
463             #-------------------------------------------------------
464              
465             sub cache {
466 3     3 1 5 my ( $self, $cache ) = @_;
467              
468 3 50       8 defined $cache
469             or _croak( "missing or undefined cache argument\n" );
470              
471 3 100       7 if ( $cache ) {
472 1         3 $self->_app->cache;
473             }
474             else {
475 2         5 $self->_app->uncache;
476             }
477             }
478              
479             sub uncache {
480 23     23 1 898 my %opt = Params::Validate::validate(
481             @_,
482             {
483             All => { default => undef, type => Params::Validate::SCALAR },
484             App => { default => undef, type => Params::Validate::SCALAR },
485             Site => { default => undef, type => Params::Validate::SCALAR },
486             CacheID => { default => undef, type => Params::Validate::SCALAR },
487             } );
488              
489 23 100       114 if ( $opt{All} ) {
    100          
490 1         3 delete $opt{All};
491             _croak( "can't specify All option with other options\n" )
492 1 50       2 if grep { defined $_ } values %opt;
  3         7  
493              
494 1         18 delete $EnvCache{$_} foreach keys %EnvCache;
495             }
496              
497             elsif ( defined $opt{CacheID} ) {
498 1         2 my $cacheid = delete $opt{CacheID};
499             _croak( "can't specify CacheID option with other options\n" )
500 1 50       4 if grep { defined $_ } values %opt;
  3         6  
501              
502 1         7 delete $EnvCache{$cacheid};
503             }
504             else {
505             _croak( "must specify App or CacheID options\n" )
506 21 50       33 unless defined $opt{App};
507              
508             # don't use normal rules for Site specification as we're trying
509             # to delete a specific one.
510             delete $EnvCache{ _modulename( _App_Env_Site( $opt{Site} ), $opt{App} )
511 21         32 };
512             }
513              
514 23         52 return;
515             }
516              
517             #-------------------------------------------------------
518              
519             sub _modulename {
520 193     193   298 return join( '::', 'App::Env', grep { defined $_ } @_ );
  299         866  
521             }
522              
523              
524             #-------------------------------------------------------
525              
526             # construct a module name based upon the current or requested site.
527             # requires the module if found. returns the module name if module is
528             # found, false if not, die's if require fails
529              
530             sub _require_module {
531 87     87   279 my ( $app, $usite, $loop, $app_opts ) = @_;
532              
533 87   100     415 $app_opts ||= {};
534              
535 87   100     308 $loop ||= 1;
536 87 50       188 _croak( "too many alias loops for $app\n" )
537             if $loop == 10;
538              
539 87         203 my @sites = _App_Env_Site( $usite );
540              
541             # check possible sites, in turn.
542             my ( $module )
543 172         288 = grep { defined $_ }
544 87         169 ( map { _existsModule( _modulename( $_, $app ) ) } @sites ),
  85         169  
545             _existsModule( _modulename( $app ) );
546              
547 87 100       180 if ( defined $module ) {
548             ## no critic ( ProhibitStringyEval );
549 86 50       4301 eval "require $module"
550             or _croak $@;
551              
552             # see if this is an alias
553 86 100       4338 if ( my $alias = $module->can( 'alias' ) ) {
554 6         16 ( $app, my $napp_opts ) = $alias->();
555 6 100       31 @{$app_opts}{ keys %$napp_opts } = @{$napp_opts}{ keys %$napp_opts }
  2         3  
  2         7  
556             if $napp_opts;
557 6         17 return _require_module( $app, $usite, ++$loop, $app_opts );
558             }
559             }
560              
561             else {
562 1         3 return;
563             }
564              
565 80         334 return ( $module, $app_opts );
566             }
567              
568             #-------------------------------------------------------
569              
570             sub _exclude_param_check {
571 9 100 100 9   155 !ref $_[0]
      100        
572             || 'ARRAY' eq ref $_[0]
573             || 'Regexp' eq ref $_[0]
574             || 'CODE' eq ref $_[0];
575             }
576              
577             #-------------------------------------------------------
578              
579             sub env {
580 63     63 1 27993 my $self = shift;
581 63 100       188 my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );
582              
583             # mostly a duplicate of what's in str(). ick.
584 63         730 my %opt = Params::Validate::validate(
585             @opts,
586             {
587             Exclude => {
588             callbacks => { 'type' => \&_exclude_param_check },
589             default => undef
590             },
591             } );
592              
593             # Exclude is only allowed in scalar calling context where
594             # @_ is empty, has more than one element, or the first element
595             # is not a scalar.
596             _croak( "Cannot use Exclude in this calling context\n" )
597 63 50 33     282 if $opt{Exclude} && ( wantarray() || ( @_ == 1 && !ref $_[0] ) );
      66        
598              
599              
600 63 100       188 my $include = [ @_ ? @_ : qr/.*/ ];
601 63         112 my $env = $self->_envhash;
602              
603 63         136 my @vars = $self->_filter_env( $include, $opt{Exclude} );
604              
605             ## no critic ( ProhibitAccessOfPrivateData )
606 63 100 100     284 if ( wantarray() ) {
    100          
607 1 100       3 return map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  3         9  
608             }
609             elsif ( @_ == 1 && !ref $_[0] ) {
610 33 100       234 return exists $env->{ $vars[0] } ? $env->{ $vars[0] } : undef;
611             }
612             else {
613 29         40 my %env;
614 29 50       34 @env{@vars} = map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  590         960  
615 29         214 return \%env;
616             }
617             }
618              
619             #-------------------------------------------------------
620              
621             sub setenv {
622 9     9 1 325 my $self = shift;
623 9         14 my $var = shift;
624              
625 9 50       24 defined $var
626             or _croak( "missing variable name argument\n" );
627              
628 9 100       23 if ( @_ ) {
629 8         18 $self->_envhash->{$var} = $_[0];
630             }
631             else {
632 1         2 delete $self->_envhash->{$var};
633             }
634             }
635              
636             #-------------------------------------------------------
637              
638             # return an env compatible string
639             sub str {
640 5     5 1 28623 my $self = shift;
641 5 100       39 my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );
642              
643             # validate type. Params::Validate doesn't do Regexp, so
644             # this is a bit messy.
645 5         150 my %opt = Params::Validate::validate(
646             @opts,
647             {
648             Exclude => {
649             callbacks => { 'type' => \&_exclude_param_check },
650             optional => 1
651             },
652             } );
653              
654 5 100       42 my $include = [ @_ ? @_ : qr/.*/ ];
655              
656 5 50       10 if ( !grep { $_ eq 'TERMCAP' } @$include ) {
  5         19  
657 5   100     15 $opt{Exclude} ||= [];
658 5 100       11 $opt{Exclude} = [ $opt{Exclude} ] unless 'ARRAY' eq ref $opt{Exclude};
659 5         7 push @{ $opt{Exclude} }, 'TERMCAP';
  5         21  
660             }
661              
662 5         24 my $env = $self->_envhash;
663             ## no critic ( ProhibitAccessOfPrivateData )
664 113         138 my @vars = grep { exists $env->{$_} }
665 5         11 $self->_filter_env( $include, $opt{Exclude} );
666 5         12 return join( ' ', map { "$_=" . _shell_escape( $env->{$_} ) } @vars );
  113         157  
667             }
668              
669             #-------------------------------------------------------
670              
671             # return a list of included variables, in the requested
672             # order, based upon a list of include and exclude specs.
673             # variable names passed as plain strings are not checked
674             # for existance in the environment.
675             sub _filter_env {
676 68     68   115 my ( $self, $included, $excluded ) = @_;
677              
678 68         149 my @exclude = $self->_match_var( $excluded );
679              
680 68         102 my %exclude = map { $_ => 1 } @exclude;
  16         51  
681 68         118 return grep { !$exclude{$_} } $self->_match_var( $included );
  750         998  
682             }
683              
684             #-------------------------------------------------------
685              
686             # return a list of variables which matched the specifications.
687             # this takes a list of scalars, coderefs, or regular expressions.
688             # variable names passed as plain strings are not checked
689             # for existance in the environment.
690             sub _match_var {
691 136     136   173 my ( $self, $match ) = @_;
692              
693 136         170 my $env = $self->_envhash;
694              
695 136 100       297 $match = [$match] unless 'ARRAY' eq ref $match;
696              
697 136         146 my @keys;
698 136         208 for my $spec ( @$match ) {
699 142 100       246 next unless defined $spec;
700              
701 84 100       180 if ( !ref $spec ) {
    100          
    50          
702             # always return a plain name. this allows
703             # @values = $env->env( @names) to work.
704 45         84 push @keys, $spec;
705             }
706             elsif ( 'Regexp' eq ref $spec ) {
707 37         198 push @keys, grep { /$spec/ } keys %$env;
  990         2261  
708             }
709             elsif ( 'CODE' eq ref $spec ) {
710             ## no critic ( ProhibitAccessOfPrivateData )
711 2         29 push @keys, grep { $spec->( $_, $env->{$_} ) } keys %$env;
  58         190  
712             }
713             else {
714 0         0 _croak( "match specification is of unsupported type: ",
715             ref $spec, "\n" );
716             }
717             }
718              
719 136         313 return @keys;
720             }
721              
722             #-------------------------------------------------------
723              
724              
725             sub _shell_escape {
726 113     113   131 my $str = shift;
727              
728             # empty string
729 113 100       138 if ( $str eq '' ) {
730 12         16 $str = "''";
731             }
732              
733             # otherwise, escape all but the "known" non-magic characters.
734             else {
735 101         215 $str =~ s{([^\w/.:=\-+%])}{\\$1}go;
736             }
737              
738 113         253 $str;
739             }
740              
741             #-------------------------------------------------------
742              
743             sub system {
744 7     7 1 4547 my $self = shift;
745              
746 7         10 local %ENV = %{$self};
  7         29  
747 7 100       33 if ( $self->_opt->{SysFatal} ) {
748 4         1328 require IPC::System::Simple;
749 4         15897 return IPC::System::Simple::system( @_ );
750             }
751             else {
752 3         10933 return CORE::system( @_ );
753             }
754             }
755              
756             #-------------------------------------------------------
757              
758             sub qexec {
759 8     8 1 2647 my $self = shift;
760 8         25 local %ENV = %{$self};
  8         63  
761              
762 8         70 require IPC::System::Simple;
763              
764 8         26 my ( @res, $res );
765              
766 8 100       40 if ( wantarray ) {
767 1         2 @res = eval { IPC::System::Simple::capture( @_ ) }
  1         10  
768             }
769             else {
770 7         23 $res = eval { IPC::System::Simple::capture( @_ ) }
  7         55  
771             }
772              
773 8 100       29080 if ( $@ ne '' ) {
774 4 100       63 _croak( $@ ) if $self->_opt->{SysFatal};
775 1         119 return;
776             }
777              
778 4 100       516 return wantarray ? @res : $res;
779             }
780              
781             #-------------------------------------------------------
782              
783             sub capture {
784 5     5 1 23415 my $self = shift;
785 5         33 my @args = @_;
786              
787 5         16 local %ENV = %{$self};
  5         28  
788              
789 5         1381 require Capture::Tiny;
790 5         42750 require IPC::System::Simple;
791              
792             my $sub
793             = $self->_opt->{SysFatal}
794 3     3   3335 ? sub { IPC::System::Simple::system( @args ) }
795 5 100   2   4984 : sub { CORE::system( @args ) };
  2         8742  
796              
797 5         12 my ( $stdout, $stderr );
798              
799             # Capture::Tiny::capture is prototyped as (&;@). App::Env
800             # lazy-loads Capture::Tiny and thus nominally avoids the prototype
801             # check. However, if Capture::Tiny is explicitly loaded prior to
802             # App::Env, the prototype check will be performed when App::Env is
803             # compiled. In that case the following calls to capture are
804             # singled out, as while the calls are correct, the prototype
805             # requires an explicit block or sub{}. So, explicitly
806             # ignore prototypes.
807              
808 5 100       15 if ( wantarray ) {
809 3         4 ( $stdout, $stderr ) = eval { &Capture::Tiny::capture( $sub ) };
  3         62  
810              
811             }
812             else {
813 2         4 $stdout = eval { &Capture::Tiny::capture( $sub ) };
  2         41  
814             }
815              
816 5 100       16622 _croak( $@ ) if $@ ne '';
817              
818 2 50       243 return wantarray ? ( $stdout, $stderr ) : $stdout;
819             }
820              
821             #-------------------------------------------------------
822              
823             sub exec {
824 0     0 1 0 my $self = shift;
825              
826 0         0 local %ENV = %{$self};
  0         0  
827 0         0 exec( @_ );
828             }
829              
830              
831             #-------------------------------------------------------
832              
833             sub which {
834 1     1 1 353 require File::Which;
835 1         823 my $self = shift;
836              
837             {
838 1         2 local %ENV = %{$self};
  1         2  
  1         5  
839 1         4 return File::Which::which( @_ );
840             }
841             }
842              
843             ###############################################
844             ###############################################
845              
846             package App::Env::_app;
847              
848 25     25   242 use Carp();
  25         44  
  25         473  
849 25     25   113 use Storable ();
  25         39  
  25         450  
850 25     25   10410 use Digest;
  25         11724  
  25         640  
851              
852 25     25   132 use strict;
  25         39  
  25         396  
853 25     25   100 use warnings;
  25         38  
  25         19895  
854              
855             # new( pid => $pid, app => $app, opt => \%opt )
856             # new( pid => $pid, env => \%env, module => $module, cacheid => $cacheid )
857             sub new {
858 86     86   345 my ( $class, %opt ) = @_;
859              
860             # make copy of options
861 86         4033 my $self = bless Storable::dclone( \%opt ), $class;
862              
863 86 100       376 if ( exists $self->{env} ) {
864 5 50       15 $self->{opt} = {} unless defined $self->{opt};
865 5         10 $self->{ENV} = delete $self->{env};
866             }
867             else {
868              
869             ( $self->{module}, my $app_opts )
870 81         127 = eval { App::Env::_require_module( $self->{app}, $self->{opt}{Site} ) };
  81         340  
871              
872 81 0       201 _croak(
    50          
873             ref $@
874             ? $@
875             : "error loading application environment module for $self->{app}:\n",
876             $@
877             ) if $@;
878              
879             _croak(
880             "application environment module for $self->{app} does not exist\n" )
881 81 100       199 unless defined $self->{module};
882              
883             # merge possible alias AppOpts
884 80   50     175 $self->{opt}{AppOpts} ||= {};
885 80         155 $self->{opt}{AppOpts} = { %$app_opts, %{ $self->{opt}{AppOpts} } };
  80         190  
886              
887 80         239 $self->mk_cacheid;
888             }
889              
890             # return cached entry if possible
891 85 100 100     194 if ( exists $App::Env::EnvCache{ $self->cacheid } && !$opt{opt}{Force} ) {
892 16         30 $self = $App::Env::EnvCache{ $self->cacheid };
893             }
894              
895             else {
896 69 100       221 $self->load unless $self->{NoLoad};
897 69         109 delete $self->{NoLoad};
898             }
899              
900 85         261 return $self;
901             }
902              
903             #-------------------------------------------------------
904              
905             sub mk_cacheid {
906 82     82   153 my ( $self, $cacheid ) = @_;
907              
908 82 100       197 $cacheid = $self->{opt}{CacheID} unless defined $cacheid;
909              
910 82         107 my @elements;
911              
912 82 100       179 if ( defined $cacheid ) {
913 6 100       20 push @elements, $cacheid eq 'AppID' ? $self->{module} : $cacheid;
914             }
915             else {
916             # create a hash of unique stuff which will be folded
917             # into the cacheid
918 76         96 my %uniq;
919             $uniq{AppOpts} = $self->{opt}{AppOpts}
920 76 100 66     226 if defined $self->{opt}{AppOpts} && keys %{ $self->{opt}{AppOpts} };
  76         307  
921              
922 76         133 my $digest;
923              
924 76 100       152 if ( keys %uniq ) {
925 7         13 local $Storable::canonical = 1;
926 7         22 $digest = Storable::freeze( \%uniq );
927              
928             # use whatever digest aglorithm we can find. if none is
929             # found, default to the frozen representation of the
930             # options
931 7         358 for my $alg ( qw[ SHA-256 SHA-1 MD5 ] ) {
932 7         10 my $ctx = eval { Digest->new( $alg ) };
  7         35  
933              
934 7 50       11584 if ( defined $ctx ) {
935 7         68 $digest = $ctx->add( $digest )->digest;
936 7         33 last;
937             }
938             }
939             }
940 76         186 push @elements, $self->{module}, $digest;
941             }
942              
943 82         145 $self->cacheid( join( $;, grep { defined $_ } @elements ) );
  158         471  
944             }
945              
946              
947             #-------------------------------------------------------
948              
949             sub load {
950 67     67   138 my ( $self ) = @_;
951              
952             # only load if we haven't before
953 67 100       159 return $self->{ENV} if exists $self->{ENV};
954              
955 62         139 my $module = $self->module;
956              
957 62         82 my $envs;
958 62         378 my $fenvs = $module->can( 'envs' );
959              
960 62 50       161 _croak( "$module does not have an 'envs' function\n" )
961             unless $fenvs;
962              
963 62         177 $envs = eval { $fenvs->( $self->{opt}{AppOpts} ) };
  62         189  
964              
965 62 0       1447 _croak( ref $@ ? $@ : "error in ${module}::envs: $@\n" )
    50          
966             if $@;
967              
968             # make copy of environment
969 62         76 $self->{ENV} = { %{$envs} };
  62         767  
970              
971             # cache it
972 62 100       261 $self->cache if $self->{opt}{Cache};
973              
974 62         554 return $self->{ENV};
975             }
976              
977             #-------------------------------------------------------
978              
979             sub cache {
980 42     42   69 my ( $self ) = @_;
981 42         99 $App::Env::EnvCache{ $self->cacheid } = $self;
982             }
983              
984             #-------------------------------------------------------
985              
986             sub uncache {
987 2     2   6 my ( $self ) = @_;
988 2         5 my $cacheid = $self->cacheid;
989              
990             delete $App::Env::EnvCache{$cacheid}
991             if exists $App::Env::EnvCache{$cacheid}
992 2 100 66     11 && $App::Env::EnvCache{$cacheid}{pid} eq $self->{pid};
993             }
994              
995             #-------------------------------------------------------
996              
997 26 100   26   204 sub _opt { @_ > 1 ? $_[0]->{opt} = $_[1] : $_[0]->{opt} }
998 315 100   315   1084 sub cacheid { @_ > 1 ? $_[0]->{cacheid} = $_[1] : $_[0]->{cacheid} }
999 72     72   162 sub module { $_[0]->{module} }
1000              
1001             #-------------------------------------------------------
1002              
1003             1;
1004              
1005             #
1006             # This file is part of App-Env
1007             #
1008             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
1009             #
1010             # This is free software, licensed under:
1011             #
1012             # The GNU General Public License, Version 3, June 2007
1013             #
1014              
1015             __END__