telescope-0.3.0: src/Telescope/Asdf/Encoding.hs
module Telescope.Asdf.Encoding where
import Conduit
import Data.Binary.Get
import Data.ByteString (ByteString)
import Data.ByteString.Lazy qualified as BL
import Data.Function ((&))
import Effectful
import Effectful.Error.Static
import Effectful.Reader.Dynamic
import Effectful.Resource
import Effectful.State.Static.Local
import Telescope.Asdf.Class
import Telescope.Asdf.Core
import Telescope.Asdf.Encoding.File
import Telescope.Asdf.Encoding.Stream
import Telescope.Asdf.Error
import Telescope.Asdf.Node
import Telescope.Data.Parser (ParseError, runParser)
import Text.Libyaml qualified as Yaml
-- | Encode a 'ToAsdf' to a 'ByteString'
encodeM :: (ToAsdf a, MonadIO m, MonadThrow m) => a -> m ByteString
encodeM a = runAsdfM $ encode a
-- | Encode a 'ToAsdf' to a 'ByteString'
encode :: (ToAsdf a, IOE :> es, Error AsdfError :> es) => a -> Eff es ByteString
encode a = do
asdf <- toAsdfDoc a
file <- encodeAsdf asdf
pure $ concatAsdfFile file
-- | Encode an 'Asdf' document to a 'ByteString'
encodeAsdf :: (IOE :> es, Error AsdfError :> es) => Asdf -> Eff es AsdfFile
encodeAsdf a = do
(doc, bds) <- encodeNode (toNode a)
let tree = encodeTree doc
let blocks = fmap encodeBlock bds
let index = encodeIndex $ blockIndex tree blocks
pure $ AsdfFile{tree, blocks, index}
-- | Low-level encoding of a node to a Yaml tree, without required headers, etc
encodeNode :: (IOE :> es, Error AsdfError :> es) => Node -> Eff es (ByteString, [BlockData])
encodeNode node = do
runYamlError $ encodeStream (yieldDocumentStream $ yieldNode node)
-- | Create a stream of yaml events
encodeStream :: (IOE :> es, Error AsdfError :> es) => ConduitT () Yaml.Event (Eff (State Anchors : State [BlockData] : Resource : es)) () -> Eff es (ByteString, [BlockData])
encodeStream con = do
runStream $ con .| Yaml.encodeWith format
where
format =
Yaml.defaultFormatOptions
& Yaml.setTagRendering Yaml.renderUriTags
& Yaml.setWidth (Just 100)
-- | Decode a 'ByteString' to a 'FromAsdf'
decodeM :: (FromAsdf a, MonadIO m, MonadThrow m) => ByteString -> m a
decodeM bs = runAsdfM $ decode bs
-- | Decode a 'ByteString' to a 'FromAsdf'
decodeEither :: forall a m. (FromAsdf a, MonadIO m) => ByteString -> m (Either String a)
decodeEither bs = do
res <- liftIO $ runEff $ runErrorNoCallStack @AsdfError $ decode @a bs
pure $ either (Left . show) Right res
-- | Decode a 'ByteString' to a 'FromAsdf'
decode :: (FromAsdf a, IOE :> es, Error AsdfError :> es) => ByteString -> Eff es a
decode bs = do
f <- splitAsdfFile bs
tree <- parseAsdfTree f.tree f.blocks
decodeFromTree tree
-- | Decode a 'Tree' to a 'FromAsdf'
decodeFromTree :: forall a es. (Error AsdfError :> es) => (FromAsdf a) => Tree -> Eff es a
decodeFromTree (Tree o) = do
runParseError $ runParser $ parseValue @a (Object o)
-- | Parse the asdf file parts into a 'Tree'
parseAsdfTree :: (Error AsdfError :> es, IOE :> es) => Encoded Tree -> [Encoded Block] -> Eff es Tree
parseAsdfTree etree eblks = do
(root, _) <- streamAsdfFile etree eblks
pure $ Tree root
streamAsdfFile :: (Error AsdfError :> es, IOE :> es) => Encoded Tree -> [Encoded Block] -> Eff es (Object, Anchors)
streamAsdfFile (Encoded inp) ebks = do
blocks <- mapM decodeBlock ebks
runYamlError $ do
runReader blocks . runState @Anchors mempty . runResource . runConduit $ Yaml.decode inp .| sinkTree
decodeBlock :: (Error AsdfError :> es) => Encoded Block -> Eff es BlockData
decodeBlock (Encoded blk) = do
case runGetOrFail getBlock (BL.fromStrict blk) of
Left (_, num, err) -> throwError $ BlockError $ "at " ++ show num ++ ": " ++ err
Right ("", _, b) -> pure b
Right (rest, _, _) -> throwError $ BlockError $ "Unused bytes: " ++ show rest
-- | Decode the BlockIndex
decodeBlockIndex :: (Error AsdfError :> es, IOE :> es) => ByteString -> Eff es BlockIndex
decodeBlockIndex inp =
runYamlError . runResource . runConduit $ Yaml.decode inp .| sinkIndex
runYamlError :: (Error AsdfError :> es) => Eff (Error YamlError : es) a -> Eff es a
runYamlError = runErrorNoCallStackWith @YamlError (throwError . YamlError . show)
runParseError :: (Error AsdfError :> es) => Eff es (Either ParseError a) -> Eff es a
runParseError eff = do
res <- eff
either (throwError . ParseError . show) pure res