packages feed

find-conduit 0.2.3 → 0.3.0

raw patch · 5 files changed

+466/−508 lines, 5 filesdep +exceptionsdep +monad-controldep +transformersdep −profunctorsPVP ok

version bump matches the API change (PVP)

Dependencies added: exceptions, monad-control, transformers, transformers-base

Dependencies removed: profunctors

API changes (from Hackage documentation)

- Data.Conduit.Find: and_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b
- Data.Conduit.Find: class HasFileInfo a
- Data.Conduit.Find: consider :: a -> Looped m a b -> Looped m a b
- Data.Conduit.Find: entryInfo :: FileEntry -> FileInfo
- Data.Conduit.Find: findWithPreFilter :: (MonadIO m, MonadResource m) => FilePath -> Bool -> Predicate m FileInfo -> Predicate m FileEntry -> Producer m FileEntry
- Data.Conduit.Find: getFileInfo :: HasFileInfo a => a -> FileInfo
- Data.Conduit.Find: ignoreAll :: Monad m => Looped m a b
- Data.Conduit.Find: instance Eq FindFilter
- Data.Conduit.Find: instance HasFileInfo FileEntry
- Data.Conduit.Find: instance HasFileInfo FileInfo
- Data.Conduit.Find: instance Show FileInfo
- Data.Conduit.Find: instance Show FindFilter
- Data.Conduit.Find: matchAll :: Monad m => Predicate m a
- Data.Conduit.Find: not_ :: MonadIO m => Predicate m a -> Predicate m a
- Data.Conduit.Find: or_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b
- Data.Conduit.Find: prune :: MonadIO m => Predicate m a -> Predicate m a
- Data.Conduit.Find: readPaths :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FilePath -> Producer m FilePath
- Data.Conduit.Find: type Predicate m a = Looped m a a
- Data.Conduit.Find: withStatus :: Monad m => (FileStatus -> m Bool) -> Predicate m FileEntry
+ Data.Conduit.Find: entryDepth :: FileEntry -> Int
+ Data.Conduit.Find: findRaw :: (MonadIO m, MonadResource m) => FilePath -> Bool -> Predicate m FileEntry -> Source m FileEntry
+ Data.Conduit.Find: withFileStatus :: Monad m => (FileStatus -> m Bool) -> Predicate m FileEntry
- Data.Conduit.Find: FileEntry :: FileInfo -> FileStatus -> FileEntry
+ Data.Conduit.Find: FileEntry :: FilePath -> Int -> Maybe FileStatus -> FileEntry
- Data.Conduit.Find: depth :: (Monad m, HasFileInfo e) => (Int -> Bool) -> Predicate m e
+ Data.Conduit.Find: depth :: Monad m => (Int -> Bool) -> Predicate m FileEntry
- Data.Conduit.Find: entryPath :: HasFileInfo a => a -> FilePath
+ Data.Conduit.Find: entryPath :: FileEntry -> FilePath
- Data.Conduit.Find: entryStatus :: FileEntry -> FileStatus
+ Data.Conduit.Find: entryStatus :: FileEntry -> Maybe FileStatus
- Data.Conduit.Find: filenameS_ :: (Monad m, HasFileInfo e) => (String -> Bool) -> Predicate m e
+ Data.Conduit.Find: filenameS_ :: Monad m => (String -> Bool) -> Predicate m FileEntry
- Data.Conduit.Find: filename_ :: (Monad m, HasFileInfo e) => (FilePath -> Bool) -> Predicate m e
+ Data.Conduit.Find: filename_ :: Monad m => (FilePath -> Bool) -> Predicate m FileEntry
- Data.Conduit.Find: filepathS_ :: (Monad m, HasFileInfo e) => (String -> Bool) -> Predicate m e
+ Data.Conduit.Find: filepathS_ :: Monad m => (String -> Bool) -> Predicate m FileEntry
- Data.Conduit.Find: filepath_ :: (Monad m, HasFileInfo e) => (FilePath -> Bool) -> Predicate m e
+ Data.Conduit.Find: filepath_ :: Monad m => (FilePath -> Bool) -> Predicate m FileEntry
- Data.Conduit.Find: find :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FilePath
+ Data.Conduit.Find: find :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Source m FilePath
- Data.Conduit.Find: find' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: find' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Source m FileEntry
- Data.Conduit.Find: glob :: (Monad m, HasFileInfo e) => Text -> Predicate m e
+ Data.Conduit.Find: glob :: Monad m => Text -> Predicate m FileEntry
- Data.Conduit.Find: ignoreVcs :: (MonadIO m, HasFileInfo e) => Predicate m e
+ Data.Conduit.Find: ignoreVcs :: Monad m => Predicate m FileEntry
- Data.Conduit.Find: lfind :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FilePath
+ Data.Conduit.Find: lfind :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Source m FilePath
- Data.Conduit.Find: lfind' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: lfind' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Source m FileEntry
- Data.Conduit.Find: lstat :: MonadIO m => Looped m FileInfo FileEntry
+ Data.Conduit.Find: lstat :: MonadIO m => Predicate m FileEntry
- Data.Conduit.Find: regex :: (Monad m, HasFileInfo e) => Text -> Predicate m e
+ Data.Conduit.Find: regex :: Monad m => Text -> Predicate m FileEntry
- Data.Conduit.Find: stat :: MonadIO m => Looped m FileInfo FileEntry
+ Data.Conduit.Find: stat :: MonadIO m => Predicate m FileEntry
- Data.Conduit.Find: withPath :: HasFileInfo a => Monad m => (FilePath -> m Bool) -> Predicate m a
+ Data.Conduit.Find: withPath :: Monad m => (FilePath -> m Bool) -> Predicate m FileEntry

Files

