packages feed

shelly 0.4.1 → 0.5.0

raw patch · 2 files changed

+130/−33 lines, 2 files

Files

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