使用 Conduit 从 ByteString 过滤 ANSI 转义序列
Filtering ANSI escape sequences from a ByteString with Conduit
我正在尝试制作一个从 ByteString 中过滤 ANSI 转义码的管道。我想出了一个将 ByteString 转换为 Word8 流、进行过滤并在最后转换回 ByteStream 流的函数。
当我在 GHCi 中使用它时似乎工作正常:
> runConduit $ yield "hello[23;1m world" .| ansiFilter .| printC
"hello world"
当我在我的应用程序中使用它时,包含 ansiFilter
的管道似乎没有通过任何东西。这是完整的来源:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Conduit
import Control.Concurrent.Async
import Control.Concurrent.STM
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Conduit.TQueue
import Data.Word8 (Word8)
import qualified Data.Word8 as Word8
main :: IO ()
main = do
queue <- atomically $ newTBQueue 25
let qSource = sourceTBQueue queue
atomically $ writeTBQueue queue ("hello" :: ByteString)
race_
(putInputIntoQueue queue)
(doConversionAndPrint qSource)
putInputIntoQueue q =
runConduit
$ stdinC
.| iterMC (atomically . writeTBQueue q)
.| sinkNull
doConversionAndPrint src =
runConduit
$ src
.| ansiFilter
.| stdoutC
ansiFilter :: MonadIO m => ConduitM ByteString ByteString m ()
ansiFilter = toWord8 .| ansiFilter' .| toByteString
where
ansiFilter' = awaitForever $ \first -> do
msecond <- peekC
case (first, msecond) of
(0x1b, Just 0x5b) -> do
dropWhileC (not . Word8.isLetter)
dropC 1
_ -> yield first
toWord8 = concatC
toByteString :: Monad m => ConduitM Word8 ByteString m ()
toByteString =
(mapC BS.singleton .| foldC) >>= yield
该程序应该回显 stdin
的过滤内容,但没有任何回显。
但是,如果我在 doConversionAndPrint
中注释掉 ansiFilter
,回显确实有效,这让我认为 ansiFilter
函数是错误的。
如有任何帮助,我们将不胜感激!
采用了一种略有不同的方法,我更幸运地保留了 ByteStrings。我认为这放弃了一些流媒体内容,但对于我的用例来说是可以接受的。
ansiFilter :: Monad m => Conduit ByteString m ByteString
ansiFilter = mapC (go "")
where
csi = "["
go acc "" = acc
go acc remaining = go (acc <> filtered) (stripCode unfiltered)
where
(filtered, unfiltered) = BS.breakSubstring csi remaining
stripCode bs = BS.drop 1 (BS.dropWhile (not . Word8.isLetter) bs)
我根据管道组合器中更高级别的分块数据函数重新实现了 ansiFilter
,例如 takeWhileCE
。这似乎可行,并且应该通过让更多数据保留在有效的内存表示中来提高效率:
ansiFilter :: MonadIO m => ConduitM ByteString ByteString m ()
ansiFilter = loop
where
loop = do
takeWhileCE (/= 0x1b)
mfirst <- headCE
case mfirst of
Nothing -> return ()
Just first -> assert (first == 0x1b) $ do
msecond <- peekCE
case msecond of
Just 0x5b -> do
dropWhileCE (not . Word8.isLetter)
dropCE 1
_ -> yield $ BS.singleton first
loop
我正在尝试制作一个从 ByteString 中过滤 ANSI 转义码的管道。我想出了一个将 ByteString 转换为 Word8 流、进行过滤并在最后转换回 ByteStream 流的函数。
当我在 GHCi 中使用它时似乎工作正常:
> runConduit $ yield "hello[23;1m world" .| ansiFilter .| printC
"hello world"
当我在我的应用程序中使用它时,包含 ansiFilter
的管道似乎没有通过任何东西。这是完整的来源:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Conduit
import Control.Concurrent.Async
import Control.Concurrent.STM
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Conduit.TQueue
import Data.Word8 (Word8)
import qualified Data.Word8 as Word8
main :: IO ()
main = do
queue <- atomically $ newTBQueue 25
let qSource = sourceTBQueue queue
atomically $ writeTBQueue queue ("hello" :: ByteString)
race_
(putInputIntoQueue queue)
(doConversionAndPrint qSource)
putInputIntoQueue q =
runConduit
$ stdinC
.| iterMC (atomically . writeTBQueue q)
.| sinkNull
doConversionAndPrint src =
runConduit
$ src
.| ansiFilter
.| stdoutC
ansiFilter :: MonadIO m => ConduitM ByteString ByteString m ()
ansiFilter = toWord8 .| ansiFilter' .| toByteString
where
ansiFilter' = awaitForever $ \first -> do
msecond <- peekC
case (first, msecond) of
(0x1b, Just 0x5b) -> do
dropWhileC (not . Word8.isLetter)
dropC 1
_ -> yield first
toWord8 = concatC
toByteString :: Monad m => ConduitM Word8 ByteString m ()
toByteString =
(mapC BS.singleton .| foldC) >>= yield
该程序应该回显 stdin
的过滤内容,但没有任何回显。
但是,如果我在 doConversionAndPrint
中注释掉 ansiFilter
,回显确实有效,这让我认为 ansiFilter
函数是错误的。
如有任何帮助,我们将不胜感激!
采用了一种略有不同的方法,我更幸运地保留了 ByteStrings。我认为这放弃了一些流媒体内容,但对于我的用例来说是可以接受的。
ansiFilter :: Monad m => Conduit ByteString m ByteString
ansiFilter = mapC (go "")
where
csi = "["
go acc "" = acc
go acc remaining = go (acc <> filtered) (stripCode unfiltered)
where
(filtered, unfiltered) = BS.breakSubstring csi remaining
stripCode bs = BS.drop 1 (BS.dropWhile (not . Word8.isLetter) bs)
我根据管道组合器中更高级别的分块数据函数重新实现了 ansiFilter
,例如 takeWhileCE
。这似乎可行,并且应该通过让更多数据保留在有效的内存表示中来提高效率:
ansiFilter :: MonadIO m => ConduitM ByteString ByteString m ()
ansiFilter = loop
where
loop = do
takeWhileCE (/= 0x1b)
mfirst <- headCE
case mfirst of
Nothing -> return ()
Just first -> assert (first == 0x1b) $ do
msecond <- peekCE
case msecond of
Just 0x5b -> do
dropWhileCE (not . Word8.isLetter)
dropCE 1
_ -> yield $ BS.singleton first
loop