packages feed

find-conduit 0.0.1 → 0.1.0

raw patch · 4 files changed

+461/−51 lines, 4 filesdep +find-conduitdep +hspecdep +mtldep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: find-conduit, hspec, mtl, profunctors

Dependency ranges changed: base

API changes (from Hackage documentation)

- Data.Conduit.Find: Predicate :: (FileEntry -> m (Maybe FileEntry, Maybe (Predicate m))) -> Predicate m
- Data.Conduit.Find: newtype Predicate m
+ Data.Conduit.Find: and_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b
+ Data.Conduit.Find: class HasFilePath a
+ Data.Conduit.Find: executable :: Monad m => Predicate m FileEntry
+ Data.Conduit.Find: find :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FilePath
+ Data.Conduit.Find: find' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: findWithPreFilter :: (MonadIO m, MonadResource m) => FilePath -> Bool -> Predicate m FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: getFilePath :: HasFilePath a => a -> FilePath
+ Data.Conduit.Find: getPath :: MonadIO m => Looped m FileEntry FilePath
+ Data.Conduit.Find: instance Eq FindFilter
+ Data.Conduit.Find: instance HasFilePath FileEntry
+ Data.Conduit.Find: instance HasFilePath FilePath
+ Data.Conduit.Find: instance Show FileEntry
+ Data.Conduit.Find: instance Show FindFilter
+ Data.Conduit.Find: lfind :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FilePath
+ Data.Conduit.Find: lfind' :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FileEntry -> Producer m FileEntry
+ Data.Conduit.Find: lstat :: MonadIO m => Looped m FilePath FileEntry
+ Data.Conduit.Find: not_ :: MonadIO m => Looped m a a -> Looped m a a
+ Data.Conduit.Find: or_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b
+ Data.Conduit.Find: prune :: (Monad m, HasFilePath e) => FilePath -> Predicate m e
+ Data.Conduit.Find: readPaths :: (MonadIO m, MonadResource m) => FilePath -> Predicate m FilePath -> Producer m FilePath
+ Data.Conduit.Find: regular :: Monad m => Predicate m FileEntry
+ Data.Conduit.Find: stat :: MonadIO m => Looped m FilePath FileEntry
+ Data.Conduit.Find: test :: MonadIO m => Predicate m FileEntry -> FilePath -> m Bool
+ Data.Conduit.Find: type Predicate m a = Looped m a a
- Data.Conduit.Find: glob :: Monad m => Text -> Predicate m
+ Data.Conduit.Find: glob :: (Monad m, HasFilePath e) => Text -> Predicate m e
- Data.Conduit.Find: ignoreVcs :: MonadIO m => Predicate m
+ Data.Conduit.Find: ignoreVcs :: (MonadIO m, HasFilePath e) => Predicate m e
- Data.Conduit.Find: matchAll :: Monad m => Predicate m
+ Data.Conduit.Find: matchAll :: Monad m => Predicate m a
- Data.Conduit.Find: regex :: Monad m => Text -> Predicate m
+ Data.Conduit.Find: regex :: (Monad m, HasFilePath e) => Text -> Predicate m e
- Data.Conduit.Find: regexMatcher :: Monad m => (FilePath -> FilePath) -> Bool -> Text -> Predicate m
+ Data.Conduit.Find: regexMatcher :: (Monad m, HasFilePath e) => (FilePath -> FilePath) -> Text -> Predicate m e
- Data.Conduit.Find: sourceFileEntries :: MonadResource m => Bool -> Predicate m -> FilePath -> Producer m FileEntry
+ Data.Conduit.Find: sourceFileEntries :: MonadResource m => Looped m FilePath FileEntry -> FilePath -> Producer m FileEntry

Files

