packages feed

conduit-find 0.1.0.3 → 0.1.0.4

raw patch · 8 files changed

+1265/−1208 lines, 8 filesdep +resourcetdep +transformers-eitherdep +unliftio-coredep −directorydep −doctestdep ~basedep ~conduitdep ~filepathPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: resourcet, transformers-either, unliftio-core

Dependencies removed: directory, doctest

Dependency ranges changed: base, conduit, filepath, semigroups, text, unix, unix-compat

API changes (from Hackage documentation)

- Data.Cond: instance (GHC.Base.Monad m, Data.Semigroup.Semigroup b) => Data.Semigroup.Semigroup (Data.Cond.CondT a m b)
- Data.Cond: instance Control.Monad.Catch.MonadMask m => Control.Monad.Catch.MonadMask (Data.Cond.CondT a m)
- Data.Cond: instance Data.Semigroup.Semigroup (Data.Cond.Result a m b)
+ Data.Cond: instance (GHC.Base.Monad m, GHC.Base.Semigroup b) => GHC.Base.Semigroup (Data.Cond.CondT a m b)
+ Data.Cond: instance Control.Monad.Catch.MonadMask m => Control.Monad.Catch.MonadMask (Data.Cond.CondT aa m)
+ Data.Cond: instance Control.Monad.Fail.MonadFail m => Control.Monad.Fail.MonadFail (Data.Cond.CondT a m)
+ Data.Cond: instance GHC.Base.Semigroup (Data.Cond.Result a m b)
+ Data.Conduit.Find: CondEitherT :: StateT a (EitherT (Maybe (Maybe (CondEitherT a m b))) m) (b, Maybe (Maybe (CondEitherT a m b))) -> CondEitherT a (m :: Type -> Type) b
+ Data.Conduit.Find: CondT :: StateT a m (Result a m b) -> CondT a (m :: Type -> Type) b
+ Data.Conduit.Find: [getCondT] :: CondT a (m :: Type -> Type) b -> StateT a m (Result a m b)
+ Data.Conduit.Find: and_ :: forall (m :: Type -> Type) a b. Monad m => [CondT a m b] -> CondT a m ()
+ Data.Conduit.Find: apply :: Monad m => (a -> m (Maybe b)) -> CondT a m b
+ Data.Conduit.Find: applyCond :: a -> Cond a b -> ((Maybe b, Maybe (Cond a b)), a)
+ Data.Conduit.Find: applyCondT :: Monad m => a -> CondT a m b -> m ((Maybe b, Maybe (CondT a m b)), a)
+ Data.Conduit.Find: consider :: Monad m => (a -> m (Maybe (b, a))) -> CondT a m b
+ Data.Conduit.Find: fromCondT :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondEitherT a m b
+ Data.Conduit.Find: guardM :: Monad m => m Bool -> CondT a m ()
+ Data.Conduit.Find: guardM_ :: Monad m => (a -> m Bool) -> CondT a m ()
+ Data.Conduit.Find: guard_ :: forall (m :: Type -> Type) a. Monad m => (a -> Bool) -> CondT a m ()
+ Data.Conduit.Find: if_ :: forall (m :: Type -> Type) a r b. Monad m => CondT a m r -> CondT a m b -> CondT a m b -> CondT a m b
+ Data.Conduit.Find: ignore :: forall (m :: Type -> Type) a b. Monad m => CondT a m b
+ Data.Conduit.Find: matches :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m Bool
+ Data.Conduit.Find: newtype CondEitherT a (m :: Type -> Type) b
+ Data.Conduit.Find: newtype CondT a (m :: Type -> Type) b
+ Data.Conduit.Find: norecurse :: forall (m :: Type -> Type) a. Monad m => CondT a m ()
+ Data.Conduit.Find: not_ :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m ()
+ Data.Conduit.Find: or_ :: forall (m :: Type -> Type) a b. Monad m => [CondT a m b] -> CondT a m b
+ Data.Conduit.Find: prune :: forall (m :: Type -> Type) a b. Monad m => CondT a m b
+ Data.Conduit.Find: recurse :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m b
+ Data.Conduit.Find: runCond :: Cond a b -> a -> Maybe b
+ Data.Conduit.Find: runCondT :: Monad m => CondT a m b -> a -> m (Maybe b)
+ Data.Conduit.Find: toCondT :: forall (m :: Type -> Type) a b. Monad m => CondEitherT a m b -> CondT a m b
+ Data.Conduit.Find: type Cond a = CondT a Identity
+ Data.Conduit.Find: unless_ :: forall (m :: Type -> Type) a r. Monad m => CondT a m r -> CondT a m () -> CondT a m ()
+ Data.Conduit.Find: when_ :: forall (m :: Type -> Type) a r. Monad m => CondT a m r -> CondT a m () -> CondT a m ()
- Data.Cond: CondEitherT :: (StateT a (EitherT (Maybe (Maybe (CondEitherT a m b))) m) (b, Maybe (Maybe (CondEitherT a m b)))) -> CondEitherT a m b
+ Data.Cond: CondEitherT :: StateT a (EitherT (Maybe (Maybe (CondEitherT a m b))) m) (b, Maybe (Maybe (CondEitherT a m b))) -> CondEitherT a (m :: Type -> Type) b
- Data.Cond: CondT :: StateT a m (Result a m b) -> CondT a m b
+ Data.Cond: CondT :: StateT a m (Result a m b) -> CondT a (m :: Type -> Type) b
- Data.Cond: [getCondT] :: CondT a m b -> StateT a m (Result a m b)
+ Data.Cond: [getCondT] :: CondT a (m :: Type -> Type) b -> StateT a m (Result a m b)
- Data.Cond: and_ :: Monad m => [CondT a m b] -> CondT a m ()
+ Data.Cond: and_ :: forall (m :: Type -> Type) a b. Monad m => [CondT a m b] -> CondT a m ()
- Data.Cond: fromCondT :: Monad m => CondT a m b -> CondEitherT a m b
+ Data.Cond: fromCondT :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondEitherT a m b
- Data.Cond: guard_ :: Monad m => (a -> Bool) -> CondT a m ()
+ Data.Cond: guard_ :: forall (m :: Type -> Type) a. Monad m => (a -> Bool) -> CondT a m ()
- Data.Cond: if_ :: Monad m => CondT a m r -> CondT a m b -> CondT a m b -> CondT a m b
+ Data.Cond: if_ :: forall (m :: Type -> Type) a r b. Monad m => CondT a m r -> CondT a m b -> CondT a m b -> CondT a m b
- Data.Cond: ignore :: Monad m => CondT a m b
+ Data.Cond: ignore :: forall (m :: Type -> Type) a b. Monad m => CondT a m b
- Data.Cond: matches :: Monad m => CondT a m b -> CondT a m Bool
+ Data.Cond: matches :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m Bool
- Data.Cond: newtype CondEitherT a m b
+ Data.Cond: newtype CondEitherT a (m :: Type -> Type) b
- Data.Cond: newtype CondT a m b
+ Data.Cond: newtype CondT a (m :: Type -> Type) b
- Data.Cond: norecurse :: Monad m => CondT a m ()
+ Data.Cond: norecurse :: forall (m :: Type -> Type) a. Monad m => CondT a m ()
- Data.Cond: not_ :: Monad m => CondT a m b -> CondT a m ()
+ Data.Cond: not_ :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m ()
- Data.Cond: or_ :: Monad m => [CondT a m b] -> CondT a m b
+ Data.Cond: or_ :: forall (m :: Type -> Type) a b. Monad m => [CondT a m b] -> CondT a m b
- Data.Cond: prune :: Monad m => CondT a m b
+ Data.Cond: prune :: forall (m :: Type -> Type) a b. Monad m => CondT a m b
- Data.Cond: recurse :: Monad m => CondT a m b -> CondT a m b
+ Data.Cond: recurse :: forall (m :: Type -> Type) a b. Monad m => CondT a m b -> CondT a m b
- Data.Cond: toCondT :: Monad m => CondEitherT a m b -> CondT a m b
+ Data.Cond: toCondT :: forall (m :: Type -> Type) a b. Monad m => CondEitherT a m b -> CondT a m b
- Data.Cond: unless_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()
+ Data.Cond: unless_ :: forall (m :: Type -> Type) a r. Monad m => CondT a m r -> CondT a m () -> CondT a m ()
- Data.Cond: when_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()
+ Data.Cond: when_ :: forall (m :: Type -> Type) a r. Monad m => CondT a m r -> CondT a m () -> CondT a m ()
- Data.Conduit.Find: FileEntry :: !FilePath -> !Int -> !FindOptions -> !(Maybe FileStatus) -> FileEntry
+ Data.Conduit.Find: FileEntry :: !FilePath -> !Int -> !FindOptions -> !Maybe FileStatus -> FileEntry
- Data.Conduit.Find: [entryStatus] :: FileEntry -> !(Maybe FileStatus)
+ Data.Conduit.Find: [entryStatus] :: FileEntry -> !Maybe FileStatus
- Data.Conduit.Find: amin_ :: MonadIO m => Int -> CondT FileEntry m ()
+ Data.Conduit.Find: amin_ :: forall (m :: Type -> Type). MonadIO m => Int -> CondT FileEntry m ()
- Data.Conduit.Find: anewer_ :: MonadIO m => FilePath -> CondT FileEntry m ()
+ Data.Conduit.Find: anewer_ :: forall (m :: Type -> Type). MonadIO m => FilePath -> CondT FileEntry m ()
- Data.Conduit.Find: atime_ :: MonadIO m => Int -> CondT FileEntry m ()
+ Data.Conduit.Find: atime_ :: forall (m :: Type -> Type). MonadIO m => Int -> CondT FileEntry m ()
- Data.Conduit.Find: depth_ :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: depth_ :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: directory :: MonadIO m => CondT FileEntry m ()
+ Data.Conduit.Find: directory :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m ()
- Data.Conduit.Find: empty_ :: MonadIO m => CondT FileEntry m ()
+ Data.Conduit.Find: empty_ :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m ()
- Data.Conduit.Find: executable :: MonadIO m => CondT FileEntry m ()
+ Data.Conduit.Find: executable :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m ()
- Data.Conduit.Find: executable_ :: MonadIO m => CondT FileEntry m ()
+ Data.Conduit.Find: executable_ :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m ()
- Data.Conduit.Find: filename_ :: Monad m => (FilePath -> Bool) -> CondT FileEntry m ()
+ Data.Conduit.Find: filename_ :: forall (m :: Type -> Type). Monad m => (FilePath -> Bool) -> CondT FileEntry m ()
- Data.Conduit.Find: find :: MonadResource m => FilePath -> CondT FileEntry m a -> Producer m FilePath
+ Data.Conduit.Find: find :: forall (m :: Type -> Type) a i. MonadResource m => FilePath -> CondT FileEntry m a -> ConduitT i FilePath m ()
- Data.Conduit.Find: findFilePaths :: (MonadIO m, MonadResource m) => FindOptions -> FilePath -> CondT FileEntry m a -> Producer m FilePath
+ Data.Conduit.Find: findFilePaths :: forall (m :: Type -> Type) a i. (MonadIO m, MonadResource m) => FindOptions -> FilePath -> CondT FileEntry m a -> ConduitT i FilePath m ()
- Data.Conduit.Find: findFiles :: (MonadIO m, MonadBaseControl IO m, MonadThrow m) => FindOptions -> FilePath -> CondT FileEntry m a -> m ()
+ Data.Conduit.Find: findFiles :: (MonadIO m, MonadBaseControl IO m, MonadThrow m, MonadUnliftIO m) => FindOptions -> FilePath -> CondT FileEntry m a -> m ()
- Data.Conduit.Find: follow_ :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: follow_ :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: getDepth :: Monad m => CondT FileEntry m Int
+ Data.Conduit.Find: getDepth :: forall (m :: Type -> Type). Monad m => CondT FileEntry m Int
- Data.Conduit.Find: getFilePath :: Monad m => CondT FileEntry m FilePath
+ Data.Conduit.Find: getFilePath :: forall (m :: Type -> Type). Monad m => CondT FileEntry m FilePath
- Data.Conduit.Find: gid_ :: MonadIO m => Int -> CondT FileEntry m ()
+ Data.Conduit.Find: gid_ :: forall (m :: Type -> Type). MonadIO m => Int -> CondT FileEntry m ()
- Data.Conduit.Find: glob :: Monad m => String -> CondT FileEntry m ()
+ Data.Conduit.Find: glob :: forall (m :: Type -> Type). Monad m => String -> CondT FileEntry m ()
- Data.Conduit.Find: hasMode :: MonadIO m => FileMode -> CondT FileEntry m ()
+ Data.Conduit.Find: hasMode :: forall (m :: Type -> Type). MonadIO m => FileMode -> CondT FileEntry m ()
- Data.Conduit.Find: hasStatus :: MonadIO m => (FileStatus -> Bool) -> CondT FileEntry m ()
+ Data.Conduit.Find: hasStatus :: forall (m :: Type -> Type). MonadIO m => (FileStatus -> Bool) -> CondT FileEntry m ()
- Data.Conduit.Find: ignoreErrors_ :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: ignoreErrors_ :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: ignoreVcs :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: ignoreVcs :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: lastAccessed_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()
+ Data.Conduit.Find: lastAccessed_ :: forall (m :: Type -> Type). MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()
- Data.Conduit.Find: lastModified_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()
+ Data.Conduit.Find: lastModified_ :: forall (m :: Type -> Type). MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()
- Data.Conduit.Find: lstat :: MonadIO m => CondT FileEntry m FileStatus
+ Data.Conduit.Find: lstat :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m FileStatus
- Data.Conduit.Find: maxdepth_ :: Monad m => Int -> CondT FileEntry m ()
+ Data.Conduit.Find: maxdepth_ :: forall (m :: Type -> Type). Monad m => Int -> CondT FileEntry m ()
- Data.Conduit.Find: mindepth_ :: Monad m => Int -> CondT FileEntry m ()
+ Data.Conduit.Find: mindepth_ :: forall (m :: Type -> Type). Monad m => Int -> CondT FileEntry m ()
- Data.Conduit.Find: name_ :: Monad m => FilePath -> CondT FileEntry m ()
+ Data.Conduit.Find: name_ :: forall (m :: Type -> Type). Monad m => FilePath -> CondT FileEntry m ()
- Data.Conduit.Find: noIgnoreErrors_ :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: noIgnoreErrors_ :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: noleaf_ :: Monad m => CondT FileEntry m ()
+ Data.Conduit.Find: noleaf_ :: forall (m :: Type -> Type). Monad m => CondT FileEntry m ()
- Data.Conduit.Find: pathname_ :: Monad m => (FilePath -> Bool) -> CondT FileEntry m ()
+ Data.Conduit.Find: pathname_ :: forall (m :: Type -> Type). Monad m => (FilePath -> Bool) -> CondT FileEntry m ()
- Data.Conduit.Find: prune_ :: Monad m => CondT a m ()
+ Data.Conduit.Find: prune_ :: forall (m :: Type -> Type) a. Monad m => CondT a m ()
- Data.Conduit.Find: regex :: Monad m => String -> CondT FileEntry m ()
+ Data.Conduit.Find: regex :: forall (m :: Type -> Type). Monad m => String -> CondT FileEntry m ()
- Data.Conduit.Find: regular :: MonadIO m => CondT FileEntry m ()
+ Data.Conduit.Find: regular :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m ()
- Data.Conduit.Find: sourceFindFiles :: MonadResource m => FindOptions -> FilePath -> CondT FileEntry m a -> Producer m (FileEntry, a)
+ Data.Conduit.Find: sourceFindFiles :: forall (m :: Type -> Type) a i. MonadResource m => FindOptions -> FilePath -> CondT FileEntry m a -> ConduitT i (FileEntry, a) m ()
- Data.Conduit.Find: stat :: MonadIO m => CondT FileEntry m FileStatus
+ Data.Conduit.Find: stat :: forall (m :: Type -> Type). MonadIO m => CondT FileEntry m FileStatus

