File Coverage

blib/lib/App/Env.pm
Criterion Covered Total %
statement 366 376 97.3
branch 162 192 84.3
condition 48 59 81.3
subroutine 55 58 94.8
pod 17 17 100.0
total 648 702 92.3


line stmt bran cond sub pod time code
1             package App::Env;
2              
3             # ABSTRACT: manage application specific environments
4              
5 25     25   3810274 use 5.00800;
  25         186  
6 25     25   124 use strict;
  25         51  
  25         499  
7 25     25   98 use warnings;
  25         53  
  25         649  
8              
9 25     25   116 use Scalar::Util;
  25         55  
  25         897  
10 25     25   12785 use Storable ();
  25         67943  
  25         611  
11              
12 25     25   11042 use Params::Validate ();
  25         186433  
  25         609  
13              
14             # need to distinguish between a non-existent module
15             # and one which has compile errors.
16 25     25   9040 use Module::Find qw( );
  25         29787  
  25         930  
17              
18             our $VERSION = '1.01';
19              
20             use overload
21 25         193 '%{}' => '_envhash',
22             '""' => 'str',
23 25     25   168 fallback => 1;
  25         52  
24              
25             #-------------------------------------------------------
26              
27             sub _croak {
28 6     6   121 require Carp;
29 6         916 goto &Carp::croak;
30             }
31              
32             my %existsModule;
33              
34             sub _loadModuleList {
35 35     35   110 %existsModule = ();
36              
37 35         110 for my $path ( Module::Find::findallmod( 'App::Env' ) ) {
38             # greedy match picks up full part of path
39 367         80239 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
40              
41             # store lowercased module
42 367         913 $existsModule{ $base . '::' . lc $app } = $path;
43             }
44              
45 35         74 return;
46             }
47              
48             sub _existsModule {
49 196     196   370 my ( $path ) = @_;
50              
51             # reconstruct path with lowercased application name.
52             # greedy match picks up full part of path
53 196         1094 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
54 196         568 $path = $base . '::' . lc $app;
55              
56             # (re)load cache if we can't find the module in the list
57             _loadModuleList
58 196 100       567 unless $existsModule{$path};
59              
60             # really check
61 196         672 return $existsModule{$path};
62             }
63              
64             #-------------------------------------------------------
65              
66             # allow site specific site definition
67 25         38 use constant APP_ENV_SITE => do {
68 25 100 100     138 if ( !exists $ENV{APP_ENV_SITE} && _existsModule( 'App::Env::Site' ) ) {
69 21         44 eval { require App::Env::Site };
  21         6684  
70 21 0       434 _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     80535 && length $ENV{APP_ENV_SITE} ? $ENV{APP_ENV_SITE} : undef;
76 25     25   7755 };
  25         49  
77              
78             # _App_Env_Site ( [$alt_site] );
79             # if $alt_site is non-empty, return it.
80             # if $alt_site is empty or undefined return ().
81             # otherwise return APP_ENV_SITE
82             sub _App_Env_Site {
83              
84 109 100   109   469 @_ || return APP_ENV_SITE;
85              
86 17         26 my $site = shift;
87              
88 17 100 100     69 return () if !defined $site || $site eq '';
89 14         26 return $site;
90              
91             # _croak( "Environment variable APP_ENV_SITE is only obeyed at the time that ${ \__PACKAGE__ } is loaded" )
92             # if ( defined( APP_ENV_SITE ) xor defined $ENV{APP_ENV_SITE} )
93             # || ( defined( APP_ENV_SITE ) && defined $ENV{APP_ENV_SITE} && APP_ENV_SITE ne $ENV{APP_ENV_SITE} );
94             }
95              
96             #-------------------------------------------------------
97              
98             # Options
99             my %SharedOptions = (
100             Force => { default => 0 },
101             Cache => { default => 1 },
102             Site => { optional => 1 },
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   16074 my $this = $_[0];
153              
154             # object method?
155 42 100 66     330 if ( Scalar::Util::blessed $this && $this->isa( __PACKAGE__ ) ) {
156 11         23 my $self = shift;
157 11 50       39 _croak( __PACKAGE__, "->import: too many arguments\n" )
158             if @_;
159              
160 11         15 while ( my ( $key, $value ) = each %{$self} ) {
  307         434  
161 296         753 $ENV{$key} = $value;
162             }
163             }
164              
165             else {
166              
167             # if class method, get rid of class in argument list
168 31 100 66     213 shift if !ref $this && $this eq __PACKAGE__;
169              
170             # if no arguments, nothing to do. "use App::Env;" will cause this.
171 31 100       27986 return unless @_;
172              
173             # if the only argument is a hash, it sets defaults
174 14 100 100     86 if ( @_ == 1 && 'HASH' eq ref $_[0] ) {
175 2         14 config( @_ );
176 2         2246 return;
177             }
178              
179 12         66 App::Env->new( @_ )->import;
180             }
181             }
182              
183              
184             # class method
185             # retrieve a cached environment.
186             sub retrieve {
187              
188 4     4 1 422 my ( $cacheid ) = @_;
189 4         8 my $self;
190              
191 4 100       12 if ( defined $EnvCache{$cacheid} ) {
192 2         6 $self = __PACKAGE__->new();
193 2         7 $self->_var( app => $EnvCache{$cacheid} );
194             }
195              
196 4         9 return $self;
197             }
198              
199             #-------------------------------------------------------
200              
201             sub config {
202 2     2 1 100 my %default = Params::Validate::validate( @_, \%OptionDefaults );
203 2         20 $OptionDefaults{$_}{default} = $default{$_} for keys %default;
204 2         8 return;
205             }
206              
207             #-------------------------------------------------------
208              
209             sub new {
210 78     78 1 42567 my $class = shift;
211              
212 78 100       326 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 78         202 my $self = bless \{}, $class;
218              
219 78 100       385 $self->_load_envs( @_, $opts ) if @_;
220              
221 77         276 return $self;
222             }
223              
224             #-------------------------------------------------------
225              
226             sub clone {
227 2     2 1 331 my $self = shift;
228              
229 2         25 my %nopt = Params::Validate::validate( @_, \%CloneOptions );
230              
231 2         192 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       5 : $self->lobject_id
239             );
240              
241 2         3 my %opt = ( %{ $clone->_opt }, %nopt );
  2         5  
242 2         6 $clone->_opt( \%opt );
243              
244 2         6 $clone->cache( $opt{Cache} );
245              
246 2         750 return $clone;
247             }
248              
249             #-------------------------------------------------------
250              
251             sub _load_envs {
252 76     76   127 my $self = shift;
253 76         197 my @opts = ( pop );
254 76         177 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 76 100 66     2347 my %opts = Params::Validate::validate( @opts,
270             @apps == 1 && !ref( $apps[0] )
271             ? \%ApplicationOptions
272             : \%SharedOptions );
273              
274              
275 76 100       412 $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 76         149 my @cacheids;
281             my @Apps;
282 76         175 for my $app ( @apps ) {
283             # initialize the application specific opts from the shared opts
284 82         289 my %app_opt = %opts;
285              
286             # special filtering of options if this is part of a multi-app
287             # merge
288 82 100       247 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         16 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         16 delete $app_opt{Force};
299             }
300              
301             # handle application specific options.
302 82 100       203 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         3 %app_opt = ( %app_opt, %$opts );
308              
309 1 50       3 if ( @apps > 1 ) {
310 1         1 for my $iopt ( qw( Cache Force ) ) {
311 2 50       5 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 82 100       184 if ( @apps > 1 ) {
324 12         15 $app_opt{Force} = 1;
325 12         25 $app_opt{Cache} = 0;
326             }
327              
328             # validate possible application options and get default
329             # values. Params::Validate wants a real array
330 82         223 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 82         1962 %app_opt
338             = ( Params::Validate::validate( @opts, \%ApplicationOptions ) );
339 82         531 my $appo = App::Env::_app->new(
340             pid => $self->lobject_id,
341             app => $app,
342             NoLoad => 1,
343             opt => \%app_opt
344             );
345 81         245 push @cacheids, $appo->cacheid;
346 81         596 push @Apps, $appo;
347             }
348              
349              
350             # create a cacheid for the multi-app environment
351 75   33     380 my $cacheid = $opts{CacheId} || join( $;, @cacheids );
352 75         115 my $App;
353              
354             # use cache if possible
355 75 100 100     428 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 18 100       45 if ( $opts{Temp} ) {
359 2         145 $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         3 { %{ $App->_opt }, map { $_ => $opts{$_} } keys %TempOptions }
  2         5  
  4         19  
368             );
369             }
370              
371             else {
372 16         91 $App = $EnvCache{$cacheid};
373             }
374             }
375              
376             # not cached; is this really just a single application?
377             elsif ( @Apps == 1 ) {
378 52         137 $App = shift @Apps;
379 52         169 $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         236 local %ENV = %ENV;
391              
392 5         14 my @modules;
393 5         12 foreach my $app ( @Apps ) {
394 10         24 push @modules, $app->module;
395              
396             # embrace new merged environment
397 10         12 %ENV = %{ $app->load };
  10         23  
398             }
399              
400 5         82 $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       45 if ( $opts{Cache} ) { $App->cache; }
  5         16  
409             }
410              
411             # record the final things we need to know.
412 75         230 $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 841     841   950 my $self = shift;
422 841         1027 my $var = shift;
423              
424 841 100       1319 ${$self}->{$var} = shift if @_;
  154         382  
