{-# language DeriveDataTypeable #-} {-# language DeriveFoldable #-} {-# language DeriveFunctor #-} {-# language DeriveGeneric #-} {-# language DeriveTraversable #-} {-# language LambdaCase #-} {-| Description: Contains the tree data structure used to store directory hierarchies -} module System.Directory.Contents.Types where import Data.Data import Data.Map (Map) import qualified Data.Map as Map import GHC.Generics import System.FilePath -- | The contents of a directory, represented as a tree. See 'Symlink' for -- special handling of symlinks. data DirTree a = DirTree_Dir FilePath (Map FileName (DirTree a)) | DirTree_File FilePath a | DirTree_Symlink FilePath (Symlink a) deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Generic, Data) -- | Symlink cycles are prevented by separating symlinks into two categories: -- those that point to paths already within the directory hierarchy being -- recursively listed, and those that are not. In the former case, rather than -- following the symlink and listing the target redundantly, we simply store -- the symlink reference itself. In the latter case, we treat the symlink as we -- would any other folder and produce a list of its contents. -- -- The 'String' argument represents the symlink reference (e.g., "../somefile"). -- In the 'Symlink_Internal' case, the second ('FilePath') argument is the path -- to the symlink target. -- In the 'Symlink_External' case, the second (@[DirTree a]@) argument contains -- the contents of the symlink target. data Symlink a = Symlink_Internal String FilePath | Symlink_External String (Map FileName (DirTree a)) deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Generic, Data) -- * Utilities -- | Extract the 'FilePath' from a 'DirTree' node filePath :: DirTree a -> FilePath filePath = \case DirTree_Dir f _ -> f DirTree_File f _ -> f DirTree_Symlink f _ -> f -- | File names, as opposed to file paths, are used to uniquely identify -- siblings at each level type FileName = String -- | Generate the key used to identify siblings fileName :: DirTree a -> FileName fileName = takeFileName . filePath -- | Construct a map of files indexed by filename. Should only be used for a -- particular generation or level in the directory hierarchy (since that's the -- only time we can be sure that names are unique) fileNameMap :: [DirTree a] -> Map FileName (DirTree a) fileNameMap xs = Map.fromList $ zip (fileName <$> xs) xs -- | Add a sibling to a map of files insertSibling :: DirTree a -> Map FileName (DirTree a) -> Map FileName (DirTree a) insertSibling a = Map.insert (fileName a) a -- | Remove sibling from a map of files removeSibling :: DirTree a -> Map FileName (DirTree a) -> Map FileName (DirTree a) removeSibling a = Map.delete (fileName a) -- | Map a function over the first child and the rest of the children withFirstChild :: Map FileName (DirTree a) -> (DirTree a -> Map FileName (DirTree a) -> x) -> Maybe x withFirstChild m f = case Map.minView m of Nothing -> Nothing Just (firstChild, children) -> Just $ f firstChild children