diff --git a/Data/Yaml.hs b/Data/Yaml.hs
new file mode 100644
--- /dev/null
+++ b/Data/Yaml.hs
@@ -0,0 +1,243 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Data.Yaml
+    ( -- * Types
+      Value (..)
+    , Parser
+      -- * Constructors and accessors
+    , object
+    , array
+    , (.=)
+    , (.:)
+    , (.:?)
+    , (.!=)
+      -- * Parsing
+    , parseMonad
+    , parseEither
+    , parseMaybe
+      -- * Classes
+    , ToJSON (..)
+    , FromJSON (..)
+      -- * Encoding/decoding
+    , encode
+    , encodeFile
+    , decode
+    , decodeFile
+    ) where
+
+import qualified Text.Libyaml as Y
+import Data.Aeson
+    ( Value (..), ToJSON (..), FromJSON (..), object
+    , (.=) , (.:) , (.:?) , (.!=)
+    )
+import Data.Aeson.Types (Pair, parseMaybe, parseEither, Parser)
+import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as S8
+import qualified Data.Map as Map
+import System.IO.Unsafe (unsafePerformIO)
+import Control.Exception (try, throwIO, fromException, Exception)
+import Control.Monad.Trans.State
+import qualified Data.Conduit as C
+import qualified Data.Conduit.List as CL
+import Control.Monad.Trans.Class (MonadTrans, lift)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad (liftM)
+import qualified Data.Vector as V
+import Data.Text (Text, pack)
+import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
+import Data.Text.Encoding.Error (lenientDecode)
+import qualified Data.HashMap.Strict as M
+import Data.Typeable
+
+encode :: ToJSON a => a -> ByteString
+encode obj = unsafePerformIO $
+    C.runResourceT $ CL.sourceList (objToEvents $ toJSON obj)
+                C.$$ Y.encode
+
+encodeFile :: ToJSON a => FilePath -> a -> IO ()
+encodeFile fp obj = C.runResourceT
+            $ CL.sourceList (objToEvents $ toJSON obj)
+         C.$$ Y.encodeFile fp
+
+objToEvents :: Value -> [Y.Event]
+objToEvents o = (:) EventStreamStart
+              . (:) EventDocumentStart
+              $ objToEvents' o
+              [ EventDocumentEnd
+              , EventStreamEnd
+              ]
+
+{- FIXME
+scalarToEvent :: YamlScalar -> Event
+scalarToEvent (YamlScalar v t s) = EventScalar v t s Nothing
+-}
+
+objToEvents' :: Value -> [Y.Event] -> [Y.Event]
+--objToEvents' (Scalar s) rest = scalarToEvent s : rest
+objToEvents' (Array list) rest =
+    EventSequenceStart Nothing
+  : foldr ($) (EventSequenceEnd : rest) (map objToEvents' $ V.toList list)
+objToEvents' (Object pairs) rest =
+    EventMappingStart Nothing
+  : foldr ($) (EventMappingEnd : rest) (map pairToEvents $ M.toList pairs)
+objToEvents' (String s) rest = EventScalar (encodeUtf8 s) NoTag Any Nothing : rest
+objToEvents' Null rest = EventScalar "null" NoTag Literal Nothing : rest
+objToEvents' (Bool True) rest = EventScalar "true" NoTag Literal Nothing : rest
+objToEvents' (Bool False) rest = EventScalar "false" NoTag Literal Nothing : rest
+objToEvents' (Number n) rest = EventScalar (S8.pack $ show n) NoTag Literal Nothing : rest
+
+pairToEvents :: Pair -> [Y.Event] -> [Y.Event]
+pairToEvents (k, v) rest =
+    EventScalar (encodeUtf8 k) NoTag Any Nothing
+  : objToEvents' v rest
+
+-- Parsing
+
+data ParseException = NonScalarKey
+                    | UnknownAlias { _anchorName :: Y.AnchorName }
+                    | UnexpectedEvent { _received :: Maybe Event
+                                      , _expected :: Maybe Event
+                                      }
+                    | InvalidYaml (Maybe String)
+    deriving (Show, Typeable)
+instance Exception ParseException
+
+newtype PErrorT m a = PErrorT { runPErrorT :: m (Either ParseException a) }
+instance Monad m => Monad (PErrorT m) where
+    return = PErrorT . return . Right
+    (PErrorT m) >>= f = PErrorT $ do
+        e <- m
+        case e of
+            Left e' -> return $ Left e'
+            Right a -> runPErrorT $ f a
+instance MonadTrans PErrorT where
+    lift = PErrorT . liftM Right
+instance MonadIO m => MonadIO (PErrorT m) where
+    liftIO = lift . liftIO
+
+type Parse = StateT (Map.Map String Value) IO
+
+requireEvent :: Event -> C.Sink Event Parse ()
+requireEvent e = do
+    f <- CL.head
+    if f == Just e
+        then return ()
+        else liftIO $ throwIO $ UnexpectedEvent f $ Just e
+
+parse :: C.Sink Event Parse Value
+parse = do
+    requireEvent EventStreamStart
+    requireEvent EventDocumentStart
+    res <- parseO
+    requireEvent EventDocumentEnd
+    requireEvent EventStreamEnd
+    return res
+
+parseScalar :: ByteString -> Anchor
+            -> C.Sink Event Parse Text
+parseScalar v a = do
+    let res = decodeUtf8With lenientDecode v
+    case a of
+        Nothing -> return res
+        Just an -> do
+            lift $ modify (Map.insert an $ String res)
+            return res
+
+parseO :: C.Sink Event Parse Value
+parseO = do
+    me <- CL.head
+    case me of
+        Just (EventScalar v _t _s a) -> fmap String $ parseScalar v a
+        Just (EventSequenceStart a) -> parseS a id
+        Just (EventMappingStart a) -> parseM a M.empty
+        Just (EventAlias an) -> do
+            m <- lift get
+            case Map.lookup an m of
+                Nothing -> liftIO $ throwIO $ UnknownAlias an
+                Just v -> return v
+        _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
+
+parseS :: Y.Anchor
+       -> ([Value] -> [Value])
+       -> C.Sink Event Parse Value
+parseS a front = do
+    me <- CL.peek
+    case me of
+        Just EventSequenceEnd -> do
+            CL.drop 1
+            let res = Array $ V.fromList $ front []
+            case a of
+                Nothing -> return res
+                Just an -> do
+                    lift $ modify $ Map.insert an res
+                    return res
+        _ -> do
+            o <- parseO
+            parseS a $ front . (:) o
+
+parseM :: Y.Anchor
+       -> M.HashMap Text Value
+       -> C.Sink Event Parse Value
+parseM a front = do
+    me <- CL.peek
+    case me of
+        Just EventMappingEnd -> do
+            CL.drop 1
+            let res = Object front
+            case a of
+                Nothing -> return res
+                Just an -> do
+                    lift $ modify $ Map.insert an res
+                    return res
+        _ -> do
+            CL.drop 1
+            s <- case me of
+                    Just (EventScalar v _ _ a') -> parseScalar v a'
+                    _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
+            o <- parseO
+
+            let al  = M.insert s o front
+                al' = if s == pack "<<"
+                         then case o of
+                                  Object l  -> M.union al l
+                                  Array l -> M.union al $ foldl merge' M.empty $ V.toList l
+                                  _          -> al
+                         else al
+            parseM a $ M.insert s o al'
+    where merge' al (Object om) = M.union al om
+          merge' al _           = al
+
+decode :: FromJSON a
+       => ByteString
+       -> Maybe a
+decode bs = unsafePerformIO $ fmap (either (const Nothing) id) $ decodeHelper (Y.decode bs)
+
+decodeFile :: FromJSON a
+           => FilePath
+           -> IO (Maybe a)
+decodeFile fp = decodeHelper (Y.decodeFile fp) >>= either throwIO return
+
+decodeHelper :: FromJSON a
+             => C.Source Parse Y.Event
+             -> IO (Either ParseException (Maybe a))
+decodeHelper src = do
+    x <- try $ flip evalStateT Map.empty $ C.runResourceT $ src C.$$ parse
+    case x of
+        Left e
+            | Just pe <- fromException e -> return $ Left pe
+            | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just $ show (ye :: YamlException)
+            | otherwise -> throwIO e
+        Right y -> return $ Right $ parseMaybe parseJSON y
+
+array :: [Value] -> Value
+array = Array . V.fromList
+
+parseMonad :: Monad m => (a -> Parser b) -> a -> m b
+parseMonad p = either fail return . parseEither p
diff --git a/Text/Libyaml.hs b/Text/Libyaml.hs
--- a/Text/Libyaml.hs
+++ b/Text/Libyaml.hs
@@ -9,6 +9,8 @@
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE CPP #-}
 
+-- | Low-level, streaming YAML interface. For a higher-level interface, see
+-- "Data.Yaml".
 module Text.Libyaml
     ( -- * The event stream
       Event (..)
@@ -41,8 +43,9 @@
 import Control.Monad.Trans.Class (lift)
 
 import Control.Exception (throwIO, Exception, finally)
-import Data.Enumerator
 import Control.Applicative
+import Control.Monad.Trans.Resource
+import qualified Data.Conduit as C
 
 data Event =
       EventStreamStart
@@ -120,8 +123,8 @@
 foreign import ccall unsafe "yaml_parser_initialize"
     c_yaml_parser_initialize :: Parser -> IO CInt
 
-foreign import ccall unsafe "&yaml_parser_delete"
-    c_yaml_parser_delete :: FunPtr (Parser -> IO ())
+foreign import ccall unsafe "yaml_parser_delete"
+    c_yaml_parser_delete :: Parser -> IO ()
 
 foreign import ccall unsafe "yaml_parser_set_input_string"
     c_yaml_parser_set_input_string :: Parser
@@ -146,8 +149,8 @@
     c_fclose :: File
              -> IO ()
 
-foreign import ccall unsafe "&fclose_helper"
-    c_fclose_helper :: FunPtr (File -> Parser -> IO ())
+foreign import ccall unsafe "fclose_helper"
+    c_fclose_helper :: File -> IO ()
 
 withForeignPtr' :: MonadIO m => ForeignPtr a -> (Ptr a -> m b) -> m b
 withForeignPtr' fp f = do
@@ -455,60 +458,67 @@
     deriving (Show, Typeable)
 instance Exception ToEventRawException
 
-decode :: MonadIO m => B.ByteString -> Enumerator Event m a
-decode bs i = do
-    fp <- liftIO $ mallocForeignPtrBytes parserSize
-    res <- liftIO $ withForeignPtr fp c_yaml_parser_initialize
-    liftIO $ addForeignPtrFinalizer c_yaml_parser_delete fp
-    a <-
-      if (res == 0)
-        then throwError $ YamlException "Yaml out of memory"
-        else do -- NOTE: can't replace the following with unsafeUseAsCString
-                -- since it must run in a MonadIO
-            let (fptr, offset, len) = B.toForeignPtr bs
-            withForeignPtr' fptr $ \ptr -> do
-                let ptr' = castPtr ptr `plusPtr` offset
-                    len' = fromIntegral len
-                liftIO $ withForeignPtr fp $ \p ->
-                    c_yaml_parser_set_input_string p ptr' len'
-                runParser fp i
-    return a
+decode :: ResourceIO m => B.ByteString -> C.Source m Event
+decode bs =
+    C.sourceIO alloc cleanup (runParser . fst)
+  where
+    alloc = mask_ $ do
+        ptr <- mallocBytes parserSize
+        res <- c_yaml_parser_initialize ptr
+        if res == 0
+            then do
+                c_yaml_parser_delete ptr
+                free ptr
+                throwIO $ YamlException "Yaml out of memory"
+            else do
+                let (bsfptr, offset, len) = B.toForeignPtr bs
+                let bsptrOrig = unsafeForeignPtrToPtr bsfptr
+                let bsptr = castPtr bsptrOrig `plusPtr` offset
+                c_yaml_parser_set_input_string ptr bsptr (fromIntegral len)
+                return (ptr, bsfptr)
+    cleanup (ptr, bsfptr) = do
+        touchForeignPtr bsfptr
+        c_yaml_parser_delete ptr
+        free ptr
 
-decodeFile :: MonadIO m => FilePath -> Enumerator Event m a
-decodeFile file i = do
-    fp <- liftIO $ mallocForeignPtrBytes parserSize
-    liftIO $ addForeignPtrFinalizer c_yaml_parser_delete fp
-    res <- liftIO $ withForeignPtr fp c_yaml_parser_initialize
-    a <-
-      if res == 0
-        then throwError $ YamlException "Yaml out of memory"
-        else do
-            file' <- liftIO
-                    $ withCString file $ \file' -> withCString "r" $ \r' ->
-                            c_fopen file' r'
-            liftIO $ addForeignPtrFinalizerEnv c_fclose_helper file' fp
-            if (file' == nullPtr)
-                then throwError $ YamlException
-                            $ "Yaml file not found: " ++ file
-                else do
-                    liftIO $ withForeignPtr fp $ \p ->
-                        c_yaml_parser_set_input_file p file'
-                    a <- runParser fp i
-                    return a
-    return a
+decodeFile :: ResourceIO m => FilePath -> C.Source m Event
+decodeFile file =
+    C.sourceIO alloc cleanup (runParser . fst)
+  where
+    alloc = mask_ $ do
+        ptr <- mallocBytes parserSize
+        res <- c_yaml_parser_initialize ptr
+        if res == 0
+            then do
+                c_yaml_parser_delete ptr
+                free ptr
+                throwIO $ YamlException "Yaml out of memory"
+            else do
+                file' <- liftIO
+                        $ withCString file $ \file' -> withCString "r" $ \r' ->
+                                c_fopen file' r'
+                if file' == nullPtr
+                    then do
+                        c_fclose_helper file'
+                        c_yaml_parser_delete ptr
+                        free ptr
+                        throwIO $ YamlException
+                                $ "Yaml file not found: " ++ file
+                    else do
+                        c_yaml_parser_set_input_file ptr file'
+                        return (ptr, file')
+    cleanup (ptr, file') = do
+        c_fclose_helper file'
+        c_yaml_parser_delete ptr
+        free ptr
 
-runParser :: MonadIO m
-          => ForeignPtr ParserStruct
-          -> Enumerator Event m a
-runParser fp (Continue k) = do
-    e <- liftIO $ withForeignPtr fp parserParseOne'
+runParser :: ResourceIO m => Parser -> m (C.SourceResult Event)
+runParser parser = liftIO $ do
+    e <- parserParseOne' parser
     case e of
-        Left err -> throwError $ YamlException err
-        Right Nothing -> continue k
-        Right (Just ev) -> do
-            step' <- lift $ runIteratee $ k $ Chunks [ev]
-            runParser fp step'
-runParser _ step = returnI step
+        Left err -> throwIO $ YamlException err
+        Right Nothing -> return $ C.Closed
+        Right (Just ev) -> return $ C.Open ev
 
 parserParseOne' :: Parser
                 -> IO (Either String (Maybe Event))
@@ -529,57 +539,60 @@
             , show offset
             , "\n"
             ]
-        else liftIO $ Right <$> getEvent er
+        else Right <$> getEvent er
 
-encode :: MonadIO m => Iteratee Event m B.ByteString
-encode = do
-    fp <- liftIO $ mallocForeignPtrBytes emitterSize
-    res <- liftIO $ withForeignPtr fp c_yaml_emitter_initialize
-    when (res == 0) $ throwError
-        $ YamlException "c_yaml_emitter_initialize failed"
-    buf <- liftIO $ mallocForeignPtrBytes bufferSize
-    liftIO $ withForeignPtr buf c_buffer_init
-    liftIO $ withForeignPtr fp $
-        \emitter -> withForeignPtr buf $
-        \b -> c_my_emitter_set_output emitter b
-    runEmitter (go buf) fp
+encode :: ResourceIO m => C.Sink Event m ByteString
+encode =
+    runEmitter alloc close
   where
-    go buf = withForeignPtr buf $ \b -> do
+    alloc emitter = do
+        fbuf <- mallocForeignPtrBytes bufferSize
+        withForeignPtr fbuf c_buffer_init
+        withForeignPtr fbuf $ c_my_emitter_set_output emitter
+        return fbuf
+    close fbuf = withForeignPtr fbuf $ \b -> do
         ptr' <- c_get_buffer_buff b
         len <- c_get_buffer_used b
         fptr <- newForeignPtr_ $ castPtr ptr'
         return $ B.fromForeignPtr fptr 0 $ fromIntegral len
 
-encodeFile :: MonadIO m
+encodeFile :: ResourceIO m
            => FilePath
-           -> Iteratee Event m ()
-encodeFile filePath = do
-    fp <- liftIO $ mallocForeignPtrBytes emitterSize
-    res <- liftIO $ withForeignPtr fp c_yaml_emitter_initialize
-    when (res == 0) $ throwError
-        $ YamlException "c_yaml_emitter_initialize failed"
-    file <- liftIO $ withCString filePath $
-                \filePath' -> withCString "w" $
-                \w' -> c_fopen filePath' w'
-    when (file == nullPtr) $ throwError
-        $ YamlException $ "could not open file for write: " ++ filePath
-    liftIO $ withForeignPtr fp $ flip c_yaml_emitter_set_output_file file
-    runEmitter (c_fclose file) fp
+           -> C.Sink Event m ()
+encodeFile filePath = C.Sink $ do
+    (releaseKey, file) <- flip withIO c_fclose $ do
+        file <- liftIO $ withCString filePath $
+                    \filePath' -> withCString "w" $
+                    \w' -> c_fopen filePath' w'
+        if (file == nullPtr)
+            then throwIO $ YamlException $ "could not open file for write: " ++ filePath
+            else return file
+    C.prepareSink $ runEmitter (alloc file) (return) -- FIXME close file early
+  where
+    alloc file emitter = do
+        c_yaml_emitter_set_output_file emitter file
+        return ()
 
-runEmitter :: MonadIO m
-           => IO a
-           -> ForeignPtr EmitterStruct
-           -> Iteratee Event m a
-runEmitter close fp = continue go
+runEmitter :: ResourceIO m
+           => (Emitter -> IO a) -- ^ alloc
+           -> (a -> IO b) -- ^ close
+           -> C.Sink Event m b
+runEmitter allocI closeI =
+    C.sinkIO alloc cleanup push close
   where
-    go EOF = do
-        liftIO $ withForeignPtr fp c_yaml_emitter_delete
-        a <- liftIO close
-        yield a EOF
-    go (Chunks c) = do
-        liftIO $ withForeignPtr fp $ \emitter ->
-            mapM_ (\e -> toEventRaw e $ c_yaml_emitter_emit emitter) c
-        continue go
+    alloc = mask_ $ do
+        emitter <- mallocBytes emitterSize
+        res <- c_yaml_emitter_initialize emitter
+        when (res == 0) $ throwIO $ YamlException "c_yaml_emitter_initialize failed"
+        a <- allocI emitter
+        return (emitter, a)
+    cleanup (emitter, _) = do
+        c_yaml_emitter_delete emitter
+        free emitter
+    push (emitter, _) e = do
+        liftIO $ toEventRaw e $ c_yaml_emitter_emit emitter
+        return C.Processing
+    close (_, a) = liftIO $ closeI a
 
 data YamlException = YamlException String
     deriving (Show, Typeable)
diff --git a/c/helper.c b/c/helper.c
--- a/c/helper.c
+++ b/c/helper.c
@@ -135,7 +135,7 @@
 	yaml_parser_set_input_file(parser, in);
 }
 
-int fclose_helper(FILE *file, yaml_parser_t *parser)
+int fclose_helper(FILE *file)
 {
 	if (! file) return 0;
 	return fclose(file);
diff --git a/runtests.hs b/runtests.hs
deleted file mode 100644
--- a/runtests.hs
+++ /dev/null
@@ -1,176 +0,0 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE PackageImports #-}
-import Test.Framework (defaultMain)
-
-import Text.Libyaml
-import qualified Data.ByteString.Char8 as B8
-
-import Test.Framework.Providers.HUnit
-import Test.HUnit hiding (Test, path)
-
-import qualified Data.Enumerator as E
-import Data.Enumerator (($$))
-import Data.List (foldl')
-
-import System.Directory
-import Control.Monad
-
-main :: IO ()
-main = defaultMain
-    [ testCase "count scalars with anchor" caseCountScalarsWithAnchor
-    , testCase "count sequences with anchor" caseCountSequencesWithAnchor
-    , testCase "count mappings with anchor" caseCountMappingsWithAnchor
-    , testCase "count aliases" caseCountAliases
-    , testCase "count scalars" caseCountScalars
-    , testCase "largest string" caseLargestString
-    , testCase "encode/decode" caseEncodeDecode
-    , testCase "encode/decode file" caseEncodeDecodeFile
-    , testCase "interleaved encode/decode" caseInterleave
-    , testCase "decode invalid document (without segfault)" caseDecodeInvalidDocument
-    ]
-
-counter :: (Event -> Bool) -> Int -> E.Step Event IO Int
-counter pred' acc =
-    E.Continue go
-  where
-    go E.EOF = E.yield acc E.EOF
-    go (E.Chunks c) = E.returnI $ counter pred' $ length (filter pred' c) + acc
-
-caseHelper :: String
-           -> (Event -> Bool)
-           -> Int
-           -> Assertion
-caseHelper yamlString pred' expRes = do
-    Right res <- E.run $ decode (B8.pack yamlString) (counter pred' 0)
-    res @?= expRes
-
-caseCountScalarsWithAnchor :: Assertion
-caseCountScalarsWithAnchor =
-    caseHelper yamlString isScalarA 1
-  where
-    yamlString = "foo:\n  - &anchor bin1\n  - bin2\n  - bin3"
-    isScalarA (EventScalar _ _ _ (Just _)) = True
-    isScalarA _ = False
-
-caseCountSequencesWithAnchor :: Assertion
-caseCountSequencesWithAnchor =
-    caseHelper yamlString isSequenceStartA 1
-  where
-    yamlString = "foo: &anchor\n  - bin1\n  - bin2\n  - bin3"
-    isSequenceStartA (EventSequenceStart (Just _)) = True
-    isSequenceStartA _ = False
-
-caseCountMappingsWithAnchor :: Assertion
-caseCountMappingsWithAnchor =
-    caseHelper yamlString isMappingA 1
-  where
-    yamlString = "foo: &anchor\n  key1: bin1\n  key2: bin2\n  key3: bin3"
-    isMappingA (EventMappingStart (Just _)) = True
-    isMappingA _ = False
-
-caseCountAliases :: Assertion
-caseCountAliases =
-    caseHelper yamlString isAlias 1
-  where
-    yamlString = "foo: &anchor\n  key1: bin1\n  key2: bin2\n  key3: bin3\nboo: *anchor"
-    isAlias EventAlias{} = True
-    isAlias _ = False
-
-caseCountScalars :: Assertion
-caseCountScalars = do
-    Right res <- E.run $ decode yamlBS $ counter' accum
-    res @?= (7, 1, 2)
-  where
-    yamlString = "foo:\n  baz: [bin1, bin2, bin3]\nbaz: bazval"
-    yamlBS = B8.pack yamlString
-    counter' acc = E.Continue $ \s ->
-        case s of
-            E.EOF -> E.yield acc E.EOF
-            E.Chunks c -> E.returnI $ counter' $ foldl' adder acc c
-    adder (s, l, m) (EventScalar{})        = (s + 1, l, m)
-    adder (s, l, m) (EventSequenceStart{}) = (s, l + 1, m)
-    adder (s, l, m) (EventMappingStart{})  = (s, l, m + 1)
-    adder a         _                      = a
-    accum = (0, 0, 0) :: (Int, Int, Int)
-
-caseLargestString :: Assertion
-caseLargestString = do
-    Right res <- E.run $ decodeFile filePath $ dec accum
-    res @?= (length expected, expected)
-    where
-        expected = "this one is just a little bit bigger than the others"
-        filePath = "test/largest-string.yaml"
-        dec acc = E.Continue $ \s ->
-            case s of
-                E.EOF -> E.yield acc E.EOF
-                E.Chunks c ->
-                    let acc' = foldl' adder acc c
-                     in E.returnI $ dec acc'
-        adder (i, s) (EventScalar bs _ _ _) =
-            let s' = B8.unpack bs
-                i' = length s'
-             in if i' > i then (i', s') else (i, s)
-        adder acc _ = acc
-        accum = (0, "no strings found")
-
-newtype MyEvent = MyEvent Event deriving Show
-instance Eq MyEvent where
-    (MyEvent (EventScalar s t _ _)) == (MyEvent (EventScalar s' t' _ _)) =
-        s == s' && t == t'
-    MyEvent e1 == MyEvent e2 = e1 == e2
-
-caseEncodeDecode :: Assertion
-caseEncodeDecode = do
-    Right eList <- E.run $ decode yamlBS $$ E.consume
-    Right bs <- E.run $ E.enumList 1 eList $$ encode
-    Right eList2 <- E.run $ decode bs $$ E.consume
-    map MyEvent eList @=? map MyEvent eList2
-  where
-    yamlString = "foo: bar\nbaz:\n - bin1\n - bin2\n"
-    yamlBS = B8.pack yamlString
-
-removeFile' :: FilePath -> IO ()
-removeFile' fp = do
-    x <- doesFileExist fp
-    when x $ removeFile fp
-
-caseEncodeDecodeFile :: Assertion
-caseEncodeDecodeFile = do
-    removeFile' tmpPath
-    Right eList <- E.run $ decodeFile filePath $$ E.consume
-    E.run $ E.enumList 1 eList $$ encodeFile tmpPath
-    Right eList2 <- E.run $ decodeFile filePath $$ E.consume
-    map MyEvent eList @=? map MyEvent eList2
-  where
-    filePath = "test/largest-string.yaml"
-    tmpPath = "tmp.yaml"
-
-caseInterleave :: Assertion
-caseInterleave = do
-    removeFile' tmpPath
-    removeFile' tmpPath2
-    Right () <- E.run $ decodeFile filePath $$ encodeFile tmpPath
-    Right () <- E.run $ decodeFile tmpPath $$ encodeFile tmpPath2
-    f1 <- readFile tmpPath
-    f2 <- readFile tmpPath2
-    f1 @=? f2
-  where
-    filePath = "test/largest-string.yaml"
-    tmpPath = "tmp.yaml"
-    tmpPath2 = "tmp2.yaml"
-
-caseDecodeInvalidDocument :: Assertion
-caseDecodeInvalidDocument = do
-    x <- E.run $ decode yamlBS ignore
-    case x of
-        Left _ -> return ()
-        Right y -> do
-            putStrLn $ "bad return value: " ++ show y
-            assertFailure "expected parsing exception, but got no errors"
-  where
-    yamlString = "  - foo\n  - baz\nbuz"
-    yamlBS = B8.pack yamlString
-    ignore = E.Continue $ \s ->
-        case s of
-            E.EOF -> E.yield () E.EOF
-            E.Chunks _-> E.returnI ignore
diff --git a/test/largest-string.yaml b/test/largest-string.yaml
new file mode 100644
--- /dev/null
+++ b/test/largest-string.yaml
@@ -0,0 +1,6 @@
+this is a long string: but this one is even longer
+this is shorter:
+    - many
+    - tiny
+    - string
+one the other hand: this one is just a little bit bigger than the others
diff --git a/test/main.hs b/test/main.hs
new file mode 100644
--- /dev/null
+++ b/test/main.hs
@@ -0,0 +1,289 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE PackageImports #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+import qualified Text.Libyaml as Y
+import qualified Data.ByteString.Char8 as B8
+
+import Test.HUnit hiding (Test, path)
+
+import qualified Data.Conduit as C
+import qualified Data.Conduit.List as CL
+
+import System.Directory
+import Control.Monad
+import Control.Exception (try, SomeException)
+import Test.Hspec.Monadic
+import Test.Hspec.HUnit ()
+
+import qualified Data.Yaml as D
+import Data.Yaml (object, array)
+import Data.Maybe
+import qualified Data.HashMap.Strict as M
+import qualified Data.Text as T
+
+main :: IO ()
+main = hspecX $ do
+    describe "streaming" $ do
+        it "count scalars with anchor" caseCountScalarsWithAnchor
+        it "count sequences with anchor" caseCountSequencesWithAnchor
+        it "count mappings with anchor" caseCountMappingsWithAnchor
+        it "count aliases" caseCountAliases
+        it "count scalars" caseCountScalars
+        it "largest string" caseLargestString
+        it "encode/decode" caseEncodeDecode
+        it "encode/decode file" caseEncodeDecodeFile
+        it "interleaved encode/decode" caseInterleave
+        it "decode invalid document (without segfault)" caseDecodeInvalidDocument
+    describe "Data.Yaml" $ do
+        it "encode/decode" caseEncodeDecodeData
+        it "encode/decode file" caseEncodeDecodeFileData
+        it "encode/decode strings" caseEncodeDecodeStrings
+        it "decode invalid file" caseDecodeInvalid
+    describe "Data.Yaml aliases" $ do
+        it "simple scalar alias" caseSimpleScalarAlias
+        it "simple sequence alias" caseSimpleSequenceAlias
+        it "simple mapping alias" caseSimpleMappingAlias
+        it "mapping alias before anchor" caseMappingAliasBeforeAnchor
+        it "mapping alias inside anchor" caseMappingAliasInsideAnchor
+        it "scalar alias overriding" caseScalarAliasOverriding
+    describe "Data.Yaml merge keys" $ do
+        it "test uniqueness of keys" caseAllKeysShouldBeUnique
+        it "test mapping merge" caseSimpleMappingMerge
+        it "test sequence of mappings merging" caseMergeSequence
+
+counter :: (Y.Event -> Bool) -> C.Sink Y.Event IO Int
+counter pred' =
+    CL.fold (\cnt e -> (if pred' e then 1 else 0) + cnt) 0
+
+caseHelper :: String
+           -> (Y.Event -> Bool)
+           -> Int
+           -> Assertion
+caseHelper yamlString pred' expRes = do
+    res <- C.runResourceT $ Y.decode (B8.pack yamlString) C.$$ counter pred'
+    res @?= expRes
+
+caseCountScalarsWithAnchor :: Assertion
+caseCountScalarsWithAnchor =
+    caseHelper yamlString isScalarA 1
+  where
+    yamlString = "foo:\n  - &anchor bin1\n  - bin2\n  - bin3"
+    isScalarA (Y.EventScalar _ _ _ (Just _)) = True
+    isScalarA _ = False
+
+caseCountSequencesWithAnchor :: Assertion
+caseCountSequencesWithAnchor =
+    caseHelper yamlString isSequenceStartA 1
+  where
+    yamlString = "foo: &anchor\n  - bin1\n  - bin2\n  - bin3"
+    isSequenceStartA (Y.EventSequenceStart (Just _)) = True
+    isSequenceStartA _ = False
+
+caseCountMappingsWithAnchor :: Assertion
+caseCountMappingsWithAnchor =
+    caseHelper yamlString isMappingA 1
+  where
+    yamlString = "foo: &anchor\n  key1: bin1\n  key2: bin2\n  key3: bin3"
+    isMappingA (Y.EventMappingStart (Just _)) = True
+    isMappingA _ = False
+
+caseCountAliases :: Assertion
+caseCountAliases =
+    caseHelper yamlString isAlias 1
+  where
+    yamlString = "foo: &anchor\n  key1: bin1\n  key2: bin2\n  key3: bin3\nboo: *anchor"
+    isAlias Y.EventAlias{} = True
+    isAlias _ = False
+
+caseCountScalars :: Assertion
+caseCountScalars = do
+    res <- C.runResourceT $ Y.decode yamlBS C.$$ CL.fold adder accum
+    res @?= (7, 1, 2)
+  where
+    yamlString = "foo:\n  baz: [bin1, bin2, bin3]\nbaz: bazval"
+    yamlBS = B8.pack yamlString
+    adder (s, l, m) (Y.EventScalar{})        = (s + 1, l, m)
+    adder (s, l, m) (Y.EventSequenceStart{}) = (s, l + 1, m)
+    adder (s, l, m) (Y.EventMappingStart{})  = (s, l, m + 1)
+    adder a         _                      = a
+    accum = (0, 0, 0) :: (Int, Int, Int)
+
+caseLargestString :: Assertion
+caseLargestString = do
+    res <- C.runResourceT $ Y.decodeFile filePath C.$$ CL.fold adder accum
+    res @?= (length expected, expected)
+    where
+        expected = "this one is just a little bit bigger than the others"
+        filePath = "test/largest-string.yaml"
+        adder (i, s) (Y.EventScalar bs _ _ _) =
+            let s' = B8.unpack bs
+                i' = length s'
+             in if i' > i then (i', s') else (i, s)
+        adder acc _ = acc
+        accum = (0, "no strings found")
+
+newtype MyEvent = MyEvent Y.Event deriving Show
+instance Eq MyEvent where
+    (MyEvent (Y.EventScalar s t _ _)) == (MyEvent (Y.EventScalar s' t' _ _)) =
+        s == s' && t == t'
+    MyEvent e1 == MyEvent e2 = e1 == e2
+
+caseEncodeDecode :: Assertion
+caseEncodeDecode = do
+    eList <- C.runResourceT $ Y.decode yamlBS C.$$ CL.consume
+    bs <- C.runResourceT $ CL.sourceList eList C.$$ Y.encode
+    eList2 <- C.runResourceT $ Y.decode bs C.$$ CL.consume
+    map MyEvent eList @=? map MyEvent eList2
+  where
+    yamlString = "foo: bar\nbaz:\n - bin1\n - bin2\n"
+    yamlBS = B8.pack yamlString
+
+removeFile' :: FilePath -> IO ()
+removeFile' fp = do
+    x <- doesFileExist fp
+    when x $ removeFile fp
+
+caseEncodeDecodeFile :: Assertion
+caseEncodeDecodeFile = do
+    removeFile' tmpPath
+    eList <- C.runResourceT $ Y.decodeFile filePath C.$$ CL.consume
+    C.runResourceT $ CL.sourceList eList C.$$ Y.encodeFile tmpPath
+    eList2 <- C.runResourceT $ Y.decodeFile filePath C.$$ CL.consume
+    map MyEvent eList @=? map MyEvent eList2
+  where
+    filePath = "test/largest-string.yaml"
+    tmpPath = "tmp.yaml"
+
+caseInterleave :: Assertion
+caseInterleave = do
+    removeFile' tmpPath
+    removeFile' tmpPath2
+    () <- C.runResourceT $ Y.decodeFile filePath C.$$ Y.encodeFile tmpPath
+    () <- C.runResourceT $ Y.decodeFile tmpPath C.$$ Y.encodeFile tmpPath2
+    f1 <- readFile tmpPath
+    f2 <- readFile tmpPath2
+    f1 @=? f2
+  where
+    filePath = "test/largest-string.yaml"
+    tmpPath = "tmp.yaml"
+    tmpPath2 = "tmp2.yaml"
+
+caseDecodeInvalidDocument :: Assertion
+caseDecodeInvalidDocument = do
+    x <- try $ C.runResourceT $ Y.decode yamlBS C.$$ CL.sinkNull
+    case x of
+        Left (_ :: SomeException) -> return ()
+        Right y -> do
+            putStrLn $ "bad return value: " ++ show y
+            assertFailure "expected parsing exception, but got no errors"
+  where
+    yamlString = "  - foo\n  - baz\nbuz"
+    yamlBS = B8.pack yamlString
+
+mkScalar :: String -> D.Value
+mkScalar = mkStrScalar
+
+mkStrScalar :: String -> D.Value
+mkStrScalar = D.String . T.pack
+
+mappingKey :: D.Value-> String -> D.Value
+mappingKey (D.Object m) k = (fromJust . M.lookup (T.pack k) $ m)
+mappingKey _ _ = error "expected Object"
+
+decodeYaml :: String -> Maybe D.Value
+decodeYaml s = D.decode $ B8.pack s
+
+sample :: D.Value
+sample = array
+    [ D.String "foo"
+    , object
+        [ ("bar1", D.String "bar2")
+        ]
+    ]
+
+caseEncodeDecodeData :: Assertion
+caseEncodeDecodeData = do
+    let out = D.decode $ D.encode sample
+    out @?= Just sample
+
+caseEncodeDecodeFileData :: Assertion
+caseEncodeDecodeFileData = do
+    let fp = "tmp.yaml"
+    D.encodeFile fp sample
+    out <- D.decodeFile fp
+    out @?= Just sample
+
+caseEncodeDecodeStrings :: Assertion
+caseEncodeDecodeStrings = do
+    let out = D.decode $ D.encode sample
+    out @?= Just sample
+
+caseDecodeInvalid :: Assertion
+caseDecodeInvalid = do
+    let invalid = B8.pack "\tthis is 'not' valid :-)"
+    Nothing @=? (D.decode invalid :: Maybe D.Value)
+
+caseSimpleScalarAlias :: Assertion
+caseSimpleScalarAlias = do
+    let maybeRes = decodeYaml "- &anch foo\n- baz\n- *anch"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    res @?= array [(mkScalar "foo"), (mkScalar "baz"), (mkScalar "foo")]
+
+caseSimpleSequenceAlias :: Assertion
+caseSimpleSequenceAlias = do
+    let maybeRes = decodeYaml "seq: &anch\n  - foo\n  - baz\nseq2: *anch"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    res @?= object [("seq", array [(mkScalar "foo"), (mkScalar "baz")]), ("seq2", array [(mkScalar "foo"), (mkScalar "baz")])]
+
+caseSimpleMappingAlias :: Assertion
+caseSimpleMappingAlias = do
+    let maybeRes = decodeYaml "map: &anch\n  key1: foo\n  key2: baz\nmap2: *anch"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    res @?= object [(T.pack "map", object [("key1", mkScalar "foo"), ("key2", (mkScalar "baz"))]), (T.pack "map2", object [("key1", (mkScalar "foo")), ("key2", mkScalar "baz")])]
+
+caseMappingAliasBeforeAnchor :: Assertion
+caseMappingAliasBeforeAnchor = do
+    let res = decodeYaml "map: *anch\nmap2: &anch\n  key1: foo\n  key2: baz"
+    isNothing res @? "decode should return Nothing due to unknown alias"
+
+caseMappingAliasInsideAnchor :: Assertion
+caseMappingAliasInsideAnchor = do
+    let res = decodeYaml "map: &anch\n  key1: foo\n  key2: *anch"
+    isNothing res @? "decode should return Nothing due to unknown alias"
+
+caseScalarAliasOverriding :: Assertion
+caseScalarAliasOverriding = do
+    let maybeRes = decodeYaml "- &anch foo\n- baz\n- *anch\n- &anch boo\n- buz\n- *anch"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    res @?= array [(mkScalar "foo"), (mkScalar "baz"), (mkScalar "foo"), (mkScalar "boo"), (mkScalar "buz"), (mkScalar "boo")]
+
+caseAllKeysShouldBeUnique :: Assertion
+caseAllKeysShouldBeUnique = do
+    let maybeRes = decodeYaml "foo1: foo\nfoo2: baz\nfoo1: buz"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    mappingKey res "foo1" @?= (mkScalar "buz")
+
+caseSimpleMappingMerge :: Assertion
+caseSimpleMappingMerge = do
+    let maybeRes = decodeYaml "foo1: foo\nfoo2: baz\n<<:\n  foo1: buz\n  foo3: fuz"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    mappingKey res "foo1" @?= (mkScalar "foo")
+    mappingKey res "foo3" @?= (mkScalar "fuz")
+
+caseMergeSequence :: Assertion
+caseMergeSequence = do
+    let maybeRes = decodeYaml "m1: &m1\n  k1: !!str 1\n  k2: !!str 2\nm2: &m2\n  k1: !!str 3\n  k3: !!str 4\nfoo1: foo\n<<: [ *m1, *m2 ]"
+    isJust maybeRes @? "decoder should return Just YamlObject but returned Nothing"
+    let res = fromJust maybeRes
+    mappingKey res "foo1" @?= (mkScalar "foo")
+    mappingKey res "k1" @?= (mkStrScalar "1")
+    mappingKey res "k2" @?= (mkStrScalar "2")
+    mappingKey res "k3" @?= (mkStrScalar "4")
diff --git a/yaml.cabal b/yaml.cabal
--- a/yaml.cabal
+++ b/yaml.cabal
@@ -1,5 +1,5 @@
 name:            yaml
-version:         0.4.1.2
+version:         0.5.0
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>, Anton Ageev <antage@gmail.com>,Kirill Simonov 
@@ -12,35 +12,32 @@
                  don't need to worry about any non-Haskell dependencies.
 category:        Web
 stability:       unstable
-cabal-version:   >= 1.2
+cabal-version:   >= 1.8
 build-type:      Simple
 homepage:        http://github.com/snoyberg/yaml/
 extra-source-files: c/helper.h,
                     libyaml/yaml_private.h,
                     libyaml/yaml.h,
-		    libyaml/LICENSE
+		            libyaml/LICENSE
+                    test/main.hs
+                    test/largest-string.yaml
 
 flag system-libyaml
   description: Use the system-wide libyaml instead of the bundled copy
   default: False
 
-flag nolib
-  description: Skip building the library
-  default: False
-flag buildtests
-  description: Build the executable to run unit tests
-  default: False
-
 library
-    if flag(nolib)
-        Buildable: False
-    else
-        Buildable: True
     build-depends:   base >= 4 && < 5
                    , transformers >= 0.1 && < 0.3
                    , bytestring >= 0.9.1.4 && < 0.10
-                   , enumerator >= 0.4 && < 0.5
+                   , conduit >= 0.0 && < 0.1
+                   , aeson >= 0.5
+                   , containers
+                   , unordered-containers
+                   , vector
+                   , text
     exposed-modules: Text.Libyaml
+                     Data.Yaml
     ghc-options:     -Wall
     c-sources:       c/helper.c
     include-dirs:    c
@@ -57,33 +54,23 @@
                              libyaml/writer.c
             include-dirs:    libyaml
 
-executable           runtests
-    if flag(buildtests)
-        Buildable: True
-        cpp-options:     -DTEST
-        build-depends:   test-framework
-                       , test-framework-hunit
-                       , HUnit
-                       , directory
-                       , base >= 4 && < 5
-                       , transformers >= 0.1 && < 0.3
-                       , bytestring >= 0.9.1.4 && < 0.10
-                       , enumerator >= 0.4 && < 0.5
-    else
-        Buildable: False
+test-suite test
+    type: exitcode-stdio-1.0
+    hs-source-dirs:  test
+    main-is:         main.hs
+    cpp-options:     -DTEST
+    build-depends:   hspec
+                   , HUnit
+                   , directory
+                   , base >= 4 && < 5
+                   , transformers >= 0.1 && < 0.3
+                   , bytestring >= 0.9.1.4 && < 0.10
+                   , conduit
+                   , yaml
+                   , text
+                   , unordered-containers
     ghc-options:     -Wall
     c-sources:       c/helper.c
     include-dirs:    c
-    if flag(system-libyaml)
-            pkgconfig-depends: yaml-0.1
-    else
-            c-sources:       libyaml/api.c,
-                             libyaml/dumper.c,
-                             libyaml/emitter.c,
-                             libyaml/loader.c,
-                             libyaml/parser.c,
-                             libyaml/reader.c,
-                             libyaml/scanner.c,
-                             libyaml/writer.c
-            include-dirs:    libyaml
-    main-is:         runtests.hs
+    pkgconfig-depends: yaml-0.1
+    extra-libraries: yaml
