App-Env

 view release on metacpan or  search on metacpan

lib/App/Env.pm  view on Meta::CPAN

#    * as a class method, i.e.
#       use App:Env qw( application )
#       App:Env->import( $application )
#
#    * as a class function (just so as not to confuse folks
#       App::Env::import( $application )
#
#    * as an object method
#       $env->import

sub import {

    my $this = $_[0];

    # object method?
    if ( Scalar::Util::blessed $this && $this->isa( __PACKAGE__ ) ) {
        my $self = shift;
        App::Env::_Util::croak( __PACKAGE__, "->import: too many arguments\n" )
          if @_;

        $ENV{$_} = $self->{$_} for keys %$self;    ## no critic (Variables::RequireLocalizedPunctuationVars)
    }

    else {

        # if class method, get rid of class in argument list
        shift if !ref $this && $this eq __PACKAGE__;

        # if no arguments, nothing to do.  "use App::Env;" will cause this.
        return unless @_;

        # if the only argument is a hash, it sets defaults
        if ( @_ == 1 && 'HASH' eq ref $_[0] ) {
            config( @_ );
            return;
        }

        App::Env->new( @_ )->import;
    }
}


# class method
# retrieve a cached environment.
sub retrieve {

    my ( $cacheid ) = @_;
    my $self;

    if ( defined( my $app = App::Env::_Util::getCacheEntry( $cacheid ) ) ) {
        $self = __PACKAGE__->new();
        $self->_var( app => $app );
    }

    return $self;
}

#-------------------------------------------------------

sub config {
    my %default = Params::Validate::validate( @_, \%OptionDefaults );
    $OptionDefaults{$_}{default} = $default{$_} for keys %default;
    return;
}

#-------------------------------------------------------

sub new {
    my $class = shift;

    my $opts = 'HASH' eq ref $_[-1] ? pop : {};

    # %{} is overloaded, so an extra reference is required to avoid
    # an infinite loop when doing things like $self->{}.  instead,
    # use $$self->{}
    my $self = bless \{}, $class;

    $self->_load_envs( @_, $opts ) if @_;

    return $self;
}

#-------------------------------------------------------

sub clone {
    my $self = shift;

    my %nopt = Params::Validate::validate( @_, \%CloneOptions );

    my $clone = Storable::dclone( $self );
    delete ${$clone}->{id};

    # create new cache id
    $clone->_app->mk_cacheid(
        CacheID => defined $nopt{CacheID}
        ? $nopt{CacheID}
        : $self->lobject_id,
    );

    my %opt = ( %{ $clone->_opt }, %nopt );
    $clone->_opt( \%opt );

    $clone->cache( $opt{Cache} );

    return $clone;
}

#-------------------------------------------------------

