packages feed

dotenv 0.4.0.0 → 0.5.0.0

raw patch · 7 files changed

+185/−46 lines, 7 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Configuration.Dotenv.Types: Config :: [FilePath] -> [FilePath] -> Bool -> Config
+ Configuration.Dotenv.Types: [configExamplePath] :: Config -> [FilePath]
+ Configuration.Dotenv.Types: [configOverride] :: Config -> Bool
+ Configuration.Dotenv.Types: [configPath] :: Config -> [FilePath]
+ Configuration.Dotenv.Types: data Config
+ Configuration.Dotenv.Types: defaultConfig :: Config
+ Configuration.Dotenv.Types: instance GHC.Classes.Eq Configuration.Dotenv.Types.Config
+ Configuration.Dotenv.Types: instance GHC.Show.Show Configuration.Dotenv.Types.Config
- Configuration.Dotenv: loadFile :: MonadIO m => Bool -> FilePath -> m ()
+ Configuration.Dotenv: loadFile :: MonadIO m => Config -> m ()

Files

CHANGELOG.md view
@@ -1,4 +1,11 @@ ## MASTER+## Dotenv 0.5.0.0++* Add [dotenv-safe functionality](https://www.npmjs.com/package/dotenv-safe)+* Add the `Config` type with options to override env variables, and setting the+path for .env and .env.example files.+* Changed `loadFile` function to get `Config` with the paths for the .env file+and the .env.example file.  ## Dotenv 0.4.0.0 
README.md view
@@ -43,6 +43,22 @@ environment using standard functions from `System.Environment` such as `lookupEnv` and `getEnv`. +### NOTE: Empty environment variables++If you need to have empty environment variables in your configuration, you can+use something like the code below:++```haskell+fromMaybe "" <$> lookupEnv "ENV_VAR"+```++Currently, `dotenv-hs` doesn't allow you to set empty environment variables,+because of [setEnv](https://hackage.haskell.org/package/base-4.9.1.0/docs/System-Environment.html#v:setEnv)+from our `System.Environment`. This is bug reported in [GHC ticket](https://ghc.haskell.org/trac/ghc/ticket/12494).+We have had many [dicussions](https://github.com/stackbuilders/dotenv-hs/issues/48)+about this. Fortunately, there is already some work for this issue in+[GHC Phabricator](https://phabricator.haskell.org/D3726).+ ## Configuration  The first argument to `loadFile` specifies whether you want to@@ -65,15 +81,50 @@ You can call dotenv from the command line in order to load settings from one or more dotenv file before invoking an executable: -```-dotenv -f mydotenvfile myprogram+```shell+$ dotenv -e mydotenvfile myprogram ```  Aditionally you can pass arguments and flags to the program passed to Dotenv: +```shell+$ dotenv -e mydotenvfile myprogram -- --myflag myargument ```-dotenv -f mydotenvfile myprogram -- --myflag myargument++or:++```shell+$ dotenv -e mydotenvfile "myprogram --myflag myargument"+```++Also, you can use a `--example` flag to use [dotenv-safe functionality](https://www.npmjs.com/package/dotenv-safe)+so that you can have a list of strict envs that should be defined in the environment+or in your dotenv files before the execution of your program. For instance:++```shell+$ cat .env.example+DOTENV=+FOO=+BAR=++$ cat .env+DOTENV=123++$ echo $FOO+123+```++This will fail:+```shell+$ dotenv -e .env --example .env.example "myprogram --myflag myargument"+> dotenv: Missing env vars! Please, check (this/these) var(s) (is/are) set: BAR+```++This will succeed:+```shell+$ export BAR=123 # Or you can do something like: "echo 'BAR=123' >> .env"+$ dotenv -e .env --example .env.example "myprogram --myflag myargument" ```  Hint: The `env` program in most Unix-like environments prints out the
app/Main.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP             #-}+{-# LANGUAGE RecordWildCards #-}  module Main where @@ -8,36 +9,50 @@  import Options.Applicative -import Configuration.Dotenv (loadFile)+import Control.Monad (void) -import Control.Monad.IO.Class(MonadIO(..))+import Configuration.Dotenv (loadFile)+import Configuration.Dotenv.Types (Config(..))  import System.Process (system) import System.Exit (exitWith)  data Options = Options-  { files    :: [String]-  , overload :: Bool-  , program  :: String-  , args     :: [String]+  { dotenvFiles        :: [String]+  , dotenvExampleFiles :: [String]+  , override           :: Bool+  , program            :: String+  , args               :: [String]   } deriving (Show)  main :: IO ()-main = execParser opts >>= dotEnv-  where-    opts = info (helper <*> config)-      ( fullDesc-     <> progDesc "Runs PROGRAM after loading options from FILE"-     <> header "dotenv - loads options from dotenv files" )+main = do+  Options{..} <- execParser opts+  void $ loadFile Config+    { configExamplePath = dotenvExampleFiles+    , configOverride = override+    , configPath = dotenvFiles+    }+  system (program ++ concatMap (" " ++) args) >>= exitWith+    where+      opts = info (helper <*> config)+        ( fullDesc+       <> progDesc "Runs PROGRAM after loading options from FILE"+       <> header "dotenv - loads options from dotenv files" )  config :: Parser Options config = Options      <$> some (strOption (-                  long "file"+                  long "dotenv"                   <> short 'f'-                  <> metavar "FILE"-                  <> help "File to read for options" ))+                  <> metavar "DOTENV"+                  <> help "File to read for environmental variables" )) +     <*> many (strOption (+                  long "example"+                  <> metavar "DOTENV_EXAMPLE"+                  <> help "File to read for needed environmental variables" ))+      <*> switch ( long "overload"                   <> short 'o'                   <> help "Specify this flag to override existing variables" )@@ -46,10 +61,3 @@       <*> many (argument str (metavar "ARG")) -dotEnv :: MonadIO m => Options -> m ()-dotEnv opts = liftIO $ do-  mapM_ (loadFile (overload opts)) (files opts)-  code <- system (program opts ++ programArguments)-  exitWith code-  where-   programArguments = concatMap (" " ++) (args opts)
dotenv.cabal view
@@ -1,5 +1,5 @@ name:                dotenv-version:             0.4.0.0+version:             0.5.0.0 synopsis:            Loads environment variables from dotenv files homepage:            https://github.com/stackbuilders/dotenv-hs description:@@ -72,6 +72,7 @@                       , Configuration.Dotenv.Parse                       , Configuration.Dotenv.ParsedVariable                       , Configuration.Dotenv.Text+                      , Configuration.Dotenv.Types    build-depends:         base >=4.7 && <5.0                        , base-compat >= 0.4@@ -100,6 +101,7 @@                        , Configuration.Dotenv.ParseSpec                        , Configuration.Dotenv                        , Configuration.Dotenv.Text+                       , Configuration.Dotenv.Types                        , Configuration.Dotenv.Parse                        , Configuration.Dotenv.ParsedVariable 
spec/Configuration/DotenvSpec.hs view
@@ -2,6 +2,7 @@  module Configuration.DotenvSpec (main, spec) where +import Configuration.Dotenv.Types (Config(..)) import Configuration.Dotenv (load, loadFile, parseFile, onMissingFile)  import Test.Hspec@@ -52,33 +53,48 @@        lookupEnv "foo" `shouldReturn` Just "new setting" -  describe "loadFile" $ after_ (unsetEnv "DOTENV") $ do+  describe "loadFile" $ after_ (mapM_ unsetEnv ["DOTENV" , "ANOTHER_ENV"]) $ do     it "loads the configuration options to the environment from a file" $ do       lookupEnv "DOTENV" `shouldReturn` Nothing -      loadFile False "spec/fixtures/.dotenv"+      loadFile $ Config ["spec/fixtures/.dotenv"] [] False        lookupEnv "DOTENV" `shouldReturn` Just "true"      it "respects predefined settings when overload is false" $ do       setEnv "DOTENV" "preset" -      loadFile False "spec/fixtures/.dotenv"+      loadFile $ Config ["spec/fixtures/.dotenv"] [] False        lookupEnv "DOTENV" `shouldReturn` Just "preset"      it "overrides predefined settings when overload is true" $ do       setEnv "DOTENV" "preset" -      loadFile True "spec/fixtures/.dotenv"+      loadFile $ Config ["spec/fixtures/.dotenv"] [] 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++      context "when the needed env vars are missing" $+        it "should fail with an error call" $+          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+          setEnv "ANOTHER_ENV" "hello"+          loadFile config `shouldReturn` ()+          lookupEnv "DOTENV" `shouldReturn` Just "true"+          lookupEnv "UNICODE_TEST" `shouldReturn` Just "Manabí"+          lookupEnv "ANOTHER_ENV" `shouldReturn` Just "hello"+   describe "parseFile" $ after_ (unsetEnv "DOTENV") $ do     it "returns variables from a file without changing the environment" $ do       lookupEnv "DOTENV" `shouldReturn` Nothing -      (liftM head $ parseFile "spec/fixtures/.dotenv") `shouldReturn`+      liftM head (parseFile "spec/fixtures/.dotenv") `shouldReturn`         ("DOTENV", "true")        lookupEnv "DOTENV" `shouldReturn` Nothing@@ -99,9 +115,10 @@   describe "onMissingFile" $ after_ (unsetEnv "DOTENV") $ do     context "when target file is present" $       it "loading works as usual" $ do-        onMissingFile (loadFile True "spec/fixtures/.dotenv") (return ())+        onMissingFile (loadFile $ Config ["spec/fixtures/.dotenv"] [] True) (return ())         lookupEnv "DOTENV" `shouldReturn` Just "true"+     context "when target file is missing" $       it "executes supplied handler instead" $-        onMissingFile (True <$ loadFile True "spec/fixtures/foo") (return False)+        onMissingFile (True <$ (loadFile $ Config ["spec/fixtures/foo"] [] True)) (return False)           `shouldReturn` False
src/Configuration/Dotenv.hs view
@@ -9,7 +9,8 @@ -- -- This module contains common functions to load and read dotenv files. -{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP             #-}+{-# LANGUAGE RecordWildCards #-}  module Configuration.Dotenv   ( load@@ -18,18 +19,21 @@   , onMissingFile )  where +import Control.Monad (liftM) import Configuration.Dotenv.Parse (configParser) import Configuration.Dotenv.ParsedVariable (interpolateParsedVariables)+import Configuration.Dotenv.Types (Config(..)) import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO(..))+import Data.List (union, intersectBy, unionBy) import System.Environment (lookupEnv) import System.IO.Error (isDoesNotExistError)-import Text.Megaparsec (parse)+import Text.Megaparsec (parse, parseErrorPretty)  #if MIN_VERSION_base(4,7,0)-import System.Environment (setEnv)+import System.Environment (getEnvironment, setEnv) #else-import System.Environment.Compat (setEnv)+import System.Environment.Compat (getEnvironment, setEnv) #endif  -- | Loads the given list of options into the environment. Optionally@@ -41,14 +45,30 @@   -> m () load override = mapM_ (applySetting override) --- | Loads the options in the given file to the environment. Optionally--- override existing variables with values from Dotenv files.-loadFile ::-  MonadIO m =>-  Bool        -- ^ Override existing settings?-  -> FilePath -- ^ A file containing options to load into the environment+-- | @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+  => Config   -> m ()-loadFile override f = load override =<< parseFile f+loadFile Config{..} = do+  environment <- liftIO getEnvironment+  readedVars <- concat `liftM` mapM parseFile configPath+  neededVars <- concat `liftM` mapM parseFile configExamplePath+  let coincidences = (environment `union` readedVars) `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  -- | Parses the given dotenv file and returns values /without/ adding them to -- the environment.@@ -60,7 +80,7 @@   contents <- liftIO $ readFile f    case parse configParser f contents of-    Left e        -> error $ "Failed to read file" ++ show e+    Left e        -> error $ parseErrorPretty e     Right options -> liftIO $ interpolateParsedVariables options  applySetting :: MonadIO m => Bool -> (String, String) -> m ()
+ src/Configuration/Dotenv/Types.hs view
@@ -0,0 +1,34 @@+{- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2017 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Provides the types with extra options for loading a dotenv file.+-}++module Configuration.Dotenv.Types+  ( Config(..)+  , defaultConfig+  )+  where++-- | Configuration Data Types with extra options for executing dotenv.+data Config = Config+  { 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+  } deriving (Eq, Show)++-- | Default configuration. Use .env file without .env.example strict envs and+-- without overriding.+defaultConfig :: Config+defaultConfig =+  Config+    { configExamplePath = []+    , configOverride = False+    , configPath = [ ".env" ]+    }