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 +7/−0
- README.md +54/−3
- app/Main.hs +31/−23
- dotenv.cabal +3/−1
- spec/Configuration/DotenvSpec.hs +24/−7
- src/Configuration/Dotenv.hs +32/−12
- src/Configuration/Dotenv/Types.hs +34/−0
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" ]+ }