sub _load_envs {
    my $self = shift;
    my @opts = ( pop );
    my @apps = @_;

    # most of the following logic is for the case where multiple applications
    # are being loaded in one call.  Checking caching requires that we generate
    # a cacheid from the applications' cacheids.

    # if import is called as import( [$app, \%opts], \%shared_opts ),
    # this is equivalent to import( $app, { %shared_opts, %opts } ),
    # but we still validate %shared_opts as SharedOptions, just to be
    # precise.

    # if there's a single application passed as a scalar (rather than
    # an array containing the app name and options), treat @opts as
    # ApplicationOptions, else SharedOptions

    my %opts = Params::Validate::validate( @opts, @apps == 1 && !ref( $apps[0] )
        ? \%ApplicationOptions
        : \%SharedOptions );


    $opts{Cache} = 0 if $opts{Temp};

    # iterate through the applications to ensure that any application specific
    # options are valid and to form a basis for a multi-application
    # cacheid to check for cacheing.
    my @cacheids;
    my @Apps;
    for my $app ( @apps ) {
        # initialize the application specific opts from the shared opts
        my %app_opt = %opts;

        # special filtering of options if this is part of a multi-app
        # merge
        if ( @apps > 1 ) {
            # don't use the shared CacheID option
            delete $app_opt{CacheID};

            # don't cache individual apps in a merged environment,
            # as the cached environments will be polluted.
            delete $app_opt{Cache};

            # ignore a Force option.  This will be turned on later;
            # if set now it will prevent proper error checking
            delete $app_opt{Force};
        }

        # handle application specific options.
        if ( 'ARRAY' eq ref( $app ) ) {
            ( $app, my $opts ) = @$app;
            App::Env::_Util::croak( "$app: application options must be a hashref\n" )
              unless 'HASH' eq ref $opts;

            %app_opt = ( %app_opt, %$opts );

            if ( @apps > 1 ) {
                for my $iopt ( qw( Cache Force ) ) {
                    if ( exists $app_opt{$iopt} ) {
                        App::Env::_Util::croak(
                            "$app: do not specify the $iopt option for individual applications in a merge\n" );
                        delete $app_opt{$iopt};
                    }
                }
            }
        }

        # set forced options for apps in multi-app merges, otherwise
        # the defaults will be set by the call to validate below.
        if ( @apps > 1 ) {
            $app_opt{Force} = 1;
            $app_opt{Cache} = 0;
        }

        # validate possible application options and get default
        # values. Params::Validate wants a real array
        my ( @app_opts ) = %app_opt;

        # return an environment object, but don't load it. we need the
        # module name to create a cacheid for the merged environment.
        # don't load now to prevent unnecessary loading of uncached
        # environments if later it turns out this is a cached
        # multi-application environment
        %app_opt = ( Params::Validate::validate( @app_opts, \%ApplicationOptions ) );
        my $appo = App::Env::_app->new(
            pid    => $self->lobject_id,
            app    => $app,
            NoLoad => 1,
            opt    => \%app_opt,
        );
        push @cacheids, $appo->cacheid;
        push @Apps,     $appo;
    }


    # create a cacheid for the multi-app environment
    my $cacheid = $opts{CacheId} // join( $;, @cacheids );
    my $App;

    # use cache if possible
    if ( !$opts{Force} && defined( my $app = App::Env::_Util::getCacheEntry( $cacheid ) ) ) {
        # if this is a temporary object and a cached version exists,
        # clone it and assign a new cache id.
        if ( $opts{Temp} ) {
            $App = Storable::dclone( $app );

            # should really call $self->cacheid here, but $self
            # doesn't have an app attached to it yet so that'll fail.
            $App->cacheid( $self->lobject_id );

            # update Temp compatible options
            $App->opt( { %{ $App->opt }, map { $_ => $opts{$_} } keys %TempOptions } );
        }

        else {
            $App = $app;
        }
    }

    # not cached; is this really just a single application?
    elsif ( @Apps == 1 ) {
        $App = shift @Apps;
        $App->load;
    }

    # ok, create new environment by iteration through the apps
    else {
        # we don't want to merge environments, as apps may
        # modify a variable that we don't know how to merge.
        # PATH is easy, but we have no idea what might be in
        # others.  so, let the apps handle it.

        # apps get loaded in the current environment.
        local %ENV = %ENV;

        my @modules;
        foreach my $app ( @Apps ) {
            push @modules, $app->module;

            # embrace new merged environment
            %ENV = %{ $app->load };    ## no critic (Variables::RequireLocalizedPunctuationVars)
        }

        $App = App::Env::_app->new(

lib/App/Env.pm  view on Meta::CPAN

    ${$self}->{$var} = shift if @_;

    return ${$self}->{$var};
}

sub module   { $_[0]->_app->module }
sub cacheid  { $_[0]->_app->cacheid }
sub _cacheid { my $self = shift; $self->app->cacheid( @_ ) }
sub _opt {
    my $self = shift;
    $self->_app->opt( @_ );
}
sub _app     { $_[0]->_var( 'app' ) }
sub _envhash { $_[0]->_app->{ENV} }

# would rather use Object::ID but it uses Hash::FieldHash which
# (through no fault of its own:
# http://rt.cpan.org/Ticket/Display.html?id=58030 ) stringify's the
# passed reference on pre 5.10 perls, which causes problems.

# stolen as much as possible from Object::ID to keep the interface the same
{
    my $Last_ID = 'a';

    sub lobject_id {
        my $self = shift;

        return $self->_var( 'id' ) if defined $self->_var( 'id' );
        return $self->_var( 'id', ++$Last_ID );
    }
}

#-------------------------------------------------------

sub cache {
    my ( $self, $cache ) = @_;

    defined $cache
      or App::Env::_Util::croak( "missing or undefined cache argument\n" );

    if ( $cache ) {
        $self->_app->cache;
    }
    else {
        $self->_app->uncache;
    }
}

sub uncache {
    goto \&App::Env::_Util::uncache;
}


#-------------------------------------------------------

sub env {
    my $self = shift;
    my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );

    # mostly a duplicate of what's in str(). ick.
    my %opt = Params::Validate::validate(
        @opts,
        {
            Exclude => {
                callbacks => { 'type' => \&App::Env::_Util::exclude_param_check },
                default   => undef,
            },
            AllowIllegalVariableNames => {
                optional => 1,
                default  => !!1,
            },
        } );

    # Exclude is only allowed in scalar calling context where
    # @_ is empty, has more than one element, or the first element
    # is not a scalar.
    App::Env::_Util::croak( "Cannot use Exclude in this calling context\n" )
      if $opt{Exclude} && ( wantarray() || ( @_ == 1 && !ref $_[0] ) );    ## no critic (Community::Wantarray)

    my $include = [ @_ ? @_ : qr/.*/ ];
    my $env     = $self->_envhash;

    my @exclude
      = defined( $opt{Exclude} )
      ? ( 'ARRAY' eq ref $opt{Exclude} ? @{ $opt{Exclude} } : ( $opt{Exclude} ) )
      : ();

    # exclude any variables with non-word characters
    push @exclude, qr/\W/
      unless $opt{AllowIllegalVariableNames};

    my @vars = $self->_filter_env( $include, \@exclude );

    if ( wantarray() ) {    ## no critic (Community::Wantarray)
        return map { exists $env->{$_} ? $env->{$_} : undef } @vars;
    }
    elsif ( @_ == 1 && !ref $_[0] ) {
        return @vars && exists $env->{ $vars[0] } ? $env->{ $vars[0] } : undef;
    }
    else {
        my %env;
        @env{@vars} = map { exists $env->{$_} ? $env->{$_} : undef } @vars;
        return \%env;
    }
}

#-------------------------------------------------------

sub setenv {
    my $self = shift;
    my $var  = shift;

    defined $var
      or App::Env::_Util::croak( "missing variable name argument\n" );

    if ( @_ ) {
        $self->_envhash->{$var} = $_[0];
    }
    else {
        delete $self->_envhash->{$var};
    }
}

#-------------------------------------------------------

# return an env compatible string
sub str {
    my $self = shift;
    my @opts = ( 'HASH' eq ref $_[-1] ? pop : {} );

    # validate type.  Params::Validate doesn't do Regexp, so
    # this is a bit messy.
    my %opt = Params::Validate::validate(
        @opts,
        {
            Exclude => {
                callbacks => { 'type' => \&App::Env::_Util::exclude_param_check },
                optional  => 1,
            },
            AllowIllegalVariableNames => {
                optional => 1,
                default  => !!0,
            },
        } );

    my $include = [ @_ ? @_ : qr/.*/ ];
    my @exclude
      = defined( $opt{Exclude} )
      ? ( 'ARRAY' eq ref $opt{Exclude} ? @{ $opt{Exclude} } : ( $opt{Exclude} ) )
      : ();

    push @exclude, 'TERMCAP'
      if List::Util::none { $_ eq 'TERMCAP' } @$include;

    # exclude any variables with non-word characters
    push @exclude, qr/\W/
      unless $opt{AllowIllegalVariableNames};

    my $env  = $self->_envhash;
    my @vars = grep { exists $env->{$_} } $self->_filter_env( $include, \@exclude );
    return join( q{ }, map { "$_=" . App::Env::_Util::shell_escape( $env->{$_} ) } @vars );
}

#-------------------------------------------------------

# return a list of included variables, in the requested
# order, based upon a list of include and exclude specs.
# variable names  passed as plain strings are not checked
# for existance in the environment.
sub _filter_env {
    my ( $self, $included, $excluded ) = @_;

    my @exclude = $self->_match_var( $excluded );

    my %exclude = map { $_ => 1 } @exclude;
    return grep { !$exclude{$_} } $self->_match_var( $included );
}

#-------------------------------------------------------

# return a list of variables which matched the specifications.
# this takes a list of scalars, coderefs, or regular expressions.
# variable names  passed as plain strings are not checked
# for existance in the environment.
sub _match_var {
    my ( $self, $match ) = @_;

    my $env = $self->_envhash;

    $match = [$match] unless 'ARRAY' eq ref $match;

    my @keys;
    for my $spec ( @$match ) {



( run in 0.392 second using v1.01-cache-2.11-cpan-39bf76dae61 )