packages feed

find-conduit 0.1.0 → 0.2.0

raw patch · 4 files changed

+163/−91 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Data.Conduit.Find: class HasFilePath a
- Data.Conduit.Find: getFilePath :: HasFilePath a => a -> FilePath
- Data.Conduit.Find: instance HasFilePath FileEntry
- Data.Conduit.Find: instance HasFilePath FilePath
- Data.Conduit.Find: sourceFileEntries :: MonadResource m => Looped m FilePath FileEntry -> FilePath -> Producer m FileEntry
+ Data.Conduit.Find: class HasFileInfo a
+ Data.Conduit.Find: depth :: (Monad m, HasFileInfo e) => (Int -> Bool) -> Predicate m e
+ Data.Conduit.Find: entryInfo :: FileEntry -> FileInfo
+ Data.Conduit.Find: filename_ :: (Monad m, HasFileInfo e) => FilePath -> Predicate m e
+ Data.Conduit.Find: getFileInfo :: HasFileInfo a => a -> FileInfo
+ Data.Conduit.Find: instance HasFileInfo FileEntry
+ Data.Conduit.Find: instance HasFileInfo FileInfo
+ Data.Conduit.Find: instance Show FileInfo
+ Data.Conduit.Find: withPath :: HasFileInfo a => Monad m => (FilePath -> m Bool) -> Predicate m a
+ Data.Conduit.Find: withStatus :: Monad m => (FileStatus -> m Bool) -> Predicate m FileEntry
- Data.Conduit.Find: FileEntry :: FilePath -> FileStatus -> FileEntry
+ Data.Conduit.Find: FileEntry :: FileInfo -> FileStatus -> FileEntry
- Data.Conduit.Find: entryPath :: FileEntry -> FilePath
+ Data.Conduit.Find: entryPath :: HasFileInfo a => a -> FilePath
- Data.Conduit.Find: findWithPreFilter :: (MonadIO m, MonadResource m) => FilePath -> Bool -> Predicate m FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: findWithPreFilter :: (MonadIO m, MonadResource m) => FilePath -> Bool -> Predicate m FileInfo -> Predicate m FileEntry -> Producer m FileEntry
- Data.Conduit.Find: glob :: (Monad m, HasFilePath e) => Text -> Predicate m e
+ Data.Conduit.Find: glob :: (Monad m, HasFileInfo e) => Text -> Predicate m e
- Data.Conduit.Find: ignoreVcs :: (MonadIO m, HasFilePath e) => Predicate m e
+ Data.Conduit.Find: ignoreVcs :: (MonadIO m, HasFileInfo e) => Predicate m e
- Data.Conduit.Find: lstat :: MonadIO m => Looped m FilePath FileEntry
+ Data.Conduit.Find: lstat :: MonadIO m => Looped m FileInfo FileEntry
- Data.Conduit.Find: prune :: (Monad m, HasFilePath e) => FilePath -> Predicate m e
+ Data.Conduit.Find: prune :: MonadIO m => Looped m a a -> Looped m a a
- Data.Conduit.Find: regex :: (Monad m, HasFilePath e) => Text -> Predicate m e
+ Data.Conduit.Find: regex :: (Monad m, HasFileInfo e) => Text -> Predicate m e
- Data.Conduit.Find: regexMatcher :: (Monad m, HasFilePath e) => (FilePath -> FilePath) -> Text -> Predicate m e
+ Data.Conduit.Find: regexMatcher :: (Monad m, HasFileInfo e) => (FilePath -> FilePath) -> Text -> Predicate m e
- Data.Conduit.Find: stat :: MonadIO m => Looped m FilePath FileEntry
+ Data.Conduit.Find: stat :: MonadIO m => Looped m FileInfo FileEntry

Files

