packages feed

dotenv 0.3.2.0 → 0.3.3.0

raw patch · 9 files changed

+189/−34 lines, 9 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Configuration.Dotenv.ParsedVariable: DoubleQuoted :: VarContents -> VarValue
+ Configuration.Dotenv.ParsedVariable: ParsedVariable :: VarName -> VarValue -> ParsedVariable
+ Configuration.Dotenv.ParsedVariable: SingleQuoted :: VarContents -> VarValue
+ Configuration.Dotenv.ParsedVariable: Unquoted :: VarContents -> VarValue
+ Configuration.Dotenv.ParsedVariable: VarInterpolation :: String -> VarFragment
+ Configuration.Dotenv.ParsedVariable: VarLiteral :: String -> VarFragment
+ Configuration.Dotenv.ParsedVariable: data ParsedVariable
+ Configuration.Dotenv.ParsedVariable: data VarFragment
+ Configuration.Dotenv.ParsedVariable: data VarValue
+ Configuration.Dotenv.ParsedVariable: instance GHC.Classes.Eq Configuration.Dotenv.ParsedVariable.ParsedVariable
+ Configuration.Dotenv.ParsedVariable: instance GHC.Classes.Eq Configuration.Dotenv.ParsedVariable.VarFragment
+ Configuration.Dotenv.ParsedVariable: instance GHC.Classes.Eq Configuration.Dotenv.ParsedVariable.VarValue
+ Configuration.Dotenv.ParsedVariable: instance GHC.Show.Show Configuration.Dotenv.ParsedVariable.ParsedVariable
+ Configuration.Dotenv.ParsedVariable: instance GHC.Show.Show Configuration.Dotenv.ParsedVariable.VarFragment
+ Configuration.Dotenv.ParsedVariable: instance GHC.Show.Show Configuration.Dotenv.ParsedVariable.VarValue
+ Configuration.Dotenv.ParsedVariable: interpolateParsedVariables :: [ParsedVariable] -> IO [(String, String)]
+ Configuration.Dotenv.ParsedVariable: type VarContents = [VarFragment]
+ Configuration.Dotenv.ParsedVariable: type VarName = String
- Configuration.Dotenv.Parse: configParser :: Parser [(String, String)]
+ Configuration.Dotenv.Parse: configParser :: Parser [ParsedVariable]

Files

