packages feed

shelly 0.13.0.1 → 0.13.1

raw patch · 4 files changed

+778/−34 lines, 4 files

Files

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