shellmate 0.3.2.2 → 0.3.3
raw patch · 6 files changed
+215/−9 lines, 6 filesdep +unix
Dependencies added: unix
Files
- Control/Shell.hs +3/−2
- Control/Shell/Concurrent.hs +45/−4
- Control/Shell/Daemon.hs +48/−0
- Control/Shell/Handle.hs +15/−1
- Control/Shell/Posix.hs +91/−0
- shellmate.cabal +13/−2
Control/Shell.hs view
@@ -35,8 +35,9 @@ , inTempDirectory, inCustomTempDirectory -- * Working with handles- , Handle, IOMode (..)- , hFlush, hClose+ , Handle, IOMode (..), BufferMode (..)+ , hFlush, hClose, hReady+ , hGetBuffering, hSetBuffering , getStdIn, getStdOut, getStdErr -- * Text I/O
Control/Shell/Concurrent.hs view
@@ -1,15 +1,19 @@ {-# LANGUAGE CPP #-} -- | Concurrency for Shellmate programs. module Control.Shell.Concurrent (- Future,+ Future, ThreadId,+ Control.Shell.Concurrent.forkIO, Control.Shell.Concurrent.killThread,+ fork2, fork3, future, await, check, parallel, parallel_, chunks ) where-import Control.Concurrent+import Control.Concurrent as CC import Control.Monad import Control.Shell+import Control.Shell.Internal (inEnv) import Data.IORef+import System.Process -- | Only used to have something reliable to attach the futures' weakrefs to. type FinalizerHandle = IORef ThreadId@@ -35,12 +39,12 @@ env <- getShellEnv unsafeLiftIO $ do v <- newEmptyMVar- tid <- forkIO $ runSh env m >>= putMVar v+ tid <- CC.forkIO $ runSh env m >>= putMVar v -- We need a WeakRef to something that's not referenced by the computation -- to be able to kill it when the result is not reachable. IORef to TID is -- as good as anything. r <- newIORef tid- _ <- mkWeakIORef r (killThread tid)+ _ <- mkWeakIORef r (CC.killThread tid) return $ Future r v -- | Wait for a future value.@@ -73,3 +77,40 @@ chunks _ [] = [] chunks n xs | length xs > n = take n xs : chunks n (drop n xs) | otherwise = [xs]++-- | Run a computation in a separate thread. If the thread throws an error,+-- it will simply die; the error will not propagate to its parent thread.+forkIO :: Shell () -> Shell ThreadId+forkIO m = do+ env <- getShellEnv+ unsafeLiftIO $ do+ CC.forkIO $ void $ runSh env m++-- | Run a computation in a separate thread, with its standard input and output+-- provided by the first and second handles returned respectively.+-- The handles are line buffered by default.+fork2 :: Shell () -> Shell (Handle, Handle, ThreadId)+fork2 m = do+ env <- getShellEnv+ (ri, wi) <- unsafeLiftIO createPipe+ (ro, wo) <- unsafeLiftIO createPipe+ mapM_ (flip hSetBuffering LineBuffering) [wi,wo]+ tid <- Control.Shell.Concurrent.forkIO $ do+ inEnv (env {envStdOut = wo, envStdIn = ri}) m+ return (wi, ro, tid)++-- | Like 'fork2', but adds a third handle for standard error.+fork3 :: Shell () -> Shell (Handle, Handle, Handle, ThreadId)+fork3 m = do+ env <- getShellEnv+ (ri, wi) <- unsafeLiftIO createPipe+ (ro, wo) <- unsafeLiftIO createPipe+ (re, we) <- unsafeLiftIO createPipe+ mapM_ (flip hSetBuffering LineBuffering) [wi,wo,we]+ tid <- inEnv (env {envStdOut = wo, envStdErr = we, envStdIn = ri}) $ do+ Control.Shell.Concurrent.forkIO m+ return (wi, ro, re, tid)++-- | Terminate a thread spawned by 'Control.Shell.Concurrent.forkIO'.+killThread :: ThreadId -> Shell ()+killThread = liftIO . CC.killThread
+ Control/Shell/Daemon.hs view
@@ -0,0 +1,48 @@+-- | Daemonize a shellmate computation.+-- Not present on Windows, unless shellmate is installed with the+-- @with-posix@ flag.+module Control.Shell.Daemon (daemonize) where+import Control.Shell hiding (stdin)+import System.Directory+import System.Exit+import System.IO+import System.Posix++-- | Daemonize a shellmate computation. This should be the last thing a+-- computation does, as this function will terminate the parent computation.+-- In short, daemonizing a computation involves setting the file creation+-- mask to 0, closing standard input, output and error file descriptors,+-- blocking sighup and changing the working directory to @/@.+--+-- On Windows without Cygwin, @daemonize@ is a no-op. Consider running any+-- program intended to be deamonized using @START /B your_app@, or better yet,+-- rewriting it as a Windows service.+daemonize :: Shell () -> Shell ()+daemonize m = do+ env <- getShellEnv+ unsafeLiftIO $ do+ void $ setFileCreationMask 0+ void $ forkProcess (p env)+ exitImmediately ExitSuccess+ where+ p env = do+ void $ createSession+ void $ forkProcess (p' env)+ exitImmediately ExitSuccess+ p' env = do+ changeWorkingDirectory "/"+ devnull <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags+ mapM_ closeFd [stdInput, stdOutput, stdError]+ mapM_ (dupTo devnull) [stdInput, stdOutput, stdError]+ void $ installHandler sigHUP Ignore Nothing+ dir <- getCurrentDirectory+ res <- flip runSh m $ env+ { envStdIn = stdin+ , envStdOut = stdout+ , envStdErr = stderr+ , envWorkDir = dir+ , envEnvVars = envEnvVars env+ }+ case res of+ Left (Failure _) -> exitFailure+ _ -> exitSuccess
Control/Shell/Handle.hs view
@@ -1,9 +1,11 @@ -- | Writing Shell programs using 'Handle's. module Control.Shell.Handle (+ IO.BufferMode (..), hPutStr, hPutStrLn, hGetLine, hGetContents, hGetBytes, hPutBytes, hGetByteLine, hGetByteContents,- hFlush, hClose+ hReady, hFlush, hClose,+ hGetBuffering, hSetBuffering ) where import qualified System.IO as IO import qualified Data.ByteString as BS@@ -24,6 +26,18 @@ -- | Flush a handle. hFlush :: IO.Handle -> Shell () hFlush = unsafeLiftIO . IO.hFlush++-- | Is the handle ready for reading?+hReady :: IO.Handle -> Shell Bool+hReady = unsafeLiftIO . IO.hReady++-- | Set the buffering mode of the given handle.+hSetBuffering :: IO.Handle -> IO.BufferMode -> Shell ()+hSetBuffering h = unsafeLiftIO . IO.hSetBuffering h++-- | Get the buffering mode of the given handle.+hGetBuffering :: IO.Handle -> Shell IO.BufferMode+hGetBuffering = unsafeLiftIO . IO.hGetBuffering -- | Read a line of input from a handle. hGetLine :: IO.Handle -> Shell String
+ Control/Shell/Posix.hs view
@@ -0,0 +1,91 @@+-- | Working with daemons, users, groups, file permissions and file ownerships.+-- Not present on Windows unless shellmate is installed with the @with-posix@+-- flag.+module Control.Shell.Posix+ ( -- * Users and groups+ User, Group+ , getCurrentUser, Control.Shell.Posix.getCurrentGroups+ , getOwner, getGroup, getOwnership+ , setOwner, setGroup, setOwnership++ -- * File permissions+ , CMode+ , unionFileModes, intersectFileModes+ , nullFileMode+ , ownerReadMode, ownerWriteMode, ownerExecuteMode, ownerModes+ , groupReadMode, groupWriteMode, groupExecuteMode, groupModes+ , otherReadMode, otherWriteMode, otherExecuteMode, otherModes+ , stdFileMode, accessModes+ , Control.Shell.Posix.setFileMode, getFileMode+ , Control.Shell.Posix.setFileCreationMask++ -- * Daemonizing shell computations+ , daemonize+ ) where+import System.Posix as Posix+import Control.Shell+import Control.Shell.Daemon++type User = String+type Group = String++-- | Set the mode (permissions, etc.) of the given file.+setFileMode :: FilePath -> CMode -> Shell ()+setFileMode f = liftIO . Posix.setFileMode f++-- | Get the mode of the given file.+getFileMode :: FilePath -> Shell CMode+getFileMode = liftIO . fmap fileMode . getFileStatus++-- | Get the currently effective user name.+getCurrentUser :: Shell String+getCurrentUser = liftIO getEffectiveUserName++-- | Get the list of groups associated with the current process.+getCurrentGroups :: Shell [String]+getCurrentGroups = liftIO $ do+ gids <- Posix.getGroups+ mapM (fmap groupName . getGroupEntryForID) gids++-- | Set the owner of the given file.+setOwner :: User -> FilePath -> Shell ()+setOwner name file = liftIO $ do+ uid <- userID <$> getUserEntryForName name+ setOwnerAndGroup file uid (-1)++-- | Set the group of the given file.+setGroup :: Group -> FilePath -> Shell ()+setGroup group file = liftIO $ do+ gid <- groupID <$> getGroupEntryForName group+ setOwnerAndGroup file (-1) gid++-- | Set the owner and group of the given file.+setOwnership :: User -> Group -> FilePath -> Shell ()+setOwnership name group file = liftIO $ do+ uid <- userID <$> getUserEntryForName name+ gid <- groupID <$> getGroupEntryForName group+ setOwnerAndGroup file uid gid++-- | Get the owner of the given file.+getOwner :: FilePath -> Shell User+getOwner file = liftIO $ do+ uid <- fileOwner <$> getFileStatus file+ userName <$> getUserEntryForID uid++-- | Get the group of the given file.+getGroup :: FilePath -> Shell Group+getGroup file = liftIO $ do+ gid <- fileGroup <$> getFileStatus file+ groupName <$> getGroupEntryForID gid++-- | Get the owner and group of the given file.+getOwnership :: FilePath -> Shell (User, Group)+getOwnership file = liftIO $ do+ st <- getFileStatus file+ user <- userName <$> getUserEntryForID (fileOwner st)+ group <- groupName <$> getGroupEntryForID (fileGroup st)+ return (user, group)++-- | Set the file creation mask of this process.+setFileCreationMask :: CMode -> Shell CMode+setFileCreationMask = unsafeLiftIO . Posix.setFileCreationMask
shellmate.cabal view
@@ -1,5 +1,5 @@ name: shellmate-version: 0.3.2.2+version: 0.3.3 synopsis: Simple interface for shell scripting in Haskell. description: Aims to simplify development of cross-platform shell scripts and similar things. homepage: https://github.com/valderman/shellmate@@ -15,7 +15,13 @@ source-repository head type: git location: https://github.com/valderman/shellmate.git- ++flag with-posix+ default: False+ description:+ Build the POSIX-specific Control.Shell.Posix module even when on Windows.+ This requires a POSIX compatibility layer such as Cygwin.+ library exposed-modules: Control.Shell@@ -41,3 +47,8 @@ Haskell2010 ghc-options: -Wall+ if flag(with-posix) || !os(windows)+ cpp-options: -DWITH_POSIX+ exposed-modules: Control.Shell.Posix+ other-modules: Control.Shell.Daemon+ build-depends: unix >=2.7 && <2.8