packages feed

dotenv 0.5.2.5 → 0.6.0.1

raw patch · 18 files changed

+330/−189 lines, 18 filesdep +containersdep ~hspec-megaparsecdep ~megaparsecdep ~processPVP ok

version bump matches the API change (PVP)

Dependencies added: containers

Dependency ranges changed: hspec-megaparsec, megaparsec, process

API changes (from Hackage documentation)

- Configuration.Dotenv.Scheme: loadSafeFile :: MonadIO m => FilePath -> Config -> m [(String, String)]
- Configuration.Dotenv.Scheme: runSchemaChecker :: FilePath -> Config -> IO ()
- Configuration.Dotenv.Scheme.Parser: parseEnvAs :: String -> EnvType -> Either (ParseError Char Void) ()
- Configuration.Dotenv.Scheme.Parser: showType :: EnvType -> String
- Configuration.Dotenv.Scheme.Types: EnvBool :: EnvType
- Configuration.Dotenv.Scheme.Types: EnvInteger :: EnvType
- Configuration.Dotenv.Scheme.Types: EnvText :: EnvType
- Configuration.Dotenv.Scheme.Types: data EnvType
+ Configuration.Dotenv: Config :: [FilePath] -> [FilePath] -> Bool -> Config
+ Configuration.Dotenv: [configExamplePath] :: Config -> [FilePath]
+ Configuration.Dotenv: [configOverride] :: Config -> Bool
+ Configuration.Dotenv: [configPath] :: Config -> [FilePath]
+ Configuration.Dotenv: data Config
+ Configuration.Dotenv: defaultValidatorMap :: ValidatorMap
+ Configuration.Dotenv: loadSafeFile :: MonadIO m => ValidatorMap -> FilePath -> Config -> m [(String, String)]
+ Configuration.Dotenv: type ValidatorMap = Map Text (Text -> Bool)
+ Configuration.Dotenv.Scheme: readScheme :: FilePath -> IO [Env]
+ Configuration.Dotenv.Scheme.Parser: defaultValidatorMap :: ValidatorMap
+ Configuration.Dotenv.Scheme.Parser: typeValidator :: ValidatorMap -> String -> EnvType -> Either String ()
+ Configuration.Dotenv.Scheme.Types: EnvType :: Text -> EnvType
+ Configuration.Dotenv.Scheme.Types: defaultValidatorMap :: ValidatorMap
+ Configuration.Dotenv.Scheme.Types: newtype EnvType
+ Configuration.Dotenv.Scheme.Types: type ValidatorMap = Map Text (Text -> Bool)
- Configuration.Dotenv.Scheme: checkConfig :: [(String, String)] -> [Env] -> IO ()
+ Configuration.Dotenv.Scheme: checkConfig :: ValidatorMap -> [(String, String)] -> [Env] -> IO ()
- Configuration.Dotenv.Scheme.Parser: parseEnvsWithScheme :: [(String, EnvType)] -> Either [ParseError Char Void] ()
+ Configuration.Dotenv.Scheme.Parser: parseEnvsWithScheme :: ValidatorMap -> [(String, EnvType)] -> Either [String] ()

Files

