{-| The FileTree module builds on the "Data.Tree" module to create a tree of file information (for a Posix-based filesystem). To build the initial tree, the 'get_filetree' operation is used. The result is a Forest (an array of Trees) where each entry in the Forest corresponds to the directory tree rooted at the corresponding input argument in the array provided to 'get_filetree'. * If the entry is a directory, the corresponding result will be a tree mirroring the file directory tree. * If the entry is a file, the result will be a single tree entry representing that file, with an empty subForest. * If the entry does not exist, then no corresponding entry is added to the output tree. This initial tree returned by 'get_filetree' has a FilePath (the full file or directory path specification) as the 'rootLabel' for each entry. It is often more useful to have each entry in the tree be a more complex type that can contain various information regarding that entry in the tree. This can be created by calling 'get_filetree' to generate the initial tree and then using 'filetree_of' to convert each entry to the desired type. There are several functions that can be used to filter the FileTree to examine only the interesting entries. These functions are 'filetree_filterBy', 'filetree_exclude', and 'filetree_excludes'; each takes slightly different arguments but each acts to return filtered version of the input FileTree. -} module FSOps.FileTree ( -- ** Types FileTree, FileTrees, -- ** Creating a FileTree get_filetree, get_filetrees, -- ** Transforming a FileTree filetree_of, -- ** Filtering FileTrees filetree_filterBy, filetrees_filterBy, filetree_exclude, filetree_excludes, files_filterBy, subdirs_filterBy, subdirs_and_files, ) where import Data.Monoid import Data.Tree import Data.List import Data.Maybe import Control.Monad import System.Posix import FSOps.FileBase import FSOps.FileInfo -- | Representation of a single 'Tree' of files type FileTree a = Tree a type FileTrees a = Forest a -- ^ An array of 'FileTree's. Equivalent to @[FileTree a]@ -- |Returns the contents of the specified directory as a list dirlist :: FilePath -> IO [FilePath] dirlist n = do fs <- getFileStatus n case (isDirectory fs) of False -> return [] True -> do ds <- openDirStream n entries <- get_dirlist ds closeDirStream ds return entries where get_dirlist ds = do a <- readDirStream ds case a of "" -> return [] "." -> get_dirlist ds ".." -> get_dirlist ds _ -> do remaining <- get_dirlist ds return $ mappend [n /// a] remaining {- | The get_filetree function is used to create a "Data.Tree" 'Forest' from the input list of 'FilePath's. The 'rootLabel' for each entry is the full path of that entry. -} get_filetree :: FilePath -> IO (FileTree FilePath) get_filetree r = unfoldTreeM get_fstree r where get_fstree rootdir = do contents <- dirlist rootdir return (rootdir, contents) get_filetrees :: [FilePath] -> IO (FileTrees FilePath) get_filetrees [] = return [] get_filetrees (x:xs) = do this <- get_filetree x next <- get_filetrees xs return $ mappend [this] next filetree_of :: (a -> b) -> FileTree a -> FileTree b filetree_of convert = fmap convert {- | Filters a 'FileTree' to only those entries for which the passed predicate returns true when passed a rootLabel in the 'FileTree'. -} filetree_filterBy :: (a -> Bool) -> FileTree a -> Maybe (FileTree a) filetree_filterBy filterfunc ftree = let dofilt = filterfunc . rootLabel fnode n = Node (rootLabel n) (filtsub n) filtsub = mapMaybe (filetree_filterBy filterfunc) . subForest in if (dofilt ftree) then Just (fnode ftree) else Nothing {- | Filters an array of FileTree's to only those entries for which the passed predicate returns true when passed a rootLabel in the FileTree. Similar to 'filetree_filterBy' but operates on an array instead. -} filetrees_filterBy :: (a -> Bool) -> FileTrees a -> FileTrees a filetrees_filterBy filterfunc forest = let dofilt = filterfunc . rootLabel fnode n = Node (rootLabel n) (filetrees_filterBy filterfunc (subForest n)) in map fnode (filter dofilt forest) {- | Filters a 'FileTree' and returns only the direct entries in that FileTree that are files (not directories) and for which the passed predicate also returns true. -} files_filterBy :: (a -> Bool) -> FileTree a -> FileTrees a files_filterBy filterfunc = filter (filterfunc . rootLabel) . snd . subdirs_and_files {- | Filters a 'FileTree' and returns only the direct entries in that FileTree that are subdirectories (not files) and for which the passed predicate also returns true. -} subdirs_filterBy :: (a -> Bool) -> FileTree a -> FileTrees a subdirs_filterBy filterfunc = filter (filterfunc . rootLabel) . fst . subdirs_and_files {- | Returns a tuple for a 'FileTree' where the first entry in the tuple is the array of subdirectories and the second entry is the array of files. -} subdirs_and_files :: FileTree a -> (FileTrees a, FileTrees a) subdirs_and_files = partition (not . null . subForest) . subForest {- | Filters entries with the specified name (filename or path ending) out of the FileTree. This is a breadth-first algorithm, so if the specified path is a directory name, that directory and all its contents and subdirectories are removed efficiently. -} filetree_exclude :: String -> FileTree String -> Maybe (FileTree String) filetree_exclude fname = filetree_filterBy (\n -> n /= fname && (not $ isSuffixOf ("/" ++ fname) n)) {- | Filters entries with any of the specified names (filename or path ending) out of the FileTree. Equivalent to: @ foldr filetree_exclude filetree fnames @ -} filetree_excludes :: [String] -> FileTree String -> Maybe (FileTree String) filetree_excludes fnames = flip (foldM (flip filetree_exclude)) fnames