425              
426 841         874 return ${$self}->{$var};
  841         9568  
427             }
428              
429 0     0 1 0 sub module { $_[0]->_app->module }
430 6     6 1 17 sub cacheid { $_[0]->_app->cacheid }
431 0     0   0 sub _cacheid { my $self = shift; $self->app->cacheid( @_ ) }
  0         0  
432 22     22   371 sub _opt { my $self = shift; $self->_app->_opt( @_ ) }
  22         62  
433 588     588   911 sub _app { $_[0]->_var( 'app' ) }
434 555     555   12311 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 88     88 1 154 my $self = shift;
457              
458 88 100       209 return $self->_var( 'id' ) if defined $self->_var( 'id' );
459 77         240 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       10 defined $cache
469             or _croak( "missing or undefined cache argument\n" );
470              
471 3 100       8 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 1076 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 => { optional => 1, type => Params::Validate::SCALAR },
486             CacheID => { default => undef, type => Params::Validate::SCALAR },
487             } );
488              
489 23 100       124 if ( $opt{All} ) {
    100          
490 1         2 delete $opt{All};
491             _croak( "can't specify All option with other options\n" )
492 1 50       4 if grep { defined $_ } values %opt;
  2         7  
493              
494 1         33 delete $EnvCache{$_} foreach keys %EnvCache;
495             }
496              
497             elsif ( defined $opt{CacheID} ) {
498 1         3 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;
  2         6  
501              
502 1         16 delete $EnvCache{$cacheid};
503             }
504             else {
505             _croak( "must specify App or CacheID options\n" )
506 21 50       47 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{
511             _modulename(
512             _App_Env_Site( exists $opt{Site} ? ( $opt{Site} ) : () ),
513 21 100       55 $opt{App} ) };
514             }
515              
516 23         54 return;
517             }
518              
519             #-------------------------------------------------------
520              
521             sub _modulename {
522 194     194   353 return join( '::', 'App::Env', grep { defined $_ } @_ );
  300         955  
523             }
524              
525              
526             #-------------------------------------------------------
527              
528             # construct a module name based upon the current or requested site.
529             # requires the module if found. returns the module name if module is
530             # found, false if not, die's if require fails
531              
532             sub _require_module {
533 88     88   245 my ( $app, %par ) = @_;
534              
535 88   100     537 my $app_opts = $par{app_opts} ||= {};
536 88   100     335 my $loop = $par{loop} ||= 1;
537              
538 88 50       202 _croak( "too many alias loops for $app\n" )
539             if $loop == 10;
540              
541 88 100       315 my @sites = _App_Env_Site( exists $par{site} ? $par{site} : () );
542              
543             # check possible sites, in turn.
544             my ( $module )
545 173         341 = grep { defined $_ }
546 88         211 ( map { _existsModule( _modulename( $_, $app ) ) } @sites ),
  85         206  
547             _existsModule( _modulename( $app ) );
548              
549 88 100       234 if ( defined $module ) {
550             ## no critic ( ProhibitStringyEval );
551 87 50       5341 eval "require $module"
552             or _croak $@;
553              
554             # see if this is an alias
555 87 100       4607 if ( my $alias = $module->can( 'alias' ) ) {
556 6         26 ( $app, my $napp_opts ) = $alias->();
557 6 100       43 @{$app_opts}{ keys %$napp_opts } = @{$napp_opts}{ keys %$napp_opts }
  2         6  
  2         8  
558             if $napp_opts;
559 6         104 return _require_module(
560             $app, %par,
561             loop => ++$loop,
562             app_opts => $app_opts
563             );
564             }
565             }
566              
567             else {
568 1         7 return;
569             }
570              
571 81         437 return ( $module, $app_opts );
572             }
573              
574             #-------------------------------------------------------
575              
576             sub _exclude_param_check {
577 9 100 100 9   219 !ref $_[0]
      100        
578             || 'ARRAY' eq ref $_[0]
579             || 'Regexp' eq ref $_[0]
580             || 'CODE' eq ref $_[0];
581             }
582              
583             #-------------------------------------------------------
584              
585             sub env {
586 64     64 1 28701 my $self = shift;
587 64 100       206 my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );
588              
589             # mostly a duplicate of what's in str(). ick.
590 64         712 my %opt = Params::Validate::validate(
591             @opts,
592             {
593             Exclude => {
594             callbacks => { 'type' => \&_exclude_param_check },
595             default => undef
596             },
597             } );
598              
599             # Exclude is only allowed in scalar calling context where
600             # @_ is empty, has more than one element, or the first element
601             # is not a scalar.
602             _croak( "Cannot use Exclude in this calling context\n" )
603 64 50 33     294 if $opt{Exclude} && ( wantarray() || ( @_ == 1 && !ref $_[0] ) );
      66        
