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 +9/−0
- README.md +2/−2
- app/Main.hs +3/−3
- dotenv.cabal +12/−9
- spec/Configuration/Dotenv/ParseSpec.hs +2/−2
- spec/Configuration/Dotenv/Scheme/ParseSpec.hs +20/−48
- spec/Configuration/Dotenv/SchemeSpec.hs +29/−25
- spec/Configuration/DotenvSpec.hs +50/−3
- spec/fixtures/.scheme.yml +2/−0
- src/Configuration/Dotenv.hs +35/−9
- src/Configuration/Dotenv/Parse.hs +15/−12
- src/Configuration/Dotenv/ParsedVariable.hs +11/−0
- src/Configuration/Dotenv/Scheme.hs +19/−35
- src/Configuration/Dotenv/Scheme/Helpers.hs +11/−0
- src/Configuration/Dotenv/Scheme/Parser.hs +47/−29
- src/Configuration/Dotenv/Scheme/Types.hs +60/−9
- src/Configuration/Dotenv/Text.hs +2/−2
- src/Configuration/Dotenv/Types.hs +1/−1
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>