packages feed

conferer 1.0.0.1 → 1.1.0.0

raw patch · 22 files changed

+271/−242 lines, 22 filesdep ~bytestringdep ~containersdep ~textPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: bytestring, containers, text

API changes (from Hackage documentation)

- Conferer.FromConfig: data TypeMismatchWithDefault
- Conferer.FromConfig: throwTypeMismatchWithDefault :: forall a b. Typeable a => Key -> Dynamic -> IO b
- Conferer.FromConfig: typeMismatchWithDefault :: forall a. Typeable a => Key -> Dynamic -> TypeMismatchWithDefault
- Conferer.FromConfig.Internal: TypeMismatchWithDefault :: Key -> Dynamic -> TypeRep -> TypeMismatchWithDefault
- Conferer.FromConfig.Internal: data TypeMismatchWithDefault
- Conferer.FromConfig.Internal: fetchFromConfigG :: FromConfigG f => Key -> Config -> IO (f a)
- Conferer.FromConfig.Internal: fetchFromConfigWithConNameG :: FromConfigWithConNameG f => String -> Key -> Config -> IO (f a)
- Conferer.FromConfig.Internal: instance Conferer.FromConfig.Internal.FromConfig inner => Conferer.FromConfig.Internal.FromConfigG (GHC.Generics.Rec0 inner)
- Conferer.FromConfig.Internal: instance GHC.Classes.Eq Conferer.FromConfig.Internal.TypeMismatchWithDefault
- Conferer.FromConfig.Internal: instance GHC.Exception.Type.Exception Conferer.FromConfig.Internal.TypeMismatchWithDefault
- Conferer.FromConfig.Internal: instance GHC.Show.Show Conferer.FromConfig.Internal.TypeMismatchWithDefault
- Conferer.FromConfig.Internal: throwTypeMismatchWithDefault :: forall a b. Typeable a => Key -> Dynamic -> IO b
- Conferer.FromConfig.Internal: typeMismatchWithDefault :: forall a. Typeable a => Key -> Dynamic -> TypeMismatchWithDefault
+ Conferer: fetchFromConfig :: forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a
+ Conferer.Config: removeDefault :: forall t. Typeable t => Key -> Config -> Config
+ Conferer.FromConfig: OverrideFromConfig :: (Key -> Config -> IO a) -> OverrideFromConfig a
+ Conferer.FromConfig: data OverrideFromConfig a
+ Conferer.FromConfig: fromConfig :: (FromConfig a, Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a
+ Conferer.FromConfig: overrideFetch :: forall a. Typeable a => (Key -> Config -> IO a) -> Dynamic
+ Conferer.FromConfig.Internal: OverrideFromConfig :: (Key -> Config -> IO a) -> OverrideFromConfig a
+ Conferer.FromConfig.Internal: data OverrideFromConfig a
+ Conferer.FromConfig.Internal: fromConfig :: (FromConfig a, Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a
+ Conferer.FromConfig.Internal: fromConfigG :: FromConfigG f => Key -> Config -> IO (f a)
+ Conferer.FromConfig.Internal: fromConfigWithConNameG :: FromConfigWithConNameG f => String -> Key -> Config -> IO (f a)
+ Conferer.FromConfig.Internal: fromDynamics :: forall a. Typeable a => [Dynamic] -> Maybe a
+ Conferer.FromConfig.Internal: instance (Conferer.FromConfig.Internal.FromConfig inner, Data.Typeable.Internal.Typeable inner) => Conferer.FromConfig.Internal.FromConfigG (GHC.Generics.Rec0 inner)
+ Conferer.FromConfig.Internal: overrideFetch :: forall a. Typeable a => (Key -> Config -> IO a) -> Dynamic
+ Conferer.Key: isKeyCharacter :: Char -> Bool
+ Conferer.Key: isValidKeyFragment :: Text -> Bool
+ Conferer.Key.Internal: isKeyCharacter :: Char -> Bool
+ Conferer.Key.Internal: isValidKeyFragment :: Text -> Bool
+ Conferer.Source.PropertiesFile: fromFilePath' :: FilePath -> IO Source
- Conferer: unsafeFetchKey :: forall a. FromConfig a => Config -> Key -> IO a
+ Conferer: unsafeFetchKey :: forall a. (FromConfig a, Typeable a) => Config -> Key -> IO a
- Conferer.Config: FoundInDefaults :: Key -> Dynamic -> KeyLookupResult
+ Conferer.Config: FoundInDefaults :: Key -> [Dynamic] -> KeyLookupResult
- Conferer.Config.Internal: getKeyFromDefaults :: Key -> Config -> Maybe Dynamic
+ Conferer.Config.Internal: getKeyFromDefaults :: Key -> Config -> Maybe [Dynamic]
- Conferer.Config.Internal: removeDefault :: Key -> Config -> Config
+ Conferer.Config.Internal: removeDefault :: forall t. Typeable t => Key -> Config -> Config
- Conferer.Config.Internal.Types: Config :: [Source] -> Map Key Dynamic -> [(Key, Key)] -> Config
+ Conferer.Config.Internal.Types: Config :: [Source] -> Map Key [Dynamic] -> [(Key, Key)] -> Config
- Conferer.Config.Internal.Types: FoundInDefaults :: Key -> Dynamic -> KeyLookupResult
+ Conferer.Config.Internal.Types: FoundInDefaults :: Key -> [Dynamic] -> KeyLookupResult
- Conferer.Config.Internal.Types: [configDefaults] :: Config -> Map Key Dynamic
+ Conferer.Config.Internal.Types: [configDefaults] :: Config -> Map Key [Dynamic]
- Conferer.FromConfig: FoundInDefaults :: Key -> Dynamic -> KeyLookupResult
+ Conferer.FromConfig: FoundInDefaults :: Key -> [Dynamic] -> KeyLookupResult
- Conferer.FromConfig: fetchFromConfig :: (FromConfig a, Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a
+ Conferer.FromConfig: fetchFromConfig :: forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a
- Conferer.FromConfig: fetchFromDefaults :: forall a. Typeable a => Key -> Config -> IO (Maybe a)
+ Conferer.FromConfig: fetchFromDefaults :: forall a. Typeable a => Key -> Config -> Maybe a
- Conferer.FromConfig: fetchFromRootConfig :: forall a. FromConfig a => Config -> IO a
+ Conferer.FromConfig: fetchFromRootConfig :: forall a. (FromConfig a, Typeable a) => Config -> IO a
- Conferer.FromConfig.Internal: fetchFromConfig :: (FromConfig a, Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a
+ Conferer.FromConfig.Internal: fetchFromConfig :: forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a
- Conferer.FromConfig.Internal: fetchFromDefaults :: forall a. Typeable a => Key -> Config -> IO (Maybe a)
+ Conferer.FromConfig.Internal: fetchFromDefaults :: forall a. Typeable a => Key -> Config -> Maybe a
- Conferer.FromConfig.Internal: fetchFromRootConfig :: forall a. FromConfig a => Config -> IO a
+ Conferer.FromConfig.Internal: fetchFromRootConfig :: forall a. (FromConfig a, Typeable a) => Config -> IO a
- Conferer.Source.PropertiesFile: fromFilePath :: FilePath -> IO Source
+ Conferer.Source.PropertiesFile: fromFilePath :: FilePath -> SourceCreator

Files

CHANGELOG.md view
@@ -8,6 +8,22 @@  Nothing +## [v1.1.0.0] - 2021-03-01++### Changed++* Rename `fromFilePath` to `fromFilePath'`.+* Define a new `fromFilePath` whose type is `FilePath -> SourceCreator` instaed of `FilePath -> IO Source`.+* (Internal) Remove the mismatched type exception since it's not actionable by the user+* (Internal) Use a list for default values so that many different defaults are available,+  possible point of extension in the future.+* Constraint valid key names to lowercase ascii and numbers (previously some non ascii characters were allowed)++### Added++* `isValidKeyFragment` and `isKeyCharacter` to validate `Key`s+* Added an overriding method based on type and key (details in the docs)+ ## [v1.0.0.1] - 2021-01-17  ### Fixed@@ -27,6 +43,7 @@  First release -[Unreleased]: https://github.com/ludat/conferer/compare/conferer_v1.0.0.0...HEAD-[v1.0.0.0]: https://github.com/ludat/conferer/releases/tag/v0.0.0.0...conferer_v1.0.0.0+[Unreleased]: https://github.com/ludat/conferer/compare/conferer_v1.1.0.0...HEAD+[v1.1.0.0]: https://github.com/ludat/conferer/releases/tag/conferer_v1.0.0.1...conferer_v1.1.0.0 [v1.0.0.1]: https://github.com/ludat/conferer/releases/tag/conferer_v1.0.0.0...conferer_v1.0.0.1+[v1.0.0.0]: https://github.com/ludat/conferer/releases/tag/v0.0.0.0...conferer_v1.0.0.0
conferer.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: e95018cf9b8ebe424a2926412853affcae168760a3b9ea0530fbf3a4c1d83b14+-- hash: 80fdd520360a49e55b6dfe3ff9e148c716a7af0b6cf3d0326f8ed3d4a8f67611  name:           conferer-version:        1.0.0.1+version:        1.1.0.0 synopsis:       Configuration management library  description:    Library to abstract the parsing of many haskell config values from different config sources
src/Conferer.hs view
@@ -22,6 +22,7 @@   , fetch   , fetch'   , fetchKey+  , fetchFromConfig   , safeFetchKey   , unsafeFetchKey   , DefaultConfig(..)@@ -69,7 +70,7 @@ safeFetchKey c k = fetchFromConfig k c  -- | Same as 'fetchKey' but it throws when the value isn't present.-unsafeFetchKey :: forall a. (FromConfig a) => Config -> Key -> IO a+unsafeFetchKey :: forall a. (FromConfig a, Typeable a) => Config -> Key -> IO a unsafeFetchKey c k = fetchFromConfig k c  
src/Conferer/Config.hs view
@@ -21,6 +21,7 @@   , addDefault   , addDefaults   , addKeyMappings+  , removeDefault   , Defaults     -- * Re-Exports   , module Conferer.Key
src/Conferer/Config/Internal.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE TypeApplications #-} -- | -- Copyright: (c) 2019 Lucas David Traverso -- License: MPL-2.0@@ -17,6 +18,7 @@ import Data.Dynamic import Data.List (sort, nub, union) import Data.Text (Text)+import Data.Maybe (isJust) import qualified Data.Map as Map  import Conferer.Key@@ -155,7 +157,7 @@         Nothing -> go otherSources  -- | This function gets values from the defaults-getKeyFromDefaults :: Key -> Config -> Maybe Dynamic+getKeyFromDefaults :: Key -> Config -> Maybe [Dynamic] getKeyFromDefaults key Config{..} =   let     possibleKeys = fmap mappedKey $ getKeysFromMappings configKeyMappings key@@ -180,13 +182,41 @@ -- | This function adds defaults to a 'Config' addDefaults :: [(Key, Dynamic)] -> Config -> Config addDefaults configMap config =+  let+    constructedMap =+      Map.fromListWith (++)+      . fmap (\(k,v) -> (k, [v]))+      $ configMap+  in config+  { configDefaults =+    Map.unionWith (++) constructedMap+      $ configDefaults config+  }++-- | This function removes a default from a 'Config', this is the+-- oposite of 'addDefault', it deletes the first element of+-- matching type in a certain 'Key'.+removeDefault :: forall t. Typeable t => Key -> Config -> Config+removeDefault key config =   config   { configDefaults =-    Map.fromList configMap-      `Map.union`-      configDefaults config+      Map.update removeFirstDynamic key $ configDefaults config   }+  where+    removeFirst :: (a -> Bool) -> [a] -> [a]+    removeFirst _ [] = []+    removeFirst condition (x:xs) =+      if condition x+        then xs+        else x : removeFirst condition xs +    removeFirstDynamic :: [Dynamic] -> Maybe [Dynamic]+    removeFirstDynamic dynamics =+      let result = removeFirst (isJust . fromDynamic @t) dynamics+      in if null result+          then Nothing +          else Just result+ -- | This function adds one default of a custom type to a 'Config' -- --   Note that unlike 'addDefaults' this function does the toDyn so@@ -194,14 +224,7 @@ addDefault :: (Typeable a) => Key -> a -> Config -> Config addDefault key value config =   config-  { configDefaults = Map.insert key (toDyn value) $ configDefaults config-  }---- | This function removes a key default from a 'Config'-removeDefault :: Key -> Config -> Config-removeDefault key config =-  config-  { configDefaults = Map.delete key $ configDefaults config+  { configDefaults = Map.insertWith (++) key [toDyn value] $ configDefaults config   }  -- | Instantiate a 'Source' using an 'SourceCreator' and a 'Config' and add
src/Conferer/Config/Internal/Types.hs view
@@ -21,7 +21,7 @@ data Config =   Config   { configSources :: [Source]-  , configDefaults :: Map Key Dynamic+  , configDefaults :: Map Key [Dynamic]   , configKeyMappings :: [(Key, Key)]   } deriving (Show) @@ -29,7 +29,7 @@ data KeyLookupResult   = MissingKey [Key]   | FoundInSources Key Text-  | FoundInDefaults Key Dynamic+  | FoundInDefaults Key [Dynamic]   deriving (Show)  -- | The type for creating a source given a 'Config', some sources require a
src/Conferer/FromConfig.hs view
@@ -7,7 +7,8 @@ -- -- Public API module providing FromConfig functionality module Conferer.FromConfig -  ( FromConfig(fetchFromConfig)+  ( FromConfig(fromConfig)+  , fetchFromConfig   , DefaultConfig(configDef)   , fetchFromConfigWithDefault   , fetchFromRootConfig@@ -26,13 +27,12 @@   , throwConfigParsingError   , configParsingError -  , TypeMismatchWithDefault-  , throwTypeMismatchWithDefault-  , typeMismatchWithDefault   , Key   , (/.)   , File(..)   , KeyLookupResult(..)+  , OverrideFromConfig(..)+  , overrideFetch    , fetchFromDefaults   , fetchRequiredFromDefaults
src/Conferer/FromConfig/Internal.hs view
@@ -26,7 +26,7 @@ import Text.Read (readMaybe) import Data.Dynamic import GHC.Generics-import Data.Function (on, (&))+import Data.Function ((&))  import Conferer.Key import Conferer.Config.Internal.Types@@ -37,6 +37,7 @@ import qualified System.FilePath as FilePath import Data.List (nub, foldl', sort) import Data.String (IsString(..))+import Data.Foldable (msum)  -- | The typeclass for defining the way to get values from a 'Config', hiding the -- 'Text' based nature of the 'Conferer.Source.Source's and parse whatever value@@ -60,16 +61,22 @@   --   -- * Try desconstructing the value in as many keys as possible since is allows easier   -- partial overriding.-  fetchFromConfig :: Key -> Config -> IO a-  default fetchFromConfig :: (Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a-  fetchFromConfig k c = do-    defaultValue <- fetchFromDefaults @a k c-    let config =-          case defaultValue of-            Just d -> c & addDefaults (intoDefaultsG k $ from d)-            Nothing -> c-    to <$> fetchFromConfigG k config+  fromConfig :: Key -> Config -> IO a+  default fromConfig :: (Typeable a, Generic a, IntoDefaultsG (Rep a), FromConfigG (Rep a)) => Key -> Config -> IO a+  fromConfig key config = do+    let configWithDefaults = case fetchFromDefaults @a key config of+          Just d -> config & addDefaults (intoDefaultsG key $ from d)+          Nothing -> config+    to <$> fromConfigG key configWithDefaults +fetchFromConfig :: forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a+fetchFromConfig key config =+  case fetchFromDefaults @(OverrideFromConfig a) key config of+    Just (OverrideFromConfig fetch) ->+      fetch key $ removeDefault @(OverrideFromConfig a) key config+    Nothing ->+      fromConfig key config+ -- | Utility only typeclass to smooth the naming differences between default values for -- external library settings --@@ -78,26 +85,24 @@   configDef :: a  instance {-# OVERLAPPABLE #-} Typeable a => FromConfig a where-  fetchFromConfig key config = do-    fetchFromConfigWith (const Nothing) key config+  fromConfig = fetchRequiredFromDefaults  instance FromConfig () where-  fetchFromConfig _key _config = return ()+  fromConfig _key _config = return ()  instance FromConfig String where-  fetchFromConfig = fetchFromConfigWith (Just . Text.unpack)+  fromConfig = fetchFromConfigWith (Just . Text.unpack)  instance {-# OVERLAPPABLE #-} (Typeable a, FromConfig a) =>     FromConfig [a] where-  fetchFromConfig key config = do+  fromConfig key config = do     keysForItems <- getSubkeysForItems     case keysForItems of       Nothing -> do         fetchRequiredFromDefaults @[a] key config       Just subkeys -> do-        defaultsMay <- fetchFromDefaults @[a] key config         let configWithDefaults :: Config =-              case defaultsMay of+              case fetchFromDefaults @[a] key config of                 Just defaults ->                   foldl' (\c (index, value) ->                     c & addDefault (key /. "defaults" /. mkKey (show index)) value) config@@ -111,7 +116,7 @@               else                 configWithDefaults & addKeyMappings [(key /. k, key /. "prototype")])     where-    getSubkeysForItems ::IO (Maybe [Key])+    getSubkeysForItems :: IO (Maybe [Key])     getSubkeysForItems = do       fetchFromConfig @(Maybe Text) (key /. "keys") config         >>= \case@@ -135,40 +140,37 @@             return $ if null subelements then Nothing else Just subelements  instance FromConfig Int where-  fetchFromConfig = fetchFromConfigByRead+  fromConfig = fetchFromConfigByRead  instance FromConfig Integer where-  fetchFromConfig k c = do-    fetchFromConfigByRead k c+  fromConfig = fetchFromConfigByRead  instance FromConfig Float where-  fetchFromConfig = fetchFromConfigByRead+  fromConfig = fetchFromConfigByRead  instance FromConfig BS.ByteString where-  fetchFromConfig = fetchFromConfigWith (Just . Text.encodeUtf8)+  fromConfig = fetchFromConfigWith (Just . Text.encodeUtf8)  instance FromConfig LBS.ByteString where-  fetchFromConfig = fetchFromConfigWith (Just . LBS.fromStrict . Text.encodeUtf8)+  fromConfig = fetchFromConfigWith (Just . LBS.fromStrict . Text.encodeUtf8)  instance forall a. (Typeable a, FromConfig a) => FromConfig (Maybe a) where-  fetchFromConfig key config = do+  fromConfig key config = do     let-      newConfig =-        case getKeyFromDefaults key config >>= fromDynamic @(Maybe a) of-        Just (Just defaultThing) -> do-          config & addDefault key defaultThing-        Just Nothing -> do-          config & removeDefault key-        _ -> do-          config-    (Just <$> fetchFromConfig @a key newConfig)+      configWithUnwrappedDefault =+        case fetchFromDefaults @(Maybe a) key config of+          Just (Just defaultThing) -> do+            config & addDefault key defaultThing+          _ -> do+            config+    (Just <$> fetchFromConfig @a key configWithUnwrappedDefault)       `catch` (\(_e :: MissingRequiredKey) -> return Nothing)  instance FromConfig Text where-  fetchFromConfig = fetchFromConfigWith Just+  fromConfig = fetchFromConfigWith Just  instance FromConfig Bool where-  fetchFromConfig = fetchFromConfigWith parseBool+  fromConfig = fetchFromConfigWith parseBool  -- | A newtype wrapper for a 'FilePath' to allow implementing 'FromConfig' -- with something better than just a 'String'@@ -183,9 +185,8 @@   fromString s = File s  instance FromConfig File where-  fetchFromConfig key config' = do-    defaultPath <- fetchFromDefaults @File key config'-    let config = removeDefault key config'+  fromConfig key config = do+    let defaultPath = fetchFromDefaults @File key config      filepath <- fetchFromConfig @(Maybe String) key config @@ -222,6 +223,14 @@     "true" -> Just True     _ -> Nothing +data OverrideFromConfig a =+  OverrideFromConfig (Key -> Config -> IO a)++-- | Allow the programmer to override this 'FromConfig' instance by providing+-- a special 'OverrideFromConfig' value.+--+-- To avoid infinite recursion we remove the Override before calling the value+ -- | Helper function to implement fetchFromConfig using the 'Read' instance fetchFromConfigByRead :: (Typeable a, Read a) => Key -> Config -> IO a fetchFromConfigByRead = fetchFromConfigWith (readMaybe . Text.unpack)@@ -245,13 +254,16 @@           Nothing -> do             throwConfigParsingError @a k value -      FoundInDefaults k dynamic ->-        case fromDynamic dynamic of+      FoundInDefaults k dynamics ->+        case fromDynamics dynamics of           Just a -> do             return a           Nothing -> do-            throwTypeMismatchWithDefault @a k dynamic+            throwMissingRequiredKeys @a [k] +fromDynamics :: forall a. Typeable a => [Dynamic] -> Maybe a+fromDynamics =+  msum . fmap (fromDynamic @a)  -- | Helper function does the plumbing of desconstructing a default into smaller -- defaults, which is usefull for nested 'fetchFromConfig'.@@ -266,8 +278,7 @@   Config ->   IO Config addDefaultsAfterDeconstructingToDefaults destructureValue key config = do-  fetchFromDefaults @a key config-    >>= \case+  case fetchFromDefaults @a key config of     Just value -> do       let newDefaults =             ((\(k, d) -> (key /. k, d)) <$> destructureValue value)@@ -276,6 +287,14 @@     Nothing -> do       return config +-- | Helper function to override the fetching function for a certain key.+--+-- This function creates a 'Dynamic' that when added to the defaults allows+-- overriding the default 'FromConfig' instance.+overrideFetch :: forall a. Typeable a => (Key -> Config -> IO a) -> Dynamic+overrideFetch f =+  toDyn @(OverrideFromConfig a) $ OverrideFromConfig f+ -- | Exception to show that a value couldn't be parsed properly data ConfigParsingError =   ConfigParsingError Key Text TypeRep@@ -343,63 +362,23 @@ missingRequiredKeys keys =   MissingRequiredKey keys (typeRep (Proxy :: Proxy a)) --- | Exception to show that the provided default had the wrong type, this is usually a--- programmer error and a user that configures the library can not do much to fix it.-data TypeMismatchWithDefault =-  TypeMismatchWithDefault Key Dynamic TypeRep-  deriving (Typeable)--instance Eq TypeMismatchWithDefault where-  (==) = (==) `on`-    (\(TypeMismatchWithDefault k dyn t) -> (k, show dyn, t))-instance Exception TypeMismatchWithDefault-instance Show TypeMismatchWithDefault where-  show (TypeMismatchWithDefault key dyn aTypeRep) =-    concat-    [ "Couldn't parse the default from key "-    , show key-    , " since there is a type mismatch. "-    , "Expected type is "-    , show aTypeRep-    , " but the actual type is '"-    , show dyn-    , "'"-    ]---- | Helper function to throw a 'TypeMismatchWithDefault'-throwTypeMismatchWithDefault :: forall a b. (Typeable a) => Key -> Dynamic -> IO b-throwTypeMismatchWithDefault key dynamic =-  throwIO $ typeMismatchWithDefault @a  key dynamic---- | Helper function to create a 'TypeMismatchWithDefault'-typeMismatchWithDefault :: forall a. (Typeable a) => Key -> Dynamic -> TypeMismatchWithDefault-typeMismatchWithDefault key dynamic =-  TypeMismatchWithDefault key dynamic $ typeRep (Proxy :: Proxy a)- -- | Fetch from value from the defaults map of a 'Config' or else throw fetchRequiredFromDefaults :: forall a. (Typeable a) => Key -> Config -> IO a fetchRequiredFromDefaults key config =-  fetchFromDefaults key config >>=-    \case+  case fetchFromDefaults key config of     Nothing -> do       throwMissingRequiredKey @a key     Just a ->       return a  -- | Fetch from value from the defaults map of a 'Config' or else return a 'Nothing'-fetchFromDefaults :: forall a. (Typeable a) => Key -> Config -> IO (Maybe a)+fetchFromDefaults :: forall a. (Typeable a) => Key -> Config -> Maybe a fetchFromDefaults key config =-  case getKeyFromDefaults key config of-    Nothing -> do-      return Nothing-    Just dyn ->-      case fromDynamic @a dyn of-        Nothing ->-          throwTypeMismatchWithDefault @a key dyn-        Just a -> return $ Just a+  getKeyFromDefaults key config+    >>= fromDynamics @a  -- | Same as 'fetchFromConfig' using the root key-fetchFromRootConfig :: forall a. (FromConfig a) => Config -> IO a+fetchFromRootConfig :: forall a. (FromConfig a, Typeable a) => Config -> IO a fetchFromRootConfig =   fetchFromConfig "" @@ -416,32 +395,32 @@  -- | Purely 'Generic's machinery, ignore... class FromConfigG f where-  fetchFromConfigG :: Key -> Config -> IO (f a)+  fromConfigG :: Key -> Config -> IO (f a)  instance FromConfigG inner =>     FromConfigG (D1 metadata inner) where-  fetchFromConfigG key config = do-    M1 <$> fetchFromConfigG key config+  fromConfigG key config = do+    M1 <$> fromConfigG key config  instance (FromConfigWithConNameG inner, Constructor constructor) =>     FromConfigG (C1 constructor inner) where-  fetchFromConfigG key config =-    M1 <$> fetchFromConfigWithConNameG @inner (conName @constructor undefined) key config+  fromConfigG key config =+    M1 <$> fromConfigWithConNameG @inner (conName @constructor undefined) key config  -- | Purely 'Generic's machinery, ignore... class FromConfigWithConNameG f where-  fetchFromConfigWithConNameG :: String -> Key -> Config -> IO (f a)+  fromConfigWithConNameG :: String -> Key -> Config -> IO (f a)  instance (FromConfigWithConNameG left, FromConfigWithConNameG right) =>     FromConfigWithConNameG (left :*: right) where-  fetchFromConfigWithConNameG s key config = do-    leftValue <- fetchFromConfigWithConNameG @left s key config-    rightValue <- fetchFromConfigWithConNameG @right s key config+  fromConfigWithConNameG s key config = do+    leftValue <- fromConfigWithConNameG @left s key config+    rightValue <- fromConfigWithConNameG @right s key config     return (leftValue :*: rightValue)  instance (FromConfigG inner, Selector selector) =>     FromConfigWithConNameG (S1 selector inner) where-  fetchFromConfigWithConNameG s key config =+  fromConfigWithConNameG s key config =     let       applyFirst :: (Char -> Char) -> Text -> Text       applyFirst f t = case Text.uncons t of@@ -454,10 +433,10 @@         case Text.stripPrefix prefix fieldName of           Just stripped -> applyFirst Char.toLower stripped           Nothing -> fieldName-    in M1 <$> fetchFromConfigG @inner (key /. fromText scopedKey) config+    in M1 <$> fromConfigG @inner (key /. fromText scopedKey) config -instance (FromConfig inner) => FromConfigG (Rec0 inner) where-  fetchFromConfigG key config = do+instance (FromConfig inner, Typeable inner) => FromConfigG (Rec0 inner) where+  fromConfigG key config = do     K1 <$> fetchFromConfig @inner key config  -- | Purely 'Generic's machinery, ignore...@@ -487,7 +466,6 @@  instance (IntoDefaultsG inner, Selector selector) =>     IntoDefaultsWithConNameG (S1 selector inner) where-   intoDefaultsWithConNameG s key (M1 inner) =     let       applyFirst :: (Char -> Char) -> Text -> Text
src/Conferer/Key.hs view
@@ -15,6 +15,8 @@   , (/.)   , stripKeyPrefix   , isKeyPrefixOf+  , isValidKeyFragment+  , isKeyCharacter   -- * Introspecting 'Key's   , rawKeyComponents   , unconsKey
src/Conferer/Key/Internal.hs view
@@ -45,7 +45,7 @@ mkKey s =   Key .   filter (/= mempty) .-  fmap (Text.filter Char.isAlphaNum . Text.toLower) .+  fmap (Text.filter isKeyCharacter . Text.toLower) .   Text.split (== '.') .   fromString   $ s@@ -89,3 +89,15 @@ unconsKey :: Key -> Maybe (Text, Key) unconsKey (Key []) = Nothing unconsKey (Key (k:ks)) = Just (k, Key ks)++-- | Check if a 'Text' is a valid fragment of a 'Key', meaning+-- that each character 'isKeyCharacter'+isValidKeyFragment :: Text -> Bool+isValidKeyFragment t =+  (not $ Text.null t) && Text.all isKeyCharacter t++-- | Checks if the given 'Char' is a valid for a 'Key'.+-- Meaning it is a lower case ascii letter or a number.+isKeyCharacter :: Char -> Bool+isKeyCharacter c =+  Char.isAsciiLower c || Char.isDigit c
src/Conferer/Source/PropertiesFile.hs view
@@ -39,12 +39,18 @@ fromConfig :: Key -> SourceCreator fromConfig key config = do   filePath <- getFilePathFromEnv key "properties" config-  fromFilePath filePath+  fromFilePath' filePath +-- | Create a 'SourceCreator' reading the file and using that as a properties file, but+-- if the file doesn't exist do nothing.+fromFilePath :: FilePath -> SourceCreator+fromFilePath filepath _config =+  fromFilePath' filepath+ -- | Create a 'Source' reading the file and using that as a properties file, but -- if the file doesn't exist do nothing.-fromFilePath :: FilePath -> IO Source-fromFilePath filePath = do+fromFilePath' :: FilePath -> IO Source+fromFilePath' filePath = do   fileExists <- doesFileExist filePath   if fileExists     then do
test/Conferer/ConfigSpec.hs view
@@ -7,6 +7,7 @@  import Conferer.Config import Conferer.Source.InMemory+import Conferer.FromConfig.Internal (fromDynamics)  missingKey :: Key -> KeyLookupResult -> Bool  missingKey expectedKey (MissingKey k) = [expectedKey] == k@@ -21,7 +22,7 @@ foundInDefaults :: forall expected. (Eq expected, Typeable expected)   => Key -> expected -> KeyLookupResult -> Bool  foundInDefaults expectedKey expected (FoundInDefaults k d) =  -  Just expected == fromDynamic @expected d && expectedKey == k+  Just expected == fromDynamics @expected d && expectedKey == k foundInDefaults _expctedKey _expected _ =     False @@ -197,3 +198,14 @@                   [ ]             res <- listSubkeys "a" c             res `shouldBe` ["a.k"]++    describe "#addDefaults" $ do+      context "with multiple defaults on the same key" $ do+        it "the last one has more priority" $ do+          let c = emptyConfig+                & addDefaults+                  [ ("some.key", toDyn @Int 1)+                  , ("some.key", toDyn @Int 2)+                  ]+          res <- getKey "some.key" c+          res `shouldSatisfy` foundInDefaults "some.key" (2 :: Int)
test/Conferer/FromConfig/BoolSpec.hs view
@@ -12,7 +12,6 @@   context "Basic fetching" $ do     describe "fetching a Bool from config" $ do       ensureEmptyConfigThrows @Bool-      ensureWrongTypeDefaultThrows @Bool        ensureUsingDefaultReturnsSameValue @Bool True       ensureUsingDefaultReturnsSameValue @Bool False
test/Conferer/FromConfig/Extended.hs view
@@ -11,9 +11,7 @@   , aConfigParserError   , aMissingRequiredKey   , aMissingRequiredKeys-  , aTypeMismatchWithDefaultError   , ensureEmptyConfigThrows-  , ensureWrongTypeDefaultThrows   , ensureUsingDefaultReturnsSameValue   , ensureSingleConfigParsesTheRightThing   , ensureSingleConfigThrowsParserError@@ -38,12 +36,12 @@   , ConfigParsingError(..)   ) import Conferer.FromConfig-import Conferer.Source.InMemory+import qualified Conferer.Source.InMemory as InMemory  configWith :: [(Key, Text)] -> IO Config configWith keyValues =   emptyConfig-  & addSource (fromConfig keyValues)+  & addSource (InMemory.fromConfig keyValues)  anyConfigParserError :: ConfigParsingError -> Bool anyConfigParserError _ = True@@ -60,11 +58,6 @@ aMissingRequiredKeys keys (MissingRequiredKey k t) =   keys == k && typeRep (Proxy :: Proxy t) == t -aTypeMismatchWithDefaultError :: forall a dyn. (Typeable dyn, Typeable a) =>-  Key -> dyn -> TypeMismatchWithDefault -> Bool-aTypeMismatchWithDefaultError key dyn e =-  e == typeMismatchWithDefault @a key (toDyn dyn)- data InvalidThing = InvalidThing deriving (Show, Eq)  ensureEmptyConfigThrows :: forall a. (Typeable a, FromConfig a) => SpecWith ()@@ -75,17 +68,8 @@       fetchFromConfig @a "some.key" config         `shouldThrow` aMissingRequiredKey @a "some.key" -ensureWrongTypeDefaultThrows :: forall a. (Typeable a, FromConfig a) => SpecWith ()-ensureWrongTypeDefaultThrows =-  context "with invalid types in the defaults"  $ do-    it "throws an exception" $ do-      config <- configWith []-      fetchFromConfig @a "some.key"-          (config & addDefault "some.key" InvalidThing)-        `shouldThrow` aTypeMismatchWithDefaultError @a "some.key" InvalidThing- ensureSingleConfigThrowsParserError ::-    forall a. (FromConfig a) =>+    forall a. (FromConfig a, Typeable a) =>     Text -> SpecWith () ensureSingleConfigThrowsParserError keyContent =   context "with invalid types in the defaults"  $ do@@ -106,7 +90,7 @@       fetchedValue `shouldBe` value  ensureSingleConfigParsesTheRightThing ::-    forall a. (Eq a, Show a, FromConfig a) =>+    forall a. (Eq a, Show a, FromConfig a, Typeable a) =>     Text -> a -> SpecWith () ensureSingleConfigParsesTheRightThing keyContent value =   context ("with a config value of '" ++ unpack keyContent ++ "'" ) $ do@@ -116,7 +100,7 @@       fetchedValue `shouldBe` value  ensureSingleConfigThrows ::-    forall a e. (FromConfig a, Exception e) =>+    forall a e. (FromConfig a, Typeable a, Exception e) =>     Text -> (e -> Bool) -> SpecWith () ensureSingleConfigThrows keyContent checkException =   it "gets the right value" $ do@@ -127,6 +111,7 @@ ensureFetchParses ::     forall a.     ( FromConfig a+    , Typeable a     , Eq a     , Show a     )@@ -146,6 +131,7 @@ ensureFetchThrows ::     forall a e.     ( FromConfig a+    , Typeable a     , Exception e     )     => [(Key, Text)]
test/Conferer/FromConfig/FileSpec.hs view
@@ -22,8 +22,6 @@           , "some.key.filename"           ] -      ensureFetchThrows @File [] [("", toDyn False)] $-        aTypeMismatchWithDefaultError @File "some.key" False       context "with whole path in root" $ do         ensureFetchParses @File           [ ("", pack $ "some" </> "file.png")
test/Conferer/FromConfig/ListSpec.hs view
@@ -20,7 +20,6 @@ spec = do   describe "list parsing" $ do     ensureEmptyConfigThrows @[Int]-    ensureWrongTypeDefaultThrows @[Int]     ensureUsingDefaultReturnsSameValue @[Int] [7]      context "with an empty keys it always gets an empty list" $ do@@ -165,8 +164,8 @@             \fromconfig uses listSubkeys" $ do       ensureFetchParses         @[[Int]]-        [ ("1.first", "0")+        [ ("1.0", "0")         ]-        [ ("prototype.second", toDyn @Int 8)+        [ ("prototype.1", toDyn @Int 8)         ]         [[0, 8]]
test/Conferer/FromConfig/NumbersSpec.hs view
@@ -14,7 +14,6 @@   context "Numbers fetching" $ do     describe "fetching an Int from config" $ do       ensureEmptyConfigThrows @Int-      ensureWrongTypeDefaultThrows @Int       ensureUsingDefaultReturnsSameValue @Int 7       ensureSingleConfigParsesTheRightThing @Int "7" 7       ensureSingleConfigParsesTheRightThing @Int "-7" (-7)@@ -22,7 +21,6 @@      describe "fetching a Float from config" $ do       ensureEmptyConfigThrows @Float-      ensureWrongTypeDefaultThrows @Float       ensureUsingDefaultReturnsSameValue @Float 7.5       ensureSingleConfigParsesTheRightThing @Float "9.5" 9.5       ensureSingleConfigParsesTheRightThing @Float "-9.5" (-9.5)
test/Conferer/FromConfig/StringLikeSpec.hs view
@@ -12,21 +12,17 @@ spec = do     describe "fetching a String from config" $ do       ensureEmptyConfigThrows @String-      ensureWrongTypeDefaultThrows @String       ensureUsingDefaultReturnsSameValue @String "aaa"       ensureSingleConfigParsesTheRightThing @String "thing" "thing"     describe "fetching text from config" $ do       ensureEmptyConfigThrows @Text-      ensureWrongTypeDefaultThrows @Text       ensureUsingDefaultReturnsSameValue @Text "aaa"       ensureSingleConfigParsesTheRightThing @Text "thing" "thing"     describe "fetching bytestring from config" $ do       ensureEmptyConfigThrows @BS.ByteString-      ensureWrongTypeDefaultThrows @BS.ByteString       ensureUsingDefaultReturnsSameValue @BS.ByteString "aaa"       ensureSingleConfigParsesTheRightThing @BS.ByteString "thing" "thing"     describe "fetching lazy bytestring from config" $ do       ensureEmptyConfigThrows @LBS.ByteString-      ensureWrongTypeDefaultThrows @LBS.ByteString       ensureUsingDefaultReturnsSameValue @LBS.ByteString "aaa"       ensureSingleConfigParsesTheRightThing @LBS.ByteString "thing" "thing"
test/Conferer/FromConfigSpec.hs view
@@ -1,8 +1,14 @@+{-# LANGUAGE TypeApplications #-} module Conferer.FromConfigSpec (spec) where  import Test.Hspec+import Conferer.FromConfig+import Conferer.FromConfig.Extended  spec :: Spec spec = do-  context "Basic fetching" $ do-    return ()+  describe "Override FromConfig" $ do+    ensureFetchParses @Int+      []+      [ ("", overrideFetch @Int $ \_k _c -> pure 42) ]+      42
test/Conferer/GenericsSpec.hs view
@@ -5,10 +5,9 @@ import Test.Hspec  import Conferer.FromConfig-import Conferer.Config-import Conferer.Source.InMemory  import GHC.Generics+import Conferer.FromConfig.Extended  data Thing = Thing   { thingA :: Int@@ -35,88 +34,65 @@   describe "Generics" $ do     context "with a simple record" $ do       context "without a default but with all of the keys defined" $ do-        it "returns the default" $ do-          c <- emptyConfig-                & addSource (fromConfig-                  [ ("somekey.a", "0")-                  , ("somekey.b", "0")-                  ])--          res <- fetchFromConfig @Thing "somekey" c-          res `shouldBe` Thing { thingA = 0, thingB = 0 }+        ensureFetchParses+          [ ("a", "0")+          , ("b", "0")+          ]+          []+          Thing { thingA = 0, thingB = 0 }       context "when no keys are set" $ do-        it "returns the default" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Thing)-                & addSource (fromConfig [ ])--          res <- fetchFromConfig @Thing "somekey" c-          res `shouldBe` Thing { thingA = 0, thingB = 0 }+        ensureFetchParses+          []+          [ ("", toDyn $ configDef @Thing)+          ]+          Thing { thingA = 0, thingB = 0 }       context "when all keys are set" $ do-        it "return the keys set" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Thing)-                & addSource (fromConfig-                  [ ("somekey.a", "1")-                  , ("somekey.b", "2")-                  ])--          res <- fetchFromConfig @Thing "somekey" c-          res `shouldBe` Thing { thingA = 1, thingB = 2 }+        ensureFetchParses+          [ ("a", "1")+          , ("b", "2")+          ]+          [ ("", toDyn $ configDef @Thing)+          ]+          Thing { thingA = 1, thingB = 2 }        context "when some keys are set" $ do-        it "uses the default and returns the keys set" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Thing)-                & addSource (fromConfig-                  [ ("somekey.b", "2")-                  ])--          res <- fetchFromConfig @Thing "somekey" c-          res `shouldBe` Thing { thingA = 0, thingB = 2 }+        ensureFetchParses+          [ ("b", "2")+          ]+          [ ("", toDyn $ configDef @Thing)+          ]+          Thing { thingA = 0, thingB = 2 }      context "with a nested record" $ do       context "when none of the keys are set" $ do-        it "returns the default of both records" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Bigger)-                & addSource (fromConfig-                  [ ])--          res <- fetchFromConfig @Bigger "somekey" c-          res `shouldBe` Bigger { biggerThing = Thing { thingA = 27, thingB = 0 }, biggerB = 1}+        ensureFetchParses+          [ ]+          [ ("", toDyn @Bigger configDef )+          ]+          Bigger { biggerThing = Thing { thingA = 27, thingB = 0 }, biggerB = 1}        context "when some keys of the top record are set" $ do-        it "returns the default for the inner record" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Bigger)-                & addSource (fromConfig-                  [ ("somekey.b", "30")-                  ])--          res <- fetchFromConfig @Bigger "somekey" c-          res `shouldBe` Bigger { biggerThing = Thing { thingA = 27, thingB = 0 }, biggerB = 30}+        ensureFetchParses+          [ ("b", "30")+          ]+          [ ("", toDyn @Bigger configDef)+          ]+          Bigger { biggerThing = Thing { thingA = 27, thingB = 0 }, biggerB = 30}        context "when some keys of the inner record are set" $ do-        it "returns the inner record updated" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Bigger)-                & addSource (fromConfig-                  [ ("somekey.thing.a", "30")-                  ])--          res <- fetchFromConfig @Bigger "somekey" c-          res `shouldBe` Bigger { biggerThing = Thing { thingA = 30, thingB = 0 }, biggerB = 1}+        ensureFetchParses+          [ ("thing.a", "30")+          ]+          [ ("", toDyn @Bigger configDef)+          ]+          Bigger { biggerThing = Thing { thingA = 30, thingB = 0 }, biggerB = 1}        context "when every key is set" $ do-        it "returns everything with the right values" $ do-          c <- emptyConfig-                & addDefault "somekey" (configDef @Bigger)-                & addSource (fromConfig-                  [ ("somekey.thing.a", "10")-                  , ("somekey.thing.b", "20")-                  , ("somekey.b", "30")-                  ])--          res <- fetchFromConfig @Bigger "somekey" c-          res `shouldBe` Bigger { biggerThing = Thing { thingA = 10, thingB = 20 }, biggerB = 30}+        ensureFetchParses+          [ ("thing.a", "10")+          , ("thing.b", "20")+          , ("b", "30")+          ]+          [ ("", toDyn @Bigger configDef)+          ]+          Bigger { biggerThing = Thing { thingA = 10, thingB = 20 }, biggerB = 30}
test/Conferer/KeySpec.hs view
@@ -7,7 +7,7 @@  spec :: Spec spec = do-  describe "keys" $ do+  describe "#fromString" $ do     it "parsing keys does the right thing" $ do       "some.key" `shouldBe` Key ["some", "key"]     it "an empty string is the empty list" $ do@@ -18,3 +18,22 @@       "some.key" `shouldBe` ("SoME.KeY" :: Key)     it "'s true representation is case insensitive" $ do       "somE.Key" `shouldBe` Key ["some", "key"]+  describe "#isValidKeyFragment" $ do+    context "with a all leters" $ do+      it "is valid" $+        isValidKeyFragment "fragment" `shouldBe` True+    context "with only numbers" $ do+      it "is valid" $+        isValidKeyFragment "000" `shouldBe` True+    context "with both numbers and letters" $ do+      it "is valid" $+        isValidKeyFragment "000" `shouldBe` True+    context "with a dot" $ do+      it "is not valid" $ do+        isValidKeyFragment "some.fragment" `shouldBe` False+    context "with an uppercase letter" $ do+      it "is not valid" $ do+        isValidKeyFragment "FRAGMENT" `shouldBe` False+    context "with an empty string" $ do+      it "is not valid" $ do+        isValidKeyFragment "" `shouldBe` False
test/ConfererSpec.hs view
@@ -7,7 +7,7 @@ import Test.Hspec  import Data.Text (Text)-import Conferer.FromConfig ( FromConfig(fetchFromConfig) )+import Conferer.FromConfig (fetchFromConfig) import Conferer.Source.InMemory (fromConfig) import Data.Dynamic (toDyn) import Conferer.Config (Config)