+ Data/Cond.hs view
@@ -0,0 +1,300 @@+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++module Data.Cond+    ( Result(..), toResult, fromResult+    , CondT(..), runCondT, Cond, runCond+    , if_, ifM_, apply, test+    , reject, rejectAll, norecurse+    , or_, (||:), and_, (&&:), not_, prune+    , traverseRecursively+    ) where++import Control.Applicative+import Control.Arrow (first)+import Control.Monad+import Control.Monad.Base+import Control.Monad.Catch+import Control.Monad.Reader.Class+import Control.Monad.State.Class+import Control.Monad.Trans+import Control.Monad.Trans.Control+import Control.Monad.Trans.State (StateT(..), withStateT, evalStateT)+import Data.Foldable+import Data.Functor.Identity+import Data.List.NonEmpty+import Data.Maybe (fromMaybe, isJust)+import Data.Monoid hiding ((<>))+import Data.Semigroup+import Prelude hiding (foldr1)++-- | 'Result' is an enriched 'Maybe' type which additionally specifies whether+--   recursion should occur from the given input, and if so, how such+--   recursion should be done.  It is isomorphic to:+--+-- @+-- type Result a m b = (Maybe b, Maybe (Maybe (CondT a m b)))+-- @+data Result a m b = Ignore+                  | Keep b+                  | RecurseOnly (Maybe (CondT a m b))+                  | KeepAndRecurse b (Maybe (CondT a m b))++instance Show b => Show (Result a m b) where+    show Ignore               = "Ignore"+    show (Keep a)             = "Keep " ++ show a+    show (RecurseOnly _)      = "RecurseOnly"+    show (KeepAndRecurse a _) = "KeepAndRecurse " ++ show a++instance Monad m => Functor (Result a m) where+    fmap _ Ignore               = Ignore+    fmap f (Keep a)             = Keep (f a)+    fmap f (RecurseOnly l)      = RecurseOnly (liftM (fmap f) l)+    fmap f (KeepAndRecurse a l) = KeepAndRecurse (f a) (liftM (fmap f) l)++instance Semigroup (Result a m b) where+    Ignore        <> _                  = Ignore+    _             <> Ignore             = Ignore+    RecurseOnly _ <> Keep _             = Ignore+    RecurseOnly _ <> KeepAndRecurse _ m = RecurseOnly m+    RecurseOnly m <> _                  = RecurseOnly m+    Keep _        <> RecurseOnly _      = Ignore+    _             <> RecurseOnly m      = RecurseOnly m+    _             <> Keep b             = Keep b+    Keep _        <> KeepAndRecurse b _ = Keep b+    _             <> KeepAndRecurse b m = KeepAndRecurse b m++instance Monoid b => Monoid (Result a m b) where+    mempty        = KeepAndRecurse mempty Nothing+    x `mappend` y = x <> y++recurse :: Result a m b+recurse = RecurseOnly Nothing++accept :: b -> Result a m b+accept = flip KeepAndRecurse Nothing++toResult :: Monad m => Maybe a -> forall r. Result r m a+toResult Nothing  = recurse+toResult (Just a) = accept a++fromResult :: Monad m => forall r. Result r m a -> Maybe a+fromResult Ignore               = Nothing+fromResult (Keep a)             = Just a+fromResult (RecurseOnly _)      = Nothing+fromResult (KeepAndRecurse a _) = Just a++-- | 'CondT" is an arrow that maps from 'a' to @m b@, but only if 'a'+--   satisfies certain conditions.  It is a Monad, meaning each condition+--   stated must be satisfied for the map to succeed (in the spirit of the+--   'Maybe' short-circuiting Monad).  In fact, 'CondT' is nearly equivalent+--   to @StateT a (MaybeT m) b@, with some additional complexity for+--   performing recursive iterations (see the 'Result' type above).+--+--   You can promote functions of type @a -> m (Maybe b)@ into 'CondT' using+--   'apply'.  Pure functions @a -> Bool@ are lifted with 'if_', and+--   @a -> m Bool@ with 'ifM_'.  In effect, @if_ f@ is the same as+--   @ask >>= guard . f@.+--+--   Here is a trivial example:+--+-- @+--   flip runCondT 42 $ do+--     if_ even+--     liftIO $ putStrLn "42 must be even to reach here"+--+--     if_ odd <|> if_ even+--     if_ even &&: if_ (== 42)+--     if_ ((== 0) . (`mod` 6))+-- @+--+--   'CondT' is typically invoked using 'runCondT', in which case it maps 'a'+--   to 'Maybe b'.  It can also be run with 'applyCondT', which does case+--   analysis on the final 'Result' type, specifying how recursion should be+--   performed from the given 'a' value.  This is useful when applying+--   Conduits to structural traversals, and was the motivation behind this+--   type.+newtype CondT a m b = CondT { getCondT :: StateT a m (Result a m b) }++type Cond a = CondT a Identity++instance Monad m => Semigroup (CondT a m b) where+    (<>) = (>>)++instance Monad m => Monoid (CondT a m a) where+    mempty  = ask+    mappend = (<>)++instance Monad m => Functor (CondT a m) where+    fmap f (CondT g) = CondT (liftM (fmap f) g)++instance Monad m => Applicative (CondT a m) where+    pure  = return+    (<*>) = ap++instance Monad m => Monad (CondT a m) where+    return = CondT . return . accept+    fail _ = mzero+    CondT f >>= k = CondT $ do+        r <- f+        case r of+            Ignore -> return Ignore+            Keep b -> do+                n <- getCondT (k b)+                return $ case n of+                    RecurseOnly _      -> Ignore+                    KeepAndRecurse c _ -> Keep c+                    _                  -> n+            RecurseOnly l -> return $ RecurseOnly (fmap (>>= k) l)+            KeepAndRecurse b _ -> getCondT (k b)++instance Monad m => MonadReader a (CondT a m) where+    ask               = CondT $ liftM accept get+    local f (CondT m) = CondT $ withStateT f m+    reader f          = f <$> ask++instance Monad m => MonadState a (CondT a m) where+    get     = CondT $ accept `liftM` get+    put s   = CondT $ accept `liftM` put s+    state f = CondT $ state (fmap (first accept) f)++instance Monad m => MonadPlus (CondT a m) where+    mzero = CondT $ return recurse+    CondT f `mplus` CondT g = CondT $ do+        r <- f+        case r of+            x@(Keep _) -> return x+            x@(KeepAndRecurse _ _) -> return x+            _ -> g++instance MonadThrow m => MonadThrow (CondT a m) where+  throwM = CondT . throwM++instance MonadCatch m => MonadCatch (CondT a m) where+    catch (CondT m) c = CondT $ m `catch` \e -> getCondT (c e)+    mask a = CondT $ mask $ \u -> getCondT (a $ q u)+      where q u = CondT . u . getCondT+    uninterruptibleMask a =+        CondT $ uninterruptibleMask $ \u -> getCondT (a $ q u)+      where q u = CondT . u . getCondT++instance MonadBase b m => MonadBase b (CondT a m) where+    liftBase m = CondT $ accept `liftM` liftBase m++instance MonadIO m => MonadIO (CondT a m) where+    liftIO m = CondT $ accept `liftM` liftIO m++instance MonadTrans (CondT a) where+    lift m = CondT $ accept `liftM` lift m++instance MonadBaseControl b m => MonadBaseControl b (CondT r m) where+    newtype StM (CondT r m) a =+        CondTStM { unCondTStM :: StM m (Result r m a, r) }+    liftBaseWith f = CondT $ StateT $ \s ->+        liftM (\x -> (accept x, s)) $ liftBaseWith $ \runInBase -> f $ \k ->+            liftM CondTStM $ runInBase $ runStateT (getCondT k) s+    restoreM = CondT . StateT . const . restoreM . unCondTStM+    {-# INLINE liftBaseWith #-}+    {-# INLINE restoreM #-}++runCondT :: Monad m => CondT a m b -> a -> m (Maybe b)+runCondT (CondT f) a = do+    r <- evalStateT f a+    return $ case r of+        Ignore             -> Nothing+        Keep b             -> Just b+        RecurseOnly _      -> Nothing+        KeepAndRecurse b _ -> Just b++runCond :: Cond a b -> a -> Maybe b+runCond = (runIdentity .) . runCondT++if_ :: Monad m => (a -> Bool) -> CondT a m ()+if_ f = ask >>= guard . f++ifM_ :: Monad m => (a -> m Bool) -> CondT a m ()+ifM_ f = ask >>= lift . f >>= guard++apply :: Monad m => (a -> m (Maybe b)) -> CondT a m b+apply f = CondT $ liftM toResult . lift . f =<< get++test :: Monad m => CondT a m b -> a -> m Bool+test = (liftM isJust .) . runCondT++-- | 'reject' rejects the current entry, but allows recursion.  This is the+--   same as 'mzero'.+reject :: Monad m => CondT a m b+reject = mzero++-- | 'rejectAll' rejects the entry and all of its descendents.+rejectAll :: Monad m => CondT a m b+rejectAll = CondT $ return Ignore++-- | 'norecurse' indicates that recursion should not be performed on the current+--   item.  Note that this library doesn't perform any actual recursion; that+--   is up to the consumer of the final 'Result' value, typically+--   'applyCondT'.+norecurse :: Monad m => CondT a m ()+norecurse = CondT $ return (Keep ())++or_ :: Monad m => NonEmpty (CondT a m b) -> CondT a m b+or_ = foldr1 mplus++infixr 3 &&:+(||:) :: Monad m => CondT a m b -> CondT a m b -> CondT a m b+(||:) = mplus++and_ :: Monad m => [CondT a m b] -> CondT a m [b]+and_ = foldl' (\acc x -> (:) <$> x <*> acc) (return [])++infixr 2 ||:+(&&:) :: Monad m => CondT a m b -> CondT a m c -> CondT a m (b, c)+(&&:) = liftM2 (,)++-- | 'not_' inverts the meaning of the given predicate while preserving+--   recursion.+not_ :: Monad m => CondT a m () -> CondT a m ()+not_ (CondT f) = CondT $ go `liftM` f+  where+    go Ignore               = accept ()+    go (Keep _)             = recurse+    go (RecurseOnly _)      = accept ()+    go (KeepAndRecurse _ _) = recurse++-- | 'prune' is like 'not_', but does not preserve recursion.  It should be read+--   as "prune this entry and all its descendents if the predicate matches".+--   It is the same as @x ||: rejectAll@.+prune :: Monad m => CondT a m () -> CondT a m ()+prune (CondT f) = CondT $ go `liftM` f+  where+    go Ignore               = accept ()+    go (Keep _)             = Ignore+    go (RecurseOnly _)      = accept ()+    go (KeepAndRecurse _ _) = Ignore++-- | This function expresses the pattern of recursive traversal, directed by a+--   'CondT' predicate.  It does not require that the structure being traversed+--   in memory, as it might very well be a filesystem, the decision tree of an+--   algorithm, etc.  It works very nicely with conduits, in order to iterate+--   over the traversal in constant space.+traverseRecursively :: (Monad m, Monad n)+                    => a+                    -> CondT a m b+                    -> (a -> b -> n ())+                    -> (forall x. m x -> n x)+                    -> (a -> (a -> n ()) -> n ())+                    -> n ()+traverseRecursively a c f trans getChildren = do+    r <- trans $ evalStateT (getCondT c) a+    case r of+        Ignore             -> return ()+        Keep b             -> f a b+        RecurseOnly m      -> descend (fromMaybe c m)+        KeepAndRecurse b m -> f a b >> descend (fromMaybe c m)+  where+    descend next = getChildren a $ \child ->+        traverseRecursively child next f trans getChildren
Data/Conduit/Find.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GADTs #-}  module Data.Conduit.Find     (@@ -20,11 +21,10 @@     , find'     , lfind     , lfind'-    , findWithPreFilter-    , readPaths     , stat     , lstat     , test+    , findRaw      -- * File path predicates     , ignoreVcs@@ -35,7 +35,6 @@     , filepath_     , filepathS_     , withPath-    , entryPath      -- * File entry predicates (uses stat information)     , regular@@ -44,39 +43,33 @@     , depth     , lastAccessed     , lastModified-    , withStatus+    , withFileStatus      -- * Predicate combinators-    , or_-    , and_-    , not_-    , prune-    , matchAll-    , ignoreAll-    , consider+    , module Cond     , (=~)      -- * Types and type classes     , FileEntry(..)-    , Predicate-    , HasFileInfo(..)     ) where -import Conduit-import Control.Applicative-import Control.Arrow-import Control.Monad-import Data.Attoparsec.Text-import Data.Bits-import Data.Conduit.Find.Looped-import Data.Monoid-import Data.Text (Text, unpack, pack)-import Data.Time-import Data.Time.Clock.POSIX-import Filesystem.Path.CurrentOS (FilePath, encodeString, filename)-import Prelude hiding (FilePath)-import System.Posix.Files-import System.Posix.Types+import           Conduit+import           Control.Applicative+import           Control.Monad+import           Control.Monad.State.Class+import           Data.Attoparsec.Text+import           Data.Bits+import qualified Data.Cond as Cond+import           Data.Cond hiding (test)+import           Data.Maybe (fromMaybe)+import           Data.Monoid+import           Data.Text (Text, unpack, pack)+import           Data.Time+import           Data.Time.Clock.POSIX+import           Filesystem.Path.CurrentOS (FilePath, encodeString, filename)+import           Prelude hiding (FilePath)+import           System.Posix.Files+import           System.Posix.Types import qualified Text.Regex.Posix as R ((=~))  {- $intro@@ -100,7 +93,7 @@ Would in find-conduit be:  @-find "src" (glob "*.hs" \<\> regular) $$ mapM_C (liftIO . print)+find "src" (glob \"*.hs\" \<\> regular) $$ mapM_C (liftIO . print) @  The 'glob' predicate matches the file basename against the globbing pattern,@@ -142,7 +135,7 @@ This is the same as using '-maxdepth 2' in find.  @-find \".\" (prune (filename_ (== "dist")))+find \".\" (prune (filename_ (== \"dist\"))) @  This is the same as:@@ -171,94 +164,54 @@  {- $notes -Predicates form a Category and an Arrow, so you can use Arrow-style-composition rather than Monoids if you wish.  They also form an Applicative, a-Monad and a MonadPlus.--In the Monad, the value bound over is whatever the predicate chooses to return-(most Predicates return the same FilePath they examined, however, making the-Monad less value).  Here's an example Monad: If the find takes longer than 5-minutes, abort.  We could have used 'timeout', but this is for illustration.--@-start <- liftIO getCurrentTime-find \".\" $ do-    glob \"*.hs\"--    end <- liftIO getCurrentTime-    if diffUTCTIme end start > 300-        then ignoreAll-        else matchAll-@--The Predicate Monad is a short-circuiting monad, meaning we stop as soon as it-can be determined that the user is not interested in a given file.  To access-the current file, simply bind the result value from any Predicate.  To change-the file being matched against,for whatever reason, use 'consider'.+See 'Data.Cond' for more details on the Monad used to build predicates. -} -data FileInfo = FileInfo-    { infoPath  :: FilePath-    , infoDepth :: Int-    }--instance Show FileInfo where-    show info = "FileInfo " ++ show (infoPath info)-              ++ " " ++ show (infoDepth info)+type Predicate m a = CondT a m ()  data FileEntry = FileEntry-    { entryInfo   :: FileInfo-    , entryStatus :: FileStatus+    { entryPath   :: FilePath+    , entryDepth  :: Int+    , entryStatus :: Maybe FileStatus+      -- ^ This is Nothing until we determine stat should be called.     }  instance Show FileEntry where-    show entry = "FileEntry " ++ show (entryInfo entry)--class HasFileInfo a where-    getFileInfo :: a -> FileInfo--instance HasFileInfo FileInfo where-    getFileInfo = id-    {-# INLINE getFileInfo #-}--instance HasFileInfo FileEntry where-    getFileInfo = entryInfo-    {-# INLINE getFileInfo #-}+    show entry = "FileEntry "+              ++ show (entryPath entry)+              ++ " " ++ show (entryDepth entry) -entryPath :: HasFileInfo a => a -> FilePath-entryPath = infoPath . getFileInfo+newFileEntry :: FilePath -> Int -> FileEntry+newFileEntry p d = FileEntry p d Nothing  -- | Return all entries, except for those within version-control metadata --   directories (and not including the version control directory itself either).-ignoreVcs :: (MonadIO m, HasFileInfo e) => Predicate m e-ignoreVcs = Looped $ \entry ->-    return $ if filename (entryPath entry) `elem` vcsDirs-             then Ignore-             else KeepAndRecurse entry ignoreVcs+ignoreVcs :: Monad m => Predicate m FileEntry+ignoreVcs = prune (filename_ (`elem` vcsDirs))   where     vcsDirs = [ ".git", "CVS", "RCS", "SCCS", ".svn", ".hg", "_darcs" ] -regex :: (Monad m, HasFileInfo e) => Text -> Predicate m e+regex :: Monad m => Text -> Predicate m FileEntry regex pat = filename_ (=~ pat)  -- | This is a re-export of 'Text.Regex.Posix.=~', with the types changed for --   ease of use with this module.  For example, you can simply say: -- -- @---    filename_ (=~ "\\.hs$")+--    filename_ (=~ \"\\\\.hs$\") -- @ -- -- Which is the same thing as: -- -- @---    regex "\\.hs$"+--    regex \"\\\\.hs$\" -- @ (=~) :: FilePath -> Text -> Bool str =~ pat = encodeString str R.=~ unpack pat  -- | Find every entry whose filename part matching the given filename globbing --   expression.  For example: @glob "*.hs"@.-glob :: (Monad m, HasFileInfo e) => Text -> Predicate m e+glob :: Monad m => Text -> Predicate m FileEntry glob g = case parseOnly globParser g of     Left e  -> error $ "Failed to parse glob: " ++ e     Right x -> regex ("^" <> x <> "$")@@ -278,53 +231,62 @@                             then ['\\', x]                             else [x] -doStat :: MonadIO m-       => (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+doStat :: MonadIO m => (String -> IO FileStatus) -> Predicate m FileEntry+doStat getstatus = do+    entry <- get+    s <- liftIO $ getstatus (encodeString (entryPath entry))+    put $ entry { entryStatus = Just s } -lstat :: MonadIO m => Looped m FileInfo FileEntry+lstat :: MonadIO m => Predicate m FileEntry lstat = doStat getSymbolicLinkStatus -stat :: MonadIO m => Looped m FileInfo FileEntry+stat :: MonadIO m => Predicate m FileEntry stat = doStat getFileStatus -withStatus :: Monad m => (FileStatus -> m Bool) -> Predicate m FileEntry-withStatus f = ifM_ (f . entryStatus)+getStatus :: FileEntry -> FileStatus+getStatus e = fromMaybe+    (error $ "FileStatus has not been determined for: " ++ show (entryPath e))+    (entryStatus e) +withFileStatus :: Monad m+               => (FileStatus -> m Bool)+               -> Predicate m FileEntry+withFileStatus f = ifM_ (f . getStatus)+ status :: Monad m => (FileStatus -> Bool) -> Predicate m FileEntry-status f = withStatus (return . f)+status f = withFileStatus (return . f)  regular :: Monad m => Predicate m FileEntry regular = status isRegularFile +directory :: Monad m => Predicate m FileEntry+directory = status isDirectory+ hasMode :: Monad m => FileMode -> Predicate m FileEntry hasMode m = status (\s -> fileMode s .&. m /= 0)  executable :: Monad m => Predicate m FileEntry executable = hasMode ownerExecuteMode -withPath :: HasFileInfo a => Monad m => (FilePath -> m Bool) -> Predicate m a+withPath :: Monad m+         => (FilePath -> m Bool)+         -> Predicate m FileEntry withPath f = ifM_ (f . entryPath) -filename_ :: (Monad m, HasFileInfo e) => (FilePath -> Bool) -> Predicate m e+filename_ :: Monad m => (FilePath -> Bool) -> Predicate m FileEntry filename_ f = withPath (return . f . filename) -filenameS_ :: (Monad m, HasFileInfo e) => (String -> Bool) -> Predicate m e+filenameS_ :: Monad m => (String -> Bool) -> Predicate m FileEntry filenameS_ f = withPath (return . f . encodeString . filename) -filepath_ :: (Monad m, HasFileInfo e) => (FilePath -> Bool) -> Predicate m e+filepath_ :: Monad m => (FilePath -> Bool) -> Predicate m FileEntry filepath_ f = withPath (return . f) -filepathS_ :: (Monad m, HasFileInfo e) => (String -> Bool) -> Predicate m e+filepathS_ :: Monad m => (String -> Bool) -> Predicate m FileEntry filepathS_ f = withPath (return . f . encodeString) -depth :: (Monad m, HasFileInfo e) => (Int -> Bool) -> Predicate m e-depth f = if_ (f . infoDepth . getFileInfo)+depth :: Monad m => (Int -> Bool) -> Predicate m FileEntry+depth f = if_ (f . entryDepth)  withStatusTime :: Monad m                => (UTCTime -> Bool) -> (FileStatus -> POSIXTime)@@ -337,102 +299,77 @@ lastModified :: Monad m => (UTCTime -> Bool) -> Predicate m FileEntry lastModified = flip withStatusTime modificationTimeHiRes --- Walk through the entries of a directory tree, allowing the user to specify--- a 'Predicate' which may decides not only which entries to yield from the--- conduit, but also which directories to follow, and how to recurse into that--- directory by permitting the use of a subsequent 'Predicate'.+-- | A raw find does no processing on the FileEntry, leaving it up to the user+--   to determine when and if stat should be called.  Note that unless you+--   take care to indicate when recursion should happen, an error will result+--   when the raw finder attempts to recurse on a non-directory.  The bare+--   minimum for a proper finder should look like this for non-recursion: ----- Note that the 'followSymlinks' parameter to this function has a different--- meaning than it does for 'sourceDirectoryDeep': if @True@, symlinks are--- never passed to the predicate, only what they point to; if @False@,--- symlinks are never read at all.  For 'sourceDirectoryDeep', if--- 'followSymlinks' is @False@ it only prevents directory symlinks from being--- read.-sourceFileEntries :: MonadResource m-                  => FileInfo-                  -> Looped m FileInfo FileEntry-                  -> Producer m FileEntry-sourceFileEntries (FileInfo p d) m = sourceDirectory p =$= awaitForever f+-- @+-- findRaw \<path\> $ do+--     \<apply predicates needing only pathname or depth\>+--     localM stat $ do+--         directory ||: norecurse+--         \<apply predicates needing stat info\>+-- @+--+-- To apply predicates only to a single directory, without recursing, simply+-- start (or end) the predicate with 'norecurse', and use @localM stat@ or+-- @localM lstat@ at the point where you need 'FileStatus' information.+findRaw :: (MonadIO m, MonadResource m)+        => FilePath -> Bool -> Predicate m FileEntry -> Source m FileEntry+findRaw path follow predicate =+    traverseRecursively+        (newFileEntry path 0)+        predicate+        (const . yield)+        lift+        readDirectory   where-    f fp = applyLooped m (FileInfo fp d) yield $-        sourceFileEntries (FileInfo fp (succ d))+    readDirectory (FileEntry p d mst) go = do+        -- If no status has been determined yet, we must now in order to know+        -- whether to traverse or not.+        recurse <- isDirectory <$> case mst of+            Nothing -> liftIO $ (if follow+                                 then getFileStatus+                                 else getSymbolicLinkStatus)+                              $ encodeString p+            Just st -> return st+        when recurse $+            (sourceDirectory p =$) $ awaitForever $ \fp ->+                mapInput (const ()) (const Nothing) $+                    go $ newFileEntry fp (succ d) +basicFind :: (MonadIO m, MonadResource m)+          => Predicate m FileEntry+          -> Bool+          -> FilePath+          -> Predicate m FileEntry+          -> Source m FileEntry+basicFind f follow path pr = findRaw path follow $+    f >> (directory ||: norecurse) >> pr+ find' :: (MonadIO m, MonadResource m)-      => FilePath -> Predicate m FileEntry -> Producer m FileEntry-find' path pr = sourceFileEntries (FileInfo path 1) (stat >>> pr)+      => FilePath -> Predicate m FileEntry+      -> Source m FileEntry+find' = basicFind stat True  find :: (MonadIO m, MonadResource m)-     => FilePath -> Predicate m FileEntry -> Producer m FilePath+     => FilePath -> Predicate m FileEntry+     -> Source 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 (FileInfo path 1) (lstat >>> pr)+       => FilePath -> Predicate m FileEntry+       -> Source m FileEntry+lfind' = basicFind lstat False  lfind :: (MonadIO m, MonadResource m)-      => FilePath -> Predicate m FileEntry -> Producer m FilePath+      => FilePath -> Predicate m FileEntry+      -> Source 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 =$= awaitForever f-  where-    f fp = do-        r <- lift $ runLooped pr fp-        case r of-            Ignore -> return ()-            Keep a -> yield a-            RecurseOnly _ -> return ()-            KeepAndRecurse a _ -> yield a--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 info-        let candidate = case r of-                Ignore -> IgnoreFile-                Keep _ -> ConsiderFile-                RecurseOnly _ -> MaybeRecurse-                KeepAndRecurse _ _ -> ConsiderFile-        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 ->-                    applyLooped m (FileEntry (FileInfo fp d) st) yield next--findWithPreFilter :: (MonadIO m, MonadResource m)-                  => FilePath-                  -> Bool-                  -> Predicate m FileInfo-                  -> Predicate m FileEntry-                  -> Producer m FileEntry-findWithPreFilter path = doFindPreFilter (FileInfo path 1)- -- | Test a file path using the same type of 'Predicate' that is accepted by --   'find'. test :: MonadIO m => Predicate m FileEntry -> FilePath -> m Bool-test matcher path =-    getAny `liftM` testSingle (stat >>> matcher) (FileInfo path 0) alwaysTrue-  where-    alwaysTrue = const (return (Any True))+test matcher path = Cond.test (stat >> matcher) (newFileEntry path 0)
− Data/Conduit/Find/Looped.hs
@@ -1,283 +0,0 @@-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-}---- | Main entry point to the application.-module Data.Conduit.Find.Looped where--import Control.Applicative-import Control.Arrow-import Control.Category-import Control.Monad-import Control.Monad.Trans-import Data.Monoid hiding ((<>))-import Data.Profunctor-import Data.Semigroup-import Prelude hiding ((.), id)--data Result m a b-    = Ignore-    | Keep b-    | RecurseOnly (Looped m a b)-    | KeepAndRecurse b (Looped m a b)--instance Show a => Show (Result r m a) where-    show Ignore = "Ignore"-    show (Keep a) = "Keep " ++ show a-    show (RecurseOnly _) = "RecurseOnly"-    show (KeepAndRecurse a _) = "KeepAndRecurse " ++ show a--instance Functor m => Functor (Result m a) where-    fmap _ Ignore = Ignore-    fmap f (Keep a) = Keep (f a)-    fmap f (RecurseOnly l) = RecurseOnly (fmap f l)-    fmap f (KeepAndRecurse a l)  = KeepAndRecurse (f a) (fmap f l)--instance Functor m => Profunctor (Result m) where-    lmap _ Ignore = Ignore-    lmap _ (Keep a) = Keep a-    lmap f (RecurseOnly l) = RecurseOnly (lmap f l)-    lmap f (KeepAndRecurse a l)  = KeepAndRecurse a (lmap f l)-    rmap = fmap--instance (Functor m, Monad m) => Applicative (Result m a) where-    pure = return-    (<*>) = ap--instance Monad m => Monad (Result m a) where-    return = Keep-    Ignore >>= _ = Ignore-    Keep a >>= f = case f a of-        Ignore -> Ignore-        Keep b -> Keep b-        (RecurseOnly _) -> Ignore-        (KeepAndRecurse b _) -> Keep b-    RecurseOnly (Looped l) >>= f =-        RecurseOnly (Looped $ \r -> liftM (>>= f) (l r))-    KeepAndRecurse a _ >>= f = f a--instance Semigroup (Result m a a) where-    Ignore <> _ = Ignore-    _ <> Ignore = Ignore-    RecurseOnly m <> _ = RecurseOnly m-    _ <> RecurseOnly m = RecurseOnly m-    _ <> Keep b = Keep b-    Keep _ <> KeepAndRecurse b _ = Keep b-    KeepAndRecurse _ _ <> KeepAndRecurse b m = KeepAndRecurse b m--instance Monoid (Result m a a) where-    mempty = Ignore-    x `mappend` y = x <> y--instance Monad m => MonadPlus (Result m a) where-    mzero = Ignore-    Ignore `mplus` _ = Ignore-    _ `mplus` Ignore = Ignore-    RecurseOnly m `mplus` _ = RecurseOnly m-    _ `mplus` RecurseOnly m = RecurseOnly m-    _ `mplus` Keep b = Keep b-    Keep _ `mplus` KeepAndRecurse b _ = Keep b-    KeepAndRecurse _ _ `mplus` KeepAndRecurse b m = KeepAndRecurse b m--newtype Looped m a b = Looped { runLooped :: a -> m (Result m a b) }--instance Functor m => Functor (Looped m a) where-    fmap f (Looped g) = Looped (fmap (fmap (fmap f)) g)--instance Functor m => Profunctor (Looped m) where-    lmap f (Looped k) = Looped (fmap (fmap (lmap f)) (k . f))-    rmap = fmap--instance (Functor m, Monad m) => Applicative (Looped m a) where-    pure = return-    (<*>) = ap--instance Monad m => Monad (Looped m a) where-    return = Looped . const . return . return-    Looped f >>= k = Looped $ \a -> do-        r <- f a-        case r of-            Ignore -> return Ignore-            Keep b -> runLooped (k b) a-            RecurseOnly l -> runLooped (l >>= k) a-            KeepAndRecurse b _ -> runLooped (k b) a--instance Monad m => Category (Looped m) where-    id = matchAll-    Looped f . Looped g = Looped $ \a -> do-          r <- g a-          case r of-            Ignore -> return Ignore-            Keep b -> do-                r' <- f b-                return $ case r' of-                    Ignore -> Ignore-                    Keep c -> Keep c-                    RecurseOnly _ -> Ignore-                    KeepAndRecurse c _ -> Keep c-            RecurseOnly (Looped l) ->-                return $ RecurseOnly (Looped f . Looped l)-            KeepAndRecurse b (Looped l) -> do-                r' <- f b-                return $ case r' of-                    Ignore -> Ignore-                    Keep c -> Keep c-                    RecurseOnly (Looped m) ->-                        RecurseOnly (Looped m . Looped l)-                    KeepAndRecurse c (Looped m) ->-                        KeepAndRecurse c (Looped m . Looped l)--instance Monad m => Arrow (Looped m) where-    arr f = Looped $ return . Keep . f-    first (Looped f) = Looped $ \(a, c) -> do-        r <- f a-        return $ case r of-            Ignore -> Ignore-            Keep b -> Keep (b, c)-            RecurseOnly l -> RecurseOnly (first l)-            KeepAndRecurse b l -> KeepAndRecurse (b, c) (first l)---- | Within a predicate block, 'consider' a different item than what is---   currently being predicated upon.  This makes it possible to write custom---   logic within the Monad instance for a predicate, such as in this---   contrived example:------ @---   flip runLooped \"bar.hs\" $ do---       x <- filename_ (== \"foo.hs\")---       when (x /= \"\") $---           consider \"baz.hs\" $---               filename_ (== \"baz.hs\")--- @-consider :: a -> Looped m a b -> Looped m a b-consider x l = Looped $ const $ runLooped l x--applyLooped :: (MonadTrans t, (Monad (t m)), Monad m, Show b)-            => Looped m a b -> a -> (b -> t m ())-            -> (Looped m a b -> t m ()) -> t m ()-applyLooped l x f g = do-    r <- lift $ runLooped l x-    case r of-        Ignore -> return ()-        Keep a -> f a-        RecurseOnly m -> g m-        KeepAndRecurse a m -> f a >> g m--testSingle :: (Monad m, Monoid c) => Looped m a b -> a -> (b -> m c) -> m c-testSingle l x f = do-    r <- runLooped l x-    case r of-        Ignore -> return mempty-        Keep a -> f a-        RecurseOnly _ -> return mempty-        KeepAndRecurse a _ -> f a--if_ :: Monad m => (a -> Bool) -> Looped m a a-if_ f = Looped $ \a ->-    return $ if f a-             then KeepAndRecurse a (if_ f)-             else RecurseOnly (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 RecurseOnly (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-    case r of-        Keep b -> return $ Keep b-        KeepAndRecurse b l -> return $ KeepAndRecurse b l-        _ -> g a--and_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b-and_ (Looped f) (Looped g) = Looped $ \a -> do-    r <- f a-    case r of-        Ignore -> return Ignore-        Keep _ -> g a-        RecurseOnly l -> return $ RecurseOnly l-        KeepAndRecurse _ _ -> g a--liftArrow :: Monad m => (a -> b) -> Looped m a b-liftArrow f = Looped $ \a -> return $ KeepAndRecurse (f a) (liftArrow f)--liftKleisli :: Monad m => (a -> m b) -> Looped m a b-liftKleisli f = Looped $ \a -> do-    r <- f a-    return $ KeepAndRecurse r (liftKleisli f)--liftKleisliMaybe :: Monad m => (a -> m (Maybe b)) -> Looped m a b-liftKleisliMaybe f = Looped $ \a -> do-    r <- f a-    return $ case r of-        Nothing -> RecurseOnly (liftKleisliMaybe f)-        Just b -> KeepAndRecurse b (liftKleisliMaybe f)--lowerKleisliMaybe :: Monad m => Looped m a b -> a -> m (Maybe b)-lowerKleisliMaybe (Looped f) a = do-    r <- f a-    return $ case r of-        Ignore -> Nothing-        Keep b -> Just b-        RecurseOnly _ -> Nothing-        KeepAndRecurse b _ -> Just b--type Predicate m a = Looped m a a--instance (Functor m, Monad m) => Semigroup (Predicate m a) where-    Looped f <> Looped g = Looped $ \a -> do-        r <- f a-        case r of-            Ignore -> g a-            Keep b -> return $ Keep b-            RecurseOnly _ -> g a-            KeepAndRecurse b m -> return $ KeepAndRecurse b m--instance (Functor m, Monad m) => Monoid (Predicate m a) where-    mempty = let x = Looped (\a -> return $ KeepAndRecurse a x) in x-    f `mappend` g = f <> g--instance Monad m => MonadPlus (Looped m a) where-    mzero = ignoreAll-    Looped f `mplus` Looped g = Looped $ \a -> do-        r <- f a-        case r of-            Ignore -> g a-            Keep b -> return $ Keep b-            RecurseOnly _ -> g a-            KeepAndRecurse b m -> return $ KeepAndRecurse b m---- | 'matchAll' is id in the 'Predicate' Category.-matchAll :: Monad m => Predicate m a-matchAll = Looped $ \entry -> return $ KeepAndRecurse entry matchAll---- | 'ignore' rejects the current entry, but allows recursion.-ignore :: Monad m => Looped m a b-ignore = Looped $ const $ return $ RecurseOnly ignore---- | 'ignoreAll' rejects every entry, and does not recurse.  This is the same---   as 'mzero'.-ignoreAll :: Monad m => Looped m a b-ignoreAll = Looped $ const $ return Ignore---- | 'not_' reverse the meaning of the given predicate, preserving recursion.-not_ :: MonadIO m => Predicate m a -> Predicate m a-not_ (Looped f) = Looped (\a -> go a `liftM` f a)-  where-    go a Ignore = Keep a-    go _ (Keep _) = Ignore-    go a (RecurseOnly l) = KeepAndRecurse a (not_ l)-    go _ (KeepAndRecurse _ l) = RecurseOnly (not_ l)---- | 'prune' is much like 'not_', but does not preserve recursion.-prune :: MonadIO m => Predicate m a -> Predicate m a-prune (Looped f) = Looped (\a -> go a `liftM` f a)-  where-    go a Ignore = Keep a-    go _ (Keep _) = Ignore-    go a (RecurseOnly l) = KeepAndRecurse a (prune l)-    go _ (KeepAndRecurse _ _) = Ignore
find-conduit.cabal view
@@ -1,5 +1,5 @@ Name:                find-conduit-Version:             0.2.3+Version:             0.3.0 Synopsis:            A file-finding conduit that allows user control over traversals. License-file:        LICENSE License:             MIT@@ -28,14 +28,17 @@       , unix                 >= 2.5.1.1       , text                 >= 0.11.3.1       , regex-posix-      , profunctors       , mtl       , semigroups+      , exceptions       , time+      , transformers+      , transformers-base+      , monad-control     exposed-modules:         Data.Conduit.Find     other-modules:-        Data.Conduit.Find.Looped+        Data.Cond  test-suite test     hs-source-dirs: test@@ -56,4 +59,8 @@       , mtl       , time       , semigroups+      , exceptions+      , transformers+      , transformers-base+      , monad-control       , hspec                >= 1.4
test/main.hs view
@@ -3,7 +3,6 @@ module Main where  import Conduit-import Control.Arrow import Data.Conduit.Find import Test.Hspec @@ -11,7 +10,7 @@ main = hspec $ do     describe "basic tests" $ do         it "file passes a test" $ do-            res <- test (regular >>> not_ executable) "README.md"+            res <- test (regular >> not_ executable) "README.md"             res `shouldBe` True          it "file fails a test" $ do@@ -19,18 +18,18 @@             res `shouldBe` False          it "file fails another test" $ do-            res <- test (regular >>> executable) "README.md"+            res <- test (regular >> executable) "README.md"             res `shouldBe` False          it "finds files" $ do             xs <- runResourceT $                 find "."                     (    ignoreVcs-                     >>> prune (filename_ (== "dist"))-                     >>> glob "*.hs"-                     >>> not_ (glob "Setup*")-                     >>> regular-                     >>> not_ executable+                     >> prune (filename_ (== "dist"))+                     >> glob "*.hs"+                     >> not_ (glob "Setup*")+                     >> regular+                     >> not_ executable                     )                     $$ sinkList @@ -43,13 +42,13 @@             xs <- runResourceT $                 find "."                     (    ignoreVcs-                     >>> glob "*.hs"-                     >>> not_ (glob "Setup*")+                     >> glob "*.hs"+                     >> not_ (glob "Setup*")                      -- 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+                     >> prune (filename_ (== "dist"))+                     >> regular+                     >> not_ executable                     )                     $$ sinkList @@ -60,15 +59,14 @@          it "finds files using a pre-pass filter" $ do             xs <- runResourceT $-                findWithPreFilter "." True-                    (    ignoreVcs-                     >>> prune (filename_ (== "dist"))-                     >>> glob "*.hs"-                     >>> not_ (glob "Setup*")-                    )-                    (    regular-                     >>> not_ executable-                    )+                findRaw "." True+                    (do ignoreVcs+                        prune (filename_ (== "dist"))+                        glob "*.hs"+                        not_ (glob "Setup*")+                        stat+                        regular+                        not_ executable)                     =$ mapC entryPath $$ sinkList              "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True@@ -78,16 +76,15 @@          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-                    )+                findRaw "." True+                    (do ignoreVcs+                        prune (depth (>=1))+                        prune (filename_ (== "dist"))+                        glob "*.hs"+                        not_ (glob "Setup*")+                        stat+                        regular+                        not_ executable)                     =$ mapC entryPath $$ sinkList              "./Data/Conduit/Find.hs" `elem` xs `shouldBe` False