salak 0.1.6 → 0.1.7
raw patch · 10 files changed
+230/−173 lines, 10 filesdep +mtldep +transformers
Dependencies added: mtl, transformers
Files
- README.md +1/−1
- salak.cabal +5/−1
- src/Data/Salak.hs +43/−36
- src/Data/Salak/Aeson.hs +9/−17
- src/Data/Salak/CommandLine.hs +13/−1
- src/Data/Salak/Environment.hs +9/−1
- src/Data/Salak/Operation.hs +23/−12
- src/Data/Salak/Types.hs +76/−63
- src/Data/Salak/Yaml.hs +17/−0
- test/Spec.hs +34/−41
README.md view
@@ -1,6 +1,6 @@ # salak -[](https://hackage.haskell.org/package/salak)+[](https://hackage.haskell.org/package/salak) [](https://travis-ci.org/leptonyu/salak)
salak.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: salak-version: 0.1.6+version: 0.1.7 license: BSD3 license-file: LICENSE copyright: (c) 2018 Daniel YU@@ -36,8 +36,10 @@ base >=4.11 && <5, directory >=1.3.3.0 && <1.4, filepath >=1.4.2.1 && <1.5,+ mtl >=2.2.2 && <2.3, scientific >=0.3.6.2 && <0.4, text >=1.2.3.1 && <1.3,+ transformers >=0.5.5.0 && <0.6, unordered-containers >=0.2.9.0 && <0.3, vector >=0.12.0.2 && <0.13, yaml >=0.11.0.0 && <0.12@@ -68,8 +70,10 @@ directory >=1.3.3.0 && <1.4, filepath >=1.4.2.1 && <1.5, hspec ==2.*,+ mtl >=2.2.2 && <2.3, scientific >=0.3.6.2 && <0.4, text >=1.2.3.1 && <1.3,+ transformers >=0.5.5.0 && <0.6, unordered-containers >=0.2.9.0 && <0.3, vector >=0.12.0.2 && <0.13, yaml >=0.11.0.0 && <0.12
src/Data/Salak.hs view
@@ -15,34 +15,40 @@ -- $use -- * Properties Loader- defaultProperties- , ParseCommandLine+ LoadProperties+ , runLoad+ , askProperties+ , setValue+ , loadCommandLine+ , loadEnvironment+ , loadJSON+ , loadYaml+ , loadYamlIfExists+ -- ** Predefined Loaders+ , defaultProperties , defaultProperties' , defaultPropertiesWithFile , defaultPropertiesWithFile' , empty -- * Lookup Properties , lookup- , lookup' , toKeys -- * Types , Property(..) , Properties(..) , Key , FromProperties(..)- , Return(..)+ , Return -- * Properties Loader Helper , insert- , makePropertiesFromEnvironment , defaultParseCommandLine- , makePropertiesFromCommandLine- , makePropertiesFromJson- , makePropertiesFromYaml+ , ParseCommandLine , FileName -- * Operations , module Data.Salak.Operation ) where +import Control.Monad.Trans.Class (lift) import Data.Maybe import Data.Salak.Aeson import Data.Salak.CommandLine@@ -50,10 +56,10 @@ import Data.Salak.Operation import Data.Salak.Types import Data.Salak.Yaml-import Data.Text (Text, unpack)-import Prelude hiding (lookup)+import Data.Text (pack)+import Prelude hiding (lookup) import System.Directory-import System.FilePath ((</>))+import System.FilePath ((</>)) -- | Initialize default properties from `CommandLine` and `Environment`. -- `CommandLine` use default parser.@@ -62,12 +68,12 @@ -- | Initialize default properties from `CommandLine` and `Environment`. defaultProperties' :: ParseCommandLine -> IO Properties-defaultProperties' dpc- = makePropertiesFromCommandLine dpc empty- >>= makePropertiesFromEnvironment+defaultProperties' dpc = runLoad $ do+ loadCommandLine dpc+ loadEnvironment -- | Yaml file name.-type FileName = Text+type FileName = String -- | Initialize default properties from `CommandLine`, `Environment` and `Yaml` files. -- All these configuration sources has orders, from highest order to lowest order:@@ -96,21 +102,20 @@ :: FileName -- ^ specify default config file name, can reset by config "salak.config.name" from `CommandLine` or `Environment`. -> ParseCommandLine -- ^ parser for command line -> IO Properties-defaultPropertiesWithFile' name dpc = do- p <- defaultProperties' dpc- let n = fromMaybe name $ lookup "salak.config.name" p- p' = insert (toKeys "salak.config.name") (PStr n) p- c <- getCurrentDirectory- h <- getHomeDirectory- foldl (go n) (return p') [(lookup "salak.config.dir" p, False), (Just c, True), (Just h, True)]- where- go _ p (Nothing, _) = p- go n p (Just d, ok) = let f = d </> unpack n in do- p' <- p- b <- doesFileExist f- if b- then makePropertiesFromYaml f p'- else if ok then return p' else error $ "File " ++ f ++ " not found"+defaultPropertiesWithFile' name dpc = runLoad $ do+ loadCommandLine dpc+ loadEnvironment+ p <- askProperties+ let n = fromMaybe name $ p .>> "salak.config.name"+ f = p .>> "salak.config.dir"+ setValue "salak.config.name" (PStr $ pack n)+ case f of+ Just y -> loadYaml (y </> n)+ _ -> return ()+ c <- lift getCurrentDirectory+ loadYamlIfExists (Just $ c </> n)+ h <- lift getHomeDirectory+ loadYamlIfExists (Just $ h </> n) -- $use --@@ -133,14 +138,16 @@ -- > , ext :: Int -- > } deriving (Eq, Show) -- >--- > instance FromJSON Config where--- > parseJSON = withObject "Config" $ \v -> Config--- > <$> v .: "name"--- > <*> v .:? "dir"--- > <*> (fromMaybe 1 <$> v .:? "ext")+-- > instance FromProperties Config where+-- > fromProperties v = Config+-- > <$> v .?> "name"+-- > <*> v .?> "dir"+-- > <*> v .?> "ext" .?= 1 -- -- > main = do -- > p <- defaultPropertiesWithFile "salak.yml"--- > let Just config = lookup "salak.config" p :: Maybe Config+-- > let config = p .>> "salak.config" :: Config+-- > enabled = p .?> "salak.enabled" .|= True -- > print config+-- > print enabled
src/Data/Salak/Aeson.hs view
@@ -4,10 +4,12 @@ module Data.Salak.Aeson where +import Control.Monad.IO.Class+import Control.Monad.State import Data.Aeson-import qualified Data.HashMap.Strict as M+import qualified Data.HashMap.Strict as M import Data.Salak.Types-import Data.Vector (fromList, toList)+import Data.Vector (toList) -- | Load `Properties` from JSON `Value` makePropertiesFromJson :: Value -> Properties -> Properties@@ -30,18 +32,8 @@ g2 (a:_) = [a] g3 (as,bs) (a,b) = (as++a,bs++b) -instance FromProperties Value where- fromProperties (Properties [] []) = Empty- fromProperties (Properties [PBool p] []) = OK $ Bool p- fromProperties (Properties [PNum p] []) = OK $ Number p- fromProperties (Properties [PStr p] []) = OK $ String p- fromProperties (Properties ps []) = Array . fromList <$> traverse (fromProperties.singleton) ps- fromProperties (Properties _ [m]) = OK $ Object $ M.map (fromReturn Null . fromProperties) m- fromProperties (Properties _ ms) = Array . fromList <$> traverse (fromProperties.singletonMap) ms--instance {-# OVERLAPPABLE #-} FromJSON a => FromProperties a where- fromProperties a = do- v :: Value <- fromProperties a- case fromJSON v of- Success r -> OK r- Error e -> Fail e+-- | Load Properties from JSON Value+--+-- @since 0.2.2+loadJSON :: MonadIO m => Value -> LoadProperties m ()+loadJSON = modify . makePropertiesFromJson
src/Data/Salak/CommandLine.hs view
@@ -1,8 +1,10 @@ module Data.Salak.CommandLine where +import Control.Monad.IO.Class+import Control.Monad.State import Data.Maybe import Data.Salak.Types-import Data.Text (pack)+import Data.Text (pack) import System.Environment -- | CommandLine parser. Parse command line into property key values.@@ -33,3 +35,13 @@ makePropertiesFromCommandLine' args parser p = do v <- parser args return $ makeProperties (fmap (\(a,b) -> (pack a,b)) v) p+++-- | Load Properties from CommandLine+--+-- @since 0.2.2+loadCommandLine :: MonadIO m => ParseCommandLine -> LoadProperties m ()+loadCommandLine pcl = do+ p <- get+ q <- liftIO $ makePropertiesFromCommandLine pcl p+ put q
src/Data/Salak/Environment.hs view
@@ -1,8 +1,10 @@ module Data.Salak.Environment where +import Control.Monad.IO.Class+import Control.Monad.State import Data.Char import Data.Salak.Types-import Data.Text (pack)+import Data.Text (pack) import System.Environment -- | Load `Properties` from 'Environment'@@ -16,3 +18,9 @@ go (k,v) = (pack $ fmap g2 k,PStr $ pack v) g2 '_' = '.' g2 a = toLower a++-- | Load Properties from CommandLine+--+-- @since 0.2.2+loadEnvironment :: MonadIO m => LoadProperties m ()+loadEnvironment = get >>= liftIO . makePropertiesFromEnvironment >>= put
src/Data/Salak/Operation.hs view
@@ -3,26 +3,37 @@ import Data.Salak.Types import Data.Text (Text, unpack) +-- | Find `Properties` by key and convert to specific Haskell value.+--+-- @since 0.2.1 infixl 5 .?> (.?>) :: FromProperties a => Properties -> Text -> Return a-(.?>) = flip lookup'+(.?>) = flip Data.Salak.Types.lookup +-- | Get property or use default value if not found, but will throw+-- exception if parse failed.+--+-- @since 0.2.1 infixl 5 .|= (.|=) :: Return a -> a -> a-(.|=) (OK a) _ = a-(.|=) (Fail e) _ = error e-(.|=) _ d = d+(.|=) (Right a) _ = a+(.|=) (Left (Fail e)) _ = error e+(.|=) _ d = d +-- | Use default value if Key not found+--+-- @since 0.2.1 infixl 5 .?= (.?=) :: Return a -> a -> Return a-(.?=) a b = OK (a .|= b)+(.?=) a b = Right (a .|= b) +-- | Find `Properties` by key and convert to specific Haskell value.+-- Throw error if property not found or parse failed+--+-- @since 0.2.1 infixl 5 .>> (.>>) :: FromProperties a => Properties -> Text -> a-(.>>) p k = case p .?> k of- OK v -> v- Empty -> case fromProperties empty of- (OK a) -> a- (Fail e) -> error e- _ -> error $ "Config " <> unpack k <> " not found"- Fail e -> error e+(.>>) p key = case p .?> key of+ Right v -> v+ Left (EmptyKey k) -> error $ "property " <> unpack k <> " not set"+ Left (Fail e) -> error e
src/Data/Salak/Types.hs view
@@ -6,6 +6,7 @@ module Data.Salak.Types where import Control.Monad ((>=>))+import Control.Monad.State import Data.Char import qualified Data.HashMap.Strict as M import Data.Int@@ -15,7 +16,8 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Word-import Text.Read+import System.Directory+import Text.Read (readMaybe) -- | Property key type Key = Text@@ -65,7 +67,6 @@ empty :: Properties empty = Properties [] [] - singleton :: Property -> Properties singleton p = Properties [p] [] @@ -92,25 +93,24 @@ insertMap as p = M.alter (Just . insert as p . fromMaybe empty) -- | Find `Properties` by key and convert to specific Haskell value.--- Return `Nothing` means not found, and throw `ErrorCall` means convert failed.-lookup :: FromProperties a => Text -> Properties -> Maybe a-lookup k = from . lookup' k- where- from :: Return a -> Maybe a- from (OK a) = Just a- from Empty = Nothing- from (Fail e) = error e---- | Find `Properties` by key and convert to specific Haskell value.-lookup' :: FromProperties a => Text -> Properties -> Return a-lookup' = go . toKeys+lookup :: FromProperties a => Text -> Properties -> Return a+lookup k = go (toKeys k) where+ {-# INLINE go #-} go [] p = fromProperties p- go (a:as) (Properties _ [m]) = case M.lookup a m of- Just n -> go as n- _ -> Empty- go _ _ = Empty+ go (a:as) (Properties _ ms) = case go as $ fromMaybe empty $ select a ms of+ Left (EmptyKey ke) -> Left $ EmptyKey $ joinKey a ke+ v -> v+ select _ [] = Nothing+ select a [m] = M.lookup a m+ select _ _ = Nothing ++joinKey :: Text -> Text -> Text+joinKey "" k = k+joinKey k "" = k+joinKey a b = a <> "." <> b+ -- | Insert batch properties to `Properties` makeProperties :: [(Text, Property)] -> Properties -> Properties makeProperties = flip (foldl go)@@ -118,52 +118,33 @@ go m (k,v) = insert (toKeys k) v m -- | Return of `FromProperties`-data Return a- = Empty- | OK a- | Fail String- deriving Show--instance Functor Return where- fmap f (OK a) = OK (f a)- fmap _ Empty = Empty- fmap _ (Fail b) = Fail b--instance Applicative Return where- pure = OK- (OK f) <*> (OK a) = OK (f a)- (Fail x) <*> (Fail y) = Fail $ x ++ ";" ++ y- (Fail x) <*> _ = Fail x- _ <*> (Fail y) = Fail y- _ <*> _ = Empty--instance Monad Return where- (OK a) >>= f = f a- Empty >>= _ = Empty- (Fail b) >>= _ = Fail b--fromReturn :: b -> Return b -> b-fromReturn _ (OK a) = a-fromReturn a _ = a+type Return = Either ErrResult+data ErrResult = EmptyKey Text | Fail String deriving Show -- | Convert `Properties` to Haskell value. class FromProperties a where fromProperties :: Properties -> Return a instance FromProperties Property where- fromProperties (Properties [a] _) = OK a- fromProperties (Properties [] _) = Empty- fromProperties _ = Fail "property has multi values"+ fromProperties (Properties [a] _) = Right a+ fromProperties (Properties [] _) = Left $ EmptyKey ""+ fromProperties _ = Left $ Fail "property has multi values" instance {-# OVERLAPPABLE #-} FromProperties a => FromProperties [a] where- fromProperties (Properties ps ms) = traverse fromProperties $ fmap singleton ps ++ fmap singletonMap ms+ fromProperties (Properties ps ms) = traverse fromProperties $ fmap singleton ps <> fmap singletonMap ms +instance {-# OVERLAPPABLE #-} FromProperties a => FromProperties (Maybe a) where+ fromProperties p = case fromProperties p of+ Right a -> Right (Just a)+ Left (EmptyKey _) -> Right Nothing+ Left r -> Left r+ instance FromProperties Scientific where fromProperties = fromProperties >=> go where- go (PNum a) = OK a+ go (PNum a) = Right a go (PStr a) = to readMaybe $ T.unpack a- go _ = Fail "bool cannot convert to number"+ go _ = Left $ Fail "bool cannot convert to number" instance FromProperties String where fromProperties a = T.unpack <$> fromProperties a@@ -171,8 +152,8 @@ instance FromProperties Text where fromProperties = fromProperties >=> go where- go (PStr a) = OK a- go a = OK $ T.pack $ show a+ go (PStr a) = Right a+ go a = Right $ T.pack $ show a instance FromProperties Float where fromProperties a = toRealFloat <$> fromProperties a@@ -215,25 +196,57 @@ to :: (b -> Maybe a) -> b -> Return a to v b = case v b of- Just a -> OK a- Nothing -> Fail "number convert failed"+ Just a -> Right a+ Nothing -> Left $ Fail "number convert failed" instance FromProperties Bool where fromProperties = fromProperties >=> go where- go (PBool a) = OK a+ go (PBool a) = Right a go (PStr a) = g2 $ T.toLower a- go _ = Fail "number cannot convert to bool"+ go _ = Left $ Fail "number cannot convert to bool" g2 :: Text -> Return Bool- g2 "true" = OK True- g2 "false" = OK False- g2 _ = Fail "string value cannot convert to bool"+ g2 "1" = Right True+ g2 "true" = Right True+ g2 "0" = Right False+ g2 "false" = Right False+ g2 _ = Left $ Fail "string value cannot convert to bool" instance FromProperties Char where fromProperties = fromProperties >=> go where go (PStr s)- | T.null s = Empty- | otherwise = OK $ T.head s- go _ = Fail "cannot convert to char"+ | T.null s = Left $ EmptyKey ""+ | otherwise = Right $ T.head s+ go _ = Left $ Fail "cannot convert to char" +-- | Monad to Load Properties+--+-- @since 0.2.2+type LoadProperties = StateT Properties++-- | Load Properties+--+-- @since 0.2.2+runLoad :: Monad m => LoadProperties m a -> m Properties+runLoad a = snd <$> runStateT a empty++-- | Get current Properties+--+-- @since 0.2.2+askProperties :: Monad m => LoadProperties m Properties+askProperties = get++-- | Set value to current properties+--+-- @since 0.2.2+setValue :: Monad m => Text -> Property -> LoadProperties m ()+setValue k v = do+ p <- askProperties+ put (insert (toKeys k) v p)++loadIfExists :: MonadIO m => Maybe FilePath -> (FilePath -> LoadProperties m ()) -> LoadProperties m ()+loadIfExists (Just f) a = do+ e <- liftIO $ doesFileExist f+ when e (a f)+loadIfExists _ _ = return ()
src/Data/Salak/Yaml.hs view
@@ -1,5 +1,7 @@ module Data.Salak.Yaml where +import Control.Monad.IO.Class+import Control.Monad.State import Data.Salak.Aeson import Data.Salak.Types import Data.Yaml@@ -9,3 +11,18 @@ makePropertiesFromYaml file p = do v <- decodeFileThrow file return $ makePropertiesFromJson v p++-- | Load Properties from Yaml+--+-- @since 0.2.2+loadYaml :: MonadIO m => FilePath -> LoadProperties m ()+loadYaml file = do+ p <- get+ q <- liftIO $ makePropertiesFromYaml file p+ put q++-- | Load Properties from Yaml if exists+--+-- @since 0.2.2+loadYamlIfExists :: MonadIO m => Maybe FilePath -> LoadProperties m ()+loadYamlIfExists mf = loadIfExists mf loadYaml
test/Spec.hs view
@@ -4,13 +4,11 @@ module Main where -import Data.Aeson import qualified Data.HashMap.Strict as M-import Data.Maybe import Data.Salak import Data.Salak.CommandLine import Data.Salak.Environment-import qualified Data.Salak.Types as P+import Data.Salak.Operation () import Data.Text (Text, intercalate, pack) import System.Environment import Test.Hspec@@ -32,11 +30,11 @@ , ext :: Int } deriving (Eq, Show) -instance FromJSON Config where- parseJSON = withObject "Config" $ \v -> Config- <$> v .: "name"- <*> v .: "dir"- <*> (fromMaybe 1 <$> v .:? "ext")+instance FromProperties Config where+ fromProperties v = Config+ <$> v .?> "name"+ <*> v .?> "dir"+ <*> v .?> "ext" .?= 1 specProperty = do context "empty" $ do@@ -63,39 +61,39 @@ it "Reject replacement" $ do insert k "1" m `shouldBe` m it "quickCheck" $ do- quickCheck $ \a' (b :: Integer) -> let a = pack a' in Just b == P.lookup a (insert (toKeys a) (PNum $ fromInteger b) empty)- quickCheck $ \a' (b :: Bool) -> let a = pack a' in Just b == P.lookup a (insert (toKeys a) (PBool b) empty)- quickCheck $ \a' (b :: String) -> let a = pack a' in Just b == P.lookup a (insert (toKeys a) (PStr $ pack b) empty)+ quickCheck $ \a' (b :: Int) -> let a = pack a' in Just b == (insert (toKeys a) (PNum $ fromIntegral b) empty) .>> a+ quickCheck $ \a' (b :: Bool) -> let a = pack a' in Just b == (insert (toKeys a) (PBool b) empty) .>> a+ quickCheck $ \a' (b :: String) -> let a = pack a' in Just b == (insert (toKeys a) (PStr $ pack b) empty) .>> a context "lookup" $ do it "normal" $ do let m = insert ["a"] (PNum 1) empty n = insert ["a"] (PStr "true") empty- (P.lookup "a" empty :: Maybe Int) `shouldBe` Nothing- (P.lookup "a" m :: Maybe Int) `shouldBe` Just 1- (P.lookup "a" m :: Maybe Bool) `shouldFail` Nothing- (P.lookup "a" m :: Maybe String) `shouldBe` Just "1.0"- (P.lookup "a" n :: Maybe Int) `shouldFail` Nothing- (P.lookup "a" n :: Maybe Bool) `shouldBe` Just True- (P.lookup "a" n :: Maybe Text) `shouldBe` Just "true"+ (empty .>> "a" :: Maybe Int) `shouldBe` Nothing+ (m .>> "a" :: Maybe Int) `shouldBe` Just 1+ (m .>> "a" :: Maybe Bool) `shouldFail` Nothing+ (m .>> "a" :: Maybe String) `shouldBe` Just "1.0"+ (n .>> "a" :: Maybe Int) `shouldFail` Nothing+ (n .>> "a" :: Maybe Bool) `shouldBe` Just True+ (n .>> "a" :: Maybe Text) `shouldBe` Just "true" context "makeProperties" $ do it "normal" $ do m <- makePropertiesFromEnvironment empty h <- getEnv "HOME"- (P.lookup "home" m) `shouldBe` Just h+ (m .>> "home") `shouldBe` Just h context "command-line" $ do it "normal" $ do let args = ["--package.a.enabled=true","--package.b.enabled=false","package.c.enabled=false"] m <- makePropertiesFromCommandLine' args defaultParseCommandLine empty- P.lookup "package.a.enabled" m `shouldBe` Just True- P.lookup "package.b.enabled" m `shouldBe` Just False- (P.lookup "package.c.enabled" m :: Maybe Bool )`shouldBe` Nothing+ (m .>> "package.a.enabled") `shouldBe` Just True+ (m .>> "package.b.enabled") `shouldBe` Just False+ (m .>> "package.c.enabled" :: Maybe Bool )`shouldBe` Nothing context "environment" $ do it "normal" $ do let args = [("PACKAGE_A_ENABLED","true"),("PACKAGE_B_ENABLED","false")] m = makePropertiesFromEnvironment' args empty- P.lookup "package.a.enabled" m `shouldBe` Just True- P.lookup "package.b.enabled" m `shouldBe` Just False- (P.lookup "package.c.enabled" m :: Maybe Bool )`shouldBe` Nothing+ (m .>> "package.a.enabled") `shouldBe` Just True+ (m .>> "package.b.enabled") `shouldBe` Just False+ (m .>> "package.c.enabled" :: Maybe Bool )`shouldBe` Nothing specProperties = do context "defaultPropertiesWithFile" $ do@@ -103,32 +101,27 @@ confName' = pack confName it "read config" $ do unsetEnv "SALAK_CONFIG_NAME"- p <- defaultPropertiesWithFile confName'- P.lookup "salak.config.name" p `shouldBe` Just confName+ p <- defaultPropertiesWithFile confName+ p .>> "salak.config.name" `shouldBe` Just confName it "read config - replacement" $ do setEnv "SALAK_CONFIG_NAME" confName p <- defaultPropertiesWithFile "salak.ok"- P.lookup "salak.config.name" p `shouldBe` Just confName+ p .>> "salak.config.name" `shouldBe` Just confName it "read config - not found" $ do setEnv "SALAK_CONFIG_NAME" "salak.notfound" setEnv "SALAK_CONFIG_DIR" "test"- defaultPropertiesWithFile confName' `shouldThrow` anyErrorCall+ defaultPropertiesWithFile confName `shouldThrow` anyException it "read config - read file parse Config" $ do setEnv "SALAK_CONFIG_NAME" confName setEnv "SALAK_CONFIG_DIR" "test" setEnv "SALAK_CONFIG" confName- p <- defaultPropertiesWithFile confName'- let get :: FromProperties a => Text -> Maybe a- get = flip P.lookup p- -- print p- -- let v::Return Value = fromProperties p- -- P.fromReturn (return ()) $ BC.putStrLn . encodePretty <$> v- get "salak.config.name" `shouldBe` Just confName- get "salak.config" `shouldBe` Just confName- (get "salak.config" :: Maybe Config) `shouldBe` Just (Config confName' "test" 1)- (get "array" :: Maybe [String]) `shouldBe` Just ["a","b"]- (get "array" :: Maybe [Int]) `shouldFail` Nothing- (get "array" :: Maybe String) `shouldFail` Nothing+ p <- defaultPropertiesWithFile confName+ (p .>> "salak.config.name") `shouldBe` Just confName+ (p .>> "salak.config") `shouldBe` Just confName+ (p .>> "salak.config" :: Maybe Config) `shouldBe` Just (Config confName' "test" 1)+ (p .>> "array" :: Maybe [String]) `shouldBe` Just ["a","b"]+ (p .>> "array" :: Maybe [Int]) `shouldFail` Nothing+ (p .>> "array" :: Maybe String) `shouldFail` Nothing