fsnotify 0.0.3 → 0.0.4
raw patch · 6 files changed
+585/−44 lines, 6 filesnew-uploaderPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- fsnotify.cabal +39/−33
- test/FSNotify.hs +198/−0
- test/Path.hs +139/−0
- test/Util.hs +189/−0
- test/main.hs +0/−11
- test/watch-here.hs +20/−0
fsnotify.cabal view
@@ -1,5 +1,5 @@ Name: fsnotify-Version: 0.0.3+Version: 0.0.4 Author: Mark Dittmer <mark.s.dittmer@gmail.com> Maintainer: Mark Dittmer <mark.s.dittmer@gmail.com>, Greg Weber <greg@gregweber.info> License: BSD3@@ -13,6 +13,10 @@ Category: Filesystem Cabal-Version: >= 1.8 Build-Type: Simple+Extra-Source-Files: test/FSNotify.hs+ test/Path.hs+ test/Util.hs+ test/watch-here.hs Library@@ -59,38 +63,40 @@ -- , text >= 0.11.0 -- , time >= 1.4 -Test-Suite test- Type: exitcode-stdio-1.0- Main-Is: main.hs- -- Type: detailed-0.9- -- Test-Module: Tests- Hs-Source-Dirs: test, src- GHC-Options: -Wall -threaded- Build-depends: base >= 4.3.1.0- , bytestring >= 0.9.2.1- , Cabal >= 1.14.0- , containers >= 0.4.2.1- , directory >= 1.1.0.2- , Glob >= 0.7.1- , hspec >= 1.3.0- , random >= 1.0.1.1- , system-filepath >= 0.4.6- , system-fileio >= 0.3.7- , text >= 0.10- , time >= 1.4- , QuickCheck >= 2.4.2- , uniqueid >= 0.1.1- if os(linux)- CPP-Options: -DOS_Linux- Build-Depends: hinotify == 0.3.2- else- if os(windows)- CPP-Options: -DOS_Win32- Build-Depends: Win32-notify == 0.2, ghc >= 7.4.2- else- if os(darwin)- CPP-Options: -DOS_Mac- Build-Depends: hfsevents >= 0.1.3+-- DISABLED: older Cabal lib versions have a bug which means they fall+-- over when looking at test-suite sections that contain conditionals+-- Test-Suite test+-- Type: exitcode-stdio-1.0+-- Main-Is: main.hs+-- -- Type: detailed-0.9+-- -- Test-Module: Tests+-- Hs-Source-Dirs: test, src+-- GHC-Options: -Wall -threaded+-- Build-depends: base >= 4.3.1.0+-- , bytestring >= 0.9.2.1+-- , Cabal >= 1.14.0+-- , containers >= 0.4.2.1+-- , directory >= 1.1.0.2+-- , Glob >= 0.7.1+-- , hspec >= 1.3.0+-- , random >= 1.0.1.1+-- , system-filepath >= 0.4.6+-- , system-fileio >= 0.3.7+-- , text >= 0.10+-- , time >= 1.4+-- , QuickCheck >= 2.4.2+-- , uniqueid >= 0.1.1+-- if os(linux)+-- CPP-Options: -DOS_Linux+-- Build-Depends: hinotify == 0.3.2+-- else+-- if os(windows)+-- CPP-Options: -DOS_Win32+-- Build-Depends: Win32-notify == 0.2, ghc >= 7.4.2+-- else+-- if os(darwin)+-- CPP-Options: -DOS_Mac+-- Build-Depends: hfsevents >= 0.1.3 Source-Repository head
+ test/FSNotify.hs view
@@ -0,0 +1,198 @@+--+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org+--++module FSNotify (spec) where++import Prelude hiding (appendFile, FilePath, writeFile)++import Control.Concurrent.Chan (newChan, writeChan)+import Data.ByteString (empty)+import Data.Text (pack)+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import Filesystem (removeFile, rename, writeFile, writeTextFile)+import Filesystem.Path.CurrentOS ((</>), FilePath)+import System.FilePath.Glob (compile, match, Pattern)+import System.FSNotify.Path (fp)+import System.FSNotify.Types+import Test.Hspec (describe, it, Spec)+import Util+type Assertion = IO ()++spec :: Spec+spec = do+ describe "watchDir" $ do+ it "Create file" $ testFileName "txt" >>= createFileSpec ActionEnv+ it "Modify file" $ testFileName "txt" >>= modifyFileSpec ActionEnv+ it "Remove file" $ testFileName "txt" >>= removeFileSpec ActionEnv+ it "Rename file" $ renameInput >>= renameFileSpec ActionEnv+ it "Debounce" $ testFileName "txt" >>= dbFileSpec ActionEnv+ describe "watchDirChan" $ do+ it "Create file" $ testFileName "txt" >>= createFileSpec ChanEnv+ it "Modify file" $ testFileName "txt" >>= modifyFileSpec ChanEnv+ it "Remove file" $ testFileName "txt" >>= removeFileSpec ChanEnv+ it "Rename file" $ renameInput >>= renameFileSpec ChanEnv+ describe "watchTree" $ do+ it "Create file (pre-existing directory)" $ testFileName "txt" >>= createFileSpecR1 ActionEnv+ it "Create file (create directory)" $ testFileName "txt" >>= createFileSpecR2 ActionEnv+ it "Modify file" $ testFileName "txt" >>= modifyFileSpecR ActionEnv+ it "Remove file" $ testFileName "txt" >>= removeFileSpecR ActionEnv+ it "Rename file" $ renameInput >>= renameFileSpecR ActionEnv+ describe "watchTreeChan" $ do+ it "Create file (pre-existing directory)" $ testFileName "txt" >>= createFileSpecR1 ChanEnv+ it "Create file (create directory)" $ testFileName "txt" >>= createFileSpecR2 ChanEnv+ it "Modify file" $ testFileName "txt" >>= modifyFileSpecR ChanEnv+ it "Remove file" $ testFileName "txt" >>= removeFileSpecR ChanEnv+ it "Rename file" $ renameInput >>= renameFileSpecR ChanEnv++createFileSpec :: ChanActionEnv -> FilePath -> Assertion+createFileSpec envType fileName = do+ inEnv envType DirEnv act action $ matchEvents matchers+ where+ action :: FilePath -> IO ()+ action envDir = writeFile (envDir </> fileName) empty+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File creation" (matchCreate fileName)]++modifyFileSpec :: ChanActionEnv -> FilePath -> Assertion+modifyFileSpec envType fileName = do+ withTempDir $ \envDir -> do+ writeFile (envDir </> fileName) empty+ inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir+ where+ action :: FilePath -> IO ()+ action envDir = do+ writeTextFile (envDir </> fileName) $ pack "Hello world"+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File modification" (matchModify fileName)]++removeFileSpec :: ChanActionEnv -> FilePath -> Assertion+removeFileSpec envType fileName = do+ withTempDir $ \envDir -> do+ writeFile (envDir </> fileName) empty+ inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir+ where+ action :: FilePath -> IO ()+ action envDir = removeFile (envDir </> fileName)+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File deletion" (matchRemove fileName)]++renameFileSpec :: ChanActionEnv -> (FilePath, FilePath) -> Assertion+renameFileSpec envType (oldFileName, newFileName) = do+ withTempDir $ \envDir -> do+ writeFile (envDir </> oldFileName) empty+ inTempDirEnv envType DirEnv act action (matchEvents matchers) envDir+ where+ action :: FilePath -> IO ()+ action envDir = rename (envDir </> oldFileName) (envDir </> newFileName)+ matchers :: [EventPredicate]+ matchers = [ EventPredicate "Rename: File deletion" (matchRemove oldFileName)+ , EventPredicate "Rename: File creation" (matchCreate newFileName) ]++-- TODO: This is a weak test. What we actually need is an interface for+-- "anti-matchers" to ensure that certain events do NOT get reported.+dbFileSpec :: ChanActionEnv -> FilePath -> Assertion+dbFileSpec envType _ = do+ chan <- newChan+ inChanEnv envType DirEnv act (action chan) (matchEvents matchers) chan+ where+ action :: EventChannel -> FilePath -> IO ()+ action chan _ = do+ writeChan chan e1+ writeChan chan e2+ matchers :: [EventPredicate]+ matchers = [EventPredicate "First debounced event" (\e -> e == e1)]+ e1 :: Event+ e1 = Added (fp "") (posixSecondsToUTCTime 0)+ e2 :: Event+ e2 = Modified (fp "") (posixSecondsToUTCTime 0)++createFileSpecR1 :: ChanActionEnv -> FilePath -> Assertion+createFileSpecR1 envType fileName = do+ withTempDir $ \envDir -> do+ withNestedTempDir envDir $ \envPath -> do+ inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir+ where+ action :: FilePath -> FilePath -> IO ()+ action envPath _ = do+ writeFile (envPath </> fileName) empty+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File creation" (matchCreate fileName)]++createFileSpecR2 :: ChanActionEnv -> FilePath -> Assertion+createFileSpecR2 envType fileName = do+ withTempDir $ \envDir -> do+ inTempDirEnv envType TreeEnv act (action envDir) (matchEvents matchers) envDir+ where+ action :: FilePath -> FilePath -> IO ()+ action envDir _ = do+ withNestedTempDir envDir $ \envPath -> writeFile (envPath </> fileName) empty+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File creation" (matchCreate fileName)]++modifyFileSpecR :: ChanActionEnv -> FilePath -> Assertion+modifyFileSpecR envType fileName = do+ withTempDir $ \envDir -> do+ withNestedTempDir envDir $ \envPath -> do+ writeFile (envPath </> fileName) empty+ inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir+ where+ action :: FilePath -> FilePath -> IO ()+ action envPath _ = do+ writeTextFile (envPath </> fileName) $ pack "Hello world"+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File deletion" (matchModify fileName)]++removeFileSpecR :: ChanActionEnv -> FilePath -> Assertion+removeFileSpecR envType fileName = do+ withTempDir $ \envDir -> do+ withNestedTempDir envDir $ \envPath -> do+ writeFile (envPath </> fileName) empty+ inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir+ where+ action :: FilePath -> FilePath -> IO ()+ action envPath _ = do+ removeFile (envPath </> fileName)+ matchers :: [EventPredicate]+ matchers = [EventPredicate "File deletion" (matchRemove fileName)]++renameFileSpecR :: ChanActionEnv -> (FilePath, FilePath) -> Assertion+renameFileSpecR envType (oldFileName, newFileName) = do+ withTempDir $ \envDir -> do+ withNestedTempDir envDir $ \envPath -> do+ writeFile (envPath </> oldFileName) empty+ inTempDirEnv envType TreeEnv act (action envPath) (matchEvents matchers) envDir+ where+ action :: FilePath -> FilePath -> IO ()+ action envPath _ = rename (envPath </> oldFileName) (envPath </> newFileName)+ matchers :: [EventPredicate]+ matchers = [ EventPredicate "Rename: File deletion" (matchRemove oldFileName)+ , EventPredicate "Rename: File creation" (matchCreate newFileName) ]++renameInput :: IO (FilePath, FilePath)+renameInput = do+ oldName <- testFileName "txt"+ newName <- testFileName "txt"+ return (oldName, newName)++matchCreate :: FilePath -> Event -> Bool+matchCreate fileName (Added path _) = matchFP pattern path+ where+ pattern = compile $ "**/*" ++ fp fileName+matchCreate _ _ = False++matchModify :: FilePath -> Event -> Bool+matchModify fileName (Modified path _) = matchFP pattern path+ where+ pattern = compile $ "**/*" ++ fp fileName+matchModify _ _ = False++matchRemove :: FilePath -> Event -> Bool+matchRemove fileName (Removed path _) = matchFP pattern path+ where+ pattern = compile $ "**/*" ++ fp fileName+matchRemove _ _ = False++matchFP :: Pattern -> FilePath -> Bool+matchFP pattern path = match pattern $ fp path
+ test/Path.hs view
@@ -0,0 +1,139 @@+--+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org+--+{-# LANGUAGE CPP #-}++module Path (spec) where++import Prelude hiding (FilePath, writeFile)++import Control.Applicative ((<*>))+import Filesystem (writeFile)+import Filesystem.Path.CurrentOS (FilePath)+import Filesystem.Path ((</>), empty)+import System.FilePath.Glob (compile, match)+import System.FSNotify.Path (canonicalizeDirPath, canonicalizePath, findDirs, findFiles, fp)+import Test.Hspec (describe, it, Spec, shouldBe)+import Util+import qualified Data.ByteString as BS+type Assertion = IO ()++-- Boolean XOR+(.^.) :: Bool -> Bool -> Bool+(.^.) True True = False+(.^.) True False = True+(.^.) False True = True+(.^.) False False = False++hasTrailingSlash :: FilePath -> (FilePath -> IO FilePath) -> Assertion+hasTrailingSlash path canonicalizeFn = do+ let expectedTail = last $ fp (fp "dir" </> empty) -- Get OS/filesystem's idea of a separator+ actualPath <- canonicalizeFn path+ let actualTail = last (fp actualPath) :: Char+ actualTail `shouldBe` expectedTail++relPath :: FilePath+relPathSlash :: FilePath+absPath :: FilePath+absPathSlash :: FilePath++relPath = fp "."+#ifdef OS_Linux+absPath = fp "/home"+absPathSlash = fp "/home/"+relPathSlash = fp "./" </> empty+#else+# ifdef OS_Win32+absPath = fp "C:" </> fp "Windows"+absPathSlash = fp "C:" </> fp "Windows" </> empty+relPathSlash = fp ".\\" </> empty+# else+# ifdef OS_Mac+absPath = fp "/Users"+absPathSlash = fp "/Users/"+relPathSlash = fp "./"+# else+-- Assume UNIX-like for anything non-Linux/Windows/Mac+absPath = fp "/home"+absPathSlash = fp "/home/"+relPathSlash = fp "./" </> empty+# endif+# endif+#endif+++spec :: Spec+spec = do+ describe "canonicalizeDirPath" $ do+ it "Absolute path keeps trailing slash" $ do+ hasTrailingSlash absPathSlash canonicalizeDirPath+ it "Absolute path gains trailing slash" $ do+ hasTrailingSlash absPath canonicalizeDirPath+ it "Relative path keeps trailing slash" $ do+ hasTrailingSlash relPathSlash canonicalizeDirPath+ it "Relative path gains trailing slash" $ do+ hasTrailingSlash relPath canonicalizeDirPath+ describe "canonicalizePath" $ do+ it "Absolute path keeps trailing slash" $ do+ hasTrailingSlash absPathSlash canonicalizePath+ it "Relative path keeps trailing slash" $ do+ hasTrailingSlash relPathSlash canonicalizePath+ describe "findFiles" $ do+ it "Non-recursive" $ do+ withTempDir $ \tmpDir -> do+ fileName <- testFileName "txt"+ writeFile (tmpDir </> fileName) BS.empty+ files <- findFiles False tmpDir+ 1 `shouldBe` length files+ let (resultFP:_) = files+ pattern = "**/*" ++ fp fileName+ result = fp resultFP+ if match (compile pattern) result then+ True `shouldBe` True+ else+ result `shouldBe` pattern+ it "Recursive" $ do+ withTempDir $ \tmpDir -> do+ withNestedTempDir tmpDir $ \tmpPath -> do+ fileName <- testFileName "txt"+ writeFile (tmpPath </> fileName) BS.empty+ files <- findFiles True tmpDir+ 1 `shouldBe` length files+ let (resultFP:_) = files+ pattern = "**/*" ++ fp fileName+ result = fp resultFP+ if match (compile pattern) result then+ True `shouldBe` True+ else+ result `shouldBe` pattern+ describe "findDirs" $ do+ it "Non-recursive" $+ withTempDir $ \tmpDir -> do+ withNestedTempDir tmpDir $ \dirName -> do+ dirs <- findDirs False tmpDir+ 1 `shouldBe` length dirs+ let (resultFP:_) = dirs+ pattern = "**/*" ++ fp dirName+ result = fp resultFP+ if match (compile pattern) result then+ True `shouldBe` True+ else+ result `shouldBe` pattern+ it "Recursive" $+ withTempDir $ \tmpDir -> do+ withNestedTempDir tmpDir $ \dirName1 -> do+ withNestedTempDir tmpDir $ \dirName2 -> do+ dirs <- findDirs False tmpDir+ 2 `shouldBe` length dirs+ let pats = ["**/*" ++ fp dirName1, "**/*" ++ fp dirName2]+ patFns = map (match . compile) pats+ dirStrings = map fp dirs+ (r1:r2:r3:r4:_) = patFns <*> dirStrings+ -- The two patterns should succeed once and fail once on+ -- opposite tests.+ if (r1 .^. r2) && (r3 .^. r4) && (r1 .^. r3) && (r2 .^. r4) then+ True `shouldBe` True+ else+ dirStrings `shouldBe` pats+ return ()
+ test/Util.hs view
@@ -0,0 +1,189 @@+--+-- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org+-- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org+--++module Util where++import Prelude hiding (FilePath, catch, pred)++import Control.Concurrent (threadDelay)+import Control.Concurrent.Chan+import Control.Concurrent.MVar (MVar, newMVar, readMVar, swapMVar)+import Control.Exception+import Control.Monad (when)+import Data.Unique.Id+import Filesystem.Path.CurrentOS hiding (concat)+import System.Directory+import System.IO.Error (isPermissionError)+import System.FSNotify+import System.FSNotify.Path+import System.FSNotify.Types+import System.Random+import System.Timeout (timeout)++data ChanActionEnv =+ ChanEnv+ | ActionEnv+data DirTreeEnv =+ DirEnv+ | TreeEnv+data TestContext = TestContext ChanActionEnv DirTreeEnv ActionPredicate++data TestReport = TestReport FilePath [Event] deriving (Show)+data TestResult = TestResult Bool String TestReport deriving (Show)+type TestAction = FilePath -> IO ()+type MTestResult = MVar TestResult+type TestCase = MTestResult -> IO ()+type CurriedEventProcessor = TestReport -> IO (TestResult)+type EventProcessor = MTestResult -> CurriedEventProcessor+data EventPredicate = EventPredicate String (Event -> Bool)++void :: IO ()+void = return ()++predicateName :: EventPredicate -> String+predicateName (EventPredicate name _) = name++matchEvents :: [EventPredicate] -> EventProcessor+matchEvents preds mVar report@(TestReport _ events) =+ swapMVar mVar result >> return result+ where+ matchMatrix :: [[Bool]]+ matchMatrix = map (\(EventPredicate _ pred) -> map (\event -> pred event) events) preds+ matchList :: [Bool]+ matchList = map (\lst -> any id lst) matchMatrix+ errorList :: [(Bool, String)]+ errorList = zip matchList (map (\(EventPredicate errStr _) -> errStr) preds)+ errorString :: String+ errorString = foldl foldError "" errorList+ foldError :: String -> (Bool, String) -> String+ foldError accStr (success, errStr) = if not success then accStr ++ " " ++ errStr else accStr+ status :: Bool+ status = all id matchList+ result = if status then+ TestResult status "" report+ else+ TestResult status ("Failed to match events: " ++ errorString) report++newId :: IO String+newId = randomIO >>= initIdSupply >>= return . show . hashedId . idFromSupply++testFileName :: String -> IO FilePath+testFileName ext = do+ uId <- newId+ return $ fp ("test-" ++ uId ++ "." ++ ext)++testName :: IO FilePath+testName = do+ uId <- newId+ return $ fp ("sandbox-" ++ uId) </> empty++dirPreAction :: Int+dirPreAction = 500000++-- Delay to keep temporary directories around long enough for events to be+-- picked up by OS (in microseconds)+dirPostAction :: Int+dirPostAction = 500000++withTempDir :: (FilePath -> IO ()) -> IO ()+withTempDir fn = withNestedTempDir empty fn++withNestedTempDir :: FilePath -> (FilePath -> IO ()) -> IO ()+withNestedTempDir firstPath fn = do+ secondPath <- testName+ let path = if firstPath /= empty then+ fp $ firstPath </> secondPath+ else+ fp secondPath+ bracket (createDirectory path >> threadDelay dirPreAction >> return path) (attemptDirectoryRemoval . fp) (fn . fp)++attemptDirectoryRemoval :: FilePath -> IO ()+attemptDirectoryRemoval path = do+ threadDelay dirPostAction+ catch+ (removeDirectoryRecursive pathString)+ (\e -> when+ (not $ isPermissionError e)+ (throw e))+ where+ pathString = fp path++performAction :: TestAction -> FilePath -> IO ()+performAction action path = action path++reportOnAction :: FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult+reportOnAction = reportOnAction' []++reportOnAction' :: [Event] -> FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult+reportOnAction' events path chan processor = do+ result@(TestResult status _ _) <- processor (TestReport path events)+ if not status then do+ event <- readChan chan+ reportOnAction' (event:events) path chan processor+ else+ return result++actAndReport :: TestAction -> FilePath -> EventChannel -> CurriedEventProcessor -> IO TestResult+actAndReport action path chan processor = do+ performAction action path+ reportOnAction path chan processor++testTimeout :: Int+testTimeout = 3000000++timeoutTest :: MTestResult -> Maybe () -> IO ()+timeoutTest mResult Nothing = do+ result <- readMVar mResult+ error $ "TIMEOUT: Last test result: " ++ show result+timeoutTest mResult (Just _) = do+ result <- readMVar mResult+ case result of+ (TestResult False _ _) -> error $ show result+ (TestResult True _ _) -> void++runTest :: TestCase -> IO ()+runTest test = do+ mVar <- newMVar $ TestResult False "Timeout with no test result" (TestReport empty [])+ timeout testTimeout (test mVar) >>= timeoutTest mVar++inEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor -> IO ()+inEnv caEnv dtEnv reportPred action eventProcessor =+ withTempDir $ inTempDirEnv caEnv dtEnv reportPred action eventProcessor++inTempDirEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor-> FilePath -> IO ()+inTempDirEnv caEnv dtEnv reportPred action eventProcessor path =+ withManagerConf NoDebounce $ \manager -> do+ chan <- newChan+ inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan++inChanEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor -> EventChannel -> IO ()+inChanEnv caEnv dtEnv reportPred action eventProcessor chan =+ withTempDir $ \path -> do+ withManagerConf NoDebounce $ \manager -> do+ inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan++inTempDirChanEnv :: ChanActionEnv -> DirTreeEnv -> ActionPredicate -> TestAction -> EventProcessor-> FilePath -> WatchManager -> EventChannel -> IO ()+inTempDirChanEnv caEnv dtEnv reportPred action eventProcessor path manager chan = do+ watchInEnv caEnv dtEnv manager path reportPred chan+ runTest $ \mVar -> do+ _ <- actAndReport action path chan $ eventProcessor mVar+ void+ void++actionAsChan :: (WatchManager -> FilePath -> ActionPredicate -> Action -> IO ()) ->+ WatchManager -> FilePath -> ActionPredicate -> EventChannel -> IO ()+actionAsChan actionFunction wm path ap ec = actionFunction wm path ap (writeChan ec)++watchInEnv :: ChanActionEnv+ -> DirTreeEnv+ -> WatchManager+ -> FilePath+ -> ActionPredicate+ -> EventChannel+ -> IO ()+watchInEnv ChanEnv DirEnv = watchDirChan+watchInEnv ChanEnv TreeEnv = watchTreeChan+watchInEnv ActionEnv DirEnv = actionAsChan watchDir+watchInEnv ActionEnv TreeEnv = actionAsChan watchTree
− test/main.hs
@@ -1,11 +0,0 @@-module Main where--import Test.Hspec (hspec, Spec)-import qualified Path as P-import qualified FSNotify as FSN--main :: IO ()-main = hspec spec--spec :: Spec-spec = P.spec >> FSN.spec
+ test/watch-here.hs view
@@ -0,0 +1,20 @@+{-+{-# LANGUAGE OverloadedStrings #-}+import Filesystem.Path.CurrentOS+-}+import System.FSNotify+import Filesystem++main :: IO ()+main = do+ -- let wd = "."+ wd <- getWorkingDirectory+ print wd+ man <- startManager+ watchTree man wd (const True) print+ print "press retrun to stop"+ getLine+ print "watching stopped, press retrun to exit"+ stopManager man+ getLine+ return ()