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   4089824 use 5.00800;
  25         201  
6 25     25   122 use strict;
  25         39  
  25         493  
7 25     25   103 use warnings;
  25         54  
  25         638  
8              
9 25     25   112 use Scalar::Util;
  25         51  
  25         924  
10 25     25   13257 use Storable ();
  25         71584  
  25         600  
11              
12 25     25   11022 use Params::Validate ();
  25         199436  
  25         692  
13              
14             # need to distinguish between a non-existent module
15             # and one which has compile errors.
16 25     25   9305 use Module::Find qw( );
  25         33071  
  25         1062  
17              
18             our $VERSION = '1.02';
19              
20             use overload
21 25         206 '%{}' => '_envhash',
22             '""' => 'str',
23 25     25   164 fallback => 1;
  25         49  
24              
25             #-------------------------------------------------------
26              
27             sub _croak {
28 7     7   122 require Carp;
29 7         1003 goto &Carp::croak;
30             }
31              
32             my %existsModule;
33              
34             sub _loadModuleList {
35 35     35   89 %existsModule = ();
36              
37 35         116 for my $path ( Module::Find::findallmod( 'App::Env' ) ) {
38             # greedy match picks up full part of path
39 367         84919 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
40              
41             # store lowercased module
42 367         947 $existsModule{ $base . '::' . lc $app } = $path;
43             }
44              
45 35         82 return;
46             }
47              
48             sub _existsModule {
49 196     196   462 my ( $path ) = @_;
50              
51             # reconstruct path with lowercased application name.
52             # greedy match picks up full part of path
53 196         1151 my ( $base, $app ) = $path =~ /^(.*)::(.*)/;
54 196         578 $path = $base . '::' . lc $app;
55              
56             # (re)load cache if we can't find the module in the list
57             _loadModuleList
58 196 100       637 unless $existsModule{$path};
59              
60             # really check
61 196         645 return $existsModule{$path};
62             }
63              
64             #-------------------------------------------------------
65              
66             # allow site specific site definition
67 25         43 use constant APP_ENV_SITE => do {
68 25 100 100     142 if ( !exists $ENV{APP_ENV_SITE} && _existsModule( 'App::Env::Site' ) ) {
69 21         46 eval { require App::Env::Site };
  21         6609  
70 21 0       484 _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     88304 && length $ENV{APP_ENV_SITE} ? $ENV{APP_ENV_SITE} : undef;
76 25     25   8622 };
  25         51  
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   434 @_ || return APP_ENV_SITE;
85              
86 17         26 my $site = shift;
87              
88 17 100 100     68 return () if !defined $site || $site eq '';
89 14         32 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   15008 my $this = $_[0];
153              
154             # object method?
155 42 100 66     298 if ( Scalar::Util::blessed $this && $this->isa( __PACKAGE__ ) ) {
156 11         26 my $self = shift;
157 11 50       27 _croak( __PACKAGE__, "->import: too many arguments\n" )
158             if @_;
159              
160 11         20 while ( my ( $key, $value ) = each %{$self} ) {
  307         561  
161 296         732 $ENV{$key} = $value;
162             }
163             }
164              
165             else {
166              
167             # if class method, get rid of class in argument list
168 31 100 66     200 shift if !ref $this && $this eq __PACKAGE__;
169              
170             # if no arguments, nothing to do. "use App::Env;" will cause this.
171 31 100       29734 return unless @_;
172              
173             # if the only argument is a hash, it sets defaults
174 14 100 100     77 if ( @_ == 1 && 'HASH' eq ref $_[0] ) {
175 2         10 config( @_ );
176 2         1681 return;
177             }
178              
179 12         64 App::Env->new( @_ )->import;
180             }
181             }
182              
183              
184             # class method
185             # retrieve a cached environment.
186             sub retrieve {
187              
188 4     4 1 423 my ( $cacheid ) = @_;
189 4         6 my $self;
190              
191 4 100       13 if ( defined $EnvCache{$cacheid} ) {
192 2         8 $self = __PACKAGE__->new();
193 2         6 $self->_var( app => $EnvCache{$cacheid} );
194             }
195              
196 4         11 return $self;
197             }
198              
199             #-------------------------------------------------------
200              
201             sub config {
202 2     2 1 72 my %default = Params::Validate::validate( @_, \%OptionDefaults );
203 2         17 $OptionDefaults{$_}{default} = $default{$_} for keys %default;
204 2         6 return;
205             }
206              
207             #-------------------------------------------------------
208              
209             sub new {
210 78     78 1 48099 my $class = shift;
211              
212 78 100       305 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         206 my $self = bless \{}, $class;
218              
219 78 100       401 $self->_load_envs( @_, $opts ) if @_;
220              
221 77         287 return $self;
222             }
223              
224             #-------------------------------------------------------
225              
226             sub clone {
227 2     2 1 374 my $self = shift;
228              
229 2         30 my %nopt = Params::Validate::validate( @_, \%CloneOptions );
230              
231 2         205 my $clone = Storable::dclone( $self );
232 2         6 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         5  
242 2         6 $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 76     76   138 my $self = shift;
253 76         185 my @opts = ( pop );
254 76         199 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     2529 my %opts = Params::Validate::validate( @opts,
270             @apps == 1 && !ref( $apps[0] )
271             ? \%ApplicationOptions
272             : \%SharedOptions );
273              
274              
275 76 100       457 $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         146 my @cacheids;
281             my @Apps;
282 76         169 for my $app ( @apps ) {
283             # initialize the application specific opts from the shared opts
284 82         320 my %app_opt = %opts;
285              
286             # special filtering of options if this is part of a multi-app
287             # merge
288 82 100       273 if ( @apps > 1 ) {
289             # don't use the shared CacheID option
290 12         22 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         18 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       239 if ( 'ARRAY' eq ref( $app ) ) {
303 1         2 ( $app, my $opts ) = @$app;
304 1 50       3 _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         4 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       213 if ( @apps > 1 ) {
324 12         21 $app_opt{Force} = 1;
325 12         16 $app_opt{Cache} = 0;
326             }
327              
328             # validate possible application options and get default
329             # values. Params::Validate wants a real array
330 82         256 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         5550 %app_opt
338             = ( Params::Validate::validate( @opts, \%ApplicationOptions ) );
339 82         890 my $appo = App::Env::_app->new(
340             pid => $self->lobject_id,
341             app => $app,
342             NoLoad => 1,
343             opt => \%app_opt
344             );
345 81         182 push @cacheids, $appo->cacheid;
346 81         280 push @Apps, $appo;
347             }
348              
349              
350             # create a cacheid for the multi-app environment
351 75   33     411 my $cacheid = $opts{CacheId} || join( $;, @cacheids );
352 75         120 my $App;
353              
354             # use cache if possible
355 75 100 100     508 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       43 if ( $opts{Temp} ) {
359 2         149 $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         8 $App->cacheid( $self->lobject_id );
364              
365             # update Temp compatible options
366             $App->_opt(
367 2         3 { %{ $App->_opt }, map { $_ => $opts{$_} } keys %TempOptions }
  2         6  
  4         14  
368             );
369             }
370              
371             else {
372 16         30 $App = $EnvCache{$cacheid};
373             }
374             }
375              
376             # not cached; is this really just a single application?
377             elsif ( @Apps == 1 ) {
378 52         104 $App = shift @Apps;
379 52         259 $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         232 local %ENV = %ENV;
391              
392 5         16 my @modules;
393 5         12 foreach my $app ( @Apps ) {
394 10         26 push @modules, $app->module;
395              
396             # embrace new merged environment
397 10         15 %ENV = %{ $app->load };
  10         19  
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       44 if ( $opts{Cache} ) { $App->cache; }
  5         17  
409             }
410              
411             # record the final things we need to know.
412 75         254 $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   1056 my $self = shift;
422 841         1175 my $var = shift;
423              
424 841 100       1459 ${$self}->{$var} = shift if @_;
  154         387  
425              
426 841         930 return ${$self}->{$var};
  841         10198  
427             }
428              
429 0     0 1 0 sub module { $_[0]->_app->module }
430 6     6 1 19 sub cacheid { $_[0]->_app->cacheid }
431 0     0   0 sub _cacheid { my $self = shift; $self->app->cacheid( @_ ) }
  0         0  
432 22     22   467 sub _opt { my $self = shift; $self->_app->_opt( @_ ) }
  22         60  
433 588     588   888 sub _app { $_[0]->_var( 'app' ) }
434 555     555   13560 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 155 my $self = shift;
457              
458 88 100       211 return $self->_var( 'id' ) if defined $self->_var( 'id' );
459 77         258 return $self->_var( 'id', ++$Last_ID );
460             }
461             }
462              
463             #-------------------------------------------------------
464              
465             sub cache {
466 3     3 1 7 my ( $self, $cache ) = @_;
467              
468 3 50       8 defined $cache
469             or _croak( "missing or undefined cache argument\n" );
470              
471 3 100       9 if ( $cache ) {
472 1         2 $self->_app->cache;
473             }
474             else {
475 2         7 $self->_app->uncache;
476             }
477             }
478              
479             sub uncache {
480 23     23 1 1483 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       159 if ( $opt{All} ) {
    100          
490 1         3 delete $opt{All};
491             _croak( "can't specify All option with other options\n" )
492 1 50       4 if grep { defined $_ } values %opt;
  2         8  
493              
494 1         26 delete $EnvCache{$_} foreach keys %EnvCache;
495             }
496              
497             elsif ( defined $opt{CacheID} ) {
498 1         4 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         7  
501              
502 1         11 delete $EnvCache{$cacheid};
503             }
504             else {
505             _croak( "must specify App or CacheID options\n" )
506 21 50       45 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       54 $opt{App} ) };
514             }
515              
516 23         61 return;
517             }
518              
519             #-------------------------------------------------------
520              
521             sub _modulename {
522 194     194   339 return join( '::', 'App::Env', grep { defined $_ } @_ );
  300         1017  
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   248 my ( $app, %par ) = @_;
534              
535 88   100     579 my $app_opts = $par{app_opts} ||= {};
536 88   100     391 my $loop = $par{loop} ||= 1;
537              
538 88 50       225 _croak( "too many alias loops for $app\n" )
539             if $loop == 10;
540              
541 88 100       307 my @sites = _App_Env_Site( exists $par{site} ? $par{site} : () );
542              
543             # check possible sites, in turn.
544             my ( $module )
545 173         342 = grep { defined $_ }
546 88         237 ( map { _existsModule( _modulename( $_, $app ) ) } @sites ),
  85         203  
547             _existsModule( _modulename( $app ) );
548              
549 88 100       231 if ( defined $module ) {
550             ## no critic ( ProhibitStringyEval );
551 87 50       5136 eval "require $module"
552             or _croak $@;
553              
554             # see if this is an alias
555 87 100       4783 if ( my $alias = $module->can( 'alias' ) ) {
556 6         19 ( $app, my $napp_opts ) = $alias->();
557 6 100       43 @{$app_opts}{ keys %$napp_opts } = @{$napp_opts}{ keys %$napp_opts }
  2         4  
  2         6  
558             if $napp_opts;
559 6         23 return _require_module(
560             $app, %par,
561             loop => ++$loop,
562             app_opts => $app_opts
563             );
564             }
565             }
566              
567             else {
568 1         5 return;
569             }
570              
571 81         435 return ( $module, $app_opts );
572             }
573              
574             #-------------------------------------------------------
575              
576             sub _exclude_param_check {
577 9 100 100 9   182 !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 31197 my $self = shift;
587 64 100       210 my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );
588              
589             # mostly a duplicate of what's in str(). ick.
590 64         804 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     329 if $opt{Exclude} && ( wantarray() || ( @_ == 1 && !ref $_[0] ) );
      66        