Data/Conduit/Find.hs view
@@ -1,30 +1,48 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-}  module Data.Conduit.Find     ( FileEntry(..)-    , Predicate(..)+    , Predicate+    , HasFilePath(..)     , sourceFileEntries     , matchAll     , ignoreVcs     , regexMatcher     , regex     , glob+    , stat+    , lstat+    , getPath+    , regular+    , executable+    , prune+    , test+    , find+    , find'+    , lfind+    , lfind'+    , findWithPreFilter+    , readPaths+    , or_+    , and_+    , not_     ) where  import Conduit import Control.Applicative-import Control.Monad (when)+import Control.Arrow+import Control.Monad import Data.Attoparsec.Text+import Data.Bits+import Data.Conduit.Find.Looped import Data.Foldable (for_)-import Data.Monoid ((<>), mconcat)+import Data.Monoid import Data.Text (Text, unpack, pack) import Filesystem.Path.CurrentOS (FilePath, encodeString, filename) import Prelude hiding (FilePath)-import System.Posix.Files (FileStatus, getFileStatus, getSymbolicLinkStatus,-                           isDirectory)+import System.Posix.Files import Text.Regex.Posix ((=~))  data FileEntry = FileEntry@@ -32,9 +50,20 @@     , entryStatus :: FileStatus     } -newtype Predicate m =-    Predicate (FileEntry -> m (Maybe FileEntry, Maybe (Predicate m)))+instance Show FileEntry where+    show entry = "FileEntry " ++ show (entryPath entry) +class HasFilePath a where+    getFilePath :: a -> FilePath++instance HasFilePath FilePath where+    getFilePath = id++instance HasFilePath FileEntry where+    getFilePath = entryPath++type Predicate m a = Looped m a a+ -- | Walk through the entries of a directory tree, allowing the user to --   specify a 'Predicate' which may decides not only which entries to yield --   from the conduit, but also which directories to follow, and how to@@ -48,74 +77,60 @@ --   'followSymlinks' is @False@ it only prevents directory symlinks from --   being read. sourceFileEntries :: MonadResource m-                  => Bool -> Predicate m -> FilePath -> Producer m FileEntry-sourceFileEntries followSymlinks (Predicate matcher) dir =-    sourceDirectory dir =$= go+                  => Looped m FilePath FileEntry+                  -> FilePath+                  -> Producer m FileEntry+sourceFileEntries matcher dir = sourceDirectory dir =$= go matcher   where-    go = do+    go m = do         mfp <- await         for_ mfp $ \fp -> do-            stat <- liftIO $-                (if followSymlinks-                 then getFileStatus-                 else getSymbolicLinkStatus)-                (encodeString fp)-            let entry = FileEntry fp stat-            res <- lift $ matcher entry-            for_ (fst res) yield-            when (isDirectory stat) $-                for_ (snd res) $-                    flip (sourceFileEntries followSymlinks) fp-            go+            applyPredicate m fp yield (`sourceFileEntries` fp)+            go m  -- | Return all entries.  This is the same as 'sourceDirectoryDeep', except --   that the 'FileStatus' structure for each entry is also provided.  As a --   result, only one stat call is ever made per entry, compared to two per --   directory in the current version of 'sourceDirectoryDeep'.-matchAll :: Monad m => Predicate m-matchAll = Predicate $ \entry -> return (Just entry, Just matchAll)+matchAll :: Monad m => Predicate m a+matchAll = Looped $ \entry -> return $ KeepAndRecurse entry matchAll  -- | Return all entries, except for those within version-control metadata --   directories (and not including the version control directory itself either).-ignoreVcs :: MonadIO m => Predicate m-ignoreVcs = Predicate $ \entry ->-    return $ if filename (entryPath entry) `elem` vcsDirs-             then (Nothing, Nothing)-             else (Just entry, Just ignoreVcs)+ignoreVcs :: (MonadIO m, HasFilePath e) => Predicate m e+ignoreVcs = Looped $ \entry ->+    return $ if filename (getFilePath entry) `elem` vcsDirs+             then Ignore+             else KeepAndRecurse entry ignoreVcs   where-    vcsDirs = [ ".git", "CVS", "RCS", "SCCS", ".svn", ".hg" ]+    vcsDirs = [ ".git", "CVS", "RCS", "SCCS", ".svn", ".hg", "_darcs" ]  -- | The 'regexMatcher' predicate builder matches some part of every path --   against a given regex.  Use the simpler 'regex' if you just want to apply --   a regex to every file name.-regexMatcher :: Monad m+regexMatcher :: (Monad m, HasFilePath e)              => (FilePath -> FilePath)                 -- ^ Function that specifies which part of the pathname to                 --   match against.  Use this to match against only filenames,                 --   or to relativize the path against the search root before                 --   comparing.-             -> Bool-                -- ^ If True, prune directories from the search that do not-                --   match.              -> Text                 -- ^ The regular expression search pattern.-             -> Predicate m-regexMatcher accessor pruneNonMatching (unpack -> pat) = go+             -> Predicate m e+regexMatcher accessor (unpack -> pat) = go   where-    go = Predicate $ \entry ->-        return $ if encodeString (accessor (entryPath entry)) =~ pat-                 then (Just entry, Just go)-                 else (Nothing, if pruneNonMatching-                                then Nothing-                                else Just go)+    go = Looped $ \entry ->+        return $ if encodeString (accessor (getFilePath entry)) =~ pat+                 then KeepAndRecurse entry go+                 else Recurse go  -- | Find every entry whose filename part matching the given regular expression.-regex :: Monad m => Text -> Predicate m-regex = regexMatcher filename False+regex :: (Monad m, HasFilePath e) => Text -> Predicate m e+regex = regexMatcher filename  -- | Find every entry whose filename part matching the given filename globbing --   expression.  For example: @glob "*.hs"@.-glob :: Monad m => Text -> Predicate m+glob :: (Monad m, HasFilePath e) => Text -> Predicate m e glob g = case parseOnly globParser g of     Left e  -> error $ "Failed to parse glob: " ++ e     Right x -> regex ("^" <> x <> "$")@@ -124,14 +139,120 @@     globParser = fmap mconcat $ many $             char '*' *> return ".*"         <|> char '?' *> return "."+        <|> string "[]]" *> return "[]]"         <|> (\x y z -> pack ((x:y) ++ [z]))                 <$> char '['-                -- jww (2014-04-23): This does not yet handle the pattern []],-                -- which is legal.                 <*> manyTill anyChar (try (char ']'))                 <*> char ']'         <|> do             x <- anyChar-            return . pack $ if x `elem` ['.', '(', ')', '^', '$']+            return . pack $ if x `elem` ".()^$"                             then ['\\', x]                             else [x]++doStat :: MonadIO m+       => (String -> IO FileStatus) -> Looped m FilePath FileEntry+doStat getstatus = Looped $ \path -> do+    s <- liftIO $ getstatus (encodeString path)+    let entry = FileEntry path s+    return $ if isDirectory s+             then KeepAndRecurse entry (doStat getstatus)+             else Keep entry++lstat :: MonadIO m => Looped m FilePath FileEntry+lstat = doStat getSymbolicLinkStatus++stat :: MonadIO m => Looped m FilePath FileEntry+stat = doStat getFileStatus++getPath :: MonadIO m => Looped m FileEntry FilePath+getPath = liftLooped (return . entryPath)++status :: Monad m => (FileStatus -> Bool) -> Predicate m FileEntry+status f = if_ (f . entryStatus)++regular :: Monad m => Predicate m FileEntry+regular = status isRegularFile++executable :: Monad m => Predicate m FileEntry+executable = status (\s -> fileMode s .&. ownerExecuteMode /= 0)++prune :: (Monad m, HasFilePath e) => FilePath -> Predicate m e+prune path = Looped $ \entry ->+    return $ if getFilePath entry == path+             then Ignore+             else KeepAndRecurse entry (prune path)++test :: MonadIO m => Predicate m FileEntry -> FilePath -> m Bool+test matcher path =+    getAny `liftM` testSingle (stat >>> matcher) path alwaysTrue+  where+    alwaysTrue = const (return (Any True))++find :: (MonadIO m, MonadResource m)+     => FilePath -> Predicate m FileEntry -> Producer m FilePath+find path pr = sourceFileEntries (stat >>> pr) path =$= mapC entryPath++data FindFilter+    = IgnoreFile+    | ConsiderFile+    | MaybeRecurse+    deriving (Show, Eq)++-- | Run a find, but using a pre-pass filter on the FilePaths, to eliminates+--   files from consideration early and avoid calling stat on them.+findWithPreFilter :: (MonadIO m, MonadResource m)+                  => FilePath+                  -> Bool+                  -> Predicate m FilePath+                  -> Predicate m FileEntry+                  -> Producer m FileEntry+findWithPreFilter path follow filt pr =+    sourceDirectory path =$= go pr+  where+    go m = do+        mfp <- await+        for_ mfp $ \fp -> do+            r <- lift $ runLooped filt fp+            let candidate = case r of+                    Ignore -> IgnoreFile+                    Keep _ -> ConsiderFile+                    Recurse _ -> MaybeRecurse+                    KeepAndRecurse _ _ -> ConsiderFile+            unless (candidate == IgnoreFile) $ do+                st <- liftIO $+                    (if follow+                     then getFileStatus+                     else getSymbolicLinkStatus) (encodeString fp)+                let next = when (isDirectory st) .+                               findWithPreFilter fp follow filt+                case candidate of+                    IgnoreFile -> return ()+                    MaybeRecurse -> next pr+                    ConsiderFile ->+                        applyPredicate m (FileEntry fp st) yield next+            go m++find' :: (MonadIO m, MonadResource m)+      => FilePath -> Predicate m FileEntry -> Producer m FileEntry+find' path pr = sourceFileEntries (stat >>> pr) path++lfind :: (MonadIO m, MonadResource m)+      => FilePath -> Predicate m FileEntry -> Producer m FilePath+lfind path pr = sourceFileEntries (lstat >>> pr) path =$= mapC entryPath++lfind' :: (MonadIO m, MonadResource m)+       => FilePath -> Predicate m FileEntry -> Producer m FileEntry+lfind' path pr = sourceFileEntries (lstat >>> pr) path++readPaths :: (MonadIO m, MonadResource m)+          => FilePath -> Predicate m FilePath -> Producer m FilePath+readPaths path pr = sourceDirectory path =$= do+    mfp <- await+    for_ mfp $ \fp -> do+        r <- lift $ runLooped pr fp+        case r of+            Ignore -> return ()+            Keep a -> yield a+            Recurse _ -> return ()+            KeepAndRecurse a _ -> yield a
+ Data/Conduit/Find/Looped.hs view
@@ -0,0 +1,191 @@+{-# LANGUAGE TupleSections #-}++-- | Main entry point to the application.+module Data.Conduit.Find.Looped where++import Control.Applicative+import Control.Arrow+import Control.Category+import Control.Monad+import Control.Monad.Trans+import Data.Monoid+import Data.Profunctor+import Prelude hiding ((.), id)++data Result m a b+    = Ignore+    | Keep b+    | Recurse (Looped m a b)+    | KeepAndRecurse b (Looped m a b)++instance Show a => Show (Result r m a) where+    show Ignore = "Ignore"+    show (Keep a) = "Keep " ++ show a+    show (Recurse _) = "Recurse"+    show (KeepAndRecurse a _) = "KeepAndRecurse " ++ show a++instance Functor m => Functor (Result m a) where+    fmap _ Ignore = Ignore+    fmap f (Keep a) = Keep (f a)+    fmap f (Recurse l) = Recurse (fmap f l)+    fmap f (KeepAndRecurse a l)  = KeepAndRecurse (f a) (fmap f l)++instance Functor m => Profunctor (Result m) where+    lmap _ Ignore = Ignore+    lmap _ (Keep a) = Keep a+    lmap f (Recurse l) = Recurse (lmap f l)+    lmap f (KeepAndRecurse a l)  = KeepAndRecurse a (lmap f l)+    rmap = fmap++instance (Functor m, Monad m) => Applicative (Result m a) where+    pure = return+    (<*>) = ap++instance Monad m => Monad (Result m a) where+    return = Keep+    Ignore >>= _ = Ignore+    Keep a >>= f = case f a of+        Ignore -> Ignore+        Keep b -> Keep b+        (Recurse _) -> Ignore+        (KeepAndRecurse b _) -> Keep b+    Recurse (Looped l) >>= f =+        Recurse (Looped $ \r -> liftM (>>= f) (l r))+    KeepAndRecurse a _ >>= f = f a++newtype Looped m a b = Looped { runLooped :: a -> m (Result m a b) }++instance Functor m => Functor (Looped m a) where+    fmap f (Looped g) = Looped (fmap (fmap (fmap f)) g)++instance Functor m => Profunctor (Looped m) where+    lmap f (Looped k) = Looped (fmap (fmap (lmap f)) (k . f))+    rmap = fmap++instance (Functor m, Monad m) => Applicative (Looped m a) where+    pure = return+    (<*>) = ap++instance Monad m => Monad (Looped m a) where+    return = Looped . const . return . return+    Looped f >>= k = Looped $ \a -> do+        r <- f a+        case r of+            Ignore -> return Ignore+            Keep b -> runLooped (k b) a+            Recurse l -> runLooped (l >>= k) a+            KeepAndRecurse b _ -> runLooped (k b) a++instance Monad m => Category (Looped m) where+    id = let x = Looped $ \a -> return $ KeepAndRecurse a x in x+    Looped f . Looped g = Looped $ \a -> do+          r <- g a+          case r of+            Ignore -> return Ignore+            Keep b -> do+                r' <- f b+                return $ case r' of+                    Ignore -> Ignore+                    Keep c -> Keep c+                    Recurse _ -> Ignore+                    KeepAndRecurse c _ -> Keep c+            Recurse (Looped l) ->+                return $ Recurse (Looped f . Looped l)+            KeepAndRecurse b (Looped l) -> do+                r' <- f b+                return $ case r' of+                    Ignore -> Ignore+                    Keep c -> Keep c+                    Recurse (Looped m) ->+                        Recurse (Looped m . Looped l)+                    KeepAndRecurse c (Looped m) ->+                        KeepAndRecurse c (Looped m . Looped l)++instance Monad m => Arrow (Looped m) where+    arr f = Looped $ return . Keep . f+    first (Looped f) = Looped $ \(a, c) -> do+        r <- f a+        return $ case r of+            Ignore -> Ignore+            Keep b -> Keep (b, c)+            Recurse l -> Recurse (first l)+            KeepAndRecurse b l -> KeepAndRecurse (b, c) (first l)++evens :: Monad m => Looped m Int Int+evens = Looped $ \x ->+    return $ if even x+             then KeepAndRecurse x evens+             else Recurse evens++tens :: Monad m => Looped m Int Int+tens = Looped $ \x ->+    return $ if x `mod` 10 == 0+             then KeepAndRecurse x tens+             else Ignore++apply :: Looped m a b -> a -> Looped m a b+apply l x = Looped $ const $ runLooped l x++applyPredicate :: (MonadTrans t, (Monad (t m)), Monad m, Show b)+               => Looped m a b -> a -> (b -> t m ())+               -> (Looped m a b -> t m ()) -> t m ()+applyPredicate l x f g = do+    r <- lift $ runLooped l x+    case r of+        Ignore -> return ()+        Keep a -> f a+        Recurse m -> g m+        KeepAndRecurse a m -> f a >> g m++testSingle :: (Monad m, Monoid c) => Looped m a b -> a -> (b -> m c) -> m c+testSingle l x f = do+    r <- runLooped l x+    case r of+        Ignore -> return mempty+        Keep a -> f a+        Recurse _ -> return mempty+        KeepAndRecurse a _ -> f a++liftLooped :: Monad m => (a -> m b) -> Looped m a b+liftLooped f = Looped $ \a -> do+    r <- f a+    return $ KeepAndRecurse r (liftLooped f)++if_ :: Monad m => (a -> Bool) -> Looped m a a+if_ f = Looped $ \a ->+    return $ if f a+             then KeepAndRecurse a (if_ f)+             else Recurse (if_ f)++or_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b+or_ (Looped f) (Looped g) = Looped $ \a -> do+    r <- f a+    case r of+        Keep b -> return $ Keep b+        KeepAndRecurse b l -> return $ KeepAndRecurse b l+        _ -> g a++and_ :: MonadIO m => Looped m a b -> Looped m a b -> Looped m a b+and_ (Looped f) (Looped g) = Looped $ \a -> do+    r <- f a+    case r of+        Ignore -> return Ignore+        Keep _ -> g a+        Recurse l -> return $ Recurse l+        KeepAndRecurse _ _ -> g a++not_ :: MonadIO m => Looped m a a -> Looped m a a+not_ (Looped f) = Looped (\a -> go a `liftM` f a)+  where+    go a Ignore = Keep a+    go _ (Keep _) = Ignore+    go a (Recurse l) = KeepAndRecurse a (not_ l)+    go _ (KeepAndRecurse _ l) = Recurse (not_ l)++pruneIgnored :: MonadIO m => Looped m a a -> Looped m a a+pruneIgnored (Looped f) = Looped (\a -> go a `liftM` f a)+  where+    go _ Ignore = Ignore+    go _ x@(Keep _) = x+    go _ (Recurse _) = Ignore+    go _ (KeepAndRecurse b m) = KeepAndRecurse b (pruneIgnored m)
find-conduit.cabal view
@@ -1,5 +1,5 @@ Name:                find-conduit-Version:             0.0.1+Version:             0.1.0 Synopsis:            A file-finding conduit that allows user control over traversals. License-file:        LICENSE License:             MIT@@ -27,5 +27,28 @@       , unix                 >= 2.5.1.1       , text                 >= 0.11.3.1       , regex-posix+      , profunctors+      , mtl     exposed-modules:         Data.Conduit.Find+    other-modules:+        Data.Conduit.Find.Looped++test-suite test+    hs-source-dirs: test+    default-language: Haskell2010+    main-is: main.hs+    type: exitcode-stdio-1.0+    ghc-options: -Wall -threaded+    build-depends:+        base+      , find-conduit+      , conduit+      , conduit-combinators+      , attoparsec+      , system-filepath+      , unix                 >= 2.5.1.1+      , text                 >= 0.11.3.1+      , regex-posix+      , mtl+      , hspec                >= 1.4
+ test/main.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Conduit+import Control.Arrow+import Data.Conduit.Find+import Test.Hspec++main :: IO ()+main = hspec $ do+    describe "basic tests" $ do+        it "file passes a test" $ do+            res <- test (regular >>> not_ executable) "README.md"+            res `shouldBe` True++        it "file fails a test" $ do+            res <- test (not_ regular) "README.md"+            res `shouldBe` False++        it "file fails another test" $ do+            res <- test (regular >>> executable) "README.md"+            res `shouldBe` False++        it "finds files" $ do+            xs <- runResourceT $+                find "."+                    (    ignoreVcs+                     >>> prune "./dist"+                     >>> glob "*.hs"+                     >>> not_ (glob "Setup*")+                     >>> regular+                     >>> not_ executable+                    )+                    $$ sinkList++            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist/setup-config" `elem` xs `shouldBe` False+            "./Setup.hs" `elem` xs `shouldBe` False+            "./.git/config" `elem` xs `shouldBe` False++        it "finds files with a different ordering" $ do+            xs <- runResourceT $+                find "."+                    (    ignoreVcs+                     >>> glob "*.hs"+                     >>> not_ (glob "Setup*")+                     >>> prune "./dist"+                     >>> regular+                     >>> not_ executable+                    )+                    $$ sinkList++            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist/setup-config" `elem` xs `shouldBe` False+            "./Setup.hs" `elem` xs `shouldBe` False+            "./.git/config" `elem` xs `shouldBe` False++        it "finds files using a pre-pass filter" $ do+            xs <- runResourceT $+                findWithPreFilter "." True+                    (    ignoreVcs+                     >>> glob "*.hs"+                     >>> not_ (glob "Setup*")+                     >>> prune "./dist"+                    )+                    (    regular+                     >>> not_ executable+                    )+                    =$ mapC getFilePath $$ sinkList++            "./Data/Conduit/Find.hs" `elem` xs `shouldBe` True+            "./dist/setup-config" `elem` xs `shouldBe` False+            "./Setup.hs" `elem` xs `shouldBe` False+            "./.git/config" `elem` xs `shouldBe` False