config-manager 0.1.0.0 → 0.2.0.0
raw patch · 8 files changed
+165/−11 lines, 8 filesdep +timePVP ok
version bump matches the API change (PVP)
Dependencies added: time
API changes (from Hackage documentation)
+ Data.ConfigManager.Types: class Configured a
+ Data.ConfigManager.Types: convert :: Configured a => Value -> Maybe a
- Data.ConfigManager: lookup :: Read a => Name -> Config -> Either Text a
+ Data.ConfigManager: lookup :: Configured a => Name -> Config -> Either Text a
Files
- Data/ConfigManager.hs +42/−4
- Data/ConfigManager/Instances.hs +24/−0
- Data/ConfigManager/Parser/Duration.hs +36/−0
- Data/ConfigManager/Reader.hs +2/−2
- Data/ConfigManager/Types.hs +2/−0
- Data/ConfigManager/Types/Internal.hs +8/−1
- config-manager.cabal +10/−3
- tests/Test.hs +41/−1
Data/ConfigManager.hs view
@@ -22,6 +22,9 @@ -- ** Comments -- $comments + -- ** Example+ -- $example+ -- * Configuration loading readConfig @@ -31,7 +34,6 @@ ) where import Prelude hiding (lookup)-import Text.Read (readMaybe) import Data.Text (Text) import qualified Data.Text as T@@ -39,6 +41,7 @@ import qualified Data.ConfigManager.Reader as R import Data.ConfigManager.Types+import Data.ConfigManager.Instances () -- | Load a 'Config' from a given 'FilePath'. @@ -47,13 +50,13 @@ -- | Lookup for the value associated to a name. -lookup :: Read a => Name -> Config -> Either Text a+lookup :: Configured a => Name -> Config -> Either Text a lookup name config = case M.lookup name (hashMap config) of Nothing -> Left . T.concat $ ["Value not found for Key ", name] Just value ->- case readMaybe . T.unpack $ value of+ case convert value of Nothing -> Left . T.concat $ ["Reading error for key ", name] Just result -> Right result @@ -80,8 +83,11 @@ -- > a_double = 4.0 -- > thatIsABoolean = True -- > a_double = 5.0+-- > diffTime = 1 day+-- > otherDiffTime = 3 hours ----- If two or more bindings have the same name, only the last one is kept.+-- * If two or more bindings have the same name, only the last one is kept.+-- * Accepted duration values are seconds, minutes, hours, days and weeks. -- $import --@@ -96,3 +102,35 @@ -- -- > # Comment -- > x = 8 # Another comment++-- $example+--+-- From application.conf:+--+-- > port = 3000+-- > mailFrom = "no-reply@mail.com"+-- > currency = "$"+-- > expiration = 30 minutes+--+-- Read the configuration:+--+-- > import qualified Data.ConfigManager as Conf+-- > import Data.Time.Clock (DiffTime)+-- >+-- > data Conf = Conf+-- > { port :: Int+-- > , mailFrom :: String+-- > , currency :: String+-- > , expiration :: DiffTime+-- > } deriving (Read, Eq, Show)+-- >+-- > getConfig :: IO (Either Text Conf)+-- > getConfig =+-- > (flip fmap) (Conf.readConfig "application.conf") (\configOrError -> do+-- > conf <- configOrError+-- > Conf <$>+-- > Conf.lookup "port" conf <*>+-- > Conf.lookup "mailFrom" conf <*>+-- > Conf.lookup "currency" conf <*>+-- > Conf.lookup "expiration" conf+-- > )
+ Data/ConfigManager/Instances.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE IncoherentInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.ConfigManager.Instances+ () where++import qualified Data.Text as T+import Data.Time.Clock (DiffTime, NominalDiffTime)++import Text.Read (readMaybe)++import Data.ConfigManager.Types.Internal+import Data.ConfigManager.Parser.Duration (parseDuration)++instance Configured DiffTime where+ convert value = parseDuration value++instance Configured NominalDiffTime where+ convert value = realToFrac <$> parseDuration value++instance Read a => Configured a where+ convert = readMaybe . T.unpack
+ Data/ConfigManager/Parser/Duration.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.ConfigManager.Parser.Duration+ ( parseDuration+ ) where++import Control.Applicative ((<|>))++import Data.Text (Text)+import qualified Data.Text as T+import Data.Time.Clock (DiffTime)+import qualified Data.Time.Clock as Time++import Text.Read (readMaybe)++parseDuration :: Text -> Maybe DiffTime+parseDuration input =+ case T.splitOn " " input of+ [count, unit] -> do+ n <- readMaybe . T.unpack $ count+ let matchDuration singularUnit pluralUnit seconds =+ if ((n == 0 || n == 1) && unit == singularUnit) || (n > 1 && unit == pluralUnit)+ then Just . Time.secondsToDiffTime $ n * seconds+ else Nothing+ (matchDuration "second" "seconds" second)+ <|> (matchDuration "minute" "minutes" minute)+ <|> (matchDuration "hour" "hours" hour)+ <|> (matchDuration "day" "days" day)+ <|> (matchDuration "week" "weeks" week)+ _ -> Nothing+ where+ second = 1+ minute = 60 * second+ hour = 60 * minute+ day = 24 * hour+ week = 7 * day
Data/ConfigManager/Reader.hs view
@@ -43,8 +43,8 @@ Binding name value -> return . Right . Config $ M.insert name value (hashMap config) Import requirement path -> do- eitherConfig <- readConfig requirement (fileDir </> path)- case eitherConfig of+ configOrError <- readConfig requirement (fileDir </> path)+ case configOrError of Left errorMessage -> return . Left $ errorMessage Right importedConfig ->
Data/ConfigManager/Types.hs view
@@ -12,6 +12,8 @@ , Name , Value , Requirement(..)+ , Configured+ , convert ) where import Data.ConfigManager.Types.Internal
Data/ConfigManager/Types/Internal.hs view
@@ -4,10 +4,11 @@ , Name , Value , Requirement(..)+ , Configured+ , convert ) where import Data.Text (Text)- import Data.HashMap.Strict -- | Configuration data.@@ -37,3 +38,9 @@ Required | Optional deriving (Eq, Read, Show)++-- | This class represents types that can be converted /from/ a value /to/ a+-- destination type++class Configured a where+ convert :: Value -> Maybe a
config-manager.cabal view
@@ -1,5 +1,5 @@ name: config-manager-version: 0.1.0.0+version: 0.2.0.0 synopsis: Configuration management description: A configuration management library which supports:@@ -9,6 +9,9 @@ * required or optional imports, . * comments.+ .+ For details of the configuration file format, see+ <http://hackage.haskell.org/packages/archive/config-manager/latest/doc/html/Data-ConfigManager.html>. homepage: https://gitlab.com/guyonvarch/config-manager bug-reports: https://gitlab.com/guyonvarch/config-manager/issues license: GPL-3@@ -26,13 +29,16 @@ Data.ConfigManager.Types other-modules: Data.ConfigManager.Reader Data.ConfigManager.Parser+ Data.ConfigManager.Instances Data.ConfigManager.Types.Internal+ Data.ConfigManager.Parser.Duration ghc-options: -Wall build-depends: base < 5, text, unordered-containers, parsec,- filepath+ filepath,+ time default-language: Haskell2010 source-repository head@@ -52,5 +58,6 @@ test-framework-hunit, temporary, directory,- unordered-containers+ unordered-containers,+ time default-language: Haskell2010
tests/Test.hs view
@@ -18,6 +18,8 @@ import Data.ConfigManager import Data.ConfigManager.Types (Config(..)) import qualified Data.Text as T+import Data.Time.Clock (DiffTime)+import qualified Data.Time.Clock as Time import Helper (forceGetConfig, getConfig, eitherToMaybe) @@ -32,6 +34,7 @@ , testCase "value" valueAssertion , testCase "skip" skipAssertion , testCase "import" importAssertion+ , testCase "duration" durationAssertion ] bindingAssertion :: Assertion@@ -41,7 +44,7 @@ oneBinding <- forceGetConfig "x = \"foo\"" assertEqual "one binding present" (Right "foo") (lookup "x" oneBinding)- assertBool "one binding missing" (isLeft $ (lookup "y" oneBinding :: Either Text Int))+ assertBool "one binding missing" (isLeft (lookup "y" oneBinding :: Either Text Int)) assertEqual "one binding count" 1 (M.size . hashMap $ oneBinding) multipleBindings <- forceGetConfig $ T.unlines@@ -133,3 +136,40 @@ , "x = 4" ] assertEqual "missing optional config" (Right 4) (lookup "x" missingOptionalConfig)++durationAssertion :: Assertion+durationAssertion = do+ config <- forceGetConfig $ T.unlines+ [ "a = 1 second"+ , "b = 5 seconds"+ , "c = 1 minute"+ , "d = 10 minutes"+ , "e = 1 hour"+ , "f = 7 hours"+ , "g = 1 day"+ , "h = 2 days"+ , "i = 1 week"+ , "j = 9 weeks"+ , ""+ , "k = 1 minutes"+ , "l = 20 weeks"+ ]++ let second = 1+ let minute = 60 * second+ let hour = 60 * minute+ let day = 24 * hour+ let week = 7 * day++ assertEqual "a" (Right (Time.secondsToDiffTime $ 1 * second)) (lookup "a" config)+ assertEqual "b" (Right (Time.secondsToDiffTime $ 5 * second)) (lookup "b" config)+ assertEqual "c" (Right (Time.secondsToDiffTime $ 1 * minute)) (lookup "c" config)+ assertEqual "d" (Right (Time.secondsToDiffTime $ 10 * minute)) (lookup "d" config)+ assertEqual "e" (Right (Time.secondsToDiffTime $ 1 * hour)) (lookup "e" config)+ assertEqual "f" (Right (Time.secondsToDiffTime $ 7 * hour)) (lookup "f" config)+ assertEqual "g" (Right (Time.secondsToDiffTime $ 1 * day)) (lookup "g" config)+ assertEqual "h" (Right (Time.secondsToDiffTime $ 2 * day)) (lookup "h" config)+ assertEqual "i" (Right (Time.secondsToDiffTime $ 1 * week)) (lookup "i" config)+ assertEqual "j" (Right (Time.secondsToDiffTime $ 9 * week)) (lookup "j" config)+ assertBool "k" (isLeft (lookup "k" config :: Either Text DiffTime))+ assertBool "l" (isLeft (lookup "l" config :: Either Text DiffTime))