604              
605              
606 64 100       213 my $include = [ @_ ? @_ : qr/.*/ ];
607 64         128 my $env = $self->_envhash;
608              
609 64         152 my @vars = $self->_filter_env( $include, $opt{Exclude} );
610              
611             ## no critic ( ProhibitAccessOfPrivateData )
612 64 100 100     288 if ( wantarray() ) {
    100          
613 1 100       7 return map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  3         10  
614             }
615             elsif ( @_ == 1 && !ref $_[0] ) {
616 33 100       247 return exists $env->{ $vars[0] } ? $env->{ $vars[0] } : undef;
617             }
618             else {
619 30         39 my %env;
620 30 50       45 @env{@vars} = map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  591         1019  
621 30         244 return \%env;
622             }
623             }
624              
625             #-------------------------------------------------------
626              
627             sub setenv {
628 9     9 1 222 my $self = shift;
629 9         16 my $var = shift;
630              
631 9 50       22 defined $var
632             or _croak( "missing variable name argument\n" );
633              
634 9 100       30 if ( @_ ) {
635 8         20 $self->_envhash->{$var} = $_[0];
636             }
637             else {
638 1         3 delete $self->_envhash->{$var};
639             }
640             }
641              
642             #-------------------------------------------------------
643              
644             # return an env compatible string
645             sub str {
646 5     5 1 38941 my $self = shift;
647 5 100       27 my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );
648              
649             # validate type. Params::Validate doesn't do Regexp, so
650             # this is a bit messy.
651 5         177 my %opt = Params::Validate::validate(
652             @opts,
653             {
654             Exclude => {
655             callbacks => { 'type' => \&_exclude_param_check },
656             optional => 1
657             },
658             } );
659              
660 5 100       52 my $include = [ @_ ? @_ : qr/.*/ ];
661              
662 5 50       12 if ( !grep { $_ eq 'TERMCAP' } @$include ) {
  5         20  
663 5   100     16 $opt{Exclude} ||= [];
664 5 100       16 $opt{Exclude} = [ $opt{Exclude} ] unless 'ARRAY' eq ref $opt{Exclude};
665 5         7 push @{ $opt{Exclude} }, 'TERMCAP';
  5         22  
666             }
667              
668 5         52 my $env = $self->_envhash;
669             ## no critic ( ProhibitAccessOfPrivateData )
670 113         186 my @vars = grep { exists $env->{$_} }
671 5         12 $self->_filter_env( $include, $opt{Exclude} );
672 5         17 return join( ' ', map { "$_=" . _shell_escape( $env->{$_} ) } @vars );
  113         228  
