filemanip/System/FilePath/Find.hs

380 lines
11 KiB
Haskell
Raw Normal View History

2007-05-01 01:49:58 -04:00
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- |
-- Module: System.FilePath.Find
-- Copyright: Bryan O'Sullivan
-- License: LGPL
-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
-- Stability: unstable
-- Portability: Unix-like systems (requires newtype deriving)
2007-04-30 02:30:30 -04:00
module System.FilePath.Find (
FileInfo(..)
, FileType(..)
2007-04-30 02:30:30 -04:00
, FindClause
, FilterPredicate
, RecursionPredicate
, find
, findWithHandler
, fold
, foldWithHandler
, filePath
, fileStatus
, depth
, fileInfo
2007-04-30 02:30:30 -04:00
, always
, extension
, directory
, fileName
, fileType
, deviceID
, fileID
, fileOwner
, fileGroup
, fileSize
, linkCount
, specialDeviceID
, fileMode
, filePerms
, anyPerms
, accessTime
, modificationTime
, statusChangeTime
2007-04-30 18:53:13 -04:00
, readLink
, followStatus
2007-04-30 03:08:57 -04:00
2007-04-30 03:29:32 -04:00
, (~~?)
2007-04-30 02:30:30 -04:00
, (/~?)
, (==?)
, (/=?)
, (>?)
, (<?)
, (>=?)
, (<=?)
, (&&?)
, (||?)
2007-04-30 03:08:57 -04:00
, (.&.?)
2007-04-30 02:30:30 -04:00
) where
import Control.Monad (foldM, forM, liftM, liftM2)
import Control.Monad.Fix (MonadFix)
import Control.Monad.State (State(..), evalState)
import Data.Bits (Bits, (.&.))
import Data.List (sort)
import System.Directory (getDirectoryContents)
2007-04-30 18:53:13 -04:00
import System.FilePath ((</>), replaceFileName, takeDirectory, takeExtension,
takeFileName)
2007-04-30 13:06:48 -04:00
import System.FilePath.GlobPattern (GlobPattern, (~~), (/~))
2007-04-30 02:30:30 -04:00
import System.IO (hPutStrLn, stderr)
2007-04-30 03:29:32 -04:00
import System.IO.Unsafe (unsafeInterleaveIO, unsafePerformIO)
import qualified Control.Exception as E
2007-04-30 02:30:30 -04:00
import qualified System.Posix.Files as F
import qualified System.Posix.Types as T
2007-05-01 02:24:44 -04:00
-- | Information collected during the traversal of a directory.
data FileInfo = FileInfo
{
2007-05-01 02:24:44 -04:00
infoPath :: FilePath -- ^ file path
, infoDepth :: Int -- ^ current recursion depth
, infoStatus :: F.FileStatus -- ^ status of file
2007-04-30 18:53:13 -04:00
} deriving (Eq)
instance Eq F.FileStatus where
a == b = F.deviceID a == F.deviceID b &&
F.fileID a == F.fileID b
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Construct a 'FileInfo' value.
mkFI :: FilePath -> Int -> F.FileStatus -> FileInfo
mkFI = FileInfo
2007-05-01 02:24:44 -04:00
-- | Monadic container for file information, allowing for clean
-- construction of combinators. Wraps the 'State' monad, but doesn't
-- allow 'get' or 'put'.
newtype FindClause a = FC { runFC :: State FileInfo a }
2007-05-01 01:49:58 -04:00
deriving (Functor, Monad)
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Run the given find clause and return a pure value.
2007-04-30 02:30:30 -04:00
evalFI :: FindClause a
-> FilePath
-> Int
-> F.FileStatus
-> a
2007-05-01 02:24:44 -04:00
evalFI m p d s = evalState (runFC m) (mkFI p d s)
mkFindClause :: (FileInfo -> (a, FileInfo)) -> FindClause a
2007-05-01 02:24:44 -04:00
mkFindClause = FC . State
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Return the current 'FileInfo'.
fileInfo :: FindClause FileInfo
2007-04-30 03:29:32 -04:00
fileInfo = mkFindClause $ \st -> (st, st)
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Return the name of the file being visited.
2007-04-30 02:30:30 -04:00
filePath :: FindClause FilePath
filePath = infoPath `liftM` fileInfo
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Return the current recursion depth.
2007-04-30 02:30:30 -04:00
depth :: FindClause Int
depth = infoDepth `liftM` fileInfo
2007-04-30 02:30:30 -04:00
2007-05-01 02:24:44 -04:00
-- | Return the 'F.FileStatus' for the current file.
2007-04-30 03:29:32 -04:00
fileStatus :: FindClause F.FileStatus
fileStatus = infoStatus `liftM` fileInfo
2007-04-30 03:29:32 -04:00
2007-04-30 02:30:30 -04:00
type FilterPredicate = FindClause Bool
type RecursionPredicate = FindClause Bool
2007-05-01 02:24:44 -04:00
-- | List the files in the given directory, sorted, and without \".\"
-- or \"..\".
2007-04-30 02:30:30 -04:00
getDirContents :: FilePath -> IO [FilePath]
getDirContents dir = (sort . filter goodName) `liftM` getDirectoryContents dir
where goodName "." = False
goodName ".." = False
goodName _ = True
2007-05-01 02:24:44 -04:00
-- | Search a directory recursively, with recursion controlled by a
-- 'RecursionPredicate'. Lazily return a sorted list of all files
-- matching the given 'FilterPredicate'. Any errors that occur are
-- dealt with by the given handler.
findWithHandler :: (FilePath -> E.Exception -> IO [FilePath]) -- ^ error handler
-> RecursionPredicate -- ^ control recursion into subdirectories
-> FilterPredicate -- ^ decide whether a file appears in the result
-> FilePath -- ^ directory to start searching
-> IO [FilePath] -- ^ files that matched the 'FilterPredicate'
2007-04-30 02:30:30 -04:00
findWithHandler errHandler recurse filter path =
E.handle (errHandler path) $ F.getSymbolicLinkStatus path >>= visit path 0
where visit path depth st =
if F.isDirectory st && evalFI recurse path depth st
then unsafeInterleaveIO (traverse path (succ depth) st)
else filterPath path depth st []
traverse dir depth dirSt = do
names <- E.catch (getDirContents dir) (errHandler dir)
filteredPaths <- forM names $ \name -> do
let path = dir </> name
unsafeInterleaveIO $ E.handle (errHandler path)
(F.getSymbolicLinkStatus path >>= visit path depth)
filterPath dir depth dirSt (concat filteredPaths)
filterPath path depth st result =
return $ if evalFI filter path depth st
then path:result
else result
2007-05-01 02:24:44 -04:00
-- | Search a directory recursively, with recursion controlled by a
-- 'RecursionPredicate'. Lazily return a sorted list of all files
-- matching the given 'FilterPredicate'. Any errors that occur are
-- ignored, with warnings printed to 'stderr'.
find :: RecursionPredicate -- ^ control recursion into subdirectories
-> FilterPredicate -- ^ decide whether a file appears in the result
-> FilePath -- ^ directory to start searching
-> IO [FilePath] -- ^ files that matched the 'FilterPredicate'
2007-04-30 02:30:30 -04:00
find = findWithHandler warnOnError
where warnOnError path err =
hPutStrLn stderr (path ++ ": " ++ show err) >> return []
2007-05-01 02:24:44 -04:00
-- | Search a directory recursively, with recursion controlled by a
-- 'RecursionPredicate'. Fold over all files found. Any errors that
-- occur are dealt with by the given handler. The fold function is
-- run from \"left\" to \"right\", so it should be strict in its left
-- argument to avoid space leaks. If you need a right-to-left fold,
-- use 'foldr' on the result of 'findWithHandler' instead.
foldWithHandler :: (FilePath -> a -> E.Exception -> IO a) -- ^ error handler
-> RecursionPredicate -- ^ control recursion into subdirectories
-> (a -> FileInfo -> a) -- ^ function to fold with
-> a -- ^ seed value for fold
-> FilePath -- ^ directory to start searching
-> IO a -- ^ final value after folding
2007-04-30 02:30:30 -04:00
foldWithHandler errHandler recurse f state path =
E.handle (errHandler path state) $
F.getSymbolicLinkStatus path >>= visit state path 0
where visit state path depth st =
if F.isDirectory st && evalFI recurse path depth st
then traverse state path (succ depth) st
else return (f state (mkFI path depth st))
2007-04-30 02:30:30 -04:00
traverse state dir depth dirSt = E.handle (errHandler dir state) $
getDirContents dir >>=
flip foldM (f state (mkFI dir depth dirSt)) (\state name ->
2007-04-30 02:30:30 -04:00
E.handle (errHandler dir state) $
let path = dir </> name
in F.getSymbolicLinkStatus path >>= visit state path depth)
2007-05-01 02:24:44 -04:00
-- | Search a directory recursively, with recursion controlled by a
-- 'RecursionPredicate'. Fold over all files found. Any errors that
-- occur are ignored, with warnings printed to 'stderr'. The fold
-- function is run from \"left\" to \"right\", so it should be strict
-- in its left argument to avoid space leaks. If you need a
-- right-to-left fold, use 'foldr' on the result of 'findWithHandler'
-- instead.
2007-04-30 02:30:30 -04:00
fold :: RecursionPredicate
-> (a -> FileInfo -> a)
2007-04-30 02:30:30 -04:00
-> a
-> FilePath
-> IO a
fold = foldWithHandler warnOnError
where warnOnError path a err =
hPutStrLn stderr (path ++ ": " ++ show err) >> return a
always :: FindClause Bool
always = return True
extension :: FindClause FilePath
extension = takeExtension `liftM` filePath
fileName :: FindClause FilePath
fileName = takeFileName `liftM` filePath
directory :: FindClause FilePath
directory = takeDirectory `liftM` filePath
2007-04-30 18:53:13 -04:00
withLink :: (FilePath -> IO a) -> FindClause (Maybe a)
withLink f = do
2007-04-30 02:30:30 -04:00
path <- filePath
st <- fileStatus
return $ if F.isSymbolicLink st
2007-04-30 18:53:13 -04:00
then unsafePerformIO $ E.handle (const (return Nothing)) $
Just `liftM` f path
else Nothing
readLink :: FindClause (Maybe FilePath)
readLink = withLink F.readSymbolicLink
followStatus :: FindClause (Maybe F.FileStatus)
followStatus = withLink F.getFileStatus
2007-04-30 02:30:30 -04:00
data FileType = BlockDevice
| CharacterDevice
| NamedPipe
| RegularFile
| Directory
| SymbolicLink
| Socket
| Unknown
deriving (Eq, Ord, Show)
fileType :: FindClause FileType
fileType = fType `liftM` fileStatus
where fType st | F.isBlockDevice st = BlockDevice
fType st | F.isCharacterDevice st = CharacterDevice
fType st | F.isNamedPipe st = NamedPipe
fType st | F.isRegularFile st = RegularFile
fType st | F.isDirectory st = Directory
fType st | F.isSymbolicLink st = SymbolicLink
fType st | F.isSocket st = Socket
fType _ = Unknown
deviceID :: FindClause T.DeviceID
deviceID = F.deviceID `liftM` fileStatus
fileID :: FindClause T.FileID
fileID = F.fileID `liftM` fileStatus
fileOwner :: FindClause T.UserID
fileOwner = F.fileOwner `liftM` fileStatus
fileGroup :: FindClause T.GroupID
fileGroup = F.fileGroup `liftM` fileStatus
fileSize :: FindClause T.FileOffset
fileSize = F.fileSize `liftM` fileStatus
linkCount :: FindClause T.LinkCount
linkCount = F.linkCount `liftM` fileStatus
specialDeviceID :: FindClause T.DeviceID
specialDeviceID = F.specialDeviceID `liftM` fileStatus
fileMode :: FindClause T.FileMode
fileMode = F.fileMode `liftM` fileStatus
filePerms :: FindClause T.FileMode
filePerms = (.&. 0777) `liftM` fileMode
anyPerms :: T.FileMode
-> FindClause Bool
anyPerms m = filePerms >>= \p -> return (p .&. m /= 0)
accessTime :: FindClause T.EpochTime
accessTime = F.accessTime `liftM` fileStatus
modificationTime :: FindClause T.EpochTime
modificationTime = F.modificationTime `liftM` fileStatus
statusChangeTime :: FindClause T.EpochTime
statusChangeTime = F.statusChangeTime `liftM` fileStatus
2007-05-01 01:49:58 -04:00
contains :: FilePath -> FindClause Bool
contains p = do
d <- filePath
return $ unsafePerformIO $
E.handle (const (return False)) $
F.getFileStatus (d </> p) >> return True
2007-04-30 02:30:30 -04:00
liftOp :: Monad m => (a -> b -> c) -> m a -> b -> m c
liftOp f a b = a >>= \a' -> return (f a' b)
2007-04-30 19:07:37 -04:00
(~~?) :: FindClause FilePath -> GlobPattern -> FindClause Bool
2007-04-30 03:29:32 -04:00
(~~?) = liftOp (~~)
infix 4 ~~?
2007-04-30 02:30:30 -04:00
2007-04-30 19:07:37 -04:00
(/~?) :: FindClause FilePath -> GlobPattern -> FindClause Bool
2007-04-30 03:29:32 -04:00
(/~?) = liftOp (/~)
2007-04-30 02:30:30 -04:00
infix 4 /~?
2007-04-30 19:07:37 -04:00
(==?) :: Eq a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(==?) = liftOp (==)
infix 4 ==?
2007-04-30 19:07:37 -04:00
(/=?) :: Eq a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(/=?) = liftOp (/=)
infix 4 /=?
2007-04-30 19:07:37 -04:00
(>?) :: Ord a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(>?) = liftOp (>)
infix 4 >?
2007-04-30 19:07:37 -04:00
(<?) :: Ord a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(<?) = liftOp (<)
infix 4 <?
2007-04-30 19:07:37 -04:00
(.&.?) :: Bits a => FindClause a -> a -> FindClause a
2007-04-30 02:30:30 -04:00
(.&.?) = liftOp (.&.)
infixl 7 .&.?
2007-04-30 19:07:37 -04:00
(>=?) :: Ord a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(>=?) = liftOp (>=)
infix 4 >=?
2007-04-30 19:07:37 -04:00
(<=?) :: Ord a => FindClause a -> a -> FindClause Bool
2007-04-30 02:30:30 -04:00
(<=?) = liftOp (<=)
infix 4 <=?
2007-04-30 19:07:37 -04:00
(&&?) :: FindClause Bool -> FindClause Bool -> FindClause Bool
2007-04-30 02:30:30 -04:00
(&&?) = liftM2 (&&)
infixr 3 &&?
2007-04-30 19:07:37 -04:00
(||?) :: FindClause Bool -> FindClause Bool -> FindClause Bool
2007-04-30 02:30:30 -04:00
(||?) = liftM2 (||)
infixr 2 ||?