2012-02-25 6 views
10

Come si può definire 'catchOutput' in modo che le uscite principali in esecuzione siano solo 'bar'?Acquisizione/dirottamento stdout in haskell

Cioè, come posso accedere separatamente al flusso di uscita (stdout) e all'effettiva uscita di un'azione io?

catchOutput :: IO a -> IO (a,String) 
catchOutput = undefined 

doSomethingWithOutput :: IO a -> IO() 
doSomethingWithOutput io = do 
    (_ioOutp, stdOutp) <- catchOutput io 
    if stdOutp == "foo" 
     then putStrLn "bar" 
     else putStrLn "fail!" 

main = doSomethingWithOutput (putStr "foo") 

La migliore "soluzione" ipotetica che ho trovato finora comprende la deviazione standard output, inspired by this, ad un flusso di file e quindi la lettura dal file (Oltre ad essere super-brutto non sono stato in grado di leggere direttamente dopo aver scritto da un file. È possibile creare un "flusso buffer personalizzato" che non deve essere archiviato in un file?). Anche se sembra un po 'come una traccia laterale.

Un altro angolo sembra utilizzare 'hGetContents stdout' se si suppone che faccia ciò che penso dovrebbe. Ma non mi è stato dato il permesso di leggere dallo stdout. Sebbene su Google, sembra mostrare che è stato used.

+0

A seconda del caso di utilizzo e se la portabilità è un problema, è possibile utilizzare questa libreria sperimentale: https://hackage.haskell.org/package/system-posix-redirect-1.1.0.1/docs/System-Posix -Redirect.html –

risposta

4

Perché non utilizzare invece una monade writer? Ad esempio,

import Control.Monad.Writer 

doSomethingWithOutput :: WriterT String IO a -> IO() 
doSomethingWithOutput io = do 
    (_, res) <- runWriterT io 
    if res == "foo" 
     then putStrLn "bar" 
     else putStrLn "fail!" 

main = doSomethingWithOutput (tell "foo") 

In alternativa, è possibile modificare la vostra azione interiore per prendere un Handle di scrivere al posto di stdout. È quindi possibile utilizzare qualcosa come knob per creare un handle di file in memoria che è possibile passare all'azione interna e controllarne il contenuto in seguito.

+0

Il fatto è che non posso davvero fare molto sull'argomento io nel mio codice attuale. Ciò richiederebbe la riprogettazione del comportamento verso l'esterno. Si suppone che l'"applicazione client" invii/passi azioni io (come argomenti). Quindi ad un certo punto o un altro dovrò usare qualcosa come "catchOutput". Quindi la monade dello scrittore non è realmente applicabile al mio problema .. Knob d'altra parte sembra potenzialmente utile. :) – worldsayshi

+1

@worldsayshi Sembra che tu abbia frainteso il suggerimento di hammar. Notate che non ha usato la monade 'Writer', ma una monade' WriterT a IO' - quindi potete ancora chiamare la vostra azione 'IO' usando' liftIO io'. –

+0

Ho fatto una correzione al mio codice, mi dispiace per quello. Probabilmente c'è una certa confusione in questo.Quello che cerco in primo luogo è il testo che termina in stdout. Quello che è _now_ "res", nel mio esempio. Non vedo come liftIO possa darmi accesso a questo. Poi voglio anche che l'output esegua effettivamente anche l'azione io. Questo è attualmente l'output ignorato da "catchOutput". Se ottengo un 'IO()' come argomento sollevandolo mi darà un 'm()'. – worldsayshi

1

Come @hammar sottolineato, è possibile utilizzare una manopola per creare un file in memoria, ma è anche possibile utilizzare hDuplicate e hDuplicateTo cambiare stdout al file di memoria, e viceversa. Qualcosa come il seguente codice di completamente non testato:

catchOutput io = do 
    knob <- newKnob (pack []) 
    let before = do 
     h <- newFileHandle knob "<stdout>" WriteMode 
     stdout' <- hDuplicate stdout 
     hDuplicateTo h stdout 
     hClose h 
     return stdout' 
     after stdout' = do 
     hDuplicateTo stdout' stdout 
     hClose stdout' 
    a <- bracket_ before after io 
    bytes <- Data.Knob.getContents knob 
    return (a, unpack bytes) 
+3

Hmm, ora che ho effettivamente installato 'Data.Knob', non riesco a far funzionare questo metodo. La chiamata a 'hDuplicateTo' fallisce quando si tenta di duplicare l'handle della manopola su' stdout', con il seguente errore: 'Wots: : hDuplicateTo: operazione illegale (le maniglie sono incompatibili)'. – pat

3

ho usato la seguente funzione di una prova di unità di una funzione che consente di stampare su stdout.

import GHC.IO.Handle 
import System.IO 
import System.Directory 

catchOutput :: IO() -> IO String 
catchOutput f = do 
    tmpd <- getTemporaryDirectory 
    (tmpf, tmph) <- openTempFile tmpd "haskell_stdout" 
    stdout_dup <- hDuplicate stdout 
    hDuplicateTo tmph stdout 
    hClose tmph 
    f 
    hDuplicateTo stdout_dup stdout 
    str <- readFile tmpf 
    removeFile tmpf 
    return str 

io non sono sicuro circa l'approccio di file in memoria, ma funziona bene per una piccola quantità di output con un file temporaneo.

4

Ci sono alcuni pacchetti su Hackage che promettono di farlo: io-capture e silenziosamente. silenziosamente sembra essere mantenuto e funziona anche su Windows (io-capture funziona solo su Unix). Con in silenzio, si utilizza la cattura:

import System.IO.Silently 

main = do 
    (output, _) <- capture $ putStr "hello" 
    putStrLn $ output ++ " world" 

Nota che funziona reindirizzando l'output in un file temporaneo e poi leggerlo ... Ma finché funziona!