packages feed

turtle 1.2.8 → 1.3.0

raw patch · 10 files changed

+1326/−199 lines, 10 filesdep +ansi-wl-pprintdep +bytestringdep +unix-compatdep ~basedep ~criteriondep ~optparse-applicative

Dependencies added: ansi-wl-pprint, bytestring, unix-compat

Dependency ranges changed: base, criterion, optparse-applicative

Files

+ CHANGELOG.md view
@@ -0,0 +1,98 @@+1.3++* BREAKING CHANGE: Several utilities now produce and consume `Line`s instead of+  `Text`+    * The purpose of this change is to fix a very common source of confusion for+      new users about which utilities are line-aware+    * Most of the impact on existing code is just changing the types by+      replacing `Text` with `Line` in the right places.  The change at the+      term level should be small (based on the changes to the tutorial examples)+* BREAKING CHANGE: `Description` now wraps a `Doc` instead of `Text`+    * In the most common case where users use string literals this has no effect+* New `Turtle.Bytes` module that provides `ByteString` variations on subprocess+  runners+* Fix `du` reporting incorrect sizes for directories+* Add `pushd`, `stat`, `lstat`, `which`, `procStrictWithErr`,+  `shellStrictWithErr`, `onFiles`, `header`, `subcommandGroup`, and `parallel`+* Backport `need` to GHC 7.6.3+* Fix missing help text for option parsers+* Fix bugs in subprocess management++1.2.8++* Increase upper bound on `time` and `transformers`+* Fix incorrect lower bound for `base`++1.2.7++* Increase upper bound on `clock` dependency++1.2.6++* Generalize several types to use `MonadManaged`+* Generalize type of `printf` to use `MonadIO`+* Add `system`, and `copymod`+* Fix `rmtree` to more accurately match behavior of `rm -r`++1.2.5++* Add `printf`, `utc`, `procs`, and `shells`++1.2.4++* Generalize type of `d` format specifier to format any `Integral` type+* Add `inprocWithErr`, `inShellWithErr`, `inplace`, and `sz`++1.2.3++* Add `subcommand` and `testpath`+* Use line buffering for `Text`-based subprocesses++1.2.2++* Re-export `with`+* Add `begins`, `ends`, `contains`, `lowerBounded`, `mktempfile`, `nl`, `paste`+  `endless`, `lsif`, and `cut`+* Fix subprocess management bugs++1.2.1++* Fix subprocess management bugs++1.2.0++* BREAKING CHANGE: `du` now returns a `Size` instead of an `Integer`+* New `Turtle.Options` module that provides convenient utilities for options+  parsing+* Add `hostname`, `outhandle`, `stderr`, `cache`, `countChars`, `countWords`,+  and `countLines`+* Fix subprocess management bugs++1.1.1++* Add `bounded`, `upperBounded`, `procStrict`, `shellStrict`, `arguments`+* Add several `Permissions`-related commands+* Generalize several types to `MonadIO`++1.1.0++* BREAKING CHANGE: Remove `Floating`/`Fractional` instances for `Pattern` and+  `Shell`+* BREAKING CHANGE: Change behavior of `Num` instance for `Pattern` and `Shell`+* Re-export `(&)`+* Add `asciiCI`, `(.||.)`, `(.&&.)`, `strict`++1.0.2++* Add `fp` format specifier+* Add `chars`/`chars` high-efficiency parsing primitives+* Fix bugs in path handling++1.0.1++* Generalize type of `die`+* Fix doctest++1.0.0++* Initial release
src/Turtle.hs view
@@ -70,6 +70,7 @@     , module Turtle.Pattern     , module Turtle.Options     , module Turtle.Shell+    , module Turtle.Line     , module Turtle.Prelude     , module Control.Applicative     , module Control.Monad@@ -92,6 +93,7 @@ import Turtle.Pattern import Turtle.Options import Turtle.Shell+import Turtle.Line import Turtle.Prelude import Control.Applicative     ( Applicative(..)@@ -148,7 +150,7 @@ import System.Exit (ExitCode(..)) import Prelude hiding (FilePath) -#if MIN_VERSION_base(4,8,0)+#if __GLASGOW_HASKELL__ >= 710 import Data.Function ((&)) #else infixl 1 &
+ src/Turtle/Bytes.hs view
@@ -0,0 +1,609 @@+{-# LANGUAGE RankNTypes #-}++{-| This module provides `ByteString` analogs of several utilities in+    "Turtle.Prelude".  The main difference is that the chunks of bytes read by+    these utilities are not necessarily aligned to line boundaries.+-}++module Turtle.Bytes (+    -- * Byte operations+      stdin+    , input+    , inhandle+    , stdout+    , output+    , outhandle+    , append+    , stderr+    , strict++    -- * Subprocess management+    , proc+    , shell+    , system+    , procs+    , shells+    , inproc+    , inshell+    , inprocWithErr+    , inshellWithErr+    , procStrict+    , shellStrict+    , procStrictWithErr+    , shellStrictWithErr+    ) where++import Control.Applicative+import Control.Concurrent.Async (Async, Concurrently(..))+import Control.Foldl (FoldM(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Managed (MonadManaged(..))+import Data.ByteString (ByteString)+import Data.Text (Text)+import Filesystem.Path (FilePath)+import Prelude hiding (FilePath)+import System.Exit (ExitCode(..))+import System.IO (Handle)+import Turtle.Internal (ignoreSIGPIPE)+import Turtle.Prelude (ProcFailed(..), ShellFailed(..))+import Turtle.Shell (Shell(..), fold, sh)++import qualified Control.Concurrent.Async      as Async+import qualified Control.Concurrent.STM        as STM+import qualified Control.Concurrent.MVar       as MVar+import qualified Control.Concurrent.STM.TQueue as TQueue+import qualified Control.Exception             as Exception+import qualified Control.Foldl+import qualified Control.Monad+import qualified Control.Monad.Managed         as Managed+import qualified Data.ByteString+import qualified Data.Text+import qualified Foreign+import qualified System.IO+import qualified System.Process                as Process+import qualified Turtle.Prelude++{-| Read chunks of bytes from standard input++    The chunks are not necessarily aligned to line boundaries+-}+stdin :: Shell ByteString+stdin = inhandle System.IO.stdin++{-| Read chunks of bytes from a file++    The chunks are not necessarily aligned to line boundaries+-}+input :: FilePath -> Shell ByteString+input file = do+    handle <- using (Turtle.Prelude.readonly file)+    inhandle handle++{-| Read chunks of bytes from a `Handle`++    The chunks are not necessarily aligned to line boundaries+-}+inhandle :: Handle -> Shell ByteString+inhandle handle = Shell (\(FoldM step begin done) -> do+    x0 <- begin+    let loop x = do+            eof <- System.IO.hIsEOF handle+            if eof+                then done x+                else do+                    bytes <- Data.ByteString.hGetSome handle defaultChunkSize+                    x'    <- step x bytes+                    loop $! x'+    loop $! x0 )+  where+    -- Copied from `Data.ByteString.Lazy.Internal`+    defaultChunkSize :: Int+    defaultChunkSize = 32 * 1024 - 2 * Foreign.sizeOf (undefined :: Int)++{-| Stream chunks of bytes to standard output++    The chunks are not necessarily aligned to line boundaries+-}+stdout :: MonadIO io => Shell ByteString -> io ()+stdout s = sh (do+    bytes <- s+    liftIO (Data.ByteString.hPut System.IO.stdout bytes) )++{-| Stream chunks of bytes to a file++    The chunks do not need to be aligned to line boundaries+-}+output :: MonadIO io => FilePath -> Shell ByteString -> io ()+output file s = sh (do+    handle <- using (Turtle.Prelude.writeonly file)+    bytes  <- s+    liftIO (Data.ByteString.hPut handle bytes) )++{-| Stream chunks of bytes to a `Handle`++    The chunks do not need to be aligned to line boundaries+-}+outhandle :: MonadIO io => Handle -> Shell ByteString -> io ()+outhandle handle s = sh (do+    bytes <- s+    liftIO (Data.ByteString.hPut handle bytes) )++{-| Append chunks of bytes to append to a file++    The chunks do not need to be aligned to line boundaries+-}+append :: MonadIO io => FilePath -> Shell ByteString -> io ()+append file s = sh (do+    handle <- using (Turtle.Prelude.appendonly file)+    bytes  <- s+    liftIO (Data.ByteString.hPut handle bytes) )++{-| Stream chunks of bytes to standard error++    The chunks do not need to be aligned to line boundaries+-}+stderr :: MonadIO io => Shell ByteString -> io ()+stderr s = sh (do+    bytes <- s+    liftIO (Data.ByteString.hPut System.IO.stderr bytes) )++-- | Read in a stream's contents strictly+strict :: MonadIO io => Shell ByteString -> io ByteString+strict s = do+    listOfByteStrings <- fold s Control.Foldl.list+    return (Data.ByteString.concat listOfByteStrings)++{-| Run a command using @execvp@, retrieving the exit code++    The command inherits @stdout@ and @stderr@ for the current process+-}+proc+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io ExitCode+    -- ^ Exit code+proc cmd args =+    system+        ( (Process.proc (Data.Text.unpack cmd) (map Data.Text.unpack args))+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.Inherit+            , Process.std_err = Process.Inherit+            } )++{-| Run a command line using the shell, retrieving the exit code++    This command is more powerful than `proc`, but highly vulnerable to code+    injection if you template the command line with untrusted input++    The command inherits @stdout@ and @stderr@ for the current process+-}+shell+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io ExitCode+    -- ^ Exit code+shell cmdline =+    system+        ( (Process.shell (Data.Text.unpack cmdline))+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.Inherit+            , Process.std_err = Process.Inherit+            } )++{-| This function is identical to `proc` except this throws `ProcFailed` for+    non-zero exit codes+-}+procs+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io ()+procs cmd args s = do+    exitCode <- proc cmd args s+    case exitCode of+        ExitSuccess -> return ()+        _           -> liftIO (Exception.throwIO (ProcFailed cmd args exitCode))++{-| This function is identical to `shell` except this throws `ShellFailed` for+    non-zero exit codes+-}+shells+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io ()+    -- ^ Exit code+shells cmdline s = do+    exitCode <- shell cmdline s+    case exitCode of+        ExitSuccess -> return ()+        _           -> liftIO (Exception.throwIO (ShellFailed cmdline exitCode))++{-| Run a command using @execvp@, retrieving the exit code and stdout as a+    non-lazy blob of Text++    The command inherits @stderr@ for the current process+-}+procStrict+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString)+    -- ^ Exit code and stdout+procStrict cmd args =+    systemStrict (Process.proc (Data.Text.unpack cmd) (map Data.Text.unpack args))++{-| Run a command line using the shell, retrieving the exit code and stdout as a+    non-lazy blob of Text++    This command is more powerful than `proc`, but highly vulnerable to code+    injection if you template the command line with untrusted input++    The command inherits @stderr@ for the current process+-}+shellStrict+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString)+    -- ^ Exit code and stdout+shellStrict cmdline = systemStrict (Process.shell (Data.Text.unpack cmdline))++{-| Run a command using @execvp@, retrieving the exit code, stdout, and stderr+    as a non-lazy blob of Text+-}+procStrictWithErr+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString, ByteString)+    -- ^ (Exit code, stdout, stderr)+procStrictWithErr cmd args =+    systemStrictWithErr (Process.proc (Data.Text.unpack cmd) (map Data.Text.unpack args))++{-| Run a command line using the shell, retrieving the exit code, stdout, and+    stderr as a non-lazy blob of Text++    This command is more powerful than `proc`, but highly vulnerable to code+    injection if you template the command line with untrusted input+-}+shellStrictWithErr+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString, ByteString)+    -- ^ (Exit code, stdout, stderr)+shellStrictWithErr cmdline =+    systemStrictWithErr (Process.shell (Data.Text.unpack cmdline))++-- | Halt an `Async` thread, re-raising any exceptions it might have thrown+halt :: Async a -> IO ()+halt a = do+    m <- Async.poll a+    case m of+        Nothing        -> Async.cancel a+        Just (Left  e) -> Exception.throwIO e+        Just (Right _) -> return ()++{-| `system` generalizes `shell` and `proc` by allowing you to supply your own+    custom `CreateProcess`.  This is for advanced users who feel comfortable+    using the lower-level @process@ API+-}+system+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io ExitCode+    -- ^ Exit code+system p s = liftIO (do+    let open = do+            (m, Nothing, Nothing, ph) <- Process.createProcess p+            case m of+                Just hIn -> System.IO.hSetBuffering hIn (System.IO.BlockBuffering Nothing)+                _        -> return ()+            return (m, ph)++    -- Prevent double close+    mvar <- MVar.newMVar False+    let close handle = do+            MVar.modifyMVar_ mvar (\finalized -> do+                Control.Monad.unless finalized+                    (ignoreSIGPIPE (System.IO.hClose handle))+                return True )+    let close' (Just hIn, ph) = do+            close hIn+            Process.terminateProcess ph+        close' (Nothing , ph) = do+            Process.terminateProcess ph++    let handle (Just hIn, ph) = do+            let feedIn :: (forall a. IO a -> IO a) -> IO ()+                feedIn restore =+                    restore (ignoreSIGPIPE (outhandle hIn s))+                    `Exception.finally` close hIn+            Exception.mask_ (Async.withAsyncWithUnmask feedIn (\a -> Process.waitForProcess ph <* halt a) )+        handle (Nothing , ph) = do+            Process.waitForProcess ph++    Exception.bracket open close' handle )++systemStrict+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString)+    -- ^ Exit code and stdout+systemStrict p s = liftIO (do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.Inherit+            }++    let open = do+            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')+            System.IO.hSetBuffering hIn (System.IO.BlockBuffering Nothing)+            return (hIn, hOut, ph)++    -- Prevent double close+    mvar <- MVar.newMVar False+    let close handle = do+            MVar.modifyMVar_ mvar (\finalized -> do+                Control.Monad.unless finalized+                    (ignoreSIGPIPE (System.IO.hClose handle))+                return True )++    Exception.bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, ph) -> do+        let feedIn :: (forall a. IO a -> IO a) -> IO ()+            feedIn restore =+                restore (ignoreSIGPIPE (outhandle hIn s))+                `Exception.finally` close hIn++        Async.concurrently+            (Exception.mask_ (Async.withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* halt a)))+            (Data.ByteString.hGetContents hOut) ) )++systemStrictWithErr+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> io (ExitCode, ByteString, ByteString)+    -- ^ Exit code and stdout+systemStrictWithErr p s = liftIO (do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.CreatePipe+            }++    let open = do+            (Just hIn, Just hOut, Just hErr, ph) <- liftIO (Process.createProcess p')+            System.IO.hSetBuffering hIn (System.IO.BlockBuffering Nothing)+            return (hIn, hOut, hErr, ph)++    -- Prevent double close+    mvar <- MVar.newMVar False+    let close handle = do+            MVar.modifyMVar_ mvar (\finalized -> do+                Control.Monad.unless finalized+                    (ignoreSIGPIPE (System.IO.hClose handle))+                return True )++    Exception.bracket open (\(hIn, _, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, hErr, ph) -> do+        let feedIn :: (forall a. IO a -> IO a) -> IO ()+            feedIn restore =+                restore (ignoreSIGPIPE (outhandle hIn s))+                `Exception.finally` close hIn++        runConcurrently $ (,,)+            <$> Concurrently (Exception.mask_ (Async.withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* halt a)))+            <*> Concurrently (Data.ByteString.hGetContents hOut)+            <*> Concurrently (Data.ByteString.hGetContents hErr) ) )++{-| Run a command using @execvp@, streaming @stdout@ as chunks of `ByteString`++    The command inherits @stderr@ for the current process+-}+inproc+    :: Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell ByteString+    -- ^ Chunks of bytes read from process output+inproc cmd args =+    stream (Process.proc (Data.Text.unpack cmd) (map Data.Text.unpack args))++{-| Run a command line using the shell, streaming @stdout@ as chunks of+    `ByteString`++    This command is more powerful than `inproc`, but highly vulnerable to code+    injection if you template the command line with untrusted input++    The command inherits @stderr@ for the current process+-}+inshell+    :: Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell ByteString+    -- ^ Chunks of bytes read from process output+inshell cmd = stream (Process.shell (Data.Text.unpack cmd))++stream+    :: Process.CreateProcess+    -- ^ Command+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell ByteString+    -- ^ Chunks of bytes read from process output+stream p s = do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.Inherit+            }++    let open = do+            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')+            System.IO.hSetBuffering hIn (System.IO.BlockBuffering Nothing)+            return (hIn, hOut, ph)++    -- Prevent double close+    mvar <- liftIO (MVar.newMVar False)+    let close handle = do+            MVar.modifyMVar_ mvar (\finalized -> do+                Control.Monad.unless finalized (System.IO.hClose handle)+                return True )++    (hIn, hOut, ph) <- using (Managed.managed (Exception.bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph)))+    let feedIn :: (forall a. IO a -> IO a) -> IO ()+        feedIn restore =+            restore (sh (do+                bytes <- s+                liftIO (Data.ByteString.hPut hIn bytes) ) )+            `Exception.finally` close hIn++    a <- using (Managed.managed (Exception.mask_ . Async.withAsyncWithUnmask feedIn))+    inhandle hOut <|> (liftIO (Process.waitForProcess ph *> halt a) *> empty)++streamWithErr+    :: Process.CreateProcess+    -- ^ Command+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell (Either ByteString ByteString)+    -- ^ Chunks of bytes read from process output+streamWithErr p s = do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.CreatePipe+            }++    let open = do+            (Just hIn, Just hOut, Just hErr, ph) <- liftIO (Process.createProcess p')+            System.IO.hSetBuffering hIn (System.IO.BlockBuffering Nothing)+            return (hIn, hOut, hErr, ph)++    -- Prevent double close+    mvar <- liftIO (MVar.newMVar False)+    let close handle = do+            MVar.modifyMVar_ mvar (\finalized -> do+                Control.Monad.unless finalized (System.IO.hClose handle)+                return True )++    (hIn, hOut, hErr, ph) <- using (Managed.managed (Exception.bracket open (\(hIn, _, _, ph) -> close hIn >> Process.terminateProcess ph)))+    let feedIn :: (forall a. IO a -> IO a) -> IO ()+        feedIn restore =+            restore (sh (do+                bytes <- s+                liftIO (Data.ByteString.hPut hIn bytes) ) )+            `Exception.finally` close hIn++    queue <- liftIO TQueue.newTQueueIO+    let forwardOut :: (forall a. IO a -> IO a) -> IO ()+        forwardOut restore =+            restore (sh (do+                bytes <- inhandle hOut+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Right bytes)))) ))+            `Exception.finally` STM.atomically (TQueue.writeTQueue queue Nothing)+    let forwardErr :: (forall a. IO a -> IO a) -> IO ()+        forwardErr restore =+            restore (sh (do+                bytes <- inhandle hErr+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Left  bytes)))) ))+            `Exception.finally` STM.atomically (TQueue.writeTQueue queue Nothing)+    let drain = Shell (\(FoldM step begin done) -> do+            x0 <- begin+            let loop x numNothing+                    | numNothing < 2 = do+                        m <- STM.atomically (TQueue.readTQueue queue)+                        case m of+                            Nothing -> loop x $! numNothing + 1+                            Just e  -> do+                                x' <- step x e+                                loop x' numNothing+                    | otherwise      = return x+            x1 <- loop x0 (0 :: Int)+            done x1 )++    a <- using (Managed.managed (Exception.mask_ . Async.withAsyncWithUnmask feedIn    ))+    b <- using (Managed.managed (Exception.mask_ . Async.withAsyncWithUnmask forwardOut))+    c <- using (Managed.managed (Exception.mask_ . Async.withAsyncWithUnmask forwardErr))+    let l `also` r = do+            _ <- l <|> (r *> STM.retry)+            _ <- r+            return ()+    let waitAll = STM.atomically (Async.waitSTM a `also` (Async.waitSTM b `also` Async.waitSTM c))+    drain <|> (liftIO (Process.waitForProcess ph *> waitAll) *> empty)++{-| Run a command using the shell, streaming @stdout@ and @stderr@ as chunks of+    `ByteString`.  Chunks from @stdout@ are wrapped in `Right` and chunks from+    @stderr@ are wrapped in `Left`.  This does /not/ throw an exception if the+    command returns a non-zero exit code+-}+inprocWithErr+    :: Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell (Either ByteString ByteString)+    -- ^ Chunks of either output (`Right`) or error (`Left`)+inprocWithErr cmd args =+    streamWithErr (Process.proc (Data.Text.unpack cmd) (map Data.Text.unpack args))+++{-| Run a command line using the shell, streaming @stdout@ and @stderr@ as+    chunks of `ByteString`.  Chunks from @stdout@ are wrapped in `Right` and+    chunks from @stderr@ are wrapped in `Left`.  This does /not/ throw an+    exception if the command returns a non-zero exit code++    This command is more powerful than `inprocWithErr`, but highly vulnerable to+    code injection if you template the command line with untrusted input+-}+inshellWithErr+    :: Text+    -- ^ Command line+    -> Shell ByteString+    -- ^ Chunks of bytes written to process input+    -> Shell (Either ByteString ByteString)+    -- ^ Chunks of either output (`Right`) or error (`Left`)+inshellWithErr cmd = streamWithErr (Process.shell (Data.Text.unpack cmd))
+ src/Turtle/Internal.hs view
@@ -0,0 +1,16 @@+module Turtle.Internal+    ( ignoreSIGPIPE+    ) where++import Control.Exception (handle, throwIO)+import Foreign.C.Error (Errno(..), ePIPE)+import GHC.IO.Exception (IOErrorType(..), IOException(..))++ignoreSIGPIPE :: IO () -> IO ()+ignoreSIGPIPE = handle (\e -> case e of+    IOError+        { ioe_type = ResourceVanished+        , ioe_errno = Just ioe }+        | Errno ioe == ePIPE -> return ()+    _ -> throwIO e+    )
+ src/Turtle/Line.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE CPP                        #-}+{-# LANGUAGE DeriveDataTypeable         #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}++module Turtle.Line+  ( Line+  , lineToText+  , textToLines+  , linesToText+  , textToLine+  , unsafeTextToLine+  , NewlineForbidden(..)+  ) where++import Data.Text (Text)+import qualified Data.Text as Text+#if __GLASGOW_HASKELL__ >= 708+import Data.Coerce+#endif+import Data.String+#if __GLASGOW_HASKELL__ >= 710+#else+import Data.Monoid+#endif+import Data.Maybe+import Data.Typeable+import Control.Exception++-- | The `NewlineForbidden` exception is thrown when you construct a `Line`+-- using an overloaded string literal or by calling `fromString` explicitly+-- and the supplied string contains newlines. This is a programming error to+-- do so: if you aren't sure that the input string is newline-free, do not+-- rely on the @`IsString` `Line`@ instance.+--+-- When debugging, it might be useful to look for implicit invocations of+-- `fromString` for `Line`:+--+-- >>> sh (do { line <- "Hello\nWorld"; echo line })+-- *** Exception: NewlineForbidden+--+-- In the above example, `echo` expects its argument to be a `Line`, thus+-- @line :: `Line`@. Since we bind @line@ in `Shell`, the string literal+-- @\"Hello\\nWorld\"@ has type @`Shell` `Line`@. The+-- @`IsString` (`Shell` `Line`)@ instance delegates the construction of a+-- `Line` to the @`IsString` `Line`@ instance, where the exception is thrown.+--+-- To fix the problem, use `textToLines`:+--+-- >>> sh (do { line <- select (textToLines "Hello\nWorld"); echo line })+-- Hello+-- World+data NewlineForbidden = NewlineForbidden+  deriving (Show, Typeable)++instance Exception NewlineForbidden++-- | A line of text (does not contain newlines).+newtype Line = Line Text+  deriving (Eq, Ord, Show, Monoid)++instance IsString Line where+  fromString = fromMaybe (throw NewlineForbidden) . textToLine . fromString++-- | Convert a line to a text value.+lineToText :: Line -> Text+lineToText (Line t) = t++-- | Split text into lines. The inverse of `linesToText`.+textToLines :: Text -> [Line]+textToLines =+#if __GLASGOW_HASKELL__ >= 708+  coerce Text.lines+#else+  map unsafeTextToLine . Text.lines+#endif++-- | Merge lines into a single text value.+linesToText :: [Line] -> Text+linesToText =+#if __GLASGOW_HASKELL__ >= 708+  coerce Text.unlines+#else+  Text.unlines . map lineToText+#endif++-- | Try to convert a text value into a line.+-- Precondition (checked): the argument does not contain newlines.+textToLine :: Text -> Maybe Line+textToLine = fromSingleton . textToLines+  where+    fromSingleton []  = Just (Line "")+    fromSingleton [a] = Just a+    fromSingleton _   = Nothing++-- | Convert a text value into a line.+-- Precondition (unchecked): the argument does not contain newlines.+unsafeTextToLine :: Text -> Line+unsafeTextToLine = Line
src/Turtle/Options.hs view
@@ -1,4 +1,7 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}  -- | Example usage of this module: --@@ -64,6 +67,7 @@        -- * Consume parsers     , subcommand+    , subcommandGroup     , options      ) where@@ -82,13 +86,21 @@ import qualified Options.Applicative as Opts import qualified Options.Applicative.Types as Opts import Prelude hiding (FilePath)+import Text.PrettyPrint.ANSI.Leijen (Doc, displayS, renderCompact)  -- | Parse the given options from the command line options :: MonadIO io => Description -> Parser a -> io a options desc parser = liftIO-    $ Opts.execParser+    $ Opts.customExecParser (Opts.prefs prefs)     $ Opts.info (Opts.helper <*> parser)-                (Opts.header (Text.unpack (getDescription desc)))+                (Opts.headerDoc (Just (getDescription desc)))+  where+    prefs :: Opts.PrefsMod+#if MIN_VERSION_optparse_applicative(0,13,0)+    prefs = Opts.showHelpOnError <> Opts.showHelpOnEmpty+#else+    prefs = Opts.showHelpOnError+#endif  {-| The name of a command-line argument @@ -115,7 +127,7 @@      This description will appear in the header of the @--help@ output -}-newtype Description = Description { getDescription :: Text }+newtype Description = Description { getDescription :: Doc }     deriving (IsString)  {-| A helpful message explaining what a flag does@@ -228,10 +240,28 @@ -} subcommand :: CommandName -> Description -> Parser a -> Parser a subcommand cmdName desc p =-    Opts.subparser (Opts.command name info <> Opts.metavar name)+    Opts.hsubparser (Opts.command name info <> Opts.metavar name)   where     name = Text.unpack (getCommandName cmdName) -    info = Opts.info-        (Opts.helper <*> p)-        (Opts.header (Text.unpack (getDescription desc)))+    info = Opts.info p (Opts.progDescDoc (Just (getDescription desc)))++-- | Create a named group of sub-commands+subcommandGroup :: forall a. Description -> [(CommandName, Description, Parser a)] -> Parser a+subcommandGroup name cmds =+    Opts.hsubparser (Opts.commandGroup name' <> foldMap f cmds <> Opts.metavar metavar)+  where+    f :: (CommandName, Description, Parser a) -> Opts.Mod Opts.CommandFields a+    f (cmdName, desc, p) =+        Opts.command+            (Text.unpack (getCommandName cmdName))+            (Opts.info p (Opts.progDescDoc (Just (getDescription desc))))++    metavar :: String+    metavar = Text.unpack (Text.intercalate " | " (map g cmds))+      where+        g :: (CommandName, Description, Parser a) -> Text+        g (cmdName, _, _) = getCommandName cmdName++    name' :: String+    name' = displayS (renderCompact (getDescription name)) ""
src/Turtle/Prelude.hs view
@@ -111,13 +111,11 @@     , Filesystem.readTextFile     , Filesystem.writeTextFile     , arguments-#if MIN_VERSION_base(4,7,0)+#if __GLASGOW_HASKELL__ >= 710     , export     , unset #endif-#if MIN_VERSION_base(4,6,0)     , need-#endif     , env     , cd     , pwd@@ -138,6 +136,8 @@     , touch     , time     , hostname+    , which+    , whichAll     , sleep     , exit     , die@@ -153,12 +153,9 @@     , mktempdir     , fork     , wait+    , pushd      -- * Shell-    , inproc-    , inshell-    , inprocWithErr-    , inshellWithErr     , stdin     , input     , inhandle@@ -174,6 +171,7 @@     , cat     , grep     , sed+    , onFiles     , inplace     , find     , yes@@ -183,6 +181,7 @@     , limit     , limitWhile     , cache+    , parallel      -- * Folds     , countChars@@ -198,8 +197,14 @@     , system     , procs     , shells+    , inproc+    , inshell+    , inprocWithErr+    , inshellWithErr     , procStrict     , shellStrict+    , procStrictWithErr+    , shellStrictWithErr      -- * Permissions     , Permissions@@ -227,6 +232,26 @@     , gibibytes     , tebibytes +    -- * File status+    , PosixCompat.FileStatus+    , stat+    , lstat+    , fileSize+    , accessTime+    , modificationTime+    , statusChangeTime+    , PosixCompat.isBlockDevice+    , PosixCompat.isCharacterDevice+    , PosixCompat.isNamedPipe+    , PosixCompat.isRegularFile+    , PosixCompat.isDirectory+    , PosixCompat.isSymbolicLink+    , PosixCompat.isSocket++    -- * Headers+    , WithHeader(..)+    , header+     -- * Exceptions     , ProcFailed(..)     , ShellFailed(..)@@ -235,22 +260,26 @@ import Control.Applicative import Control.Concurrent (threadDelay) import Control.Concurrent.Async-    (Async, withAsync, withAsyncWithUnmask, wait, waitSTM, concurrently)+    (Async, withAsync, withAsyncWithUnmask, waitSTM, concurrently,+     Concurrently(..))+import qualified Control.Concurrent.Async import Control.Concurrent.MVar (newMVar, modifyMVar_) import qualified Control.Concurrent.STM as STM import qualified Control.Concurrent.STM.TQueue as TQueue-import Control.Exception (Exception, bracket, finally, mask_, throwIO)+import Control.Exception (Exception, bracket, bracket_, finally, mask_, throwIO) import Control.Foldl (Fold, FoldM(..), genericLength, handles, list, premap)+import qualified Control.Foldl import qualified Control.Foldl.Text-import Control.Monad (liftM, msum, when, unless)+import Control.Monad (guard, liftM, msum, when, unless, (>=>)) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Managed (MonadManaged(..), managed, runManaged)+import Control.Monad.Managed (MonadManaged(..), managed, managed_, runManaged) #ifdef mingw32_HOST_OS import Data.Bits ((.&.)) #endif import Data.IORef (newIORef, readIORef, writeIORef) import Data.Text (Text, pack, unpack) import Data.Time (NominalDiffTime, UTCTime, getCurrentTime)+import Data.Time.Clock.POSIX (POSIXTime) import Data.Traversable import qualified Data.Text    as Text import qualified Data.Text.IO as Text@@ -263,11 +292,11 @@ import System.Clock (Clock(..), TimeSpec(..), getTime) import System.Environment (     getArgs,-#if MIN_VERSION_base(4,7,0)+#if __GLASGOW_HASKELL__ >= 710     setEnv,     unsetEnv, #endif-#if MIN_VERSION_base(4,6,0)+#if __GLASGOW_HASKELL__ >= 708     lookupEnv, #endif     getEnvironment )@@ -277,7 +306,9 @@ import System.IO (Handle, hClose) import qualified System.IO as IO import System.IO.Temp (withTempDirectory, withTempFile)-import System.IO.Error (catchIOError, ioeGetErrorType)+import System.IO.Error+    (catchIOError, ioeGetErrorType, isPermissionError, isDoesNotExistError)+import qualified System.PosixCompat as PosixCompat import qualified System.Process as Process #ifdef mingw32_HOST_OS import qualified System.Win32 as Win32@@ -286,16 +317,15 @@     openDirStream,     readDirStream,     closeDirStream,-    touchFile,-    getSymbolicLinkStatus,-    isDirectory,-    isSymbolicLink )+    touchFile ) #endif import Prelude hiding (FilePath)  import Turtle.Pattern (Pattern, anyChar, chars, match, selfless, sepBy) import Turtle.Shell import Turtle.Format (Format, format, makeFormat, d, w, (%))+import Turtle.Internal (ignoreSIGPIPE)+import Turtle.Line  {-| Run a command using @execvp@, retrieving the exit code @@ -307,7 +337,7 @@     -- ^ Command     -> [Text]     -- ^ Arguments-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io ExitCode     -- ^ Exit code@@ -330,7 +360,7 @@     :: MonadIO io     => Text     -- ^ Command line-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io ExitCode     -- ^ Exit code@@ -359,7 +389,7 @@     -- ^ Command     -> [Text]     -- ^ Arguments-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io () procs cmd args s = do@@ -382,7 +412,7 @@     :: MonadIO io     => Text     -- ^ Command line-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io ()     -- ^ Exit code@@ -403,7 +433,7 @@     -- ^ Command     -> [Text]     -- ^ Arguments-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io (ExitCode, Text)     -- ^ Exit code and stdout@@ -422,12 +452,54 @@     :: MonadIO io     => Text     -- ^ Command line-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io (ExitCode, Text)     -- ^ Exit code and stdout shellStrict cmdLine = systemStrict (Process.shell (Text.unpack cmdLine)) +{-| Run a command using @execvp@, retrieving the exit code, stdout, and stderr+    as a non-lazy blob of Text+-}+procStrictWithErr+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell Line+    -- ^ Lines of standard input+    -> io (ExitCode, Text, Text)+    -- ^ (Exit code, stdout, stderr)+procStrictWithErr cmd args =+    systemStrictWithErr (Process.proc (Text.unpack cmd) (map Text.unpack args))++{-| Run a command line using the shell, retrieving the exit code, stdout, and+    stderr as a non-lazy blob of Text++    This command is more powerful than `proc`, but highly vulnerable to code+    injection if you template the command line with untrusted input+-}+shellStrictWithErr+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell Line+    -- ^ Lines of standard input+    -> io (ExitCode, Text, Text)+    -- ^ (Exit code, stdout, stderr)+shellStrictWithErr cmdLine =+    systemStrictWithErr (Process.shell (Text.unpack cmdLine))++-- | Halt an `Async` thread, re-raising any exceptions it might have thrown+halt :: Async a -> IO ()+halt a = do+    m <- Control.Concurrent.Async.poll a+    case m of+        Nothing        -> Control.Concurrent.Async.cancel a+        Just (Left  e) -> throwIO e+        Just (Right _) -> return ()+ {-| `system` generalizes `shell` and `proc` by allowing you to supply your own     custom `CreateProcess`.  This is for advanced users who feel comfortable     using the lower-level @process@ API@@ -436,37 +508,45 @@     :: MonadIO io     => Process.CreateProcess     -- ^ Command-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io ExitCode     -- ^ Exit code system p s = liftIO (do     let open = do-            (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p-            IO.hSetBuffering hIn IO.LineBuffering-            return (hIn, ph)+            (m, Nothing, Nothing, ph) <- Process.createProcess p+            case m of+                Just hIn -> IO.hSetBuffering hIn IO.LineBuffering+                _        -> return ()+            return (m, ph)      -- Prevent double close     mvar <- newMVar False     let close handle = do             modifyMVar_ mvar (\finalized -> do-                unless finalized (hClose handle)+                unless finalized (ignoreSIGPIPE (hClose handle))                 return True )+    let close' (Just hIn, ph) = do+            close hIn+            Process.terminateProcess ph+        close' (Nothing , ph) = do+            Process.terminateProcess ph -    bracket open (\(hIn, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, ph) -> do-        let feedIn :: (forall a. IO a -> IO a) -> IO ()-            feedIn restore =-                restore (sh (do-                    txt <- s-                    liftIO (Text.hPutStrLn hIn txt) ) )-                `finally` close hIn-        mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a) ) ) )+    let handle (Just hIn, ph) = do+            let feedIn :: (forall a. IO a -> IO a) -> IO ()+                feedIn restore =+                    restore (ignoreSIGPIPE (outhandle hIn s)) `finally` close hIn+            mask_ (withAsyncWithUnmask feedIn (\a -> Process.waitForProcess ph <* halt a) )+        handle (Nothing , ph) = do+            Process.waitForProcess ph +    bracket open close' handle )+ systemStrict     :: MonadIO io     => Process.CreateProcess     -- ^ Command-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input     -> io (ExitCode, Text)     -- ^ Exit code and stdout@@ -486,21 +566,55 @@     mvar <- newMVar False     let close handle = do             modifyMVar_ mvar (\finalized -> do-                unless finalized (hClose handle)+                unless finalized (ignoreSIGPIPE (hClose handle))                 return True )      bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, ph) -> do         let feedIn :: (forall a. IO a -> IO a) -> IO ()             feedIn restore =-                restore (sh (do-                    txt <- s-                    liftIO (Text.hPutStrLn hIn txt) ) )-                `finally` close hIn+                restore (ignoreSIGPIPE (outhandle hIn s)) `finally` close hIn          concurrently-            (mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a)))+            (mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* halt a)))             (Text.hGetContents hOut) ) ) +systemStrictWithErr+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell Line+    -- ^ Lines of standard input+    -> io (ExitCode, Text, Text)+    -- ^ Exit code and stdout+systemStrictWithErr p s = liftIO (do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.CreatePipe+            }++    let open = do+            (Just hIn, Just hOut, Just hErr, ph) <- liftIO (Process.createProcess p')+            IO.hSetBuffering hIn IO.LineBuffering+            return (hIn, hOut, hErr, ph)++    -- Prevent double close+    mvar <- newMVar False+    let close handle = do+            modifyMVar_ mvar (\finalized -> do+                unless finalized (ignoreSIGPIPE (hClose handle))+                return True )++    bracket open (\(hIn, _, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, hErr, ph) -> do+        let feedIn :: (forall a. IO a -> IO a) -> IO ()+            feedIn restore =+                restore (ignoreSIGPIPE (outhandle hIn s)) `finally` close hIn++        runConcurrently $ (,,)+            <$> Concurrently (mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* halt a)))+            <*> Concurrently (Text.hGetContents hOut)+            <*> Concurrently (Text.hGetContents hErr) ) )+ {-| Run a command using @execvp@, streaming @stdout@ as lines of `Text`      The command inherits @stderr@ for the current process@@ -510,9 +624,9 @@     -- ^ Command     -> [Text]     -- ^ Arguments-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard output inproc cmd args = stream (Process.proc (unpack cmd) (map unpack args)) @@ -526,18 +640,18 @@ inshell     :: Text     -- ^ Command line-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard output inshell cmd = stream (Process.shell (unpack cmd))  stream     :: Process.CreateProcess     -- ^ Command-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard output stream p s = do     let p' = p@@ -560,21 +674,17 @@      (hIn, hOut, ph) <- using (managed (bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph)))     let feedIn :: (forall a. IO a -> IO a) -> IO ()-        feedIn restore =-            restore (sh (do-                txt <- s-                liftIO (Text.hPutStrLn hIn txt) ) )-            `finally` close hIn+        feedIn restore = restore (outhandle hIn s) `finally` close hIn      a <- using (managed (mask_ . withAsyncWithUnmask feedIn))-    inhandle hOut <|> (liftIO (Process.waitForProcess ph *> wait a) *> empty)+    inhandle hOut <|> (liftIO (Process.waitForProcess ph *> halt a) *> empty)  streamWithErr     :: Process.CreateProcess     -- ^ Command-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell (Either Text Text)+    -> Shell (Either Line Line)     -- ^ Lines of standard output streamWithErr p s = do     let p' = p@@ -597,24 +707,20 @@      (hIn, hOut, hErr, ph) <- using (managed (bracket open (\(hIn, _, _, ph) -> close hIn >> Process.terminateProcess ph)))     let feedIn :: (forall a. IO a -> IO a) -> IO ()-        feedIn restore =-            restore (sh (do-                txt <- s-                liftIO (Text.hPutStrLn hIn txt) ) )-            `finally` close hIn+        feedIn restore = restore (outhandle hIn s) `finally` close hIn      queue <- liftIO TQueue.newTQueueIO     let forwardOut :: (forall a. IO a -> IO a) -> IO ()         forwardOut restore =             restore (sh (do-                txt <- inhandle hOut-                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Right txt)))) ))+                line <- inhandle hOut+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Right line)))) ))             `finally` STM.atomically (TQueue.writeTQueue queue Nothing)     let forwardErr :: (forall a. IO a -> IO a) -> IO ()         forwardErr restore =             restore (sh (do-                txt <- inhandle hErr-                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Left  txt)))) ))+                line <- inhandle hErr+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Left  line)))) ))             `finally` STM.atomically (TQueue.writeTQueue queue Nothing)     let drain = Shell (\(FoldM step begin done) -> do             x0 <- begin@@ -650,9 +756,9 @@     -- ^ Command     -> [Text]     -- ^ Arguments-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell (Either Text Text)+    -> Shell (Either Line Line)     -- ^ Lines of either standard output (`Right`) or standard error (`Left`) inprocWithErr cmd args =     streamWithErr (Process.proc (unpack cmd) (map unpack args))@@ -669,36 +775,36 @@ inshellWithErr     :: Text     -- ^ Command line-    -> Shell Text+    -> Shell Line     -- ^ Lines of standard input-    -> Shell (Either Text Text)+    -> Shell (Either Line Line)     -- ^ Lines of either standard output (`Right`) or standard error (`Left`) inshellWithErr cmd = streamWithErr (Process.shell (unpack cmd))  -- | Print to @stdout@-echo :: MonadIO io => Text -> io ()-echo txt = liftIO (Text.putStrLn txt)+echo :: MonadIO io => Line -> io ()+echo line = liftIO (Text.putStrLn (lineToText line))  -- | Print to @stderr@-err :: MonadIO io => Text -> io ()-err txt = liftIO (Text.hPutStrLn IO.stderr txt)+err :: MonadIO io => Line -> io ()+err line = liftIO (Text.hPutStrLn IO.stderr (lineToText line))  {-| Read in a line from @stdin@      Returns `Nothing` if at end of input -}-readline :: MonadIO io => io (Maybe Text)+readline :: MonadIO io => io (Maybe Line) readline = liftIO (do     eof <- IO.isEOF     if eof         then return Nothing-        else fmap (Just . pack) getLine )+        else fmap (Just . unsafeTextToLine . pack) getLine )  -- | Get command line arguments in a list arguments :: MonadIO io => io [Text] arguments = liftIO (fmap (map pack) getArgs) -#if MIN_VERSION_base(4,7,0)+#if __GLASGOW_HASKELL__ >= 710 -- | Set or modify an environment variable export :: MonadIO io => Text -> Text -> io () export key val = liftIO (setEnv (unpack key) (unpack val))@@ -708,10 +814,12 @@ unset key = liftIO (unsetEnv (unpack key)) #endif -#if MIN_VERSION_base(4,6,0) -- | Look up an environment variable need :: MonadIO io => Text -> io (Maybe Text)+#if __GLASGOW_HASKELL__ >= 708 need key = liftIO (fmap (fmap pack) (lookupEnv (unpack key)))+#else+need key = liftM (lookup key) env #endif  -- | Retrieve all environment variables@@ -724,6 +832,21 @@ cd :: MonadIO io => FilePath -> io () cd path = liftIO (Filesystem.setWorkingDirectory path) +{-| Change the current directory. Once the current 'Shell' is done, it returns+back to the original directory.++>>> :set -XOverloadedStrings+>>> cd "/"+>>> view (pushd "/tmp" >> pwd)+FilePath "/tmp"+>>> pwd+FilePath "/"+-}+pushd :: MonadManaged managed => FilePath -> managed ()+pushd path = do+    cwd <- pwd+    using (managed_ (bracket_ (cd path) (cd cwd)))+ -- | Get the current directory pwd :: MonadIO io => io FilePath pwd = liftIO Filesystem.getWorkingDirectory@@ -873,20 +996,10 @@ rmtree :: MonadIO io => FilePath -> io () rmtree path0 = liftIO (sh (loop path0))   where-#ifdef mingw32_HOST_OS     loop path = do-        isDir <- testdir path-        if isDir-            then (do-                child <- ls path-                loop child ) <|> rmdir path-            else rm path-#else-    loop path = do-        let path' = Filesystem.encodeString path-        stat <- liftIO $ getSymbolicLinkStatus path'-        let isLink = isSymbolicLink stat-            isDir = isDirectory stat+        linkstat <- lstat path+        let isLink = PosixCompat.isSymbolicLink linkstat+            isDir = PosixCompat.isDirectory linkstat         if isLink             then rm path             else do@@ -895,7 +1008,6 @@                         child <- ls path                         loop child ) <|> rmdir path                     else rm path-#endif  -- | Check if a file exists testfile :: MonadIO io => FilePath -> io Bool@@ -943,7 +1055,7 @@  > chmod rwo        "foo.txt"  -- chmod u=rw foo.txt > chmod executable "foo.txt"  -- chmod u+x foo.txt-> chmod nonwritable "foo.txt" -- chmod u-x foo.txt+> chmod nonwritable "foo.txt" -- chmod u-w foo.txt -} chmod     :: MonadIO io@@ -1073,6 +1185,30 @@ hostname :: MonadIO io => io Text hostname = liftIO (fmap Text.pack getHostName) +-- | Show the full path of an executable file+which :: MonadIO io => FilePath -> io (Maybe FilePath)+which cmd = fold (whichAll cmd) Control.Foldl.head++-- | Show all matching executables in PATH, not just the first+whichAll :: FilePath -> Shell FilePath+whichAll cmd = do+  Just paths <- need "PATH"+  path <- select (Text.split (== ':') paths)+  let path' = Filesystem.fromText path </> cmd++  True <- testfile path'++  let handler :: IOError -> IO Permissions+      handler e =+          if isPermissionError e || isDoesNotExistError e+              then return Directory.emptyPermissions+              else throwIO e++  perms <- liftIO (getmod path' `catchIOError` handler)++  guard (Directory.executable perms)+  return path'+ {-| Sleep for the given duration      A numeric literal argument is interpreted as seconds.  In other words,@@ -1180,18 +1316,22 @@ fork :: MonadManaged managed => IO a -> managed (Async a) fork io = using (managed (withAsync io)) +-- | Wait for an `Async` action to complete+wait :: MonadIO io => Async a -> io a+wait a = liftIO (Control.Concurrent.Async.wait a)+ -- | Read lines of `Text` from standard input-stdin :: Shell Text+stdin :: Shell Line stdin = inhandle IO.stdin  -- | Read lines of `Text` from a file-input :: FilePath -> Shell Text+input :: FilePath -> Shell Line input file = do     handle <- using (readonly file)     inhandle handle  -- | Read lines of `Text` from a `Handle`-inhandle :: Handle -> Shell Text+inhandle :: Handle -> Shell Line inhandle handle = Shell (\(FoldM step begin done) -> do     x0 <- begin     let loop x = do@@ -1200,45 +1340,45 @@                 then done x                 else do                     txt <- Text.hGetLine handle-                    x'  <- step x txt+                    x'  <- step x (unsafeTextToLine txt)                     loop $! x'     loop $! x0 )  -- | Stream lines of `Text` to standard output-stdout :: MonadIO io => Shell Text -> io ()+stdout :: MonadIO io => Shell Line -> io () stdout s = sh (do-    txt <- s-    liftIO (echo txt) )+    line <- s+    liftIO (echo line) )  -- | Stream lines of `Text` to a file-output :: MonadIO io => FilePath -> Shell Text -> io ()+output :: MonadIO io => FilePath -> Shell Line -> io () output file s = sh (do     handle <- using (writeonly file)-    txt    <- s-    liftIO (Text.hPutStrLn handle txt) )+    line   <- s+    liftIO (Text.hPutStrLn handle (lineToText line)) )  -- | Stream lines of `Text` to a `Handle`-outhandle :: MonadIO io => Handle -> Shell Text -> io ()+outhandle :: MonadIO io => Handle -> Shell Line -> io () outhandle handle s = sh (do-    txt <- s-    liftIO (Text.hPutStrLn handle txt) )+    line <- s+    liftIO (Text.hPutStrLn handle (lineToText line)) )  -- | Stream lines of `Text` to append to a file-append :: MonadIO io => FilePath -> Shell Text -> io ()+append :: MonadIO io => FilePath -> Shell Line -> io () append file s = sh (do     handle <- using (appendonly file)-    txt    <- s-    liftIO (Text.hPutStrLn handle txt) )+    line   <- s+    liftIO (Text.hPutStrLn handle (lineToText line)) )  -- | Stream lines of `Text` to standard error-stderr :: MonadIO io => Shell Text -> io ()+stderr :: MonadIO io => Shell Line -> io () stderr s = sh (do-    txt <- s-    liftIO (err txt) )+    line <- s+    liftIO (err line) )  -- | Read in a stream's contents strictly-strict :: MonadIO io => Shell Text -> io Text-strict s = liftM Text.unlines (fold s list)+strict :: MonadIO io => Shell Line -> io Text+strict s = liftM linesToText (fold s list)  -- | Acquire a `Managed` read-only `Handle` from a `FilePath` readonly :: MonadManaged managed => FilePath -> managed Handle@@ -1257,36 +1397,45 @@ cat = msum  -- | Keep all lines that match the given `Pattern`-grep :: Pattern a -> Shell Text -> Shell Text+grep :: Pattern a -> Shell Line -> Shell Line grep pattern s = do-    txt <- s-    _:_ <- return (match pattern txt)-    return txt+    line <- s+    _:_ <- return (match pattern (lineToText line))+    return line  {-| Replace all occurrences of a `Pattern` with its `Text` result      `sed` performs substitution on a line-by-line basis, meaning that     substitutions may not span multiple lines.  Additionally, substitutions may     occur multiple times within the same line, like the behavior of-    @s/.../.../g@.+    @s\/...\/...\/g@.      Warning: Do not use a `Pattern` that matches the empty string, since it will     match an infinite number of times.  `sed` tries to detect such `Pattern`s     and `die` with an error message if they occur, but this detection is     necessarily incomplete. -}-sed :: Pattern Text -> Shell Text -> Shell Text+sed :: Pattern Text -> Shell Line -> Shell Line sed pattern s = do     when (matchesEmpty pattern) (die message)     let pattern' = fmap Text.concat             (many (pattern <|> fmap Text.singleton anyChar))-    txt    <- s-    txt':_ <- return (match pattern' txt)-    return txt'+    line   <- s+    txt':_ <- return (match pattern' (lineToText line))+    select (textToLines txt')   where     message = "sed: the given pattern matches the empty string"     matchesEmpty = not . null . flip match "" +-- | Make a `Shell Text -> Shell Text` function work on `FilePath`s instead.+-- | Ignores any paths which cannot be decoded as valid `Text`.+onFiles :: (Shell Text -> Shell Text) -> Shell FilePath -> Shell FilePath+onFiles f = fmap Filesystem.fromText . f . getRights . fmap Filesystem.toText+  where+    getRights :: forall a. Shell (Either a Text) -> Shell Text+    getRights s = s >>= either (const empty) return++ -- | Like `sed`, but operates in place on a `FilePath` (analogous to @sed -i@) inplace :: MonadIO io => Pattern Text -> FilePath -> io () inplace pattern file = liftIO (runManaged (do@@ -1307,7 +1456,7 @@     return path  -- | A Stream of @\"y\"@s-yes :: Shell Text+yes :: Shell Line yes = fmap (\_ -> "y") endless  -- | Number each element of a `Shell` (starting at 0)@@ -1437,8 +1586,8 @@ cache :: (Read a, Show a) => FilePath -> Shell a -> Shell a cache file s = do     let cached = do-            txt <- input file-            case reads (Text.unpack txt) of+            line <- input file+            case reads (Text.unpack (lineToText line)) of                 [(ma, "")] -> return ma                 _          ->                     die (format ("cache: Invalid data stored in "%w) file)@@ -1458,6 +1607,18 @@                     empty             justs <|> nothing +{-| Run a list of IO actions in parallel using fork and wait.+++>>> view (parallel [(sleep 3) >> date, date, date])+2016-12-01 17:22:10.83296 UTC+2016-12-01 17:22:07.829876 UTC+2016-12-01 17:22:07.829963 UTC++-}+parallel :: [IO a] -> Shell a+parallel = traverse fork >=> select >=> wait+ -- | Split a line into chunks delimited by the given `Pattern` cut :: Pattern a -> Text -> [Text] cut pattern txt = head (match (selfless chars `sepBy` pattern) txt)@@ -1473,7 +1634,18 @@  -- | Get the size of a file or a directory du :: MonadIO io => FilePath -> io Size-du path = liftIO (fmap Size (Filesystem.getSize path))+du path = liftIO (do+    isDir <- testdir path+    size <- do+        if isDir+        then do+            let sizes = do+                    child <- lstree path+                    True  <- testfile child+                    liftIO (Filesystem.getSize child)+            fold sizes Control.Foldl.sum+        else Filesystem.getSize path+    return (Size size) )  {-| An abstract file size @@ -1493,7 +1665,7 @@ >>> format sz 2309 "2.309 KB" >>> format sz 949203-"949.203 MB"+"949.203 KB" >>> format sz 1600000000 "1.600 GB" >>> format sz 999999999999999999@@ -1556,8 +1728,10 @@     This uses the convention that the elements of the stream are implicitly     ended by newlines that are one character wide -}-countChars :: Integral n => Fold Text n-countChars = Control.Foldl.Text.length + charsPerNewline * countLines+countChars :: Integral n => Fold Line n+countChars =+  premap lineToText Control.Foldl.Text.length ++    charsPerNewline * countLines  charsPerNewline :: Num a => a #ifdef mingw32_HOST_OS@@ -1567,13 +1741,64 @@ #endif  -- | Count the number of words in the stream (like @wc -w@)-countWords :: Integral n => Fold Text n-countWords = premap Text.words (handles traverse genericLength)+countWords :: Integral n => Fold Line n+countWords = premap (Text.words . lineToText) (handles traverse genericLength)  {-| Count the number of lines in the stream (like @wc -l@)      This uses the convention that each element of the stream represents one     line -}-countLines :: Integral n => Fold Text n+countLines :: Integral n => Fold Line n countLines = genericLength++-- | Get the status of a file+stat :: MonadIO io => FilePath -> io PosixCompat.FileStatus+stat = liftIO . PosixCompat.getFileStatus . Filesystem.encodeString++-- | Size of the file in bytes. Does not follow symlinks+fileSize :: PosixCompat.FileStatus -> Size+fileSize = fromIntegral . PosixCompat.fileSize++-- | Time of last access+accessTime :: PosixCompat.FileStatus -> POSIXTime+accessTime = realToFrac . PosixCompat.accessTime++-- | Time of last modification+modificationTime :: PosixCompat.FileStatus -> POSIXTime+modificationTime = realToFrac . PosixCompat.modificationTime++-- | Time of last status change (i.e. owner, group, link count, mode, etc.)+statusChangeTime :: PosixCompat.FileStatus -> POSIXTime+statusChangeTime = realToFrac . PosixCompat.statusChangeTime++-- | Get the status of a file, but don't follow symbolic links+lstat :: MonadIO io => FilePath -> io PosixCompat.FileStatus+lstat = liftIO . PosixCompat.getSymbolicLinkStatus . Filesystem.encodeString++data WithHeader a+    = Header a+    -- ^ The first line with the header+    | Row a a+    -- ^ Every other line: 1st element is header, 2nd element is original row+    deriving (Show)++data Pair a b = Pair !a !b++header :: Shell a -> Shell (WithHeader a)+header (Shell k) = Shell k'+  where+    k' (FoldM step begin done) = k (FoldM step' begin' done')+      where+        step' (Pair x Nothing ) a = do+            x' <- step x (Header a)+            return (Pair x' (Just a))+        step' (Pair x (Just a)) b = do+            x' <- step x (Row a b)+            return (Pair x' (Just a))++        begin' = do+            x <- begin+            return (Pair x Nothing)++        done' (Pair x _) = done x
src/Turtle/Tutorial.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE NoCPP #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-}  {-| Use @turtle@ if you want to write light-weight and maintainable shell@@ -373,7 +374,7 @@ -- > $ ./example.hs -- >  -- > example.hs:8:10:--- >     Couldn't match expected type `Text' with actual type `UTCTime'+-- >     Couldn't match expected type `Line' with actual type `UTCTime' -- >     In the first argument of `echo', namely `time' -- >     In a stmt of a 'do' block: echo time -- >     In the expression:@@ -382,13 +383,14 @@ -- >            echo time } -- -- The error points to the last line of our program: @(example.hs:8:10)@ means--- line 8, column 10 of our program.  If you study the error message closely--- you'll see that the `echo` function expects a `Text` value, but we passed it--- @\'time\'@, which was a `UTCTime` value.  Although the error is at the end of--- our script, Haskell catches this error before even running the script.  When--- we \"interpret\" a Haskell script the Haskell compiler actually compiles the--- script without any optimizations to generate a temporary executable and then--- runs the executable, much like Perl does for Perl scripts.+-- line 8, column 10 of our program. If you study the error message closely+-- you'll see that the `echo` function expects a `Line` value (a piece of text+-- without newlines), but we passed it @\'time\'@, which was a `UTCTime` value.+-- Although the error is at the end of our script, Haskell catches this error+-- before even running the script.  When we \"interpret\" a Haskell script the+-- Haskell compiler actually compiles the script without any optimizations to+-- generate a temporary executable and then runs the executable, much like Perl+-- does for Perl scripts. -- -- You might wonder: \"where are the types?\"  None of the above programs had -- any type signatures or type annotations, yet the compiler still detected type@@ -590,8 +592,8 @@ -- > --      v  v -- > main :: IO () ----- Not every top-level value has to be a subroutine, though.  For example, you--- can define unadorned `Text` values at the top-level, as we saw previously:+-- Not every top-level value has to be a subroutine, though. For example, you+-- can define unadorned `Line` values at the top-level, as we saw previously: -- -- > #!/usr/bin/env stack -- > -- stack --install-ghc runghc --package turtle@@ -600,7 +602,7 @@ -- >  -- > import Turtle -- > --- > str :: Text+-- > str :: Line -- > str = "Hello!" -- >  -- > main :: IO ()@@ -638,21 +640,21 @@ -- >     In an equation for `str': str = "Hello, world!" -- >  -- > example.hs:11:13:--- >     Couldn't match expected type `Text' with actual type `Int'+-- >     Couldn't match expected type `Line' with actual type `Int' -- >     In the first argument of `echo', namely `str' -- >     In the expression: echo str -- >     In an equation for `main': main = echo str -- -- The first error message relates to the @OverloadedStrings@ extensions. When -- we enable @OverloadedStrings@ the compiler overloads string literals,--- interpreting them as any type that implements the `IsString` interface.  The+-- interpreting them as any type that implements the `IsString` interface. The -- error message says that `Int` does not implement the `IsString` interface so -- the compiler cannot interpret a string literal as an `Int`.  On the other--- hand the `Text` and `Turtle.FilePath` types do implement `IsString`, which--- is why we can interpret string literals as `Text` or `Turtle.FilePath`--- values.+-- hand the `Text`, `Line` and `Turtle.FilePath` types do implement `IsString`,+-- which is why we can interpret string literals as `Text`, `Line` or+-- `Turtle.FilePath` values. ----- The second error message says that `echo` expects a `Text` value, but we+-- The second error message says that `echo` expects a `Line` value, but we -- declared @str@ to be an `Int`, so the compiler aborts compilation, requiring -- us to either fix or delete our type signature. --@@ -672,7 +674,7 @@ -- >  -- > import Turtle -- > --- > str :: Text+-- > str :: Line -- > str = 4 -- >  -- > main :: IO ()@@ -683,16 +685,16 @@ -- > $ ./example.hs -- >  -- > example.hs:8:7:--- >     No instance for (Num Text)+-- >     No instance for (Num Line) -- >       arising from the literal `4'--- >     Possible fix: add an instance declaration for (Num Text)+-- >     Possible fix: add an instance declaration for (Num Line) -- >     In the expression: 4 -- >     In an equation for `str': str = 4 -- -- Haskell also automatically overloads numeric literals, too.  The compiler -- interprets integer literals as any type that implements the `Num` interface.--- The `Text` type does not implement the `Num` interface, so we cannot--- interpret integer literals as `Text` strings.+-- The `Line` type does not implement the `Num` interface, so we cannot+-- interpret integer literals as `Line` strings.  -- $system --@@ -733,7 +735,7 @@ -- @ -- `shell` --     :: Text         -- Command line---     -> Shell Text   -- Standard input (as lines of \`Text\`)+--     -> Shell Line   -- Standard input (as lines of \`Text\`) --     -> IO `ExitCode`  -- Exit code of the shell command -- @ --@@ -774,7 +776,7 @@ -- `proc` --     :: Text         -- Program --     -> [Text]       -- Arguments---     -> Shell Text   -- Standard input (as lines of \`Text\`)+--     -> Shell Line   -- Standard input (as lines of \`Text\`) --     -> IO ExitCode  -- Exit code of the shell command -- @ --@@ -928,7 +930,7 @@ -- @ -- Prelude Turtle> view (`liftIO` readline) -- ABC\<Enter\>--- Just \"ABC\"+-- Just (Line "ABC") -- @ -- -- Another way to say that is:@@ -1112,13 +1114,13 @@ -- For example, you can write to standard output using the `stdout` utility: -- -- @--- `stdout` :: Shell Text -> IO ()+-- `stdout` :: Shell Line -> IO () -- `stdout` s = sh (do --     txt <- s --     liftIO (echo txt) ) -- @ ----- `stdout` outputs each `Text` value on its own line:+-- `stdout` outputs each `Line` value on its own line: -- -- > Prelude Turtle> stdout "Line 1" -- > Line 1@@ -1126,11 +1128,11 @@ -- > Line 1 -- > Line 2 ----- Another useful stream is `stdin`, which emits one line of `Text` per line of+-- Another useful stream is `stdin`, which emits one `Line` value per line of -- standard input: -- -- @--- `stdin` :: Shell Text+-- `stdin` :: Shell Line -- @ -- -- Let's combine `stdin` and `stdout` to forward all input from standard input@@ -1193,8 +1195,8 @@ -- @ -- `inshell` --     :: Text    -- Command line---     -> Shell Text  -- Standard input to feed to program---     -> Shell Text  -- Standard output produced by program+--     -> Shell Line  -- Standard input to feed to program+--     -> Shell Line  -- Standard output produced by program -- @ -- -- This means you can use `inshell` to embed arbitrary external utilities as@@ -1211,8 +1213,8 @@ -- `inproc` --     :: Text        -- Program --     -> [Text]      -- Arguments---     -> Shell Text  -- Standard input to feed to program---     -> Shell Text  -- Standard output produced by program+--     -> Shell Line  -- Standard input to feed to program+--     -> Shell Line  -- Standard output produced by program -- @ -- -- Using `inproc`, you would write:@@ -1237,7 +1239,7 @@ -- Let's look at the type of `grep`: -- -- @--- `grep` :: Pattern a -> Shell Text -> Shell Text+-- `grep` :: Pattern a -> Shell Line -> Shell Line -- @ -- -- The first argument of `grep` is actually a `Pattern`, which implements@@ -1654,7 +1656,7 @@ -- >  -- > parser :: Parser Command -- > parser--- >     =   fmap IncreaseVolume +-- >     =   fmap IncreaseVolume -- >             (subcommand "up" "Turn the volume up" -- >                 (argInt "amount" "How much to increase the volume") ) -- >     <|> fmap DecreaseVolume@@ -1664,8 +1666,8 @@ -- > main = do -- >     x <- options "Volume adjuster" parser -- >     case x of--- >         IncreaseVolume n -> echo (format ("Increasing the volume by "%d) n)--- >         DecreaseVolume n -> echo (format ("Decreasing the volume by "%d) n)+-- >         IncreaseVolume n -> printf ("Increasing the volume by "%d%"\n") n+-- >         DecreaseVolume n -> printf ("Decreasing the volume by "%d%"\n") n -- -- This will provide `--help` output at both the top level and for each -- subcommand:
+ test/RegressionBrokenPipe.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+import Control.Monad+import System.Timeout+import Turtle+import qualified Turtle.Bytes as Bytes++main :: IO ()+main = do+    echo "proc (text)"+    void $ timeout duration $ forever $ proc "true" [] message+    echo "procStrict (text)"+    void $ timeout duration $ forever $ procStrict "true" [] message+    echo "procStrictWithErr (text)"+    void $ timeout duration $ forever $ procStrictWithErr "true" [] message+    echo "proc (bytes)"+    void $ timeout duration $ forever $ Bytes.proc "true" [] message+    echo "procStrict (bytes)"+    void $ timeout duration $ forever $ Bytes.procStrict "true" [] message+    echo "procStrictWithErr (bytes)"+    void $ timeout duration $ forever $ Bytes.procStrictWithErr "true" [] message+  where+    message :: IsString s => s+    message = "foobarbaz"+    duration = 3 * 10^ (6 :: Int)
turtle.cabal view
@@ -1,5 +1,5 @@ Name: turtle-Version: 1.2.8+Version: 1.3.0 Cabal-Version: >=1.10 Build-Type: Simple License: BSD3@@ -22,7 +22,7 @@     .     * Portability: Works on Windows, OS X, and Linux     .-    * Exception safety: Safely acquire and release resources +    * Exception safety: Safely acquire and release resources     .     * Streaming: Transform or fold command output in constant space     .@@ -40,6 +40,8 @@     then you should also check out the @Shelly@ library which provides similar     functionality. Category: System+Extra-Source-Files:+    CHANGELOG.md Source-Repository head     Type: git     Location: https://github.com/Gabriel439/Haskell-Turtle-Library@@ -47,35 +49,42 @@ Library     HS-Source-Dirs: src     Build-Depends:-        base                 >= 4.6     && < 5  ,-        async                >= 2.0.0.0 && < 2.2,-        clock                >= 0.4.1.2 && < 0.8,-        directory            >= 1.0.7   && < 1.3,-        foldl                >= 1.1     && < 1.3,-        hostname                           < 1.1,-        managed              >= 1.0.3   && < 1.1,-        process              >= 1.0.1.1 && < 1.5,-        system-filepath      >= 0.3.1   && < 0.5,-        system-fileio        >= 0.2.1   && < 0.4,-        stm                                < 2.5,-        temporary                          < 1.3,-        text                               < 1.3,-        time                               < 1.7,-        transformers         >= 0.2.0.0 && < 0.6,-        optparse-applicative >= 0.11    && < 0.13,-        optional-args        >= 1.0     && < 2.0+        base                 >= 4.6     && < 5   ,+        ansi-wl-pprint       >= 0.6     && < 0.7 ,+        async                >= 2.0.0.0 && < 2.2 ,+        bytestring           >= 0.9.1.8 && < 0.11,+        clock                >= 0.4.1.2 && < 0.8 ,+        directory            >= 1.0.7   && < 1.3 ,+        foldl                >= 1.1     && < 1.3 ,+        hostname                           < 1.1 ,+        managed              >= 1.0.3   && < 1.1 ,+        process              >= 1.0.1.1 && < 1.5 ,+        system-filepath      >= 0.3.1   && < 0.5 ,+        system-fileio        >= 0.2.1   && < 0.4 ,+        stm                                < 2.5 ,+        temporary                          < 1.3 ,+        text                               < 1.3 ,+        time                               < 1.7 ,+        transformers         >= 0.2.0.0 && < 0.6 ,+        optparse-applicative >= 0.13    && < 0.14,+        optional-args        >= 1.0     && < 2.0 ,+        unix-compat          >= 0.4     && < 0.5     if os(windows)         Build-Depends: Win32 >= 2.2.0.1 && < 2.4     else         Build-Depends: unix  >= 2.5.1.0 && < 2.8     Exposed-Modules:         Turtle,+        Turtle.Bytes,         Turtle.Format,         Turtle.Pattern,         Turtle.Shell,         Turtle.Options,+        Turtle.Line,         Turtle.Prelude,         Turtle.Tutorial+    Other-Modules:+        Turtle.Internal     GHC-Options: -Wall     Default-Language: Haskell2010 @@ -89,6 +98,16 @@         base    >= 4   && < 5   ,         doctest >= 0.7 && < 0.12 +test-suite regression-broken-pipe+    Type: exitcode-stdio-1.0+    HS-Source-Dirs: test+    Main-Is: RegressionBrokenPipe.hs+    GHC-Options: -Wall -threaded+    Default-Language: Haskell2010+    Build-Depends:+        base    >= 4   && < 5   ,+        turtle+ benchmark bench     Type: exitcode-stdio-1.0     HS-Source-Dirs: bench@@ -97,6 +116,9 @@     Default-Language: Haskell2010     Build-Depends:         base      >= 4   && < 5  ,-        criterion >= 0.4 && < 2  ,         text                < 1.3,         turtle+    if impl(ghc < 7.8)+        Build-Depends: criterion >= 0.4 && < 1.1.4.0+    else+        Build-Depends: criterion >= 0.4 && < 1.2