CHANGELOG.md view
@@ -1,3 +1,9 @@+## MASTER++## Dotenv 0.3.3.0++* Add support for variable expansion. Thanks to حبيب الامين (GitHub: habibalamin) for making this contribution.+ ## Dotenv 0.3.2.0  * Add the option to pass arguments to the program passed to Dotenv. Thanks to
README.md view
@@ -20,7 +20,7 @@ ## Installation  In most cases you will just add `dotenv` to your cabal file. You can-also install the library and executable by invoking `cabal install dotenv`.+also install the library and executable by invoking `stack install dotenv`.  ## Usage 
dotenv.cabal view
@@ -1,5 +1,5 @@ name:                dotenv-version:             0.3.2.0+version:             0.3.3.0 synopsis:            Loads environment variables from dotenv files homepage:            https://github.com/stackbuilders/dotenv-hs description:@@ -35,7 +35,7 @@ license-file:        LICENSE author:              Justin Leitgeb maintainer:          hackage@stackbuilders.com-copyright:           2015-2016 Stack Builders Inc.+copyright:           2015-2017 Stack Builders Inc. category:            Configuration build-type:          Simple extra-source-files:    spec/fixtures/.dotenv@@ -70,6 +70,7 @@ library   exposed-modules:      Configuration.Dotenv                       , Configuration.Dotenv.Parse+                      , Configuration.Dotenv.ParsedVariable                       , Configuration.Dotenv.Text    build-depends:         base >=4.6 && <5.0@@ -97,6 +98,7 @@                        , Configuration.Dotenv                        , Configuration.Dotenv.Text                        , Configuration.Dotenv.Parse+                       , Configuration.Dotenv.ParsedVariable    build-depends:       base >=4.6 && <5.0                      , base-compat >= 0.4
spec/Configuration/Dotenv/ParseSpec.hs view
@@ -3,6 +3,9 @@ module Configuration.Dotenv.ParseSpec (main, spec) where  import Configuration.Dotenv.Parse (configParser)+import Configuration.Dotenv.ParsedVariable (ParsedVariable(..),+                                            VarValue(..),+                                            VarFragment(..)) import Test.Hspec (it, describe, Spec, hspec) import Test.Hspec.Megaparsec (shouldParse, shouldFailOn) import Text.Megaparsec (ParseError, Dec, parse)@@ -13,76 +16,130 @@ spec :: Spec spec = describe "parse" $ do   it "parses unquoted values" $-    parseConfig "FOO=bar" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO=bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "parses values with spaces around equal signs" $ do-    parseConfig "FOO =bar" `shouldParse` [("FOO", "bar")]-    parseConfig "FOO= bar" `shouldParse` [("FOO", "bar")]-    parseConfig "FOO =\t bar" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO =bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]+    parseConfig "FOO= bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]+    parseConfig "FOO =\t bar"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "parses double-quoted values" $-    parseConfig "FOO=\"bar\"" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO=\"bar\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar"])]    it "parses single-quoted values" $-    parseConfig "FOO='bar'" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO='bar'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "bar"])]    it "parses escaped double quotes" $     parseConfig "FOO=\"escaped\\\"bar\""-      `shouldParse` [("FOO", "escaped\"bar")]+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "escaped\"bar"])]    it "supports CRLF line breaks" $     parseConfig "FOO=bar\r\nbaz=fbb"-      `shouldParse` [("FOO", "bar"), ("baz", "fbb")]+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"]),+                     ParsedVariable "baz" (Unquoted [VarLiteral "fbb"])]    it "parses empty values" $-    parseConfig "FOO=" `shouldParse` [("FOO", "")]+    parseConfig "FOO="+      `shouldParse` [ParsedVariable "FOO" (Unquoted [])] +  it "parses unquoted interpolated values" $ do+    parseConfig "FOO=$HOME"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarInterpolation "HOME"])]+    parseConfig "FOO=abc_$HOME"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "abc_",+                                                     VarInterpolation "HOME"])+                    ]+    parseConfig "FOO=${HOME}"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarInterpolation "HOME"])]+    parseConfig "FOO=abc_${HOME}"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "abc_",+                                                     VarInterpolation "HOME"])+                    ]++  it "parses double-quoted interpolated values" $ do+    parseConfig "FOO=\"$HOME\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarInterpolation "HOME"])]+    parseConfig "FOO=\"abc_$HOME\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "abc_",+                                                         VarInterpolation "HOME"])+                    ]+    parseConfig "FOO=\"${HOME}\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarInterpolation "HOME"])]+    parseConfig "FOO=\"abc_${HOME}\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "abc_",+                                                         VarInterpolation "HOME"])+                    ]++  it "parses single-quoted interpolated values as literals" $ do+    parseConfig "FOO='$HOME'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "$HOME"])]+    parseConfig "FOO='abc_$HOME'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "abc_$HOME"])]+    parseConfig "FOO='${HOME}'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "${HOME}"])]+    parseConfig "FOO='abc_${HOME}'"+      `shouldParse` [ParsedVariable "FOO" (SingleQuoted [VarLiteral "abc_${HOME}"])]+   it "does not parse if line format is incorrect" $ do     parseConfig `shouldFailOn` "lol$wut"     parseConfig `shouldFailOn` "KEY=\nVALUE"     parseConfig `shouldFailOn` "KEY\n=VALUE"    it "expands newlines in quoted strings" $-    parseConfig "FOO=\"bar\nbaz\"" `shouldParse` [("FOO", "bar\nbaz")]+    parseConfig "FOO=\"bar\nbaz\""+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar\nbaz"])]    it "does not parse variables with hyphens in the name" $     parseConfig `shouldFailOn` "FOO-BAR=foobar"    it "parses variables with \"_\" in the name" $-    parseConfig "FOO_BAR=foobar" `shouldParse` [("FOO_BAR", "foobar")]+    parseConfig "FOO_BAR=foobar"+      `shouldParse` [ParsedVariable "FOO_BAR" (Unquoted [VarLiteral "foobar"])]    it "parses variables with digits after the first character" $-    parseConfig "FOO_BAR_12=foobar" `shouldParse` [("FOO_BAR_12", "foobar")]+    parseConfig "FOO_BAR_12=foobar"+      `shouldParse` [ParsedVariable "FOO_BAR_12" (Unquoted [VarLiteral "foobar"])]    it "allows vertical spaces after a quoted variable" $-    parseConfig "foo='bar' " `shouldParse` [("foo", "bar")]+    parseConfig "foo='bar' "+      `shouldParse` [ParsedVariable "foo" (SingleQuoted [VarLiteral "bar"])]    it "does not parse variable names beginning with a digit" $     parseConfig `shouldFailOn` "45FOO_BAR=foobar"    it "strips unquoted values" $-    parseConfig "foo=bar " `shouldParse` [("foo", "bar")]+    parseConfig "foo=bar "+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"])]    it "ignores empty lines" $     parseConfig "\n \t  \nfoo=bar\n \nfizz=buzz"-      `shouldParse` [("foo", "bar"), ("fizz", "buzz")]+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"]),+                     ParsedVariable "fizz" (Unquoted [VarLiteral "buzz"])]    it "ignores inline comments after unquoted arguments" $-    parseConfig "FOO=bar # this is foo" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO=bar # this is foo"+      `shouldParse` [ParsedVariable "FOO" (Unquoted [VarLiteral "bar"])]    it "ignores inline comments after quoted arguments" $-    parseConfig "FOO=\"bar\" # this is foo" `shouldParse` [("FOO", "bar")]+    parseConfig "FOO=\"bar\" # this is foo"+      `shouldParse` [ParsedVariable "FOO" (DoubleQuoted [VarLiteral "bar"])]    it "allows \"#\" in quoted values" $     parseConfig "foo=\"bar#baz\" # comment"-      `shouldParse` [("foo", "bar#baz")]+      `shouldParse` [ParsedVariable "foo" (DoubleQuoted [VarLiteral "bar#baz"])]    it "ignores comment lines" $     parseConfig "\n\t \n\n # HERE GOES FOO \nfoo=bar"-      `shouldParse` [("foo", "bar")]+      `shouldParse` [ParsedVariable "foo" (Unquoted [VarLiteral "bar"])]    it "doesn't allow more configuration options after a quoted value" $     parseConfig `shouldFailOn` "foo='bar'baz='buz'" -parseConfig :: String -> Either (ParseError Char Dec) [(String, String)]+parseConfig :: String -> Either (ParseError Char Dec) [ParsedVariable] parseConfig = parse configParser ""
spec/Configuration/DotenvSpec.hs view
@@ -8,6 +8,10 @@  import System.Environment (lookupEnv) import Control.Monad (liftM)+import Data.Maybe (fromMaybe)+#if !MIN_VERSION_base(4,8,0)+import Data.Functor ((<$>))+#endif  #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$))@@ -82,6 +86,15 @@     it "recognizes unicode characters" $       liftM (!! 1) (parseFile "spec/fixtures/.dotenv") `shouldReturn`         ("UNICODE_TEST", "Manabí")++    it "recognises environment variables" $ do+      home <- fromMaybe "" <$> lookupEnv "HOME"+      liftM (!! 2) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("ENVIRONMENT", home)++    it "recognises previous variables" $+      liftM (!! 3) (parseFile "spec/fixtures/.dotenv") `shouldReturn`+        ("PREVIOUS", "true")    describe "onMissingFile" $ after_ (unsetEnv "DOTENV") $ do     context "when target file is present" $
spec/fixtures/.dotenv view
@@ -1,2 +1,4 @@ DOTENV=true UNICODE_TEST=Manabí+ENVIRONMENT="$HOME"+PREVIOUS="$DOTENV"
src/Configuration/Dotenv.hs view
@@ -19,6 +19,7 @@  where  import Configuration.Dotenv.Parse (configParser)+import Configuration.Dotenv.ParsedVariable (interpolateParsedVariables) import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO(..)) import System.Environment (lookupEnv)@@ -60,7 +61,7 @@    case parse configParser f contents of     Left e        -> error $ "Failed to read file" ++ show e-    Right options -> return options+    Right options -> liftIO $ interpolateParsedVariables options  applySetting :: MonadIO m => Bool -> (String, String) -> m () applySetting override (key, value) =
src/Configuration/Dotenv/Parse.hs view
@@ -16,43 +16,60 @@  module Configuration.Dotenv.Parse (configParser) where +import Configuration.Dotenv.ParsedVariable import Control.Applicative import Control.Monad import Text.Megaparsec import Text.Megaparsec.String (Parser) import qualified Text.Megaparsec.Lexer as L +data QuoteType = SingleQuote | DoubleQuote+ -- | Returns a parser for a Dotenv configuration file. Accepts key and value -- arguments separated by @=@. Comments in all positions are handled -- appropriately.-configParser :: Parser [(String, String)]+configParser :: Parser [ParsedVariable] configParser = between scn eof (sepEndBy1 envLine (eol <* scn))  -- | Parse a single environment variable assignment.-envLine :: Parser (String, String)-envLine = (,) <$> (lexeme variableName <* lexeme (char '=')) <*> lexeme value+envLine :: Parser ParsedVariable+envLine = ParsedVariable <$> (lexeme variableName <* lexeme (char '=')) <*> lexeme value  -- | Variables must start with a letter or underscore, and may contain -- letters, digits or '_' character after the first character.-variableName :: Parser String+variableName :: Parser VarName variableName = ((:) <$> firstChar <*> many otherChar) <?> "variable name"   where     firstChar = char '_'  <|> letterChar     otherChar = firstChar <|> digitChar  -- | Value: quoted or unquoted.-value :: Parser String+value :: Parser VarValue value = (quotedValue <|> unquotedValue) <?> "variable value"   where-    quotedValue   = quotedWith '\'' <|> quotedWith '\"'-    unquotedValue = many (noneOf "\'\" \t\n\r")+    quotedValue   = quotedWith SingleQuote <|> quotedWith DoubleQuote+    unquotedValue = Unquoted <$> (many $ fragment "\'\" \t\n\r")  -- | Parse a value quoted with given character.-quotedWith :: Char -> Parser String-quotedWith q = between (char q) (char q) (many $ escapedChar <|> normalChar)+quotedWith :: QuoteType -> Parser VarValue+quotedWith SingleQuote = SingleQuoted <$> (between (char '\'') (char '\'') $ many (literalValueFragment "\'\\"))+quotedWith DoubleQuote = DoubleQuoted <$> (between (char '\"') (char '\"') $ many (fragment "\""))++fragment :: [Char] -> Parser VarFragment+fragment charsToEscape = interpolatedValueFragment <|> literalValueFragment ('$' : '\\' : charsToEscape)++interpolatedValueFragment :: Parser VarFragment+interpolatedValueFragment = VarInterpolation <$>+                            ((between (symbol "${") (symbol "}") variableName) <|>+                            (char '$' >> variableName))   where+    symbol                = L.symbol sc++literalValueFragment :: [Char] -> Parser VarFragment+literalValueFragment charsToEscape = VarLiteral <$> (some $ escapedChar <|> normalChar)+  where     escapedChar = (char '\\' *> anyChar) <?> "escaped character"-    normalChar  = noneOf (q : "\\") <?> "unescaped character"+    normalChar  = noneOf charsToEscape <?> "unescaped character"  ---------------------------------------------------------------------------- -- Boilerplate and whitespace setup
+ src/Configuration/Dotenv/ParsedVariable.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE CPP #-}++module Configuration.Dotenv.ParsedVariable (ParsedVariable(..),+                                            VarName,+                                            VarValue(..),+                                            VarContents,+                                            VarFragment(..),+                                            interpolateParsedVariables) where++import Control.Monad (foldM)+#if !MIN_VERSION_base(4,8,0)+import Data.Functor ((<$>))+#endif+import Control.Applicative ((<|>))+import System.Environment (lookupEnv)++data ParsedVariable+  = ParsedVariable VarName VarValue deriving (Show, Eq)++type VarName = String++data VarValue+  = Unquoted VarContents+  | SingleQuoted VarContents+  | DoubleQuoted VarContents deriving (Show, Eq)++type VarContents = [VarFragment]++data VarFragment+  = VarInterpolation String+  | VarLiteral String deriving (Show, Eq)++interpolateParsedVariables :: [ParsedVariable] -> IO [(String, String)]+interpolateParsedVariables = fmap reverse . foldM addInterpolated []++addInterpolated :: [(String, String)] -> ParsedVariable -> IO [(String, String)]+addInterpolated previous (ParsedVariable name value) = (: previous) <$> ((,) name <$> interpolate previous value)++interpolate :: [(String, String)] -> VarValue -> IO String+interpolate _        (SingleQuoted contents) = return $ joinContents contents+interpolate previous (DoubleQuoted contents) = interpolateContents previous contents+interpolate previous (Unquoted     contents) = interpolateContents previous contents++interpolateContents :: [(String, String)] -> VarContents -> IO String+interpolateContents previous contents = concat <$> mapM (interpolateFragment previous) contents++interpolateFragment :: [(String, String)] -> VarFragment -> IO String+interpolateFragment _        (VarLiteral       value  ) = return value+interpolateFragment previous (VarInterpolation varname) = fromPreviousOrEnv >>= maybe (return "") return+  where+    fromPreviousOrEnv = (lookup varname previous <|>) <$> lookupEnv varname++joinContents :: VarContents -> String+joinContents = concatMap fragmentToString+  where+    fragmentToString (VarInterpolation value) = value+    fragmentToString (VarLiteral value)       = value