604              
605              
606 64 100       220 my $include = [ @_ ? @_ : qr/.*/ ];
607 64         152 my $env = $self->_envhash;
608              
609 64         176 my @vars = $self->_filter_env( $include, $opt{Exclude} );
610              
611             ## no critic ( ProhibitAccessOfPrivateData )
612 64 100 100     321 if ( wantarray() ) {
    100          
613 1 100       3 return map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  3         12  
614             }
615             elsif ( @_ == 1 && !ref $_[0] ) {
616 33 100       245 return exists $env->{ $vars[0] } ? $env->{ $vars[0] } : undef;
617             }
618             else {
619 30         42 my %env;
620 30 50       48 @env{@vars} = map { exists $env->{$_} ? $env->{$_} : undef } @vars;
  591         1195  
621 30         256 return \%env;
622             }
623             }
624              
625             #-------------------------------------------------------
626              
627             sub setenv {
628 9     9 1 248 my $self = shift;
629 9         15 my $var = shift;
630              
631 9 50       27 defined $var
632             or _croak( "missing variable name argument\n" );
633              
634 9 100       25 if ( @_ ) {
635 8         21 $self->_envhash->{$var} = $_[0];
636             }
637             else {
638 1         2 delete $self->_envhash->{$var};
639             }
640             }
641              
642             #-------------------------------------------------------
643              
644             # return an env compatible string
645             sub str {
646 5     5 1 37700 my $self = shift;
647 5 100       38 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         219 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       61 my $include = [ @_ ? @_ : qr/.*/ ];
661              
662 5 50       15 if ( !grep { $_ eq 'TERMCAP' } @$include ) {
  5         26  
663 5   100     20 $opt{Exclude} ||= [];
664 5 100       32 $opt{Exclude} = [ $opt{Exclude} ] unless 'ARRAY' eq ref $opt{Exclude};
665 5         9 push @{ $opt{Exclude} }, 'TERMCAP';
  5         29  
666             }
667              
668 5         41 my $env = $self->_envhash;
669             ## no critic ( ProhibitAccessOfPrivateData )
670 113         187 my @vars = grep { exists $env->{$_} }
671 5         18 $self->_filter_env( $include, $opt{Exclude} );
672 5         19 return join( ' ', map { "$_=" . _shell_escape( $env->{$_} ) } @vars );
  113         207  
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   145 my ( $self, $included, $excluded ) = @_;
683              
684 69         167 my @exclude = $self->_match_var( $excluded );
685              
686 69         124 my %exclude = map { $_ => 1 } @exclude;
  16         61  
687 69         159 return grep { !$exclude{$_} } $self->_match_var( $included );
  751         1302  
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   226 my ( $self, $match ) = @_;
698              
699 138         237 my $env = $self->_envhash;
700              
701 138 100       339 $match = [$match] unless 'ARRAY' eq ref $match;
702              
703 138         185 my @keys;
704 138         234 for my $spec ( @$match ) {
705 144 100       281 next unless defined $spec;
706              
707 85 100       249 if ( !ref $spec ) {
    100          
    50          
708             # always return a plain name. this allows
709             # @values = $env->env( @names) to work.
710 45         102 push @keys, $spec;
711             }
712             elsif ( 'Regexp' eq ref $spec ) {
713 38         230 push @keys, grep { /$spec/ } keys %$env;
  991         2790  
714             }
715             elsif ( 'CODE' eq ref $spec ) {
716             ## no critic ( ProhibitAccessOfPrivateData )
717 2         35 push @keys, grep { $spec->( $_, $env->{$_} ) } keys %$env;
  58         250  
718             }
719             else {
720 0         0 _croak( "match specification is of unsupported type: ",
721             ref $spec, "\n" );
722             }
723             }
724              
725 138         411 return @keys;
726             }
727              
728             #-------------------------------------------------------
729              
730              
731             sub _shell_escape {
732 113     113   149 my $str = shift;
733              
734             # empty string
735 113 100       172 if ( $str eq '' ) {
736 12         28 $str = "''";
737             }
738              
739             # otherwise, escape all but the "known" non-magic characters.
740             else {
741 101         245 $str =~ s{([^\w/.:=\-+%])}{\\$1}go;
742             }
743              
744 113         336 $str;
745             }
746              
747             #-------------------------------------------------------
748              
749             sub system {
750 7     7 1 6394 my $self = shift;
751              
752 7         30 local %ENV = %{$self};
  7         29  
753 7 100       43 if ( $self->_opt->{SysFatal} ) {
754 4         1391 require IPC::System::Simple;
755 4         17747 return IPC::System::Simple::system( @_ );
756             }
757             else {
758 3         14990 return CORE::system( @_ );
759             }
760             }
761              
762             #-------------------------------------------------------
763              
764             sub qexec {
765 8     8 1 2357 my $self = shift;
766 8         27 local %ENV = %{$self};
  8         58  
767              
768 8         86 require IPC::System::Simple;
769              
770 8         26 my ( @res, $res );
771              
772 8 100       46 if ( wantarray ) {
773 1         3 @res = eval { IPC::System::Simple::capture( @_ ) }
  1         11  
774             }
775             else {
776 7         45 $res = eval { IPC::System::Simple::capture( @_ ) }
  7         48  
777             }
778              
779 8 100       31088 if ( $@ ne '' ) {
780 4 100       76 _croak( $@ ) if $self->_opt->{SysFatal};
781 1         115 return;
782             }
783              
784 4 100       630 return wantarray ? @res : $res;
785             }
786              
787             #-------------------------------------------------------
788              
789             sub capture {
790 5     5 1 25596 my $self = shift;
791 5         32 my @args = @_;
792              
793 5         11 local %ENV = %{$self};
  5         34  
794              
795 5         1255 require Capture::Tiny;
796 5         42859 require IPC::System::Simple;
797              
798             my $sub
799             = $self->_opt->{SysFatal}
800 3     3   3432 ? sub { IPC::System::Simple::system( @args ) }
801 5 100   2   4918 : sub { CORE::system( @args ) };
  2         9654  
802              
803 5         13 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       15 if ( wantarray ) {
815 3         5 ( $stdout, $stderr ) = eval { &Capture::Tiny::capture( $sub ) };
  3         70  
816              
817             }
818             else {
819 2         6 $stdout = eval { &Capture::Tiny::capture( $sub ) };
  2         40  
820             }
821              
822 5 100       17318 _croak( $@ ) if $@ ne '';
823              
824 2 50       270 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 328 require File::Which;
841 1         881 my $self = shift;
842              
843             {
844 1         2 local %ENV = %{$self};
  1         2  
  1         4  
845 1         4 return File::Which::which( @_ );
846             }
847             }
848              
849             ###############################################
850             ###############################################
851              
852             package App::Env::_app;
853              
854 25     25   222 use Carp();
  25         53  
  25         440  
