Skip to content

Commit fe8e0ff

Browse files
fix bugs in copy/move/remove; ad some helper functions; reorganize exports
1 parent e6ad81c commit fe8e0ff

File tree

2 files changed

+85
-48
lines changed

2 files changed

+85
-48
lines changed

directory-trees.cabal

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,5 @@ library
2121
, data-lens
2222
, dlist
2323
, mtl
24-
, data-default
2524
, cond == 0.0.*
2625
exposed-modules: System.Directory.Tree

src/System/Directory/Tree.hs

Lines changed: 85 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3232
import System.IO.Unsafe (unsafeInterleaveIO)
3333
import 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 ((</>))
3739
import System.Posix.Files (getSymbolicLinkStatus, isSymbolicLink)
40+
3841
import Data.Tree (Tree(..), Forest)
3942
import 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)
4547
import Control.Monad.Identity (runIdentity)
46-
import Control.Applicative ((<$>))
48+
import Control.Applicative ((<$>), (<*))
4749
import Control.Arrow (second)
50+
import Data.Foldable (foldrM)
4851
import Data.Maybe (mapMaybe)
4952
import 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(..))
5356
import Data.Word (Word)
5457
import Data.Typeable (Typeable)
5558
import Data.Data (Data)
5659

57-
import Prelude hiding (filter)
60+
import Prelude hiding (filter, catch)
5861
import qualified Prelude as P (filter)
5962

6063
newtype 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

8891
getDirectory :: FilePath -> IO FSTree
89-
getDirectory = getDir defaultOptions
92+
getDirectory = getDir_ unsafeInterleaveIO
9093

9194
getDirectory' :: 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
120109
isSymLink :: FilePath -> IO Bool
121110
isSymLink 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+
123116
pop :: FSTree -> (FilePath, FSForest)
124117
pop fs = (path, map prepend cs)
125118
where path = getL label fs
@@ -132,6 +125,10 @@ pop_ = snd . pop
132125
flatten :: FSTree -> [FilePath]
133126
flatten = 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+
135132
filter :: (FilePath -> Bool) -> FSForest -> FSForest
136133
filter p = fst . extract p
137134

@@ -177,25 +174,66 @@ truncateAt n = mapMaybe (truncate' 0)
177174

178175

179176
prependPaths :: 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-
187183
copyTo :: 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

190189
moveTo :: 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

193212
remove :: 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

Comments
 (0)