{-# OPTIONS_GHC -fno-warn-orphans #-}
module Control.Shell.Base
( module Control.Shell.Internal
, module System.FilePath
, IO.Handle, FileMode (..)
, MonadIO (..), shellEnv
, shell_
, stdin, echo, echo_, ask
, capture, captureStdErr, capture2, capture3, stream, lift
, takeEnvLock, releaseEnvLock, setShellEnv, joinResult, absPath
) where
import qualified System.Process as Proc
import qualified System.IO as IO
import qualified Control.Concurrent as Conc
import qualified System.Directory as Dir
import qualified System.Environment as Env
import System.FilePath
import Data.Either (either)
import Data.IORef
import Control.Monad.IO.Class
import System.IO.Unsafe
import Control.Shell.Internal
data FileMode = BinaryMode | TextMode
deriving (Show, Eq)
absPath :: Env -> FilePath -> FilePath
absPath env fp
| isAbsolute fp = fp
| otherwise = envWorkDir env </> fp
{-# NOINLINE globalEnvLock #-}
globalEnvLock :: Conc.MVar ()
globalEnvLock = unsafePerformIO $ Conc.newMVar ()
{-# NOINLINE globalEnv #-}
globalEnv :: IORef Env
globalEnv = unsafePerformIO $ newIORef undefined
takeEnvLock :: Shell ()
takeEnvLock = unsafeLiftIO $ Conc.takeMVar globalEnvLock
releaseEnvLock :: Shell ()
releaseEnvLock = unsafeLiftIO $ Conc.putMVar globalEnvLock ()
setShellEnv :: Env -> IO Env
setShellEnv env = do
evs <- Env.getEnvironment
wd <- Dir.getCurrentDirectory
writeIORef globalEnv env
Dir.setCurrentDirectory (envWorkDir env)
mapM_ Env.unsetEnv (filter (not . null) $ map fst evs)
mapM_ (uncurry Env.setEnv) (filter (not . null . fst) $ envEnvVars env)
return $ Env IO.stdin IO.stdout IO.stderr wd evs
shellEnv :: IO Env
shellEnv = readIORef globalEnv
instance MonadIO Shell where
liftIO m = do
env <- getEnv
unsafeLiftIO $ Conc.withMVar globalEnvLock $ \_ -> do
oldenv <- setShellEnv env
m <* setShellEnv oldenv
shell_ :: Shell a -> IO a
shell_ m = do
res <- shell m
case res of
Right x -> pure x
Left Success -> pure $ error "Shell computation terminated successfully"
Left (Failure e) -> error e
capture :: Shell () -> Shell String
capture m = do
env <- getEnv
(r, w) <- unsafeLiftIO Proc.createPipe
v <- unsafeLiftIO $ Conc.newEmptyMVar
unsafeLiftIO $ Conc.forkIO $ do
s <- IO.hGetContents r
Conc.putMVar v $! length s `seq` s
inEnv (env {envStdOut = w}) m
unsafeLiftIO $ IO.hClose w >> Conc.takeMVar v
captureStdErr :: Shell () -> Shell String
captureStdErr m = do
env <- getEnv
(r, w) <- unsafeLiftIO Proc.createPipe
v <- unsafeLiftIO $ Conc.newEmptyMVar
unsafeLiftIO $ Conc.forkIO $ do
s <- IO.hGetContents r
Conc.putMVar v $! length s `seq` s
inEnv (env {envStdErr = w}) m
unsafeLiftIO $ IO.hClose w >> Conc.takeMVar v
capture2 :: Shell () -> Shell (String, String)
capture2 m = do
env <- getEnv
(ro, wo) <- unsafeLiftIO Proc.createPipe
(re, we) <- unsafeLiftIO Proc.createPipe
vo <- unsafeLiftIO $ Conc.newEmptyMVar
ve <- unsafeLiftIO $ Conc.newEmptyMVar
unsafeLiftIO $ Conc.forkIO $ do
so <- IO.hGetContents ro
se <- IO.hGetContents re
Conc.putMVar vo $! length so `seq` so
Conc.putMVar ve $! length se `seq` se
inEnv (env {envStdOut = wo, envStdErr = we}) m
unsafeLiftIO $ do
IO.hClose wo
IO.hClose we
o <- Conc.takeMVar vo
e <- Conc.takeMVar ve
return (o, e)
capture3 :: Shell () -> Shell (String, String, ExitReason)
capture3 m = do
env <- getEnv
(ro, wo) <- unsafeLiftIO Proc.createPipe
(re, we) <- unsafeLiftIO Proc.createPipe
vo <- unsafeLiftIO $ Conc.newEmptyMVar
ve <- unsafeLiftIO $ Conc.newEmptyMVar
unsafeLiftIO $ Conc.forkIO $ do
so <- IO.hGetContents ro
se <- IO.hGetContents re
Conc.putMVar vo $! length so `seq` so
Conc.putMVar ve $! length se `seq` se
res <- inEnv (env {envStdOut = wo, envStdErr = we}) $ try m
unsafeLiftIO $ do
IO.hClose wo
IO.hClose we
o <- Conc.takeMVar vo
e <- Conc.takeMVar ve
return (o, e, either Failure (const Success) res)
stream :: (String -> String) -> Shell ()
stream f = stdin >>= echo_ . f
lift :: (String -> Shell String) -> Shell ()
lift f = stdin >>= f >>= echo_
stdin :: Shell String
stdin = getEnv >>= unsafeLiftIO . IO.hGetContents . envStdIn
echo :: String -> Shell ()
echo s = getEnv >>= unsafeLiftIO . flip IO.hPutStrLn s . envStdOut
echo_ :: String -> Shell ()
echo_ s = getEnv >>= unsafeLiftIO . flip IO.hPutStr s . envStdOut
ask :: Shell String
ask = getEnv >>= unsafeLiftIO . IO.hGetLine . envStdIn
joinResult :: Shell (Either ExitReason a) -> Shell a
joinResult m = do
res <- m
case res of
Right x -> pure x
Left Success -> exit
Left (Failure e) -> fail e