shelly 0.4.1 → 0.5.0
raw patch · 2 files changed
+130/−33 lines, 2 files
Files
- Shelly.hs +129/−32
- shelly.cabal +1/−1
Shelly.hs view
@@ -1,18 +1,29 @@ {-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable, OverloadedStrings, MultiParamTypeClasses, FlexibleInstances #-} --- | A module for shell-like / perl-like programming in Haskell. The stuff in--- here is not always pretty, but it gets the job done. The functionality provided by+-- | A module for shell-like / perl-like programming in Haskell.+-- Shelly's focus is entirely on ease of use for those coming from shell scripting.+-- However, it also tries to use modern libraries and techniques to keep things efficient.+--+-- The functionality provided by -- this module is (unlike standard Haskell filesystem functionality) -- thread-safe: each ShIO maintains its own environment and its own working -- directory.+--+-- I highly recommend putting the following at the top of your program,+-- otherwise you will need either type annotations/conversions+--+-- {-# LANGUAGE OverloadedStrings #-}+-- {-# LANGUAGE ExtendedDefaultRules #-}+-- {-# OPTIONS_GHC -fno-warn-type-defaults #-}+-- default (Text) -- this goes after import statements module Shelly ( -- * Entering ShIO. ShIO, shelly, sub, silently, verbosely, print_commands -- * Modifying and querying environment.- , setenv, getenv, getenv_def, appendPath+ , setenv, getenv, getenv_def, appendToPath -- * Environment directory , cd, chdir, pwd@@ -24,14 +35,14 @@ , ls, ls', test_e, test_f, test_d, test_s, which, find -- * Filename helpers- , path, absPath+ , path, absPath, (</>), (<.>) -- * Manipulating filesystem. , mv, rm_f, rm_rf, cp, cp_r, mkdir, mkdir_p , readfile, writefile, appendfile, withTmpDir -- * Running external commands.- , run, ( # ), run_, (-|-), lastStderr, setStdin+ , run, result, ( # ), run_, (-|-), lastStderr, setStdin , command, command_, command1, command1_ -- , Sudo(..), run_sudo @@ -46,11 +57,32 @@ -- * mappend (<>) Text with a FilePath , (|<>), (<>|) -- * convert between Text and FilePath- , toTextUnsafe, toTextWarn, fromText+ , toTextIgnore, toTextWarn, fromText -- * Re-exported for your convenience , liftIO, when, unless ) where +-- TODO:+-- shebang runner that puts wrappers in and invokes+-- convenience for commands that use record arguments+{-+ let oFiles = ("a.o", "b.o")+ let ldOutput x = ("-o", x)++ let def = LD { output = error "", verbose = False, inputs = [] }+ data LD = LD { output :: FilePath, verbose :: Bool, inputs :: [FilePath] } deriving(Data, Typeable)+ instance Runnable LD where+ run :: LD -> IO ()++ class Runnable a where+ run :: a -> ShIO Text++ let ld = def :: LD+ run (ld "foo") { oFiles = [] }+ run ld { oFiles = [] }+ ld = ..magic..+-}+ import Prelude hiding ( catch, readFile, FilePath ) import Data.List( isInfixOf ) import Data.Char( isAlphaNum )@@ -73,9 +105,10 @@ import qualified Data.Text.Lazy as LT import Data.Text.Lazy (Text) import qualified Data.Text.Lazy.Builder as B+import qualified Data.Text as T import Data.Monoid (mappend) -import Filesystem.Path.CurrentOS hiding (concat, fromText)+import Filesystem.Path.CurrentOS hiding (concat, fromText, (</>), (<.>)) import Filesystem import qualified Filesystem.Path.CurrentOS as FP @@ -85,17 +118,81 @@ infixr 5 <>| infixr 5 |<> --- | mappend a Text & FilePath. Warning: uses toTextUnsafe+{- GHC won't default to Text with this+class ShellArgs a where+ toTextArgs :: a -> [Text]++instance ShellArgs Text where toTextArgs t = [t]+instance ShellArgs FilePath where toTextArgs t = [toTextIgnore t]+instance ShellArgs [Text] where toTextArgs = id+instance ShellArgs [FilePath] where toTextArgs = map toTextIgnore++instance ShellArgs (Text, Text) where+ toTextArgs (t1,t2) = [t1, t2]+instance ShellArgs (FilePath, FilePath) where+ toTextArgs (fp1,fp2) = [toTextIgnore fp1, toTextIgnore fp2]+instance ShellArgs (Text, FilePath) where+ toTextArgs (t1, fp1) = [t1, toTextIgnore fp1]+instance ShellArgs (FilePath, Text) where+ toTextArgs (fp1,t1) = [toTextIgnore fp1, t1]++cmd :: (ShellArgs args) => FilePath -> args -> ShIO Text+cmd fp args = run fp $ toTextArgs args+-}++class ToFilePath a where+ toFilePath :: a -> FilePath++instance ToFilePath FilePath where toFilePath = id+instance ToFilePath Text where toFilePath = fromText+instance ToFilePath T.Text where toFilePath = FP.fromText+instance ToFilePath String where toFilePath = FP.fromText . T.pack++class ShellArg a where toTextArg :: a -> Text+instance ShellArg Text where toTextArg = id+instance ShellArg FilePath where toTextArg = toTextIgnore+++-- | For the variadic argument version of 'run' called 'result'.+class ShellCommand t where+ cmdAll :: FilePath -> [Text] -> t+instance ShellCommand (ShIO Text) where+ cmdAll fp args = run fp args+instance ShellCommand (ShIO ()) 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.+-- Convenient (avoid list syntax and use FilePath without converting), but has a big downside:+-- It always returns a result, it should always be used as value <- result+-- Note that value could be ().+-- If you do not return a value you will get a nasty error message!+-- So the function is named 'result' to remind you of that.+-- An argument can be a Text or a FilePath.+-- a FilePath is converted to Text with 'toTextIgnore'.+result :: (ShellCommand result) => FilePath -> result+result fp = cmdAll fp []++-- | uses System.FilePath.CurrentOS, but can automatically convert a Text+(</>) :: (ToFilePath filepath) => filepath -> filepath -> FilePath+x </> y = toFilePath x FP.</> toFilePath y++-- | uses System.FilePath.CurrentOS, but can automatically convert a Text+(<.>) :: (ToFilePath filepath) => filepath -> Text -> FilePath+x <.> y = toFilePath x FP.<.> LT.toStrict y++-- | mappend a Text & FilePath. Warning: uses toTextIgnore (<>|) :: Text -> FilePath -> Text-(<>|) t fp = t `mappend` toTextUnsafe fp+(<>|) t fp = t `mappend` toTextIgnore fp --- | mappend a FilePath & Text. Warning: uses toTextUnsafe+-- | mappend a FilePath & Text. Warning: uses toTextIgnore (|<>) :: FilePath -> Text -> Text-(|<>) fp t = toTextUnsafe fp `mappend` t+(|<>) fp t = toTextIgnore fp `mappend` t -- | silently uses the Right or Left value of "Filesystem.Path.CurrentOS.toText"-toTextUnsafe :: FilePath -> Text-toTextUnsafe fp = LT.fromStrict $ case toText fp of+toTextIgnore :: FilePath -> Text+toTextIgnore fp = LT.fromStrict $ case toText fp of Left f -> f Right f -> f @@ -166,9 +263,9 @@ gets f = f <$> get runInteractiveProcess' :: FilePath -> [Text] -> ShIO (Handle, Handle, Handle, ProcessHandle)-runInteractiveProcess' cmd args = do+runInteractiveProcess' exe args = do st <- get- liftIO $ runInteractiveProcess (unpack cmd)+ liftIO $ runInteractiveProcess (unpack exe) (map LT.unpack args) (Just $ unpack $ sDirectory st) (Just $ sEnvironment st)@@ -223,7 +320,7 @@ -- | makes an absolute path. @path@ will also normalize absPath :: FilePath -> ShIO FilePath-absPath p | relative p = (</> p) <$> gets sDirectory+absPath p | relative p = (FP.</> p) <$> gets sDirectory | otherwise = return p -- | apply a String IO operations to a Text FilePath@@ -264,11 +361,11 @@ find :: FilePath -> ShIO [FilePath] find dir = do bits <- ls dir subDir <- forM bits $ \x -> do- ex <- test_d $ dir </> x- sym <- test_s $ dir </> x- if ex && not sym then find (dir </> x)+ ex <- test_d $ dir FP.</> x+ sym <- test_s $ dir FP.</> x+ if ex && not sym then find (dir FP.</> x) else return []- return $ map (dir </>) bits ++ concat subDir+ return $ map (dir FP.</>) bits ++ concat subDir -- | Obtain the current (ShIO) working directory. pwd :: ShIO FilePath@@ -376,8 +473,8 @@ in modify $ \x -> x { sEnvironment = wibble $ sEnvironment x } -- | add the filepath onto the PATH env variable-appendPath :: FilePath -> ShIO ()-appendPath filepath = do+appendToPath :: FilePath -> ShIO ()+appendToPath filepath = do tp <- toTextWarn filepath pe <- getenv path_env setenv path_env $ pe `mappend` ":" `mappend` tp@@ -439,9 +536,9 @@ data RunFailed = RunFailed FilePath [Text] Int Text deriving (Typeable) instance Show RunFailed where- show (RunFailed cmd args code errs) =+ show (RunFailed exe args code errs) = "error running " ++- unpack cmd ++ " " ++ show args +++ unpack exe ++ " " ++ show args ++ ": exit status " ++ show code ++ ":\n" ++ LT.unpack errs instance Exception RunFailed@@ -449,7 +546,7 @@ -- | An infix shorthand for "run". Write @\"command\" # [ \"argument\" ... ]@. ( # ) :: FilePath -> [Text] -> ShIO Text-cmd # args = run cmd args+exe # args = run exe args -- | 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@@ -463,7 +560,7 @@ -- You can avoid this but still consume the result by using "run'", -- or if you need to process the output than "runFoldLines" run :: FilePath -> [Text] -> ShIO Text-run cmd args = fmap B.toLazyText $ runFoldLines (B.fromText "") foldBuilder cmd args+run exe args = fmap B.toLazyText $ runFoldLines (B.fromText "") foldBuilder exe args foldBuilder :: (B.Builder, Text) -> B.Builder foldBuilder (b, line) = b `mappend` B.fromLazyText line `mappend` B.singleton '\n'@@ -499,12 +596,12 @@ -- same as "run", but fold over stdout as it is read to avoid keeping it in memory -- stderr is still placed in memory (this could be changed in the future) runFoldLines :: a -> FoldCallback a -> FilePath -> [Text] -> ShIO a-runFoldLines start cb cmd args = do+runFoldLines start cb exe args = do state <- get when (sPrintCommands state) $ do- c <- toTextWarn cmd+ c <- toTextWarn exe echo $ LT.intercalate " " (c:args)- (inH,outH,errH,procH) <- sRun state cmd args+ (inH,outH,errH,procH) <- sRun state exe args errV <- liftIO newEmptyMVar outV <- liftIO newEmptyMVar@@ -538,7 +635,7 @@ } case ex of ExitSuccess -> return outs- ExitFailure n -> throw $ RunFailed cmd args n errs+ ExitFailure n -> throw $ RunFailed exe args n errs -- | The output of last external command. See "run". lastStderr :: ShIO Text@@ -590,7 +687,7 @@ cp_r :: FilePath -> FilePath -> ShIO () cp_r from to = do whenM (test_d from) $- mkdir to >> ls from >>= mapM_ (\item -> cp_r (from </> item) (to </> item))+ mkdir to >> ls from >>= mapM_ (\item -> cp_r (from FP.</> item) (to FP.</> item)) whenM (test_f from) $ cp from to -- | Copy a file. The second path could be a directory, in which case the@@ -600,7 +697,7 @@ from' <- absPath from to' <- absPath to to_dir <- test_d to- liftIO $ copyFile from' $ if to_dir then to' </> filename from else to'+ liftIO $ copyFile from' $ if to_dir then to' FP.</> filename from else to' class PredicateLike pattern hay where match :: pattern -> hay -> Bool
shelly.cabal view
@@ -1,6 +1,6 @@ Name: shelly -Version: 0.4.1+Version: 0.5.0 Synopsis: shell-like (systems) programming in Haskell Description: Shelly is a package provides a single module for convenient