files.bend
bend-kit-files@0.3.1.0 · 307 lines · 43 definitions
1
2import Base
3import bend-kit-bytes@0.3.3.0/bytes.bend as Bytes
4
5
6
7
8
9def path.is_absolute(p: String) -> Bool:
10 String.starts_with(p, "/")
11
12def path.join.rel(bare: Bool, a: String, b: String) -> String:
13 match bare:
14 case True{}:
15 a ++ b
16 case False{}:
17 a ++ "/" ++ b
18
19def path.join.if(abs: Bool, +a: String, b: String) -> String:
20 match abs:
21 case True{}:
22 b
23 case False{}:
24 path.join.rel(String.is_empty(a) || String.ends_with(a, "/"), a, b)
25
26
27def path.join(a: String, +b: String) -> String:
28 path.join.if(path.is_absolute(b), a, b)
29
30def path.norm.pop.if(up: Bool, h: String, t: List<&2, String>) ->
31 List<&2, String>:
32 match up:
33 case True{}:
34 ".." <> (h <> t)
35 case False{}:
36 t
37
38
39def path.norm.pop(stack: List<&2, String>, abs: Bool) -> List<&2, String>:
40 match stack:
41 case Nil{}:
42 Bool.pick(List<&2, String>, abs, Nil{}, [".."])
43 case Con{+h, t}:
44 path.norm.pop.if(String.eq(h, ".."), h, t)
45
46def path.norm.step(
47 skip: Bool, up: Bool, s: String, stack: List<&2, String>, abs: Bool
48) -> List<&2, String>:
49 match skip:
50 case True{}:
51 stack
52 case False{}:
53 match up:
54 case True{}:
55 path.norm.pop(stack, abs)
56 case False{}:
57 s <> stack
58
59
60def path.norm.go(segs: List<&2, String>, stack: List<&2, String>, +abs: Bool) ->
61 List<&2, String>:
62 match segs:
63 case Nil{}:
64 stack
65 case Con{+s, t}:
66 path.norm.go(t,
67 path.norm.step(String.eq(s, "") || String.eq(s, "."), String.eq(s, ".."),
68 s, stack, abs),
69 abs)
70
71def path.norm.fin(abs: Bool, body: String) -> String:
72 match abs:
73 case True{}:
74 "/" ++ body
75 case False{}:
76 match body:
77 case SNil{}:
78 "."
79 case SCon{h, t}:
80 SCon{h, t}
81
82
83
84def path.normalize(+p: String) -> String:
85 +abs = path.is_absolute(p)
86 path.norm.fin(abs,
87 String.join(List.reverse(&2, String,
88 path.norm.go(String.split(p, '/'), Nil{}, abs)), "/"))
89
90def path.parent(p: String) -> String:
91 +n = path.normalize(p)
92 path.norm.fin(path.is_absolute(n),
93 String.join(List.reverse(&2, String,
94 List.tail(&2, String, List.reverse(&2, String, String.split(n, '/')))), "/"))
95
96def path.file_name.fin(m: Maybe<&2, String>) -> String:
97 match m:
98 case None{}:
99 ""
100 case Some{+s}:
101 Bool.pick(String, String.eq(s, "."), "", s)
102
103
104def path.file_name(p: String) -> String:
105 path.file_name.fin(List.last(&2, String, String.split(path.normalize(p), '/')))
106
107def path.extension.fin(bare: Bool, ext: String) -> String:
108 match bare:
109 case True{}:
110 ""
111 case False{}:
112 ext
113
114def path.extension.go(rev: List<&2, String>) -> String:
115 match rev:
116 case Nil{}:
117 ""
118 case Con{ext, Nil{}}:
119 ""
120 case Con{ext, Con{prev, rest}}:
121 path.extension.fin(String.is_empty(prev) && List.is_empty(&2, String, rest),
122 ext)
123
124
125
126def path.extension(p: String) -> String:
127 path.extension.go(List.reverse(&2, String, String.split(path.file_name(p), '.')))
128
129type FileType is Data:
130 Regular{}
131 Directory{}
132 Other{}
133
134
135type Info is Data:
136 Info{kind: FileType, size: U32, mtime: U32}
137
138
139def list_dir.raw(path: String) -> IO(Result<&1, &1, U32 & String, String>):
140 import "./effs/files.c"
141 import "./effs/files.js"
142
143
144
145def stat.raw(path: String) ->
146 IO(Result<&1, &1, U32 & String, U32 & U32 & U32>):
147 import "./effs/files.c"
148 import "./effs/files.js"
149
150def mkdir(path: String) -> IO(Result<&1, &1, U32 & String, Unit>):
151 import "./effs/files.c"
152 import "./effs/files.js"
153
154
155def remove(path: String) -> IO(Result<&1, &1, U32 & String, Unit>):
156 import "./effs/files.c"
157 import "./effs/files.js"
158
159def rename(from: String, to: String) ->
160 IO(Result<&1, &1, U32 & String, Unit>):
161 import "./effs/files.c"
162 import "./effs/files.js"
163
164
165def temp_dir() -> IO(Result<&1, &1, U32 & String, String>):
166 import "./effs/files.c"
167 import "./effs/files.js"
168
169
170
171def read.words(file: File, max: U32) ->
172 IO(File & Result<&1, &1, U32 & String, U32 & Array<U32>>):
173 import "./effs/files.c"
174 import "./effs/files.js"
175
176
177def size64(file: File) ->
178 IO(File & Result<&1, &1, U32 & String, U32 & U32>):
179 import "./effs/files.c"
180 import "./effs/files.js"
181
182
183def read.words.at(file: File, hi: U32, lo: U32, max: U32) ->
184 IO(File & Result<&1, &1, U32 & String, U32 & Array<U32>>):
185 import "./effs/files.c"
186 import "./effs/files.js"
187
188def read_bytes.wrap(result: Result<&1, &1, U32 & String, U32 & Array<U32>>) ->
189 Result<&1, &1, U32 & String, Bytes.Bytes>:
190 match result:
191 case Fail{err}:
192 Fail{err}
193 case Done{(len, words)}:
194 Done{Bytes.Bytes{len, words}}
195
196def read_bytes.pair(pair: File & Result<&1, &1, U32 & String, U32 & Array<U32>>) ->
197 File & Result<&1, &1, U32 & String, Bytes.Bytes>:
198 match pair:
199 case (handle, result):
200 (handle, read_bytes.wrap(result))
201
202
203def read_bytes(file: File, max: U32) ->
204 IO(File & Result<&1, &1, U32 & String, Bytes.Bytes>):
205 do IO<File & Result<&1, &1, U32 & String, Bytes.Bytes>>:
206 pair : File & Result<&1, &1, U32 & String, U32 & Array<U32>> <- read.words(file, max)
207 return read_bytes.pair(pair)
208
209
210
211def write.words(file: File, len: U32, words: Array<U32>) ->
212 IO(File & Result<&1, &1, U32 & String, Unit>):
213 import "./effs/files.c"
214 import "./effs/files.js"
215
216def write_bytes(file: File, data: Bytes.Bytes) ->
217 IO(File & Result<&1, &1, U32 & String, Unit>):
218 Bytes.Bytes{len, buf} = data
219 write.words(file, len, buf)
220
221def list_dir.fin(r: Result<&1, &1, U32 & String, String>) ->
222 Result<&1, &1, U32 & String, List<&2, String>>:
223 match r:
224 case Fail{e}:
225 Fail{e}
226 case Done{s}:
227 Done{List.sort(~String, ~String.is_le,
228 List.filter(~String, ~(n => Bool.not(String.is_empty(n))),
229 String.split(s, Chr{0})))}
230
231
232def list_dir(path: String) ->
233 IO(Result<&1, &1, U32 & String, List<&2, String>>):
234 do IO<Result<&1, &1, U32 & String, List<&2, String>>>:
235 r : Result<&1, &1, U32 & String, String> <- list_dir.raw(path)
236 return list_dir.fin(r)
237
238def stat.kind(+k: U32) -> FileType:
239 Bool.pick(FileType, U32.is_eq(k, 0), Regular{},
240 Bool.pick(FileType, U32.is_eq(k, 1), Directory{}, Other{}))
241
242def stat.fin(r: Result<&1, &1, U32 & String, U32 & U32 & U32>) ->
243 Result<&1, &1, U32 & String, Info>:
244 match r:
245 case Fail{e}:
246 Fail{e}
247 case Done{(k, size, mtime)}:
248 Done{Info{stat.kind(k), size, mtime}}
249
250def stat(path: String) -> IO(Result<&1, &1, U32 & String, Info>):
251 do IO<Result<&1, &1, U32 & String, Info>>:
252 r : Result<&1, &1, U32 & String, U32 & U32 & U32> <- stat.raw(path)
253 return stat.fin(r)
254
255def exists(path: String) -> IO(Bool):
256 do IO<Bool>:
257 r : Result<&1, &1, U32 & String, U32 & U32 & U32> <- stat.raw(path)
258 return Result.is_done(&1, &1, U32 & String, U32 & U32 & U32, r)
259
260def mkdir_all.dir(r: Result<&1, &1, U32 & String, Info>) ->
261 Result<&1, &1, U32 & String, Unit>:
262 match r:
263 case Fail{e}:
264 Fail{e}
265 case Done{Info{Directory{}, size, mtime}}:
266 Done{Unit{}}
267 case Done{i}:
268 Fail{(17, "File exists")}
269
270def mkdir_all.fail(exists: Bool, e: U32 & String,
271 k: IO(Result<&1, &1, U32 & String, Unit>)) ->
272 IO(Result<&1, &1, U32 & String, Unit>):
273 match exists:
274 case True{}:
275 k
276 case False{}:
277 IO.pure(Result<&1, &1, U32 & String, Unit>, Fail{e})
278
279def mkdir_all.step(r: Result<&1, &1, U32 & String, Unit>,
280 k: IO(Result<&1, &1, U32 & String, Unit>)) ->
281 IO(Result<&1, &1, U32 & String, Unit>):
282 match r:
283 case Fail{(+code, text)}:
284 mkdir_all.fail(U32.is_eq(code, 17), (code, text), k)
285 case Done{u}:
286 k
287
288def mkdir_all.go(segs: List<&2, String>, +at: String) ->
289 IO(Result<&1, &1, U32 & String, Unit>):
290 match segs:
291 case Nil{}:
292 do IO<Result<&1, &1, U32 & String, Unit>>:
293 r : Result<&1, &1, U32 & String, Info> <- stat(at)
294 return mkdir_all.dir(r)
295 case Con{s, t}:
296 +next = path.join(at, s)
297 do IO<Result<&1, &1, U32 & String, Unit>>:
298 r : Result<&1, &1, U32 & String, Unit> <- mkdir(next)
299 mkdir_all.step(r, mkdir_all.go(t, next))
300
301
302
303def mkdir_all(+path: String) -> IO(Result<&1, &1, U32 & String, Unit>):
304 mkdir_all.go(
305 List.filter(~String, ~(n => Bool.not(String.eq(n, ""))),
306 String.split(path, '/')),
307 Bool.pick(String, path.is_absolute(path), "/", "."))