{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module Nest ( -- * Errors NestError (..) , renderNestError -- * Types , Environment (..) , Parser (..) -- * Basic parsers , variable , string , numeric , flag , setting , failure -- * Combinators for enriching parsers , option , withDefault , withContext -- * Run parsers with environment , run , runT , runWith , runWithT , force ) where import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Trans.Class (MonadTrans (..)) import Data.ByteString (ByteString) import Data.Map (Map) import qualified Data.Map as Map import Data.String (IsString (..)) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Text.IO as Text import Nest.Prelude import qualified System.Exit as Exit import qualified System.IO as IO import qualified System.Posix.Env.ByteString as Posix data NestError = NestMissing ByteString | NestParseError ByteString Text | NestContextError Text NestError deriving (Eq, Ord, Show) renderNestError :: NestError -> Text renderNestError e = case e of NestMissing field -> mconcat ["[", Text.decodeUtf8 field, "]: required environment variable not found."] NestParseError field msg -> mconcat ["[", Text.decodeUtf8 field, "]: environment variable could not be parsed. ", msg] NestContextError context err -> mconcat [renderNestError err, " | ", context] newtype Environment = Environment { getEnvironment :: Map ByteString ByteString } deriving (Eq, Ord, Show) newtype Parser m a = Parser { parse :: Environment -> EitherT NestError m a } instance Functor m => Functor (Parser m) where fmap f m = Parser $ \e -> f <$> parse m e instance Monad m => Applicative (Parser m) where pure a = Parser $ \_ -> pure a f <*> a = Parser $ \e -> parse f e <*> parse a e instance Monad m => Monad (Parser m) where return = pure m >>= f = Parser $ \e -> newEitherT $ runEitherT (parse m e) >>= \x -> case x of Left err -> pure $ Left err Right a -> runEitherT $ parse (f a) e instance MonadIO m => MonadIO (Parser m) where liftIO = lift . liftIO instance MonadTrans Parser where lift = Parser . const . lift variable :: Monad m => ByteString -> Parser m ByteString variable name = Parser $ \e -> fromMaybeM (left $ NestMissing name) $ Map.lookup name $ getEnvironment e string :: (Monad m, IsString s) => ByteString -> Parser m s string name = with (variable name) $ fromString . Text.unpack . Text.decodeUtf8 numeric :: (Monad m, Read n, Num n) => ByteString -> Parser m n numeric name = do s <- string name fromMaybeM (failure name . mconcat $ ["Could not parse numeric value from '", s ,"'"]) $ readMaybe . Text.unpack $ s flag :: Monad m => ByteString -> a -> a -> Parser m a flag name true false = do s <- string name Parser $ \_ -> case s of "t" -> pure true "true" -> pure true "1" -> pure true "f" -> pure false "false" -> pure false "0" -> pure false _ -> left . NestParseError name . mconcat $ [ "Invalid boolean flag value, expected true ['t', 'true', '1'] or false ['f', 'false', '0'], got: ", s ] setting :: Monad m => ByteString -> Map Text a -> Parser m a setting name settings = do s <- string name Parser $ \_ -> case Map.lookup s settings of Just x -> pure x Nothing -> left . NestParseError name $ mconcat [ "Unknown setting option [" , s , "]. Expected one of: [" , Text.intercalate ", " $ Map.keys settings , "]" ] failure :: Monad m => ByteString -> Text -> Parser m a failure name message = Parser $ \_ -> left $ NestParseError name message option :: Monad m => Parser m a -> Parser m (Maybe a) option p = Parser $ \e -> newEitherT $ runEitherT (parse p e) >>= \xx -> case xx of Left (NestMissing _) -> pure $ Right Nothing Left err -> pure $ Left err Right x -> pure $ Right (Just x) withDefault :: Monad m => Parser m a -> a -> Parser m a withDefault p dfault = Parser $ \e -> newEitherT $ runEitherT (parse p e) >>= \xx -> case xx of Left (NestMissing _) -> pure $ Right dfault x -> pure x withContext :: Monad m => Parser m a -> Text -> Parser m a withContext p context = Parser $ \e -> newEitherT $ runEitherT (parse p e) >>= \xx -> case xx of Left err -> pure . Left $ NestContextError context err x -> pure x run :: MonadIO m => Parser m a -> m (Either NestError a) run p = liftIO Posix.getEnvironment >>= flip runWith p . Environment . Map.fromList runT :: MonadIO m => Parser m a -> EitherT NestError m a runT = newEitherT . run runWith :: MonadIO m => Environment -> Parser m a -> m (Either NestError a) runWith e p = runEitherT (parse p e) runWithT :: MonadIO m => Environment -> Parser m a -> EitherT NestError m a runWithT e = newEitherT . runWith e force :: MonadIO m => Parser m a -> m a force p = run p >>= \e -> case e of Left err -> liftIO $ do Text.hPutStrLn IO.stderr $ renderNestError err Exit.exitFailure Right a -> pure a