packages feed

arion-0.1.0.8: src/Arion/Runner.hs

{-# LANGUAGE ScopedTypeVariables, TupleSections #-}
module Arion.Runner(
    run
) where

import           Arion.EventProcessor
import           Arion.Help
import           Arion.Types
import           Arion.Utilities
import           Control.Applicative       ((<$>))
import           Control.Concurrent        (MVar, newEmptyMVar, putMVar,
                                            takeMVar, threadDelay)
import           Control.Exception         (SomeException, bracket_, try)
import           Control.Monad             (forever, void)
import           Control.Monad             (join)
import           Data.IORef                (IORef, atomicModifyIORef', newIORef)
import           Data.Map                  (Map)
import qualified Data.Map                  as Map
import           Data.Text                 (pack)
import           Filesystem.Path.CurrentOS (fromText)
import           System.Directory          (canonicalizePath)
import           System.FilePath.Find      (always, extension, find, (==?),
                                            (||?))
import           System.FSNotify           (WatchManager, watchTree,
                                            withManager)
import           System.Process            (callCommand)

run :: [String] -> IO ()
run args
    | "--help" `elem` args = putStrLn usage
    | length args >= 3 = let (path:sourceFolder:testFolder:_) = args
                         in withManager (startWatching path sourceFolder testFolder)
    | otherwise = putStrLn "Try arion --help for more information"

startWatching :: String -> String -> String -> WatchManager -> IO ()
startWatching path sourceFolder testFolder manager = do
  sourceFiles <- mapM (\x -> uncurry toSourceFile <$> filePathAndContent x)
                 =<< findHaskellFiles sourceFolder
  testFiles   <- mapM (\x -> uncurry toTestFile <$> filePathAndContent x)
                 =<< findHaskellFiles testFolder

  let sourceToTestFileMap = associate sourceFiles testFiles

  lock <- newEmptyMVar
  inProgress <- newIORef Map.empty
  _ <- watchTree manager (fromText $ pack path) (const True)
       (eventHandler lock inProgress (processEvent sourceToTestFileMap sourceFolder testFolder) . respondToEvent)
  forever $ threadDelay maxBound

filePathAndContent :: String -> IO (FilePath, FileContent)
filePathAndContent relativePath = do
                          canonicalizedPath <- canonicalizePath relativePath
                          content <- readFile canonicalizedPath
                          return (canonicalizedPath, content)

findHaskellFiles :: String -> IO [String]
findHaskellFiles = find always (extension ==? ".hs" ||? extension ==? ".lhs")

-- 10th of a sec? seems ok.
dELAY = 100000

-- eventHandler :: Show a => MVar () -> IORef (Map Command ()) -> (a
        -- -> [Command]) -> a -> IO ()
eventHandler lock inProgress handler Nothing  = return ()
eventHandler lock inProgress handler (Just (fp,time)) = do
  commands <- join $ atomicModifyIORef' inProgress
          (\running -> case Map.lookup fp running of
              Just _ -> (running,return [])
              Nothing -> (Map.insert fp () running,
                          do threadDelay dELAY
                             atomicModifyIORef' inProgress
                               (\hash -> (Map.delete fp hash,
                                          ()))
                             return $ handler (fp,time)
                         ))

  mapM_ (runCommand lock) commands

runCommand :: Show a => MVar () -> a -> IO ()
runCommand lock command = do
  bracket_ (putMVar lock ())
           (takeMVar lock)
           (noisyTry (callCommand $ show command))

noisyTry :: IO () -> IO ()
noisyTry f = do
  (x :: Either SomeException ()) <- try f
  case x of
    Left y -> print ("error", y)
    _ ->  return ()