0 | module System.Directory.Tree
2 | import Control.Monad.Either
6 | import System.Directory
21 | data FileName : Path -> Type where
22 | MkFileName : String -> FileName root
26 | fileName : FileName root -> String
27 | fileName (MkFileName str) = str
31 | toRelative : FileName root -> FileName (parse "")
32 | toRelative (MkFileName x) = MkFileName x
37 | toFilePath : {root : Path} -> FileName root -> Path
38 | toFilePath file = root /> fileName file
41 | directoryExists : {root : Path} -> FileName root -> IO Bool
42 | directoryExists fp = do
43 | Right dir <- openDir (show $
toFilePath fp)
44 | | Left _ => pure False
56 | SubTree : Path -> Type
59 | record Tree (root : Path) where
61 | files : List (FileName root)
62 | subTrees : List (SubTree root)
64 | SubTree root = (dir : FileName root ** IO (Tree (root /> fileName dir)))
68 | emptyTree : Tree root
69 | emptyTree = MkTree [] []
75 | toRelative : Tree root -> Tree (parse "")
76 | toRelative x = believe_me x
80 | filter : (filePred, dirPred : {root : _} -> FileName root -> Bool) ->
81 | {root : _} -> Tree root -> Tree root
82 | filter filePred dirPred (MkTree files dirs) = MkTree files' dirs' where
84 | files' : List (FileName root)
85 | files' = filter filePred files
87 | dirs' : List (SubTree root)
88 | dirs' = flip List.mapMaybe dirs $
\ (
dname ** iot)
=> do
89 | guard (dirPred dname)
90 | pure (
dname ** map (assert_total (filter filePred dirPred)) iot)
94 | sortBy : (fileCmp, dirCmp : {root : _} -> FileName root -> FileName root -> Ordering) ->
95 | {root : _} -> Tree root -> Tree root
96 | sortBy fileCmp dirCmp (MkTree files dirs) = MkTree files' dirs' where
98 | files' : List (FileName root)
99 | files' = sortBy fileCmp files
101 | dirs' : List (SubTree root)
102 | dirs' = sortBy (\ d1, d2 => dirCmp (fst d1) (fst d2))
103 | $
flip map dirs $
\ (
dname ** iot)
=>
104 | (
dname ** map (assert_total (sortBy fileCmp dirCmp)) iot)
108 | sort : {root : _} -> Tree root -> Tree root
109 | sort = sortBy cmp cmp where
111 | cmp : {root : _} -> FileName root -> FileName root -> Ordering
112 | cmp a b = compare (fileName a) (fileName b)
117 | explore : (root : Path) -> IO (Tree root)
119 | go : {root : Path} -> Directory -> Tree root -> IO (Tree root)
122 | Right dir <- openDir (show root)
123 | | Left err => pure emptyTree
124 | assert_total (go dir emptyTree)
126 | go dir acc = case !(nextDirEntry dir) of
128 | Left err => acc <$ closeDir dir
129 | Right Nothing => acc <$ closeDir dir
131 | Right (Just entry) => do
133 | let False = elem entry [".", ".."]
134 | | _ => assert_total (go dir acc)
137 | let entry : FileName root = MkFileName entry
138 | let acc = if !(directoryExists entry)
139 | then { subTrees $= ((
entry ** explore _)
::) } acc
140 | else { files $= (entry ::) } acc
141 | assert_total (go dir acc)
146 | depthFirst : ({root : Path} -> FileName root -> Lazy (IO a) -> IO a) ->
147 | {root : Path} -> Tree root -> IO a -> IO a
148 | depthFirst check t k =
149 | let next = foldr (\ (
dir ** iot)
, def => depthFirst check !iot def) k t.subTrees in
150 | foldr (\ fn, def => check fn def) next t.files
155 | findFile : {root : Path} -> String -> Tree root -> IO (Maybe Path)
156 | findFile needle t = depthFirst check t (pure Nothing) where
158 | check : {root : Path} -> FileName root ->
159 | Lazy (IO (Maybe Path)) -> IO (Maybe Path)
160 | check fn next = if fileName fn == needle
161 | then pure (Just (toFilePath fn))
170 | print : Tree root -> IO ()
171 | print t = go [([], ".", Evidence root (pure t))] where
176 | padding : Bool -> List Bool -> String
177 | padding isDir = fastConcat . go [] where
179 | go : List String -> List Bool -> List String
181 | go acc (b :: bs) = go (hd :: acc) bs where
183 | hd = if isDir && isNil acc
184 | then if b then " ├ " else " └ "
185 | else if b then " │" else " "
187 | prefixes : Nat -> List Bool
188 | prefixes n = snoc (replicate (pred n) True) False
191 | go : List (List Bool, String, Exists (IO . Tree)) -> IO ()
193 | go ((bs, fn, Evidence _ iot) :: iots) = do
195 | putStrLn (padding True bs ++ fn)
196 | let pad = padding False bs
197 | let pads = prefixes (length t.files + length t.subTrees)
198 | for_ (zip pads t.files) $
\ (b, fp) =>
199 | putStrLn (pad ++ (if b then " ├ " else " └ ") ++ fileName fp)
200 | let bss = map (:: bs) (prefixes (length t.subTrees))
201 | go (zipWith (\ bs, (
dir ** iot)
=> (bs, fileName dir, Evidence _ iot)) bss t.subTrees)
209 | copyDir : HasIO io => (src : Path) -> (target : Path) -> io (Either FileError ())
210 | copyDir src target = runEitherT $
do
211 | MkEitherT $
createDir $
show target
212 | copyDirContents !(liftIO $
explore src) target
214 | copyFile' : (srcDir : Path) -> (targetDir : Path) -> (fileName : String) -> EitherT FileError io ()
215 | copyFile' srcDir targetDir fileName = MkEitherT $
do
216 | Right ok <- copyFile (show $
srcDir /> fileName) (show $
targetDir /> fileName)
217 | | Left (err, size) => pure (Left err)
221 | copyDirContents : {srcDir : Path} -> Tree srcDir -> (targetDir : Path) -> EitherT FileError io ()
222 | copyDirContents (MkTree files subTrees) targetDir = do
223 | traverse_ (copyFile' srcDir targetDir) (map fileName files)
224 | traverse_ (\(
subDir ** subDirTree)
=> do
225 | let targetSubDir = targetDir /> fileName subDir
226 | MkEitherT $
createDir $
show $
targetSubDir
227 | copyDirContents !(liftIO subDirTree) targetSubDir