unix-recursive (empty) → 0.1.0.0
raw patch · 16 files changed
+1675/−0 lines, 16 filesdep +basedep +bytestringdep +criterionsetup-changed
Dependencies added: base, bytestring, criterion, dir-traverse, directory, dirstream, hspec, pipes, pipes-safe, system-filepath, unix, unix-recursive, utf8-string
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +57/−0
- Setup.hs +2/−0
- System/Posix/Recursive.hs +274/−0
- System/Posix/Recursive/ByteString.hs +277/−0
- System/Posix/Recursive/ByteString/Unsafe.hs +242/−0
- System/Posix/Recursive/Unsafe.hs +238/−0
- bench/Bench.hs +43/−0
- bin/dir-traverse.hs +11/−0
- bin/dirstream.hs +32/−0
- bin/unix-recursive-bytestring.hs +13/−0
- bin/unix-recursive-string.hs +11/−0
- test/Spec.hs +82/−0
- test/Spec/Listing.hs +242/−0
- unix-recursive.cabal +118/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for watch-fs-hs++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,57 @@+# Unix Recursive++++Blazingly fast functions for recursive file system operations.+Utilizing lazy IO for constant space & computation efficiant bindigns to Posix [`dirstream.h`](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/dirstream.h;h=8303f07fab6f6efaa39e51411ef924e712d995e0;hb=fa39685d5c7df2502213418bead44e9543a9b9ec) api.++## Comparision with other libraries:++Tests were performed on relatively modern consumer grade HW with relatively modern consumer grade M.2 SSD drive.+All done on binaries compiled with optimization that are listing the same directory+with 1,705,184 items.++### [this lib] Unix Recursive `RawFilePath`/ByteString++++- [source](bin/unix-recursive-bytestring.hs)++### [this lib] Unix Recursive `FilePath`/String++++- [source](bin/unix-recursive-string.hs)++### [alternative] Dir Traverse (`FilePath`/String is the only option)++++- [source](bin/dir-traverse.hs)+- [lib](https://hackage.haskell.org/package/dir-traverse)++### [alternative] Dirstream (`Filesystem.Path` is the only option)++++- [source](bin/dirstream.hs)+- [lib](https://hackage.haskell.org/package/dirstream)++## Testing performance++Cabal flag `bin` is being used for building the example binaries that can be used for measurement.++```+make bin+```++or to build and run one of the binaries:++```+$ stack build --flag unix-recursive:bin --exec "unix-recursive-bytestring-bin ${path-to-directory}"+```++## Hacking++This project uses the "Perfect Haskell Preprocessor" (PHP) for generating haskell source from meta module hs.+See [`gen`](gen) directory and [`MakeFile`](MakeFile) before you start hackking on stuff.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/Posix/Recursive.hs view
@@ -0,0 +1,274 @@+{- |+ Module : System.Posix.Recursive-- Copyright : (c) Marek Fajkus+ License : BSD3++ Maintainer : marek.faj@gmail.com++ All modules profided by @unix-recursive@ expose similar API.+ Make sure you're using the module wich best fits your needs based on:+ - Working with 'RawFilePath' (faster and more packed) or 'FilePath' (slower but easier to work with safely)+ - Exception free (Default) or @Unsafe@ variants of functions++ = Usage++ This module is designed to be imported as @qualified@:++ > import qualified System.Posix.Recursive as PosixRecursive++ __Results__++ All functions return will return root path (the one they accept in argument) as a first item in the list:++ > head <$> PosixRecursive.list "System"+ > >>> "System"++ Other than that, this library __provides no guarantees about the order in which files appear in the resulting list__+ to make it possible to change the underlaying strategy in future versions.++ __Laziness__++ All IO operations are __guaranteed to be lazy and have constanct space characteristic__.+ Only the IO required by lazy computation will be performed so for instance running code like:++ > take 20 <$> PosixRecursive.listDirectories "/"++ Will perform only minimal ammount of IO needed to collect 20 directories on a root partition+-}+module System.Posix.Recursive (+ -- * Basic Listing+ -- $basic_listing+ list,+ followList,+ listMatching,+ followListMatching,++ -- * File Type Based Listing+ -- $type_based+ listAccessible,+ listDirectories,+ listRegularFiles,+ listSymbolicLinks,++ -- * Custom Listing+ -- $custom+ Conf (..),+ defConf,+ listCustom,+) where++import Control.Exception (bracket, handle)+import Data.Foldable (fold)+import System.IO.Unsafe (unsafeInterleaveIO)+import System.Posix.Files (FileStatus)++import qualified System.Posix.Directory as Posix+import qualified System.Posix.Files as Posix+++-- Helpers++foldMapA :: (Monoid b, Traversable t, Applicative f) => (a -> f b) -> t a -> f b+foldMapA = (fmap fold .) . traverse+++{-# INLINE listDir #-}+listDir :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+listDir predicate path =+ bracket+ (Posix.openDirStream path)+ Posix.closeDirStream+ (go [])+ where+ go :: [FilePath] -> Posix.DirStream -> IO [FilePath]+ go acc dirp = do+ e <- Posix.readDirStream dirp+ if null e+ then pure acc+ else+ if e /= "." && e /= ".."+ then+ let fullPath = path <> "/" <> e+ in if predicate fullPath+ then go (fullPath : acc) dirp+ else go acc dirp+ else go acc dirp+{- $basic_listing+ Functions for listing contents of directory recursively.+ These functions list all the content they encounter while traversing+ the file system tree including directories, files, symlinks, broken symlinks.++ __Directories (and files) which process can't open due to permissions are listed but not recursed into.__++ Functions from this section is gurantee to always return the root path as a first element even+ if this path does not exist.++ > PosixRecursive.list "i-dont-exist"+ > >>> ["i-dont-exist"]++ In these cases the root path is considered the same way as symlink+ to non existing location.+-}+++{-# INLINE listAll' #-}+listAll' :: Bool -> (FilePath -> Bool) -> FilePath -> IO [FilePath]+listAll' followSymlinks predicate path =+ handle (\(_ :: IOError) -> pure []) $ do+ file <- getFileStatus path+ if Posix.isDirectory file+ then do+ content <- listDir predicate path++ next <- unsafeInterleaveIO $ foldMapA (listAll' followSymlinks predicate) content+ pure $ content ++ next+ else pure []+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++{-# INLINE listAll'' #-}+listAll'' :: Bool -> (FilePath -> Bool) -> FilePath -> IO [FilePath]+listAll'' followSymlinks predicate path =+ (path :) <$> listAll' followSymlinks predicate path+++-- | Like 'list' but uses provided function to test in which 'FilePath' to recurse into.+listMatching :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+listMatching =+ listAll'' False+++-- | Like 'followList' but uses provided function to test in which 'FilePath' to recurse into.+followListMatching :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+followListMatching =+ listAll'' True+++{- | List all files, directories & symlinks recursively.+ Symlinks are not followed. See 'followList'.+-}+list :: FilePath -> IO [FilePath]+list =+ listAll'' False (const True)+++{- | List all files, directories & symlinks recursively.+ Unlike 'list', symlinks are followed recursively as well.+-}+followList :: FilePath -> IO [FilePath]+followList =+ listAll'' True (const True)+++{- $custom+ All /File Type Based Listing/ functions are based on top of this interface.+ This part of API exposes exposes access for writing custom filtering functions.++ All paths are tested for filter functions so unreadble files won't appear in the result list:++ > PosixRecursive.listCustom PosixRecursive.defConf "i-dont-exist"+ > >>> []++ It's not possible to turn of this behaviour because this functions must get the 'FileStatus'+ type which requires reading each entry.+-}+++-- | Configuration arguments for 'listCustom'.+data Conf = Conf+ { -- | Filter paths algorithm should recurse into+ filterPath :: !(FilePath -> Bool)+ , -- | Test if file should be included in returned List+ includeFile :: !(FileStatus -> FilePath -> IO Bool)+ , -- | Follow symbolic links+ followSymlinks :: !Bool+ }+++{- | Default 'Conf'iguration.++> listCustom defConf == listAccessible++* Recurses into all Paths+* Includes all file types+* Does __not__ follow symlinks+-}+defConf :: Conf+defConf =+ Conf+ { filterPath = const True+ , includeFile = \_ _ -> pure True+ , followSymlinks = False+ }+++{-# INLINE listAccessible' #-}+listAccessible' :: Conf -> FilePath -> IO [FilePath]+listAccessible' Conf{..} path =+ handle (\(_ :: IOError) -> pure []) $ do+ file <- getFileStatus path+ next <-+ if Posix.isDirectory file+ then do+ content <- listDir filterPath path+ unsafeInterleaveIO $ foldMapA (listAccessible' Conf{..}) content+ else pure []++ include <- includeFile file path+ if include+ then pure $ path : next+ else pure next+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++-- | Recursively list files using custom filters.+listCustom :: Conf -> FilePath -> IO [FilePath]+listCustom =+ listAccessible'+++{- | Like 'list' but automatically filters out inacessible files like broken symlins+or unreadable files and directories.+-}+listAccessible :: FilePath -> IO [FilePath]+listAccessible =+ listAccessible' defConf+++{- $type_based+ Functions for listing specific file type. Reading the file type requires+ ability to read the file.++ __These function do not return broken symlinks and inacessible files__++ Include test is applied even for the root entry (path past in as an argument).+ This means that non existing paths are filtered.++ > PosixRecursive.listDirectories "i-dont-exist"+ > >>> []+-}+++-- | List sub directories of given directory.+listDirectories :: FilePath -> IO [FilePath]+listDirectories =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isDirectory file}+++-- | Lists only files (while recursing into directories still).+listRegularFiles :: FilePath -> IO [FilePath]+listRegularFiles =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isRegularFile file}+++-- | Lists only symbolic links (while recursing into directories still).+listSymbolicLinks :: FilePath -> IO [FilePath]+listSymbolicLinks =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isSymbolicLink file}
+ System/Posix/Recursive/ByteString.hs view
@@ -0,0 +1,277 @@+{- |+ Module : System.Posix.Recursive.ByteString-- Copyright : (c) Marek Fajkus+ License : BSD3++ Maintainer : marek.faj@gmail.com++ All modules profided by @unix-recursive@ expose similar API.+ Make sure you're using the module wich best fits your needs based on:+ - Working with 'RawFilePath' (faster and more packed) or 'FilePath' (slower but easier to work with safely)+ - Exception free (Default) or @Unsafe@ variants of functions++ = Usage++ This module is designed to be imported as @qualified@:++ > import qualified System.Posix.Recursive.ByteString as PosixRecursive++ __Results__++ All functions return will return root path (the one they accept in argument) as a first item in the list:++ > head <$> PosixRecursive.list "System"+ > >>> "System"++ Other than that, this library __provides no guarantees about the order in which files appear in the resulting list__+ to make it possible to change the underlaying strategy in future versions.++ __Laziness__++ All IO operations are __guaranteed to be lazy and have constanct space characteristic__.+ Only the IO required by lazy computation will be performed so for instance running code like:++ > take 20 <$> PosixRecursive.listDirectories "/"++ Will perform only minimal ammount of IO needed to collect 20 directories on a root partition+-}+module System.Posix.Recursive.ByteString (+ -- * Basic Listing+ -- $basic_listing+ list,+ followList,+ listMatching,+ followListMatching,++ -- * File Type Based Listing+ -- $type_based+ listAccessible,+ listDirectories,+ listRegularFiles,+ listSymbolicLinks,++ -- * Custom Listing+ -- $custom+ Conf (..),+ defConf,+ listCustom,+) where++import Control.Exception (bracket, handle)+import Data.Foldable (fold)+import System.IO.Unsafe (unsafeInterleaveIO)+import System.Posix.Files (FileStatus)++import qualified Data.ByteString as BS+import System.Posix.ByteString.FilePath (RawFilePath)++import qualified System.Posix.Directory.ByteString as Posix+import qualified System.Posix.Files.ByteString as Posix+++-- Helpers++foldMapA :: (Monoid b, Traversable t, Applicative f) => (a -> f b) -> t a -> f b+foldMapA = (fmap fold .) . traverse+++{-# INLINE listDir #-}+listDir :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listDir predicate path =+ bracket+ (Posix.openDirStream path)+ Posix.closeDirStream+ (go [])+ where+ go :: [RawFilePath] -> Posix.DirStream -> IO [RawFilePath]+ go acc dirp = do+ e <- Posix.readDirStream dirp+ if BS.null e+ then pure acc+ else+ if e /= "." && e /= ".."+ then+ let fullPath = path <> "/" <> e+ in if predicate fullPath+ then go (fullPath : acc) dirp+ else go acc dirp+ else go acc dirp+{- $basic_listing+ Functions for listing contents of directory recursively.+ These functions list all the content they encounter while traversing+ the file system tree including directories, files, symlinks, broken symlinks.++ __Directories (and files) which process can't open due to permissions are listed but not recursed into.__++ Functions from this section is gurantee to always return the root path as a first element even+ if this path does not exist.++ > PosixRecursive.list "i-dont-exist"+ > >>> ["i-dont-exist"]++ In these cases the root path is considered the same way as symlink+ to non existing location.+-}+++{-# INLINE listAll' #-}+listAll' :: Bool -> (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listAll' followSymlinks predicate path =+ handle (\(_ :: IOError) -> pure []) $ do+ file <- getFileStatus path+ if Posix.isDirectory file+ then do+ content <- listDir predicate path++ next <- unsafeInterleaveIO $ foldMapA (listAll' followSymlinks predicate) content+ pure $ content ++ next+ else pure []+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++{-# INLINE listAll'' #-}+listAll'' :: Bool -> (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listAll'' followSymlinks predicate path =+ (path :) <$> listAll' followSymlinks predicate path+++-- | Like 'list' but uses provided function to test in which 'RawFilePath' to recurse into.+listMatching :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listMatching =+ listAll'' False+++-- | Like 'followList' but uses provided function to test in which 'RawFilePath' to recurse into.+followListMatching :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+followListMatching =+ listAll'' True+++{- | List all files, directories & symlinks recursively.+ Symlinks are not followed. See 'followList'.+-}+list :: RawFilePath -> IO [RawFilePath]+list =+ listAll'' False (const True)+++{- | List all files, directories & symlinks recursively.+ Unlike 'list', symlinks are followed recursively as well.+-}+followList :: RawFilePath -> IO [RawFilePath]+followList =+ listAll'' True (const True)+++{- $custom+ All /File Type Based Listing/ functions are based on top of this interface.+ This part of API exposes exposes access for writing custom filtering functions.++ All paths are tested for filter functions so unreadble files won't appear in the result list:++ > PosixRecursive.listCustom PosixRecursive.defConf "i-dont-exist"+ > >>> []++ It's not possible to turn of this behaviour because this functions must get the 'FileStatus'+ type which requires reading each entry.+-}+++-- | Configuration arguments for 'listCustom'.+data Conf = Conf+ { -- | Filter paths algorithm should recurse into+ filterPath :: !(RawFilePath -> Bool)+ , -- | Test if file should be included in returned List+ includeFile :: !(FileStatus -> RawFilePath -> IO Bool)+ , -- | Follow symbolic links+ followSymlinks :: !Bool+ }+++{- | Default 'Conf'iguration.++> listCustom defConf == listAccessible++* Recurses into all Paths+* Includes all file types+* Does __not__ follow symlinks+-}+defConf :: Conf+defConf =+ Conf+ { filterPath = const True+ , includeFile = \_ _ -> pure True+ , followSymlinks = False+ }+++{-# INLINE listAccessible' #-}+listAccessible' :: Conf -> RawFilePath -> IO [RawFilePath]+listAccessible' Conf{..} path =+ handle (\(_ :: IOError) -> pure []) $ do+ file <- getFileStatus path+ next <-+ if Posix.isDirectory file+ then do+ content <- listDir filterPath path+ unsafeInterleaveIO $ foldMapA (listAccessible' Conf{..}) content+ else pure []++ include <- includeFile file path+ if include+ then pure $ path : next+ else pure next+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++-- | Recursively list files using custom filters.+listCustom :: Conf -> RawFilePath -> IO [RawFilePath]+listCustom =+ listAccessible'+++{- | Like 'list' but automatically filters out inacessible files like broken symlins+or unreadable files and directories.+-}+listAccessible :: RawFilePath -> IO [RawFilePath]+listAccessible =+ listAccessible' defConf+++{- $type_based+ Functions for listing specific file type. Reading the file type requires+ ability to read the file.++ __These function do not return broken symlinks and inacessible files__++ Include test is applied even for the root entry (path past in as an argument).+ This means that non existing paths are filtered.++ > PosixRecursive.listDirectories "i-dont-exist"+ > >>> []+-}+++-- | List sub directories of given directory.+listDirectories :: RawFilePath -> IO [RawFilePath]+listDirectories =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isDirectory file}+++-- | Lists only files (while recursing into directories still).+listRegularFiles :: RawFilePath -> IO [RawFilePath]+listRegularFiles =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isRegularFile file}+++-- | Lists only symbolic links (while recursing into directories still).+listSymbolicLinks :: RawFilePath -> IO [RawFilePath]+listSymbolicLinks =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isSymbolicLink file}
+ System/Posix/Recursive/ByteString/Unsafe.hs view
@@ -0,0 +1,242 @@+{- |+ Module : System.Posix.Recursive.ByteString.Unsafe-- Copyright : (c) Marek Fajkus+ License : BSD3++ Maintainer : marek.faj@gmail.com++ All modules profided by @unix-recursive@ expose similar API.+ Make sure you're using the module wich best fits your needs based on:+ - Working with 'RawFilePath' (faster and more packed) or 'FilePath' (slower but easier to work with safely)+ - Exception free (Default) or @Unsafe@ variants of functions++ = Usage++ This module is designed to be imported as @qualified@:++ > import qualified System.Posix.Recursive.ByteString.Unsafe as PosixRecursive++ __Results__++ All functions return will return root path (the one they accept in argument) as a first item in the list:++ > head <$> PosixRecursive.list "System"+ > >>> "System"++ Other than that, this library __provides no guarantees about the order in which files appear in the resulting list__+ to make it possible to change the underlaying strategy in future versions.++ __Laziness__++ All IO operations are __guaranteed to be lazy and have constanct space characteristic__.+ Only the IO required by lazy computation will be performed so for instance running code like:++ > take 20 <$> PosixRecursive.listDirectories "/"++ Will perform only minimal ammount of IO needed to collect 20 directories on a root partition+-}+module System.Posix.Recursive.ByteString.Unsafe (+ -- * Basic Listing+ -- $basic_listing+ list,+ followList,+ listMatching,+ followListMatching,++ -- * File Type Based Listing+ -- $type_based+ listDirectories,+ listRegularFiles,+ listSymbolicLinks,++ -- * Custom Listing+ -- $custom+ Conf (..),+ defConf,+ listCustom,+) where++import Control.Exception (bracket)+import Data.Foldable (fold)+import System.IO.Unsafe (unsafeInterleaveIO)++import qualified Data.ByteString as BS+import System.Posix.ByteString.FilePath (RawFilePath)++import qualified System.Posix.Directory.ByteString as Posix+import qualified System.Posix.Files.ByteString as Posix++import System.Posix.Recursive.ByteString (Conf (..), defConf)+++-- Helpers++foldMapA :: (Monoid b, Traversable t, Applicative f) => (a -> f b) -> t a -> f b+foldMapA = (fmap fold .) . traverse+++{-# INLINE listDir #-}+listDir :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listDir predicate path =+ bracket+ (Posix.openDirStream path)+ Posix.closeDirStream+ (go [])+ where+ go :: [RawFilePath] -> Posix.DirStream -> IO [RawFilePath]+ go acc dirp = do+ e <- Posix.readDirStream dirp+ if BS.null e+ then pure acc+ else+ if e /= "." && e /= ".."+ then+ let fullPath = path <> "/" <> e+ in if predicate fullPath+ then go (fullPath : acc) dirp+ else go acc dirp+ else go acc dirp+{- $basic_listing+ Functions for listing contents of directory recursively.+ These functions list all the content they encounter while traversing+ the file system tree including directories, files, symlinks, broken symlinks.++ __Functions from this module will throw 'IOError' if it can't open the directory__+ (i.e. becacuse permission error or other process removing the given path).++ Functions from this section is gurantee to always return the root path as a first element even+ if this path does not exist.++ > PosixRecursive.list "i-dont-exist"+ > >>> ["i-dont-exist"]++ In these cases the root path is considered the same way as symlink+ to non existing location.+-}+++{-# INLINE listAll' #-}+listAll' :: Bool -> (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listAll' followSymlinks predicate path =+ do+ file <- getFileStatus path+ if Posix.isDirectory file+ then do+ content <- listDir predicate path++ next <- unsafeInterleaveIO $ foldMapA (listAll' followSymlinks predicate) content+ pure $ content ++ next+ else pure []+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++{-# INLINE listAll'' #-}+listAll'' :: Bool -> (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listAll'' followSymlinks predicate path =+ (path :) <$> listAll' followSymlinks predicate path+++-- | Like 'list' but uses provided function to test in which 'RawFilePath' to recurse into.+listMatching :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+listMatching =+ listAll'' False+++-- | Like 'followList' but uses provided function to test in which 'RawFilePath' to recurse into.+followListMatching :: (RawFilePath -> Bool) -> RawFilePath -> IO [RawFilePath]+followListMatching =+ listAll'' True+++{- | List all files, directories & symlinks recursively.+ Symlinks are not followed. See 'followList'.+-}+list :: RawFilePath -> IO [RawFilePath]+list =+ listAll'' False (const True)+++{- | List all files, directories & symlinks recursively.+ Unlike 'list', symlinks are followed recursively as well.+-}+followList :: RawFilePath -> IO [RawFilePath]+followList =+ listAll'' True (const True)+++{- $custom+ All /File Type Based Listing/ functions are based on top of this interface.+ This part of API exposes exposes access for writing custom filtering functions.++ All paths are tested for filter functions so unreadble files won't appear in the result list:++ > PosixRecursive.listCustom PosixRecursive.defConf "i-dont-exist"+ > >>> []++ It's not possible to turn of this behaviour because this functions must get the 'FileStatus'+ type which requires reading each entry.+-}+++{-# INLINE listAccessible' #-}+listAccessible' :: Conf -> RawFilePath -> IO [RawFilePath]+listAccessible' Conf{..} path =+ do+ file <- getFileStatus path+ next <-+ if Posix.isDirectory file+ then do+ content <- listDir filterPath path+ unsafeInterleaveIO $ foldMapA (listAccessible' Conf{..}) content+ else pure []++ include <- includeFile file path+ if include+ then pure $ path : next+ else pure next+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++-- | Recursively list files using custom filters.+listCustom :: Conf -> RawFilePath -> IO [RawFilePath]+listCustom =+ listAccessible'+++{- $type_based+ Functions for listing specific file type. Reading the file type requires+ ability to read the file.++ __These functions will throw 'IOError' when tring to open unreadable file.__++ Include test is applied even for the root entry (path past in as an argument).+ This means that non existing paths are filtered.++ > PosixRecursive.listDirectories "i-dont-exist"+ > >>> []+-}+++-- | List sub directories of given directory.+listDirectories :: RawFilePath -> IO [RawFilePath]+listDirectories =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isDirectory file}+++-- | Lists only files (while recursing into directories still).+listRegularFiles :: RawFilePath -> IO [RawFilePath]+listRegularFiles =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isRegularFile file}+++-- | Lists only symbolic links (while recursing into directories still).+listSymbolicLinks :: RawFilePath -> IO [RawFilePath]+listSymbolicLinks =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isSymbolicLink file}
+ System/Posix/Recursive/Unsafe.hs view
@@ -0,0 +1,238 @@+{- |+ Module : System.Posix.Recursive.Unsafe-- Copyright : (c) Marek Fajkus+ License : BSD3++ Maintainer : marek.faj@gmail.com++ All modules profided by @unix-recursive@ expose similar API.+ Make sure you're using the module wich best fits your needs based on:+ - Working with 'RawFilePath' (faster and more packed) or 'FilePath' (slower but easier to work with safely)+ - Exception free (Default) or @Unsafe@ variants of functions++ = Usage++ This module is designed to be imported as @qualified@:++ > import qualified System.Posix.Recursive.Unsafe as PosixRecursive++ __Results__++ All functions return will return root path (the one they accept in argument) as a first item in the list:++ > head <$> PosixRecursive.list "System"+ > >>> "System"++ Other than that, this library __provides no guarantees about the order in which files appear in the resulting list__+ to make it possible to change the underlaying strategy in future versions.++ __Laziness__++ All IO operations are __guaranteed to be lazy and have constanct space characteristic__.+ Only the IO required by lazy computation will be performed so for instance running code like:++ > take 20 <$> PosixRecursive.listDirectories "/"++ Will perform only minimal ammount of IO needed to collect 20 directories on a root partition+-}+module System.Posix.Recursive.Unsafe (+ -- * Basic Listing+ -- $basic_listing+ list,+ followList,+ listMatching,+ followListMatching,++ -- * File Type Based Listing+ -- $type_based+ listDirectories,+ listRegularFiles,+ listSymbolicLinks,++ -- * Custom Listing+ -- $custom+ Conf (..),+ defConf,+ listCustom,+) where++import Control.Exception (bracket)+import Data.Foldable (fold)+import System.IO.Unsafe (unsafeInterleaveIO)++import qualified System.Posix.Directory as Posix+import qualified System.Posix.Files as Posix+import System.Posix.Recursive (Conf (..), defConf)+++-- Helpers++foldMapA :: (Monoid b, Traversable t, Applicative f) => (a -> f b) -> t a -> f b+foldMapA = (fmap fold .) . traverse+++{-# INLINE listDir #-}+listDir :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+listDir predicate path =+ bracket+ (Posix.openDirStream path)+ Posix.closeDirStream+ (go [])+ where+ go :: [FilePath] -> Posix.DirStream -> IO [FilePath]+ go acc dirp = do+ e <- Posix.readDirStream dirp+ if null e+ then pure acc+ else+ if e /= "." && e /= ".."+ then+ let fullPath = path <> "/" <> e+ in if predicate fullPath+ then go (fullPath : acc) dirp+ else go acc dirp+ else go acc dirp+{- $basic_listing+ Functions for listing contents of directory recursively.+ These functions list all the content they encounter while traversing+ the file system tree including directories, files, symlinks, broken symlinks.++ __Functions from this module will throw 'IOError' if it can't open the directory__+ (i.e. becacuse permission error or other process removing the given path).++ Functions from this section is gurantee to always return the root path as a first element even+ if this path does not exist.++ > PosixRecursive.list "i-dont-exist"+ > >>> ["i-dont-exist"]++ In these cases the root path is considered the same way as symlink+ to non existing location.+-}+++{-# INLINE listAll' #-}+listAll' :: Bool -> (FilePath -> Bool) -> FilePath -> IO [FilePath]+listAll' followSymlinks predicate path =+ do+ file <- getFileStatus path+ if Posix.isDirectory file+ then do+ content <- listDir predicate path++ next <- unsafeInterleaveIO $ foldMapA (listAll' followSymlinks predicate) content+ pure $ content ++ next+ else pure []+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++{-# INLINE listAll'' #-}+listAll'' :: Bool -> (FilePath -> Bool) -> FilePath -> IO [FilePath]+listAll'' followSymlinks predicate path =+ (path :) <$> listAll' followSymlinks predicate path+++-- | Like 'list' but uses provided function to test in which 'FilePath' to recurse into.+listMatching :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+listMatching =+ listAll'' False+++-- | Like 'followList' but uses provided function to test in which 'FilePath' to recurse into.+followListMatching :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+followListMatching =+ listAll'' True+++{- | List all files, directories & symlinks recursively.+ Symlinks are not followed. See 'followList'.+-}+list :: FilePath -> IO [FilePath]+list =+ listAll'' False (const True)+++{- | List all files, directories & symlinks recursively.+ Unlike 'list', symlinks are followed recursively as well.+-}+followList :: FilePath -> IO [FilePath]+followList =+ listAll'' True (const True)+++{- $custom+ All /File Type Based Listing/ functions are based on top of this interface.+ This part of API exposes exposes access for writing custom filtering functions.++ All paths are tested for filter functions so unreadble files won't appear in the result list:++ > PosixRecursive.listCustom PosixRecursive.defConf "i-dont-exist"+ > >>> []++ It's not possible to turn of this behaviour because this functions must get the 'FileStatus'+ type which requires reading each entry.+-}+++{-# INLINE listAccessible' #-}+listAccessible' :: Conf -> FilePath -> IO [FilePath]+listAccessible' Conf{..} path =+ do+ file <- getFileStatus path+ next <-+ if Posix.isDirectory file+ then do+ content <- listDir filterPath path+ unsafeInterleaveIO $ foldMapA (listAccessible' Conf{..}) content+ else pure []++ include <- includeFile file path+ if include+ then pure $ path : next+ else pure next+ where+ {-# INLINE getFileStatus #-}+ getFileStatus+ | followSymlinks = Posix.getFileStatus+ | otherwise = Posix.getSymbolicLinkStatus+++-- | Recursively list files using custom filters.+listCustom :: Conf -> FilePath -> IO [FilePath]+listCustom =+ listAccessible'+++{- $type_based+ Functions for listing specific file type. Reading the file type requires+ ability to read the file.++ __These functions will throw 'IOError' when tring to open unreadable file.__++ Include test is applied even for the root entry (path past in as an argument).+ This means that non existing paths are filtered.++ > PosixRecursive.listDirectories "i-dont-exist"+ > >>> []+-}+++-- | List sub directories of given directory.+listDirectories :: FilePath -> IO [FilePath]+listDirectories =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isDirectory file}+++-- | Lists only files (while recursing into directories still).+listRegularFiles :: FilePath -> IO [FilePath]+listRegularFiles =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isRegularFile file}+++-- | Lists only symbolic links (while recursing into directories still).+listSymbolicLinks :: FilePath -> IO [FilePath]+listSymbolicLinks =+ listAccessible' defConf{includeFile = \file _ -> pure $ Posix.isSymbolicLink file}
+ bench/Bench.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}++module Main (main) where++import Criterion.Main++import System.Directory.Recursive as DirTraverse+import qualified System.Posix.Recursive as String+import qualified System.Posix.Recursive.ByteString as ByteString+++main :: IO ()+main =+ defaultMain+ [ bgroup+ "compare different api groups"+ [ bgroup+ "ByteString"+ [ bench "filtering base on path" $ nfIO $ ByteString.list "."+ , bench "filter by path and file status" $ nfIO $ ByteString.listAccessible "."+ ]+ , bgroup+ "String"+ [ bench "filtering base on path" $ nfIO $ String.list "."+ , bench "filter by path and file status" $ nfIO $ String.listAccessible "."+ ]+ ]+ , bgroup+ "compare with dir traverse"+ [ bgroup+ "Evaluate whole list"+ [ bench "unix-recursive String" $ nfIO $ String.followList "."+ , bench "unix-recursive ByteString" $ nfIO $ ByteString.followList "."+ , bench "dir-traverse" $ nfIO $ DirTraverse.getDirRecursive "."+ ]+ , bgroup+ "Test laziness"+ [ bench "unix-recursive String" $ nfIO (head . tail <$> String.followList ".")+ , bench "unix-recursive ByteString" $ nfIO (head . tail <$> ByteString.followList ".")+ , bench "dir-traverse" $ nfIO ((head . tail) <$> DirTraverse.getDirRecursive ".")+ ]+ ]+ ]
+ bin/dir-traverse.hs view
@@ -0,0 +1,11 @@+module Main where++import System.Directory.Recursive as Lib+import System.Environment (getArgs)+++main :: IO ()+main = do+ [path] <- getArgs+ all <- Lib.getDirRecursive path+ print $ length all
+ bin/dirstream.hs view
@@ -0,0 +1,32 @@+module Main where++import Control.Applicative+import Data.DirStream+import Data.String+import qualified Filesystem.Path as F+import Pipes+import Pipes.Prelude hiding (print)+import Pipes.Safe+import System.Environment+import Prelude hiding (length)+++dirstreamGet :: F.FilePath -> ListT (SafeT IO) F.FilePath+dirstreamGet path = do+ child <- childOf path+ isDir <- liftIO $ isDirectory child+ if isDir+ then pure child <|> descendentOf child+ else pure child+++main :: IO ()+main = do+ [path] <- getArgs++ count <-+ runSafeT $+ runEffect $+ (length (every (descendentOf (fromString path))))++ print count
+ bin/unix-recursive-bytestring.hs view
@@ -0,0 +1,13 @@+module Main where++import qualified Data.ByteString.UTF8 as BS+import System.Environment (getArgs)+import qualified System.Posix.Recursive.ByteString as Lib+++main :: IO ()+main = do+ [path'] <- getArgs+ let path = BS.fromString path'+ all <- Lib.followList path+ print $ length all
+ bin/unix-recursive-string.hs view
@@ -0,0 +1,11 @@+module Main where++import System.Environment (getArgs)+import qualified System.Posix.Recursive as Lib+++main :: IO ()+main = do+ [path] <- getArgs+ all <- Lib.followList path+ print $ length all
+ test/Spec.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++import Data.List (sort)++import Spec.Listing+import Test.Hspec --(anyException, context, hspec, it, shouldThrow)++import System.IO.Error (isPermissionError)+import System.Posix.ByteString.FilePath (RawFilePath)++import qualified Data.ByteString as BS+import qualified System.Posix.Recursive as String+import qualified System.Posix.Recursive.ByteString as ByteString+import qualified System.Posix.Recursive.ByteString.Unsafe as UnsafeBS+import qualified System.Posix.Recursive.Unsafe as Unsafe+++-- Helper+isSuffixOf :: String -> String -> Bool+isSuffixOf needle haystack+ | needleLen > hayLen = False+ | otherwise = needle == drop (hayLen - needleLen) haystack+ where+ needleLen = length needle+ hayLen = length haystack+++instance DirectoryListing FilePath where+ list = String.list+ followList = String.followList+ listMatching = String.listMatching+ followListMatching = String.followListMatching+ listAccessible = String.listAccessible+ listDirectories = String.listDirectories+ listRegularFiles = String.listRegularFiles+ listSymbolicLinks = String.listSymbolicLinks+ listCustom filterPath includeFile =+ String.listCustom+ String.defConf+ { String.filterPath = filterPath+ , String.includeFile = includeFile+ }+++instance DirectoryListing RawFilePath where+ list = ByteString.list+ followList = ByteString.followList+ listMatching = ByteString.listMatching+ followListMatching = ByteString.followListMatching+ listDirectories = ByteString.listDirectories+ listAccessible = ByteString.listAccessible+ listRegularFiles = ByteString.listRegularFiles+ listSymbolicLinks = ByteString.listSymbolicLinks+ listCustom filterPath includeFile =+ ByteString.listCustom+ ByteString.defConf+ { ByteString.filterPath = filterPath+ , ByteString.includeFile = includeFile+ }+++main :: IO ()+main =+ hspec $ do+ context "System.Posix.Recursive" $ spec isSuffixOf++ context "System.Posix.Recursive.Unsafe" $+ it "should throw" $ do+ let exp = do+ res <- Unsafe.list "test/workdir"+ print $ length res+ exp `shouldThrow` isPermissionError++ context "System.Posix.Recursive.ByteString" $ spec BS.isSuffixOf++ context "System.Posix.Recursive.ByteString.Unsafe" $+ it "should throw" $ do+ let exp = do+ res <- UnsafeBS.list "test/workdir"+ print $ length res+ exp `shouldThrow` isPermissionError
+ test/Spec/Listing.hs view
@@ -0,0 +1,242 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Spec.Listing (spec, DirectoryListing (..)) where++import Data.List (sort)+import Data.String+import System.Posix.Files (FileStatus)+import qualified System.Posix.Files as Posix+import Test.Hspec+++class (Show a, Ord a, IsString a) => DirectoryListing a where+ list :: a -> IO [a]+ followList :: a -> IO [a]+ listMatching :: (a -> Bool) -> a -> IO [a]+ followListMatching :: (a -> Bool) -> a -> IO [a]+ listAccessible :: a -> IO [a]+ listDirectories :: a -> IO [a]+ listRegularFiles :: a -> IO [a]+ listSymbolicLinks :: a -> IO [a]+ listCustom :: (a -> Bool) -> (FileStatus -> a -> IO Bool) -> a -> IO [a]+++spec :: forall a. DirectoryListing a => (a -> a -> Bool) -> Spec+spec filter = do+ context "Not following symlinks" $ do+ describe "listEverything" $ do+ it "returns expected list of everything in workdir" $ do+ res :: [a] <- list "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/file3"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/dir3"+ , "test/workdir/dir3/sub1"+ , "test/workdir/dir3/sub1/file1"+ , "test/workdir/dir3/sub2"+ , "test/workdir/dir3/sub2/broken-sym1"+ , "test/workdir/file1"+ , "test/workdir/file2"+ , "test/workdir/only-roots-dir1"+ ]+ res `shouldMatchList` expected++ describe "listAll" $ do+ it "returns list excluding files & dirs ending with `3`" $ do+ res :: [a] <- listMatching (not . filter "3") "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/file1"+ , "test/workdir/file2"+ , "test/workdir/only-roots-dir1"+ ]+ res `shouldMatchList` expected++ describe "listEverythingAccessible" $ do+ it "returns everything that is accessible in workdir" $ do+ res :: [a] <- listAccessible "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/file3"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/dir3"+ , "test/workdir/dir3/sub1"+ , "test/workdir/dir3/sub1/file1"+ , "test/workdir/dir3/sub2"+ , "test/workdir/dir3/sub2/broken-sym1"+ , "test/workdir/file1"+ , "test/workdir/file2"+ ]+ res `shouldMatchList` expected++ describe "listDirectories" $ do+ it "returns only directories" $ do+ res :: [a] <- listDirectories "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir2"+ , "test/workdir/dir3"+ , "test/workdir/dir3/sub1"+ , "test/workdir/dir3/sub2"+ ]+ res `shouldMatchList` expected++ describe "listFiles" $ do+ it "returns only files" $ do+ res :: [a] <- listRegularFiles "test/workdir"+ let expected =+ [ "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/file3"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir3/sub1/file1"+ , "test/workdir/file1"+ , "test/workdir/file2"+ ]+ res `shouldMatchList` expected++ describe "listSymbolicLinks" $ do+ it "returns only symlinks" $ do+ res :: [a] <- listSymbolicLinks "test/workdir"+ let expected =+ [ "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/dir3/sub2/broken-sym1"+ ]+ res `shouldMatchList` expected++ describe "listCustom" $ do+ it "return everything except for symlinks and not within dir1" $ do+ res :: [a] <-+ listCustom+ (not . filter "dir1")+ (\file _ -> pure $ not $ Posix.isSymbolicLink file)+ "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir3"+ , "test/workdir/dir3/sub1"+ , "test/workdir/dir3/sub1/file1"+ , "test/workdir/dir3/sub2"+ , "test/workdir/file1"+ , "test/workdir/file2"+ ]+ res `shouldMatchList` expected++ context "Following symlinks" $ do+ describe "followListEverything" $ do+ it "returns expected list of everything in workdir" $ do+ res :: [a] <- followList "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/file3"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/dir2/sym2/sub1"+ , "test/workdir/dir2/sym2/sub1/file1"+ , "test/workdir/dir2/sym2/sub2"+ , "test/workdir/dir2/sym2/sub2/broken-sym1"+ , "test/workdir/dir3"+ , "test/workdir/dir3/sub1"+ , "test/workdir/dir3/sub1/file1"+ , "test/workdir/dir3/sub2"+ , "test/workdir/dir3/sub2/broken-sym1"+ , "test/workdir/file1"+ , "test/workdir/file2"+ , "test/workdir/only-roots-dir1"+ ]+ res `shouldMatchList` expected++ describe "followListAll" $ do+ it "returns list excluding files & dirs ending with `3`" $ do+ res <- followListMatching (not . filter "3") "test/workdir"+ let expected =+ [ "test/workdir"+ , "test/workdir/dir1"+ , "test/workdir/dir1/roots-dir1"+ , "test/workdir/dir1/roots-dir1/roots-file1"+ , "test/workdir/dir1/sub1"+ , "test/workdir/dir1/sub1/file1"+ , "test/workdir/dir1/sub1/file2"+ , "test/workdir/dir1/sub2"+ , "test/workdir/dir1/sub2/file1"+ , "test/workdir/dir1/sub2/file2"+ , "test/workdir/dir1/sub2/roots-file1"+ , "test/workdir/dir2"+ , "test/workdir/dir2/file1"+ , "test/workdir/dir2/sym1"+ , "test/workdir/dir2/sym2"+ , "test/workdir/dir2/sym2/sub1"+ , "test/workdir/dir2/sym2/sub1/file1"+ , "test/workdir/dir2/sym2/sub2"+ , "test/workdir/dir2/sym2/sub2/broken-sym1"+ , "test/workdir/file1"+ , "test/workdir/file2"+ , "test/workdir/only-roots-dir1"+ ]+ res `shouldMatchList` expected
+ unix-recursive.cabal view
@@ -0,0 +1,118 @@+cabal-version: >=1.12++name: unix-recursive+version: 0.1.0.0+synopsis: Fast and flexible primitives for recursive file system IO on Posix systems+description:+ Blazingly fast functions for recursive file system operations.+ Utilizing lazy IO for constant space & computation efficiant bindigns to Posix dirstream.h api.++bug-reports: https://github.com/turboMaCk/unix-recursive/issues+license: BSD3+license-file: LICENSE+author: Marek Fajkus <marek.faj@gmail.com>+maintainer: Marek Fajkus <marek.faj@gmail.com>+copyright: 2021 Marek Fajkus+stability: experimental+category: System, Filesystem+build-type: Simple+extra-source-files: README.md+ , ChangeLog.md+homepage: https://github.com/turboMaCk/unix-recursive++source-repository head+ type: git+ location: https://github.com/turboMaCk/unix-recursive++Flag bin+ default: False+ manual: True++library+ exposed-modules: System.Posix.Recursive+ , System.Posix.Recursive.Unsafe+ , System.Posix.Recursive.ByteString+ , System.Posix.Recursive.ByteString.Unsafe+ other-modules: Paths_unix_recursive+ hs-source-dirs: .+ build-depends: base >=4.7 && <5+ , unix+ , bytestring+ default-language: Haskell2010+ default-extensions: OverloadedStrings+ , RecordWildCards+ , ScopedTypeVariables++executable dir-traverse-bin+ main-is: dir-traverse.hs+ hs-source-dirs: bin+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends: base >=4.7 && <5+ , dir-traverse == 0.2.3.0+ default-language: Haskell2010+ if !flag(bin)+ buildable: False++executable dirstream-bin+ main-is: dirstream.hs+ hs-source-dirs: bin+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends: base >=4.7 && <5+ , dirstream+ , pipes+ , pipes-safe+ , system-filepath+ , directory+ default-language: Haskell2010+ if !flag(bin)+ buildable: False++executable unix-recursive-string-bin+ main-is: unix-recursive-string.hs+ other-modules: Paths_unix_recursive+ hs-source-dirs: bin+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends: base >=4.7 && <5+ , unix-recursive+ , unix+ default-language: Haskell2010+ if !flag(bin)+ buildable: False++executable unix-recursive-bytestring-bin+ main-is: unix-recursive-bytestring.hs+ other-modules: Paths_unix_recursive+ hs-source-dirs: bin+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends: base >=4.7 && <5+ , unix-recursive+ , unix+ , utf8-string+ default-language: Haskell2010+ if !flag(bin)+ buildable: False++benchmark unix-recursive-bench+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ hs-source-dirs: bench+ default-language: Haskell2010+ ghc-options: -Wall+ build-depends: base >=4.7 && <5+ , unix-recursive+ , criterion+ , dir-traverse == 0.2.3.0++test-suite unix-recursive-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules: Paths_unix_recursive+ , Spec.Listing+ hs-source-dirs: test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall+ build-depends: base >=4.7 && <5+ , unix-recursive+ , hspec+ , bytestring+ , unix+ default-language: Haskell2010