@@ -11,50 +11,53 @@ module System.Directory.Tree
1111 -- * Overloaded tree lenses
1212 , TreeLens (.. )
1313 -- *Retrieve directory trees from the filesystem
14- , getDir , getDir'
1514 , getDirectory , getDirectory'
16- , Options (.. ), defaultOptions
1715 -- * Operations on directory trees
1816 -- **basic operations
19- , pop , pop_ , flatten
17+ , pop , pop_ , flatten , flattenPostOrder
2018 -- **find subtrees
2119 , find , findM
2220 -- **filter subtrees
2321 , filter , filterM
22+ -- ***Useful predicates
23+ , isSymLink , isDir
2424 -- **extract subtrees
2525 , extract , extractM
2626 -- **truncate tree to a maximum level
2727 , truncateAt
2828 -- **Copy, move, and remove directory trees
29- , copyTo , moveTo , remove
29+ , copyTo , copyTo_ , moveTo , moveTo_ , mergeInto , mergeInto_ , remove
3030 )where
3131
3232import System.IO.Unsafe (unsafeInterleaveIO )
3333import Unsafe.Coerce (unsafeCoerce )
34- import System.Directory (getDirectoryContents , doesDirectoryExist
35- ,copyFile , renameFile , removeFile )
36- import System.FilePath ((</>) , replaceDirectory )
34+
35+ import System.Directory (getDirectoryContents , doesDirectoryExist , copyFile ,
36+ renameFile , removeFile , createDirectory ,
37+ createDirectoryIfMissing , removeDirectory )
38+ import System.FilePath ((</>) )
3739import System.Posix.Files (getSymbolicLinkStatus , isSymbolicLink )
40+
3841import Data.Tree (Tree (.. ), Forest )
3942import qualified Data.Tree as Tree (flatten )
40- import Data.DList as DL (DList (.. ), cons , append , toList , empty )
41- import Data.Lens.Common (Lens , lens , getL , modL )
43+ import Data.DList as DL (DList (.. ), cons , append , toList , empty , concat , snoc )
4244
43- import Data.Foldable ( foldrM )
44- import Control.Monad (forM , liftM )
45+ import Control.Exception ( catch , IOException )
46+ import Control.Monad (forM , liftM , void )
4547import Control.Monad.Identity (runIdentity )
46- import Control.Applicative ((<$>) )
48+ import Control.Applicative ((<$>) , (<*) )
4749import Control.Arrow (second )
50+ import Data.Foldable (foldrM )
4851import Data.Maybe (mapMaybe )
4952import Data.Function (on )
50- import Control.Cond (ifM , (<||>) , (<&&>) , notM )
53+ import Data.Lens.Common (Lens , lens , getL , setL , modL )
54+ import Control.Cond (ifM , (<&&>) , notM , whenM )
5155
52- import Data.Default (Default (.. ))
5356import Data.Word (Word )
5457import Data.Typeable (Typeable )
5558import Data.Data (Data )
5659
57- import Prelude hiding (filter )
60+ import Prelude hiding (filter , catch )
5861import qualified Prelude as P (filter )
5962
6063newtype FSTree = FSTree { toTree :: Tree FilePath } deriving
@@ -86,40 +89,30 @@ instance TreeLens FSTree FilePath where
8689 (\ c fs -> FSTree $ (toTree fs) {subForest = mapToTree c})
8790
8891getDirectory :: FilePath -> IO FSTree
89- getDirectory = getDir defaultOptions
92+ getDirectory = getDir_ unsafeInterleaveIO
9093
9194getDirectory' :: FilePath -> IO FSTree
92- getDirectory' = getDir' defaultOptions
93-
94- getDir :: Options -> FilePath -> IO FSTree
95- getDir = getDir_ unsafeInterleaveIO
96-
97- getDir' :: Options -> FilePath -> IO FSTree
98- getDir' = getDir_ id
99-
100- data Options = Options { followSymLinks :: Bool } deriving (Eq , Show )
101-
102- instance Default Options where
103- def = defaultOptions
95+ getDirectory' = getDir_ id
10496
105- defaultOptions :: Options
106- defaultOptions = Options { followSymLinks = False }
107-
108- getDir_ :: (IO FSTree -> IO FSTree ) -> Options -> FilePath -> IO FSTree
109- getDir_ f Options {.. } p = mkFSTree p <$> getChildren p
97+ getDir_ :: (IO FSTree -> IO FSTree ) -> FilePath -> IO FSTree
98+ getDir_ f p = mkFSTree p <$> getChildren p
11099 where getChildren path = do
111100 cs <- P. filter (`notElem` [" ." ," .." ])
112101 <$> getDirectoryContents path
113102 forM cs $ \ c ->
114103 let c' = path </> c
115- in ifM (doesDirectoryExist c' <&&> (return followSymLinks
116- <||> notM (isSymLink c')))
104+ in ifM (isDir c')
117105 ( f . fmap (mkFSTree c) . getChildren $ c' )
118106 ( return $ mkFSTree c [] )
119107
108+ -- | Checks if a path refers to a symbolic link
120109isSymLink :: FilePath -> IO Bool
121110isSymLink p = isSymbolicLink <$> getSymbolicLinkStatus p
122111
112+ -- | Checks if a path refers to a real directory (not a symbolic link)
113+ isDir :: FilePath -> IO Bool
114+ isDir p = doesDirectoryExist p <&&> notM (isSymLink p)
115+
123116pop :: FSTree -> (FilePath , FSForest )
124117pop fs = (path, map prepend cs)
125118 where path = getL label fs
@@ -132,6 +125,10 @@ pop_ = snd . pop
132125flatten :: FSTree -> [FilePath ]
133126flatten = Tree. flatten . prependPaths
134127
128+ flattenPostOrder :: FSTree -> [FilePath ]
129+ flattenPostOrder = toList . flatten' . prependPaths
130+ where flatten' (Node p cs) = DL. concat (map flatten' cs) `snoc` p
131+
135132filter :: (FilePath -> Bool ) -> FSForest -> FSForest
136133filter p = fst . extract p
137134
@@ -177,25 +174,66 @@ truncateAt n = mapMaybe (truncate' 0)
177174
178175
179176prependPaths :: FSTree -> Tree FilePath
180- prependPaths (FSTree t ) = modL children (map (prepend' root )) t
177+ prependPaths (FSTree root ) = modL children (map (prepend' rootPath )) root
181178 where
182- root = rootLabel t
183- prepend' parentPath = modL label (parentPath </> ) . prependChildren
179+ rootPath = rootLabel root
180+ prepend' parentPath = prependChildren . modL label (parentPath </> )
184181 prependChildren fs = modL children (map (prepend' (rootLabel fs))) fs
185182
186-
187183copyTo :: FilePath -> FSTree -> IO FSTree
188- copyTo = zipWithDestM copyFile
184+ copyTo = zipWithDestM (const $ createDirectoryIfMissing False ) copyFile
185+
186+ copyTo_ :: FilePath -> FSTree -> IO ()
187+ copyTo_ = (void . ) . copyTo
189188
190189moveTo :: FilePath -> FSTree -> IO FSTree
191- moveTo = zipWithDestM renameFile
190+ moveTo dest fs = do
191+ whenM (isDir dest) $ remove =<< getDirectory dest
192+ zipWithDestM
193+ (\ s d -> do tryRemoveDirectory s
194+ createDirectory d)
195+ renameFile
196+ dest fs
197+ <* removeEmptyDirectories fs
198+
199+ moveTo_ :: FilePath -> FSTree -> IO ()
200+ moveTo_ = (void . ) . moveTo
201+
202+ mergeInto :: FilePath -> FSTree -> IO FSTree
203+ mergeInto dest fs = zipWithDestM
204+ (\ _ d -> createDirectoryIfMissing False d)
205+ renameFile
206+ dest fs
207+ <* removeEmptyDirectories fs
208+
209+ mergeInto_ :: FilePath -> FSTree -> IO ()
210+ mergeInto_ = (void . ) . mergeInto
192211
193212remove :: FSTree -> IO ()
194- remove = mapM_ removeFile . flatten
195-
196- zipWithDestM :: Monad m =>
197- (FilePath -> FilePath -> m () ) -> FilePath -> FSTree -> m FSTree
198- zipWithDestM f dest fs = do
213+ remove = remove' . prependPaths
214+ where remove' (Node p cs) = do
215+ mapM_ remove' cs
216+ ifM (doesDirectoryExist p)
217+ (removeDirectory p)
218+ (removeFile p)
219+
220+ removeEmptyDirectories :: FSTree -> IO ()
221+ removeEmptyDirectories = mapM_ tryRemoveDirectory . flattenPostOrder
222+
223+ tryRemoveDirectory :: FilePath -> IO ()
224+ tryRemoveDirectory path = removeDirectory path `catch` handler
225+ where handler :: IOException -> IO ()
226+ handler = const (return () )
227+
228+ zipWithDestM :: (FilePath -> FilePath -> IO () )
229+ -> (FilePath -> FilePath -> IO () )
230+ -> FilePath -> FSTree
231+ -> IO FSTree
232+ zipWithDestM dirF fileF rootDest fs = do
199233 sequence_ $ (zipWith f `on` flatten) fs destFs
200234 return destFs
201- where destFs = modL label (`replaceDirectory` dest) fs
235+ where
236+ destFs = setL label rootDest fs
237+ f src dest = ifM (isDir src)
238+ (dirF src dest)
239+ (fileF src dest)
0 commit comments