CHANGELOG.md view
@@ -1,4 +1,13 @@ ## MASTER+## Dotenv 0.6.0.1+* Add support for `megaparsec-7.0.1`+* Drop support for `GHC 7.8.4`++## Dotenv 0.6.0.0+* Move `loadSafeFile` to `Configuration.Dotenv`+* Export `Configuration.Dotenv.Types` from `Configuration.Dotenv`+* Change `loadSafeFile` signature to accept different types of validators.+* Add `type ValidatorMap = Map Text (Text -> Bool)` for custom validations.  ## Dotenv 0.5.2.5 * Update `exceptions` bounds `>= 0.8 && < 0.11`
README.md view
@@ -35,8 +35,8 @@ settings into the environment:  ```haskell-import qualified Configuration.Dotenv as Dotenv-Dotenv.loadFile defaultConfig+import Configuration.Dotenv (loadFile, defaultConfig)+loadFile defaultConfig ```  After calling `Dotenv.load`, you are able to read the values set in your
app/Main.hs view
@@ -13,9 +13,9 @@  import Control.Monad (void, unless) -import Configuration.Dotenv (loadFile)+import Configuration.Dotenv (loadFile, loadSafeFile) import Configuration.Dotenv.Types (Config(..), defaultConfig)-import Configuration.Dotenv.Scheme ( runSchemaChecker )+import Configuration.Dotenv.Scheme.Parser ( defaultValidatorMap )  import System.Process (system) import System.Exit (exitWith)@@ -45,7 +45,7 @@    in do      void $ loadFile configDotenv      unless disableSchema-       (runSchemaChecker schemaFile configDotenv)+       (void $ loadSafeFile defaultValidatorMap schemaFile configDotenv)      system (program ++ concatMap (" " ++) args) >>= exitWith        where          opts = info (helper <*> versionOption <*> config)
dotenv.cabal view
@@ -1,5 +1,5 @@ name:                dotenv-version:             0.5.2.5+version:             0.6.0.1 synopsis:            Loads environment variables from dotenv files homepage:            https://github.com/stackbuilders/dotenv-hs description:@@ -21,8 +21,9 @@   .   To use, call `loadFile` from your application:   .+  > import Control.Monad (void)   > import Configuration.Dotenv-  > loadFile False "/my/dotenvfile"+  > void $ loadFile defaultConfig   .   This package also includes an executable that can be used   to inspect the results of applying one or more Dotenv files@@ -61,8 +62,8 @@                        , base-compat >= 0.4                        , dotenv                        , optparse-applicative >=0.11 && < 0.15-                       , megaparsec >= 6.0 && < 7.0-                       , process >= 1.4.3.0+                       , megaparsec+                       , process                        , text                        , transformers >=0.4 && < 0.6                        , yaml >= 0.8@@ -89,8 +90,9 @@   build-depends:         base >=4.7 && <5.0                        , base-compat >= 0.4                        , directory-                       , megaparsec >= 6.0 && < 7.0-                       , process >= 1.4.3.0+                       , megaparsec >= 7.0.1 && < 8.0+                       , containers+                       , process >= 1.6.4.0 && < 1.7                        , text                        , transformers >=0.4 && < 0.6                        , exceptions >= 0.8 && < 0.11@@ -128,15 +130,16 @@    build-depends:       base >=4.7 && <5.0                      , base-compat >= 0.4+                     , containers                      , dotenv                      , directory-                     , megaparsec >= 6.0 && < 7.0+                     , megaparsec                      , hspec-                     , process >= 1.4.3.0+                     , process                      , text                      , transformers >=0.4 && < 0.6                      , exceptions >= 0.8 && < 0.11-                     , hspec-megaparsec >= 1.0 && < 2.0+                     , hspec-megaparsec >= 2.0 && < 3.0                      , yaml >= 0.8    if !impl(ghc >= 7.10)
spec/Configuration/Dotenv/ParseSpec.hs view
@@ -9,7 +9,7 @@ import Data.Void (Void) import Test.Hspec (it, describe, Spec, hspec) import Test.Hspec.Megaparsec (shouldParse, shouldFailOn, shouldSucceedOn)-import Text.Megaparsec (ParseError, parse)+import Text.Megaparsec (ParseErrorBundle, parse)  main :: IO () main = hspec spec@@ -151,5 +151,5 @@   it "parses empty content (when the file is empty)" $     parseConfig `shouldSucceedOn` "" -parseConfig :: String -> Either (ParseError Char Void) [ParsedVariable]+parseConfig :: String -> Either (ParseErrorBundle String Void) [ParsedVariable] parseConfig = parse configParser ""
spec/Configuration/Dotenv/Scheme/ParseSpec.hs view
@@ -1,63 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}+ {-# OPTIONS_GHC -fno-warn-orphans #-}  module Configuration.Dotenv.Scheme.ParseSpec (spec) where -import Data.Either (isLeft, isRight) import Data.Yaml (decodeFileEither) import Test.Hspec  import Configuration.Dotenv.Scheme.Types-import Configuration.Dotenv.Scheme.Parser +-- | Extract Right from Either+-- fromRight :: (Show a, Show b) => Either a b -> b fromRight (Right v) = v fromRight (Left v) = error ("Left " ++ show v ++ " constructor instead" ) +-- |+-- spec :: Spec-spec = do-  specInstance-  specParse--specInstance :: Spec-specInstance = describe "parse a config env file" $-  it "parses the env config values from a file" $-    let expected :: [Env]-        expected =-            [ Env "DOTENV" EnvBool True-            , Env "OTHERENV" EnvBool False-            , Env "PORT" EnvInteger True-            , Env "TOKEN" EnvInteger False-            , Env "URL" EnvText True+spec =+  describe "parse a config env file" $+    it "parses the env config values from a file" $+      let expected :: [Env]+          expected =+            [ Env "DOTENV" (EnvType "bool") True+            , Env "OTHERENV" (EnvType "bool") False+            , Env "PORT" (EnvType "integer") True+            , Env "TOKEN" (EnvType "integer") False+            , Env "URL" (EnvType "text") True+            , Env "TWO" (EnvType "twoLetters") False             ]-     in do-       actual <- decodeFileEither "spec/fixtures/.scheme.yml"-       fromRight actual `shouldBe` expected--specParse :: Spec-specParse =-  describe "parseEnvAs" $ do-    context "given an integer" $ do-      context "when the integer can be parsed" $-        it "should return Right ()" $-          let varContent = "123"-              integer = EnvInteger-           in varContent `parseEnvAs` integer `shouldSatisfy` isRight--      context "when the integer can't be parsed" $-        it "should return Left (ParseError Char Void)" $-          let varContent = "123x"-              integer = EnvInteger-           in varContent `parseEnvAs` integer `shouldSatisfy` isLeft--    context "given a bool" $ do-      context "when the bool can be parsed" $-        it "should return Right ()" $-          let varContent = "true"-              boolean = EnvBool-           in varContent `parseEnvAs` boolean `shouldSatisfy` isRight--      context "when the bool can't be parsed" $-        it "should return False" $-          let varContent = "truex"-              boolean = EnvBool-           in varContent `parseEnvAs` boolean `shouldSatisfy` isLeft+       in do+         actual <- decodeFileEither "spec/fixtures/.scheme.yml"+         fromRight actual `shouldBe` expected
spec/Configuration/Dotenv/SchemeSpec.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ module Configuration.Dotenv.SchemeSpec (spec) where  import Configuration.Dotenv.Scheme@@ -6,23 +8,25 @@  import Test.Hspec +-- |+-- spec :: Spec spec = do   describe "checkScheme" $ do     context "when the env configs are unique" $       it "should succeed the check" $         let schemeEnvs =-              [ Env "FOO" EnvBool True-              , Env "BAR" EnvInteger True+              [ Env "FOO" (EnvType "bool") True+              , Env "BAR" (EnvType "integer") True               ]          in checkScheme schemeEnvs `shouldBe` schemeEnvs      context "when there are duplicated env configs" $       it "should fail the check" $         let schemeEnvs =-              [ Env "FOO" EnvBool True-              , Env "BAR" EnvInteger True-              , Env "FOO" EnvInteger True+              [ Env "FOO" (EnvType "bool") True+              , Env "BAR" (EnvType "integer") True+              , Env "FOO" (EnvType "integer") True               ]             msg = "Duplicated env variable configuration in schema: FOO"          in evaluate (checkScheme schemeEnvs) `shouldThrow` errorCall msg@@ -33,48 +37,48 @@         context "when the required envs are defined" $           it "should succeed the type check" $             let schemeEnvs =-                    [ Env "FOO" EnvBool True-                    , Env "BAR" EnvInteger False+                    [ Env "FOO" (EnvType "bool") True+                    , Env "BAR" (EnvType "integer") False                     ]-                dotenvs = [("FOO","true"), ("BAR","123")]-             in checkConfig dotenvs schemeEnvs `shouldReturn` ()+                dotenvs = [("FOO","True"), ("BAR","123")]+             in checkConfig defaultValidatorMap dotenvs schemeEnvs `shouldReturn` ()          context "when the not required envs are missing" $           it "should succeed the type check" $             let schemeEnvs =-                    [ Env "FOO" EnvBool True-                    , Env "BAR" EnvInteger False+                    [ Env "FOO" (EnvType "bool") True+                    , Env "BAR" (EnvType "integer") False                     ]-                dotenvs = [("FOO","true")]-             in checkConfig dotenvs schemeEnvs `shouldReturn` ()+                dotenvs = [("FOO","True")]+             in checkConfig defaultValidatorMap dotenvs schemeEnvs `shouldReturn` ()          context "when the required envs are missing" $           it "should fail before the type check" $             let schemeEnvs =-                    [ Env "FOO" EnvBool True-                    , Env "BAR" EnvInteger False+                    [ Env "FOO" (EnvType "bool") True+                    , Env "BAR" (EnvType "integer") False                     ]                 dotenvs = [("BAR","123")]                 msg = "The following envs: FOO must be in the dotenvs"-              in checkConfig dotenvs schemeEnvs `shouldThrow` errorCall msg+              in checkConfig defaultValidatorMap dotenvs schemeEnvs `shouldThrow` errorCall msg        context "when there are missing dotenvs in the scheme" $         it "should fail before type checking" $           let schemeEnvs =-                  [ Env "FOO" EnvBool True-                  , Env "BAR" EnvInteger False+                  [ Env "FOO" (EnvType "bool") True+                  , Env "BAR" (EnvType "integer") False                   ]-              dotenvs = [("FOO","true"), ("BAR","123"), ("BAZ","text")]+              dotenvs = [("FOO","True"), ("BAR","123"), ("BAZ","text")]               msg = "The following envs: BAZ must be in your scheme.yml"-           in checkConfig dotenvs schemeEnvs `shouldThrow` errorCall msg+           in checkConfig defaultValidatorMap dotenvs schemeEnvs `shouldThrow` errorCall msg        context "when there are missing scheme envs in the dotenv vars" $         it "should fail before type checking" $           let schemeEnvs =-                  [ Env "FOO" EnvBool True-                  , Env "BAZ" EnvText True-                  , Env "BAR" EnvInteger False+                  [ Env "FOO" (EnvType "bool") True+                  , Env "BAZ" (EnvType "text") True+                  , Env "BAR" (EnvType "integer") False                   ]-              dotenvs = [("FOO","true"), ("BAR","123")]+              dotenvs = [("FOO","True"), ("BAR","123")]               msg = "The following envs: BAZ must be in the dotenvs"-           in checkConfig dotenvs schemeEnvs `shouldThrow` errorCall msg+           in checkConfig defaultValidatorMap dotenvs schemeEnvs `shouldThrow` errorCall msg
spec/Configuration/DotenvSpec.hs view
@@ -1,16 +1,26 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}  module Configuration.DotenvSpec (main, spec) where  import Configuration.Dotenv.Types (Config(..))-import Configuration.Dotenv (load, loadFile, parseFile, onMissingFile)+import Configuration.Dotenv+  ( load+  , loadFile+  , loadSafeFile+  , parseFile+  , onMissingFile+  ) + import Test.Hspec  import System.Process (readCreateProcess, shell) import System.Environment (lookupEnv) import Control.Monad (liftM, void) import Data.Maybe (fromMaybe)+import qualified Data.Text as T+import qualified Data.Map.Lazy as M #if !MIN_VERSION_base(4,8,0) import Data.Functor ((<$>)) #endif@@ -25,8 +35,6 @@ import System.Environment.Compat (getEnvironment, setEnv, unsetEnv) #endif -{-# ANN module "HLint: ignore Reduce duplication" #-}- main :: IO () main = hspec spec @@ -99,6 +107,45 @@           lookupEnv "DOTENV" `shouldReturn` Just "true"           lookupEnv "UNICODE_TEST" `shouldReturn` Just "Manabí"           lookupEnv "ANOTHER_ENV" `shouldReturn` Just "hello"++  describe "loadSafeFile" $ after_ clearEnvs $+    context "given a custom map" $ do+      context "when the envs are written accordingly to the rules in the map" $+        it "should validate accordingly to the rules in the custom map" $+          let hasTwoLetters text = T.length text == 2+              customMap = M.fromList+                [ ("twoLetters", hasTwoLetters)+                , ("bool", const True)+                , ("text", const True)+                , ("integer", const True)+                ]+              schemaFile = "spec/fixtures/.scheme.yml"+              config = Config ["spec/fixtures/.dotenv.safe"] [] False+              expectedEnvs =+                [ ("DOTENV","true")+                , ("OTHERENV","false")+                , ("PORT","8000")+                , ("URL","http://example.com")+                , ("TWO","xD")+                ]+           in do+             envs <- loadSafeFile customMap schemaFile config+             envs `shouldMatchList` expectedEnvs++      context "when the envs are written in an unexpected way" $+        it "should throw an errorCall" $+          let unexpectedFormat text = T.length text == 3+              customMap = M.fromList+                [ ("twoLetters", unexpectedFormat)+                , ("bool", const True)+                , ("text", const True)+                , ("integer", const True)+                ]+              schemaFile = "spec/fixtures/.scheme.yml"+              config = Config ["spec/fixtures/.dotenv.safe"] [] False+           in void $ loadSafeFile customMap schemaFile config+                `shouldThrow` anyErrorCall+    describe "parseFile" $ after_ clearEnvs $ do     it "returns variables from a file without changing the environment" $ do
spec/fixtures/.scheme.yml view
@@ -12,3 +12,5 @@ - name: URL   type: text   required: true+- name: TWO+  type: twoLetters
src/Configuration/Dotenv.hs view
@@ -1,6 +1,6 @@ -- |--- Module      :  Configuration.Dotenv--- Copyright   :  © 2015–2016 Stack Builders Inc.+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc. -- License     :  MIT -- -- Maintainer  :  Stack Builders <hackage@stackbuilders.com>@@ -13,22 +13,33 @@ {-# LANGUAGE RecordWildCards #-}  module Configuration.Dotenv-  ( load+  (+  -- * Dotenv Load Functions+    load   , loadFile+  , loadSafeFile   , parseFile-  , onMissingFile )+  , onMissingFile+  -- * Dotenv Types+  , module Configuration.Dotenv.Types+  , ValidatorMap+  , defaultValidatorMap+  )  where -import Control.Monad (liftM)+import Control.Monad (liftM, when) import Configuration.Dotenv.Parse (configParser) import Configuration.Dotenv.ParsedVariable (interpolateParsedVariables)+import Configuration.Dotenv.Scheme+import Configuration.Dotenv.Scheme.Types (ValidatorMap, defaultValidatorMap) import Configuration.Dotenv.Types (Config(..)) import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO(..)) import Data.List (union, intersectBy, unionBy)+import System.Directory (doesFileExist) import System.Environment (lookupEnv) import System.IO.Error (isDoesNotExistError)-import Text.Megaparsec (parse, parseErrorPretty)+import Text.Megaparsec (parse, errorBundlePretty)  #if MIN_VERSION_base(4,7,0) import System.Environment (getEnvironment, setEnv)@@ -51,8 +62,8 @@ -- with the values defined in the dotenv file. loadFile   :: MonadIO m-  => Config-  -> m [(String, String)]+  => Config -- ^ Dotenv configuration+  -> m [(String, String)] -- ^ Environment variables loaded loadFile Config{..} = do   environment <- liftIO getEnvironment   readedVars <- concat `liftM` mapM parseFile configPath@@ -80,7 +91,7 @@   contents <- liftIO $ readFile f    case parse configParser f contents of-    Left e        -> error $ parseErrorPretty e+    Left e        -> error $ errorBundlePretty e     Right options -> liftIO $ interpolateParsedVariables options  applySetting :: MonadIO m => Bool -> (String, String) -> m (String, String)@@ -104,3 +115,18 @@   -> m a               -- ^ Action to perform if file is indeed missing   -> m a onMissingFile f h = catchIf isDoesNotExistError f (const h)++-- | @loadSafeFile@ parses the /.scheme.yml/ file and will perform the type checking+-- of the environment variables in the /.env/ file.+loadSafeFile+  :: MonadIO m+  => ValidatorMap -- ^ Map with custom validations+  -> FilePath -- ^ Filepath for schema file+  -> Config -- ^ Dotenv configuration+  -> m [(String, String)] -- ^ Environment variables loaded+loadSafeFile mapFormat schemaFile config = do+  envs <- loadFile config+  exists <- liftIO $ doesFileExist schemaFile+  when exists $+    liftIO (readScheme schemaFile >>= checkConfig mapFormat envs . checkScheme)+  return envs
src/Configuration/Dotenv/Parse.hs view
@@ -1,6 +1,6 @@ -- |--- Module      :  Configuration.Dotenv.Parse--- Copyright   :  © 2015–2016 Stack Builders Inc.+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc. -- License     :  MIT -- -- Maintainer  :  Stack Builders <hackage@stackbuilders.com>@@ -26,9 +26,12 @@ #endif import           Control.Monad                       (void) import           Data.Void                           (Void)-import           Text.Megaparsec                     (Parsec, between, eof,-                                                      sepEndBy, (<?>))-import           Text.Megaparsec.Char+import           Text.Megaparsec                     (Parsec, anySingle, between, eof,+                                                      sepEndBy, (<?>), oneOf,+                                                      noneOf)+import           Text.Megaparsec.Char                (alphaNumChar, char, eol,+                                                      spaceChar, digitChar,+                                                      letterChar) import qualified Text.Megaparsec.Char.Lexer          as L  type Parser = Parsec Void String@@ -58,14 +61,14 @@ value = (quotedValue <|> unquotedValue) <?> "variable value"   where     quotedValue   = quotedWith SingleQuote <|> quotedWith DoubleQuote-    unquotedValue = Unquoted <$> (many $ fragment "\'\" \t\n\r")+    unquotedValue = Unquoted <$> many (fragment "\'\" \t\n\r")  -- | Parse a value quoted with given character. quotedWith :: QuoteType -> Parser VarValue-quotedWith SingleQuote = SingleQuoted <$> (between (char '\'') (char '\'') $ many (literalValueFragment "\'\\"))-quotedWith DoubleQuote = DoubleQuoted <$> (between (char '\"') (char '\"') $ many (fragment "\""))+quotedWith SingleQuote = SingleQuoted <$> between (char '\'') (char '\'') (many (literalValueFragment "\'\\"))+quotedWith DoubleQuote = DoubleQuoted <$> between (char '\"') (char '\"') (many (fragment "\"")) -fragment :: [Char] -> Parser VarFragment+fragment :: String -> Parser VarFragment fragment charsToEscape =   interpolatedValueCommandInterpolation     <|> interpolatedValueVarInterpolation@@ -85,10 +88,10 @@     where       symbol = L.symbol sc -literalValueFragment :: [Char] -> Parser VarFragment-literalValueFragment charsToEscape = VarLiteral <$> (some $ escapedChar <|> normalChar)+literalValueFragment :: String -> Parser VarFragment+literalValueFragment charsToEscape = VarLiteral <$> some (escapedChar <|> normalChar)   where-    escapedChar = (char '\\' *> anyChar) <?> "escaped character"+    escapedChar = (char '\\' *> anySingle) <?> "escaped character"     normalChar  = noneOf charsToEscape <?> "unescaped character"  ----------------------------------------------------------------------------
src/Configuration/Dotenv/ParsedVariable.hs view
@@ -1,3 +1,14 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Helpers to interpolate environment variables+ {-# LANGUAGE CPP #-}  module Configuration.Dotenv.ParsedVariable (ParsedVariable(..),
src/Configuration/Dotenv/Scheme.hs view
@@ -1,37 +1,30 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Helpers for loadSafeFile+ module Configuration.Dotenv.Scheme   ( checkConfig   , checkScheme-  , loadSafeFile-  , runSchemaChecker+  , readScheme   )   where  import Control.Monad-import Control.Monad.IO.Class (MonadIO(..))  import Data.List import Data.Yaml (decodeFileEither, prettyPrintParseException)-import Text.Megaparsec-import System.Directory (doesFileExist) -import Configuration.Dotenv (loadFile)-import Configuration.Dotenv.Types (Config(..)) import Configuration.Dotenv.Scheme.Helpers import Configuration.Dotenv.Scheme.Parser import Configuration.Dotenv.Scheme.Types --- | @loadSafeFile@ parses the /.scheme.yml/ file and will perform the type checking--- of the environment variables in the /.env/ file.-loadSafeFile-  :: MonadIO m-  => FilePath-  -> Config-  -> m [(String, String)]-loadSafeFile schemaFile config = do-  envs <- loadFile config-  liftIO (readScheme schemaFile >>= checkConfig envs . checkScheme)-  return envs- readScheme :: FilePath -> IO [Env] readScheme schemeFile = do   eitherEnvConf <- decodeFileEither schemeFile@@ -53,12 +46,12 @@ duplicatedConfErrorMsg = ("Duplicated env variable configuration in schema: " ++) . showMissingDotenvs  checkConfig-  :: [(String, String)]+  :: ValidatorMap+  -> [(String, String)]   -> [Env]   -> IO ()-checkConfig envvars envsWithType =-  let prettyParsedErrors = unlines . fmap parseErrorTextPretty-      envsTypeAndValue   = joinEnvs envsWithType envvars+checkConfig mapFormat envvars envsWithType =+  let envsTypeAndValue   = joinEnvs envsWithType envvars       valuesAndTypes     = matchValueAndType envsTypeAndValue       dotenvsMissing     = filter required (missingDotenvs envsWithType envsTypeAndValue)       schemeEnvsMissing  = missingSchemeEnvs envvars envsTypeAndValue@@ -71,15 +64,6 @@        (error $ "The following envs: "                   ++ showMissingSchemeEnvs schemeEnvsMissing                   ++ " must be in your scheme.yml")-     case parseEnvsWithScheme valuesAndTypes of-        Left errors -> error (prettyParsedErrors errors)-        _ -> return ()--runSchemaChecker-  :: FilePath-  -> Config-  -> IO ()-runSchemaChecker schemeFile config = do-  exists <- doesFileExist schemeFile-  when exists-     (void $ loadSafeFile schemeFile config)+     case parseEnvsWithScheme mapFormat valuesAndTypes of+       Left errors -> error (unlines errors)+       _ -> return ()
src/Configuration/Dotenv/Scheme/Helpers.hs view
@@ -1,3 +1,14 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Helpers for loadSafeFile+ {-# LANGUAGE CPP             #-} {-# LANGUAGE RecordWildCards #-} 
src/Configuration/Dotenv/Scheme/Parser.hs view
@@ -1,47 +1,65 @@-{-# LANGUAGE CPP               #-}-{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Helpers for loadSafeFile -module Configuration.Dotenv.Scheme.Parser where+{-# LANGUAGE CPP                 #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-} +module Configuration.Dotenv.Scheme.Parser+  ( defaultValidatorMap+  , parseEnvsWithScheme+  , typeValidator+  )+  where+ #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((*>), pure) import Data.Functor ((<$>)) #endif  import Data.Either-import Data.Void-import Text.Megaparsec-import Text.Megaparsec.Char +import qualified Data.Map.Lazy as ML++import qualified Data.Text as T+ import Configuration.Dotenv.Scheme.Types ++-- |+-- parseEnvsWithScheme-  :: [(String, EnvType)]-  -> Either [ParseError Char Void] ()-parseEnvsWithScheme valuesAndTypes =+  :: ValidatorMap -- ^ MapFormat validations+  -> [(String, EnvType)] -- ^ Value and Type+  -> Either [String] ()+parseEnvsWithScheme validatorMap valuesAndTypes =   let parsedEithers = parseEnvs <$> valuesAndTypes-      parseEnvs (val, type') = val `parseEnvAs` type'+      parseEnvWith = typeValidator validatorMap+      parseEnvs (val, type') = val `parseEnvWith` type'     in if all isRight parsedEithers           then Right ()           else Left (lefts parsedEithers) -parseEnvAs-  :: String -- ^ Value of the env variable+-- |+--+typeValidator+  :: ValidatorMap -- ^ MapFormat validations+  -> String -- ^ Value of the env variable   -> EnvType -- ^ Type that the env variable should have-  -> Either (ParseError Char Void) ()-parseEnvAs envVal envTypeNeeded =-  parse dispatch "" envVal-    where-      errorMsg = "Couldn't parse " ++ envVal ++ " as " ++ showType envTypeNeeded-      evalParse parser = parser *> eof *> pure () <|> fail errorMsg-      dispatch :: Parsec Void String ()-      dispatch =-        case envTypeNeeded of-          EnvInteger -> evalParse (many digitChar)-          EnvBool    -> evalParse (string "true" <|> string "false")-          EnvText    -> pure ()--showType :: EnvType -> String-showType EnvInteger = "integer"-showType EnvBool = "boolean"-showType _ = "text"+  -> Either String ()+typeValidator validatorMap envVal (EnvType type_) =+  let errorMsg = "Couldn't parse " ++ envVal ++ " as " ++ T.unpack type_+   in case ML.lookup type_ validatorMap of+        Nothing -> Left errorMsg+        Just validator ->+          if validator (T.pack envVal)+             then Right ()+             else Left errorMsg
src/Configuration/Dotenv/Scheme/Types.hs view
@@ -1,3 +1,14 @@+-- |+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc.+-- License     :  MIT+--+-- Maintainer  :  Stack Builders <hackage@stackbuilders.com>+-- Stability   :  experimental+-- Portability :  portable+--+-- Types for 'loadSafeFile' (e. g., 'ValidatorMap')+ {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} @@ -8,21 +19,33 @@ import Data.Functor ((<$>)) #endif +import Data.Maybe (isJust)++import Data.Map.Lazy (Map)+ import Data.Yaml -data EnvType =-  EnvInteger-    | EnvBool-    | EnvText+import qualified Data.Map.Lazy as ML++import Data.Text (Text)+import qualified Data.Text as T++import Text.Read (readMaybe)+++-- |+--+newtype EnvType = EnvType Text     deriving (Show, Eq, Ord) +-- |+-- instance FromJSON EnvType where-  parseJSON (String "integer") = pure EnvInteger-  parseJSON (String "bool") = pure EnvBool-  parseJSON (String "text") = pure EnvText-  parseJSON (String x) = fail ("Don't know how to parse that kind of type: " ++ show x)-  parseJSON x = fail ("Not an object: " ++ show x)+  parseJSON (String value) = pure (EnvType value)+  parseJSON anyOther = fail ("Not an object: " ++ show anyOther) +-- |+-- data Env =   Env     { envName  :: String@@ -30,6 +53,8 @@     , required :: Bool     } deriving (Show, Eq, Ord) +-- |+-- instance FromJSON Env where   parseJSON (Object m) =     Env@@ -37,3 +62,29 @@       <*> m .: "type"       <*> m .:? "required" .!= False   parseJSON x = fail ("Not an object: " ++ show x)++-- | Parameters:+--+-- - __Key:__ Name of the /format/ to check.+--+-- - __Value:__ Function to check if some text meets the condition.+--+type ValidatorMap = Map Text (Text -> Bool)+++-- | Default configuration for 'loadSafeFile'. It currently checks:+-- @bool@, @integer@, and @text@.+--+defaultValidatorMap :: ValidatorMap+defaultValidatorMap =+  let booleanValidator :: Text -> Bool+      booleanValidator text = isJust (readMaybe (T.unpack text) :: Maybe Bool)+      integerValidator :: Text -> Bool+      integerValidator text = isJust (readMaybe (T.unpack text) :: Maybe Integer)+      textValidator :: Text -> Bool+      textValidator = const True+   in ML.fromList+        [ ("bool", booleanValidator)+        , ("integer", integerValidator)+        , ("text", textValidator)+        ]
src/Configuration/Dotenv/Text.hs view
@@ -1,6 +1,6 @@ -- |--- Module      :  Configuration.Dotenv.Text--- Copyright   :  © 2015–2016 Stack Builders Inc.+-- Module      :  Configuration.Dotenv.Types+-- Copyright   :  © 2015–2018 Stack Builders Inc. -- License     :  MIT -- -- Maintainer  :  Stack Builders <hackage@stackbuilders.com>
src/Configuration/Dotenv/Types.hs view
@@ -1,6 +1,6 @@ -- | -- Module      :  Configuration.Dotenv.Types--- Copyright   :  © 2015–2017 Stack Builders Inc.+-- Copyright   :  © 2015–2018 Stack Builders Inc. -- License     :  MIT -- -- Maintainer  :  Stack Builders <hackage@stackbuilders.com>