Уловка/захват stdout в haskell
Как я могу определить "catchOutput", так что запуск основных выходов только "бар"?
То есть, как я могу получить доступ как к выходному потоку (stdout), так и к фактическому выходу отдельного действия 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")
Лучшее гипотетическое "решение", которое я нашел до сих пор, включает отключение stdout, вдохновленный этим, в поток файлов, а затем чтение из этого файла (помимо того, что супер-уродливый Я не смог прочитать сразу после записи из файла. Можно ли создать "настраиваемый поток буфера", который не нужно хранить в файле?). Хотя это немного похоже на боковую дорожку.
Другой угол, похоже, использует "hGetContents stdout", если он должен делать то, что, как я думаю, должен. Но мне не дано разрешение на чтение из stdout. Хотя googling, похоже, показывает, что он использовался .
Ответы
Ответ 1
Почему бы просто не использовать монаду-писателю? Например,
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")
В качестве альтернативы вы можете изменить свое внутреннее действие, чтобы вместо stdout
записать Handle
вместо записи. Затем вы можете использовать что-то вроде knob, чтобы создать дескриптор файла в памяти, который вы можете передать во внутреннее действие, и затем проверить его содержимое.
Ответ 2
В Hackage есть несколько пакетов, которые обещают сделать это: io-capture и молча. тихо поддерживается и работает на Windows тоже (io-capture работает только в Unix). С молчанием вы используете захват:
import System.IO.Silently
main = do
(output, _) <- capture $ putStr "hello"
putStrLn $ output ++ " world"
Обратите внимание, что он работает, перенаправляя вывод во временный файл, а затем читайте его... Но до тех пор, пока он работает!
Ответ 3
Я использовал следующую функцию для unit test функции, которая выводит на стандартный вывод.
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
Я не уверен в подходе к файлу в памяти, но он работает нормально для небольшого объема вывода с временным файлом.
Ответ 4
Как отметил @hammar, вы можете использовать ручку для создания файла в памяти, но вы также можете использовать hDuplicate
и hDuplicateTo
для изменения stdout
в файл памяти и обратно. Что-то вроде следующего полностью непроверенного кода:
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)