shelly 0.7.1 → 0.8.0.1
raw patch · 2 files changed
+101/−40 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Shelly: instance [incoherent] Exception e => Exception (ReThrownException e)
+ Shelly: instance [incoherent] Exception e => Show (ReThrownException e)
+ Shelly: instance [incoherent] Typeable1 ReThrownException
+ Shelly: print_stdout :: ShIO a -> ShIO a
+ Shelly: tag :: ShIO a -> Text -> ShIO a
+ Shelly: trace :: Text -> ShIO ()
Files
- Shelly.hs +90/−32
- shelly.cabal +11/−8
Shelly.hs view
@@ -21,7 +21,7 @@ module Shelly ( -- * Entering ShIO.- ShIO, shelly, sub, silently, verbosely, jobs, print_commands+ ShIO, shelly, sub, silently, verbosely, print_stdout, print_commands -- * Running external commands. , run, run_, cmd, (-|-), lastStderr, setStdin@@ -36,6 +36,7 @@ -- * Printing , echo, echo_n, echo_err, echo_n_err, inspect+ , tag, trace -- * Querying filesystem. , ls, ls', test_e, test_f, test_d, test_s, which, find@@ -48,7 +49,7 @@ , readfile, writefile, appendfile, withTmpDir -- * Running external commands asynchronously.- , background, getBgResult, BgResult+ , jobs, background, getBgResult, BgResult -- * exiting the program , exit, errorExit, terror@@ -68,6 +69,7 @@ -- TODO: -- shebang runner that puts wrappers in and invokes+-- perhaps also adds monadloc -- convenience for commands that use record arguments {- let oFiles = ("a.o", "b.o")@@ -120,7 +122,8 @@ import System.PosixCompat.Files( getSymbolicLinkStatus, isSymbolicLink ) import System.Directory ( setPermissions, getPermissions, Permissions(..), getTemporaryDirectory, findExecutable ) -{- GHC won't default to Text with this+{- GHC won't default to Text with this, even with extensions!+ - see: http://hackage.haskell.org/trac/ghc/ticket/6030 class ShellArgs a where toTextArgs :: a -> [Text] @@ -242,11 +245,25 @@ , sStdin :: Maybe Text -- ^ stdin for the command to be run , sStderr :: Text , sDirectory :: FilePath- , sVerbose :: Bool- , sPrintCommands :: Bool -- ^ print out command+ , sPrintStdout :: Bool -- ^ print stdout of command that is executed+ , sPrintCommands :: Bool -- ^ print command that is executed , sRun :: FilePath -> [Text] -> ShIO (Handle, Handle, Handle, ProcessHandle)- , sEnvironment :: [(String, String)] }+ , sEnvironment :: [(String, String)]+ , sTrace :: B.Builder+ } +-- | same as 'trace', but use it combinator style+tag :: ShIO a -> Text -> ShIO a+tag action msg = do+ result <- action+ trace msg+ return result+++-- | log actions that occur+trace :: Text -> ShIO ()+trace msg = modify $ \st -> st { sTrace = sTrace st `mappend` "\n" `mappend` B.fromLazyText msg }+ type ShIO a = ReaderT (IORef State) IO a get :: ShIO State@@ -274,7 +291,7 @@ liftIO $ runInteractiveProcess (unpack exe) (map LT.unpack args) (Just $ unpack $ sDirectory st)- (Just $ sEnvironment st)+ (Just $ sEnvironment st) -- TODO: is PATH buggy? {- -- | use for commands requiring usage of sudo. see 'run_sudo'.@@ -308,6 +325,7 @@ -- operations. You will want to handle these issues differently in those cases. cd :: FilePath -> ShIO () cd dir = do dir' <- absPath dir+ trace $ "cd " `mappend` toTextIgnore dir' modify $ \st -> st { sDirectory = dir' } -- | "cd", execute a ShIO action in the new directory and then pop back to the original directory@@ -350,22 +368,25 @@ mv :: FilePath -> FilePath -> ShIO () mv a b = do a' <- absPath a b' <- absPath b+ trace $ "mv " `mappend` toTextIgnore a' `mappend` " " `mappend` toTextIgnore b' liftIO $ rename a' b' -- | Get back [Text] instead of [FilePath] ls' :: FilePath -> ShIO [Text] ls' fp = do+ trace $ "ls " `mappend` toTextIgnore fp efiles <- ls fp mapM toTextWarn efiles -- | List directory contents. Does *not* include \".\" and \"..\", but it does -- include (other) hidden files. ls :: FilePath -> ShIO [FilePath]-ls = path >=> liftIO . listDirectory+ls = path >=> \fp -> (liftIO $ listDirectory fp) `tag` ("ls " `mappend` toTextIgnore fp) -- | List directory recursively (like the POSIX utility "find"). find :: FilePath -> ShIO [FilePath]-find dir = do bits <- ls dir+find dir = do trace ("find " `mappend` toTextIgnore dir)+ bits <- ls dir subDir <- forM bits $ \x -> do ex <- test_d $ dir FP.</> x sym <- test_s $ dir FP.</> x@@ -402,12 +423,16 @@ -- | Create a new directory (fails if the directory exists). mkdir :: FilePath -> ShIO ()-mkdir = absPath >=> liftIO . createDirectory False+mkdir = absPath >=> \fp -> do+ trace $ "mkdir " `mappend` toTextIgnore fp+ liftIO $ createDirectory False fp `catchany` (\e -> throwIO e >> return ()) -- | Create a new directory, including parents (succeeds if the directory -- already exists). mkdir_p :: FilePath -> ShIO ()-mkdir_p = absPath >=> liftIO . createTree+mkdir_p = absPath >=> \fp -> do+ trace $ "mkdir_p " `mappend` toTextIgnore fp+ liftIO $ createTree fp -- | Get a full path to an executable on @PATH@, if exists. FIXME does not -- respect setenv'd environment and uses @PATH@ inherited from the process@@ -468,7 +493,9 @@ -- | Remove a file. Does not fail if the file already is not there. Does fail -- if the file is not a file. rm_f :: FilePath -> ShIO ()-rm_f f = whenM (test_e f) $ absPath f >>= liftIO . removeFile+rm_f f = whenM (test_e f) $ absPath f >>= \fp -> do+ trace $ "rm_f " `mappend` toTextIgnore fp+ liftIO $ removeFile fp -- | Set an environment variable. The environment is maintained in ShIO -- internally, and is passed to any external commands to be executed.@@ -500,12 +527,15 @@ -- | Create a sub-ShIO in which external command outputs are not echoed. See "sub". silently :: ShIO a -> ShIO a-silently a = sub $ modify (\x -> x { sVerbose = False }) >> a+silently a = sub $ modify (\x -> x { sPrintStdout = False, sPrintCommands = False }) >> a -- | Create a sub-ShIO in which external command outputs are echoed. See "sub". verbosely :: ShIO a -> ShIO a-verbosely a = sub $ modify (\x -> x { sVerbose = True }) >> a+verbosely a = sub $ modify (\x -> x { sPrintStdout = True, sPrintCommands = True }) >> a +print_stdout :: ShIO a -> ShIO a+print_stdout a = sub $ modify (\x -> x { sPrintStdout = True }) >> a+ -- | Create a sub-ShIO which has a 'limit' on the max number of background tasks. -- an invocation of jobs is independent of any others, and not tied to the ShIO monad in any way. -- This blocks the execution of the program until all 'background' jobs are finished.@@ -563,7 +593,9 @@ sub :: ShIO a -> ShIO a sub a = do state <- get- r <- a `catch_sh` (\(e :: SomeException) -> put state >> throw e)+ r <- a `catchany_sh` (\e -> do+ put state+ liftIO $ throwIO e) put state return r @@ -572,19 +604,23 @@ -- processwide working directory or environment are not reflected in the -- running ShIO. shelly :: MonadIO m => ShIO a -> m a-shelly a = do+shelly action = do env <- liftIO getEnvironment dir <- liftIO getWorkingDirectory let def = State { sCode = 0 , sStdin = Nothing , sStderr = LT.empty- , sVerbose = True+ , sPrintStdout = True , sPrintCommands = False , sRun = runInteractiveProcess' , sEnvironment = env+ , sTrace = B.fromText "" , sDirectory = dir } stref <- liftIO $ newIORef def- liftIO $ runReaderT a stref+ let caught =+ action `catchany_sh` \e ->+ get >>= liftIO . throwIO . ReThrownException e . LT.unpack . B.toLazyText . sTrace+ liftIO $ runReaderT caught stref data RunFailed = RunFailed FilePath [Text] Int Text deriving (Typeable) @@ -592,10 +628,15 @@ show (RunFailed exe args code errs) = "error running " ++ unpack exe ++ " " ++ show args ++- ": exit status " ++ show code ++ ":\n" ++ LT.unpack errs+ "\nexit status: " ++ show code ++ "\nstderr: " ++ LT.unpack errs instance Exception RunFailed +data Exception e => ReThrownException e = ReThrownException e String deriving (Typeable)+instance Exception e => Exception (ReThrownException e)+instance Exception e => Show (ReThrownException e) where+ show (ReThrownException ex msg) =+ "Exception: " ++ show ex ++ "\n" ++ msg -- | Execute an external command. Takes the command name (no shell allowed, -- just a name of something that can be found via @PATH@; FIXME: setenv'd@@ -654,7 +695,7 @@ errV <- liftIO newEmptyMVar outV <- liftIO newEmptyMVar- if sVerbose state+ if sPrintStdout state then do liftIO_ $ forkIO $ printGetContent errH stderr >>= putMVar errV liftIO_ $ forkIO $ printFoldHandleLines start cb outH stdout >>= putMVar outV@@ -684,7 +725,8 @@ } case ex of ExitSuccess -> return outs- ExitFailure n -> throw $ RunFailed exe args n errs+ ExitFailure n ->+ liftIO $ throwIO $ RunFailed exe args n errs -- | The output of last external command. See "run". lastStderr :: ShIO Text@@ -697,7 +739,7 @@ -- | Pipe operator. set the stdout the first command as the stdin of the second. (-|-) :: ShIO Text -> ShIO b -> ShIO b one -|- two = do- res <- one+ res <- silently one setStdin res two @@ -735,9 +777,12 @@ -- | Copy a file, or a directory recursively. cp_r :: FilePath -> FilePath -> ShIO () cp_r from to = do- whenM (test_d from) $- mkdir to >> ls from >>= mapM_ (\item -> cp_r (from FP.</> item) (to FP.</> item))- whenM (test_f from) $ cp from to+ trace $ "cp_r " `mappend` toTextIgnore from `mappend` " " `mappend` toTextIgnore to+ from_d <- (test_d from)+ if not from_d then cp from to else do+ unlessM (test_d to) $ mkdir to+ ls from >>= mapM_+ (\item -> cp_r (from FP.</> filename item) (to FP.</> filename item)) -- | Copy a file. The second path could be a directory, in which case the -- original file name is used, in that directory.@@ -745,8 +790,14 @@ cp from to = do from' <- absPath from to' <- absPath to+ trace $ "cp " `mappend` toTextIgnore from' `mappend` " " `mappend` toTextIgnore to' to_dir <- test_d to- liftIO $ copyFile from' $ if to_dir then to' FP.</> filename from else to'+ let to_loc = if to_dir then to' FP.</> filename from else to'+ liftIO $ copyFile from' to_loc `catchany` (\e -> throwIO $+ ReThrownException e (extraMsg to_loc from')+ )+ where+ extraMsg t f = "when copying from: " ++ unpack f ++ " to: " ++ unpack t class PredicateLike pattern hay where match :: pattern -> hay -> Bool@@ -758,7 +809,7 @@ match pat = (pat `isInfixOf`) -- | Like filter, but more conveniently used with String lists, where a--- substring match (TODO: also provide regexps, and maybe globs) is expressed as+-- substring match (TODO: also provide globs) is expressed as -- @grep \"needle\" [ \"the\", \"stack\", \"of\", \"hay\" ]@. Boolean -- predicates just like with "filter" are supported too: -- @grep (\"fun\" `isPrefixOf`) [...]@.@@ -773,6 +824,7 @@ -- computation. The directory is nuked afterwards. withTmpDir :: (FilePath -> ShIO a) -> ShIO a withTmpDir act = do+ trace "withTmpDir" dir <- liftIO getTemporaryDirectory tid <- liftIO myThreadId (pS, handle) <- liftIO $ openTempFile dir ("tmp"++filter isAlphaNum (show tid))@@ -780,21 +832,27 @@ liftIO $ hClose handle -- required on windows rm_f p mkdir p- a <- act p`catch_sh` \(e :: SomeException) -> rm_rf p >> throw e+ a <- act p`catchany_sh` \e -> do+ rm_rf p >> liftIO (throwIO e) rm_rf p return a -- | Write a Lazy Text to a file. writefile :: FilePath -> Text -> ShIO ()-writefile f bits = absPath f >>= \f' -> liftIO (TIO.writeFile (unpack f') bits)+writefile f bits = absPath f >>= \f' -> do+ trace $ "writefile " `mappend` toTextIgnore f'+ liftIO (TIO.writeFile (unpack f') bits) -- | Append a Lazy Text to a file. appendfile :: FilePath -> Text -> ShIO ()-appendfile f bits = absPath f >>= \f' -> liftIO (TIO.appendFile (unpack f') bits)+appendfile f bits = absPath f >>= \f' -> do+ trace $ "appendfile " `mappend` toTextIgnore f'+ liftIO (TIO.appendFile (unpack f') bits) -- | (Strictly) read file into a Text. -- All other functions use Lazy Text. -- So Internally this reads a file as strict text and then converts it to lazy text, which is inefficient readfile :: FilePath -> ShIO Text-readfile =- absPath >=> fmap LT.fromStrict . liftIO . STIO.readFile . unpack+readfile = absPath >=> \fp -> do+ trace $ "appendfile " `mappend` toTextIgnore fp+ (fmap LT.fromStrict . liftIO . STIO.readFile . unpack) fp
shelly.cabal view
@@ -1,22 +1,25 @@ Name: shelly -Version: 0.7.1+Version: 0.8.0.1 Synopsis: shell-like (systems) programming in Haskell -Description: Shelly is a package provides a single module for convenient+Description: Shelly provides a single module for convenient systems programming in Haskell, similar in spirit to POSIX- shells.+ shells. Shelly: .- * Shelly is aimed at getting things done rather than+ * is aimed at convenience and getting things done rather than being a demonstration of elegance. .- * Shelly maintains its own environment, making it thread-safe.+ * has detailed and useful error messages .- * Shelly is modern. It uses Text and system-filepath/system-fileio+ * maintains its own environment, making it thread-safe. .- * Shelly is aimed at convenience and newer Haskell users+ * is modern. It uses Text and system-filepath/system-fileio .- Shelly is a fork of Shellish that features low memory usage, bug fixes, and modernization+ * is aimed at convenience and newer Haskell users+ .+ Shelly is forked from the Shellish package.+ Homepage: https://github.com/yesodweb/Shelly.hs License: BSD3