shelly 0.13.0.1 → 0.13.1
raw patch · 4 files changed
+778/−34 lines, 4 files
Files
- Shelly.hs +83/−27
- Shelly/Base.hs +16/−4
- Shelly/Pipe.hs +676/−0
- shelly.cabal +3/−3
Shelly.hs view
@@ -25,7 +25,7 @@ module Shelly ( -- * Entering Sh.- Sh, ShIO, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands+ Sh, ShIO, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands, tracing -- * Running external commands. , run, run_, cmd, (-|-), lastStderr, setStdin@@ -33,7 +33,7 @@ , sshPairs, sshPairs_ -- * Modifying and querying environment.- , setenv, getenv, getenv_def, appendToPath+ , setenv, get_env, get_env_text, getenv, getenv_def, appendToPath -- * Environment directory , cd, chdir, pwd@@ -109,6 +109,7 @@ import qualified Filesystem.Path.CurrentOS as FP import System.Directory ( setPermissions, getPermissions, Permissions(..), getTemporaryDirectory, findExecutable ) +import Data.Char (isDigit) {- GHC won't default to Text with this, even with extensions! - see: http://hackage.haskell.org/trac/ghc/ticket/6030@@ -156,6 +157,7 @@ instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x]) + -- | variadic argument version of run. -- The syntax is more convenient, but more importantly it also allows the use of a FilePath as a command argument. -- So an argument can be a Text or a FilePath.@@ -315,9 +317,14 @@ -- repercussions if you are doing hundreds of thousands of filesystem -- operations. You will want to handle these issues differently in those cases. cd :: FilePath -> Sh ()-cd = absPath >=> \dir -> do- trace $ "cd " `mappend` toTextIgnore dir- modify $ \st -> st { sDirectory = dir }+cd = canonic >=> cd'+ where+ cd' dir = do+ trace $ "cd " `mappend` tdir+ unlessM (test_d dir) $ errorExit $ "not a directory: " `mappend` tdir+ modify $ \st -> st { sDirectory = dir }+ where+ tdir = toTextIgnore dir -- | "cd", execute a Sh action in the new directory and then pop back to the original directory chdir :: FilePath -> Sh a -> Sh a@@ -326,6 +333,10 @@ cd dir action `finally_sh` cd d +-- | chdir, but first create the directory if it does not exit+chdir_p :: FilePath -> Sh a -> Sh a+chdir_p d action = mkdir_p d >> chdir d action+ -- | apply a String IO operations to a Text FilePath {-@@ -360,6 +371,7 @@ exit 0 = liftIO (exitWith ExitSuccess) `tag` "exit 0" exit n = liftIO (exitWith (ExitFailure n)) `tag` ("exit " `mappend` LT.pack (show n)) +-- | echo a message and exit with status 1 errorExit :: Text -> Sh () errorExit msg = echo msg >> exit 1 @@ -389,10 +401,6 @@ (trace . mappend "which " . toTextIgnore) fp (liftIO . findExecutable . unpack >=> return . fmap pack) fp --- | A monadic-conditional version of the "when" guard.-whenM :: Monad m => m Bool -> m () -> m ()-whenM c a = c >>= \res -> when res a- -- | A monadic-conditional version of the "unless" guard. unlessM :: Monad m => m Bool -> m () -> m () unlessM c a = c >>= \res -> unless res a@@ -440,6 +448,7 @@ rm :: FilePath -> Sh () rm = absPath >=> \f -> do trace $ "rm" `mappend` toTextIgnore f+ -- TODO: better error message for removeFile (give filename) canonic f >>= liftIO . removeFile -- | Set an environment variable. The environment is maintained in Sh@@ -460,17 +469,30 @@ where path_env = "PATH" --- | Fetch the current value of an environment variable. Both empty and--- non-existent variables give empty string as a result.+-- | Fetch the current value of an environment variable.+-- if non-existant or empty text, will be Nothing+get_env :: Text -> Sh (Maybe Text)+get_env k = do+ mval <- return . fmap LT.pack . lookup (LT.unpack k) =<< gets sEnvironment+ return $ case mval of+ Nothing -> Nothing+ j@(Just val) -> if LT.null val then Nothing else j+ getenv :: Text -> Sh Text getenv k = getenv_def k ""+{-# DEPRECATED getenv "use get_env or get_env_text" #-} -- | Fetch the current value of an environment variable. Both empty and--- non-existent variables give the default value as a result+-- non-existent variables give empty string as a result.+get_env_text :: Text -> Sh Text+get_env_text k = getenv_def k ""++-- | Fetch the current value of an environment variable. Both empty and+-- non-existent variables give the default Text value as a result getenv_def :: Text -> Text -> Sh Text-getenv_def k d = gets sEnvironment >>=- return . LT.pack . fromMaybe (LT.unpack d) . lookup (LT.unpack k)+getenv_def d = get_env >=> return . fromMaybe d + -- | Create a sub-Sh in which external command outputs are not echoed and -- commands are not printed. -- See 'sub'.@@ -507,6 +529,14 @@ newState <- get put oldState { sTrace = sTrace oldState `mappend` sTrace newState } +-- | Create a sub-Sh where commands are not traced+-- Defaults to True.+-- You should only set to False temporarily for very specific reasons+tracing :: Bool -> Sh a -> Sh a+tracing shouldTrace action = sub $ do+ modify $ \st -> st { sTracing = shouldTrace }+ action+ -- | Create a sub-Sh with shell character escaping on or off. -- Defaults to True. -- Setting to False allows for shell wildcard such as * to be expanded by the shell along with any other special shell characters.@@ -534,6 +564,7 @@ , sPrintCommands = False , sRun = runCommand , sEnvironment = environment+ , sTracing = True , sTrace = B.fromText "" , sDirectory = dir } stref <- liftIO $ newIORef def@@ -549,10 +580,33 @@ liftIO $ runSh caught stref where throwExplainedException :: Exception exception => exception -> Sh a- throwExplainedException ex = get >>=- liftIO . throwIO . ReThrownException ex . errorMsg . LT.unpack . B.toLazyText . sTrace- errorMsg trc = "Ran commands: \n" `mappend` trc+ throwExplainedException ex = get >>= errorMsg >>= liftIO . throwIO . ReThrownException ex+ errorMsg st = do+ let trc = B.toLazyText . sTrace $ st+ d <- pwd+ sf <- shellyFile+ let f = d</>shelly_dir</>sf+ (writefile f trc >> return ("log of commands saved to: " `mappend` encodeString f))+ `catchany_sh` (\_ -> return $ "Ran commands: \n" `mappend` LT.unpack trc) + shelly_dir = ".shelly"+ shellyFile = chdir_p shelly_dir $ do+ fs <- ls "."+ return $ pack $ show (nextNum fs) `mappend` ".txt"++ nextNum :: [FilePath] -> Int+ nextNum [] = 1+ nextNum fs = (+ 1) . maximum . map (readDef 1 . filter isDigit . unpack . filename) $ fs++-- from safe package+readDef :: Read a => a -> String -> a+readDef def = fromMaybe def . readMay+ where+ readMay :: Read a => String -> Maybe a+ readMay s = case [x | (x,t) <- reads s, ("","") <- lex t] of+ [x] -> Just x+ _ -> Nothing+ data RunFailed = RunFailed FilePath [Text] Int Text deriving (Typeable) instance Show RunFailed where@@ -719,19 +773,21 @@ -- | Copy a file, or a directory recursively. cp_r :: FilePath -> FilePath -> Sh () cp_r from' to' = do- fromDir <- absPath from'- from_d <- (test_d fromDir)- if not from_d then cp from' to' else do+ from <- absPath from'+ fromIsDir <- (test_d from)+ if not fromIsDir then cp from' to' else do to <- absPath to'- trace $ "cp -r " `mappend` toTextIgnore fromDir `mappend` " " `mappend` toTextIgnore to+ trace $ "cp -r " `mappend` toTextIgnore from `mappend` " " `mappend` toTextIgnore to toIsDir <- test_d to- toDir <- if toIsDir- then return $ fromDir </> to- else mkdir to >> return to- when (fromDir == toDir) $ liftIO $ throwIO $ userError $ LT.unpack $ "cp_r: " `mappend`- toTextIgnore fromDir `mappend` " and " `mappend` toTextIgnore toDir `mappend` " are identical" - ls fromDir >>= mapM_ (\item -> cp_r (fromDir FP.</> filename item) (toDir FP.</> filename item))+ when (from == to) $ liftIO $ throwIO $ userError $ LT.unpack $ "cp_r: " `mappend`+ toTextIgnore from `mappend` " and " `mappend` toTextIgnore to `mappend` " are identical"++ finalTo <- if not toIsDir then mkdir to >> return to else do+ let d = to </> dirname (addTrailingSlash from)+ mkdir_p d >> return d ++ ls from >>= mapM_ (\item -> cp_r (from FP.</> filename item) (finalTo 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.
Shelly/Base.hs view
@@ -13,7 +13,10 @@ echo, echo_n, echo_err, echo_n_err, inspect, inspect_err, catchany, liftIO, (>=>),- eitherRelativeTo, relativeTo, maybeRelativeTo+ eitherRelativeTo, relativeTo, maybeRelativeTo,+ whenM+ -- * utitlities not yet exported+ , addTrailingSlash ) where import Prelude hiding ( FilePath, catch )@@ -21,7 +24,8 @@ import System.Process( ProcessHandle ) import System.IO ( Handle, hFlush, stderr, stdout ) -import Control.Monad ( (>=>) ) +import Control.Monad (when, (>=>) )+import Control.Applicative(Applicative) import Filesystem (isDirectory, listDirectory) import System.PosixCompat.Files( getSymbolicLinkStatus, isSymbolicLink ) import Filesystem.Path.CurrentOS (FilePath, encodeString, relative)@@ -45,7 +49,7 @@ newtype Sh a = Sh { unSh :: ReaderT (IORef State) IO a- } deriving (Monad, MonadIO, MonadReader (IORef State), Functor)+ } deriving (Applicative, Monad, MonadIO, MonadReader (IORef State), Functor) runSh :: Sh a -> IORef State -> IO a runSh = runReaderT . unSh@@ -58,9 +62,14 @@ , sPrintCommands :: Bool -- ^ print command that is executed , sRun :: FilePath -> [Text] -> Sh (Handle, Handle, Handle, ProcessHandle) , sEnvironment :: [(String, String)]+ , sTracing :: Bool , sTrace :: B.Builder } +-- | A monadic-conditional version of the "when" guard.+whenM :: Monad m => m Bool -> m () -> m ()+whenM c a = c >>= \res -> when res a+ -- | Makes a relative path relative to the current Sh working directory. -- An absolute path is returned as is. -- To create an absolute path, use 'absPath'@@ -111,6 +120,7 @@ Left _ -> Nothing +-- | add a trailing slash to ensure the path indicates a directory addTrailingSlash :: FilePath -> FilePath addTrailingSlash p = if FP.null (FP.filename p) then p else@@ -174,7 +184,9 @@ -- | internally log what occured. -- Log will be re-played on failure. trace :: Text -> Sh ()-trace msg = modify $ \st -> st { sTrace = sTrace st `mappend` B.fromLazyText msg `mappend` "\n" }+trace msg =+ whenM (gets sTracing) $ modify $+ \st -> st { sTrace = sTrace st `mappend` B.fromLazyText msg `mappend` "\n" } -- | List directory contents. Does *not* include \".\" and \"..\", but it does -- include (other) hidden files.
+ Shelly/Pipe.hs view
@@ -0,0 +1,676 @@+{-# LANGUAGE + FlexibleInstances, + TypeSynonymInstances, + TypeFamilies,+ ExistentialQuantification #-}+-- | This module is a wrapper for the module "Shelly". +-- The only difference is a main type "Sh". In this module +-- "Sh" contains a list of results. Actual definition of the type "Sh" is:+--+-- > import qualified Shelly as S+-- >+-- > newtype Sh a = Sh { unSh :: S.Sh [a] }+--+-- This definition can simplify some filesystem commands. +-- A monad bind operator becomes a pipe operator and we can write+--+-- > findExt ext = findWhen (pure . hasExt ext)+-- >+-- > main :: IO ()+-- > main = shs $ do+-- > mkdir "new"+-- > findExt "hs" "." >>= flip cp "new"+-- > findExt "cpp" "." >>= rm_f +-- > liftIO $ putStrLn "done"+--+-- Monad methods "return" and ">>=" behave like methods for+-- @ListT Shelly.Sh@, but ">>" forgets the number of +-- the empty effects. So the last line prints @\"done\"@ only once. +--+-- I highly recommend putting the following at the top of your program,+-- otherwise you will likely need either type annotations or type conversions+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > {-# LANGUAGE ExtendedDefaultRules #-}+-- > {-# OPTIONS_GHC -fno-warn-type-defaults #-}+-- > import Shelly+-- > import Data.Text.Lazy as LT+-- > default (LT.Text)+module Shelly.Pipe+ (+ -- * Entering Sh.+ Sh, shs, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands+ -- * List functions+ , roll, unroll, liftSh+ -- * Running external commands.+ , run, run_, cmd+ , (-|-), lastStderr, setStdin+ , command, command_, command1, command1_+ , sshPairs, sshPairs_+ + -- * Modifying and querying environment.+ , setenv, get_env, get_env_text, getenv_def, appendToPath++ -- * Environment directory+ , cd, chdir, pwd++ -- * Printing+ , echo, echo_n, echo_err, echo_n_err, inspect, inspect_err+ , tag, trace, show_command++ -- * Querying filesystem.+ , ls, lsT, test_e, test_f, test_d, test_s, which++ -- * Filename helpers+ , absPath, (</>), (<.>), canonic, canonicalize, relPath, relativeTo+ , hasExt++ -- * Manipulating filesystem.+ , mv, rm, rm_f, rm_rf, cp, cp_r, mkdir, mkdir_p++ -- * reading/writing Files+ , readfile, writefile, appendfile, touchfile, withTmpDir++ -- * find functions + , find, findWhen, findFold+ , findDirFilter, findDirFilterWhen, findFoldDirFilter++ -- * exiting the program+ , exit, errorExit, terror++ -- * Exceptions+ , catchany, catch_sh, finally_sh + , ShellyHandler(..), catches_sh+ , catchany_sh++ -- * convert between Text and FilePath+ , toTextIgnore, toTextWarn, fromText++ -- * Utilities.+ , (<$>), (<$$>), whenM, unlessM, time++ -- * Re-exported for your convenience+ , liftIO, when, unless, FilePath++ -- * internal functions for writing extensions+ , get, put+ ) where++import Prelude hiding (FilePath)++import Control.Applicative+import Control.Monad+import Control.Monad.Trans+import Control.Exception hiding (handle)++import Filesystem.Path(FilePath)++import qualified Shelly as S++import Shelly(+ (</>), (<.>), hasExt+ , (<$$>), whenM, unlessM, toTextIgnore+ , fromText, catchany)++import Shelly.Base(State)++import Data.Text.Lazy as LT hiding (concat, all, find, cons)++default (LT.Text)+++-- | This type is a simple wrapper for a type "Shelly.Sh".+-- "Sh" contains a list of results. +newtype Sh a = Sh { unSh :: S.Sh [a] }++instance Functor Sh where+ fmap f = Sh . fmap (fmap f) . unSh ++instance Monad Sh where+ return = Sh . return . return + a >>= f = Sh $ fmap concat $ mapM (unSh . f) =<< unSh a+ a >> b = Sh $ unSh a >> unSh b++instance Applicative Sh where+ pure = return+ (<*>) = ap++instance MonadPlus Sh where+ mzero = Sh $ return []+ mplus a b = Sh $ liftA2 (++) (unSh a) (unSh b)++instance MonadIO Sh where+ liftIO = sh1 liftIO++-------------------------------------------------------+-- converters++sh0 :: S.Sh a -> Sh a+sh0 = Sh . fmap return++sh1 :: (a -> S.Sh b) -> (a -> Sh b) +sh1 f = \a -> sh0 (f a)++sh2 :: (a1 -> a2 -> S.Sh b) -> (a1 -> a2 -> Sh b) +sh2 f = \a b -> sh0 (f a b)++sh3 :: (a1 -> a2 -> a3 -> S.Sh b) -> (a1 -> a2 -> a3 -> Sh b) +sh3 f = \a b c -> sh0 (f a b c)++sh4 :: (a1 -> a2 -> a3 -> a4 -> S.Sh b) -> (a1 -> a2 -> a3 -> a4 -> Sh b) +sh4 f = \a b c d -> sh0 (f a b c d)++sh0s :: S.Sh [a] -> Sh a+sh0s = Sh++sh1s :: (a -> S.Sh [b]) -> (a -> Sh b) +sh1s f = \a -> sh0s (f a)++{- Just in case ...+sh2s :: (a1 -> a2 -> S.Sh [b]) -> (a1 -> a2 -> Sh b) +sh2s f = \a b -> sh0s (f a b)++sh3s :: (a1 -> a2 -> a3 -> S.Sh [b]) -> (a1 -> a2 -> a3 -> Sh b) +sh3s f = \a b c -> sh0s (f a b c)+-}++lift1 :: (S.Sh a -> S.Sh b) -> (Sh a -> Sh b)+lift1 f = Sh . (mapM (f . return) =<< ) . unSh++lift2 :: (S.Sh a -> S.Sh b -> S.Sh c) -> (Sh a -> Sh b -> Sh c)+lift2 f a b = Sh $ join $ liftA2 (mapM2 f') (unSh a) (unSh b)+ where f' = \x y -> f (return x) (return y)++mapM2 :: Monad m => (a -> b -> m c)-> [a] -> [b] -> m [c]+mapM2 f as bs = sequence $ liftA2 f as bs ++-----------------------------------------------------------++-- | Unpack list of results.+unroll :: Sh a -> Sh [a]+unroll = Sh . fmap return . unSh ++-- | Pack list of results. It performs "concat" inside "Sh".+roll :: Sh [a] -> Sh a+roll = Sh . fmap concat . unSh++-- | Transform result as list. It can be useful for filtering. +liftSh :: ([a] -> [b]) -> Sh a -> Sh b+liftSh f = Sh . fmap f . unSh++------------------------------------------------------------------+-- Entering Sh++-- | Enter a Sh from (Monad)IO. The environment and working directories are+-- inherited from the current process-wide values. Any subsequent changes in+-- processwide working directory or environment are not reflected in the+-- running Sh.+shelly :: MonadIO m => Sh a -> m [a]+shelly = S.shelly . unSh++-- | Performs "shelly" and then an empty action @return ()@. +shs :: MonadIO m => Sh () -> m ()+shs a = shelly a >> return ()++-- | Enter a sub-Sh that inherits the environment+-- The original state will be restored when the sub-Sh completes.+-- Exceptions are propagated normally.+sub :: Sh a -> Sh a+sub = lift1 S.sub++-- | Create a sub-Sh in which external command outputs are not echoed.+-- Also commands are not printed.+-- See "sub".+silently :: Sh a -> Sh a+silently = lift1 S.silently++-- | Create a sub-Sh in which external command outputs are echoed.+-- Executed commands are printed+-- See "sub".+verbosely :: Sh a -> Sh a+verbosely = lift1 S.verbosely++-- | Create a sub-Sh with shell character escaping on or off+escaping :: Bool -> Sh a -> Sh a+escaping b = lift1 (S.escaping b)++-- | Create a sub-Sh with stdout printing on or off+print_stdout :: Bool -> Sh a -> Sh a+print_stdout b = lift1 (S.print_stdout b)++-- | Create a sub-Sh with command echoing on or off+print_commands :: Bool -> Sh a -> Sh a+print_commands b = lift1 (S.print_commands b)++------------------------------------------------------------------+-- Running external commands. ++-- | 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+-- @PATH@ is not taken into account when finding the exe name)+--+-- "stdout" and "stderr" are collected. The "stdout" is returned as+-- a result of "run", and complete stderr output is available after the fact using+-- "lastStderr" +--+-- All of the stdout output will be loaded into memory+-- You can avoid this but still consume the result by using "run_",+-- If you want to avoid the memory and need to process the output then use "runFoldLines".+run :: FilePath -> [Text] -> Sh Text+run a b = sh0 $ S.run a b++-- | The same as "run", but return () instead of the stdout content.+run_ :: FilePath -> [Text] -> Sh ()+run_ a b = sh0 $ S.run_ a b++-- | Pipe operator. set the stdout the first command as the stdin of the second.+(-|-) :: Sh Text -> Sh b -> Sh b+(-|-) = lift2 (S.-|-)++-- | The output of last external command. See "run".+lastStderr :: Sh Text+lastStderr = sh0 S.lastStderr++-- | set the stdin to be used and cleared by the next "run".+setStdin :: Text -> Sh ()+setStdin = sh1 S.setStdin ++-- | bind some arguments to run for re-use+-- Example: @monit = command "monit" ["-c", "monitrc"]@+command :: FilePath -> [Text] -> [Text] -> Sh Text+command = sh3 S.command++-- | bind some arguments to "run_" for re-use+-- Example: @monit_ = command_ "monit" ["-c", "monitrc"]@+command_ :: FilePath -> [Text] -> [Text] -> Sh ()+command_ = sh3 S.command_+++-- | bind some arguments to run for re-use, and expect 1 argument+-- Example: @git = command1 "git" []; git "pull" ["origin", "master"]@+command1 :: FilePath -> [Text] -> Text -> [Text] -> Sh Text+command1 = sh4 S.command1++-- | bind some arguments to run for re-use, and expect 1 argument+-- Example: @git_ = command1_ "git" []; git+ "pull" ["origin", "master"]@+command1_ :: FilePath -> [Text] -> Text -> [Text] -> Sh ()+command1_ = sh4 S.command1_++-- | run commands over SSH.+-- An ssh executable is expected in your path.+-- Commands are in the same form as 'run', but given as pairs+--+-- > sshPairs "server-name" [("cd", "dir"), ("rm",["-r","dir2"])]+--+-- This interface is crude, but it works for now.+--+-- Please note this sets 'escaping' to False: the commands will not be shell escaped.+-- Internally the list of commands are combined with the string " && " before given to ssh.+sshPairs :: Text -> [(FilePath, [Text])] -> Sh Text+sshPairs = sh2 S.sshPairs++-- | same as 'sshPairs', but returns ()+sshPairs_ :: Text -> [(FilePath, [Text])] -> Sh ()+sshPairs_ = sh2 S.sshPairs_++-------------------------------------------------------------------+-- Modifying and querying environment. ++-- | Set an environment variable. The environment is maintained in Sh+-- internally, and is passed to any external commands to be executed.+setenv :: Text -> Text -> Sh ()+setenv = sh2 S.setenv++-- | Fetch the current value of an environment variable.+-- if non-existant or empty text, will be Nothing+get_env :: Text -> Sh (Maybe Text)+get_env = sh1 S.get_env++-- | Fetch the current value of an environment variable. Both empty and+-- non-existent variables give empty string as a result.+get_env_text :: Text -> Sh Text+get_env_text = sh1 S.get_env_text++-- | Fetch the current value of an environment variable. Both empty and+-- non-existent variables give the default value as a result+getenv_def :: Text -> Text -> Sh Text+getenv_def = sh2 S.getenv_def++-- | add the filepath onto the PATH env variable+-- FIXME: only effects the PATH once the process is ran, as per comments in 'which'+appendToPath :: FilePath -> Sh ()+appendToPath = sh1 S.appendToPath++-------------------------------------------------------------+-- Environment directory ++-- | Change current working directory of Sh. This does *not* change the+-- working directory of the process we are running it. Instead, Sh keeps+-- track of its own working directory and builds absolute paths internally+-- instead of passing down relative paths. This may have performance+-- repercussions if you are doing hundreds of thousands of filesystem+-- operations. You will want to handle these issues differently in those cases.+cd :: FilePath -> Sh ()+cd = sh1 S.cd++-- | "cd", execute a Sh action in the new directory and then pop back to the original directory+chdir :: FilePath -> Sh a -> Sh a+chdir p = lift1 (S.chdir p)++-- | Obtain the current (Sh) working directory.+pwd :: Sh FilePath+pwd = sh0 S.pwd++-----------------------------------------------------------------+-- Printing ++-- | Echo text to standard (error, when using _err variants) output. The _n+-- variants do not print a final newline.+echo, echo_n_err, echo_err, echo_n :: Text -> Sh ()++echo = sh1 S.echo+echo_n_err = sh1 S.echo_n_err+echo_err = sh1 S.echo_err+echo_n = sh1 S.echo_n++-- | a print lifted into Sh+inspect :: Show s => s -> Sh ()+inspect = sh1 S.inspect++-- | a print lifted into Sh using stderr+inspect_err :: Show s => s -> Sh ()+inspect_err = sh1 S.inspect_err++-- | same as 'trace', but use it combinator style+tag :: Sh a -> Text -> Sh a+tag a t = lift1 (flip S.tag t) a++-- | internally log what occured.+-- Log will be re-played on failure.+trace :: Text -> Sh ()+trace = sh1 S.trace++show_command :: FilePath -> [Text] -> Text+show_command = S.show_command++------------------------------------------------------------------+-- Querying filesystem++-- | List directory contents. Does *not* include \".\" and \"..\", but it does+-- include (other) hidden files.+ls :: FilePath -> Sh FilePath+ls = sh1s S.ls++-- | Get back [Text] instead of [FilePath]+lsT :: FilePath -> Sh Text+lsT = sh1s S.lsT++-- | Does a path point to an existing filesystem object?+test_e :: FilePath -> Sh Bool+test_e = sh1 S.test_e++-- | Does a path point to an existing file?+test_f :: FilePath -> Sh Bool+test_f = sh1 S.test_f++-- | Does a path point to an existing directory?+test_d :: FilePath -> Sh Bool+test_d = sh1 S.test_d++-- | Does a path point to a symlink?+test_s :: FilePath -> Sh Bool+test_s = sh1 S.test_s++-- | Get a full path to an executable on @PATH@, if exists. FIXME does not+-- respect setenv'd environment and uses @findExecutable@ which uses the @PATH@ inherited from the process+-- environment.+-- FIXME: findExecutable does not maintain a hash of existing commands and does a ton of file stats+which :: FilePath -> Sh (Maybe FilePath)+which = sh1 S.which++---------------------------------------------------------------------+-- Filename helpers++-- | Make a relative path absolute by combining with the working directory.+-- An absolute path is returned as is.+-- To create a relative path, use 'path'.+absPath :: FilePath -> Sh FilePath+absPath = sh1 S.absPath++-- | makes an absolute path.+-- Like 'canonicalize', but on an exception returns 'path'+canonic :: FilePath -> Sh FilePath+canonic = sh1 S.canonic++-- | Obtain a (reasonably) canonic file path to a filesystem object. Based on+-- "canonicalizePath" in system-fileio.+canonicalize :: FilePath -> Sh FilePath+canonicalize = sh1 S.canonicalize++-- | Makes a relative path relative to the current Sh working directory.+-- An absolute path is returned as is.+-- To create an absolute path, use 'absPath'+relPath :: FilePath -> Sh FilePath+relPath = sh1 S.relPath++-- | make the second path relative to the first+-- Uses 'Filesystem.stripPrefix', but will canonicalize the paths if necessary+relativeTo :: FilePath -- ^ anchor path, the prefix+ -> FilePath -- ^ make this relative to anchor path+ -> Sh FilePath+relativeTo = sh2 S.relativeTo++-------------------------------------------------------------+-- Manipulating filesystem++-- | Currently a "renameFile" wrapper. TODO: Support cross-filesystem+-- move. TODO: Support directory paths in the second parameter, like in "cp".+mv :: FilePath -> FilePath -> Sh ()+mv = sh2 S.mv++-- | Remove a file.+-- Does fail if the file does not exist (use 'rm_f' instead) or is not a file.+rm :: FilePath -> Sh ()+rm = sh1 S.rm++-- | Remove a file. Does not fail if the file does not exist.+-- Does fail if the file is not a file.+rm_f :: FilePath -> Sh ()+rm_f = sh1 S.rm_f++-- | A swiss army cannon for removing things. Actually this goes farther than a+-- normal rm -rf, as it will circumvent permission problems for the files we+-- own. Use carefully.+-- Uses 'removeTree'+rm_rf :: FilePath -> Sh ()+rm_rf = sh1 S.rm_rf++-- | Copy a file. The second path could be a directory, in which case the+-- original file name is used, in that directory.+cp :: FilePath -> FilePath -> Sh ()+cp = sh2 S.cp++-- | Copy a file, or a directory recursively.+cp_r :: FilePath -> FilePath -> Sh ()+cp_r = sh2 S.cp_r++-- | Create a new directory (fails if the directory exists).+mkdir :: FilePath -> Sh ()+mkdir = sh1 S.mkdir++-- | Create a new directory, including parents (succeeds if the directory+-- already exists).+mkdir_p :: FilePath -> Sh ()+mkdir_p = sh1 S.mkdir_p++-- | (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 -> Sh Text+readfile = sh1 S.readfile++-- | Write a Lazy Text to a file.+writefile :: FilePath -> Text -> Sh ()+writefile = sh2 S.writefile++-- | Update a file, creating (a blank file) if it does not exist.+touchfile :: FilePath -> Sh ()+touchfile = sh1 S.touchfile++-- | Append a Lazy Text to a file.+appendfile :: FilePath -> Text -> Sh ()+appendfile = sh2 S.appendfile++-- | Create a temporary directory and pass it as a parameter to a Sh+-- computation. The directory is nuked afterwards.+withTmpDir :: (FilePath -> Sh a) -> Sh a+withTmpDir f = Sh $ S.withTmpDir (unSh . f)++-----------------------------------------------------------------+-- find++-- | List directory recursively (like the POSIX utility "find").+-- listing is relative if the path given is relative.+-- If you want to filter out some results or fold over them you can do that with the returned files.+-- A more efficient approach is to use one of the other find functions.+find :: FilePath -> Sh FilePath+find = sh1s S.find++-- | 'find' that filters the found files as it finds.+-- Files must satisfy the given filter to be returned in the result.+findWhen :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath+findWhen p a = Sh $ S.findWhen (fmap and . unSh . p) a++-- | Fold an arbitrary folding function over files froma a 'find'.+-- Like 'findWhen' but use a more general fold rather than a filter.+findFold :: (a -> FilePath -> Sh a) -> a -> FilePath -> Sh a+findFold cons nil a = Sh $ S.findFold cons' nil' a+ where nil' = return nil+ cons' as dir = unSh $ roll $ mapM (flip cons dir) as++-- | 'find' that filters out directories as it finds+-- Filtering out directories can make a find much more efficient by avoiding entire trees of files.+findDirFilter :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath+findDirFilter p a = Sh $ S.findDirFilter (fmap and . unSh . p) a+ +-- | similar 'findWhen', but also filter out directories+-- Alternatively, similar to 'findDirFilter', but also filter out files+-- Filtering out directories makes the find much more efficient+findDirFilterWhen :: (FilePath -> Sh Bool) -- ^ directory filter+ -> (FilePath -> Sh Bool) -- ^ file filter+ -> FilePath -- ^ directory+ -> Sh FilePath+findDirFilterWhen dirPred filePred a = + Sh $ S.findDirFilterWhen + (fmap and . unSh . dirPred) + (fmap and . unSh . filePred)+ a+++-- | like 'findDirFilterWhen' but use a folding function rather than a filter+-- The most general finder: you likely want a more specific one+findFoldDirFilter :: (a -> FilePath -> Sh a) -> a -> (FilePath -> Sh Bool) -> FilePath -> Sh a+findFoldDirFilter cons nil p a = Sh $ S.findFoldDirFilter cons' nil' p' a+ where p' = fmap and . unSh . p+ nil' = return nil+ cons' as dir = unSh $ roll $ mapM (flip cons dir) as+ +-----------------------------------------------------------+-- exiting the program ++exit :: Int -> Sh ()+exit = sh1 S.exit++errorExit :: Text -> Sh ()+errorExit = sh1 S.errorExit++-- | fail that takes a Text+terror :: Text -> Sh a+terror = sh1 S.terror++------------------------------------------------------------+-- Utilities++-- | Catch an exception in the Sh monad.+catch_sh :: (Exception e) => Sh a -> (e -> Sh a) -> Sh a+catch_sh a f = Sh $ S.catch_sh (unSh a) (unSh . f)++-- | Catch an exception in the Sh monad.+catchany_sh :: Sh a -> (SomeException -> Sh a) -> Sh a+catchany_sh = catch_sh+++-- | Catch an exception in the Sh monad.+finally_sh :: Sh a -> Sh b -> Sh a+finally_sh = lift2 S.finally_sh++-- | Run a Sh computation and collect timing information.+time :: Sh a -> Sh (Double, a)+time = lift1 S.time++-- | You need this when using 'catches_sh'.+data ShellyHandler a = forall e . Exception e => ShellyHandler (e -> Sh a)++-- | Catch multiple exceptions in the Sh monad.+catches_sh :: Sh a -> [ShellyHandler a] -> Sh a+catches_sh a hs = Sh $ S.catches_sh (unSh a) (fmap convert hs)+ where convert :: ShellyHandler a -> S.ShellyHandler [a]+ convert (ShellyHandler f) = S.ShellyHandler (unSh . f)++------------------------------------------------------------+-- convert between Text and FilePath ++toTextWarn :: FilePath -> Sh Text+toTextWarn = sh1 S.toTextWarn++-------------------------------------------------------------+-- internal functions for writing extension ++get :: Sh State+get = sh0 S.get++put :: State -> Sh ()+put = sh1 S.put++--------------------------------------------------------+-- polyvariadic vodoo++-- | Converter for the variadic argument version of 'run' called 'cmd'.+class ShellArg a where toTextArg :: a -> Text+instance ShellArg Text where toTextArg = id+instance ShellArg FilePath where toTextArg = toTextIgnore+++-- Voodoo to create the variadic function 'cmd'+class ShellCommand t where+ cmdAll :: FilePath -> [Text] -> t++instance ShellCommand (Sh Text) where+ cmdAll fp args = run fp args++instance (s ~ Text, Show s) => ShellCommand (Sh s) where+ cmdAll fp args = run fp args++-- note that Sh () actually doesn't work for its case (_<- cmd) when there is no type signature+instance ShellCommand (Sh ()) where+ cmdAll fp args = run_ fp args++instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where+ cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x])++-- | variadic argument version of run.+-- The syntax is more convenient, but more importantly it also allows the use of a FilePath as a command argument.+-- So an argument can be a Text or a FilePath.+-- a FilePath is converted to Text with 'toTextIgnore'.+-- You will need to add the following to your module:+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > {-# LANGUAGE ExtendedDefaultRules #-}+-- > {-# OPTIONS_GHC -fno-warn-type-defaults #-}+-- > import Shelly+-- > import Data.Text.Lazy as LT+-- > default (LT.Text)+--+cmd :: (ShellCommand result) => FilePath -> result+cmd fp = cmdAll fp []+
shelly.cabal view
@@ -1,6 +1,6 @@ Name: shelly -Version: 0.13.0.1+Version: 0.13.1 Synopsis: shell-like (systems) programming in Haskell Description: Shelly provides convenient systems programming in Haskell,@@ -19,7 +19,7 @@ . See the shelly-extra package for additional functionality. .- An overview is available in the README: <https://github.com/yesodweb/Shelly.hs>+ An overview is available in the README: <https://github.com/yesodweb/Shelly.hs/blob/master/README.md> Homepage: https://github.com/yesodweb/Shelly.hs@@ -33,7 +33,7 @@ Library- Exposed-modules: Shelly+ Exposed-modules: Shelly, Shelly.Pipe other-modules: Shelly.Base, Shelly.Find Build-depends: base >= 4 && < 5, time, directory, text, mtl