packages feed

dotenv 0.9.0.3 → 0.10.0.0

raw patch · 8 files changed

+158/−64 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Configuration.Dotenv: [allowDuplicates] :: Config -> Bool
+ Configuration.Dotenv: [configVerbose] :: Config -> Bool
+ Configuration.Dotenv: ask :: Monad m => ReaderT r m r
+ Configuration.Dotenv: data ReaderT r m a
+ Configuration.Dotenv: liftReaderT :: m a -> ReaderT r m a
- Configuration.Dotenv: Config :: [FilePath] -> [FilePath] -> Bool -> Config
+ Configuration.Dotenv: Config :: [FilePath] -> [FilePath] -> Bool -> Bool -> Bool -> Config
- Configuration.Dotenv: loadFile :: MonadIO m => Config -> m [(String, String)]
+ Configuration.Dotenv: loadFile :: MonadIO m => Config -> m ()

Files

CHANGELOG.md view
@@ -1,4 +1,8 @@ ## MASTER+## Dotenv 0.10.0.0+### Modified+* `loadFile` change return type (back to `m ()`)+ ## Dotenv 0.9.0.3 ### Added * Parse multi-word command interpolations (Kudos to @pbrisbin)
README.md view
@@ -50,7 +50,7 @@  ### Variable substitution -In order to use compound env vars use the following sintax within your env vars+In order to use compound env vars use the following syntax within your env vars ${your_env_var}. For instance:  ```@@ -66,8 +66,7 @@  ### Command substitution -In order to use the standard output of a command in your env vars use the following-sintax $(your_command). For instance:+In order to use the standard output of a command in your env vars use the following syntax $(your_command). For instance:  ``` DATABASE=postgres://$(whoami)@localhost/database@@ -82,7 +81,7 @@  ### Configuration -The first argument to `loadFile` specifies the configuration. You cans use+The first argument to `loadFile` specifies the configuration. You can use `defaultConfig` which parses the `.env` file in your current directory and doesn't override your envs. You can also define your own configuration with the `Config` type.@@ -99,6 +98,11 @@ (e.g `[".env.example", ".tokens.example", ".public_keys.example"]`). If you don't need this functionality you can set `configExamplePath` to an empty list. +A `False` in the `configVerbose` means that Dotenv will not print any message+when loading the envs. A `True` means that Dotenv will print a message when a variable is loaded.++A `False` on `allowDuplicates` means that Dotenv will not allow duplicate keys, and instead it will throw+an error. A `True` means that Dotenv will allow duplicate keys, and it will use the first one if,`configOverride` is not set to `True`. ### Advanced Dotenv File Syntax  You can add comments to your Dotenv file, on separate lines or after@@ -118,14 +122,13 @@ $ dotenv -f mydotenvfile myprogram ``` -The `-f` flag is optional, by default it looks for the `.env` file in the current-working directory.+The `-f` flag is optional, by default it looks for the `.env` file in the current working directory.  ```shell $ dotenv myprogram ``` -Aditionally you can pass arguments and flags to the program passed to+Additionally, you can pass arguments and flags to the program passed to Dotenv:  ```shell@@ -181,5 +184,5 @@ Do you want to contribute to this project? Please take a look at our [contributing guideline](/docs/CONTRIBUTING.md) to know how you can help us build it.  ----<img src="https://www.stackbuilders.com/media/images/Sb-supports.original.png" alt="Stack Builders" width="50%"></img>+<img src="https://cdn.stackbuilders.com/media/images/Sb-supports.original.png" alt="Stack Builders" width="50%"></img> [Check out our libraries](https://github.com/stackbuilders/) | [Join our team](https://www.stackbuilders.com/join-us/)
app/Main.hs view
@@ -11,10 +11,7 @@ import           Options.Applicative import           Paths_dotenv         (version) -import           Control.Monad        (void)- import           Configuration.Dotenv (Config (..), defaultConfig, loadFile)- import           System.Exit          (exitWith) import           System.Process       (system) @@ -22,6 +19,8 @@   { dotenvFiles        :: [String]   , dotenvExampleFiles :: [String]   , override           :: Bool+  , verbose            :: Bool+  , duplicates         :: Bool   , program            :: String   , args               :: [String]   } deriving (Show)@@ -33,13 +32,15 @@         Config           { configExamplePath = dotenvExampleFiles           , configOverride = override+          , configVerbose = verbose+          , allowDuplicates = duplicates           , configPath =               if null dotenvFiles                 then configPath defaultConfig                 else dotenvFiles           }    in do-     void $ loadFile configDotenv+     loadFile configDotenv      system (program ++ concatMap (" " ++) args) >>= exitWith        where          opts = info (helper <*> versionOption <*> config)@@ -67,6 +68,14 @@      <*> switch ( long "overload"                   <> short 'o'                   <> help "Specify this flag to override existing variables" )++     <*> switch (  long "verbose"+                  <> help "Specify this flag to print out the variables loaded and other useful insights" )++     <*> flag True False (  long "no-dups"+                  <> short 'D'+                  <> help "Specify this flag to forbid duplicate variables"+                  )       <*> argument str (metavar "PROGRAM") 
dotenv.cabal view
@@ -1,5 +1,5 @@ name:                dotenv-version:             0.9.0.3+version:             0.10.0.0 synopsis:            Loads environment variables from dotenv files homepage:            https://github.com/stackbuilders/dotenv-hs description:
spec/Configuration/DotenvSpec.hs view
@@ -12,7 +12,7 @@  import           Test.Hspec -import           Control.Monad                    (liftM, void)+import           Control.Monad                    (liftM) import           Data.Maybe                       (fromMaybe) import           System.Process                   (readCreateProcess, shell) @@ -47,31 +47,30 @@     it "loads the configuration options to the environment from a file" $ do       lookupEnv "DOTENV" `shouldReturn` Nothing -      void $ loadFile $ Config ["spec/fixtures/.dotenv"] [] False+      loadFile sampleConfig        lookupEnv "DOTENV" `shouldReturn` Just "true"      it "respects predefined settings when overload is false" $ do       setEnv "DOTENV" "preset" -      void $ loadFile $ Config ["spec/fixtures/.dotenv"] [] False+      loadFile sampleConfig        lookupEnv "DOTENV" `shouldReturn` Just "preset"      it "overrides predefined settings when overload is true" $ do       setEnv "DOTENV" "preset" -      void $ loadFile $ Config ["spec/fixtures/.dotenv"] [] True+      loadFile sampleConfig { configOverride = True }        lookupEnv "DOTENV" `shouldReturn` Just "true"      context "when the .env.example is present" $ do-      let config = Config ["spec/fixtures/.dotenv"] ["spec/fixtures/.dotenv.example"] False-+      let config = sampleConfig { configExamplePath = ["spec/fixtures/.dotenv.example"]}       context "when the needed env vars are missing" $         it "should fail with an error call" $ do           unsetEnv "ANOTHER_ENV"-          void $ loadFile config `shouldThrow` anyErrorCall+          loadFile config `shouldThrow` anyErrorCall        context "when the needed env vars are not missing" $         it "should succeed when loading all of the needed env vars" $ do@@ -80,7 +79,7 @@           home <- fromMaybe "" <$> lookupEnv "HOME"            -- Load envs-          void $ loadFile config+          loadFile config            -- Check existing envs           lookupEnv "ENVIRONMENT" `shouldReturn` Just home@@ -125,17 +124,29 @@   describe "onMissingFile" $ after_ clearEnvs $ do     context "when target file is present" $       it "loading works as usual" $ do-        void $ onMissingFile (loadFile $ Config ["spec/fixtures/.dotenv"] [] True) (return [])+        onMissingFile (loadFile sampleConfig {configOverride = True}) (return ())         lookupEnv "DOTENV" `shouldReturn` Just "true"      context "when target file is missing" $       it "executes supplied handler instead" $-        onMissingFile (True <$ loadFile (Config ["spec/fixtures/foo"] [] True)) (return False)+        onMissingFile (True <$ loadFile sampleConfig {configPath= ["spec/fixtures/.foo"], configOverride = True}) (return False)           `shouldReturn` False +  describe "onDuplicatedKeys" $ after_ clearEnvs $ do+    context "when duplicates are forbidden" $ do+      it "throws an error on duplicated keys" $+        loadFile sampleConfig { configPath = ["spec/fixtures/.dotenv", "spec/fixtures/.dotenv"], allowDuplicates = False }+          `shouldThrow` anyIOException+      it "works as usual when there is no duplicated keys" $ do+        loadFile sampleConfig { allowDuplicates = False }+        lookupEnv "DOTENV" `shouldReturn` Just "true"++sampleConfig :: Config+sampleConfig = Config ["spec/fixtures/.dotenv"] [] False False True+ clearEnvs :: IO () clearEnvs =-  fmap (fmap fst) getEnvironment >>=  mapM_ unsetEnv+  getEnvironment >>= mapM_ unsetEnv . fmap fst  setupEnv :: IO () setupEnv = do
src/Configuration/Dotenv.hs view
@@ -8,100 +8,136 @@ -- Portability :  portable -- -- This module contains common functions to load and read dotenv files.- {-# LANGUAGE RecordWildCards #-}  module Configuration.Dotenv-  (-  -- * Dotenv Load Functions+  ( -- * Dotenv Load Functions     load   , loadFile   , parseFile   , onMissingFile-  -- * Dotenv Types+      -- * Dotenv Types   , module Configuration.Dotenv.Types-  )- where+  ) where  import           Configuration.Dotenv.Environment    (getEnvironment, lookupEnv,                                                       setEnv) import           Configuration.Dotenv.Parse          (configParser) import           Configuration.Dotenv.ParsedVariable (interpolateParsedVariables)-import           Configuration.Dotenv.Types          (Config (..),-                                                      defaultConfig)-import           Control.Monad                       (liftM)+import           Configuration.Dotenv.Types          (Config (..), ReaderT, ask,+                                                      defaultConfig,+                                                      liftReaderT, runReaderT)+import           Control.Exception                   (throw)+import           Control.Monad                       (when) import           Control.Monad.Catch+import           Control.Monad.Compat                (unless) import           Control.Monad.IO.Class              (MonadIO (..)) import           Data.List                           (intersectBy, union,                                                       unionBy) import           System.IO.Error                     (isDoesNotExistError) import           Text.Megaparsec                     (errorBundlePretty, parse) +-- | Monad Stack for the application+type DotEnv m a = ReaderT Config m a+ -- | Loads the given list of options into the environment. Optionally -- override existing variables with values from Dotenv files. load ::-  MonadIO m =>-  Bool -- ^ Override existing settings?+     MonadIO m+  => Bool -- ^ Override existing settings?   -> [(String, String)] -- ^ List of values to be set in environment   -> m ()-load override = mapM_ (applySetting override)+load override kv =+  runReaderT (mapM_ applySetting kv) defaultConfig {configOverride = override}  -- | @loadFile@ parses the environment variables defined in the dotenv example -- file and checks if they are defined in the dotenv file or in the environment. -- It also allows to override the environment variables defined in the environment -- with the values defined in the dotenv file.-loadFile-  :: MonadIO m+loadFile ::+     MonadIO m   => Config -- ^ Dotenv configuration-  -> m [(String, String)] -- ^ Environment variables loaded-loadFile Config{..} = do+  -> m ()+loadFile config@Config {..} = do   environment <- liftIO getEnvironment-  readedVars <- concat `liftM` mapM parseFile configPath-  neededVars <- concat `liftM` mapM parseFile configExamplePath-  let coincidences = (environment `union` readedVars) `intersectEnvs` neededVars+  readVars <- fmap concat (mapM parseFile configPath)+  neededVars <- fmap concat (mapM parseFile configExamplePath)+  let coincidences = (environment `union` readVars) `intersectEnvs` neededVars       cmpEnvs env1 env2 = fst env1 == fst env2       intersectEnvs = intersectBy cmpEnvs       unionEnvs = unionBy cmpEnvs       vars =         if (not . null) neededVars-          then-            if length neededVars == length coincidences-              then readedVars `unionEnvs` neededVars-              else error $ "Missing env vars! Please, check (this/these) var(s) (is/are) set:" ++ concatMap ((++) " " . fst) neededVars-          else readedVars-  mapM (applySetting configOverride) vars+          then if length neededVars == length coincidences+                 then readVars `unionEnvs` neededVars+                 else error $+                      "Missing env vars! Please, check (this/these) var(s) (is/are) set:" +++                      concatMap ((++) " " . fst) neededVars+          else readVars+  unless allowDuplicates $ (lookUpDuplicates . map fst) vars+  runReaderT (mapM_ applySetting vars) config  -- | Parses the given dotenv file and returns values /without/ adding them to -- the environment. parseFile ::-  MonadIO m =>-  FilePath -- ^ A file containing options to read+     MonadIO m+  => FilePath -- ^ A file containing options to read   -> m [(String, String)] -- ^ Variables contained in the file parseFile f = do   contents <- liftIO $ readFile f-   case parse configParser f contents of     Left e        -> error $ errorBundlePretty e     Right options -> liftIO $ interpolateParsedVariables options -applySetting :: MonadIO m => Bool -> (String, String) -> m (String, String)-applySetting override (key, value) =-  if override-    then liftIO (setEnv key value) >> return (key, value)+applySetting ::+     MonadIO m+  => (String, String) -- ^ A key-value pair to set in the environment+  -> DotEnv m (String, String)+applySetting kv@(k, v) = do+  Config {..} <- ask+  if configOverride+    then info kv >> setEnv'     else do-      res <- liftIO $ lookupEnv key-+      res <- liftReaderT . liftIO $ lookupEnv k       case res of-        Nothing -> liftIO $ setEnv key value >> return (key, value)-        Just _  -> return (key, value)+        Nothing -> info kv >> setEnv'+        Just _  -> return kv+  where+    setEnv' = liftReaderT . liftIO $ setEnv k v >> return kv +-- | The function logs in console when a variable is loaded into the+-- environment.+info :: MonadIO m => (String, String) -> DotEnv m ()+info (key, value) = do+  Config {..} <- ask+  when configVerbose $+    liftReaderT . liftIO $+    putStrLn $ "[INFO]: Load env '" ++ key ++ "' with value '" ++ value ++ "'"+ -- | The helper allows to avoid exceptions in the case of missing files and -- perform some action instead. -- -- @since 0.3.1.0--onMissingFile :: MonadCatch m+onMissingFile ::+     MonadCatch m   => m a -- ^ Action to perform that may fail because of missing file-  -> m a               -- ^ Action to perform if file is indeed missing+  -> m a -- ^ Action to perform if file is indeed missing   -> m a onMissingFile f h = catchIf isDoesNotExistError f (const h)++-- | The helper throws an exception if the allow duplicate is set to False.+forbidDuplicates :: MonadIO m => String -> m ()+forbidDuplicates key =+  throw $+  userError $+  "[ERROR]: Env '" +++  key +++  "' is duplicated in a dotenv file. Please, fix that (or remove --no-dups)."++lookUpDuplicates :: MonadIO m => [String] -> m ()+lookUpDuplicates [] = return ()+lookUpDuplicates [_] = return ()+lookUpDuplicates (x:xs) =+  if x `elem` xs+    then forbidDuplicates x+    else lookUpDuplicates xs
src/Configuration/Dotenv/Parse.hs view
@@ -70,7 +70,7 @@  interpolatedValueVarInterpolation :: Parser VarFragment interpolatedValueVarInterpolation = VarInterpolation <$>-                            ((between (symbol "${") (symbol "}") variableName) <|>+                            (between (symbol "${") (symbol "}") variableName <|>                             (char '$' >> variableName))   where     symbol                = L.symbol sc
src/Configuration/Dotenv/Types.hs view
@@ -12,6 +12,10 @@ module Configuration.Dotenv.Types   ( Config(..)   , defaultConfig+  , ask+  , runReaderT+  , liftReaderT+  , ReaderT   )   where @@ -20,6 +24,8 @@   { configPath        :: [FilePath] -- ^ The paths for the .env files   , configExamplePath :: [FilePath] -- ^ The paths for the .env.example files   , configOverride    :: Bool     -- ^ Flag to allow override env variables+  , configVerbose     :: Bool     -- ^ Flag to log the loaded variables and other useful information+  , allowDuplicates   :: Bool     -- ^ Flag to allow duplicate variables   } deriving (Eq, Show)  -- | Default configuration. Use .env file without .env.example strict envs and@@ -30,4 +36,29 @@     { configExamplePath = []     , configOverride = False     , configPath = [ ".env" ]+    , configVerbose = False+    , allowDuplicates = True     }+++newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }+mapReaderT :: (m a -> n b) -> ReaderT r m a -> ReaderT r n b+mapReaderT f m = ReaderT $ f . runReaderT m+instance (Functor m) => Functor (ReaderT r m) where+    fmap f  = mapReaderT (fmap f)+instance (Applicative m) => Applicative (ReaderT r m) where+    pure    = liftReaderT . pure+    f <*> v = ReaderT $ \ r -> runReaderT f r <*> runReaderT v r++instance (Monad m) => Monad (ReaderT r m) where+    return   = liftReaderT . return+    m >>= k  = ReaderT $ \ r -> do+      a <- runReaderT m r+      runReaderT (k a) r+    m >> k = ReaderT $ \ r -> runReaderT m r >> runReaderT k r++liftReaderT :: m a -> ReaderT r m a+liftReaderT m = ReaderT (const m)++ask :: (Monad m) => ReaderT r m r+ask = ReaderT return