Files

− Data/Cond.hs
@@ -1,515 +0,0 @@-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE CPP #-}--module Data.Cond-    ( CondT(..), Cond--    -- * Executing CondT-    , runCondT, runCond, applyCondT, applyCond--    -- * Promotions-    , guardM, guard_, guardM_, apply, consider--    -- * Boolean logic-    , matches, if_, when_, unless_, or_, and_, not_--    -- * Basic conditionals-    , ignore, norecurse, prune--    -- * Helper functions-    , recurse, test--    -- * Isomorphism with a stateful EitherT-    , CondEitherT(..), fromCondT, toCondT-    ) where--import Control.Applicative-import Control.Arrow (first)-import Control.Monad hiding (mapM_, sequence_)-import Control.Monad.Base-import Control.Monad.Catch-import Control.Monad.Morph-import Control.Monad.Reader.Class-import Control.Monad.State.Class-import Control.Monad.Trans-import Control.Monad.Trans.Control-import Control.Monad.Trans.Either-import Control.Monad.Trans.State (StateT(..), withStateT, evalStateT)-import Data.Foldable-import Data.Functor.Identity-import Data.Maybe (fromMaybe, isJust)-import Data.Monoid hiding ((<>))-import Data.Semigroup-import Prelude hiding (mapM_, foldr1, sequence_)---- | 'Result' is an enriched 'Maybe' type which also specifies whether---   recursion should occur from the given input, and if so, how such---   recursion should be performed.-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)-    {-# INLINE fmap #-}--instance MFunctor (Result a) where-    hoist _ Ignore                 = Ignore-    hoist _ (Keep a)               = Keep a-    hoist nat (RecurseOnly l)      = RecurseOnly (fmap (hoist nat) l)-    hoist nat (KeepAndRecurse a l) = KeepAndRecurse a (fmap (hoist nat) l)-    {-# INLINE hoist #-}--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-    {-# INLINE (<>) #-}--instance Monoid b => Monoid (Result a m b) where-    mempty  = KeepAndRecurse mempty Nothing-    {-# INLINE mempty #-}-    mappend = (<>)-    {-# INLINE mappend #-}--getResult :: Result a m b -> (Maybe b, Maybe (CondT a m b))-getResult Ignore               = (Nothing, Nothing)-getResult (Keep b)             = (Just b, Nothing)-getResult (RecurseOnly c)      = (Nothing, c)-getResult (KeepAndRecurse b c) = (Just b, c)--setRecursion :: CondT a m b -> Result a m b -> Result a m b-setRecursion _ Ignore               = Ignore-setRecursion _ (Keep b)             = Keep b-setRecursion c (RecurseOnly _)      = RecurseOnly (Just c)-setRecursion c (KeepAndRecurse b _) = KeepAndRecurse b (Just c)--accept' :: b -> Result a m b-accept' = flip KeepAndRecurse Nothing--recurse' :: Result a m b-recurse' = RecurseOnly Nothing---- | Convert from a 'Maybe' value to its corresponding 'Result'.------ >>> maybeToResult Nothing :: Result () Identity ()--- RecurseOnly--- >>> maybeToResult (Just ()) :: Result () Identity ()--- KeepAndRecurse ()-maybeToResult :: Maybe a -> Result r m a-maybeToResult Nothing  = recurse'-maybeToResult (Just a) = accept' a--maybeFromResult :: Result r m a -> Maybe a-maybeFromResult Ignore               = Nothing-maybeFromResult (Keep a)             = Just a-maybeFromResult (RecurseOnly _)      = Nothing-maybeFromResult (KeepAndRecurse a _) = Just a---- | 'CondT' is a kind of @StateT a (MaybeT m) b@, which uses a special---   'Result' type instead of 'Maybe' to express whether recursion should be---   performed from the item under consideration.  This is used to build---   predicates that can guide recursive traversals.------ Several different types may be promoted to 'CondT':------   [@Bool@]                  Using 'guard'------   [@m Bool@]                Using 'guardM'------   [@a -> Bool@]              Using 'guard_'------   [@a -> m Bool@]            Using 'guardM_'------   [@a -> m (Maybe b)@]       Using 'apply'------   [@a -> m (Maybe (b, a))@]  Using 'consider'------ Here is a trivial example:------ @--- flip runCondT 42 $ do---   guard_ even---   liftIO $ putStrLn "42 must be even to reach here"---   guard_ odd \<|\> guard_ even---   guard_ (== 42)--- @------ If 'CondT' is executed using 'runCondT', it return a @Maybe b@ if the--- predicate matched.  It can also be run with 'applyCondT', which does case--- analysis on the 'Result', specifying how recursion should be performed from--- the given 'a' value.-newtype CondT a m b = CondT { getCondT :: StateT a m (Result a m b) }--type Cond a = CondT a Identity--instance Show (CondT a m b) where-    show _ = "CondT"--instance (Monad m, Semigroup b) => Semigroup (CondT a m b) where-    (<>) = liftM2 (<>)-    {-# INLINE (<>) #-}--instance (Monad m, Monoid b) => Monoid (CondT a m b) where-    mempty  = CondT $ return $ accept' mempty-    {-# INLINE mempty #-}-    mappend = liftM2 mappend-    {-# INLINE mappend #-}--instance Monad m => Functor (CondT a m) where-#if __GLASGOW_HASKELL__ < 710-    -- GHC 8.0.1 seems to go into some sort of optimiser loop with -O1 and above-    -- if this is `liftM`, but for GHC 7.8 `Applicative` is not a super class-    -- of `Monad` so we still need this.-    -- See: https://ghc.haskell.org/trac/ghc/ticket/12425-    fmap f (CondT g) = CondT (liftM (fmap f) g)-#else-    fmap f (CondT g) = CondT (liftA (fmap f) g)-#endif-    {-# INLINE fmap #-}--instance Monad m => Applicative (CondT a m) where-    pure  = return-    {-# INLINE pure #-}-    (<*>) = ap-    {-# INLINE (<*>) #-}--instance Monad m => Monad (CondT a m) where-    return = CondT . return . accept'-    {-# INLINE return #-}-    fail _ = mzero-    {-# INLINE fail #-}-    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 $ gets accept'-    {-# INLINE ask #-}-    local f (CondT m) = CondT $ withStateT f m-    {-# INLINE local #-}-    reader f          = liftM f ask-    {-# INLINE reader #-}--instance Monad m => MonadState a (CondT a m) where-    get     = CondT $ gets accept'-    {-# INLINE get #-}-    put s   = CondT $ liftM accept' $ put s-    {-# INLINE put #-}-    state f = CondT $ state (fmap (first accept') f)-    {-# INLINE state #-}--instance Monad m => Alternative (CondT a m) where-    empty = CondT $ return recurse'-    {-# INLINE empty #-}-    CondT f <|> CondT g = CondT $ do-        r <- f-        case r of-            x@(Keep _) -> return x-            x@(KeepAndRecurse _ _) -> return x-            _ -> g-    {-# INLINE (<|>) #-}--instance Monad m => MonadPlus (CondT a m) where-    mzero = empty-    {-# INLINE mzero #-}-    mplus = (<|>)-    {-# INLINE mplus #-}--instance MonadThrow m => MonadThrow (CondT a m) where-    throwM = CondT . throwM-    {-# INLINE throwM #-}--instance MonadCatch m => MonadCatch (CondT a m) where-    catch (CondT m) c = CondT $ m `catch` \e -> getCondT (c e)--instance MonadMask m => MonadMask (CondT a m) where-    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 $ liftM accept' $ liftBase m-    {-# INLINE liftBase #-}--instance MonadIO m => MonadIO (CondT a m) where-    liftIO m = CondT $ liftM accept' $ liftIO m-    {-# INLINE liftIO #-}--instance MonadTrans (CondT a) where-    lift m = CondT $ liftM accept' $ lift m-    {-# INLINE lift #-}--instance MonadBaseControl b m => MonadBaseControl b (CondT r m) where-    type StM (CondT r m) a = StM m (Result r m a, r)-    liftBaseWith f = CondT $ StateT $ \s ->-        liftM (\x -> (accept' x, s)) $ liftBaseWith $ \runInBase -> f $ \k ->-            runInBase $ runStateT (getCondT k) s-    restoreM = CondT . StateT . const . restoreM-    {-# INLINE liftBaseWith #-}-    {-# INLINE restoreM #-}--instance MFunctor (CondT a) where-    hoist nat (CondT m) = CondT $ hoist nat (liftM (hoist nat) m)-    {-# INLINE hoist #-}--runCondT :: Monad m => CondT a m b -> a -> m (Maybe b)-runCondT (CondT f) a = maybeFromResult `liftM` evalStateT f a-{-# INLINE runCondT #-}--runCond :: Cond a b -> a -> Maybe b-runCond = (runIdentity .) . runCondT-{-# INLINE runCond #-}---- | Case analysis of applying a condition to an input value.  The result is a---   pair whose first part is a pair of Maybes specifying if the input matched---   and if recursion is expected from this value, and whose second part is---   the (possibly) mutated input value.-applyCondT :: Monad m-           => a-           -> CondT a m b-           -> m ((Maybe b, Maybe (CondT a m b)), a)-applyCondT a cond = do-    -- Apply a condition to the input value, determining the result and the-    -- mutated value.-    (r, a') <- runStateT (getCondT cond) a--    -- Convert from the 'Result' to a pair of Maybes: one to specify if the-    -- predicate succeeded, and the other to specify if recursion should be-    -- performed.  If there is no sub-recursion specified, return 'cond'.-    return (fmap (Just . fromMaybe cond) (getResult r), a')-{-# INLINE applyCondT #-}---- | Case analysis of applying a pure condition to an input value.  The result---   is a pair whose first part is a pair of Maybes specifying if the input---   matched and if recursion is expected from this value, and whose second---   part is the (possibly) mutated input value.-applyCond :: a -> Cond a b -> ((Maybe b, Maybe (Cond a b)), a)-applyCond a cond = first (fmap (Just . fromMaybe cond) . getResult)-                       (runIdentity (runStateT (getCondT cond) a))-{-# INLINE applyCond #-}--guardM :: Monad m => m Bool -> CondT a m ()-guardM m = lift m >>= guard-{-# INLINE guardM #-}--guard_ :: Monad m => (a -> Bool) -> CondT a m ()-guard_ f = asks f >>= guard-{-# INLINE guard_ #-}--guardM_ :: Monad m => (a -> m Bool) -> CondT a m ()-guardM_ f = ask >>= lift . f >>= guard-{-# INLINE guardM_ #-}--apply :: Monad m => (a -> m (Maybe b)) -> CondT a m b-apply f = CondT $ get >>= liftM maybeToResult . lift . f-{-# INLINE apply #-}--consider :: Monad m => (a -> m (Maybe (b, a))) -> CondT a m b-consider f = CondT $ do-    mres <- lift . f =<< get-    case mres of-        Nothing      -> return Ignore-        Just (b, a') -> put a' >> return (accept' b)-{-# INLINE consider #-}---- | Return True or False depending on whether the given condition matches or---   not.  This differs from simply stating the condition in that it itself---   always succeeds.------ >>> flip runCond "foo.hs" $ matches (guard =<< asks (== "foo.hs"))--- Just True--- >>> flip runCond "foo.hs" $ matches (guard =<< asks (== "foo.hi"))--- Just False-matches :: Monad m => CondT a m b -> CondT a m Bool-matches = fmap isJust . optional-{-# INLINE matches #-}---- | A variant of ifM which branches on whether the condition succeeds or not.---   Note that @if_ x@ is equivalent to @ifM (matches x)@, and is provided---   solely for convenience.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ if_ good (return "Success") (return "Failure")--- Just "Success"--- >>> flip runCond "foo.hs" $ if_ bad (return "Success") (return "Failure")--- Just "Failure"-if_ :: Monad m => CondT a m r -> CondT a m b -> CondT a m b -> CondT a m b-if_ c x y =-    CondT $ getCondT . maybe y (const x) . maybeFromResult =<< getCondT c-{-# INLINE if_ #-}---- | 'when_' is just like 'when', except that it executes the body if the---   condition passes, rather than based on a Bool value.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ when_ good ignore--- Nothing--- >>> flip runCond "foo.hs" $ when_ bad ignore--- Just ()-when_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()-when_ c x = if_ c (void x) (return ())-{-# INLINE when_ #-}---- | 'when_' is just like 'when', except that it executes the body if the---   condition fails, rather than based on a Bool value.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ unless_ bad ignore--- Nothing--- >>> flip runCond "foo.hs" $ unless_ good ignore--- Just ()-unless_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()-unless_ c = if_ c (return ()) . void-{-# INLINE unless_ #-}---- | Check whether at least one of the given conditions is true.  This is a---   synonym for 'Data.Foldable.asum'.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ or_ [bad, good]--- Just ()--- >>> flip runCond "foo.hs" $ or_ [bad]--- Nothing-or_ :: Monad m => [CondT a m b] -> CondT a m b-or_ = asum-{-# INLINE or_ #-}---- | Check that all of the given conditions are true.  This is a synonym for---   'Data.Foldable.sequence_'.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ and_ [bad, good]--- Nothing--- >>> flip runCond "foo.hs" $ and_ [good]--- Just ()-and_ :: Monad m => [CondT a m b] -> CondT a m ()-and_ = sequence_-{-# INLINE and_ #-}---- | 'not_' inverts the meaning of the given predicate.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> flip runCond "foo.hs" $ not_ bad >> return "Success"--- Just "Success"--- >>> flip runCond "foo.hs" $ not_ good >> return "Shouldn't reach here"--- Nothing-not_ :: Monad m => CondT a m b -> CondT a m ()-not_ c = when_ c ignore-{-# INLINE not_ #-}---- | 'ignore' ignores the current entry, but allows recursion into its---   descendents.  This is the same as 'mzero'.-ignore :: Monad m => CondT a m b-ignore = mzero-{-# INLINE ignore #-}---- | 'norecurse' prevents recursion into the current entry's descendents, but---   does not ignore the entry itself.-norecurse :: Monad m => CondT a m ()-norecurse = CondT $ return $ Keep ()-{-# INLINE norecurse #-}---- | 'prune' is a synonym for both ignoring an entry and its descendents. It---   is the same as @ignore >> norecurse@.-prune :: Monad m => CondT a m b-prune = CondT $ return Ignore-{-# INLINE prune #-}---- | 'recurse' changes the recursion predicate for any child elements.  For---   example, the following file-finding predicate looks for all @*.hs@ files,---   but under any @.git@ directory looks only for a file named @config@:------ @--- if_ (name_ \".git\" \>\> directory)---     (ignore \>\> recurse (name_ \"config\"))---     (glob \"*.hs\")--- @------ NOTE: If this code had used @recurse (glob \"*.hs\"))@ instead in the else--- case, it would have meant that @.git@ is only looked for at the top-level--- of the search (i.e., the top-most element).-recurse :: Monad m => CondT a m b -> CondT a m b-recurse c = CondT $ setRecursion c `liftM` getCondT c-{-# INLINE recurse #-}---- | A specialized variant of 'runCondT' that simply returns True or False.------ >>> let good = guard_ (== "foo.hs") :: Cond String ()--- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()--- >>> runIdentity $ test "foo.hs" $ not_ bad >> return "Success"--- True--- >>> runIdentity $ test "foo.hs" $ not_ good >> return "Shouldn't reach here"--- False-test :: Monad m => a -> CondT a m b -> m Bool-test = (liftM isJust .) . flip runCondT-{-# INLINE test #-}---- | This type is for documentation only, and shows the isomorphism between---   'CondT' and 'CondEitherT'.  The reason for using 'Result' is that it---   makes meaning of the constructors more explicit.-newtype CondEitherT a m b = CondEitherT-    (StateT a (EitherT (Maybe (Maybe (CondEitherT a m b))) m)-         (b, Maybe (Maybe (CondEitherT a m b))))---- | Witness one half of the isomorphism from 'CondT' to 'CondEitherT'.-fromCondT :: Monad m => CondT a m b -> CondEitherT a m b-fromCondT (CondT f) = CondEitherT $ do-    s <- get-    (r, s') <- lift $ lift $ runStateT f s-    case r of-        Ignore             -> lift $ left Nothing-        Keep a             -> put s' >> return (a, Nothing)-        RecurseOnly m      -> lift $ left (Just (fmap fromCondT m))-        KeepAndRecurse a m -> put s' >> return (a, Just (fmap fromCondT m))---- | Witness the other half of the isomorphism from 'CondEitherT' to 'CondT'.-toCondT :: Monad m => CondEitherT a m b -> CondT a m b-toCondT (CondEitherT f) = CondT $ do-    s <- get-    eres <- lift $ runEitherT $ runStateT f s-    case eres of-        Left Nothing             -> return Ignore-        Right ((a, Nothing), s') -> put s' >> return (Keep a)-        Left (Just m)            -> return $ RecurseOnly (fmap toCondT m)-        Right ((a, Just m), s')  ->-            put s' >> return (KeepAndRecurse a (fmap toCondT m))
− Data/Conduit/Find.hs
@@ -1,610 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Data.Conduit.Find-    (-    -- * Introduction-    -- $intro--    -- ** Basic comparison with GNU find-    -- $gnufind--    -- ** Performance-    -- $performance--    -- ** Other notes-    -- $notes--    -- * Finding functions-      sourceFindFiles-    , find-    , findFiles-    , findFilePaths-    , FindOptions(..)-    , defaultFindOptions-    , test-    , ltest-    , stat-    , lstat-    , hasStatus--      -- * File path predicates-    , glob-    , regex-    , ignoreVcs--      -- * GNU find compatibility predicates-    , depth_-    , follow_-    , noleaf_-    , prune_-    , maxdepth_-    , mindepth_-    , ignoreErrors_-    , noIgnoreErrors_-    , amin_-    , atime_-    , anewer_-    , empty_-    , executable_-    , gid_-    , name_-    , getDepth-    , filename_-    , pathname_-    , getFilePath--    -- * File entry predicates (uses stat information)-    , regular-    , directory-    , hasMode-    , executable-    , lastAccessed_-    , lastModified_--    -- * Predicate combinators-    , module Cond-    , (=~)--    -- * Types and type classes-    , FileEntry(..)-    ) where--import           Conduit-import           Control.Applicative-import           Control.Exception-import           Control.Monad hiding (forM_, forM)-import           Control.Monad.Morph-import           Control.Monad.State.Class-import           Data.Attoparsec.Text as A-import           Data.Bits-import qualified Data.Cond as Cond-import           Data.Cond hiding (test)-import qualified Data.Conduit.Filesystem as CF-#if LEAFOPT-import           Data.IORef-#endif-import           Data.Maybe (fromMaybe,fromJust)-import           Data.Monoid-import           Data.Text (Text, unpack, pack)-import           Data.Time-import           Data.Time.Clock.POSIX-import qualified System.FilePath as FP-import           System.PosixCompat.Files-import           System.PosixCompat.Types-import           Text.Regex.Posix ((=~))--{- $intro--**find-conduit** is essentially a souped version of GNU find for Haskell,-using a DSL to provide both ease of us, and extensive flexbility.--In its simplest form, let's compare some uses of find to find-conduit.  Bear-in mind that the result of the find function is a conduit, so you're expected-to either sink it to a list, or operate on the file paths as they are yielded.--}--{- $gnufind--A typical find command:--@-find src -name '*.hs' -type f -print-@--Would in find-conduit be:--@-find "src" (glob \"*.hs\" \<\> regular) $$ mapM_C (liftIO . print)-@--The 'glob' predicate matches the file basename against the globbing pattern,-while the 'regular' predicate matches plain files.--A more complicated example:--@-find . -size +100M -perm 644 -mtime 1-@--Now in find-conduit:--@-let megs = 1024 * 1024-    days = 86400-now <- liftIO getCurrentTime-find \".\" ( fileSize (> 100*megs)-        \<\> hasMode 0o644-        \<\> lastModified (> addUTCTime now (-(1*days)))-         )-@--Appending predicates like this expressing an "and" relationship.  Use '<|>' to-express "or".  You can also negate any predicate:--@-find \".\" (not_ (hasMode 0o644))-@--By default, predicates, whether matching or not, will allow recursion into-directories.  In order to express that matching predicate should disallow-recursion, use 'prune':--@-find \".\" (prune (depth (> 2)))-@--This is the same as using '-maxdepth 2' in find.--@-find \".\" (prune (filename_ (== \"dist\")))-@--This is the same as:--@-find . \\( -name dist -prune \\) -o -print-@--}--{- $performance--find-conduit strives to make file-finding a well performing operation.  To-this end, a composed Predicate will only call stat once per entry being-considered; and if you prune a directory, it is not traversed at all.--By default, 'find' calls stat for every file before it applies the predicate,-in order to ensure that only one such call is needed.  Sometimes, however, you-know just from the FilePath that you don't want to consider a certain file, or-you want to prune a directory tree.--To support these types of optimized queries, a variant of find is provided-called 'findWithPreFilter'.  This takes two predicates: one that is applied to-only the FilePath, before stat (or lstat) is called; and one that is applied-to the full file information after the stat.--}--{- $notes--See 'Data.Cond' for more details on the Monad used to build predicates.--}--data FindOptions = FindOptions-    { findFollowSymlinks   :: !Bool-    , findContentsFirst    :: !Bool-    , findIgnoreErrors     :: !Bool-    , findIgnoreResults    :: !Bool-    , findLeafOptimization :: !Bool-    }--defaultFindOptions :: FindOptions-defaultFindOptions = FindOptions-    { findFollowSymlinks   = True-    , findContentsFirst    = False-    , findIgnoreErrors     = False-    , findIgnoreResults    = False-    , findLeafOptimization = True-    }--data FileEntry = FileEntry-    { entryPath        :: !FP.FilePath-    , entryDepth       :: !Int-    , entryFindOptions :: !FindOptions-    , entryStatus      :: !(Maybe FileStatus)-      -- ^ This is Nothing until we determine stat should be called.-    }--newFileEntry :: FP.FilePath -> Int -> FindOptions -> FileEntry-newFileEntry fp d f = FileEntry fp d f Nothing--instance Show FileEntry where-    show entry = "FileEntry "-              ++ show (entryPath entry)-              ++ " " ++ show (entryDepth entry)--getFilePath :: Monad m => CondT FileEntry m FP.FilePath-getFilePath = gets entryPath--pathname_ :: Monad m => (FP.FilePath -> Bool) -> CondT FileEntry m ()-pathname_ f = guard . f =<< getFilePath--filename_ :: Monad m => (FP.FilePath -> Bool) -> CondT FileEntry m ()-filename_ f = pathname_ (f . FP.takeFileName)--getDepth :: Monad m => CondT FileEntry m Int-getDepth = gets entryDepth--modifyFindOptions :: Monad m-                  => (FindOptions -> FindOptions)-                  -> CondT FileEntry m ()-modifyFindOptions f =-    modify $ \e -> e { entryFindOptions = f (entryFindOptions e) }----------------------------------------------------------------------------- Workalike options for emulating GNU find.---------------------------------------------------------------------------depth_ :: Monad m => CondT FileEntry m ()-depth_ = modifyFindOptions $ \opts -> opts { findContentsFirst = True }--follow_ :: Monad m => CondT FileEntry m ()-follow_ = modifyFindOptions $ \opts -> opts { findFollowSymlinks = True }--noleaf_ :: Monad m => CondT FileEntry m ()-noleaf_ = modifyFindOptions $ \opts -> opts { findLeafOptimization = False }--prune_ :: Monad m => CondT a m ()-prune_ = prune--ignoreErrors_ :: Monad m => CondT FileEntry m ()-ignoreErrors_ =-    modifyFindOptions $ \opts -> opts { findIgnoreErrors = True }--noIgnoreErrors_ :: Monad m => CondT FileEntry m ()-noIgnoreErrors_ =-    modifyFindOptions $ \opts -> opts { findIgnoreErrors = False }--maxdepth_ :: Monad m => Int -> CondT FileEntry m ()-maxdepth_ l = getDepth >>= guard . (<= l)--mindepth_ :: Monad m => Int -> CondT FileEntry m ()-mindepth_ l = getDepth >>= guard . (>= l)---- xdev_ = error "NYI"--timeComp :: MonadIO m-         => ((UTCTime -> Bool) -> CondT FileEntry m ()) -> Int-         -> CondT FileEntry m ()-timeComp f n = do-    now <- liftIO getCurrentTime-    f (\t -> diffUTCTime now t > fromIntegral n)--amin_ :: MonadIO m => Int -> CondT FileEntry m ()-amin_ n = timeComp lastAccessed_ (n * 60)--atime_ :: MonadIO m => Int -> CondT FileEntry m ()-atime_ n = timeComp lastAccessed_ (n * 24 * 3600)--anewer_ :: MonadIO m => FP.FilePath -> CondT FileEntry m ()-anewer_ path = do-    e  <- get-    es <- applyStat Nothing-    ms <- getStat Nothing e { entryPath   = path-                            , entryStatus = Nothing-                            }-    case ms of-        Nothing     -> prune >> error "This is never reached"-        Just (s, _) -> guard $ diffUTCTime (f s) (f es) > 0-  where-    f = posixSecondsToUTCTime . realToFrac . accessTime---- cmin_ = error "NYI"--- cnewer_ = error "NYI"--- ctime_ = error "NYI"--empty_ :: MonadIO m => CondT FileEntry m ()-empty_ = (regular   >> hasStatus ((== 0) . fileSize))-     <|> (directory >> hasStatus ((== 2) . linkCount))--executable_ :: MonadIO m => CondT FileEntry m ()-executable_ = executable--gid_ :: MonadIO m => Int -> CondT FileEntry m ()-gid_ n = hasStatus ((== n) . fromIntegral . fileGroup)--{--group_ name-ilname_ pat-iname_ pat-inum_ n-ipath_ pat-iregex_ pat-iwholename_ pat-links_ n-lname_ pat-mmin_-mtime_--}--name_ :: Monad m => FP.FilePath -> CondT FileEntry m ()-name_ = filename_ . (==)--{--newer_ path-newerXY_ ref-nogroup_-nouser_-path_ pat-perm_ mode :: Perm-readable_-regex_ pat-samefile_ path-size_ n :: Size-type_ c-uid_ n-used_ n-user_ name-wholename_ pat-writable_-xtype_ c--}----------------------------------------------------------------------------statFilePath :: Bool -> Bool -> FP.FilePath -> IO (Maybe FileStatus)-statFilePath follow ignoreErrors path = do-    let doStat = (if follow-                  then getFileStatus-                  else getSymbolicLinkStatus) path-    catch (Just <$> doStat) $ \e ->-        if ignoreErrors-        then return Nothing-        else throwIO (e :: IOException)---- | Get the current status for the file.  If the status being requested is---   already cached in the entry information, simply return it from there.-getStat :: MonadIO m-        => Maybe Bool-        -> FileEntry-        -> m (Maybe (FileStatus, FileEntry))-getStat mfollow entry = case entryStatus entry of-    Just s-        | maybe True (== follow entry) mfollow ->-            return $ Just (s, entry)-        | otherwise -> fmap (, entry) `liftM` wrapStat-    Nothing -> do-        ms <- wrapStat-        return $ case ms of-            Just s  -> Just (s, entry { entryStatus = Just s })-            Nothing -> Nothing-  where-    follow   = findFollowSymlinks . entryFindOptions-    wrapStat = liftIO $ statFilePath-        (fromMaybe (findFollowSymlinks opts) mfollow)-        (findIgnoreErrors opts)-        (entryPath entry)-      where-        opts = entryFindOptions entry--applyStat :: MonadIO m => Maybe Bool -> CondT FileEntry m FileStatus-applyStat mfollow = do-    ms <- lift . getStat mfollow =<< get-    case ms of-        Nothing      -> prune >> error "This is never reached"-        Just (s, e') -> s <$ put e'--lstat :: MonadIO m => CondT FileEntry m FileStatus-lstat = applyStat (Just False)--stat :: MonadIO m => CondT FileEntry m FileStatus-stat = applyStat (Just True)--hasStatus :: MonadIO m => (FileStatus -> Bool) -> CondT FileEntry m ()-hasStatus f = guard . f =<< applyStat Nothing--regular :: MonadIO m => CondT FileEntry m ()-regular = hasStatus isRegularFile--executable :: MonadIO m => CondT FileEntry m ()-executable = hasMode ownerExecuteMode--directory :: MonadIO m => CondT FileEntry m ()-directory = hasStatus isDirectory--hasMode :: MonadIO m => FileMode -> CondT FileEntry m ()-hasMode m = hasStatus (\s -> fileMode s .&. m /= 0)--withStatusTime :: MonadIO m-               => (FileStatus -> EpochTime) -> (UTCTime -> Bool)-               -> CondT FileEntry m ()-withStatusTime g f = hasStatus (f . posixSecondsToUTCTime . realToFrac . g)--lastAccessed_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()-lastAccessed_ = withStatusTime accessTime--lastModified_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()-lastModified_ = withStatusTime modificationTime--regex :: Monad m => String -> CondT FileEntry m ()-regex pat = filename_ (=~ pat)---- | Return all entries, except for those within version-control metadata---   directories (and not including the version control directory itself either).-ignoreVcs :: Monad m => CondT FileEntry m ()-ignoreVcs = when_ (filename_ (`elem` vcsDirs)) prune-  where-    vcsDirs = [ ".git", "CVS", "RCS", "SCCS", ".svn", ".hg", "_darcs" ]---- | Find every entry whose filename part matching the given filename globbing---   expression.  For example: @glob "*.hs"@.-glob :: Monad m => String -> CondT FileEntry m ()-glob g = case parseOnly globParser (pack g) of-    Left e  -> error $ "Failed to parse glob: " ++ e-    Right x -> regex ("^" <> unpack x <> "$")-  where-    globParser :: Parser Text-    globParser = fmap mconcat $ many $-            char '*' *> return ".*"-        <|> char '?' *> return "."-        <|> string "[]]" *> return "[]]"-        <|> (\x y z -> pack ((x:y) ++ [z]))-                <$> char '['-                <*> manyTill anyChar (A.try (char ']'))-                <*> char ']'-        <|> do-            x <- anyChar-            return . pack $ if x `elem` (".()^$" :: String)-                            then ['\\', x]-                            else [x]--#if LEAFOPT-type DirCounter = IORef LinkCount--newDirCounter :: MonadIO m => m DirCounter-newDirCounter = liftIO $ newIORef 1-#else-type DirCounter = ()--newDirCounter :: MonadIO m => m DirCounter-newDirCounter = return ()-#endif---- | Find file entries in a directory tree, recursively, applying the given---   recursion predicate to the search.  This conduit yields pairs of type---   @(FileEntry, a)@, where is the return value from the predicate at each---   step.-sourceFindFiles :: MonadResource m-                => FindOptions-                -> FilePath-                -> CondT FileEntry m a-                -> Producer m (FileEntry, a)-sourceFindFiles findOptions startPath predicate = do-    startDc <- newDirCounter-    walk startDc-        (newFileEntry startPath 0 findOptions)-        startPath-        predicate-  where-    walk :: MonadResource m-         => DirCounter-         -> FileEntry-         -> FP.FilePath-         -> CondT FileEntry m a-         -> Producer m (FileEntry, a)-    walk !dc !entry !path !cond = do-        ((!mres, !mcond), !entry') <- lift $ applyCondT entry cond-        let opts' = entryFindOptions entry-            this  = unless (findIgnoreResults opts') $-                        yieldEntry entry' mres-            next  = walkChildren dc entry' path mcond-        if findContentsFirst opts'-            then next >> this-            else this >> next-      where-        yieldEntry _      Nothing    = return ()-        yieldEntry entry' (Just res) = yield (entry', res)--    walkChildren :: MonadResource m-                 => DirCounter-                 -> FileEntry-                 -> FP.FilePath-                 -> Maybe (CondT FileEntry m a)-                 -> Producer m (FileEntry, a)-    walkChildren _ _ _ Nothing = return ()-    -- If the conditional matched, we are requested to recurse if this is a-    -- directory-    walkChildren !dc !entry !path (Just !cond) = do-        st <- lift $ checkIfDirectory dc entry path-        when (fmap isDirectory st == Just True) $ do-#if LEAFOPT-            -- Update directory count for the parent directory.-            liftIO $ modifyIORef dc pred-            -- Track the directory count for this child path.-            let leafOpt = findLeafOptimization (entryFindOptions entry)-            let lc = linkCount (fromJust st) - 2-                opts' = (entryFindOptions entry)-                    { findLeafOptimization = leafOpt && lc >= 0-                    }-            dc' <- liftIO $ newIORef lc-#else-            let dc'   = dc-                opts' = entryFindOptions entry-#endif-            CF.sourceDirectory path =$= awaitForever (go dc' opts')-      where-        go dc' opts' fp =-            let entry' = newFileEntry fp (succ (entryDepth entry)) opts'-            in walk dc' entry' fp cond--    -- Return True if the given entry is a directory.  We can sometimes use-    -- "leaf optimization" on Linux to answer this question without performing-    -- a stat call.  This is possible because the link count of a directory is-    -- two more than the number of sub-directories it contains, so we've seen-    -- that many sub-directories, the remaining entries must be files.-    checkIfDirectory :: MonadResource m-                     => DirCounter-                     -> FileEntry-                     -> FP.FilePath-                     -> m (Maybe FileStatus)-    checkIfDirectory !dc !entry !path = do-#if LEAFOPT-        let leafOpt = findLeafOptimization (entryFindOptions entry)-        doStat <- if leafOpt-                  then (> 0) <$> liftIO (readIORef dc)-                  else return True-#else-        let doStat = dc == () -- to quiet hlint warnings-#endif-        let opts = entryFindOptions entry-        if doStat-            then liftIO $ statFilePath-                (findFollowSymlinks opts)-                (findIgnoreErrors opts)-                path-            else return Nothing--findFiles :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)-          => FindOptions-          -> FilePath-          -> CondT FileEntry m a-          -> m ()-findFiles opts path predicate =-    runResourceT $-        sourceFindFiles opts { findIgnoreResults = True } path-            (hoist lift predicate) $$ sinkNull---- | A simpler version of 'findFiles', which yields only 'FilePath' values,---   and ignores any values returned by the predicate action.-findFilePaths :: (MonadIO m, MonadResource m)-              => FindOptions-              -> FilePath-              -> CondT FileEntry m a-              -> Producer m FilePath-findFilePaths opts path predicate =-    sourceFindFiles opts path predicate =$= mapC (entryPath . fst)---- | Calls 'findFilePaths' with the default set of finding options.---   Equivalent to @findFilePaths defaultFindOptions@.-find :: MonadResource m-     => FilePath -> CondT FileEntry m a -> Producer m FilePath-find = findFilePaths defaultFindOptions---- | Test a file path using the same type of predicate that is accepted by---   'findFiles'.-test :: MonadIO m => CondT FileEntry m () -> FilePath -> m Bool-test matcher path =-    Cond.test (newFileEntry path 0 defaultFindOptions) matcher---- | Test a file path using the same type of predicate that is accepted by---   'findFiles', but do not follow symlinks.-ltest :: MonadIO m => CondT FileEntry m () -> FilePath -> m Bool-ltest matcher path =-    Cond.test-        (newFileEntry path 0 defaultFindOptions-            { findFollowSymlinks = False })-        matcher
conduit-find.cabal view
@@ -1,5 +1,5 @@ Name:                conduit-find-Version:             0.1.0.3+Version:             0.1.0.4 Synopsis:            A file-finding conduit that allows user control over traversals. License-file:        LICENSE License:             MIT@@ -8,6 +8,16 @@ Build-Type:          Simple Cabal-Version:       >= 1.10 Category:            System+Stability:           Stable+tested-with:+  GHC ==8.10.7+   || ==9.0.2+   || ==9.2.8+   || ==9.4.8+   || ==9.6.7+   || ==9.8.4+   || ==9.10.2+   || ==9.12.1  Homepage:            https://github.com/erikd/conduit-find Bug-Reports:         https://github.com/erikd/conduit-find/issues@@ -27,28 +37,32 @@ Library     default-language:   Haskell2010     ghc-options: -Wall -O2 -funbox-strict-fields+    hs-source-dirs: src     if os(linux) && flag(leafopt)         cpp-options: -DLEAFOPT=1     build-depends:         base                 >= 3 && < 5-      , conduit+      , conduit              >= 1.2       , conduit-extra       , conduit-combinators       , attoparsec       , unix-compat          >= 0.4.1.1-      , text                 >= 0.11.3.1+      , text                 >= 2.0 && < 2.2       , regex-posix       , mtl       , semigroups       , exceptions           >= 0.6       , time+      , resourcet            >= 1.1 && < 1.4       , streaming-commons       , transformers       , transformers-base+      , transformers-either  >= 0.1 && < 0.2       , mmorph       , either       , monad-control        >= 1.0       , filepath+      , unliftio-core     exposed-modules:         Data.Cond, Data.Conduit.Find @@ -64,8 +78,8 @@       , conduit       , conduit-combinators       , attoparsec-      , unix-compat          >= 0.4.1.1-      , text                 >= 0.11.3.1+      , unix-compat+      , text                 >= 2.0 && < 2.2       , regex-posix       , mtl       , time@@ -79,18 +93,20 @@       , mmorph       , filepath       , hspec                >= 1.4+      , resourcet+      , unliftio-core -Test-suite doctests-    Default-language: Haskell2010-    Type:    exitcode-stdio-1.0-    Main-is: doctest.hs-    Hs-source-dirs: test-    Build-depends:-        base-      , directory    >= 1.0-      , doctest      >= 0.8-      , filepath     >= 1.3-      , semigroups   >= 0.4+-- Test-suite doctests+--     Default-language: Haskell2010+--     Type:    exitcode-stdio-1.0+--     Main-is: doctest.hs+--     Hs-source-dirs: test+--     Build-depends:+--         base+--       , directory    >= 1.0+--       , doctest      >= 0.8+--       , filepath     >= 1.3+--       , semigroups   >= 0.4  Executable find-hs     Main-is:     find-hs.hs@@ -104,8 +120,8 @@       , conduit-extra       , conduit-combinators       , attoparsec-      , unix                 >= 0.4.1.1-      , text                 >= 0.11.3.1+      , unix+      , text                 >= 2.0 && < 2.2       , regex-posix       , mtl       , time@@ -118,3 +134,5 @@       , monad-control       , mmorph       , filepath+      , resourcet+      , unliftio-core
+ src/Data/Cond.hs view
@@ -0,0 +1,560 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Data.Cond+    ( CondT(..), Cond++    -- * Executing CondT+    , runCondT, runCond, applyCondT, applyCond++    -- * Promotions+    , guardM, guard_, guardM_, apply, consider++    -- * Boolean logic+    , matches, if_, when_, unless_, or_, and_, not_++    -- * Basic conditionals+    , ignore, norecurse, prune++    -- * Helper functions+    , recurse, test++    -- * Isomorphism with a stateful EitherT+    , CondEitherT(..), fromCondT, toCondT+    ) where++import Control.Applicative (Alternative (..), liftA2, optional)+import Control.Arrow (first)+import GHC.Stack (HasCallStack)+import Control.Monad hiding (mapM_, sequence_)+import Control.Monad.Base (MonadBase (..))+import Control.Monad.Catch (MonadCatch (..), MonadMask (..), MonadThrow (..), ExitCase (..))+import Control.Monad.Morph (MFunctor, hoist)+import Control.Monad.Reader.Class (MonadReader (..), asks)+import Control.Monad.State.Class (MonadState (..), gets)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans (MonadTrans (..))+import Control.Monad.Trans.Control (MonadBaseControl (..))+import Control.Monad.Trans.Either (EitherT, left, runEitherT)+import Control.Monad.Trans.State (StateT (..), withStateT, evalStateT)+import Data.Foldable (asum)+import Data.Functor.Identity (Identity (..))+import Data.Maybe (fromMaybe, isJust)+++-- | 'Result' is an enriched 'Maybe' type which also specifies whether+--   recursion should occur from the given input, and if so, how such+--   recursion should be performed.+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)+    {-# INLINE fmap #-}++instance MFunctor (Result a) where+    hoist _ Ignore                 = Ignore+    hoist _ (Keep a)               = Keep a+    hoist nat (RecurseOnly l)      = RecurseOnly (fmap (hoist nat) l)+    hoist nat (KeepAndRecurse a l) = KeepAndRecurse a (fmap (hoist nat) l)+    {-# INLINE hoist #-}++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+    {-# INLINE (<>) #-}++instance Monoid b => Monoid (Result a m b) where+    mempty  = KeepAndRecurse mempty Nothing+    {-# INLINE mempty #-}+    mappend = (<>)+    {-# INLINE mappend #-}++getResult :: Result a m b -> (Maybe b, Maybe (CondT a m b))+getResult Ignore               = (Nothing, Nothing)+getResult (Keep b)             = (Just b, Nothing)+getResult (RecurseOnly c)      = (Nothing, c)+getResult (KeepAndRecurse b c) = (Just b, c)++setRecursion :: CondT a m b -> Result a m b -> Result a m b+setRecursion _ Ignore               = Ignore+setRecursion _ (Keep b)             = Keep b+setRecursion c (RecurseOnly _)      = RecurseOnly (Just c)+setRecursion c (KeepAndRecurse b _) = KeepAndRecurse b (Just c)++accept' :: b -> Result a m b+accept' = flip KeepAndRecurse Nothing++recurse' :: Result a m b+recurse' = RecurseOnly Nothing++-- | Convert from a 'Maybe' value to its corresponding 'Result'.+--+-- >>> maybeToResult Nothing :: Result () Identity ()+-- RecurseOnly+-- >>> maybeToResult (Just ()) :: Result () Identity ()+-- KeepAndRecurse ()+maybeToResult :: Maybe a -> Result r m a+maybeToResult Nothing  = recurse'+maybeToResult (Just a) = accept' a++maybeFromResult :: Result r m a -> Maybe a+maybeFromResult Ignore               = Nothing+maybeFromResult (Keep a)             = Just a+maybeFromResult (RecurseOnly _)      = Nothing+maybeFromResult (KeepAndRecurse a _) = Just a++-- | 'CondT' is a kind of @StateT a (MaybeT m) b@, which uses a special+--   'Result' type instead of 'Maybe' to express whether recursion should be+--   performed from the item under consideration.  This is used to build+--   predicates that can guide recursive traversals.+--+-- Several different types may be promoted to 'CondT':+--+--   [@Bool@]                  Using 'guard'+--+--   [@m Bool@]                Using 'guardM'+--+--   [@a -> Bool@]              Using 'guard_'+--+--   [@a -> m Bool@]            Using 'guardM_'+--+--   [@a -> m (Maybe b)@]       Using 'apply'+--+--   [@a -> m (Maybe (b, a))@]  Using 'consider'+--+-- Here is a trivial example:+--+-- @+-- flip runCondT 42 $ do+--   guard_ even+--   liftIO $ putStrLn "42 must be even to reach here"+--   guard_ odd \<|\> guard_ even+--   guard_ (== 42)+-- @+--+-- If 'CondT' is executed using 'runCondT', it return a @Maybe b@ if the+-- predicate matched.  It can also be run with 'applyCondT', which does case+-- analysis on the 'Result', specifying how recursion should be performed from+-- the given 'a' value.+newtype CondT a m b = CondT { getCondT :: StateT a m (Result a m b) }++type Cond a = CondT a Identity++instance Show (CondT a m b) where+    show _ = "CondT"++instance (Monad m, Semigroup b) => Semigroup (CondT a m b) where+    (<>) = liftM2 (<>)+    {-# INLINE (<>) #-}++instance (Monad m, Monoid b) => Monoid (CondT a m b) where+    mempty  = CondT $ return $ accept' mempty+    {-# INLINE mempty #-}++instance Monad m => Functor (CondT a m) where+    fmap f (CondT g) = CondT (fmap (fmap f) g)+    {-# INLINE fmap #-}++instance Monad m => Applicative (CondT a m) where+    pure  = CondT . pure . accept' -- pure+    {-# INLINE pure #-}+    (<*>) = ap+    {-# INLINE (<*>) #-}++instance Monad m => Monad (CondT a m) where+    -- return = CondT . pure . accept'+    -- {-# INLINE return #-}+    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 MonadFail m => MonadFail (CondT a m) where+    fail _ = mzero+    {-# INLINE fail #-}++instance Monad m => MonadReader a (CondT a m) where+    ask               = CondT $ gets accept'+    {-# INLINE ask #-}+    local f (CondT m) = CondT $ withStateT f m+    {-# INLINE local #-}+    reader f          = liftM f ask+    {-# INLINE reader #-}++instance Monad m => MonadState a (CondT a m) where+    get     = CondT $ gets accept'+    {-# INLINE get #-}+    put s   = CondT $ liftM accept' $ put s+    {-# INLINE put #-}+    state f = CondT $ state (fmap (first accept') f)+    {-# INLINE state #-}++instance Monad m => Alternative (CondT a m) where+    empty = CondT $ return recurse'+    {-# INLINE empty #-}+    CondT f <|> CondT g = CondT $ do+        r <- f+        case r of+            x@(Keep _) -> return x+            x@(KeepAndRecurse _ _) -> return x+            _ -> g+    {-# INLINE (<|>) #-}++instance Monad m => MonadPlus (CondT a m) where+    mzero = empty+    {-# INLINE mzero #-}+    mplus = (<|>)+    {-# INLINE mplus #-}++instance MonadThrow m => MonadThrow (CondT a m) where+    throwM = CondT . throwM+    {-# INLINE throwM #-}++instance MonadCatch m => MonadCatch (CondT a m) where+    catch (CondT m) c = CondT $ m `catch` \e -> getCondT (c e)++instance MonadMask m => MonadMask (CondT aa m) where+    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+    generalBracket :: forall a b c. HasCallStack =>+      CondT aa m a ->+      (a -> ExitCase b -> CondT aa m c) ->+      (a -> CondT aa m b) ->+      CondT aa m (b, c)+    generalBracket acquire release use = CondT go+      where+        arg1 :: StateT aa m (Result aa m a)+        arg1 = getCondT acquire+        arg2 :: (Result aa m a) -> ExitCase (Result aa m b) -> StateT aa m (Result aa m c)+        arg2 a b = case a of+          Ignore -> pure Ignore+          Keep a' ->+            case b of+              ExitCaseSuccess b' ->+                case b' of+                  Ignore -> getCondT $ release a' ExitCaseAbort+                  Keep b'' -> getCondT $ release a' (ExitCaseSuccess b'')+                  RecurseOnly _mb -> getCondT $ release a' ExitCaseAbort -- set mb?+                  KeepAndRecurse b'' _mb -> getCondT $ release a' (ExitCaseSuccess b'') -- set mb?+              ExitCaseException se -> getCondT $ release a' (ExitCaseException se)+              ExitCaseAbort -> getCondT $ release a' ExitCaseAbort+          RecurseOnly _ma ->+            pure Ignore+          KeepAndRecurse a' _ma ->+            case b of+              ExitCaseSuccess b' ->+                case b' of+                  Ignore -> getCondT $ release a' ExitCaseAbort+                  Keep b'' -> getCondT $ release a' (ExitCaseSuccess b'')+                  RecurseOnly _mb -> getCondT $ release a' ExitCaseAbort -- set mb?+                  KeepAndRecurse b'' _mb -> getCondT $ release a' (ExitCaseSuccess b'') -- set mb?+              ExitCaseException se -> getCondT $ release a' (ExitCaseException se)+              ExitCaseAbort -> getCondT $ release a' ExitCaseAbort++        arg3 :: (Result aa m a) -> StateT aa m (Result aa m b)+        arg3 a = getCondT (CondT (pure a) >>= use)++        go = do+          generalBracket arg1 arg2 arg3 >>= \case+            -- anyway looks like a total nonsense - i give up+            (Keep b, Keep c) -> pure $ Keep (b, c)+            (RecurseOnly mb, RecurseOnly mc) -> pure $ RecurseOnly (liftA2 (,) <$> mb <*> mc)+            (KeepAndRecurse b mb, KeepAndRecurse c mc) ->+              pure $ KeepAndRecurse (b, c) (liftA2 (,) <$> mb <*> mc)+            (Keep b, KeepAndRecurse c _) -> pure $ Keep (b, c)+            (KeepAndRecurse b _, Keep c) -> pure $ Keep (b, c)+            (KeepAndRecurse _ mb, RecurseOnly mc) -> pure $ RecurseOnly (liftA2 (,) <$> mb <*> mc)+            (RecurseOnly mb, KeepAndRecurse _ mc) -> pure $ RecurseOnly (liftA2 (,) <$> mb <*> mc)+            _ -> pure Ignore++instance MonadBase b m => MonadBase b (CondT a m) where+    liftBase m = CondT $ liftM accept' $ liftBase m+    {-# INLINE liftBase #-}++instance MonadIO m => MonadIO (CondT a m) where+    liftIO m = CondT $ liftM accept' $ liftIO m+    {-# INLINE liftIO #-}++instance MonadTrans (CondT a) where+    lift m = CondT $ liftM accept' $ lift m+    {-# INLINE lift #-}++instance MonadBaseControl b m => MonadBaseControl b (CondT r m) where+    type StM (CondT r m) a = StM m (Result r m a, r)+    liftBaseWith f = CondT $ StateT $ \s ->+        liftM (\x -> (accept' x, s)) $ liftBaseWith $ \runInBase -> f $ \k ->+            runInBase $ runStateT (getCondT k) s+    restoreM = CondT . StateT . const . restoreM+    {-# INLINE liftBaseWith #-}+    {-# INLINE restoreM #-}++instance MFunctor (CondT a) where+    hoist nat (CondT m) = CondT $ hoist nat (liftM (hoist nat) m)+    {-# INLINE hoist #-}++runCondT :: Monad m => CondT a m b -> a -> m (Maybe b)+runCondT (CondT f) a = maybeFromResult `liftM` evalStateT f a+{-# INLINE runCondT #-}++runCond :: Cond a b -> a -> Maybe b+runCond = (runIdentity .) . runCondT+{-# INLINE runCond #-}++-- | Case analysis of applying a condition to an input value.  The result is a+--   pair whose first part is a pair of Maybes specifying if the input matched+--   and if recursion is expected from this value, and whose second part is+--   the (possibly) mutated input value.+applyCondT :: Monad m+           => a+           -> CondT a m b+           -> m ((Maybe b, Maybe (CondT a m b)), a)+applyCondT a cond = do+    -- Apply a condition to the input value, determining the result and the+    -- mutated value.+    (r, a') <- runStateT (getCondT cond) a++    -- Convert from the 'Result' to a pair of Maybes: one to specify if the+    -- predicate succeeded, and the other to specify if recursion should be+    -- performed.  If there is no sub-recursion specified, return 'cond'.+    return (fmap (Just . fromMaybe cond) (getResult r), a')+{-# INLINE applyCondT #-}++-- | Case analysis of applying a pure condition to an input value.  The result+--   is a pair whose first part is a pair of Maybes specifying if the input+--   matched and if recursion is expected from this value, and whose second+--   part is the (possibly) mutated input value.+applyCond :: a -> Cond a b -> ((Maybe b, Maybe (Cond a b)), a)+applyCond a cond = first (fmap (Just . fromMaybe cond) . getResult)+                       (runIdentity (runStateT (getCondT cond) a))+{-# INLINE applyCond #-}++guardM :: Monad m => m Bool -> CondT a m ()+guardM m = lift m >>= guard+{-# INLINE guardM #-}++guard_ :: Monad m => (a -> Bool) -> CondT a m ()+guard_ f = asks f >>= guard+{-# INLINE guard_ #-}++guardM_ :: Monad m => (a -> m Bool) -> CondT a m ()+guardM_ f = ask >>= lift . f >>= guard+{-# INLINE guardM_ #-}++apply :: Monad m => (a -> m (Maybe b)) -> CondT a m b+apply f = CondT $ get >>= liftM maybeToResult . lift . f+{-# INLINE apply #-}++consider :: Monad m => (a -> m (Maybe (b, a))) -> CondT a m b+consider f = CondT $ do+    mres <- lift . f =<< get+    case mres of+        Nothing      -> return Ignore+        Just (b, a') -> put a' >> return (accept' b)+{-# INLINE consider #-}++-- | Return True or False depending on whether the given condition matches or+--   not.  This differs from simply stating the condition in that it itself+--   always succeeds.+--+-- >>> flip runCond "foo.hs" $ matches (guard =<< asks (== "foo.hs"))+-- Just True+-- >>> flip runCond "foo.hs" $ matches (guard =<< asks (== "foo.hi"))+-- Just False+matches :: Monad m => CondT a m b -> CondT a m Bool+matches = fmap isJust . optional+{-# INLINE matches #-}++-- | A variant of ifM which branches on whether the condition succeeds or not.+--   Note that @if_ x@ is equivalent to @ifM (matches x)@, and is provided+--   solely for convenience.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ if_ good (return "Success") (return "Failure")+-- Just "Success"+-- >>> flip runCond "foo.hs" $ if_ bad (return "Success") (return "Failure")+-- Just "Failure"+if_ :: Monad m => CondT a m r -> CondT a m b -> CondT a m b -> CondT a m b+if_ c x y =+    CondT $ getCondT . maybe y (const x) . maybeFromResult =<< getCondT c+{-# INLINE if_ #-}++-- | 'when_' is just like 'when', except that it executes the body if the+--   condition passes, rather than based on a Bool value.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ when_ good ignore+-- Nothing+-- >>> flip runCond "foo.hs" $ when_ bad ignore+-- Just ()+when_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()+when_ c x = if_ c (void x) (return ())+{-# INLINE when_ #-}++-- | 'when_' is just like 'when', except that it executes the body if the+--   condition fails, rather than based on a Bool value.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ unless_ bad ignore+-- Nothing+-- >>> flip runCond "foo.hs" $ unless_ good ignore+-- Just ()+unless_ :: Monad m => CondT a m r -> CondT a m () -> CondT a m ()+unless_ c = if_ c (return ()) . void+{-# INLINE unless_ #-}++-- | Check whether at least one of the given conditions is true.  This is a+--   synonym for 'Data.Foldable.asum'.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ or_ [bad, good]+-- Just ()+-- >>> flip runCond "foo.hs" $ or_ [bad]+-- Nothing+or_ :: Monad m => [CondT a m b] -> CondT a m b+or_ = asum+{-# INLINE or_ #-}++-- | Check that all of the given conditions are true.  This is a synonym for+--   'Data.Foldable.sequence_'.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ and_ [bad, good]+-- Nothing+-- >>> flip runCond "foo.hs" $ and_ [good]+-- Just ()+and_ :: Monad m => [CondT a m b] -> CondT a m ()+and_ = sequence_+{-# INLINE and_ #-}++-- | 'not_' inverts the meaning of the given predicate.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> flip runCond "foo.hs" $ not_ bad >> return "Success"+-- Just "Success"+-- >>> flip runCond "foo.hs" $ not_ good >> return "Shouldn't reach here"+-- Nothing+not_ :: Monad m => CondT a m b -> CondT a m ()+not_ c = when_ c ignore+{-# INLINE not_ #-}++-- | 'ignore' ignores the current entry, but allows recursion into its+--   descendents.  This is the same as 'mzero'.+ignore :: Monad m => CondT a m b+ignore = mzero+{-# INLINE ignore #-}++-- | 'norecurse' prevents recursion into the current entry's descendents, but+--   does not ignore the entry itself.+norecurse :: Monad m => CondT a m ()+norecurse = CondT $ return $ Keep ()+{-# INLINE norecurse #-}++-- | 'prune' is a synonym for both ignoring an entry and its descendents. It+--   is the same as @ignore >> norecurse@.+prune :: Monad m => CondT a m b+prune = CondT $ return Ignore+{-# INLINE prune #-}++-- | 'recurse' changes the recursion predicate for any child elements.  For+--   example, the following file-finding predicate looks for all @*.hs@ files,+--   but under any @.git@ directory looks only for a file named @config@:+--+-- @+-- if_ (name_ \".git\" \>\> directory)+--     (ignore \>\> recurse (name_ \"config\"))+--     (glob \"*.hs\")+-- @+--+-- NOTE: If this code had used @recurse (glob \"*.hs\"))@ instead in the else+-- case, it would have meant that @.git@ is only looked for at the top-level+-- of the search (i.e., the top-most element).+recurse :: Monad m => CondT a m b -> CondT a m b+recurse c = CondT $ setRecursion c `liftM` getCondT c+{-# INLINE recurse #-}++-- | A specialized variant of 'runCondT' that simply returns True or False.+--+-- >>> let good = guard_ (== "foo.hs") :: Cond String ()+-- >>> let bad  = guard_ (== "foo.hi") :: Cond String ()+-- >>> runIdentity $ test "foo.hs" $ not_ bad >> return "Success"+-- True+-- >>> runIdentity $ test "foo.hs" $ not_ good >> return "Shouldn't reach here"+-- False+test :: Monad m => a -> CondT a m b -> m Bool+test = (liftM isJust .) . flip runCondT+{-# INLINE test #-}++-- | This type is for documentation only, and shows the isomorphism between+--   'CondT' and 'CondEitherT'.  The reason for using 'Result' is that it+--   makes meaning of the constructors more explicit.+newtype CondEitherT a m b = CondEitherT+    (StateT a (EitherT (Maybe (Maybe (CondEitherT a m b))) m)+         (b, Maybe (Maybe (CondEitherT a m b))))++-- | Witness one half of the isomorphism from 'CondT' to 'CondEitherT'.+fromCondT :: Monad m => CondT a m b -> CondEitherT a m b+fromCondT (CondT f) = CondEitherT $ do+    s <- get+    (r, s') <- lift $ lift $ runStateT f s+    case r of+        Ignore             -> lift $ left Nothing+        Keep a             -> put s' >> return (a, Nothing)+        RecurseOnly m      -> lift $ left (Just (fmap fromCondT m))+        KeepAndRecurse a m -> put s' >> return (a, Just (fmap fromCondT m))++-- | Witness the other half of the isomorphism from 'CondEitherT' to 'CondT'.+toCondT :: Monad m => CondEitherT a m b -> CondT a m b+toCondT (CondEitherT f) = CondT $ do+    s <- get+    eres <- lift $ runEitherT $ runStateT f s+    case eres of+        Left Nothing             -> return Ignore+        Right ((a, Nothing), s') -> put s' >> return (Keep a)+        Left (Just m)            -> return $ RecurseOnly (fmap toCondT m)+        Right ((a, Just m), s')  ->+            put s' >> return (KeepAndRecurse a (fmap toCondT m))
+ src/Data/Conduit/Find.hs view
@@ -0,0 +1,618 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Data.Conduit.Find+    (+    -- * Introduction+    -- $intro++    -- ** Basic comparison with GNU find+    -- $gnufind++    -- ** Performance+    -- $performance++    -- ** Other notes+    -- $notes++    -- * Finding functions+      sourceFindFiles+    , find+    , findFiles+    , findFilePaths+    , FindOptions(..)+    , defaultFindOptions+    , test+    , ltest+    , stat+    , lstat+    , hasStatus++      -- * File path predicates+    , glob+    , regex+    , ignoreVcs++      -- * GNU find compatibility predicates+    , depth_+    , follow_+    , noleaf_+    , prune_+    , maxdepth_+    , mindepth_+    , ignoreErrors_+    , noIgnoreErrors_+    , amin_+    , atime_+    , anewer_+    , empty_+    , executable_+    , gid_+    , name_+    , getDepth+    , filename_+    , pathname_+    , getFilePath++    -- * File entry predicates (uses stat information)+    , regular+    , directory+    , hasMode+    , executable+    , lastAccessed_+    , lastModified_++    -- * Predicate combinators+    , module Cond+    , (=~)++    -- * Types and type classes+    , FileEntry(..)+    ) where++import           Control.Applicative (Alternative (..))+import           Control.Exception (IOException, catch, throwIO)+import           Control.Monad hiding (forM_, forM)+import           Control.Monad.Catch (MonadThrow)+import           Control.Monad.IO.Class (MonadIO, liftIO)+import           Control.Monad.IO.Unlift (MonadUnliftIO)+import           Control.Monad.Morph (hoist, lift)+import           Control.Monad.State.Class (get, gets, modify, put)+import           Control.Monad.Trans.Control (MonadBaseControl)+import           Control.Monad.Trans.Resource (MonadResource)++import           Data.Attoparsec.Text as A+import           Data.Bits ((.&.))+import           Data.Conduit (ConduitT, runConduitRes, (.|))+import qualified Data.Conduit as DC+import qualified Data.Conduit.List as DCL+import qualified Data.Cond as Cond+import           Data.Cond hiding (test)+import qualified Data.Conduit.Filesystem as CF+#if LEAFOPT+import           Data.IORef (IORef, newIORef, modifyIORef, readIORef)+#endif+import           Data.Maybe (fromMaybe, fromJust)+import           Data.Text (Text, unpack, pack)+import           Data.Time (UTCTime,  diffUTCTime)+import           Data.Time.Clock.POSIX (getCurrentTime, posixSecondsToUTCTime)+import qualified System.FilePath as FP+import           System.PosixCompat.Files (FileStatus, linkCount)+import qualified System.PosixCompat.Files as Files+import           System.PosixCompat.Types (EpochTime, FileMode)+import           Text.Regex.Posix ((=~))++{- $intro++**find-conduit** is essentially a souped version of GNU find for Haskell,+using a DSL to provide both ease of us, and extensive flexbility.++In its simplest form, let's compare some uses of find to find-conduit.  Bear+in mind that the result of the find function is a conduit, so you're expected+to either sink it to a list, or operate on the file paths as they are yielded.+-}++{- $gnufind++A typical find command:++@+find src -name '*.hs' -type f -print+@++Would in find-conduit be:++@+find "src" (glob \"*.hs\" \<\> regular) $$ mapM_C (liftIO . print)+@++The 'glob' predicate matches the file basename against the globbing pattern,+while the 'regular' predicate matches plain files.++A more complicated example:++@+find . -size +100M -perm 644 -mtime 1+@++Now in find-conduit:++@+let megs = 1024 * 1024+    days = 86400+now <- liftIO getCurrentTime+find \".\" ( fileSize (> 100*megs)+        \<\> hasMode 0o644+        \<\> lastModified (> addUTCTime now (-(1*days)))+         )+@++Appending predicates like this expressing an "and" relationship.  Use '<|>' to+express "or".  You can also negate any predicate:++@+find \".\" (not_ (hasMode 0o644))+@++By default, predicates, whether matching or not, will allow recursion into+directories.  In order to express that matching predicate should disallow+recursion, use 'prune':++@+find \".\" (prune (depth (> 2)))+@++This is the same as using '-maxdepth 2' in find.++@+find \".\" (prune (filename_ (== \"dist\")))+@++This is the same as:++@+find . \\( -name dist -prune \\) -o -print+@+-}++{- $performance++find-conduit strives to make file-finding a well performing operation.  To+this end, a composed Predicate will only call stat once per entry being+considered; and if you prune a directory, it is not traversed at all.++By default, 'find' calls stat for every file before it applies the predicate,+in order to ensure that only one such call is needed.  Sometimes, however, you+know just from the FilePath that you don't want to consider a certain file, or+you want to prune a directory tree.++To support these types of optimized queries, a variant of find is provided+called 'findWithPreFilter'.  This takes two predicates: one that is applied to+only the FilePath, before stat (or lstat) is called; and one that is applied+to the full file information after the stat.+-}++{- $notes++See 'Data.Cond' for more details on the Monad used to build predicates.+-}++data FindOptions = FindOptions+    { findFollowSymlinks   :: !Bool+    , findContentsFirst    :: !Bool+    , findIgnoreErrors     :: !Bool+    , findIgnoreResults    :: !Bool+    , findLeafOptimization :: !Bool+    }++defaultFindOptions :: FindOptions+defaultFindOptions = FindOptions+    { findFollowSymlinks   = True+    , findContentsFirst    = False+    , findIgnoreErrors     = False+    , findIgnoreResults    = False+    , findLeafOptimization = True+    }++data FileEntry = FileEntry+    { entryPath        :: !FP.FilePath+    , entryDepth       :: !Int+    , entryFindOptions :: !FindOptions+    , entryStatus      :: !(Maybe FileStatus)+      -- ^ This is Nothing until we determine stat should be called.+    }++newFileEntry :: FP.FilePath -> Int -> FindOptions -> FileEntry+newFileEntry fp d f = FileEntry fp d f Nothing++instance Show FileEntry where+    show entry = "FileEntry "+              ++ show (entryPath entry)+              ++ " " ++ show (entryDepth entry)++getFilePath :: Monad m => CondT FileEntry m FP.FilePath+getFilePath = gets entryPath++pathname_ :: Monad m => (FP.FilePath -> Bool) -> CondT FileEntry m ()+pathname_ f = guard . f =<< getFilePath++filename_ :: Monad m => (FP.FilePath -> Bool) -> CondT FileEntry m ()+filename_ f = pathname_ (f . FP.takeFileName)++getDepth :: Monad m => CondT FileEntry m Int+getDepth = gets entryDepth++modifyFindOptions :: Monad m+                  => (FindOptions -> FindOptions)+                  -> CondT FileEntry m ()+modifyFindOptions f =+    modify $ \e -> e { entryFindOptions = f (entryFindOptions e) }++------------------------------------------------------------------------+-- Workalike options for emulating GNU find.+------------------------------------------------------------------------++depth_ :: Monad m => CondT FileEntry m ()+depth_ = modifyFindOptions $ \opts -> opts { findContentsFirst = True }++follow_ :: Monad m => CondT FileEntry m ()+follow_ = modifyFindOptions $ \opts -> opts { findFollowSymlinks = True }++noleaf_ :: Monad m => CondT FileEntry m ()+noleaf_ = modifyFindOptions $ \opts -> opts { findLeafOptimization = False }++prune_ :: Monad m => CondT a m ()+prune_ = prune++ignoreErrors_ :: Monad m => CondT FileEntry m ()+ignoreErrors_ =+    modifyFindOptions $ \opts -> opts { findIgnoreErrors = True }++noIgnoreErrors_ :: Monad m => CondT FileEntry m ()+noIgnoreErrors_ =+    modifyFindOptions $ \opts -> opts { findIgnoreErrors = False }++maxdepth_ :: Monad m => Int -> CondT FileEntry m ()+maxdepth_ l = getDepth >>= guard . (<= l)++mindepth_ :: Monad m => Int -> CondT FileEntry m ()+mindepth_ l = getDepth >>= guard . (>= l)++-- xdev_ = error "NYI"++timeComp :: MonadIO m+         => ((UTCTime -> Bool) -> CondT FileEntry m ()) -> Int+         -> CondT FileEntry m ()+timeComp f n = do+    now <- liftIO getCurrentTime+    f (\t -> diffUTCTime now t > fromIntegral n)++amin_ :: MonadIO m => Int -> CondT FileEntry m ()+amin_ n = timeComp lastAccessed_ (n * 60)++atime_ :: MonadIO m => Int -> CondT FileEntry m ()+atime_ n = timeComp lastAccessed_ (n * 24 * 3600)++anewer_ :: MonadIO m => FP.FilePath -> CondT FileEntry m ()+anewer_ path = do+    e  <- get+    es <- applyStat Nothing+    ms <- getStat Nothing e { entryPath   = path+                            , entryStatus = Nothing+                            }+    case ms of+        Nothing     -> prune >> error "This is never reached"+        Just (s, _) -> guard $ diffUTCTime (f s) (f es) > 0+  where+    f = posixSecondsToUTCTime . realToFrac . Files.accessTime++-- cmin_ = error "NYI"+-- cnewer_ = error "NYI"+-- ctime_ = error "NYI"++empty_ :: MonadIO m => CondT FileEntry m ()+empty_ = (regular   >> hasStatus ((== 0) . Files.fileSize))+     <|> (directory >> hasStatus ((== 2) . linkCount))++executable_ :: MonadIO m => CondT FileEntry m ()+executable_ = executable++gid_ :: MonadIO m => Int -> CondT FileEntry m ()+gid_ n = hasStatus ((== n) . fromIntegral . Files.fileGroup)++{-+group_ name+ilname_ pat+iname_ pat+inum_ n+ipath_ pat+iregex_ pat+iwholename_ pat+links_ n+lname_ pat+mmin_+mtime_+-}++name_ :: Monad m => FP.FilePath -> CondT FileEntry m ()+name_ = filename_ . (==)++{-+newer_ path+newerXY_ ref+nogroup_+nouser_+path_ pat+perm_ mode :: Perm+readable_+regex_ pat+samefile_ path+size_ n :: Size+type_ c+uid_ n+used_ n+user_ name+wholename_ pat+writable_+xtype_ c+-}++------------------------------------------------------------------------++statFilePath :: Bool -> Bool -> FP.FilePath -> IO (Maybe FileStatus)+statFilePath follow ignoreErrors path = do+    let doStat = (if follow+                  then Files.getFileStatus+                  else Files.getSymbolicLinkStatus) path+    catch (Just <$> doStat) $ \e ->+        if ignoreErrors+        then return Nothing+        else throwIO (e :: IOException)++-- | Get the current status for the file.  If the status being requested is+--   already cached in the entry information, simply return it from there.+getStat :: MonadIO m+        => Maybe Bool+        -> FileEntry+        -> m (Maybe (FileStatus, FileEntry))+getStat mfollow entry = case entryStatus entry of+    Just s+        | maybe True (== follow entry) mfollow ->+            return $ Just (s, entry)+        | otherwise -> fmap (, entry) `liftM` wrapStat+    Nothing -> do+        ms <- wrapStat+        return $ case ms of+            Just s  -> Just (s, entry { entryStatus = Just s })+            Nothing -> Nothing+  where+    follow   = findFollowSymlinks . entryFindOptions+    wrapStat = liftIO $ statFilePath+        (fromMaybe (findFollowSymlinks opts) mfollow)+        (findIgnoreErrors opts)+        (entryPath entry)+      where+        opts = entryFindOptions entry++applyStat :: MonadIO m => Maybe Bool -> CondT FileEntry m FileStatus+applyStat mfollow = do+    ms <- lift . getStat mfollow =<< get+    case ms of+        Nothing      -> prune >> error "This is never reached"+        Just (s, e') -> s <$ put e'++lstat :: MonadIO m => CondT FileEntry m FileStatus+lstat = applyStat (Just False)++stat :: MonadIO m => CondT FileEntry m FileStatus+stat = applyStat (Just True)++hasStatus :: MonadIO m => (FileStatus -> Bool) -> CondT FileEntry m ()+hasStatus f = guard . f =<< applyStat Nothing++regular :: MonadIO m => CondT FileEntry m ()+regular = hasStatus Files.isRegularFile++executable :: MonadIO m => CondT FileEntry m ()+executable = hasMode Files.ownerExecuteMode++directory :: MonadIO m => CondT FileEntry m ()+directory = hasStatus Files.isDirectory++hasMode :: MonadIO m => FileMode -> CondT FileEntry m ()+hasMode m = hasStatus (\s -> Files.fileMode s .&. m /= 0)++withStatusTime :: MonadIO m+               => (FileStatus -> EpochTime) -> (UTCTime -> Bool)+               -> CondT FileEntry m ()+withStatusTime g f = hasStatus (f . posixSecondsToUTCTime . realToFrac . g)++lastAccessed_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()+lastAccessed_ = withStatusTime Files.accessTime++lastModified_ :: MonadIO m => (UTCTime -> Bool) -> CondT FileEntry m ()+lastModified_ = withStatusTime Files.modificationTime++regex :: Monad m => String -> CondT FileEntry m ()+regex pat = filename_ (=~ pat)++-- | Return all entries, except for those within version-control metadata+--   directories (and not including the version control directory itself either).+ignoreVcs :: Monad m => CondT FileEntry m ()+ignoreVcs = when_ (filename_ (`elem` vcsDirs)) prune+  where+    vcsDirs = [ ".git", "CVS", "RCS", "SCCS", ".svn", ".hg", "_darcs" ]++-- | Find every entry whose filename part matching the given filename globbing+--   expression.  For example: @glob "*.hs"@.+glob :: Monad m => String -> CondT FileEntry m ()+glob g = case parseOnly globParser (pack g) of+    Left e  -> error $ "Failed to parse glob: " ++ e+    Right x -> regex ("^" <> unpack x <> "$")+  where+    globParser :: Parser Text+    globParser = fmap mconcat $ many $+            char '*' *> return ".*"+        <|> char '?' *> return "."+        <|> string "[]]" *> return "[]]"+        <|> (\x y z -> pack ((x:y) ++ [z]))+                <$> char '['+                <*> manyTill anyChar (A.try (char ']'))+                <*> char ']'+        <|> do+            x <- anyChar+            return . pack $ if x `elem` (".()^$" :: String)+                            then ['\\', x]+                            else [x]++#if LEAFOPT+type DirCounter = IORef Word++newDirCounter :: MonadIO m => m DirCounter+newDirCounter = liftIO $ newIORef 1+#else+type DirCounter = ()++newDirCounter :: MonadIO m => m DirCounter+newDirCounter = return ()+#endif++-- | Find file entries in a directory tree, recursively, applying the given+--   recursion predicate to the search.  This conduit yields pairs of type+--   @(FileEntry, a)@, where is the return value from the predicate at each+--   step.+sourceFindFiles :: MonadResource m+                => FindOptions+                -> FilePath+                -> CondT FileEntry m a+                -> ConduitT i (FileEntry, a) m ()+sourceFindFiles findOptions startPath predicate = do+    startDc <- newDirCounter+    walk startDc+        (newFileEntry startPath 0 findOptions)+        startPath+        predicate+  where+    walk :: MonadResource m+         => DirCounter+         -> FileEntry+         -> FP.FilePath+         -> CondT FileEntry m a+         -> ConduitT i (FileEntry, a) m ()+    walk !dc !entry !path !cond = do+        ((!mres, !mcond), !entry') <- lift $ applyCondT entry cond+        let opts' = entryFindOptions entry+            this  = unless (findIgnoreResults opts') $+                        yieldEntry entry' mres+            next  = walkChildren dc entry' path mcond+        if findContentsFirst opts'+            then next >> this+            else this >> next+      where+        yieldEntry _      Nothing    = return ()+        yieldEntry entry' (Just res) = DC.yield (entry', res)++    walkChildren :: MonadResource m+                 => DirCounter+                 -> FileEntry+                 -> FP.FilePath+                 -> Maybe (CondT FileEntry m a)+                 -> ConduitT i (FileEntry, a) m ()+    walkChildren _ _ _ Nothing = return ()+    -- If the conditional matched, we are requested to recurse if this is a+    -- directory+    walkChildren !dc !entry !path (Just !cond) = do+        st <- lift $ checkIfDirectory dc entry path+        when (fmap Files.isDirectory st == Just True) $ do+#if LEAFOPT+            -- Update directory count for the parent directory.+            liftIO $ modifyIORef dc pred+            -- Track the directory count for this child path.+            let leafOpt = findLeafOptimization (entryFindOptions entry)+            let lc = linkCount (fromJust st) - 2+                opts' = (entryFindOptions entry)+                    { findLeafOptimization = leafOpt && lc >= 0+                    }+            dc' <- liftIO $ newIORef (fromIntegral lc :: Word)+#else+            let dc'   = dc+                opts' = entryFindOptions entry+#endif+            CF.sourceDirectory path .| DC.awaitForever (go dc' opts')+      where+        go dc' opts' fp =+            let entry' = newFileEntry fp (succ (entryDepth entry)) opts'+            in walk dc' entry' fp cond++    -- Return True if the given entry is a directory.  We can sometimes use+    -- "leaf optimization" on Linux to answer this question without performing+    -- a stat call.  This is possible because the link count of a directory is+    -- two more than the number of sub-directories it contains, so we've seen+    -- that many sub-directories, the remaining entries must be files.+    checkIfDirectory :: MonadResource m+                     => DirCounter+                     -> FileEntry+                     -> FP.FilePath+                     -> m (Maybe FileStatus)+    checkIfDirectory !dc !entry !path = do+#if LEAFOPT+        let leafOpt = findLeafOptimization (entryFindOptions entry)+        doStat <- if leafOpt+                  then (> 0) <$> liftIO (readIORef dc)+                  else return True+#else+        let doStat = dc == () -- to quiet hlint warnings+#endif+        let opts = entryFindOptions entry+        if doStat+            then liftIO $ statFilePath+                (findFollowSymlinks opts)+                (findIgnoreErrors opts)+                path+            else return Nothing++findFiles :: (MonadIO m, MonadBaseControl IO m, MonadThrow m, MonadUnliftIO m)+          => FindOptions+          -> FilePath+          -> CondT FileEntry m a+          -> m ()+findFiles opts path predicate =+    runConduitRes $+        sourceFindFiles opts { findIgnoreResults = True } path+            (hoist lift predicate) .| DCL.sinkNull++-- | A simpler version of 'findFiles', which yields only 'FilePath' values,+--   and ignores any values returned by the predicate action.+findFilePaths :: (MonadIO m, MonadResource m)+              => FindOptions+              -> FilePath+              -> CondT FileEntry m a+              -> ConduitT i FilePath m ()+findFilePaths opts path predicate =+    sourceFindFiles opts path predicate .| DCL.map (entryPath . fst)++-- | Calls 'findFilePaths' with the default set of finding options.+--   Equivalent to @findFilePaths defaultFindOptions@.+find :: MonadResource m+     => FilePath -> CondT FileEntry m a -> DC.ConduitT i FilePath m ()+find = findFilePaths defaultFindOptions++-- | Test a file path using the same type of predicate that is accepted by+--   'findFiles'.+test :: MonadIO m => CondT FileEntry m () -> FilePath -> m Bool+test matcher path =+    Cond.test (newFileEntry path 0 defaultFindOptions) matcher++-- | Test a file path using the same type of predicate that is accepted by+--   'findFiles', but do not follow symlinks.+ltest :: MonadIO m => CondT FileEntry m () -> FilePath -> m Bool+ltest matcher path =+    Cond.test+        (newFileEntry path 0 defaultFindOptions+            { findFollowSymlinks = False })+        matcher
− test/doctest.hs
@@ -1,29 +0,0 @@-module Main where--import Test.DocTest-import System.Directory-import System.FilePath-import Control.Applicative-import Control.Monad-import Data.List--main :: IO ()-main = getSources >>= \sources -> doctest $-    "-iData"-  : "-idist/build/autogen"-  : "-optP-include"-  : "-optPdist/build/autogen/cabal_macros.h"-  : sources--getSources :: IO [FilePath]-getSources =-    filter (\n -> ".hs" `isSuffixOf` n) <$> go "./Data"-  where-    go dir = do-      (dirs, files) <- getFilesAndDirectories dir-      (files ++) . concat <$> mapM go dirs--getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])-getFilesAndDirectories dir = do-  c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir-  (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
test/find-hs.hs view
@@ -1,41 +1,59 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE LambdaCase #-}+ module Main where -import Conduit-import Control.Monad-import Control.Monad.Reader.Class+import Data.Conduit ((.|), runConduitRes)+import Control.Monad (guard, void)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader.Class (asks) import Data.Conduit.Find-import Data.List-import System.Environment-import System.Posix.Process+import Data.Conduit.List qualified as DCL+import Data.List (isSuffixOf)+import System.Environment (getArgs)+import System.Posix.Process (executeFile)+import Data.Conduit.Filesystem (sourceDirectoryDeep) + main :: IO () main = do-    [command, dir] <- getArgs+    getArgs >>= \case+      [command, dir] -> runCommand command dir+      [command] -> runCommand command "."+      [] -> runCommand "find" "."+      o -> fail $ "Bad arguments: " <> show o++runCommand command dir =     case command of         "conduit" -> do             putStrLn "Running sourceDirectoryDeep from conduit-extra"-            runResourceT $+            runConduitRes $                 sourceDirectoryDeep False dir-                    =$ filterC (".hs" `isSuffixOf`)-                    $$ mapM_C (liftIO . putStrLn)+                    .| DCL.filter (".hs" `isSuffixOf`)+                    .| DCL.mapM_ (liftIO . putStrLn)+                    .| DCL.sinkNull          "find-conduit" -> do             putStrLn "Running findFiles from find-conduit"             findFiles defaultFindOptions { findFollowSymlinks = False }                 dir $ do                     path <- asks entryPath-                    guard (".hs" `isSuffixOf` path)+                    void $ guard (".hs" `isSuffixOf` path)                     norecurse                     liftIO $ putStrLn path          "find-conduit2" -> do             putStrLn "Running findFiles from find-conduit"-            runResourceT $+            runConduitRes $                 sourceFindFiles defaultFindOptions { findFollowSymlinks = False }                     dir (return ())-                    =$ filterC ((".hs" `isSuffixOf`) . entryPath . fst)-                    $$ mapM_C (liftIO . putStrLn . entryPath . fst)+                    .| DCL.filter ((".hs" `isSuffixOf`) . entryPath . fst)+                    .| DCL.mapM_ (liftIO . putStrLn . entryPath . fst)+                    .| DCL.sinkNull          "find" -> do             putStrLn "Running GNU find"             executeFile "find" True [dir, "-name", "*.hs", "-print"] Nothing++        _ ->+            putStrLn $ "Unknown command " ++ show command
test/main.hs view
@@ -1,9 +1,11 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-}  module Main where -import Conduit+import Data.Conduit import Data.Conduit.Find+import Data.Conduit.List qualified as DCL import Test.Hspec  main :: IO ()@@ -22,23 +24,20 @@             res `shouldBe` False          it "finds files" $ do-            xs <- runResourceT $+            xs <- runConduitRes $                 find "."                     (do ignoreVcs-                        when_ (name_ "dist") prune                         glob "*.hs"-                        not_ (glob "Setup*")                         regular                         not_ executable)-                    $$ sinkList+                    .| DCL.consume -            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True-            "./dist/setup-config" `elem` xs `shouldBe` False-            "./Setup.hs" `elem` xs `shouldBe` False+            "./src/Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist-newstyle/setup-config" `elem` xs `shouldBe` False             "./.git/config" `elem` xs `shouldBe` False          it "finds files with a different ordering" $ do-            xs <- runResourceT $+            xs <- runConduitRes $                 find "."                     (do ignoreVcs                         glob "*.hs"@@ -46,34 +45,32 @@                         -- This applies only to .hs files now, so it won't                         -- match anything, thus having no effect but burning                         -- CPU!-                        when_ (name_ "dist") prune+                        when_ (name_ "dist-newstyle") prune                         regular                         not_ executable)-                    $$ sinkList+                    .| DCL.consume -            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True-            "./dist/setup-config" `elem` xs `shouldBe` False-            "./Setup.hs" `elem` xs `shouldBe` False+            "./src/Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist-newstyle/setup-config" `elem` xs `shouldBe` False             "./.git/config" `elem` xs `shouldBe` False          it "finds files using a pre-pass filter" $ do-            xs <- runResourceT $+            xs <- runConduitRes $                 find "."                     (do ignoreVcs-                        when_ (name_ "dist") prune+                        when_ (name_ "dist-newstyle") prune                         glob "*.hs"                         not_ (glob "Setup*")                         regular                         not_ executable)-                    $$ sinkList+                    .| DCL.consume -            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True-            "./dist/setup-config" `elem` xs `shouldBe` False-            "./Setup.hs" `elem` xs `shouldBe` False+            "./src/Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist-newstyle/setup-config" `elem` xs `shouldBe` False             "./.git/config" `elem` xs `shouldBe` False          it "properly applies post-pass pruning" $ do-            xs <- runResourceT $+            xs <- runConduitRes $                 find "."                     (do ignoreVcs                         maxdepth_ 1@@ -82,7 +79,7 @@                         not_ (glob "Setup*")                         regular                         not_ executable)-                    $$ sinkList+                    .| DCL.consume              "./Data/Conduit/Find.hs" `elem` xs `shouldBe` False             "./dist/setup-config" `elem` xs `shouldBe` False