diff --git a/library/Potoki/Core/Prelude.hs b/library/Potoki/Core/Prelude.hs
--- a/library/Potoki/Core/Prelude.hs
+++ b/library/Potoki/Core/Prelude.hs
@@ -4,6 +4,7 @@
   ioChunkSize,
   textString,
   unsnoc,
+  mapLeft,
 )
 where
 
@@ -131,3 +132,9 @@
           _ -> case process tailVal of
             (initVal, lastMaybe) -> (headVal : initVal, lastMaybe)
         _ -> ([], Nothing)
+
+{-# INLINE mapLeft #-}
+mapLeft :: (oldLeft -> newLeft) -> Either oldLeft right -> Either newLeft right
+mapLeft f = \ case
+  Left a -> Left (f a)
+  Right b -> Right b
diff --git a/library/Potoki/Core/Transform.hs b/library/Potoki/Core/Transform.hs
--- a/library/Potoki/Core/Transform.hs
+++ b/library/Potoki/Core/Transform.hs
@@ -27,6 +27,7 @@
   -- * Parsing
   A.parseBytes,
   A.parseText,
+  A.parseLineBytesConcurrently,
   -- * Concurrency
   N.bufferize,
   N.concurrently,
diff --git a/library/Potoki/Core/Transform/Attoparsec.hs b/library/Potoki/Core/Transform/Attoparsec.hs
--- a/library/Potoki/Core/Transform/Attoparsec.hs
+++ b/library/Potoki/Core/Transform/Attoparsec.hs
@@ -3,6 +3,8 @@
 
 import Potoki.Core.Prelude hiding (take, takeWhile, filter, drop)
 import Potoki.Core.Types
+import Potoki.Core.Transform.Concurrency
+import Potoki.Core.Transform.ByteString
 import qualified Potoki.Core.Fetch as A
 import qualified Data.Attoparsec.ByteString as K
 import qualified Data.Attoparsec.Text as L
@@ -81,3 +83,13 @@
 parseText :: L.Parser parsed -> Transform Text (Either Text parsed)
 parseText parser =
   mapWithParseResult (L.parse parser)
+
+{-|
+Lift an Attoparsec ByteString parser to a transform,
+which parses the lines concurrently.
+-}
+{-# INLINE parseLineBytesConcurrently #-}
+parseLineBytesConcurrently :: Int -> K.Parser a -> Transform ByteString (Either Text a)
+parseLineBytesConcurrently concurrency parser =
+  extractLines >>> bufferize concurrency >>>
+  concurrently concurrency (arr (mapLeft fromString . K.parseOnly parser))
diff --git a/library/Potoki/Core/Transform/ByteString.hs b/library/Potoki/Core/Transform/ByteString.hs
--- a/library/Potoki/Core/Transform/ByteString.hs
+++ b/library/Potoki/Core/Transform/ByteString.hs
@@ -56,3 +56,29 @@
                         writeIORef stateRef (Just newPoking)
                         return (Just [])
               _ -> return (Just []))
+
+extractLinesWithoutTrail :: Transform ByteString ByteString
+extractLinesWithoutTrail =
+  lineList >>> filter (not . null) >>> list
+  where
+    lineList =
+      Transform $ \ (A.Fetch fetchIO) -> M.Acquire $ do
+        pokingRef <- newIORef mempty
+        return $ (, return ()) $ A.Fetch $ fetchIO >>= \ case
+          Just chunk -> case B.split 10 chunk of
+            head : tail -> do
+              poking <- readIORef pokingRef
+              let newPoking = poking <> C.bytes head
+              case unsnoc tail of
+                Just (init, last) -> do
+                  writeIORef pokingRef (C.bytes last)
+                  return (Just (D.poking newPoking : init))
+                Nothing -> do
+                  writeIORef pokingRef newPoking
+                  return (Just [])
+            _ -> return (Just [])
+          Nothing -> do
+            poking <- readIORef pokingRef
+            if C.null poking
+              then return Nothing
+              else return (Just (D.poking poking : []))
diff --git a/potoki-core.cabal b/potoki-core.cabal
--- a/potoki-core.cabal
+++ b/potoki-core.cabal
@@ -1,7 +1,7 @@
 name:
   potoki-core
 version:
-  2.2.3
+  2.2.4
 synopsis:
   Low-level components of "potoki"
 description:
@@ -17,7 +17,7 @@
 author:
   Nikita Volkov <nikita.y.volkov@mail.ru>
 maintainer:
-  Metrix.AI Ninjas <ninjas@metrix.ai>
+  Metrix.AI Tech Team <tech@metrix.ai>
 copyright:
   (c) 2017, Metrix.AI
 license:
diff --git a/tests/Main.hs b/tests/Main.hs
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -25,6 +25,12 @@
   defaultMain $
   testGroup "All tests" $
   [
+    testCase "extractLines" $ do
+      assertEqual "" ["ab", "", "cd"] =<<
+        C.produceAndConsume (E.transform A.extractLinesWithoutTrail (E.list ["a", "b\n", "\nc", "d\n"])) D.list
+      assertEqual "" ["ab", "", "cd", ""] =<<
+        C.produceAndConsume (E.transform A.extractLines (E.list ["a", "b\n", "\nc", "d\n"])) D.list
+    ,
     testProperty "list to list" $ \ (list :: [Int]) ->
     list === unsafePerformIO (C.produceAndConsume (E.list list) D.list)
     ,
