------------------------------------------------------------------------------ --- Library with some useful extensions to the IO monad. --- --- @author Michael Hanus --- @version January 2017 --- @category general ------------------------------------------------------------------------------ {-# LANGUAGE CPP #-} module IOExts ( -- execution of shell commands execCmd, evalCmd, connectToCommand -- file access , readCompleteFile,updateFile, exclusiveIO -- associations , setAssoc,getAssoc -- IORef , IORef, newIORef, readIORef, writeIORef, modifyIORef ) where #ifdef __PAKCS__ import Char (isAlphaNum) import Directory (removeFile) import Read (readNat) #endif import IO ( Handle, hClose, hGetChar, hIsEOF, hPutStrLn ) import System ( getPID, system ) --- Executes a command with a new default shell process. --- The standard I/O streams of the new process (stdin,stdout,stderr) --- are returned as handles so that they can be explicitly manipulated. --- They should be closed with IO.hClose since they are not --- closed automatically when the process terminates. --- @param cmd - the shell command to be executed --- @return the handles of the input/output/error streams of the new process execCmd :: String -> IO (Handle, Handle, Handle) execCmd cmd = prim_execCmd $## cmd prim_execCmd :: String -> IO (Handle, Handle, Handle) prim_execCmd external --- Executes a command with the given arguments as a new default shell process --- and provides the input via the process' stdin input stream. --- The exit code of the process and the contents written to the standard --- I/O streams stdout and stderr are returned. --- @param cmd - the shell command to be executed --- @param args - the command's arguments --- @param input - the input to be written to the command's stdin --- @return the exit code and the contents written to stdout and stderr evalCmd :: String -> [String] -> String -> IO (Int, String, String) #ifdef __PAKCS__ evalCmd cmd args input = do pid <- getPID let tmpfile = "/tmp/PAKCS_evalCMD"++show pid (hi,ho,he) <- execCmd (unwords (map wrapArg (cmd:args)) ++ " ; (echo $? > "++tmpfile++")") unless (null input) (hPutStrLn hi input) hClose hi outs <- hGetEOF ho errs <- hGetEOF he ecodes <- readCompleteFile tmpfile removeFile tmpfile return (readNat ecodes, outs, errs) where wrapArg str | null str = "''" -- goodChar is a pessimistic predicate, such that if an argument is -- non-empty and only contains goodChars, then there is no need to -- do any quoting or escaping | all goodChar str = str | otherwise = '\'' : foldr escape "'" str where escape c s | c == '\'' = "'\\''" ++ s | otherwise = c : s goodChar c = isAlphaNum c || c `elem` "-_.,/" --- Reads from an input handle until EOF and returns the input. hGetEOF :: Handle -> IO String hGetEOF h = do eof <- hIsEOF h if eof then hClose h >> return "" else do c <- hGetChar h cs <- hGetEOF h return (c:cs) #else evalCmd cmd args input = ((prim_evalCmd $## cmd) $## args) $## input prim_evalCmd :: String -> [String] -> String -> IO (Int, String, String) prim_evalCmd external #endif --- Executes a command with a new default shell process. --- The input and output streams of the new process is returned --- as one handle which is both readable and writable. --- Thus, writing to the handle produces input to the process and --- output from the process can be retrieved by reading from this handle. --- The handle should be closed with IO.hClose since they are not --- closed automatically when the process terminates. --- @param cmd - the shell command to be executed --- @return the handle connected to the input/output streams --- of the new process connectToCommand :: String -> IO Handle connectToCommand cmd = prim_connectToCmd $## cmd prim_connectToCmd :: String -> IO Handle prim_connectToCmd external --- An action that reads the complete contents of a file and returns it. --- This action can be used instead of the (lazy) readFile --- action if the contents of the file might be changed. --- @param file - the name of the file --- @return the complete contents of the file readCompleteFile :: String -> IO String readCompleteFile file = do s <- readFile file f s (return s) where f [] r = r f (_:cs) r = f cs r --- An action that updates the contents of a file. --- @param f - the function to transform the contents --- @param file - the name of the file updateFile :: (String -> String) -> String -> IO () updateFile f file = do s <- readCompleteFile file writeFile file (f s) --- Forces the exclusive execution of an action via a lock file. --- For instance, (exclusiveIO "myaction.lock" act) ensures that --- the action "act" is not executed by two processes on the same --- system at the same time. --- @param lockfile - the name of a global lock file --- @param action - the action to be exclusively executed --- @return the result of the execution of the action exclusiveIO :: String -> IO a -> IO a exclusiveIO lockfile action = do system ("lockfile-create --lock-name "++lockfile) catch (do actionResult <- action deleteLockFile return actionResult ) (\e -> deleteLockFile >> ioError e) where deleteLockFile = system $ "lockfile-remove --lock-name " ++ lockfile --- Defines a global association between two strings. --- Both arguments must be evaluable to ground terms before applying --- this operation. setAssoc :: String -> String -> IO () setAssoc key val = (prim_setAssoc $## key) $## val prim_setAssoc :: String -> String -> IO () prim_setAssoc external --- Gets the value associated to a string. --- Nothing is returned if there does not exist an associated value. getAssoc :: String -> IO (Maybe String) getAssoc key = prim_getAssoc $## key prim_getAssoc :: String -> IO (Maybe String) prim_getAssoc external --- Mutable variables containing values of some type. --- The values are not evaluated when they are assigned to an IORef. #ifdef __PAKCS__ data IORef a = IORef a -- precise structure internally defined #else external data IORef _ -- precise structure internally defined #endif --- Creates a new IORef with an initial value. newIORef :: a -> IO (IORef a) newIORef external --- Reads the current value of an IORef. readIORef :: IORef a -> IO a readIORef ref = prim_readIORef $# ref prim_readIORef :: IORef a -> IO a prim_readIORef external --- Updates the value of an IORef. writeIORef :: IORef a -> a -> IO () writeIORef ref val = (prim_writeIORef $# ref) val prim_writeIORef :: IORef a -> a -> IO () prim_writeIORef external --- Modify the value of an IORef. modifyIORef :: IORef a -> (a -> a) -> IO () modifyIORef ref f = readIORef ref >>= writeIORef ref . f