packages feed

silently 1.2.4.1 → 1.2.5.4

raw patch · 5 files changed

Files

+ CHANGELOG.md view
@@ -0,0 +1,22 @@+# 1.2.5.4 November 2024++* Bump `cabal-version` to 1.18.+* README: alert of malfunction in multi-threaded settings.+* Tested with GHC 8.0 - 9.12.0.++# 1.2.5.3 August 2022++* Tested with GHC 7.0 - 9.4.1.+* Remove remnants of GHC 6.x support.+* Silence incomplete pattern matching warning, refactor code.+* Add section about limitations to README.++# 1.2.5.2 November 2021++* Tested with GHC 7.0 - 9.2.+* Silence warning caused by missing `other-modules` in cabal file.+* Add README and CHANGELOG to dist.++# 1.2.5.1 July 2019++No changelog for this and earlier versions.
+ README.md view
@@ -0,0 +1,51 @@+[![Hackage version](https://img.shields.io/hackage/v/silently.svg?label=Hackage&color=informational)](http://hackage.haskell.org/package/silently)+[![silently on Stackage Nightly](https://stackage.org/package/silently/badge/nightly)](https://stackage.org/nightly/package/silently)+[![Stackage LTS version](https://www.stackage.org/package/silently/badge/lts?label=Stackage)](https://www.stackage.org/package/silently)+[![Cabal build](https://github.com/hspec/silently/workflows/Haskell-CI/badge.svg)](https://github.com/hspec/silently/actions)++# silently++Silently is a package that allows you to run an `IO` action and+prevent it from writing to `stdout`, or any other handle, by using+`silence`. Or you can capture the output for yourself using `capture`.++For example, the program+```haskell+ import System.IO.Silently++ main = do+   putStr "putStrLn: " >> putStrLn "puppies!"+   putStr "silenced: " >> silence (putStrLn "kittens!")+   putStrLn ""+   (captured, result) <- capture (putStr "wookies!" >> return 123)+   putStr "captured: " >> putStrLn captured+   putStr "returned: " >> putStrLn (show result)+```+will print:+```+ putStrLn: puppies!+ silenced:+ captured: wookies!+ returned: 123+```++## Not thread-safe++Since all threads of a process share the standard output handles `stdout` and `stderr`,+capturing output to these handle will capture the output of __all threads__,+not just the one running the action under `capture`.++In essence, this library does not work in a situation where multiple threads are writing to the handle+whose output _produced by the given action_ we want to capture.++See:+-  https://github.com/hspec/silently/issues/6++## Further limitations++Capturing/silencing might not work as expected if the action uses the FFI+or conceals output under `unsafePerformIO` or similar unsafe operations.++Examples:+- FFI: https://github.com/hspec/silently/issues/3+- `unsafePerformIO`: https://github.com/bos/filemanip/issues/22
silently.cabal view
@@ -1,40 +1,66 @@+cabal-version: 1.18 name: silently-version: 1.2.4.1-cabal-version: >= 1.8+version: 1.2.5.4 build-type: Simple license: BSD3 license-file: LICENSE+author: Trystan Spangler copyright: (c) Trystan Spangler 2011-maintainer: trystan.s@comcast.net-stability:-homepage: https://github.com/trystan/silently-package-url: https://github.com/trystan/silently-bug-reports: https://github.com/trystan/silently/issues+maintainer: Simon Hengel <sol@typeful.net>, Andreas Abel+homepage: https://github.com/hspec/silently+package-url: https://github.com/hspec/silently+bug-reports: https://github.com/hspec/silently/issues synopsis: Prevent or capture writing to stdout and other handles.-description: Prevent or capture writing to stdout and other handles.-category:-author: Trystan Spangler-tested-with: GHC ==7.0+description: Prevent or capture writing to stdout, stderr, and other handles.+category: System, Testing +tested-with:+  GHC == 9.12.0+  GHC == 9.10.1+  GHC == 9.8.3+  GHC == 9.6.6+  GHC == 9.4.8+  GHC == 9.2.8+  GHC == 9.0.2+  GHC == 8.10.7+  GHC == 8.8.4+  GHC == 8.6.5+  GHC == 8.4.4+  GHC == 8.2.2+  GHC == 8.0.2++extra-doc-files:+  CHANGELOG.md+  README.md+ source-repository head   type: git-  location: https://github.com/trystan/silently+  location: https://github.com/hspec/silently  Library+  hs-source-dirs:+      src+  default-language:+      Haskell98+  exposed-modules:+      System.IO.Silently+   build-depends:-      base >=4 && <=5+      base >= 4.3 && < 5     , directory     , deepseq-  exposed-modules: System.IO.Silently -  hs-source-dirs:-      src-   if os(windows)     cpp-options: -DWINDOWS   if os(linux) || os(osx) || os(freebsd) || os(openbsd) || os(netbsd)     cpp-options: -DUNIX +  ghc-options:+    -Wall+  if impl(ghc >= 8)+    ghc-options:+      -Wcompat+ -- This tests the platform specific implementation. -- -- NOTE: Cabal 1.10 can not deal with conditional (== if-else) options.  This@@ -43,36 +69,42 @@ -- As a consequence we can not use Hspec for testing, as this would result in -- depending on two different versions of silently at the same time! test-suite spec-specific-  main-is:-      Spec.hs   type:       exitcode-stdio-1.0-  ghc-options:-      -Wall -Werror -threaded   hs-source-dirs:       test+  main-is:+      Spec.hs+  default-language:+      Haskell98+  ghc-options:+      -Wall -threaded   build-depends:-      base+      base  >= 4.3 && < 5     , silently     , directory     , nanospec+    , temporary  -- This tests the generic implementation, that should work on all platforms. test-suite spec-generic-  main-is:-      Spec.hs   type:       exitcode-stdio-1.0-  ghc-options:-      -Wall -threaded-      -- FIXME: use -Werror-      -- -Wall -Werror -threaded   hs-source-dirs:       src     , test+  main-is:+      Spec.hs+  default-language:+      Haskell98+  ghc-options:+      -Wall -threaded+  other-modules:+      System.IO.Silently    build-depends:-      base+      base  >= 4.3 && < 5     , deepseq     , directory     , nanospec+    , temporary
src/System/IO/Silently.hs view
@@ -1,32 +1,35 @@ {-# LANGUAGE CPP #-}--- | Need to prevent output to the terminal, a file, or stderr? Need to capture it and use it for--- your own means? Now you can, with 'silence' and 'capture'.+{-# LANGUAGE ScopedTypeVariables #-} -module System.IO.Silently (-  silence,-  hSilence,-  capture,-  capture_,-  hCapture,-  hCapture_,-) where+-- | Need to prevent output to the terminal, a file, or stderr?+--   Need to capture it and use it for your own means?+--   Now you can, with 'silence' and 'capture'. -import Prelude+module System.IO.Silently+  ( silence, hSilence+  , capture, capture_, hCapture, hCapture_+  ) where -#if __GLASGOW_HASKELL__ >= 612-import GHC.IO.Handle (hDuplicate, hDuplicateTo)-#else-import GHC.Handle (hDuplicate, hDuplicateTo)-#endif+import Prelude -import System.IO import qualified Control.Exception as E import Control.DeepSeq-import System.Directory (removeFile,getTemporaryDirectory)+  ( deepseq ) +import GHC.IO.Handle+  ( hDuplicate, hDuplicateTo )++import System.Directory+  ( getTemporaryDirectory, removeFile )+import System.IO+  ( Handle, IOMode(AppendMode), SeekMode(AbsoluteSeek)+  , hClose, hFlush, hGetBuffering, hGetContents, hSeek, hSetBuffering+  , openFile, openTempFile, stdout+  )+ mNullDevice :: Maybe FilePath #ifdef WINDOWS-mNullDevice = Just "NUL"+mNullDevice = Just "\\\\.\\NUL" #elif UNIX mNullDevice = Just "/dev/null" #else@@ -38,38 +41,46 @@ silence = hSilence [stdout]  -- | Run an IO action while preventing all output to the given handles.-hSilence :: [Handle] -> IO a -> IO a-hSilence handles action = case mNullDevice of-  Just nullDevice -> E.bracket (openFile nullDevice AppendMode)-                             hClose-                             prepareAndRun+hSilence :: forall a. [Handle] -> IO a -> IO a+hSilence handles action =+  case mNullDevice of+    Just nullDevice ->+      E.bracket (openFile nullDevice AppendMode)+                hClose+                prepareAndRun+    Nothing -> withTempFile "silence" prepareAndRun+  where+    prepareAndRun :: Handle -> IO a+    prepareAndRun tmpHandle = go handles+      where+        go []     = action+        go (h:hs) = goBracket go tmpHandle h hs -  Nothing -> do-    tmpDir <- getTempOrCurrentDirectory-    E.bracket (openTempFile tmpDir "silence")-                               cleanup-                               (prepareAndRun . snd) - where-  cleanup (tmpFile,tmpHandle) = do-    hClose tmpHandle-    removeFile tmpFile-  prepareAndRun tmpHandle = go handles-    where-      go [] = action-      go hs = goBracket go tmpHandle hs-+-- Provide a tempfile for the given action and remove it afterwards.+withTempFile :: String -> (Handle -> IO a) -> IO a+withTempFile tmpName action = do+  tmpDir <- getTempOrCurrentDirectory+  E.bracket (openTempFile tmpDir tmpName)+            cleanup+            (action . snd)+  where+    cleanup :: (FilePath, Handle) -> IO ()+    cleanup (tmpFile, tmpHandle) = do+      hClose tmpHandle+      removeFile tmpFile  getTempOrCurrentDirectory :: IO String getTempOrCurrentDirectory = getTemporaryDirectory `catchIOError` (\_ -> return ".")   where     -- NOTE: We can not use `catchIOError` from "System.IO.Error", it is only-    -- availabel in base >= 4.4.+    -- available in base >= 4.4.     catchIOError :: IO a -> (IOError -> IO a) -> IO a     catchIOError = E.catch  -- | Run an IO action while preventing and capturing all output to stdout.--- This will, as a side effect, create and delete a temp file in the temp directory or current directory if there is no temp directory.+-- This will, as a side effect, create and delete a temp file in the temp directory+-- or current directory if there is no temp directory. capture :: IO a -> IO (String, a) capture = hCapture [stdout] @@ -82,30 +93,31 @@ hCapture_ handles = fmap fst . hCapture handles  -- | Run an IO action while preventing and capturing all output to the given handles.--- This will, as a side effect, create and delete a temp file in the temp directory or current directory if there is no temp directory.-hCapture :: [Handle] -> IO a -> IO (String, a)-hCapture handles action = do-  tmpDir <- getTempOrCurrentDirectory-  E.bracket (openTempFile tmpDir "capture")-                             cleanup-                             (prepareAndRun . snd)- where-  cleanup (tmpFile,tmpHandle) = do-    hClose tmpHandle-    removeFile tmpFile-  prepareAndRun tmpHandle = go handles-    where-      go [] = do-              a <- action-              mapM_ hFlush handles-              hSeek tmpHandle AbsoluteSeek 0-              str <- hGetContents tmpHandle-              str `deepseq` return (str,a)-      go hs = goBracket go tmpHandle hs+-- This will, as a side effect, create and delete a temp file in the temp directory+-- or current directory if there is no temp directory.+hCapture :: forall a. [Handle] -> IO a -> IO (String, a)+hCapture handles action = withTempFile "capture" prepareAndRun+  where+    prepareAndRun :: Handle -> IO (String, a)+    prepareAndRun tmpHandle = go handles+      where+        go [] = do+          a <- action+          mapM_ hFlush handles+          hSeek tmpHandle AbsoluteSeek 0+          str <- hGetContents tmpHandle+          str `deepseq` return (str, a)+        go (h:hs) = goBracket go tmpHandle h hs -goBracket :: ([Handle] -> IO a) -> Handle -> [Handle] -> IO a-goBracket go tmpHandle (h:hs) = E.bracket (do old <- hDuplicate h-                                              hDuplicateTo tmpHandle h-                                              return old)-                                        (\old -> hDuplicateTo old h >> hClose old)-                                        (\_   -> go hs)+goBracket :: ([Handle] -> IO a) -> Handle -> Handle -> [Handle] -> IO a+goBracket go tmpHandle h hs = do+  buffering <- hGetBuffering h+  let redirect = do+        old <- hDuplicate h+        hDuplicateTo tmpHandle h+        return old+      restore old = do+        hDuplicateTo old h+        hSetBuffering h buffering+        hClose old+  E.bracket redirect restore (\_ -> go hs)
test/Spec.hs view
@@ -4,8 +4,10 @@ import           System.IO import           System.IO.Silently import           System.Directory+import           System.IO.Temp  import           Control.Exception+import           Control.Monad  main :: IO () main = hspec spec@@ -26,3 +28,11 @@   describe "capture" $ do     it "captures stdout" $ do       capture (putStr "foo" >> return 23) `shouldReturn` ("foo", 23 :: Int)++  describe "hCapture" $ do+    forM_ [NoBuffering, LineBuffering, BlockBuffering Nothing] $ \buffering -> do+      it ("preserves " ++ show buffering) $ do+        withSystemTempFile "silently" $ \_file h -> do+          hSetBuffering h buffering+          _ <- hCapture [h] (return ())+          hGetBuffering h `shouldReturn` buffering