# NAME

App::Env - manage application specific environments

# VERSION

version 0.36

# SYNOPSIS

    # import environment from application1 then application2 into current
    # environment
    use App::Env ( $application1, $application2, \%opts );

    # import an environment at your leisure
    use App::Env;
    App::Env::import( $application, \%opts );

    # set defaults
    use App::Env ( \%defaults )
    App::Env::config( %defaults );

    # retrieve an environment but don't import it
    $env = App::Env->new( $application, \%opts );

    # execute a command in that environment; just as a convenience
    $env->system( $command );

    # exec a command in that environment; just as a convenience
    $env->exec( $command );

    # oh bother, just import the environment
    $env->import;

    # cache this environment as the default for $application
    $env->cache( 1 );

    # uncache this environment if it is the default for $application
    $env->cache( 0 );

    # generate a string compatible with the *NIX env command
    $envstr = $env->str( \%opts );

    # or, stringify it for (mostly) the same result
    system( 'env -i $env command' );

    # pretend it's a hash; read only, though
    %ENV = %$env;

# DESCRIPTION

**App::Env** presents a uniform interface to initializing environments
for applications which require special environments.  **App::Env** only
handles the loading, merging, and caching of environments; it does not
create them.  That is done within modules for each application suite
(e.g. **App::Env::MyApp**).  **App::Env** ships with two such modules,
**App::Env::Null** which simply returns a snapshot of the current
environment, and **App::Env::Example**, which provides example code for
creating an application specific environment.

**App::Env** is probably most useful in situations where a Perl program
must invoke multiple applications each of which may require an
environment different and possibly incompatible from the others.  The
simplified interface it provides makes it useful even in less
complicated situations.

## Initializing Application Environments

As mentioned above, **App::Env** does not itself provide the
environments for applications; it relies upon application specific
Perl modules to do so.  Such modules must provide an **envs()**
function which should return a hash reference containing the
environment.  Application specific options (e.g. version) may be
passed to the module.

See **App::Env::Example** for information on how to write such modules.

## Managing Environments

In the simplest usage, **App::Env** can merge (`import`) the
application's environment directly into the current environment.
For situations where multiple incompatible environments are required,
it can encapsulate those as objects with convenience methods to
easily run applications within those environments.

## Environment Caching

Environments are (by default) cached to improve performance; the
default cache id is generated from the name of the Perl module
which creates the environment and the options passed to it.
signature.  When a environment is requested its signature is compared
against those stored in the cache and if matched, the associated
cached environment is returned.