673             }
674              
675             #-------------------------------------------------------
676              
677             # return a list of included variables, in the requested
678             # order, based upon a list of include and exclude specs.
679             # variable names passed as plain strings are not checked
680             # for existance in the environment.
681             sub _filter_env {
682 69     69   125 my ( $self, $included, $excluded ) = @_;
683              
684 69         184 my @exclude = $self->_match_var( $excluded );
685              
686 69         113 my %exclude = map { $_ => 1 } @exclude;
  16         59  
687 69         138 return grep { !$exclude{$_} } $self->_match_var( $included );
  751         1116  
688             }
689              
690             #-------------------------------------------------------
691              
692             # return a list of variables which matched the specifications.
693             # this takes a list of scalars, coderefs, or regular expressions.
694             # variable names passed as plain strings are not checked
695             # for existance in the environment.
696             sub _match_var {
697 138     138   189 my ( $self, $match ) = @_;
698              
699 138         197 my $env = $self->_envhash;
700              
701 138 100       321 $match = [$match] unless 'ARRAY' eq ref $match;
702              
703 138         192 my @keys;
704 138         229 for my $spec ( @$match ) {
705 144 100       256 next unless defined $spec;
706              
707 85 100       217 if ( !ref $spec ) {
    100          
    50          
708             # always return a plain name. this allows
709             # @values = $env->env( @names) to work.
710 45         94 push @keys, $spec;
711             }
712             elsif ( 'Regexp' eq ref $spec ) {
713 38         296 push @keys, grep { /$spec/ } keys %$env;
  991         2371  
714             }
715             elsif ( 'CODE' eq ref $spec ) {
716             ## no critic ( ProhibitAccessOfPrivateData )
717 2         49 push @keys, grep { $spec->( $_, $env->{$_} ) } keys %$env;
  58         316  
718             }
719             else {
720 0         0 _croak( "match specification is of unsupported type: ",
721             ref $spec, "\n" );
722             }
723             }
724              
725 138         344 return @keys;
726             }
727              
728             #-------------------------------------------------------
729              
730              
731             sub _shell_escape {
732 113     113   163 my $str = shift;
733              
734             # empty string
735 113 100       176 if ( $str eq '' ) {
736 12         15 $str = "''";
737             }
738              
739             # otherwise, escape all but the "known" non-magic characters.
740             else {
741 101         255 $str =~ s{([^\w/.:=\-+%])}{\\$1}go;
742             }
743              
744 113         359 $str;
745             }
746              
747             #-------------------------------------------------------
748              
749             sub system {
750 7     7 1 6610 my $self = shift;
751              
752 7         14 local %ENV = %{$self};
  7         29  
753 7 100       38 if ( $self->_opt->{SysFatal} ) {
754 4         1444 require IPC::System::Simple;
755 4         16890 return IPC::System::Simple::system( @_ );
756             }
757             else {
758 3         13087 return CORE::system( @_ );
759             }
760             }
761              
762             #-------------------------------------------------------
763              
764             sub qexec {
765 8     8 1 2873 my $self = shift;
766 8         26 local %ENV = %{$self};
  8         52  
767              
768 8         89 require IPC::System::Simple;
769              
770 8         27 my ( @res, $res );
771              
772 8 100       51 if ( wantarray ) {
773 1         3 @res = eval { IPC::System::Simple::capture( @_ ) }
  1         9  
774             }
775             else {
776 7         25 $res = eval { IPC::System::Simple::capture( @_ ) }
  7         55  
777             }
778              
779 8 100       30191 if ( $@ ne '' ) {
780 4 100       79 _croak( $@ ) if $self->_opt->{SysFatal};
781 1         98 return;
782             }
783              
784 4 100       567 return wantarray ? @res : $res;
785             }
786              
787             #-------------------------------------------------------
788              
789             sub capture {
790 5     5 1 24200 my $self = shift;
791 5         45 my @args = @_;
792              
793 5         16 local %ENV = %{$self};
  5         34  
794              
795 5         1616 require Capture::Tiny;
796 5         46963 require IPC::System::Simple;
797              
798             my $sub
799             = $self->_opt->{SysFatal}
800 3     3   3903 ? sub { IPC::System::Simple::system( @args ) }
801 5 100   2   5164 : sub { CORE::system( @args ) };
  2         11167  
802              
803 5         15 my ( $stdout, $stderr );
804              
805             # Capture::Tiny::capture is prototyped as (&;@). App::Env
806             # lazy-loads Capture::Tiny and thus nominally avoids the prototype
807             # check. However, if Capture::Tiny is explicitly loaded prior to
808             # App::Env, the prototype check will be performed when App::Env is
809             # compiled. In that case the following calls to capture are
810             # singled out, as while the calls are correct, the prototype
811             # requires an explicit block or sub{}. So, explicitly
812             # ignore prototypes.
813              
814 5 100       20 if ( wantarray ) {
815 3         6 ( $stdout, $stderr ) = eval { &Capture::Tiny::capture( $sub ) };
  3         68  
816              
817             }
818             else {
819 2         4 $stdout = eval { &Capture::Tiny::capture( $sub ) };
  2         45  
820             }
821              
822 5 100       18319 _croak( $@ ) if $@ ne '';
823              
824 2 50       278 return wantarray ? ( $stdout, $stderr ) : $stdout;
825             }
826              
827             #-------------------------------------------------------
828              
829             sub exec {
830 0     0 1 0 my $self = shift;
831              
832 0         0 local %ENV = %{$self};
  0         0  
833 0         0 exec( @_ );
834             }
835              
836              
837             #-------------------------------------------------------
838              
839             sub which {
840 1     1 1 343 require File::Which;
841 1         840 my $self = shift;
842              
843             {
844 1         2 local %ENV = %{$self};
  1         2  
  1         3  
845 1         4 return File::Which::which( @_ );
846             }
847             }
848              
849             ###############################################
850             ###############################################
851              
852             package App::Env::_app;
853              
854 25     25   240 use Carp();
  25         53  
  25         416  
