RichEnv
Rich environment variable setup for Haskell
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.