All Projects → dmjio → Envy

dmjio / Envy

Licence: other
😠 Environmentally friendly environment variables

Programming Languages

haskell
3896 projects

Projects that are alternatives of or similar to Envy

Confex
Useful helper to read and use application configuration from environment variables.
Stars: ✭ 272 (+106.06%)
Mutual labels:  environment-variables, system
ngx-env
Easily inject environment variables into your Angular applications
Stars: ✭ 73 (-44.7%)
Mutual labels:  system, environment-variables
Iglance
Free system monitor for OSX and macOS. See all system information at a glance in the menu bar.
Stars: ✭ 1,358 (+928.79%)
Mutual labels:  system
Go Generics Example
Example code for Go generics
Stars: ✭ 121 (-8.33%)
Mutual labels:  generics
Sysmon
A B/S mode system monitor for linux (demo http://199.247.1.240:2048)
Stars: ✭ 110 (-16.67%)
Mutual labels:  system
Istatserverlinux
A system monitoring daemon that sends stats to Send stats to iStat View for iOS and iStat View for macOS.
Stars: ✭ 100 (-24.24%)
Mutual labels:  system
Cra Runtime Environment Variables
Guide: Runtime environment variables within Create React App
Stars: ✭ 111 (-15.91%)
Mutual labels:  environment-variables
Geotic
Entity Component System library for javascript
Stars: ✭ 97 (-26.52%)
Mutual labels:  system
Asm
Assembly Tutorial for DOS
Stars: ✭ 125 (-5.3%)
Mutual labels:  system
Fsconfig
FsConfig is a F# library for reading configuration data from environment variables and AppSettings with type safety.
Stars: ✭ 108 (-18.18%)
Mutual labels:  environment-variables
Ply
Painless polymorphism
Stars: ✭ 117 (-11.36%)
Mutual labels:  generics
Xattr
Extended attribute support for Go (linux + darwin + freebsd)
Stars: ✭ 107 (-18.94%)
Mutual labels:  system
Userpath
Cross-platform tool for adding locations to the user PATH, no elevated privileges required!
Stars: ✭ 103 (-21.97%)
Mutual labels:  system
U Root
A fully Go userland with Linux bootloaders! u-root can create a one-binary root file system (initramfs) containing a busybox-like set of tools written in Go.
Stars: ✭ 1,816 (+1275.76%)
Mutual labels:  system
Xntsv
XNTSV program for detailed viewing of system structures for Windows.
Stars: ✭ 100 (-24.24%)
Mutual labels:  system
Dialog corpus
用于训练中英文对话系统的语料库 Datasets for Training Chatbot System
Stars: ✭ 1,662 (+1159.09%)
Mutual labels:  system
Bash Oneliner
A collection of handy Bash One-Liners and terminal tricks for data processing and Linux system maintenance.
Stars: ✭ 1,359 (+929.55%)
Mutual labels:  system
Startup
🔧 R package: startup - Friendly R Startup Configuration
Stars: ✭ 107 (-18.94%)
Mutual labels:  environment-variables
Promis
The easiest Future and Promises framework in Swift. No magic. No boilerplate.
Stars: ✭ 110 (-16.67%)
Mutual labels:  generics
Mbp Fedora
Stars: ✭ 129 (-2.27%)
Mutual labels:  system

envy

Hackage Hackage Dependencies Haskell Programming Language BSD3 License Build Status

Let's face it, dealing with environment variables in Haskell isn't that satisfying.

import System.Environment
import Data.Text (pack)
import Text.Read (readMaybe)

data ConnectInfo = ConnectInfo {
  pgPort :: Int
  pgURL  :: Text
} deriving (Show, Eq)

getPGPort :: IO ConnectInfo
getPGPort = do
  portResult <- lookupEnv "PG_PORT"
  urlResult  <- lookupEnv "PG_URL"
  case (portResult, urlResult) of
    (Just port, Just url) ->
      case readMaybe port :: Maybe Int of
	Nothing -> error "PG_PORT isn't a number"
	Just portNum -> return $ ConnectInfo portNum (pack url)
    (Nothing, _) -> error "Couldn't find PG_PORT"
    (_, Nothing) -> error "Couldn't find PG_URL"
    -- Pretty gross right...

Another attempt to remedy the lookup madness is with a MaybeT IO a. See below.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Control.Applicative
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class
import System.Environment

newtype Env a = Env { unEnv :: MaybeT IO a }
    deriving (Functor, Applicative, Monad, MonadIO, Alternative, MonadPlus)

getEnv :: Env a -> IO (Maybe a)
getEnv env = runMaybeT (unEnv env)

env :: String -> Env a
env key = Env (MaybeT (lookupEnv key))

connectInfo :: Env ConnectInfo
connectInfo = ConnectInfo
   <$> env "PG_HOST"
   <*> env "PG_PORT"
   <*> env "PG_USER"
   <*> env "PG_PASS"
   <*> env "PG_DB"

This abstraction falls short in two areas:

  • Lookups don't return any information when a variable doesn't exist (just a Nothing)
  • Lookups don't attempt to parse the returned type into something meaningful (everything is returned as a String because lookupEnv :: String -> IO (Maybe String))

What if we could apply aeson's FromJSON / ToJSON pattern to give us variable lookups that provide both key-lookup and parse failure information? Armed with the GeneralizedNewTypeDeriving extension we can derive instances of Var that will parse to and from an environment variable. The Var typeclass is simply:

class Var a where
  toVar   :: a -> String
  fromVar :: String -> Maybe a

With instances for most concrete and primitive types supported (Word8 - Word64, Int, Integer, String, Text, etc.) the Var class is easily deriveable. The FromEnv typeclass provides a parser type that is an instance of MonadError String and MonadIO. This allows for connection pool initialization inside of our environment parser and custom error handling. The ToEnv class allows us to create an environment configuration given any a. See below for an example.

{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE DeriveDataTypeable         #-}
------------------------------------------------------------------------------
module Main ( main ) where
------------------------------------------------------------------------------
import           Control.Applicative
import           Control.Exception
import           Control.Monad
import           Data.Either
import           Data.Word
import           System.Environment
import           System.Envy
------------------------------------------------------------------------------
data ConnectInfo = ConnectInfo {
      pgHost :: String
    , pgPort :: Word16
    , pgUser :: String
    , pgPass :: String
    , pgDB   :: String
  } deriving (Show)

------------------------------------------------------------------------------
-- | FromEnv instances support popular aeson combinators *and* IO
-- for dealing with connection pool initialization. `env` is equivalent to (.:) in `aeson`
-- and `envMaybe` is equivalent to (.:?), except here the lookups are impure.
instance FromEnv ConnectInfo where
  fromEnv _ =
    ConnectInfo <$> envMaybe "PG_HOST" .!= "localhost"
		<*> env "PG_PORT"
		<*> env "PG_USER"
		<*> env "PG_PASS"
		<*> env "PG_DB"

------------------------------------------------------------------------------
-- | To Environment Instances
-- (.=) is a smart constructor for producing types of `EnvVar` (which ensures
-- that Strings are set properly in an environment so they can be parsed properly
instance ToEnv ConnectInfo where
  toEnv ConnectInfo {..} = makeEnv
       [ "PG_HOST" .= pgHost
       , "PG_PORT" .= pgPort
       , "PG_USER" .= pgUser
       , "PG_PASS" .= pgPass
       , "PG_DB"   .= pgDB
       ]

------------------------------------------------------------------------------
-- | Example
main :: IO ()
main = do
   setEnvironment (toEnv :: EnvList ConnectInfo)
   print =<< do decodeEnv :: IO (Either String ConnectInfo)
   -- unsetEnvironment (toEnv :: EnvList ConnectInfo)  -- remove when done

Our parser might also make use a set of an optional default values provided by the user, for dealing with errors when reading from the environment

instance FromEnv ConnectInfo where
  fromEnv Nothing =
    ConnectInfo <$> envMaybe "PG_HOST" .!= "localhost"
		<*> env "PG_PORT"
		<*> env "PG_USER"
		<*> env "PG_PASS"
		<*> env "PG_DB"

  fromEnv (Just def) =
    ConnectInfo <$> envMaybe "PG_HOST" .!= (pgHost def)
		<*> envMaybe "PG_PORT" .!= (pgPort def)
		<*> env "PG_USER" .!= (pgUser def)
		<*> env "PG_PASS" .!= (pgPass def)
		<*> env "PG_DB" .!= (pgDB def)

Note: As of base 4.7 setEnv and getEnv throw an IOException if a = is present in an environment. envy catches these synchronous exceptions and delivers them purely to the end user.

Generics

As of version 1.0, all FromEnv instance boilerplate can be completely removed thanks to GHC.Generics! Below is an example.

{-# LANGUAGE DeriveGeneric #-}
module Main where

import System.Envy
import GHC.Generics
import System.Environment.Blank

-- This record corresponds to our environment, where the field names become the variable names, and the values the environment variable value
data PGConfig = PGConfig {
    pgHost :: String -- "PG_HOST"
  , pgPort :: Int    -- "PG_PORT"
  } deriving (Generic, Show)

instance FromEnv PGConfig
-- Generically creates instance for retrieving environment variables (PG_HOST, PG_PORT)

main :: IO ()
main = do
  _ <- setEnv "PG_HOST" "valueFromEnv" True
  _ <- setEnv "PG_PORT"  "66354651" True
  print =<< do decodeEnv :: IO (Either String PGConfig)
 -- > PGConfig { pgHost = "valueFromEnv", pgPort = 66354651 }

If the variables are not found in the environment, the parser will currently fail with an error about the first missing field.

The user can decide to provide a default value, whose fields will be used by the generic instance, if retrieving them from the environment fails.

defConfig :: PGConfig
defConfig = PGConfig "localhost" 5432

main :: IO ()
main =
  _ <- setEnv "PG_HOST" "customURL" True
  print =<< decodeWithDefaults defConfig :: IO (Either String PGConfig)
 -- > PGConfig { pgHost = "customURL", pgPort = 5432 }

Suppose you'd like to customize the field name (i.e. add your own prefix, or drop the existing record prefix). This too is possible. See below.

{-# LANGUAGE DeriveGeneric #-}
module Main where

import System.Envy
import GHC.Generics

data PGConfig = PGConfig {
    connectHost :: String -- "PG_HOST"
  , connectPort :: Int    -- "PG_PORT"
  } deriving (Generic, Show)

instance DefConfig PGConfig where
  defConfig = PGConfig "localhost" 5432

-- All fields will be converted to uppercase
instance FromEnv PGConfig where
  fromEnv = gFromEnvCustom Option {
                    dropPrefixCount = 7
                  , customPrefix = "CUSTOM"
		  }

main :: IO ()
main =
  _ <- setEnv "CUSTOM_HOST" "customUrl" True
  print =<< do decodeEnv :: IO (Either String PGConfig)
 -- PGConfig { pgHost = "customUrl", pgPort = 5432 }

It's also possible to avoid typeclasses altogether using runEnv with gFromEnvCustom.

{-# LANGUAGE DeriveGeneric #-}
module Main where

import System.Envy
import GHC.Generics

data PGConfig = PGConfig {
    pgHost :: String -- "PG_HOST"
  , pgPort :: Int    -- "PG_PORT"
  } deriving (Generic, Show)

-- All fields will be converted to uppercase
getPGEnv :: IO (Either String PGConfig)
getPGEnv = runEnv $ gFromEnvCustom defOption
                                   (Just (PGConfig "localhost" 5432))

main :: IO ()
main = print =<< getPGEnv
 -- PGConfig { pgHost = "localhost", pgPort = 5432 }
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].