855 25     25   113 use Storable ();
  25         66  
  25         434  
856 25     25   10584 use Digest;
  25         12951  
  25         639  
857              
858 25     25   137 use strict;
  25         42  
  25         397  
859 25     25   101 use warnings;
  25         37  
  25         20034  
860              
861             # new( pid => $pid, app => $app, opt => \%opt )
862             # new( pid => $pid, env => \%env, module => $module, cacheid => $cacheid )
863             sub new {
864 87     87   353 my ( $class, %opt ) = @_;
865              
866             # make copy of options
867 87         4701 my $self = bless Storable::dclone( \%opt ), $class;
868              
869 87 100       491 if ( exists $self->{env} ) {
870 5 50       98 $self->{opt} = {} unless defined $self->{opt};
871 5         12 $self->{ENV} = delete $self->{env};
872             }
873             else {
874              
875 82         154 ( $self->{module}, my $app_opts ) = eval {
876             App::Env::_require_module(
877             $self->{app},
878             (
879             exists $self->{opt}{Site}
880             ? ( site => $self->{opt}{Site} )
881 82 100       350 : () ) );
882             };
883              
884 82 0       210 _croak(
    50          
885             ref $@
886             ? $@
887             : "error loading application environment module for $self->{app}:\n",
888             $@
889             ) if $@;
890              
891             _croak(
892             "application environment module for $self->{app} does not exist\n" )
893 82 100       263 unless defined $self->{module};
894              
895             # merge possible alias AppOpts
896 81   50     215 $self->{opt}{AppOpts} ||= {};
897 81         177 $self->{opt}{AppOpts} = { %$app_opts, %{ $self->{opt}{AppOpts} } };
  81         206  
898              
899 81         320 $self->mk_cacheid;
900             }
901              
902             # return cached entry if possible
903 86 100 100     207 if ( exists $App::Env::EnvCache{ $self->cacheid } && !$opt{opt}{Force} ) {
904 17         41 $self = $App::Env::EnvCache{ $self->cacheid };
905             }
906              
907             else {
908 69 100       181 $self->load unless $self->{NoLoad};
909 69         147 delete $self->{NoLoad};
910             }
911              
912 86         236 return $self;
913             }
914              
915             #-------------------------------------------------------
916              
917             sub mk_cacheid {
918 83     83   199 my ( $self, $cacheid ) = @_;
919              
920 83 100       240 $cacheid = $self->{opt}{CacheID} unless defined $cacheid;
921              
922 83         122 my @elements;
923              
924 83 100       173 if ( defined $cacheid ) {
925 6 100       21 push @elements, $cacheid eq 'AppID' ? $self->{module} : $cacheid;
926             }
927             else {
928             # create a hash of unique stuff which will be folded
929             # into the cacheid
930 77         108 my %uniq;
931             $uniq{AppOpts} = $self->{opt}{AppOpts}
932 77 100 66     270 if defined $self->{opt}{AppOpts} && keys %{ $self->{opt}{AppOpts} };
  77         324  
933              
934 77         111 my $digest;
935              
936 77 100       184 if ( keys %uniq ) {
937 7         14 local $Storable::canonical = 1;
938 7         32 $digest = Storable::freeze( \%uniq );
939              
940             # use whatever digest aglorithm we can find. if none is
941             # found, default to the frozen representation of the
942             # options
943 7         432 for my $alg ( qw[ SHA-256 SHA-1 MD5 ] ) {
944 7         10 my $ctx = eval { Digest->new( $alg ) };
  7         49  
945              
946 7 50       12458 if ( defined $ctx ) {
947 7         89 $digest = $ctx->add( $digest )->digest;
948 7         45 last;
949             }
950             }
951             }
952 77         204 push @elements, $self->{module}, $digest;
953             }
954              
955 83         183 $self->cacheid( join( $;, grep { defined $_ } @elements ) );
  160         510  
956             }
957              
958              
959             #-------------------------------------------------------
960              
961             sub load {
962 67     67   212 my ( $self ) = @_;
963              
964             # only load if we haven't before
965 67 100       185 return $self->{ENV} if exists $self->{ENV};
966              
967 62         182 my $module = $self->module;
968              
969 62         114 my $envs;
970 62         267 my $fenvs = $module->can( 'envs' );
971              
972 62 50       322 _croak( "$module does not have an 'envs' function\n" )
973             unless $fenvs;
974              
975 62         114 $envs = eval { $fenvs->( $self->{opt}{AppOpts} ) };
  62         311  
976              
977 62 0       1707 _croak( ref $@ ? $@ : "error in ${module}::envs: $@\n" )
    50          
978             if $@;
979              
980             # make copy of environment
981 62         118 $self->{ENV} = { %{$envs} };
  62         820  
982              
983             # cache it
984 62 100       317 $self->cache if $self->{opt}{Cache};
985              
986 62         587 return $self->{ENV};
987             }
988              
989             #-------------------------------------------------------
990              
991             sub cache {
992 42     42   91 my ( $self ) = @_;
993 42         98 $App::Env::EnvCache{ $self->cacheid } = $self;
994             }
995              
996             #-------------------------------------------------------
997              
998             sub uncache {
999 2     2   5 my ( $self ) = @_;
1000 2         6 my $cacheid = $self->cacheid;
1001              
1002             delete $App::Env::EnvCache{$cacheid}
1003             if exists $App::Env::EnvCache{$cacheid}
1004 2 100 66     13 && $App::Env::EnvCache{$cacheid}{pid} eq $self->{pid};
1005             }
1006              
1007             #-------------------------------------------------------
1008              
1009 26 100   26   243 sub _opt { @_ > 1 ? $_[0]->{opt} = $_[1] : $_[0]->{opt} }
1010 319 100   319   1208 sub cacheid { @_ > 1 ? $_[0]->{cacheid} = $_[1] : $_[0]->{cacheid} }
1011 72     72   152 sub module { $_[0]->{module} }
1012              
1013             #-------------------------------------------------------
1014              
1015             1;
1016              
1017             #
1018             # This file is part of App-Env
1019             #
1020             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
1021             #
1022             # This is free software, licensed under:
1023             #
1024             # The GNU General Public License, Version 3, June 2007
1025             #
1026              
1027             __END__