module SOS where import ANSIColors import System.FSNotify import System.Process import System.Exit import Control.Monad import Control.Concurrent import Data.Maybe import Text.Regex.TDFA import Filesystem.Path.CurrentOS as OS import Data.Text as T import Data.List as L import System.IO ( Handle ) import Prelude hiding ( FilePath ) type RunningProcess = IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -- | A tuple to hold our changed file events, -- list of commands to run and possibly the currently running process, -- in case it's one that hasn't terminated. type SOSState = ([Event], [String], Maybe ProcessHandle) steelOverseer :: FilePath -> [String] -> [String] -> IO () steelOverseer dir cmds exts = do putStrLn "Hit enter to quit.\n" wm <- startManager mvar <- newEmptyMVar let predicate = actionPredicateForRegexes exts action = performCommand mvar cmds in watchTree wm dir predicate action _ <- getLine mState <- tryTakeMVar mvar case mState of Just (_, _, Just pid) -> terminatePID pid _ -> return () stopManager wm actionPredicateForRegexes :: [String] -> Event -> Bool actionPredicateForRegexes ptns event = or (fmap (filepath =~) ptns :: [Bool]) where filepath = case toText $ eventPath event of Left f -> T.unpack f Right f -> T.unpack f actionPredicateForExts :: [String] -> Event -> Bool actionPredicateForExts exts event = let maybeExt = extension $ eventPath event in case fmap T.unpack maybeExt of Just ext -> ext `elem` exts Nothing -> False performCommand :: MVar SOSState -> [String] -> Event -> IO () performCommand mvar cmds event = do cyanPrint event mEvProc <- tryTakeMVar mvar eventsAndProcess <- case mEvProc of Nothing -> do -- This is the first file to change and there is no running process. startWriteProcess mvar cmds 1000000 return ([event], cmds, Nothing) Just ([], _, Just pid) -> do -- There is a hanging process. mExCode <- getProcessExitCode pid when (isNothing mExCode) $ terminatePID pid startWriteProcess mvar cmds 1000000 return ([event], cmds, Nothing) Just (events, leftoverCmds, Nothing) -> -- SOS is waiting the 1sec delay for events before running the process. return (event:events, leftoverCmds, Nothing) Just enp -> return enp putMVar mvar eventsAndProcess startWriteProcess :: MVar SOSState -> [String] -> Int -> IO () startWriteProcess mvar [] _ = do _ <- tryTakeMVar mvar return () startWriteProcess mvar (cmd:cmds) delay = void $ forkIO $ do threadDelay delay putStrLn $ colorString ANSIGreen "\n> " ++ cmd ++ "\n" pId <- runCommand cmd mEvProcCurrent <- tryTakeMVar mvar case mEvProcCurrent of Nothing -> putMVar mvar ([], cmds, Just pId) Just (_, _, _) -> void $ forkIO $ do putMVar mvar ([], cmds, Just pId) exitCode <- waitForProcess pId case exitCode of ExitSuccess -> do greenPrint exitCode startWriteProcess mvar cmds 0 _ -> redPrint exitCode terminatePID :: ProcessHandle -> IO () terminatePID pid = do terminateProcess pid putStrLn $ colorString ANSIRed "Terminated hanging process."