855 25     25   145 use Storable ();
  25         66  
  25         527  
856 25     25   10464 use Digest;
  25         12622  
  25         721  
857              
858 25     25   145 use strict;
  25         44  
  25         439  
859 25     25   106 use warnings;
  25         47  
  25         22245  
860              
861             *_croak = \&App::Env::_croak;
862              
863             # new( pid => $pid, app => $app, opt => \%opt )
864             # new( pid => $pid, env => \%env, module => $module, cacheid => $cacheid )
865             sub new {
866 87     87   388 my ( $class, %opt ) = @_;
867              
868             # make copy of options
869 87         4562 my $self = bless Storable::dclone( \%opt ), $class;
870              
871 87 100       418 if ( exists $self->{env} ) {
872 5 50       14 $self->{opt} = {} unless defined $self->{opt};
873 5         15 $self->{ENV} = delete $self->{env};
874             }
875             else {
876              
877 82         157 ( $self->{module}, my $app_opts ) = eval {
878             App::Env::_require_module(
879             $self->{app},
880             (
881             exists $self->{opt}{Site}
882             ? ( site => $self->{opt}{Site} )
883 82 100       365 : () ) );
884             };
885              
886 82 0       230 _croak(
    50          
887             ref $@
888             ? $@
889             : "error loading application environment module for $self->{app}:\n",
890             $@
891             ) if $@;
892              
893             _croak(
894             "application environment module for $self->{app} does not exist\n" )
895 82 100       213 unless defined $self->{module};
896              
897             # merge possible alias AppOpts
898 81   50     229 $self->{opt}{AppOpts} ||= {};
899 81         191 $self->{opt}{AppOpts} = { %$app_opts, %{ $self->{opt}{AppOpts} } };
  81         227  
900              
901 81         325 $self->mk_cacheid;
902             }
903              
904             # return cached entry if possible
905 86 100 100     220 if ( exists $App::Env::EnvCache{ $self->cacheid } && !$opt{opt}{Force} ) {
906 17         40 $self = $App::Env::EnvCache{ $self->cacheid };
907             }
908              
909             else {
910 69 100       200 $self->load unless $self->{NoLoad};
911 69         142 delete $self->{NoLoad};
912             }
913              
914 86         233 return $self;
915             }
916              
917             #-------------------------------------------------------
918              
919             sub mk_cacheid {
920 83     83   195 my ( $self, $cacheid ) = @_;
921              
922 83 100       305 $cacheid = $self->{opt}{CacheID} unless defined $cacheid;
923              
924 83         145 my @elements;
925              
926 83 100       188 if ( defined $cacheid ) {
927 6 100       21 push @elements, $cacheid eq 'AppID' ? $self->{module} : $cacheid;
928             }
929             else {
930             # create a hash of unique stuff which will be folded
931             # into the cacheid
932 77         112 my %uniq;
933             $uniq{AppOpts} = $self->{opt}{AppOpts}
934 77 100 66     268 if defined $self->{opt}{AppOpts} && keys %{ $self->{opt}{AppOpts} };
  77         338  
935              
936 77         124 my $digest;
937              
938 77 100       267 if ( keys %uniq ) {
939 7         12 local $Storable::canonical = 1;
940 7         31 $digest = Storable::freeze( \%uniq );
941              
942             # use whatever digest aglorithm we can find. if none is
943             # found, default to the frozen representation of the
944             # options
945 7         383 for my $alg ( qw[ SHA-256 SHA-1 MD5 ] ) {
946 7         14 my $ctx = eval { Digest->new( $alg ) };
  7         40  
947              
948 7 50       12401 if ( defined $ctx ) {
949 7         86 $digest = $ctx->add( $digest )->digest;
950 7         34 last;
951             }
952             }
953             }
954 77         233 push @elements, $self->{module}, $digest;
955             }
956              
957 83         197 $self->cacheid( join( $;, grep { defined $_ } @elements ) );
  160         566  
958             }
959              
960              
961             #-------------------------------------------------------
962              
963             sub load {
964 67     67   149 my ( $self ) = @_;
965              
966             # only load if we haven't before
967 67 100       183 return $self->{ENV} if exists $self->{ENV};
968              
969 62         150 my $module = $self->module;
970              
971 62         116 my $envs;
972 62         451 my $fenvs = $module->can( 'envs' );
973              
974 62 50       218 _croak( "$module does not have an 'envs' function\n" )
975             unless $fenvs;
976              
977 62         208 $envs = eval { $fenvs->( $self->{opt}{AppOpts} ) };
  62         216  
978              
979 62 0       1975 _croak( ref $@ ? $@ : "error in ${module}::envs: $@\n" )
    50          
980             if $@;
981              
982             # make copy of environment
983 62         106 $self->{ENV} = { %{$envs} };
  62         805  
984              
985             # cache it
986 62 100       318 $self->cache if $self->{opt}{Cache};
987              
988 62         687 return $self->{ENV};
989             }
990              
991             #-------------------------------------------------------
992              
993             sub cache {
994 42     42   79 my ( $self ) = @_;
995 42         140 $App::Env::EnvCache{ $self->cacheid } = $self;
996             }
997              
998             #-------------------------------------------------------
999              
1000             sub uncache {
1001 2     2   6 my ( $self ) = @_;
1002 2         5 my $cacheid = $self->cacheid;
1003              
1004             delete $App::Env::EnvCache{$cacheid}
1005             if exists $App::Env::EnvCache{$cacheid}
1006 2 100 66     15 && $App::Env::EnvCache{$cacheid}{pid} eq $self->{pid};
1007             }
1008              
1009             #-------------------------------------------------------
1010              
1011 26 100   26   233 sub _opt { @_ > 1 ? $_[0]->{opt} = $_[1] : $_[0]->{opt} }
1012 319 100   319   1327 sub cacheid { @_ > 1 ? $_[0]->{cacheid} = $_[1] : $_[0]->{cacheid} }
1013 72     72   187 sub module { $_[0]->{module} }
1014              
1015             #-------------------------------------------------------
1016              
1017             1;
1018              
1019             #
1020             # This file is part of App-Env
1021             #
1022             # This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.
1023             #
1024             # This is free software, licensed under:
1025             #
1026             # The GNU General Public License, Version 3, June 2007
1027             #
1028              
1029             __END__