/richenv

Rich environment variable setup for Haskell

Primary LanguageHaskellMIT LicenseMIT

RichEnv

Tests

Hackage Version richenv on Stackage Nightly richenv on Stackage LTS

nixpkgs unstable nixpkgs stable

Rich environment variable setup for Haskell

built with nix

This package exposes a type that captures a set of rules that modify an existing environment variable set, be it a provided list of key-value pairs (list of tuples) or the system's environment variable set.

Each rule can be either a prefix, a mapping or a value. Prefixes take environment variable names and prepend a prefix to them, replacing existing prefixes (i.e. fist removing old prefix, then adding the new one) if desired. Mappings replace the name of the environment variable with a different one, and values create the environment variable with the provided value.

Getting started

The idea behind this library is that you can find a set of rules for setting environment variables that may or may not use the current environment as starting stage, to replace the ones in the current process or pass a custom env to System.Process.CreateProcess to spawn some sub-process.

If your application uses a configuration file, for example in YAML format, you could add a new field to your config like this:

# example.yaml
env:
  values:
    VERBOSE: "true"
  mappings:
    NEWNAME: OLDNAME
  prefixes:
    NEW_PREFIX_:
      - PREFIXED_
      - OTHER_PREFIXED_
    OTHER_NEW_PREFIX_: [OTHER_PREFIXED_]
# More configs ...

When parsing this new env field as the RichEnv type (it provides FromJSON/ToJSON instances), this defines a set of rules:

  • values: these are simple environment variable definitions with a value (in textual format).
  • mappings: these will create new environment variables from existing environment variables on an 1-1 basis. In the YAML config above, a NEWNAME var will be created with the contents of the OLDNAME var.
  • prefixes: these will create new environment variables from existing environment variable by prefix substitution. In the example, environment variables with the prefixes OLD_PREFIX_* and OTHER_OLD_PREFIX_* will all be stripped of the prefix and created with the NEW_PREFIX_* instead.

Thus, after parsing, you will end up with a set of environment variables that you can:

  • Apply to an externally provided list of environment variables and values and then apply the result them to the current process with functions like setRichEnv.
  • Generate an environment variable list of type [(Text, Text)] with toEnvList.
  • Generate a [(String, String)] (with something like fromEnvironment . toEnvList) to pass to System.Process.CreateProcess.
  • etc

You can either provide a list of environment variables (normally of type [(Text, Text)]) to apply RichEnv rules or use the environment variables from the current process.

Code example

Assuming you are using the previous YAML example (and a controlled set of environment variables for the current process, see steps 1 and 2 below), you could use RichEnv modify the environment variables like this:

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}

module Main where

import Data.Aeson (FromJSON, ToJSON)
import Data.Yaml (decodeFileEither)
import Data.Yaml.Aeson (ParseException)
import GHC.Generics (Generic)
import RichEnv (RichEnv (..), clearEnvironment, setRichEnvFromCurrent)
import System.Environment (getEnvironment, setEnv)

newtype SomeConfig = SomeConfig {env :: RichEnv} deriving (Show, Eq, Generic, FromJSON, ToJSON)

main :: IO ()
main = do
  decodedYaml <- decodeFileEither "./example.yaml" :: IO (Either ParseException SomeConfig)
  case decodedYaml of
    Left err -> error $ show err
    Right rEnv -> do
      -- Successfully parsed. Now we can use the RichEnv
      -- 1. clear the environment of the current process
      getEnvironment >>= clearEnvironment
      -- 2. Set an example environment for the current process
      mapM_ (uncurry setEnv) [("FOO", "bar"), ("OLDNAME", "qux"), ("PREFIXED_VAR", "content"), ("OTHER_PREFIXED_VAR2", "content2")]
      -- 3. modify the current environment with the RichEnv
      setRichEnvFromCurrent (env rEnv)
      -- 4. check the environment again
      getEnvironment >>= print

-- printedOutput =
--   [ ("OTHER_NEW_PREFIX_VAR2", "content2"),
--     ("VERBOSE", "true"),
--     ("NEWNAME", "qux"),
--     ("NEW_PREFIX_VAR", "content"),
--     ("NEW_PREFIX_VAR2", "content2")
--   ]
  -- ...

As mentioned before, instead of modifying the current process, you use RichEnv to spawn processes with custom environments (for example with System.Process' proc and CreateProcess' env field) defined with your rules, effectively controlling how the environment is forwarded from the current process to the spawned ones.

See the Hackage documentation and the tests for more details and examples.