The cache id is (by default) generated from the full module name
(beginning with `App::Env` and including the optional site path --
see ["Site Specific Contexts"](#site-specific-contexts)) and the contents of the **AppOpts**
hash passed to the module.  If the **AppOpts** hash is empty, the id is
just the module name.  The cache id may be explicitly specified with
the `CacheID` option.

If `CacheID` is set to the string `AppID` the cache id is set to the
full module name, ignoring the contents of **AppOpts**.  This is useful
if an application wishes to load an environment using special options
but make it available under the more generic cache id.

To prevent caching, use the `Cache` option. It doesn't prevent
**App::Env** from _retrieving_ an existing cached environment -- to do
that, use the `Force` option, which will result in a freshly
generated environment.

To retrieve a cached environment using its cache id use the
**retrieve()** function.

If multiple applications are loaded via a single call to **import** or
**new** the applications will be loaded incrementally in the order
specified.  In order to ensure a properly merged environment the
applications will be loaded freshly (any caches will be ignored) and
the merged environment will be cached.  The cache id will by default
be generated from all of the names of the environment modules invoked;
again, this can be overridden using the **CacheID** option.

## Application Aliases

**App::Env** performs a case-insensitive search for application
modules.  For example, if the application module is named
**App::Env::CIAO**, a request for `ciao` will resolve to it.

Explicit aliases are also possible. A module should be created for
each alias with the single class method **alias** which should return
the name of the original application.  For example, to make `App3` be
an alias for `App1` create the following `App3.pm` module:

    package App::Env::App3;
    sub alias { return 'App1' };
    1;

The aliased environment can provide presets for **AppOpts** by returning
a hash as well as the application name:

    package App::Env::ciao34;
    sub alias { return 'CIAO', { Version => 3.4 } };
    1;

These will be merged with any `AppOpts` passed in via **import()**, with
the latter taking precedence.

## Site Specific Contexts

In some situations an application's environment will depend upon which
host or network it is executed on.  In such instances **App::Env**
provides a means for loading an alternate application module.  It does
this by loading the first existent module from the following set of
module names:

    App::Env::$SITE::$app
    App::Env::$app

The `$SITE` variable is taken from the environment variable
**APP\_ENV\_SITE** if it exists, or from the **Site** option to the class
**import()** function or the **new()** object constructor.
Additionally, if the **APP\_ENV\_SITE** environment variable does _not
exist_ (it is not merely empty), **App::Env** will first attempt to
load the **App::Env::Site** module, which can set the **APP\_ENV\_SITE**
environment variable.

Take as an example the situation where an application's environment is
stored in `/usr/local/myapp/setup` on one host and
`/opt/local/myapp/setup` on another.  One could include logic in a
single `App::Env::myapp` module which would recognize which file is
appropriate.  If there are multiple applications, this gets messy.  A
cleaner method is to have separate site-specific modules (e.g.
`App::Env::LAN1::myapp` and `App::Env::LAN2::myapp`), and switch
between them based upon the **APP\_ENV\_SITE** environment variable.

The logic for setting that variable might be encoded in an
**App::Env::Site** module to transparently automate things:

    package App::Env::Site;

    my %LAN1 = map { ( $_ => 1 ) } qw( sneezy breezy queasy );
    my %LAN2 = map { ( $_ => 1 ) } qw( dopey  mopey  ropey  );

    use Sys::Hostname;

    if ( $LAN1{hostname()} )
    {
      $ENV{APP_ENV_SITE} = 'LAN1';
    }
    elsif ( $LAN2{hostname()} )
    {
      $ENV{APP_ENV_SITE} = 'LAN2';
    }

    1;

## The Null Environment

**App::Env** provides the `null` environment, which simply returns a
snapshot of the current environment.  This may be useful to provide
fall-backs in case an application specific environment was not found,
but the code should fallback to using the existing environment.

    $env = eval { App::Env->new( "MyApp" ) } \
       // App::Env->new( "null", { Force => 1, Cache => 0 } );

As the `null` environment is a _snapshot_ of the current
environment, if future `null` environments should reflect the
environment at the time they are constructed, C"null" environments
should not be cached (e.g. `Cache => 0`).  The `Force => 1`
option is specified to ensure that the environment is not being read
from cache, just in case a prior `null` environment was inadvertently
cached.

# INTERFACE

**App::Env** may be used to directly import an application's
environment into the current environment, in which case the
non-object oriented interface will suffice.

For more complicated uses, the object oriented interface allows for
manipulating multiple separate environments.

## Using **App::Env** without objects

Application environments may be imported into the current environment
either when loading **App::Env** or via the **App::Env::import()**
function.

- import

        use App::Env ( $application, \%options );
        use App::Env ( @applications, \%shared_options );

        App::Env::import( $application, \%options );
        App::Env::import( @applications, \%shared_options );

    Import the specified applications.

    Options may be applied to specific applications by grouping
    application names and option hashes in arrays:

        use App::Env ( [ 'app1', \%app1_options ],
                       [ 'app2', \%app2_options ],
                       \%shared_options );

        App::Env::import( [ 'app1', \%app1_options ],
                          [ 'app2', \%app2_options ],
                          \%shared_options );

    Shared (or default) values for options may be specified in a hash passed as
    the last argument.

    The available options are listed below.  Not all options may be shared; these
    are noted.

    - AppOpts _hashref_

        This is a hash of options to pass to the
        `App::Env::<application>` module.  Their meanings are
        application specific.

        This option may not be shared.

    - Force _boolean_

        Don't use the cached environment for this application.

    - Site

        Specify a site.  See ["Application Environments"](#application-environments) for more information

    - Cache _boolean_

        Cache (or don't cache) the environment. By default it is cached.  If
        multiple environments are loaded the _combination_ is also cached.

    - CacheID

        A unique name for the environment. See ["Environment Caching"](#environment-caching) for more information.

        When used as a shared option for multiple applications, this will be
        used to identify the merged environment.  If set to the string
        `AppID`, the full module name will be used as the cache id (ignoring
        the contents of the **AppOpts** option hash).

    - SysFatal _boolean_

        If true, the **system**, **qexec**, and **capture** object methods will throw
        an exception if the passed command exits with a non-zero error.

    - Temp _boolean_

        If true, and the requested environment does not exist in the cache,
        create it but do not cache it (this overrides the **Cache** option).
        If the requested environment does exist in the cache, return an
        non-cached clone of it.  The following options are updated in
        the cloned environment:

            SysFatal

- retrieve

        $env = App::Env::retrieve( $cacheid );

    Retrieve the environment with the given cache id, or undefined if it
    doesn't exist.

## Managing Environments

- config

        App::Env::config( %Defaults );

    Configure default options for environments.  See ["Changing Default
    Option Values"](#changing-default-option-values) for more information.

- uncache

        App::Env::uncache( App => $app, [ Site => $site ] )
        App::Env::uncache( CacheID => $cacheid )

    Delete the cache entry for the given application.  If `Site` is not
    specified, the site is determined as specified in ["Site Specific
    Contexts"](#site-specific-contexts).

    It is currently _not_ possible to use this interface to
    explicitly uncache multi-application environments if they have not
    been given a unique cache id.  It is possible using **App::Env**
    objects.

    The available options are:

    - App

        The application name.  This may not be specified if **CacheID** is
        specified.

    - Site

        If the **Site** option was used when first loading the environment,
        it must be specified here in order to delete the correct cache entry.
        Do not specify this option if **CacheID** is specified.

    - CacheID

        If the **CacheID** option was used to provide a cache key for the cache
        entry, this must be specified here.  Do not specify this option if
        **App** or **Site** are specified.

    - All

        If true uncache all of the cached environments.

## Using **App::Env** objects

**App::Env** objects give greater flexibility when dealing with
multiple applications with incompatible environments.

### Constructors

- new

        $env = App::Env->new( ... )

    **new** takes the same arguments as **App::Env::import** and returns
    an **App::Env** object.  It does not modify the environment.

- clone

        $clone = $app->clone( \%opts );

    Clone an existing environment.  The available options are `CacheID`,
    `Cache`, `SysFatal` (see the documentation for the **import** function).

    The cloned environment is by default not cached.  If caching is
    requested and a cache id is not provided, a unique id is created --
    it will _not_ be the same as that of the original environment.

    This generated cache id is not based on a signature of the
    environment, so this environment will effectively not be automatically
    reused when a similar environment is requested via the **new**
    constructor (see ["Environment Caching"](#environment-caching)).

### Overloaded operators

**App::Env** overloads the %{} and "" operators.  When
dereferenced as a hash an **App::Env** object returns a hash of
the environmental variables:

    %ENV = %$env;

When interpolated in a string, it is replaced with a string suitable
for use with the \*NIX **env** command; see the **str()** method below
for its format.

### Methods

- cache

        $env->cache( $cache_state );

    If `$cache_state` is true, cache this environment using the object's
    cache id.  If `$cache_state` is false and this environment is being
    cached, delete the cache.

    Note that only the original **App::Env** object which cached the
    environment may delete it.  Objects which reuse existing, cached,
    environments cannot.

- cacheid

        $cacheid = $env->cacheid;

    Returns the cache id for this environment.

- env

        # return a hashref of the entire environment (similar to %{$env})
        $hashref = $env->env( );

        # return the value of a given variable in the environment
        $value = $env->env( $variable_name )

        # return an array of values of particular variables.
        # names should be strings
        @values = $env->env( @variable_names );

        # match variable names and return a hashref
        $hashref = $env->env( @match_specifications );

        # exclude specific variables
        $hashref = $env->env( { Exclude => $match_spec   } );
        $hashref = $env->env( { Exclude => \@match_specs } );
        $hashref = $env->env( @match_specs, { Exclude => $match_spec   } );
        $hashref = $env->env( @match_specs, { Exclude => \@match_specs } );

    Return all or parts of the environment.  What is returned
    depends upon the type of argument and which of the
    following contexts matches:

    1. If called with no arguments (or just an **Exclude** option,
    as discussed below) return a hashref containing the environment.
    2. If called in a scalar context and passed a single variable name
    (which must be a string) return the value for that variable,
    or _undef_ if it is not in the environment.
    3. If called in a list context and passed a list of variable names
    (which must be strings) return an array of values for those variables
    (_undef_ for those not in the environment).
    4. If called in a scalar context and passed one or more _match
    specifications_, return a hashref containing the subset
    of the environment which matches.  The `Exclude` option (see below)
    may be present.

        A _match specification_ may be a string, (for an exact match of a
        variable name), a regular expression created with the **qr** operator,
        or a subroutine reference.  The subroutine will be passed two
        arguments, the variable name and its value, and should return true if
        the variable should be excluded, false otherwise.

        To avoid mistaking this context for context 1 if the _match specification_
        is a single string, enclose it in an array, e.g.

            # this is context 1
            $value = $env->env( $variable_name );

            # this is context 3
            $hash = $env->env( [ $variable_name ] );

    Variable names may be excluded from the list by passing a hash with
    the key `Exclude` as the last argument (valid only in contexts 0 and
    3).  The value is either a scalar or an arrayref composed of match
    specifications (as an arrayref) as described in context 3.

- setenv

        # set an environmental variable
        $env->setenv( $var, $value );

        # delete an environmetal variable
        $env->setenv( $var );

    If `$value` is present, assign it to the named environmental
    variable.  If it is not present, delete the variable.

    **Note:** If the environment refers to a cached environment, this will
    affect all instances of the environment which share the cache.

- module

        $module = $env->module;

    This returns the name of the module which was used to load the
    environment.  If multiple modules were used, the names are
    concatenated, separated by the `$;` (subscript separator) character.

- str

        $envstr = $env->str( @match_specifications, \%options );

    This function returns a string which may be used with the \*NIX **env**
    command to set the environment.  The string contains space separated
    `var=value` pairs, with shell magic characters escaped.

    The environment may be pared down by passing _match specifications_
    and an `Exclude` option; see the documentation for the **env** method,
    context 3, for more information.

    Because the **TERMCAP** environment variable is often riddled with
    escape characters, which are not always handled well by shells, the
    **TERMCAP** variable is _always_ excluded unless it is explicitly
    included via an exact variable name match specification. For example,

        $envstr = $env->str( qr/.*/, 'TERMCAP );

    is the only means of getting all of the environment returned.

- system

        $env->system( $command, @args );

    This runs the passed command in the environment defined by **$env**.
    It has the same argument and returned value convention as the core
    Perl **system** command.

    If the **SysFatal** flag is set for this environment,
    **IPC::System::Simple::system** is called, which will cause this method
    to throw an exception if the command returned a non-zero exit value.
    It also avoid invoking a shell to run the command if possible.

- exec

        $env->exec( $command, @args );

    This execs the passed command in the environment defined by **$env**.
    It has the same argument and returned value convention as the core
    Perl **exec** command.

- qexec

        $output = $env->qexec( $command, @args );
        @lines = $env->qexec( $command, @args );

    This acts like the **qx{}** Perl operator.  It executes the passed
    command in the environment defined by **$env** and returns its
    (standard) output.  If called in a list context the output is
    split into lines.

    If the **SysFatal** flag is set for this environment,
    **IPC::System::Simple::capture** is called, which will cause this
    method to throw an exception if the command returned a non-zero exit
    value.  It also avoid invoking a shell to run the command if possible.

- capture

        $stdout = $env->capture( $command, @args );
        ($stdout, $stderr) = $env->capture( $command, @args );

    Execute the passed command in the environment defined by **$env** and
    returns content of its standard output and (optionally) standard error
    streams.

    If the **SysFatal** flag is set for this environment,
    **IPC::System::Simple::capture** is called, which will cause this
    method to throw an exception if the command returned a non-zero exit
    value.  It also avoid invoking a shell to run the command if possible.

- which

        $path = $env->which( $command );
        @paths = $env->which( $command );

    Return the path (or paths in list mode) of the passed command using
    [File::Which](https://metacpan.org/pod/File::Which).  It returns `undef` or an empty list if the command
    is not found.

## Changing Default Option Values

Default values for some options may be changed via any of the
following:

- Passing a hashref as the only argument when initially importing the
package:

        use App::Env \%Default;

- Calling the **config** function:

        App::Env::config( %Default );

The following options may have their default values changed:

    Force  Cache  Site  SysFatal

# EXAMPLE USAGE

## A single application

This is the simplest case.  If you don't care if you "pollute" the
current environment, then simply

    use App::Env qw( ApplicationName );

## A single application with options

If the **CIAO** environment module provides a `Version` option:

    use App::Env ( 'CIAO', { AppOpts => { Version => 3.4 } } );

## Two compatible applications

If two applications can share an environment, and you don't mind
changing the current environment;

    use App::Env qw( Application1 Application2 );

If you need to preserve the environment you need to be a little more
circumspect.

    $env = App::Env->new( qw( Application1 Application 2 ) );
    $env->system( $command1, @args );
    $env->system( $command2, @args );

or even

    $env->system( "$command1 | $command2" );

Or,

    {
      local %ENV = %$env;
      system( $command1);
    }

if you prefer not to use the **system** method.

## Two incompatible applications

If two applications can't share the environment, you'll need to
load them separately:

    $env1 = App::Env->new( 'Application1' );
    $env2 = App::Env->new( 'Application2' );

    $env1->system( $command1 );
    $env2->system( $command2 );

Things are trickier if you need to construct a pipeline.  That's where
the \*NIX **env** command and **App::Env** object stringification come
into play:

    system( "env -i $env1 $command1 | env -i $env2 $command2" );

This hopefully won't overfill the shell's command buffer. If you need
to specify only parts of the environment, use the **str** method to
explicitly create the arguments to the **env** command.

## Localizing changes to an environment

In some contexts an environment must be customized but the changes
shouldn't propagate into the (possibly) cached version.  A good
example of this is in sandboxing functions which may manipulate an
environment.

The **new()** constructor doesn't indicate whether an environment was
freshly constructed or pulled from cache, so the user can't tell if
manipulating it will affect other code paths.  One way around this is
to force construction of a fresh environment using the `Force` option
and turning off caching via the `Cache` option.

This guarantees isolation but is inefficient (if a compatible
environment is cached it won't be used) and any tweaks made to the
environment by the application are not seen.  Instead, use the `Temp`
option; this will either create a new environment if none exists or
clone an existing one.  In either case the result won't be cached and
any changes will be localized.

# SUPPORT

## Bugs

Please report any bugs or feature requests to bug-app-env@rt.cpan.org  or through the web interface at: https://rt.cpan.org/Public/Dist/Display.html?Name=App-Env

## Source

Source is available at

    https://gitlab.com/djerius/App-Env

and may be cloned from

    https://gitlab.com/djerius/App-Env.git

# SEE ALSO

Please see those modules/websites for more information related to this module.

- [**appexec**](https://metacpan.org/pod/appexec)

# AUTHOR

Diab Jerius <djerius@cpan.org>

# COPYRIGHT AND LICENSE

This software is Copyright (c) 2018 by Smithsonian Astrophysical Observatory.

This is free software, licensed under:

    The GNU General Public License, Version 3, June 2007