Data/Conduit/Find.hs view
@@ -5,8 +5,8 @@ module Data.Conduit.Find     ( FileEntry(..)     , Predicate-    , HasFilePath(..)-    , sourceFileEntries+    , HasFileInfo(..)+    , entryPath     , matchAll     , ignoreVcs     , regexMatcher@@ -17,6 +17,10 @@     , getPath     , regular     , executable+    , filename_+    , depth+    , withPath+    , withStatus     , prune     , test     , find@@ -30,6 +34,7 @@     , not_     ) where +import Debug.Trace import Conduit import Control.Applicative import Control.Arrow@@ -37,7 +42,6 @@ import Data.Attoparsec.Text import Data.Bits import Data.Conduit.Find.Looped-import Data.Foldable (for_) import Data.Monoid import Data.Text (Text, unpack, pack) import Filesystem.Path.CurrentOS (FilePath, encodeString, filename)@@ -45,22 +49,33 @@ import System.Posix.Files import Text.Regex.Posix ((=~)) +data FileInfo = FileInfo+    { infoPath  :: FilePath+    , infoDepth :: Int+    }++instance Show FileInfo where+    show info = "FileInfo " ++ show (infoPath info)+              ++ " " ++ show (infoDepth info)+ data FileEntry = FileEntry-    { entryPath   :: FilePath+    { entryInfo   :: FileInfo     , entryStatus :: FileStatus     }  instance Show FileEntry where-    show entry = "FileEntry " ++ show (entryPath entry)+    show entry = "FileEntry " ++ show (entryInfo entry) -class HasFilePath a where-    getFilePath :: a -> FilePath+class HasFileInfo a where+    getFileInfo :: a -> FileInfo -instance HasFilePath FilePath where-    getFilePath = id+instance HasFileInfo FileInfo where+    getFileInfo = id+    {-# INLINE getFileInfo #-} -instance HasFilePath FileEntry where-    getFilePath = entryPath+instance HasFileInfo FileEntry where+    getFileInfo = entryInfo+    {-# INLINE getFileInfo #-}  type Predicate m a = Looped m a a @@ -77,16 +92,12 @@ --   'followSymlinks' is @False@ it only prevents directory symlinks from --   being read. sourceFileEntries :: MonadResource m-                  => Looped m FilePath FileEntry-                  -> FilePath+                  => FileInfo+                  -> Looped m FileInfo FileEntry                   -> Producer m FileEntry-sourceFileEntries matcher dir = sourceDirectory dir =$= go matcher+sourceFileEntries (FileInfo p d) m = sourceDirectory p =$= awaitForever f   where-    go m = do-        mfp <- await-        for_ mfp $ \fp -> do-            applyPredicate m fp yield (`sourceFileEntries` fp)-            go m+    f fp = applyPredicate m (FileInfo (trace ("fp: " ++ show (fp)) $ fp) (trace ("d: " ++ show (d)) $ d)) yield (sourceFileEntries (FileInfo fp (succ d)))  -- | Return all entries.  This is the same as 'sourceDirectoryDeep', except --   that the 'FileStatus' structure for each entry is also provided.  As a@@ -95,11 +106,20 @@ matchAll :: Monad m => Predicate m a matchAll = Looped $ \entry -> return $ KeepAndRecurse entry matchAll +entryPath :: HasFileInfo a => a -> FilePath+entryPath = infoPath . getFileInfo++withPath :: HasFileInfo a => Monad m => (FilePath -> m Bool) -> Predicate m a+withPath f = ifM_ (f . entryPath)++withStatus :: Monad m => (FileStatus -> m Bool) -> Predicate m FileEntry+withStatus f = ifM_ (f . entryStatus)+ -- | Return all entries, except for those within version-control metadata --   directories (and not including the version control directory itself either).-ignoreVcs :: (MonadIO m, HasFilePath e) => Predicate m e+ignoreVcs :: (MonadIO m, HasFileInfo e) => Predicate m e ignoreVcs = Looped $ \entry ->-    return $ if filename (getFilePath entry) `elem` vcsDirs+    return $ if filename (entryPath entry) `elem` vcsDirs              then Ignore              else KeepAndRecurse entry ignoreVcs   where@@ -108,7 +128,7 @@ -- | The 'regexMatcher' predicate builder matches some part of every path --   against a given regex.  Use the simpler 'regex' if you just want to apply --   a regex to every file name.-regexMatcher :: (Monad m, HasFilePath e)+regexMatcher :: (Monad m, HasFileInfo e)              => (FilePath -> FilePath)                 -- ^ Function that specifies which part of the pathname to                 --   match against.  Use this to match against only filenames,@@ -120,17 +140,19 @@ regexMatcher accessor (unpack -> pat) = go   where     go = Looped $ \entry ->-        return $ if encodeString (accessor (getFilePath entry)) =~ pat+        return $ if pathStr entry =~ pat                  then KeepAndRecurse entry go                  else Recurse go +    pathStr = encodeString . accessor . entryPath+ -- | Find every entry whose filename part matching the given regular expression.-regex :: (Monad m, HasFilePath e) => Text -> Predicate m e+regex :: (Monad m, HasFileInfo e) => Text -> Predicate m e regex = regexMatcher filename  -- | Find every entry whose filename part matching the given filename globbing --   expression.  For example: @glob "*.hs"@.-glob :: (Monad m, HasFilePath e) => Text -> Predicate m e+glob :: (Monad m, HasFileInfo e) => Text -> Predicate m e glob g = case parseOnly globParser g of     Left e  -> error $ "Failed to parse glob: " ++ e     Right x -> regex ("^" <> x <> "$")@@ -151,18 +173,18 @@                             else [x]  doStat :: MonadIO m-       => (String -> IO FileStatus) -> Looped m FilePath FileEntry-doStat getstatus = Looped $ \path -> do-    s <- liftIO $ getstatus (encodeString path)-    let entry = FileEntry path s+       => (String -> IO FileStatus) -> Looped m FileInfo FileEntry+doStat getstatus = Looped $ \(FileInfo p d) -> do+    s <- liftIO $ getstatus (encodeString p)+    let entry = FileEntry (FileInfo p d) s     return $ if isDirectory s              then KeepAndRecurse entry (doStat getstatus)              else Keep entry -lstat :: MonadIO m => Looped m FilePath FileEntry+lstat :: MonadIO m => Looped m FileInfo FileEntry lstat = doStat getSymbolicLinkStatus -stat :: MonadIO m => Looped m FilePath FileEntry+stat :: MonadIO m => Looped m FileInfo FileEntry stat = doStat getFileStatus  getPath :: MonadIO m => Looped m FileEntry FilePath@@ -177,79 +199,84 @@ executable :: Monad m => Predicate m FileEntry executable = status (\s -> fileMode s .&. ownerExecuteMode /= 0) -prune :: (Monad m, HasFilePath e) => FilePath -> Predicate m e-prune path = Looped $ \entry ->-    return $ if getFilePath entry == path-             then Ignore-             else KeepAndRecurse entry (prune path)+filename_ :: (Monad m, HasFileInfo e) => FilePath -> Predicate m e+filename_ path = if_ ((== path) . filename . entryPath) +depth :: (Monad m, HasFileInfo e) => (Int -> Bool) -> Predicate m e+depth f = if_ (f . infoDepth . getFileInfo)+ test :: MonadIO m => Predicate m FileEntry -> FilePath -> m Bool test matcher path =-    getAny `liftM` testSingle (stat >>> matcher) path alwaysTrue+    getAny `liftM` testSingle (stat >>> matcher) (FileInfo path 0) alwaysTrue   where     alwaysTrue = const (return (Any True)) -find :: (MonadIO m, MonadResource m)-     => FilePath -> Predicate m FileEntry -> Producer m FilePath-find path pr = sourceFileEntries (stat >>> pr) path =$= mapC entryPath--data FindFilter-    = IgnoreFile-    | ConsiderFile-    | MaybeRecurse+data FindFilter = IgnoreFile+                | ConsiderFile+                | MaybeRecurse     deriving (Show, Eq)  -- | Run a find, but using a pre-pass filter on the FilePaths, to eliminates --   files from consideration early and avoid calling stat on them.+doFindPreFilter :: (MonadIO m, MonadResource m)+                => FileInfo+                -> Bool+                -> Predicate m FileInfo+                -> Predicate m FileEntry+                -> Producer m FileEntry+doFindPreFilter (FileInfo path dp) follow filt pr =+    sourceDirectory path =$= awaitForever (worker (succ dp) pr)+  where+    worker d m fp = do+        let info = FileInfo fp d+        r <- lift $ runLooped filt (trace ("pf info: " ++ show (info)) $ info)+        let candidate = case r of+                Ignore -> IgnoreFile+                Keep _ -> ConsiderFile+                Recurse _ -> MaybeRecurse+                KeepAndRecurse _ _ -> ConsiderFile+        trace ("candidate: " ++ show (candidate)) $ return ()+        unless (candidate == IgnoreFile) $ do+            st <- liftIO $+                (if follow+                 then getFileStatus+                 else getSymbolicLinkStatus) (encodeString fp)+            let next = when (isDirectory st) . doFindPreFilter info follow filt+            case candidate of+                IgnoreFile   -> return ()+                MaybeRecurse -> next pr+                ConsiderFile ->+                    applyPredicate m (FileEntry (FileInfo fp d) st) yield next+ findWithPreFilter :: (MonadIO m, MonadResource m)                   => FilePath                   -> Bool-                  -> Predicate m FilePath+                  -> Predicate m FileInfo                   -> Predicate m FileEntry                   -> Producer m FileEntry-findWithPreFilter path follow filt pr =-    sourceDirectory path =$= go pr-  where-    go m = do-        mfp <- await-        for_ mfp $ \fp -> do-            r <- lift $ runLooped filt fp-            let candidate = case r of-                    Ignore -> IgnoreFile-                    Keep _ -> ConsiderFile-                    Recurse _ -> MaybeRecurse-                    KeepAndRecurse _ _ -> ConsiderFile-            unless (candidate == IgnoreFile) $ do-                st <- liftIO $-                    (if follow-                     then getFileStatus-                     else getSymbolicLinkStatus) (encodeString fp)-                let next = when (isDirectory st) .-                               findWithPreFilter fp follow filt-                case candidate of-                    IgnoreFile -> return ()-                    MaybeRecurse -> next pr-                    ConsiderFile ->-                        applyPredicate m (FileEntry fp st) yield next-            go m+findWithPreFilter path = doFindPreFilter (FileInfo path 1)  find' :: (MonadIO m, MonadResource m)       => FilePath -> Predicate m FileEntry -> Producer m FileEntry-find' path pr = sourceFileEntries (stat >>> pr) path+find' path pr = sourceFileEntries (FileInfo path 1) (stat >>> pr) -lfind :: (MonadIO m, MonadResource m)-      => FilePath -> Predicate m FileEntry -> Producer m FilePath-lfind path pr = sourceFileEntries (lstat >>> pr) path =$= mapC entryPath+find :: (MonadIO m, MonadResource m)+     => FilePath -> Predicate m FileEntry -> Producer m FilePath+find path pr = find' path pr =$= mapC entryPath  lfind' :: (MonadIO m, MonadResource m)        => FilePath -> Predicate m FileEntry -> Producer m FileEntry-lfind' path pr = sourceFileEntries (lstat >>> pr) path+lfind' path pr = sourceFileEntries (FileInfo path 1) (lstat >>> pr) +lfind :: (MonadIO m, MonadResource m)+      => FilePath -> Predicate m FileEntry -> Producer m FilePath+lfind path pr = lfind' path pr =$= mapC entryPath+ readPaths :: (MonadIO m, MonadResource m)           => FilePath -> Predicate m FilePath -> Producer m FilePath-readPaths path pr = sourceDirectory path =$= do-    mfp <- await-    for_ mfp $ \fp -> do+readPaths path pr = sourceDirectory path =$= awaitForever f+  where+    f fp = do         r <- lift $ runLooped pr fp         case r of             Ignore -> return ()
Data/Conduit/Find/Looped.hs view
@@ -3,6 +3,7 @@ -- | Main entry point to the application. module Data.Conduit.Find.Looped where +import Debug.Trace import Control.Applicative import Control.Arrow import Control.Category@@ -131,7 +132,7 @@                -> (Looped m a b -> t m ()) -> t m () applyPredicate l x f g = do     r <- lift $ runLooped l x-    case r of+    case (trace ("r: " ++ show (r)) $ r) of         Ignore -> return ()         Keep a -> f a         Recurse m -> g m@@ -157,6 +158,13 @@              then KeepAndRecurse a (if_ f)              else Recurse (if_ f) +ifM_ :: Monad m => (a -> m Bool) -> Looped m a a+ifM_ f = Looped $ \a -> do+    r <- f a+    return $ if r+             then KeepAndRecurse a (ifM_ f)+             else Recurse (ifM_ f)+ or_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b or_ (Looped f) (Looped g) = Looped $ \a -> do     r <- f a@@ -182,10 +190,26 @@     go a (Recurse l) = KeepAndRecurse a (not_ l)     go _ (KeepAndRecurse _ l) = Recurse (not_ l) -pruneIgnored :: MonadIO m => Looped m a a -> Looped m a a-pruneIgnored (Looped f) = Looped (\a -> go a `liftM` f a)+prune :: MonadIO m => Looped m a a -> Looped m a a+prune (Looped f) = Looped (\a -> go a `liftM` f a)   where-    go _ Ignore = Ignore-    go _ x@(Keep _) = x-    go _ (Recurse _) = Ignore-    go _ (KeepAndRecurse b m) = KeepAndRecurse b (pruneIgnored m)+    go a Ignore = trace ("prune keep") $ Keep a+    go _ (Keep _) = trace ("prune drop") $ Ignore+    go a (Recurse l) = trace ("prune keepr") $ KeepAndRecurse a (prune l)+    go _ (KeepAndRecurse _ _) = trace ("prune drop") $ Ignore++promote :: Monad m => (a -> m (Maybe b)) -> Looped m a b+promote f = Looped $ \a -> do+    r <- f a+    return $ case r of+        Nothing -> Recurse (promote f)+        Just b -> KeepAndRecurse b (promote f)++demote :: Monad m => Looped m a b -> a -> m (Maybe b)+demote (Looped f) a = do+    r <- f a+    return $ case r of+        Ignore -> Nothing+        Keep b -> Just b+        Recurse _ -> Nothing+        KeepAndRecurse b _ -> Just b
find-conduit.cabal view
@@ -1,5 +1,5 @@ Name:                find-conduit-Version:             0.1.0+Version:             0.2.0 Synopsis:            A file-finding conduit that allows user control over traversals. License-file:        LICENSE License:             MIT
test/main.hs view
@@ -26,7 +26,7 @@             xs <- runResourceT $                 find "."                     (    ignoreVcs-                     >>> prune "./dist"+                     >>> prune (filename_ "dist")                      >>> glob "*.hs"                      >>> not_ (glob "Setup*")                      >>> regular@@ -45,7 +45,9 @@                     (    ignoreVcs                      >>> glob "*.hs"                      >>> not_ (glob "Setup*")-                     >>> prune "./dist"+                     -- This prune only applies to .hs files now, so it won't+                     -- match anything, thus having no effect but burning CPU!+                     >>> prune (filename_ "dist")                      >>> regular                      >>> not_ executable                     )@@ -60,16 +62,35 @@             xs <- runResourceT $                 findWithPreFilter "." True                     (    ignoreVcs+                     >>> prune (filename_ "dist")                      >>> glob "*.hs"                      >>> not_ (glob "Setup*")-                     >>> prune "./dist"                     )                     (    regular                      >>> not_ executable                     )-                    =$ mapC getFilePath $$ sinkList+                    =$ mapC entryPath $$ sinkList              "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist/setup-config" `elem` xs `shouldBe` False+            "./Setup.hs" `elem` xs `shouldBe` False+            "./.git/config" `elem` xs `shouldBe` False++        it "properly applies post-pass pruning" $ do+            xs <- runResourceT $+                findWithPreFilter "." True+                    (    ignoreVcs+                     >>> prune (depth (>=1))+                     >>> prune (filename_ "dist")+                     >>> glob "*.hs"+                     >>> not_ (glob "Setup*")+                    )+                    (    regular+                     >>> not_ executable+                    )+                    =$ mapC entryPath $$ sinkList++            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` False             "./dist/setup-config" `elem` xs `shouldBe` False             "./Setup.hs" `elem` xs `shouldBe` False             "./.git/config" `elem` xs `shouldBe` False