load-env 0.1.2 → 0.2.0.0
raw patch · 4 files changed
+129/−28 lines, 4 filesdep +doctestdep +filepathdep +temporary
Dependencies added: doctest, filepath, temporary
Files
- doctest/Main.hs +6/−0
- load-env.cabal +18/−3
- src/LoadEnv.hs +72/−22
- test/LoadEnvSpec.hs +33/−3
+ doctest/Main.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Test.DocTest++main :: IO ()+main = doctest ["-isrc", "src/"]
load-env.cabal view
@@ -1,11 +1,11 @@--- This file has been generated from package.yaml by hpack version 0.20.0.+-- This file has been generated from package.yaml by hpack version 0.28.2. -- -- see: https://github.com/sol/hpack ----- hash: 9c909afbd1a08e860e60320c5f83c51783e382789593a82de5798a86d1464c45+-- hash: b1f053dbd45894ad7e296e47dd667ef92f771066b17032b259e2f8d68b156898 name: load-env-version: 0.1.2+version: 0.2.0.0 synopsis: Load environment variables from a file. description: Parse a .env file and load any declared variables into the current process's environment. This allows for a .env file to specify development-friendly defaults for configuration values normally set in the deployment environment. category: Configuration@@ -29,6 +29,7 @@ build-depends: base >=4.8.0 && <5 , directory+ , filepath , parsec exposed-modules: LoadEnv@@ -37,6 +38,19 @@ Paths_load_env default-language: Haskell2010 +test-suite doctest+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Paths_load_env+ hs-source-dirs:+ doctest+ ghc-options: -Wall+ build-depends:+ base >=4.8.0 && <5+ , doctest+ default-language: Haskell2010+ test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs@@ -49,6 +63,7 @@ , hspec , load-env , parsec+ , temporary other-modules: LoadEnv.ParseSpec LoadEnvSpec
src/LoadEnv.hs view
@@ -1,41 +1,91 @@+-- |+--+-- This is effectively a port of dotenv, whose README explains it best:+--+-- /Storing configuration in the environment is one of the tenets of a+-- twelve-factor app. Anything that is likely to change between deployment+-- environments–such as resource handles for databases or credentials for+-- external services–should be extracted from the code into environment+-- variables./+--+-- /But it is not always practical to set environment variables on development+-- machines or continuous integration servers where multiple projects are run.+-- dotenv loads variables from a .env file into ENV when the environment is+-- bootstrapped./+--+-- <https://github.com/bkeepers/dotenv>+--+-- This library exposes functions for doing just that.+-- module LoadEnv ( loadEnv , loadEnvFrom+ , loadEnvFromAbsolute ) where --import Control.Monad (when)+import Control.Monad ((<=<))+import Data.Bool (bool)+import Data.Foldable (for_, traverse_)+import Data.List (inits) import LoadEnv.Parse-import System.Directory (doesFileExist)+import System.Directory+ (doesFileExist, findFile, getCurrentDirectory, makeAbsolute) import System.Environment (setEnv)+import System.FilePath (isRelative, joinPath, splitDirectories) import Text.Parsec.String (parseFromFile) --- |+-- | @'loadEnvFrom' \".env\"@+loadEnv :: IO ()+loadEnv = loadEnvFrom ".env"++-- | Parse the given file and set variables in the process's environment ----- Parse @./.env@ for variable declariations. Set those variables in the--- currently running process's environment. Variables can be declared in the--- following form:+-- Variables can be declared in the following form: -- -- > FOO=bar -- > FOO="bar" -- > FOO='bar' ----- Declarations may optionally be preceded by @export@, which will be ignored.--- Trailing whitespace is ignored. Quotes inside quoted values or spaces in--- unquoted values must be escaped with a backlash.------ Invalid lines are silently ignored.+-- Declarations may optionally be preceded by @\"export \"@, which will be+-- ignored. Trailing whitespace is ignored. Quotes inside quoted values or+-- spaces in unquoted values must be escaped with a backlash. Invalid lines are+-- silently ignored. ----- If you wish to specify your own file, use @'loadEnvFrom'@. If you wish to--- pass your own string or work with the parse result directly, use the--- lower-level functions available in @"LoadEnv.Parse"@.+-- __NOTE__: If the file-name is relative, the directory tree will be traversed+-- up to @\/@ looking for the file in each parent. Use @'loadEnvFromAbsolute'@+-- to avoid this. ---loadEnv :: IO ()-loadEnv = loadEnvFrom ".env"- loadEnvFrom :: FilePath -> IO ()-loadEnvFrom fp = do- e <- doesFileExist fp+loadEnvFrom name = do+ mFile <- if isRelative name+ then flip findFile name . takeDirectories =<< getCurrentDirectory+ else bool Nothing (Just name) <$> doesFileExist name - when e $ parseFromFile parseEnvironment fp >>=- either print (mapM_ $ uncurry setEnv)+ for_ mFile $ \file -> do+ result <- parseFromFile parseEnvironment file+ either print (traverse_ $ uncurry setEnv) result++-- | @'loadEnvFrom'@, but don't traverse up the directory tree+loadEnvFromAbsolute :: FilePath -> IO ()+loadEnvFromAbsolute = loadEnvFrom <=< makeAbsolute++-- | Get all directory names of a directory+--+-- Includes itself as the first element of the output.+--+-- >>> takeDirectories "/foo/bar/baz"+-- ["/foo/bar/baz","/foo/bar","/foo","/"]+--+-- Leading path-separator is meaningful, and determines if the root directory is+-- included or not.+--+-- >>> takeDirectories "foo/bar/baz"+-- ["foo/bar/baz","foo/bar","foo"]+--+-- Trailing path-separator is not meaningful.+--+-- >>> takeDirectories "/foo/bar/baz/"+-- ["/foo/bar/baz","/foo/bar","/foo","/"]+--+takeDirectories :: FilePath -> [FilePath]+takeDirectories = map joinPath . reverse . drop 1 . inits . splitDirectories
test/LoadEnvSpec.hs view
@@ -4,12 +4,13 @@ import Control.Monad (when) import LoadEnv-import System.Directory (doesFileExist, removeFile)-import System.Environment (lookupEnv)+import System.Directory+import System.Environment+import System.IO.Temp import Test.Hspec spec :: Spec-spec = after_ cleanup $+spec = after_ cleanup $ do describe "loadEnv" $ do it "loads environment variables from ./.env if present" $ do writeFile envFile $ unlines@@ -29,8 +30,37 @@ return () + describe "loadEnvFrom" $ do+ it "traverses up the directory tree" $ do+ inTempDirectory $ do+ writeFile ".env.test" "FOO=\"bar\"\n"+ inNewDirectory "foo/bar/baz" $ do+ loadEnvFrom ".env.test"++ lookupEnv "FOO" `shouldReturn` Just "bar"++ describe "loadEnvFromAbsolute" $ do+ it "does not traverse up the directory tree" $ do+ inTempDirectory $ do+ writeFile ".env.test" "FOO=\"bar\"\n"+ inNewDirectory "foo/bar/baz" $ do+ loadEnvFromAbsolute ".env.test"++ lookupEnv "FOO" `shouldReturn` Nothing++inTempDirectory :: IO a -> IO a+inTempDirectory f =+ withSystemTempDirectory "" $ \tmp -> withCurrentDirectory tmp f++inNewDirectory :: FilePath -> IO a -> IO a+inNewDirectory path f = do+ createDirectoryIfMissing True path+ withCurrentDirectory path f+ cleanup :: IO () cleanup = do+ unsetEnv "FOO"+ unsetEnv "BAR" e <- doesFileExist envFile when e $ removeFile envFile