0 | module Libraries.System.Directory.Tree
2 | import Control.Monad.Either
6 | import System.Directory
7 | import Libraries.Utils.Path
20 | data FileName : Path -> Type where
21 | MkFileName : String -> FileName root
25 | fileName : FileName root -> String
26 | fileName (MkFileName str) = str
30 | toRelative : FileName root -> FileName (parse "")
31 | toRelative (MkFileName x) = MkFileName x
36 | toFilePath : {root : Path} -> FileName root -> String
37 | toFilePath file = show (root /> fileName file)
40 | directoryExists : {root : Path} -> FileName root -> IO Bool
41 | directoryExists fp = do
42 | Right dir <- openDir (toFilePath fp)
43 | | Left _ => pure False
55 | SubTree : Path -> Type
58 | record Tree (root : Path) where
60 | files : List (FileName root)
61 | subTrees : List (SubTree root)
63 | SubTree root = (dir : FileName root ** IO (Tree (root /> fileName dir)))
67 | emptyTree : Tree root
68 | emptyTree = MkTree [] []
74 | toRelative : Tree root -> Tree (parse "")
75 | toRelative x = believe_me x
79 | filter : (filePred, dirPred : {root : _} -> FileName root -> Bool) ->
80 | {root : _} -> Tree root -> Tree root
81 | filter filePred dirPred (MkTree files dirs) = MkTree files' dirs' where
83 | files' : List (FileName root)
84 | files' = filter filePred files
86 | dirs' : List (SubTree root)
87 | dirs' = flip List.mapMaybe dirs $
\ (
dname ** iot)
=> do
88 | guard (dirPred dname)
89 | pure (
dname ** map (assert_total (filter filePred dirPred)) iot)
93 | sortBy : (fileCmp, dirCmp : {root : _} -> FileName root -> FileName root -> Ordering) ->
94 | {root : _} -> Tree root -> Tree root
95 | sortBy fileCmp dirCmp (MkTree files dirs) = MkTree files' dirs' where
97 | files' : List (FileName root)
98 | files' = sortBy fileCmp files
100 | dirs' : List (SubTree root)
101 | dirs' = sortBy (\ d1, d2 => dirCmp (fst d1) (fst d2))
102 | $
flip map dirs $
\ (
dname ** iot)
=>
103 | (
dname ** map (assert_total (sortBy fileCmp dirCmp)) iot)
107 | sort : {root : _} -> Tree root -> Tree root
108 | sort = sortBy cmp cmp where
110 | cmp : {root : _} -> FileName root -> FileName root -> Ordering
111 | cmp a b = compare (fileName a) (fileName b)
116 | explore : (root : Path) -> IO (Tree root)
118 | go : {root : Path} -> Directory -> Tree root -> IO (Tree root)
121 | Right dir <- openDir (show root)
122 | | Left err => pure emptyTree
123 | assert_total (go dir emptyTree)
125 | go dir acc = case !(nextDirEntry dir) of
127 | Left err => acc <$ closeDir dir
128 | Right Nothing => acc <$ closeDir dir
130 | Right (Just entry) => do
132 | let False = elem entry [".", ".."]
133 | | _ => assert_total (go dir acc)
136 | let entry : FileName root = MkFileName entry
137 | let acc = if !(directoryExists entry)
138 | then { subTrees $= ((
entry ** explore _)
::) } acc
139 | else { files $= (entry ::) } acc
140 | assert_total (go dir acc)
145 | depthFirst : ({root : Path} -> FileName root -> Lazy (IO a) -> IO a) ->
146 | {root : Path} -> Tree root -> IO a -> IO a
147 | depthFirst check t k =
148 | let next = foldr (\ (
dir ** iot)
, def => depthFirst check !iot def) k t.subTrees in
149 | foldr (\ fn, def => check fn def) next t.files
157 | print : Tree root -> IO ()
158 | print t = go [([], ".", Evidence root (pure t))] where
163 | padding : Bool -> List Bool -> String
164 | padding isDir = fastConcat . go [] where
166 | go : List String -> List Bool -> List String
168 | go acc (b :: bs) = go (hd :: acc) bs where
170 | hd = if isDir && isNil acc
171 | then if b then " ├ " else " └ "
172 | else if b then " │" else " "
174 | prefixes : Nat -> List Bool
175 | prefixes n = snoc (replicate (pred n) True) False
178 | go : List (List Bool, String, Exists (IO . Tree)) -> IO ()
180 | go ((bs, fn, Evidence _ iot) :: iots) = do
182 | putStrLn (padding True bs ++ fn)
183 | let pad = padding False bs
184 | let pads = prefixes (length t.files + length t.subTrees)
185 | for_ (zip pads t.files) $
\ (b, fp) =>
186 | putStrLn (pad ++ (if b then " ├ " else " └ ") ++ fileName fp)
187 | let bss = map (:: bs) (prefixes (length t.subTrees))
188 | go (zipWith (\ bs, (
dir ** iot)
=> (bs, fileName dir, Evidence _ iot)) bss t.subTrees)
196 | copyDir : HasIO io => (src : Path) -> (target : Path) -> io (Either FileError ())
197 | copyDir src target = runEitherT $
do
198 | MkEitherT $
createDir $
show target
199 | copyDirContents !(liftIO $
explore src) target
201 | copyFile' : (srcDir : Path) -> (targetDir : Path) -> (fileName : String) -> EitherT FileError io ()
202 | copyFile' srcDir targetDir fileName = MkEitherT $
do
203 | Right ok <- copyFile (show $
srcDir /> fileName) (show $
targetDir /> fileName)
204 | | Left (err, size) => pure (Left err)
208 | copyDirContents : {srcDir : Path} -> Tree srcDir -> (targetDir : Path) -> EitherT FileError io ()
209 | copyDirContents (MkTree files subTrees) targetDir = do
210 | traverse_ (copyFile' srcDir targetDir) (map fileName files)
211 | traverse_ (\(
subDir ** subDirTree)
=> do
212 | let targetSubDir = targetDir /> fileName subDir
213 | MkEitherT $
createDir $
show $
targetSubDir
214 | copyDirContents !(liftIO subDirTree) targetSubDir