2016-12-08 2 views
5

Ich versuche, ein Conduit zu erstellen, das ANSI-Escape-Codes von ByteStrings filtert. Ich habe eine Funktion entwickelt, die den ByteString in einen Stream von Word8 konvertiert, filtert und am Ende wieder in einen Stream von ByteStream konvertiert.Filtern von ANSI-Escape-Sequenzen von einem ByteString mit Conduit

Es scheint gut zu funktionieren, wenn ich es in GHCi verwenden:

> runConduit $ yield "hello\27[23;1m world" .| ansiFilter .| printC 
"hello world" 

Als ich es in meiner Anwendung verwenden, Leitungen, die ansiFilter enthalten scheinen nicht durch irgendetwas zu passieren. Hier ist die vollständige Quelle:

{-# 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 

Dieses Programm soll die gefilterten Inhalte stdin Echo zurück, aber nichts wird wieder wiederholt.

Allerdings, wenn ich die ansiFilter in doConversionAndPrint kommentieren, funktioniert Echo, was mich die ansiFilter Funktion falsch macht.

Jede Hilfe würde sehr geschätzt werden!

+1

Bitte fügen Sie die Importe Sie den Code verwenden, damit wir es ohne weiteres ausgeführt werden kann. – duplode

+2

Ist 'toByteString' nicht der ganze Stream von' Word8' in einen einzigen string Bytestring? Wenn dem so ist, scheint 'ansiFilter' niemals fertig zu werden. – Michael

+2

Zum Beispiel wenn ich nur 'toByteString = mapC BS.singleton' schreibe, ist es produktiv, obwohl die Interferenz von stdin und stdout ein wenig schwer zu folgen ist. – Michael

Antwort

2

I reimplementiert ansiFilter im Hinblick auf die höhere Ebene chunked Datenfunktionen in der Leitung-Kombinator, wie takeWhileCE. Dies scheint zu funktionieren, und sollte effizienter sein, um mehr der Daten bleiben in einer effizienten Speicherrepräsentation lassen:

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 
1

Ging mit einem etwas anderen Ansatz und habe mehr Glück, die ByteStrings in Ruhe zu lassen. Ich denke, das gibt einiges vom Streaming-Zeug auf, ist aber für meinen Anwendungsfall akzeptabel.

ansiFilter :: Monad m => Conduit ByteString m ByteString 
ansiFilter = mapC (go "") 
    where 
    csi = "\27[" 
    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)