yaml 0.11.4.0 → 0.11.5.0
raw patch · 5 files changed
+146/−39 lines, 5 files
Files
- ChangeLog.md +9/−0
- src/Data/Yaml.hs +49/−0
- src/Data/Yaml/Internal.hs +72/−37
- test/Data/YamlSpec.hs +14/−0
- yaml.cabal +2/−2
ChangeLog.md view
@@ -1,5 +1,14 @@ # ChangeLog for yaml +## 0.11.5.0++* New functions capable of parsing YAML streams containing multiple documents into a list of results:+ * `decodeAllEither'`+ * `decodeAllFileEither`+ * `decodeAllFileWithWarnings`+ * `decodeAllThrow`+ * `decodeAllFileThrow`+ ## 0.11.4.0 * add `ToYaml` instance for `String` [#186](https://github.com/snoyberg/yaml/pull/186)
src/Data/Yaml.hs view
@@ -37,6 +37,16 @@ , decodeFileWithWarnings , decodeThrow , decodeFileThrow+ -- ** Decoding multiple documents+ --+ -- | For situations where we need to be able to parse multiple documents+ -- separated by `---` in a YAML stream, these functions decode a list of+ -- values rather than a single value.+ , decodeAllEither'+ , decodeAllFileEither+ , decodeAllFileWithWarnings+ , decodeAllThrow+ , decodeAllFileThrow -- ** More control over decoding , decodeHelper -- * Types@@ -193,6 +203,15 @@ -> IO (Either ParseException a) decodeFileEither = fmap (fmap snd) . decodeFileWithWarnings +-- | Like `decodeFileEither`, but decode multiple documents.+--+-- @since 0.11.5.0+decodeAllFileEither+ :: FromJSON a+ => FilePath+ -> IO (Either ParseException [a])+decodeAllFileEither = fmap (fmap snd) . decodeAllFileWithWarnings+ -- | A version of `decodeFileEither` that returns warnings along with the parse -- result. --@@ -203,6 +222,15 @@ -> IO (Either ParseException ([Warning], a)) decodeFileWithWarnings = decodeHelper_ . Y.decodeFile +-- | Like `decodeFileWithWarnings`, but decode multiple documents.+--+-- @since 0.11.5.0+decodeAllFileWithWarnings+ :: FromJSON a+ => FilePath+ -> IO (Either ParseException ([Warning], [a]))+decodeAllFileWithWarnings = decodeAllHelper_ . Y.decodeFile+ decodeEither :: FromJSON a => ByteString -> Either String a decodeEither bs = unsafePerformIO $ either (Left . prettyPrintParseException) id@@ -218,17 +246,38 @@ . fmap (fmap snd) . decodeHelper . Y.decode +-- | Like 'decodeEither'', but decode multiple documents.+--+-- @since 0.11.5.0+decodeAllEither' :: FromJSON a => ByteString -> Either ParseException [a]+decodeAllEither' = either Left (either (Left . AesonException) Right)+ . unsafePerformIO+ . fmap (fmap snd) . decodeAllHelper+ . Y.decode+ -- | A version of 'decodeEither'' lifted to MonadThrow -- -- @since 0.8.31 decodeThrow :: (MonadThrow m, FromJSON a) => ByteString -> m a decodeThrow = either throwM return . decodeEither' +-- | Like `decodeThrow`, but decode multiple documents.+--+-- @since 0.11.5.0+decodeAllThrow :: (MonadThrow m, FromJSON a) => ByteString -> m [a]+decodeAllThrow = either throwM return . decodeAllEither'+ -- | A version of 'decodeFileEither' lifted to MonadIO -- -- @since 0.8.31 decodeFileThrow :: (MonadIO m, FromJSON a) => FilePath -> m a decodeFileThrow f = liftIO $ decodeFileEither f >>= either throwIO return++-- | Like `decodeFileThrow`, but decode multiple documents.+--+-- @since 0.11.5.0+decodeAllFileThrow :: (MonadIO m, FromJSON a) => FilePath -> m [a]+decodeAllFileThrow f = liftIO $ decodeAllFileEither f >>= either throwIO return -- | Construct a new 'Value' from a list of 'Value's. array :: [Value] -> Value
src/Data/Yaml/Internal.hs view
@@ -11,6 +11,8 @@ , parse , decodeHelper , decodeHelper_+ , decodeAllHelper+ , decodeAllHelper_ , textToScientific , stringScalar , defaultStringStyle@@ -56,6 +58,7 @@ import Data.Text.Encoding.Error (lenientDecode) import Data.Typeable import qualified Data.Vector as V+import Data.Void (Void) import qualified Text.Libyaml as Y import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)@@ -66,6 +69,7 @@ , _expected :: Maybe Event } | InvalidYaml (Maybe YamlException)+ | MultipleDocuments | AesonException String | OtherParseException SomeException | NonStringKey JSONPath@@ -115,6 +119,7 @@ _ -> ",\n" ++ context ++ ":\n" , problem ]+ MultipleDocuments -> "Multiple YAML documents encountered" AesonException s -> "Aeson exception:\n" ++ s OtherParseException exc -> "Generic parse exception:\n" ++ show exc NonStringKey path -> formatError path "Non-string keys are not supported"@@ -158,24 +163,34 @@ parse :: ReaderT JSONPath (ConduitM Event o Parse) Value parse = do+ docs <- parseAll+ case docs of+ [] -> return Null+ [doc] -> return doc+ _ -> liftIO $ throwIO MultipleDocuments++parseAll :: ReaderT JSONPath (ConduitM Event o Parse) [Value]+parseAll = do streamStart <- lift CL.head case streamStart of Nothing -> -- empty string input- return Null- Just EventStreamStart -> do- documentStart <- lift CL.head- case documentStart of- Just EventStreamEnd ->- -- empty file input, comment only string/file input- return Null- Just EventDocumentStart -> do- res <- parseO- requireEvent EventDocumentEnd- requireEvent EventStreamEnd- return res- _ -> liftIO $ throwIO $ UnexpectedEvent documentStart Nothing- _ -> liftIO $ throwIO $ UnexpectedEvent streamStart Nothing+ return []+ Just EventStreamStart ->+ -- empty file input, comment only string/file input+ parseDocs+ _ -> missed streamStart+ where+ parseDocs = do+ documentStart <- lift CL.head+ case documentStart of+ Just EventStreamEnd -> return []+ Just EventDocumentStart -> do+ res <- parseO+ requireEvent EventDocumentEnd+ (res :) <$> parseDocs+ _ -> missed documentStart+ missed event = liftIO $ throwIO $ UnexpectedEvent event Nothing parseScalar :: ByteString -> Anchor -> Style -> Tag -> ReaderT JSONPath (ConduitM Event o Parse) Text@@ -283,36 +298,56 @@ merge xs = (Set.fromList (M.keys xs \\ M.keys front), M.union front xs) +parseSrc :: ReaderT JSONPath (ConduitM Event Void Parse) val+ -> ConduitM () Event Parse ()+ -> IO (val, ParseState)+parseSrc eventParser src = runResourceT $ runStateT+ (runConduit $ src .| runReaderT eventParser [])+ (ParseState Map.empty [])++mkHelper :: ReaderT JSONPath (ConduitM Event Void Parse) val -- ^ parse libyaml events as Value or [Value]+ -> (SomeException -> IO (Either ParseException a)) -- ^ what to do with unhandled exceptions+ -> ((val, ParseState) -> Either ParseException a) -- ^ further transform and parse results+ -> ConduitM () Event Parse () -- ^ the libyaml event (string/file) source+ -> IO (Either ParseException a)+mkHelper eventParser onOtherExc extractResults src = catches+ (extractResults <$> parseSrc eventParser src)+ [ Handler $ \pe -> return $ Left (pe :: ParseException)+ , Handler $ \ye -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)+ , Handler $ \sae -> throwIO (sae :: SomeAsyncException)+ , Handler onOtherExc+ ]+ decodeHelper :: FromJSON a => ConduitM () Y.Event Parse () -> IO (Either ParseException ([Warning], Either String a))-decodeHelper src = do- -- This used to be tryAny, but the fact is that catching async- -- exceptions is fine here. We'll rethrow them immediately in the- -- otherwise clause.- x <- try $ runResourceT $ runStateT (runConduit $ src .| runReaderT parse []) (ParseState Map.empty [])- case x of- Left e- | Just pe <- fromException e -> return $ Left pe- | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)- | otherwise -> throwIO e- Right (y, st) -> return $ Right (parseStateWarnings st, parseEither parseJSON y)+decodeHelper = mkHelper parse throwIO $ \(v, st) ->+ Right (parseStateWarnings st, parseEither parseJSON v) +decodeAllHelper :: FromJSON a+ => ConduitM () Event Parse ()+ -> IO (Either ParseException ([Warning], Either String [a]))+decodeAllHelper = mkHelper parseAll throwIO $ \(vs, st) ->+ Right (parseStateWarnings st, mapM (parseEither parseJSON) vs)++catchLeft :: SomeException -> IO (Either ParseException a)+catchLeft = return . Left . OtherParseException+ decodeHelper_ :: FromJSON a => ConduitM () Event Parse () -> IO (Either ParseException ([Warning], a))-decodeHelper_ src = do- x <- try $ runResourceT $ runStateT (runConduit $ src .| runReaderT parse []) (ParseState Map.empty [])- case x of- Left e- | Just pe <- fromException e -> return $ Left pe- | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)- | Just sae <- fromException e -> throwIO (sae :: SomeAsyncException)- | otherwise -> return $ Left $ OtherParseException e- Right (y, st) -> return $ either- (Left . AesonException)- Right- ((,) (parseStateWarnings st) <$> parseEither parseJSON y)+decodeHelper_ = mkHelper parse catchLeft $ \(v, st) ->+ case parseEither parseJSON v of+ Left e -> Left $ AesonException e+ Right x -> Right (parseStateWarnings st, x)++decodeAllHelper_ :: FromJSON a+ => ConduitM () Event Parse ()+ -> IO (Either ParseException ([Warning], [a]))+decodeAllHelper_ = mkHelper parseAll catchLeft $ \(vs, st) ->+ case mapM (parseEither parseJSON) vs of+ Left e -> Left $ AesonException e+ Right xs -> Right (parseStateWarnings st, xs) type StringStyle = Text -> ( Tag, Style )
test/Data/YamlSpec.hs view
@@ -69,6 +69,11 @@ actual <- D.decodeThrow bs actual `shouldBe` expected +shouldDecodeAll :: (Show a, D.FromJSON a, Eq a) => B8.ByteString -> [a] -> IO ()+shouldDecodeAll bs expected = do+ actual <- D.decodeAllThrow bs+ actual `shouldBe` expected+ shouldDecodeEvents :: B8.ByteString -> [Y.Event] -> IO () shouldDecodeEvents bs expected = do actual <- runConduitRes $ Y.decode bs .| CL.consume@@ -193,6 +198,15 @@ it "returns Left" $ do (D.decodeFileEither "./does_not_exist.yaml" :: IO (Either D.ParseException D.Value)) >>= (`shouldSatisfy` isLeft) + describe "multiple document support" $ do+ it "decodes zero-length input" $+ "" `shouldDecodeAll` ([] :: [D.Value])+ it "decodes comment-only input" $+ "# foo\n# bar" `shouldDecodeAll` ([] :: [D.Value])+ it "decodes a single document stream" $+ "foo: true" `shouldDecodeAll` [object ["foo" .= True]]+ it "decodes multiple documents" $+ "--- 1\n--- 2" `shouldDecodeAll` [D.Number 1, D.Number 2] describe "round-tripping of special scalars" $ do let special = words "y Y On ON false 12345 12345.0 12345a 12e3"
yaml.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 1f75743c2fefefb33f6f3e8a51fce90cb3f286c975d205d7ded3789d5b15c488+-- hash: 55c85c8d4d3074a558a82e30a2592ecff9db2e6f1571547c73d26ba44bfc1c20 name: yaml-version: 0.11.4.0+version: 0.11.5.0 synopsis: Support for parsing and rendering YAML documents. description: README and API documentation are available at <https://www.stackage.org/package/yaml> category: Data