packages feed

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 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 ()