module Watcher
( watch
) where
import Data.String (fromString)
import Parser
import Protolude
import System.Directory
import System.FilePath
import qualified System.FilePath.Glob as G
import qualified System.FSNotify as FS
import qualified Twitch as T
type Handler = FilePath -> IO ()
watch :: Config -> Handler -> IO FS.WatchManager
watch config handler = do
currentDir <- getCurrentDirectory
baseDirectories <- mapM toAbsoluteDirectory (_dirs config)
directoriesToWatch <- getDirectoriesToWatch baseDirectories
let ignoringHandler = handlerWithIgnore (ignoredFilePatterns currentDir config) handler
T.runWithConfig currentDir (twitchConfig directoriesToWatch) $ registerAllHandlers config ignoringHandler
registerAllHandlers :: Config -> Handler -> T.Dep
registerAllHandlers config handler = mapM_ (registerHandler handler) (allFilePaths config)
registerHandler :: Handler -> FilePath -> T.Dep
registerHandler handler fileGlob = T.addModify handler (fromString $ toS fileGlob)
handlerWithIgnore :: [G.Pattern] -> Handler -> Handler
handlerWithIgnore ignored handler filePath =
if any (\pattern -> G.match pattern filePath) ignored
then return ()
else (handler filePath)
ignoredFilePatterns :: FilePath -> Config -> [G.Pattern]
ignoredFilePatterns currentDirectory Config {..} =
let
absoluteIgnoreDir :: Text -> [FilePath]
absoluteIgnoreDir dir = createFilePaths (toS (currentDirectory </> toS dir)) (concat _ignore)
allIgnored = foldl (\acc dir -> acc ++ (absoluteIgnoreDir dir)) [] _dirs
in map (G.compile) allIgnored
allFilePaths :: Config -> [FilePath]
allFilePaths Config {..} = foldl (\acc dir -> acc ++ createFilePaths dir _files) [] _dirs
createFilePaths :: Text -> [Text] -> [FilePath]
createFilePaths dir = map (\file -> toS dir </> toS file)
watchConfig :: FS.WatchConfig
watchConfig = FS.WatchConfig {FS.confDebounce = FS.DebounceDefault, FS.confPollInterval = 0, FS.confUsePolling = False}
twitchConfig :: [FilePath] -> T.Config
twitchConfig dirsToWatch = T.Config {logger = const $ return (), dirs = dirsToWatch, watchConfig = watchConfig}
toAbsoluteDirectory :: Text -> IO FilePath
toAbsoluteDirectory filePath = do
currentDir <- getCurrentDirectory
makeAbsolute $ currentDir </> toS filePath
getDirectoriesToWatch :: [FilePath] -> IO [FilePath]
getDirectoriesToWatch = foldMap getDirectories
where
getDirectories baseDirectory = do
subDirs <- findAllDirs baseDirectory
return $ baseDirectory : subDirs
findAllDirs :: FilePath -> IO [FilePath]
findAllDirs path = do
dirs <- findImmediateDirs path
nestedDirs <- mapM findAllDirs dirs
return (dirs ++ concat nestedDirs)
findImmediateDirs :: FilePath -> IO [FilePath]
findImmediateDirs = getDirectoryContentsPath >=> filterM doesDirectoryExist >=> canonicalize
where
canonicalize :: [FilePath] -> IO [FilePath]
canonicalize = mapM canonicalizeDirPath
getDirectoryContentsPath :: FilePath -> IO [FilePath]
getDirectoryContentsPath path = map (path </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents path
canonicalizeDirPath :: FilePath -> IO FilePath
canonicalizeDirPath path = addTrailingPathSeparator <$> canonicalizePath path