module DotEnv (applyDotEnv) where

import Control.Monad.IO.Class (MonadIO(..))
import Data.Foldable (traverse_)
import Data.Functor (void)
import Data.List (sortOn)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.Ord (Down(..))
import System.Environment (setEnv)
import qualified Text.ParserCombinators.ReadP as P (ReadP, readP_to_S, char, munch, sepBy1)

-- directory
import System.Directory (doesFileExist)

-- | Load, parse and apply a @.env@ file
--
-- NB : overwrites any preexisting env vars
--
-- NB2 : if the @.env@ file is not found the program continues (i.e. this function is a no-op in that case)
applyDotEnv :: MonadIO m =>
               Maybe FilePath -- ^ defaults to @.env@ if Nothing
            -> m ()
applyDotEnv :: forall (m :: * -> *). MonadIO m => Maybe FilePath -> m ()
applyDotEnv Maybe FilePath
mfp = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
  let
    fpath :: FilePath
fpath = forall a. a -> Maybe a -> a
fromMaybe FilePath
".env" Maybe FilePath
mfp
  Bool
ok <- FilePath -> IO Bool
doesFileExist FilePath
fpath
  if Bool
ok
    then
    do
      Maybe [(FilePath, FilePath)]
mp <- FilePath -> Maybe [(FilePath, FilePath)]
parseDotEnv forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> IO FilePath
readFile FilePath
fpath
      case Maybe [(FilePath, FilePath)]
mp of
        Just [(FilePath, FilePath)]
es -> forall (m :: * -> *). MonadIO m => [(FilePath, FilePath)] -> m ()
setEnvs [(FilePath, FilePath)]
es
        Maybe [(FilePath, FilePath)]
Nothing -> FilePath -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath
unwords [FilePath
"dotenv: cannot parse", FilePath
fpath]
    else
    do
      FilePath -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath
unwords [FilePath
"dotenv:", FilePath
fpath, FilePath
"not found"]

setEnvs :: MonadIO m => [(String, String)] -> m ()
setEnvs :: forall (m :: * -> *). MonadIO m => [(FilePath, FilePath)] -> m ()
setEnvs = forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ forall {m :: * -> *}. MonadIO m => (FilePath, FilePath) -> m ()
insf
  where
    insf :: (FilePath, FilePath) -> m ()
insf (FilePath
k, FilePath
v) = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
      FilePath -> FilePath -> IO ()
setEnv FilePath
k FilePath
v
      FilePath -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath
unwords [FilePath
"dotenv: set", FilePath
k] -- DEBUG

parseDotEnv :: String -- ^ contents of the @.env@ file
            -> Maybe [(String, String)]
parseDotEnv :: FilePath -> Maybe [(FilePath, FilePath)]
parseDotEnv = forall (t :: * -> *) a.
Foldable t =>
ReadP (t a) -> FilePath -> Maybe (t a)
parse1 ReadP [(FilePath, FilePath)]
keyValues

keyValues :: P.ReadP [(String, String)]
keyValues :: ReadP [(FilePath, FilePath)]
keyValues = forall a sep. ReadP a -> ReadP sep -> ReadP [a]
P.sepBy1 ReadP (FilePath, FilePath)
keyValue (Char -> ReadP Char
P.char Char
'\n') forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Char -> ReadP Char
P.char Char
'\n'

keyValue :: P.ReadP (String, String)
keyValue :: ReadP (FilePath, FilePath)
keyValue = do
  FilePath
k <- ReadP FilePath
keyP
  forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ Char -> ReadP Char
P.char Char
'='
  FilePath
v <- ReadP FilePath
valueP
  forall (f :: * -> *) a. Applicative f => a -> f a
pure (FilePath
k, FilePath
v)

keyP, valueP :: P.ReadP String
keyP :: ReadP FilePath
keyP = (Char -> Bool) -> ReadP FilePath
P.munch (forall a. Eq a => a -> a -> Bool
/= Char
'=')
valueP :: ReadP FilePath
valueP = (Char -> Bool) -> ReadP FilePath
P.munch (forall a. Eq a => a -> a -> Bool
/= Char
'\n')

-- parse :: P.ReadP b -> String -> Maybe b
-- parse p str = fst <$> (listToMaybe $ P.readP_to_S p str)

parse1 :: Foldable t => P.ReadP (t a) -> String -> Maybe (t a)
parse1 :: forall (t :: * -> *) a.
Foldable t =>
ReadP (t a) -> FilePath -> Maybe (t a)
parse1 ReadP (t a)
p FilePath
str = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> a
fst forall a b. (a -> b) -> a -> b
$ forall a. [a] -> Maybe a
listToMaybe forall a b. (a -> b) -> a -> b
$ forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (forall a. a -> Down a
Down forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Int
length forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) (forall a. ReadP a -> ReadS a
P.readP_to_S ReadP (t a)
p FilePath
str)