Захват / угон stdout в Haskell - PullRequest
       12

Захват / угон stdout в Haskell

10 голосов
/ 26 февраля 2012

Как я могу определить 'catchOutput', чтобы основные выходные работали только 'bar'?

То есть как я могу получить доступ как к выходному потоку (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")

Лучшее гипотетическое "решение", которое я нашел до сих пор, включает перенаправление стандартного вывода , вдохновленного этим , в поток файлов и затем чтение из этого файла ( Помимо того, что я очень уродлив Не удалось прочитать сразу после записи из файла. Можно ли создать «пользовательский буферный поток», который не нужно хранить в файле? ). Хотя это немного «похоже на боковую дорожку».

Другой угол, похоже, использует 'hGetContents stdout', если предполагается, что он делает то, что, как я думаю, должен. Но мне не дают разрешения на чтение с stdout. Хотя поиск в Google, кажется, показывает, что он был использован .

Ответы [ 4 ]

4 голосов
/ 21 июня 2012

В Hackage есть несколько пакетов, которые обещают сделать это: io-capture и silent.молча, кажется, поддерживается и работает на Windows тоже (io-capture работает только на Unix).В режиме без вывода сообщений вы используете capture:

import System.IO.Silently

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

. Обратите внимание, что он работает, перенаправляя вывод во временный файл, а затем считывает его ... Но до тех пор, пока он работает!

4 голосов
/ 12 марта 2012

Я использовал следующую функцию для модульного теста функции, которая печатает на стандартный вывод.

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 голосов
/ 26 февраля 2012

Почему бы просто не использовать писательскую монаду? Например,

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")

В качестве альтернативы, вы можете изменить свое внутреннее действие, чтобы записать Handle вместо stdout. Затем вы можете использовать что-то вроде knob , чтобы создать дескриптор файла в памяти, который вы можете передать внутреннему действию, и впоследствии проверить его содержимое.

1 голос
/ 26 февраля 2012

Как отметил @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)
...