turtle 1.2.5 → 1.2.6
raw patch · 5 files changed
+163/−62 lines, 5 filesdep ~doctestdep ~foldldep ~managed
Dependency ranges changed: doctest, foldl, managed, process
Files
- src/Turtle/Format.hs +3/−2
- src/Turtle/Prelude.hs +105/−45
- src/Turtle/Shell.hs +7/−8
- src/Turtle/Tutorial.hs +43/−2
- turtle.cabal +5/−5
src/Turtle/Format.hs view
@@ -66,6 +66,7 @@ ) where import Control.Category (Category(..))+import Control.Monad.IO.Class (MonadIO(..)) import Data.Monoid ((<>)) import Data.String (IsString(..)) import Data.Text (Text, pack)@@ -106,8 +107,8 @@ >>> printf ("Hello, "%s%"!\n") "world" Hello, world! -}-printf :: Format (IO ()) r -> r-printf fmt = fmt >>- Text.putStr+printf :: MonadIO io => Format (io ()) r -> r+printf fmt = fmt >>- (liftIO . Text.putStr) -- | Create your own format specifier makeFormat :: (a -> Text) -> Format r (a -> r)
src/Turtle/Prelude.hs view
@@ -105,15 +105,11 @@ module Turtle.Prelude ( -- * IO- proc- , shell- , procs- , shells- , procStrict- , shellStrict- , echo+ echo , err , readline+ , Filesystem.readTextFile+ , Filesystem.writeTextFile , arguments #if MIN_VERSION_base(4,7,0) , export@@ -196,11 +192,21 @@ -- * Text , cut + -- * Subprocess management+ , proc+ , shell+ , system+ , procs+ , shells+ , procStrict+ , shellStrict+ -- * Permissions , Permissions , chmod , getmod , setmod+ , copymod , readable, nonreadable , writable, nonwritable , executable, nonexecutable@@ -238,7 +244,7 @@ import qualified Control.Foldl.Text import Control.Monad (liftM, msum, when, unless) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Managed (Managed, managed, runManaged)+import Control.Monad.Managed (MonadManaged(..), managed, runManaged) #ifdef mingw32_HOST_OS import Data.Bits ((.&.)) #endif@@ -276,7 +282,14 @@ #ifdef mingw32_HOST_OS import qualified System.Win32 as Win32 #else-import System.Posix (openDirStream, readDirStream, closeDirStream, touchFile)+import System.Posix (+ openDirStream,+ readDirStream,+ closeDirStream,+ touchFile,+ getSymbolicLinkStatus,+ isDirectory,+ isSymbolicLink ) #endif import Prelude hiding (FilePath) @@ -298,7 +311,13 @@ -- ^ Lines of standard input -> io ExitCode -- ^ Exit code-proc cmd args = system (Process.proc (unpack cmd) (map unpack args))+proc cmd args =+ system+ ( (Process.proc (unpack cmd) (map 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 @@ -315,7 +334,13 @@ -- ^ Lines of standard input -> io ExitCode -- ^ Exit code-shell cmdLine = system (Process.shell (unpack cmdLine))+shell cmdLine =+ system+ ( (Process.shell (unpack cmdLine))+ { Process.std_in = Process.CreatePipe+ , Process.std_out = Process.Inherit+ , Process.std_err = Process.Inherit+ } ) data ProcFailed = ProcFailed { procCommand :: Text@@ -403,6 +428,10 @@ -- ^ Exit code and stdout shellStrict cmdLine = systemStrict (Process.shell (Text.unpack cmdLine)) +{-| `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@@ -412,14 +441,8 @@ -> io ExitCode -- ^ Exit code system p s = liftIO (do- let p' = p- { Process.std_in = Process.CreatePipe- , Process.std_out = Process.Inherit- , Process.std_err = Process.Inherit- }- let open = do- (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p'+ (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p IO.hSetBuffering hIn IO.LineBuffering return (hIn, ph) @@ -619,7 +642,8 @@ {-| 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`.+ are wrapped in `Left`. This does /not/ throw an exception if the command+ returns a non-zero exit code -} inprocWithErr :: Text@@ -629,14 +653,15 @@ -> Shell Text -- ^ Lines of standard input -> Shell (Either Text Text)- -- ^ Lines of standard output+ -- ^ Lines of either standard output (`Right`) or standard error (`Left`) 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`.+ @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@@ -647,7 +672,7 @@ -> Shell Text -- ^ Lines of standard input -> Shell (Either Text Text)- -- ^ Lines of standard output+ -- ^ Lines of either standard output (`Right`) or standard error (`Left`) inshellWithErr cmd = streamWithErr (Process.shell (unpack cmd)) -- | Print to @stdout@@@ -778,7 +803,7 @@ lstree :: FilePath -> Shell FilePath lstree path = do child <- ls path- isDir <- liftIO (testdir child)+ isDir <- testdir child if isDir then return child <|> lstree child else return child@@ -792,7 +817,7 @@ lsif :: (FilePath -> IO Bool) -> FilePath -> Shell FilePath lsif predicate path = do child <- ls path- isDir <- liftIO (testdir child)+ isDir <- testdir child if isDir then do continue <- liftIO (predicate child)@@ -846,7 +871,31 @@ Use at your own risk -} rmtree :: MonadIO io => FilePath -> io ()-rmtree path = liftIO (Filesystem.removeTree path)+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+ if isLink+ then rm path+ else do+ if isDir+ then (do+ child <- ls path+ loop child ) <|> rmdir path+ else rm path+#endif -- | Check if a file exists testfile :: MonadIO io => FilePath -> io Bool@@ -924,6 +973,13 @@ let path' = deslash (Filesystem.encodeString path) Directory.setPermissions path' permissions ) +-- | Copy a file or directory's permissions (analogous to @chmod --reference@)+copymod :: MonadIO io => FilePath -> FilePath -> io ()+copymod sourcePath targetPath = liftIO (do+ let sourcePath' = deslash (Filesystem.encodeString sourcePath)+ targetPath' = deslash (Filesystem.encodeString targetPath)+ Directory.copyPermissions sourcePath' targetPath' )+ -- | @+r@ readable :: Permissions -> Permissions readable = Directory.setOwnerReadable True@@ -1066,16 +1122,17 @@ Deletes the temporary directory when done -} mktempdir- :: FilePath+ :: MonadManaged managed+ => FilePath -- ^ Parent directory -> Text -- ^ Directory name template- -> Managed FilePath-mktempdir parent prefix = do+ -> managed FilePath+mktempdir parent prefix = using (do let parent' = Filesystem.encodeString parent let prefix' = unpack prefix dir' <- managed (withTempDirectory parent' prefix')- return (Filesystem.decodeString dir')+ return (Filesystem.decodeString dir')) {-| Create a temporary file underneath the given directory @@ -1087,39 +1144,41 @@ simpler API if you don't need to worry about that possibility. -} mktemp- :: FilePath+ :: MonadManaged managed+ => FilePath -- ^ Parent directory -> Text -- ^ File name template- -> Managed (FilePath, Handle)-mktemp parent prefix = do+ -> managed (FilePath, Handle)+mktemp parent prefix = using (do let parent' = Filesystem.encodeString parent let prefix' = unpack prefix (file', handle) <- managed (\k -> withTempFile parent' prefix' (\file' handle -> k (file', handle)) )- return (Filesystem.decodeString file', handle)+ return (Filesystem.decodeString file', handle) ) {-| Create a temporary file underneath the given directory Deletes the temporary file when done -} mktempfile- :: FilePath+ :: MonadManaged managed+ => FilePath -- ^ Parent directory -> Text -- ^ File name template- -> Managed FilePath-mktempfile parent prefix = do+ -> managed FilePath+mktempfile parent prefix = using (do let parent' = Filesystem.encodeString parent let prefix' = unpack prefix (file', handle) <- managed (\k -> withTempFile parent' prefix' (\file' handle -> k (file', handle)) ) liftIO (hClose handle)- return (Filesystem.decodeString file')+ return (Filesystem.decodeString file') ) -- | Fork a thread, acquiring an `Async` value-fork :: IO a -> Managed (Async a)-fork io = managed (withAsync io)+fork :: MonadManaged managed => IO a -> managed (Async a)+fork io = using (managed (withAsync io)) -- | Read lines of `Text` from standard input stdin :: Shell Text@@ -1182,16 +1241,16 @@ strict s = liftM Text.unlines (fold s list) -- | Acquire a `Managed` read-only `Handle` from a `FilePath`-readonly :: FilePath -> Managed Handle-readonly file = managed (Filesystem.withTextFile file IO.ReadMode)+readonly :: MonadManaged managed => FilePath -> managed Handle+readonly file = using (managed (Filesystem.withTextFile file IO.ReadMode)) -- | Acquire a `Managed` write-only `Handle` from a `FilePath`-writeonly :: FilePath -> Managed Handle-writeonly file = managed (Filesystem.withTextFile file IO.WriteMode)+writeonly :: MonadManaged managed => FilePath -> managed Handle+writeonly file = using (managed (Filesystem.withTextFile file IO.WriteMode)) -- | Acquire a `Managed` append-only `Handle` from a `FilePath`-appendonly :: FilePath -> Managed Handle-appendonly file = managed (Filesystem.withTextFile file IO.AppendMode)+appendonly :: MonadManaged managed => FilePath -> managed Handle+appendonly file = using (managed (Filesystem.withTextFile file IO.AppendMode)) -- | Combine the output of multiple `Shell`s, in order cat :: [Shell a] -> Shell a@@ -1235,6 +1294,7 @@ (tmpfile, handle) <- mktemp here "turtle" outhandle handle (sed pattern (input file)) liftIO (hClose handle)+ copymod file tmpfile mv tmpfile file ))
src/Turtle/Shell.hs view
@@ -73,7 +73,7 @@ import Control.Applicative import Control.Monad (MonadPlus(..), ap) import Control.Monad.IO.Class (MonadIO(..))-import Control.Monad.Managed (Managed, with)+import Control.Monad.Managed (MonadManaged(..), with) import Control.Foldl (Fold(..), FoldM(..)) import qualified Control.Foldl as Foldl import Data.Monoid@@ -143,6 +143,12 @@ x' <- step x a done x' ) +instance MonadManaged Shell where+ using resource = Shell (\(FoldM step begin done) -> do+ x <- begin+ x' <- with resource (step x)+ done x' )+ instance Monoid a => Monoid (Shell a) where mempty = pure mempty mappend = liftA2 mappend@@ -165,10 +171,3 @@ x' <- step x a k $! x' foldr step' done as $! x0 )---- | Acquire a `Managed` resource within a `Shell` in an exception-safe way-using :: Managed a -> Shell a-using resource = Shell (\(FoldM step begin done) -> do- x <- begin- x' <- with resource (step x)- done x' )
src/Turtle/Tutorial.hs view
@@ -106,6 +106,9 @@ -- * MonadIO -- $monadio + -- * MonadManaged+ -- $monadmanaged+ -- * Command line options -- $cmdline @@ -770,7 +773,7 @@ -- @ -- `proc` -- :: Text -- Program--- :: [Text] -- Arguments+-- -> [Text] -- Arguments -- -> Shell Text -- Standard input (as lines of \`Text\`) -- -> IO ExitCode -- Exit code of the shell command -- @@@ -810,8 +813,15 @@ -- -- > format fp :: FilePath -> Text --+-- For the very common case where you `echo` a formatted value, you can use+-- `printf`:+--+-- >>> printf ("Hello, "%s%"!\n") "world"+-- Hello, world!+-- -- If you are interested in this feature, check out the "Turtle.Format" module--- for more details.+-- for more details. For more complex string formatting needs, check out the+-- @text-format@ library. -- $streams -- The @turtle@ library provides support for streaming computations, just like@@ -1440,6 +1450,7 @@ -- > Applicative Shell -- > Alternative Shell -- > MonadIO Shell+-- > MonadManaged Shell -- > ... -- -- These instances represent the overloaded functions associated with `Shell`@@ -1450,6 +1461,31 @@ -- way (such as `print`), so you will still occasionally need to wrap -- subroutines in `liftIO`. +-- $monadmanaged+--+-- All `Managed` operations are also overloaded in @turtle@, meaning that you+-- can omit the `using` command. For example, we could change our last example+-- to:+--+-- > #!/usr/bin/env stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > main = sh (do+-- > dir <- mktempdir "/tmp" "turtle"+-- > (file, _) <- mktemp dir "turtle"+-- > liftIO (print file)+-- > die "Urk!" )+--+-- Any command that is generalized over the `MonadManaged` interface can run+-- in the following contexts:+--+-- * `Managed` (obviously)+-- * `Shell`+ -- $cmdline -- -- The "Turtle.Options" module lets you easily parse command line arguments,@@ -1694,6 +1730,11 @@ -- $faq -- -- These are the most frequently asked questions from new users:+--+-- /Question:/ My program hangs when I run a subprocess that reads from standard+-- input. What do I do?+--+-- /Answer:/ Make sure you compile your program with the @-threaded@ flag -- -- /Question:/ How do I convert `FilePath` to `Text`? --
turtle.cabal view
@@ -1,5 +1,5 @@ Name: turtle-Version: 1.2.5+Version: 1.2.6 Cabal-Version: >=1.10 Build-Type: Simple License: BSD3@@ -50,10 +50,10 @@ async >= 2.0.0.0 && < 2.2, clock >= 0.4.1.2 && < 0.7, directory >= 1.0.7 && < 1.3,- foldl >= 1.1 && < 1.2,+ foldl >= 1.1 && < 1.3, hostname < 1.1,- managed < 1.1,- process >= 1.0.1.1 && < 1.3,+ 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,@@ -86,7 +86,7 @@ Default-Language: Haskell2010 Build-Depends: base >= 4 && < 5 ,- doctest >= 0.9.12 && < 0.11+ doctest >= 0.9.12 && < 0.12 benchmark bench Type: exitcode-stdio-1.0