turtle 1.3.6 → 1.4.0
raw patch · 5 files changed
+142/−59 lines, 5 files
Files
- CHANGELOG.md +11/−0
- src/Turtle/Bytes.hs +19/−6
- src/Turtle/Prelude.hs +104/−52
- src/Turtle/Tutorial.hs +7/−0
- turtle.cabal +1/−1
CHANGELOG.md view
@@ -1,3 +1,14 @@+1.4.0++* BREAKING CHANGE: Remove unnecessary `Maybe` from type of `single`+* BREAKING CHANGE: Consolidate `searchable` and `executable`+* `stream{,WithErr}` now throws an `ExitCode` on failure++1.3.6++* Build against `ghc-8.2`+* Relax upper bound on `optparse-applicative` and `foldl`+ 1.3.5 * Increase upper bound on `foldl`
src/Turtle/Bytes.hs view
@@ -485,9 +485,18 @@ -- ^ Chunks of bytes read from process output inshell cmd = stream (Process.shell (Data.Text.unpack cmd)) +waitForProcessThrows :: Process.ProcessHandle -> IO ()+waitForProcessThrows ph = do+ exitCode <- Process.waitForProcess ph+ case exitCode of+ ExitSuccess -> return ()+ ExitFailure _ -> Exception.throwIO exitCode+ {-| `stream` generalizes `inproc` and `inshell` by allowing you to supply your own custom `CreateProcess`. This is for advanced users who feel comfortable using the lower-level @process@ API++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} stream :: Process.CreateProcess@@ -527,11 +536,13 @@ (Managed.managed (\k -> Exception.mask (\restore -> Async.withAsync (feedIn restore) k ) ))- inhandle hOut <|> (liftIO (Process.waitForProcess ph *> halt a) *> empty)+ inhandle hOut <|> (liftIO (waitForProcessThrows ph *> halt a) *> empty) {-| `streamWithErr` generalizes `inprocWithErr` and `inshellWithErr` by allowing you to supply your own custom `CreateProcess`. This is for advanced users who feel comfortable using the lower-level @process@ API++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} streamWithErr :: Process.CreateProcess@@ -611,12 +622,13 @@ _ <- r return () let waitAll = STM.atomically (Async.waitSTM a `also` (Async.waitSTM b `also` Async.waitSTM c))- drain <|> (liftIO (Process.waitForProcess ph *> waitAll) *> empty)+ drain <|> (liftIO (waitForProcessThrows 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+ @stderr@ are wrapped in `Left`.++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} inprocWithErr :: Text@@ -633,11 +645,12 @@ {-| 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+ chunks from @stderr@ are wrapped in `Left`. This command is more powerful than `inprocWithErr`, but highly vulnerable to code injection if you template the command line with untrusted input++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} inshellWithErr :: Text
src/Turtle/Prelude.hs view
@@ -214,7 +214,7 @@ , systemStrictWithErr -- * Permissions- , Permissions+ , Permissions(..) , chmod , getmod , setmod@@ -222,8 +222,7 @@ , readable, nonreadable , writable, nonwritable , executable, nonexecutable- , searchable, nonsearchable- , ooo,roo,owo,oox,oos,rwo,rox,ros,owx,rwx,rws+ , ooo,roo,owo,oox,rwo,rox,owx,rwx -- * File size , du@@ -307,7 +306,7 @@ lookupEnv, #endif getEnvironment )-import System.Directory (Permissions)+import qualified System.Directory import qualified System.Directory as Directory import System.Exit (ExitCode(..), exitWith) import System.IO (Handle, hClose)@@ -669,9 +668,18 @@ -- ^ Lines of standard output inshell cmd = stream (Process.shell (unpack cmd)) +waitForProcessThrows :: Process.ProcessHandle -> IO ()+waitForProcessThrows ph = do+ exitCode <- Process.waitForProcess ph+ case exitCode of+ ExitSuccess -> return ()+ ExitFailure _ -> Control.Exception.throwIO exitCode+ {-| `stream` generalizes `inproc` and `inshell` by allowing you to supply your own custom `CreateProcess`. This is for advanced users who feel comfortable using the lower-level @process@ API++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} stream :: Process.CreateProcess@@ -706,11 +714,13 @@ a <- using (managed (\k -> mask (\restore -> withAsync (feedIn restore) (restore . k))))- inhandle hOut <|> (liftIO (Process.waitForProcess ph *> halt a) *> empty)+ inhandle hOut <|> (liftIO (waitForProcessThrows ph *> halt a) *> empty) {-| `streamWithErr` generalizes `inprocWithErr` and `inshellWithErr` by allowing you to supply your own custom `CreateProcess`. This is for advanced users who feel comfortable using the lower-level @process@ API++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} streamWithErr :: Process.CreateProcess@@ -783,12 +793,13 @@ _ <- r return () let waitAll = STM.atomically (waitSTM a `also` (waitSTM b `also` waitSTM c))- drain <|> (liftIO (Process.waitForProcess ph *> waitAll) *> empty)+ drain <|> (liftIO (waitForProcessThrows ph *> waitAll) *> empty) {-| Run a command using the shell, streaming @stdout@ and @stderr@ as lines of `Text`. Lines from @stdout@ are wrapped in `Right` and lines from @stderr@- are wrapped in `Left`. This does /not/ throw an exception if the command- returns a non-zero exit code+ are wrapped in `Left`.++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} inprocWithErr :: Text@@ -802,14 +813,14 @@ inprocWithErr cmd args = streamWithErr (Process.proc (unpack cmd) (map unpack args)) - {-| Run a command line using the shell, streaming @stdout@ and @stderr@ as lines of `Text`. Lines from @stdout@ are wrapped in `Right` and lines from- @stderr@ are wrapped in `Left`. This does /not/ throw an exception if the- command returns a non-zero exit code+ @stderr@ are wrapped in `Left`. This command is more powerful than `inprocWithErr`, but highly vulnerable to code injection if you template the command line with untrusted input++ Throws an `ExitCode` exception if the command returns a non-zero exit code -} inshellWithErr :: Text@@ -1109,11 +1120,64 @@ #endif else output file empty ) +{-| This type is the same as @"System.Directory".`System.Directory.Permissions`@+ type except combining the `System.Directory.executable` and+ `System.Directory.searchable` fields into a single `executable` field for+ consistency with the Unix @chmod@. This simplification is still entirely+ consistent with the behavior of "System.Directory", which treats the two+ fields as interchangeable.+-}+data Permissions = Permissions+ { _readable :: Bool+ , _writable :: Bool+ , _executable :: Bool+ } deriving (Eq, Read, Ord, Show)++{-| Under the hood, "System.Directory" does not distinguish between+ `System.Directory.executable` and `System.Directory.searchable`. They both+ translate to the same `System.Posix.ownerExecuteMode` permission. That+ means that we can always safely just set the `System.Directory.executable`+ field and safely leave the `System.Directory.searchable` field as `False`+ because the two fields are combined with (`||`) to determine whether to set+ the executable bit.+-}+toSystemDirectoryPermissions :: Permissions -> System.Directory.Permissions+toSystemDirectoryPermissions p =+ ( System.Directory.setOwnerReadable (_readable p)+ . System.Directory.setOwnerWritable (_writable p)+ . System.Directory.setOwnerExecutable (_executable p)+ ) System.Directory.emptyPermissions++fromSystemDirectoryPermissions :: System.Directory.Permissions -> Permissions+fromSystemDirectoryPermissions p = Permissions+ { _readable = System.Directory.readable p+ , _writable = System.Directory.writable p+ , _executable =+ System.Directory.executable p || System.Directory.searchable p+ }+ {-| Update a file or directory's user permissions -> chmod rwo "foo.txt" -- chmod u=rw foo.txt-> chmod executable "foo.txt" -- chmod u+x foo.txt-> chmod nonwritable "foo.txt" -- chmod u-w foo.txt+> chmod rwo "foo.txt" -- chmod u=rw foo.txt+> chmod executable "foo.txt" -- chmod u+x foo.txt+> chmod nonwritable "foo.txt" -- chmod u-w foo.txt++ The meaning of each permission is:++ * `readable` (@+r@ for short): For files, determines whether you can read+ from that file (such as with `input`). For directories, determines+ whether or not you can list the directory contents (such as with `ls`).+ Note: if a directory is not readable then `ls` will stream an empty list+ of contents++ * `writable` (@+w@ for short): For files, determines whether you can write+ to that file (such as with `output`). For directories, determines whether+ you can create a new file underneath that directory.++ * `executable` (@+x@ for short): For files, determines whether or not that+ file is executable (such as with `proc`). For directories, determines+ whether or not you can read or execute files underneath that directory+ (such as with `input` or `proc`) -} chmod :: MonadIO io@@ -1126,22 +1190,25 @@ chmod modifyPermissions path = liftIO (do let path' = deslash (Filesystem.encodeString path) permissions <- Directory.getPermissions path'- let permissions' = modifyPermissions permissions- changed = permissions /= permissions'- when changed (Directory.setPermissions path' permissions')+ let permissions' = fromSystemDirectoryPermissions permissions+ let permissions'' = modifyPermissions permissions'+ changed = permissions' /= permissions''+ let permissions''' = toSystemDirectoryPermissions permissions'+ when changed (Directory.setPermissions path' permissions''') return permissions' ) -- | Get a file or directory's user permissions getmod :: MonadIO io => FilePath -> io Permissions getmod path = liftIO (do let path' = deslash (Filesystem.encodeString path)- Directory.getPermissions path' )+ permissions <- Directory.getPermissions path'+ return (fromSystemDirectoryPermissions permissions)) -- | Set a file or directory's user permissions setmod :: MonadIO io => Permissions -> FilePath -> io () setmod permissions path = liftIO (do let path' = deslash (Filesystem.encodeString path)- Directory.setPermissions path' permissions )+ Directory.setPermissions path' (toSystemDirectoryPermissions permissions) ) -- | Copy a file or directory's permissions (analogous to @chmod --reference@) copymod :: MonadIO io => FilePath -> FilePath -> io ()@@ -1152,39 +1219,35 @@ -- | @+r@ readable :: Permissions -> Permissions-readable = Directory.setOwnerReadable True+readable p = p { _readable = True } -- | @-r@ nonreadable :: Permissions -> Permissions-nonreadable = Directory.setOwnerReadable False+nonreadable p = p { _readable = False } -- | @+w@ writable :: Permissions -> Permissions-writable = Directory.setOwnerWritable True+writable p = p { _writable = True } -- | @-w@ nonwritable :: Permissions -> Permissions-nonwritable = Directory.setOwnerWritable False+nonwritable p = p { _writable = False } -- | @+x@ executable :: Permissions -> Permissions-executable = Directory.setOwnerExecutable True+executable p = p { _executable = True } -- | @-x@ nonexecutable :: Permissions -> Permissions-nonexecutable = Directory.setOwnerExecutable False---- | @+s@-searchable :: Permissions -> Permissions-searchable = Directory.setOwnerSearchable True---- | @-s@-nonsearchable :: Permissions -> Permissions-nonsearchable = Directory.setOwnerSearchable False+nonexecutable p = p { _executable = False } -- | @-r -w -x@ ooo :: Permissions -> Permissions-ooo = const Directory.emptyPermissions+ooo _ = Permissions+ { _readable = False+ , _writable = False+ , _executable = False+ } -- | @+r -w -x@ roo :: Permissions -> Permissions@@ -1198,10 +1261,6 @@ oox :: Permissions -> Permissions oox = executable . ooo --- | @-r -w +s@-oos :: Permissions -> Permissions-oos = searchable . ooo- -- | @+r +w -x@ rwo :: Permissions -> Permissions rwo = readable . writable . ooo@@ -1210,10 +1269,6 @@ rox :: Permissions -> Permissions rox = readable . executable . ooo --- | @+r -w +s@-ros :: Permissions -> Permissions-ros = readable . searchable . ooo- -- | @-r +w +x@ owx :: Permissions -> Permissions owx = writable . executable . ooo@@ -1222,10 +1277,6 @@ rwx :: Permissions -> Permissions rwx = readable . writable . executable . ooo --- | @+r +w +s@-rws :: Permissions -> Permissions-rws = readable . writable . searchable . ooo- {-| Time how long a command takes in monotonic wall clock time Returns the duration alongside the return value@@ -1256,15 +1307,16 @@ True <- testfile path' - let handler :: IOError -> IO Permissions+ let handler :: IOError -> IO Bool handler e = if isPermissionError e || isDoesNotExistError e- then return Directory.emptyPermissions+ then return False else throwIO e - perms <- liftIO (getmod path' `catchIOError` handler)+ let getIsExecutable = fmap _executable (getmod path')+ isExecutable <- liftIO (getIsExecutable `catchIOError` handler) - guard (Directory.executable perms)+ guard isExecutable return path' {-| Sleep for the given duration@@ -1872,11 +1924,11 @@ -- > main = do -- > Just directory <- single (inshell "pwd" empty) -- > print directory-single :: MonadIO io => Shell a -> io (Maybe a)+single :: MonadIO io => Shell a -> io a single s = do as <- fold s Control.Foldl.list case as of- [a] -> return (Just a)+ [a] -> return a _ -> do let msg = format ("single: expected 1 line of input but there were "%d%" lines of input") (length as) die msg
src/Turtle/Tutorial.hs view
@@ -1818,3 +1818,10 @@ -- /Answer:/ Use `runManaged`, `sh`, or (`<|>`) (all resources acquired in the -- left stream will close before beginning the right stream). Alternatively, -- use `with` to acquire a resource for a limited scope.+--+-- /Question:/ How do I use @turtle@ to run another shell as a subprocess?+--+-- /Answer:/ Use `Turtle.system` in conjunction with the `process` library,+-- like this:+--+-- > Turtle.system (System.Process.proc "/bin/sh" []) empty
turtle.cabal view
@@ -1,5 +1,5 @@ Name: turtle-Version: 1.3.6+Version: 1.4.0 Cabal-Version: >=1.10 Build-Type: Simple License: BSD3