{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} module Data.Configurator.Parser ( Parser , runParser , bool , int , string , value , list , optional , required , subassocs ) where import Protolude hiding (bool, list, optional) import Data.Functor.Compose import qualified Data.Map.Strict as M import qualified Data.Scientific as Scientific import Data.Text (Text) import qualified Data.Text as T import Data.Configurator.Types -- | A generic parser. -- -- A @'Parser' a b@ knows how to extract a @b@ from an @a@. Typical -- instances are @'Parser' 'Value' a@, which handles the parsing of -- individual configuration values, and @'Parser' 'Config' a@, which -- handles extracting data from a full keyed configuration file. newtype Parser a b = Parser { getParser :: Compose ((->) a) (Either Text) b } deriving (Functor, Applicative) makeParser :: (a -> Either Text b) -> Parser a b makeParser = Parser . Compose -- | Run a parser. -- -- @'runParser' p x@ runs the parser @p@ on the input @x@, returning -- a value @'Right' v@ on success, or @'Left' err@ on error. runParser :: Parser a b -> a -> Either Text b runParser = getCompose . getParser instance Monad (Parser a) where p >>= f = makeParser $ \v -> runParser p v >>= \w -> runParser (f w) v -- | Parse a required configuration field. -- -- @'required' key p@ expects the field @key@ to be present, and parses -- its value with @p@. required :: Key -> Parser Value a -> Parser Config a required key pv = makeParser $ \cfg -> case M.lookup key cfg of Nothing -> Left $ "missing key: " <> key Just v -> runParser pv v -- | Parse an optional configuration field. -- -- @'optional' key p@ returns 'Nothing' if the field @key@ is not present. -- Otherwise it returns @'Just' v@, where @v@ is the result of parsing the -- field value with @p@. optional :: Key -> Parser Value a -> Parser Config (Maybe a) optional key pv = makeParser $ \cfg -> case M.lookup key cfg of Nothing -> Right Nothing Just v -> Just <$> runParser pv v -- | Parse a set of fields with a shared prefix. -- -- @'subassocs' prefix p@ extracts all configuration keys one level -- below @prefix@, and collects pairs of the full keys and the -- corresponding field values parsed with @p@. subassocs :: Key -> Parser Value a -> Parser Config [(Key, a)] subassocs prefix pv = makeParser $ \cfg -> M.toList <$> mapM (runParser pv) (M.filterWithKey match cfg) where match k _ = if T.null prefix then not (T.isInfixOf "." k) else case T.stripPrefix (prefix <> ".") k of Nothing -> False Just suff -> not (T.isInfixOf "." suff) -- | Parse a list of values. -- -- @'list' p@ expects a list value, and parses each entry with @p@. list :: Parser Value a -> Parser Value [a] list p = makeParser $ \case List vs -> mapM (runParser p) vs _ -> Left "expected a list" -- | Extract a raw value. -- -- 'value' returns a configuration value in its raw form. value :: Parser Value Value value = makeParser pure -- | Extract a string value. -- -- 'string' expects the given value to be a string. string :: Parser Value Text string = makeParser $ \case String s -> Right s _ -> Left "expected a string" -- | Extract an integer value. -- -- 'int' expects the given value to be an 'Int'. int :: Parser Value Int int = makeParser $ \case Number n -> if Scientific.isInteger n then case Scientific.toBoundedInteger n of Just x -> Right x Nothing -> Left "int out of bounds" else Left "expected an integer" _ -> Left "expected an integer" -- | Extract a boolean value. -- -- 'bool' expects the given value to be boolean. bool :: Parser Value Bool bool = makeParser $ \case Bool b -> Right b _ -> Left "expected a boolean"