@grain/stdlib 0.6.6 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/array.gr +55 -7
- package/array.md +606 -560
- package/bigint.md +228 -228
- package/buffer.gr +85 -53
- package/buffer.md +442 -319
- package/bytes.gr +112 -35
- package/bytes.md +299 -219
- package/char.gr +201 -99
- package/char.md +447 -120
- package/exception.gr +11 -11
- package/exception.md +29 -4
- package/float32.gr +327 -3
- package/float32.md +698 -111
- package/float64.gr +320 -3
- package/float64.md +698 -111
- package/fs.gr +1082 -0
- package/fs.md +630 -0
- package/hash.gr +142 -88
- package/hash.md +105 -17
- package/int16.md +178 -178
- package/int32.gr +26 -5
- package/int32.md +266 -231
- package/int64.gr +27 -2
- package/int64.md +266 -231
- package/int8.md +178 -178
- package/json.gr +366 -51
- package/json.md +431 -15
- package/list.gr +328 -31
- package/list.md +759 -336
- package/map.gr +20 -12
- package/map.md +266 -260
- package/marshal.gr +41 -40
- package/marshal.md +14 -14
- package/number.gr +278 -35
- package/number.md +688 -269
- package/option.md +162 -162
- package/package.json +5 -3
- package/path.gr +48 -0
- package/path.md +180 -89
- package/pervasives.gr +2 -2
- package/pervasives.md +275 -275
- package/priorityqueue.gr +7 -7
- package/priorityqueue.md +131 -131
- package/queue.gr +183 -29
- package/queue.md +404 -148
- package/random.md +43 -43
- package/range.gr +4 -4
- package/range.md +42 -42
- package/rational.md +123 -123
- package/regex.gr +52 -51
- package/regex.md +102 -102
- package/result.md +118 -118
- package/runtime/atof/common.md +39 -39
- package/runtime/atof/decimal.gr +6 -6
- package/runtime/atof/decimal.md +14 -14
- package/runtime/atof/lemire.gr +5 -5
- package/runtime/atof/lemire.md +1 -1
- package/runtime/atof/parse.gr +16 -16
- package/runtime/atof/parse.md +2 -2
- package/runtime/atof/slow.md +1 -1
- package/runtime/atof/table.md +2 -2
- package/runtime/atoi/parse.gr +3 -3
- package/runtime/atoi/parse.md +1 -1
- package/runtime/bigint.gr +15 -47
- package/runtime/bigint.md +54 -60
- package/runtime/compare.gr +2 -2
- package/runtime/compare.md +8 -8
- package/runtime/dataStructures.md +211 -211
- package/runtime/debugPrint.gr +4 -1
- package/runtime/debugPrint.md +9 -9
- package/runtime/equal.gr +99 -77
- package/runtime/equal.md +8 -8
- package/runtime/exception.gr +62 -82
- package/runtime/exception.md +62 -11
- package/runtime/gc.gr +39 -45
- package/runtime/gc.md +4 -4
- package/runtime/malloc.gr +7 -7
- package/runtime/malloc.md +13 -13
- package/runtime/math/kernel/cos.gr +70 -0
- package/runtime/math/kernel/cos.md +14 -0
- package/runtime/math/kernel/sin.gr +65 -0
- package/runtime/math/kernel/sin.md +14 -0
- package/runtime/math/kernel/tan.gr +136 -0
- package/runtime/math/kernel/tan.md +14 -0
- package/runtime/math/rempio2.gr +244 -0
- package/runtime/math/rempio2.md +14 -0
- package/runtime/math/trig.gr +130 -0
- package/runtime/math/trig.md +28 -0
- package/runtime/math/umuldi.gr +26 -0
- package/runtime/math/umuldi.md +14 -0
- package/runtime/numberUtils.gr +29 -29
- package/runtime/numberUtils.md +12 -12
- package/runtime/numbers.gr +373 -381
- package/runtime/numbers.md +348 -342
- package/runtime/string.gr +37 -105
- package/runtime/string.md +20 -26
- package/runtime/unsafe/constants.md +24 -24
- package/runtime/unsafe/conv.md +19 -19
- package/runtime/unsafe/memory.gr +24 -20
- package/runtime/unsafe/memory.md +27 -7
- package/runtime/unsafe/offsets.gr +36 -0
- package/runtime/unsafe/offsets.md +88 -0
- package/runtime/unsafe/panic.gr +28 -0
- package/runtime/unsafe/panic.md +14 -0
- package/runtime/unsafe/tags.md +32 -32
- package/runtime/unsafe/wasmf32.md +28 -28
- package/runtime/unsafe/wasmf64.md +28 -28
- package/runtime/unsafe/wasmi32.md +47 -47
- package/runtime/unsafe/wasmi64.md +50 -50
- package/runtime/utf8.gr +189 -0
- package/runtime/utf8.md +117 -0
- package/runtime/wasi.gr +4 -2
- package/runtime/wasi.md +147 -147
- package/set.gr +18 -11
- package/set.md +253 -247
- package/stack.gr +171 -2
- package/stack.md +371 -89
- package/string.gr +352 -557
- package/string.md +298 -255
- package/uint16.md +170 -170
- package/uint32.gr +25 -4
- package/uint32.md +249 -214
- package/uint64.gr +25 -5
- package/uint64.md +249 -214
- package/uint8.md +170 -170
- package/uri.gr +57 -53
- package/uri.md +88 -89
- package/wasi/file.gr +67 -59
- package/wasi/file.md +308 -308
- package/wasi/process.md +26 -26
- package/wasi/random.md +12 -12
- package/wasi/time.md +16 -16
- package/runtime/utils/printing.gr +0 -60
- package/runtime/utils/printing.md +0 -26
package/fs.gr
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for high-level file system interactions. Utilizes WASI Preview 1 for underlying API
|
|
3
|
+
*
|
|
4
|
+
* @example from "fs" include Fs
|
|
5
|
+
* @example Fs.Utf8.readFile(Path.fromString("baz.txt"))
|
|
6
|
+
* @example Fs.Utf8.writeFile(Path.fromString("baz.txt"), "Hello World\n")
|
|
7
|
+
* @example Fs.copy(Path.fromString("foo.txt"), Path.fromString("foocopy.txt"))
|
|
8
|
+
*
|
|
9
|
+
* @since v0.7.0
|
|
10
|
+
*/
|
|
11
|
+
module Fs
|
|
12
|
+
|
|
13
|
+
from "array" include Array
|
|
14
|
+
from "bytes" include Bytes
|
|
15
|
+
from "int64" include Int64
|
|
16
|
+
from "option" include Option
|
|
17
|
+
from "path" include Path
|
|
18
|
+
from "result" include Result
|
|
19
|
+
from "string" include String
|
|
20
|
+
from "wasi/file" include File
|
|
21
|
+
from "runtime/wasi" include Wasi
|
|
22
|
+
from "runtime/unsafe/wasmi32" include WasmI32
|
|
23
|
+
from "runtime/dataStructures" include DataStructures
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Potential errors that can be raised from system interactions.
|
|
27
|
+
*/
|
|
28
|
+
provide enum FileError {
|
|
29
|
+
NoPreopenedDirectories,
|
|
30
|
+
PermissionDenied,
|
|
31
|
+
AddressInUse,
|
|
32
|
+
AddressNotAvailable,
|
|
33
|
+
AddressFamilyNotSupported,
|
|
34
|
+
ResourceUnavailableOrOperationWouldBlock,
|
|
35
|
+
ConnectionAlreadyInProgress,
|
|
36
|
+
BadFileDescriptor,
|
|
37
|
+
BadMessage,
|
|
38
|
+
DeviceOrResourceBusy,
|
|
39
|
+
OperationCanceled,
|
|
40
|
+
NoChildProcesses,
|
|
41
|
+
ConnectionAborted,
|
|
42
|
+
ConnectionRefused,
|
|
43
|
+
ConnectionReset,
|
|
44
|
+
ResourceDeadlockWouldOccur,
|
|
45
|
+
DestinationAddressRequired,
|
|
46
|
+
MathematicsArgumentOutOfDomainOfFunction,
|
|
47
|
+
FileExists,
|
|
48
|
+
BadAddress,
|
|
49
|
+
FileTooLarge,
|
|
50
|
+
HostIsUnreachable,
|
|
51
|
+
IdentifierRemoved,
|
|
52
|
+
IllegalByteSequence,
|
|
53
|
+
OperationInProgress,
|
|
54
|
+
InterruptedFunction,
|
|
55
|
+
InvalidArgument,
|
|
56
|
+
IOError,
|
|
57
|
+
SocketIsConnected,
|
|
58
|
+
IsADirectory,
|
|
59
|
+
TooManyLevelsOfSymbolicLinks,
|
|
60
|
+
FileDescriptorValueTooLarge,
|
|
61
|
+
TooManyLinks,
|
|
62
|
+
MessageTooLarge,
|
|
63
|
+
FilenameTooLong,
|
|
64
|
+
NetworkIsDown,
|
|
65
|
+
ConnectionAbortedByNetwork,
|
|
66
|
+
NetworkUnreachable,
|
|
67
|
+
TooManyFilesOpenInSystem,
|
|
68
|
+
NoBufferSpaceAvailable,
|
|
69
|
+
NoSuchDevice,
|
|
70
|
+
NoSuchFileOrDirectory,
|
|
71
|
+
ExecutableFileFormatError,
|
|
72
|
+
NoLocksAvailable,
|
|
73
|
+
NotEnoughSpace,
|
|
74
|
+
NoMessageOfTheDesiredType,
|
|
75
|
+
ProtocolNotAvailable,
|
|
76
|
+
NoSpaceLeftOnDevice,
|
|
77
|
+
FunctionNotSupported,
|
|
78
|
+
TheSocketIsNotConnected,
|
|
79
|
+
NotADirectoryOrASymbolicLinkToADirectory,
|
|
80
|
+
DirectoryNotEmpty,
|
|
81
|
+
StateNotRecoverable,
|
|
82
|
+
NotASocket,
|
|
83
|
+
NotSupportedOrOperationNotSupportedOnSocket,
|
|
84
|
+
InappropriateIOControlOperation,
|
|
85
|
+
NoSuchDeviceOrAddress,
|
|
86
|
+
ValueTooLargeToBeStoredInDataType,
|
|
87
|
+
PreviousOwnerDied,
|
|
88
|
+
OperationNotPermitted,
|
|
89
|
+
BrokenPipe,
|
|
90
|
+
ProtocolError,
|
|
91
|
+
ProtocolNotSupported,
|
|
92
|
+
ProtocolWrongTypeForSocket,
|
|
93
|
+
ResultTooLarge,
|
|
94
|
+
ReadOnlyFileSystem,
|
|
95
|
+
InvalidSeek,
|
|
96
|
+
NoSuchProcess,
|
|
97
|
+
ConnectionTimedOut,
|
|
98
|
+
TextFileBusy,
|
|
99
|
+
CrossDeviceLink,
|
|
100
|
+
ExtensionCapabilitiesInsufficient,
|
|
101
|
+
UnknownFileError(Number),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let errnoToFileError = errno => {
|
|
105
|
+
match (errno) {
|
|
106
|
+
2 => PermissionDenied,
|
|
107
|
+
3 => AddressInUse,
|
|
108
|
+
4 => AddressNotAvailable,
|
|
109
|
+
5 => AddressFamilyNotSupported,
|
|
110
|
+
6 => ResourceUnavailableOrOperationWouldBlock,
|
|
111
|
+
7 => ConnectionAlreadyInProgress,
|
|
112
|
+
8 => BadFileDescriptor,
|
|
113
|
+
9 => BadMessage,
|
|
114
|
+
10 => DeviceOrResourceBusy,
|
|
115
|
+
11 => OperationCanceled,
|
|
116
|
+
12 => NoChildProcesses,
|
|
117
|
+
13 => ConnectionAborted,
|
|
118
|
+
14 => ConnectionRefused,
|
|
119
|
+
15 => ConnectionReset,
|
|
120
|
+
16 => ResourceDeadlockWouldOccur,
|
|
121
|
+
17 => DestinationAddressRequired,
|
|
122
|
+
18 => MathematicsArgumentOutOfDomainOfFunction,
|
|
123
|
+
20 => FileExists,
|
|
124
|
+
21 => BadAddress,
|
|
125
|
+
22 => FileTooLarge,
|
|
126
|
+
23 => HostIsUnreachable,
|
|
127
|
+
24 => IdentifierRemoved,
|
|
128
|
+
25 => IllegalByteSequence,
|
|
129
|
+
26 => OperationInProgress,
|
|
130
|
+
27 => InterruptedFunction,
|
|
131
|
+
28 => InvalidArgument,
|
|
132
|
+
29 => IOError,
|
|
133
|
+
30 => SocketIsConnected,
|
|
134
|
+
31 => IsADirectory,
|
|
135
|
+
32 => TooManyLevelsOfSymbolicLinks,
|
|
136
|
+
33 => FileDescriptorValueTooLarge,
|
|
137
|
+
34 => TooManyLinks,
|
|
138
|
+
35 => MessageTooLarge,
|
|
139
|
+
37 => FilenameTooLong,
|
|
140
|
+
38 => NetworkIsDown,
|
|
141
|
+
39 => ConnectionAbortedByNetwork,
|
|
142
|
+
40 => NetworkUnreachable,
|
|
143
|
+
41 => TooManyFilesOpenInSystem,
|
|
144
|
+
42 => NoBufferSpaceAvailable,
|
|
145
|
+
43 => NoSuchDevice,
|
|
146
|
+
44 => NoSuchFileOrDirectory,
|
|
147
|
+
45 => ExecutableFileFormatError,
|
|
148
|
+
46 => NoLocksAvailable,
|
|
149
|
+
48 => NotEnoughSpace,
|
|
150
|
+
49 => NoMessageOfTheDesiredType,
|
|
151
|
+
50 => ProtocolNotAvailable,
|
|
152
|
+
51 => NoSpaceLeftOnDevice,
|
|
153
|
+
52 => FunctionNotSupported,
|
|
154
|
+
53 => TheSocketIsNotConnected,
|
|
155
|
+
54 => NotADirectoryOrASymbolicLinkToADirectory,
|
|
156
|
+
55 => DirectoryNotEmpty,
|
|
157
|
+
56 => StateNotRecoverable,
|
|
158
|
+
57 => NotASocket,
|
|
159
|
+
58 => NotSupportedOrOperationNotSupportedOnSocket,
|
|
160
|
+
59 => InappropriateIOControlOperation,
|
|
161
|
+
60 => NoSuchDeviceOrAddress,
|
|
162
|
+
61 => ValueTooLargeToBeStoredInDataType,
|
|
163
|
+
62 => PreviousOwnerDied,
|
|
164
|
+
63 => OperationNotPermitted,
|
|
165
|
+
64 => BrokenPipe,
|
|
166
|
+
65 => ProtocolError,
|
|
167
|
+
66 => ProtocolNotSupported,
|
|
168
|
+
67 => ProtocolWrongTypeForSocket,
|
|
169
|
+
68 => ResultTooLarge,
|
|
170
|
+
69 => ReadOnlyFileSystem,
|
|
171
|
+
70 => InvalidSeek,
|
|
172
|
+
71 => NoSuchProcess,
|
|
173
|
+
73 => ConnectionTimedOut,
|
|
174
|
+
74 => TextFileBusy,
|
|
175
|
+
75 => CrossDeviceLink,
|
|
176
|
+
76 => ExtensionCapabilitiesInsufficient,
|
|
177
|
+
_ => UnknownFileError(errno),
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Represents non-standard system file types.
|
|
183
|
+
*/
|
|
184
|
+
provide enum SpecialFileType {
|
|
185
|
+
Unknown,
|
|
186
|
+
BlockDevice,
|
|
187
|
+
CharacterDevice,
|
|
188
|
+
SocketDatagram,
|
|
189
|
+
SocketStream,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Represents different system file types.
|
|
194
|
+
*/
|
|
195
|
+
provide enum FileType {
|
|
196
|
+
File,
|
|
197
|
+
Directory,
|
|
198
|
+
SymbolicLink,
|
|
199
|
+
Special(SpecialFileType),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Represents metadata about a file.
|
|
204
|
+
*/
|
|
205
|
+
provide record Stats {
|
|
206
|
+
fileType: FileType,
|
|
207
|
+
/** File size in bytes */
|
|
208
|
+
size: Number,
|
|
209
|
+
/** Last accessed timestamp in nanoseconds */
|
|
210
|
+
accessedTimestamp: Number,
|
|
211
|
+
/** Last modified timestamp in nanoseconds */
|
|
212
|
+
modifiedTimestamp: Number,
|
|
213
|
+
/** Last file status change timestamp in nanoseconds */
|
|
214
|
+
changedTimestamp: Number,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Represents information about an item in a directory.
|
|
219
|
+
*/
|
|
220
|
+
provide record DirectoryEntry {
|
|
221
|
+
name: String,
|
|
222
|
+
fileType: FileType,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The type of removal operation to perform when calling `remove`.
|
|
227
|
+
*/
|
|
228
|
+
provide enum RemoveMode {
|
|
229
|
+
RemoveFile,
|
|
230
|
+
RemoveEmptyDirectory,
|
|
231
|
+
RemoveRecursive,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The type of copy operation to perform when calling `copy`.
|
|
236
|
+
*/
|
|
237
|
+
provide enum CopyMode {
|
|
238
|
+
CopyFile,
|
|
239
|
+
CopyRecursive,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The type of write operation to perform when calling `writeFile`.
|
|
244
|
+
*/
|
|
245
|
+
provide enum WriteMode {
|
|
246
|
+
Truncate,
|
|
247
|
+
Append,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
enum OpenMode {
|
|
251
|
+
Unlink,
|
|
252
|
+
RmDir,
|
|
253
|
+
RmrfDir,
|
|
254
|
+
CopySourceBase,
|
|
255
|
+
CopyTargetBase,
|
|
256
|
+
CopySource,
|
|
257
|
+
CopyTarget,
|
|
258
|
+
RenameSource,
|
|
259
|
+
RenameTarget,
|
|
260
|
+
ReadDir,
|
|
261
|
+
MakeDir,
|
|
262
|
+
MakeSymlink,
|
|
263
|
+
ReadLink,
|
|
264
|
+
Stats,
|
|
265
|
+
Read(Bool),
|
|
266
|
+
WriteFile(Bool),
|
|
267
|
+
AppendFile(Bool),
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let fileResult = result => {
|
|
271
|
+
match (result) {
|
|
272
|
+
Ok(ok) => Ok(ok),
|
|
273
|
+
Err(Wasi.SystemError(err)) => Err(errnoToFileError(err)),
|
|
274
|
+
_ => fail "Impossible: non-wasi error",
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let openInfo = openMode => {
|
|
279
|
+
match (openMode) {
|
|
280
|
+
Unlink => ([File.Directory: File.OpenFlag], [File.PathUnlinkFile], [], []),
|
|
281
|
+
RmDir => ([File.Directory], [File.PathRemoveDirectory], [], []),
|
|
282
|
+
RmrfDir => {
|
|
283
|
+
let rights = [
|
|
284
|
+
File.PathOpen,
|
|
285
|
+
File.PathFilestats,
|
|
286
|
+
File.PathRemoveDirectory,
|
|
287
|
+
File.PathUnlinkFile,
|
|
288
|
+
File.FdReaddir,
|
|
289
|
+
]
|
|
290
|
+
([File.Directory], rights, rights, [])
|
|
291
|
+
},
|
|
292
|
+
CopySourceBase => {
|
|
293
|
+
let rightsInheriting = [
|
|
294
|
+
File.PathOpen,
|
|
295
|
+
File.FdReaddir,
|
|
296
|
+
File.PathFilestats,
|
|
297
|
+
File.PathReadlink,
|
|
298
|
+
]
|
|
299
|
+
(
|
|
300
|
+
[File.Directory],
|
|
301
|
+
rightsInheriting,
|
|
302
|
+
[File.FdRead, ...rightsInheriting],
|
|
303
|
+
[],
|
|
304
|
+
)
|
|
305
|
+
},
|
|
306
|
+
CopyTargetBase => {
|
|
307
|
+
let rightsInheriting = [
|
|
308
|
+
File.PathOpen,
|
|
309
|
+
File.PathCreateDirectory,
|
|
310
|
+
File.PathCreateFile,
|
|
311
|
+
File.PathSetSize,
|
|
312
|
+
File.PathSymlink,
|
|
313
|
+
]
|
|
314
|
+
(
|
|
315
|
+
[File.Directory],
|
|
316
|
+
rightsInheriting,
|
|
317
|
+
[File.FdWrite, ...rightsInheriting],
|
|
318
|
+
[],
|
|
319
|
+
)
|
|
320
|
+
},
|
|
321
|
+
CopySource => ([], [File.FdRead], [], []),
|
|
322
|
+
CopyTarget => ([File.Create, File.Truncate], [File.FdWrite], [], []),
|
|
323
|
+
RenameSource => ([File.Directory], [File.PathRenameSource], [], []),
|
|
324
|
+
RenameTarget =>
|
|
325
|
+
([File.Directory], [File.PathRenameTarget, File.PathFilestats], [], []),
|
|
326
|
+
ReadDir => ([File.Directory], [File.FdReaddir], [], []),
|
|
327
|
+
MakeDir => ([File.Directory], [File.PathCreateDirectory], [], []),
|
|
328
|
+
MakeSymlink => ([File.Directory], [File.PathSymlink], [], []),
|
|
329
|
+
ReadLink =>
|
|
330
|
+
([File.Directory], [File.PathFilestats, File.PathReadlink], [], []),
|
|
331
|
+
Stats => ([File.Directory], [File.PathFilestats], [], []),
|
|
332
|
+
Read(sync) =>
|
|
333
|
+
([], [File.FdRead, File.FdFilestats], [], if (sync) [File.Rsync] else []),
|
|
334
|
+
WriteFile(sync) =>
|
|
335
|
+
(
|
|
336
|
+
[File.Create, File.Truncate],
|
|
337
|
+
[File.FdWrite],
|
|
338
|
+
[],
|
|
339
|
+
if (sync) [File.Sync] else [],
|
|
340
|
+
),
|
|
341
|
+
AppendFile(sync) =>
|
|
342
|
+
(
|
|
343
|
+
[File.Create],
|
|
344
|
+
[File.FdWrite, File.FdSeek],
|
|
345
|
+
[],
|
|
346
|
+
[File.Append, ...if (sync) [File.Sync] else []],
|
|
347
|
+
),
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let open = (parentFd=None, path, openInfo) => {
|
|
352
|
+
let (openFlags, rights, rightsInheriting, flags) = openInfo
|
|
353
|
+
let noPreopens = Result.isErr(File.fdPrestatGet(File.FileDescriptor(3)))
|
|
354
|
+
if (noPreopens) {
|
|
355
|
+
// Exists for UX purposes, to let the user know if they forgot to specify preopens
|
|
356
|
+
Err(NoPreopenedDirectories)
|
|
357
|
+
} else {
|
|
358
|
+
fileResult(match (parentFd) {
|
|
359
|
+
None =>
|
|
360
|
+
File.open(
|
|
361
|
+
Path.toString(path),
|
|
362
|
+
openFlags,
|
|
363
|
+
rights,
|
|
364
|
+
rightsInheriting,
|
|
365
|
+
flags
|
|
366
|
+
),
|
|
367
|
+
Some(parentFd) =>
|
|
368
|
+
File.pathOpen(
|
|
369
|
+
parentFd,
|
|
370
|
+
[File.SymlinkFollow],
|
|
371
|
+
Path.toString(path),
|
|
372
|
+
openFlags,
|
|
373
|
+
rights,
|
|
374
|
+
rightsInheriting,
|
|
375
|
+
flags
|
|
376
|
+
),
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let applyFileOp = (fileDescrResult, fn) => {
|
|
382
|
+
Result.flatMap(fd => {
|
|
383
|
+
let result = fn(fd)
|
|
384
|
+
File.fdClose(fd)
|
|
385
|
+
result
|
|
386
|
+
}, fileDescrResult)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Opens the specified file and applies a "wasi/file" function to the resulting FD, yielding a wrapped FileError
|
|
390
|
+
let fileOp = (
|
|
391
|
+
parentFd=None,
|
|
392
|
+
path,
|
|
393
|
+
openMode,
|
|
394
|
+
fn: File.FileDescriptor => Result<a, Exception>,
|
|
395
|
+
) => {
|
|
396
|
+
let fdResult = open(parentFd=parentFd, path, openInfo(openMode))
|
|
397
|
+
applyFileOp(fdResult, fd => fileResult(fn(fd)))
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Similar to the above function, but for when the passed in function yields a FileError rather than a "wasi/file" error
|
|
401
|
+
let wrappingFileOp = (
|
|
402
|
+
parentFd=None,
|
|
403
|
+
path,
|
|
404
|
+
openMode,
|
|
405
|
+
fn: File.FileDescriptor => Result<a, FileError>,
|
|
406
|
+
) => {
|
|
407
|
+
let fdResult = open(parentFd=parentFd, path, openInfo(openMode))
|
|
408
|
+
applyFileOp(fdResult, fn)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Convenience function for when 2 files need to be opened in tandem for an operation
|
|
412
|
+
let fileOp2 = (
|
|
413
|
+
firstParentFd=None,
|
|
414
|
+
firstPath,
|
|
415
|
+
firstOpenType,
|
|
416
|
+
secondParentFd=None,
|
|
417
|
+
secondPath,
|
|
418
|
+
secondOpenType,
|
|
419
|
+
fn,
|
|
420
|
+
) => {
|
|
421
|
+
wrappingFileOp(
|
|
422
|
+
parentFd=firstParentFd,
|
|
423
|
+
firstPath,
|
|
424
|
+
firstOpenType,
|
|
425
|
+
firstFd => {
|
|
426
|
+
fileOp(
|
|
427
|
+
parentFd=secondParentFd,
|
|
428
|
+
secondPath,
|
|
429
|
+
secondOpenType,
|
|
430
|
+
secondFd => {
|
|
431
|
+
fn(firstFd, secondFd)
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let wrappingFileOp2 = (
|
|
439
|
+
firstParentFd=None,
|
|
440
|
+
firstPath,
|
|
441
|
+
firstOpenType,
|
|
442
|
+
secondParentFd=None,
|
|
443
|
+
secondPath,
|
|
444
|
+
secondOpenType,
|
|
445
|
+
fn,
|
|
446
|
+
) => {
|
|
447
|
+
wrappingFileOp(
|
|
448
|
+
parentFd=firstParentFd,
|
|
449
|
+
firstPath,
|
|
450
|
+
firstOpenType,
|
|
451
|
+
firstFd => {
|
|
452
|
+
wrappingFileOp(
|
|
453
|
+
parentFd=secondParentFd,
|
|
454
|
+
secondPath,
|
|
455
|
+
secondOpenType,
|
|
456
|
+
secondFd => {
|
|
457
|
+
fn(firstFd, secondFd)
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let joinPathOnBaseDir = (baseDirPath, path) => {
|
|
465
|
+
match (baseDirPath) {
|
|
466
|
+
None => path,
|
|
467
|
+
Some(base) => {
|
|
468
|
+
// Provide leniency if the base dir path does not contain a trailing slash
|
|
469
|
+
let base = if (!Path.isDirectory(base))
|
|
470
|
+
Path.fromString(Path.toString(base) ++ "/")
|
|
471
|
+
else
|
|
472
|
+
base
|
|
473
|
+
|
|
474
|
+
match (Path.append(base, path)) {
|
|
475
|
+
Ok(appended) => appended,
|
|
476
|
+
Err(Path.AppendAbsolute) => path,
|
|
477
|
+
Err(Path.AppendToFile) =>
|
|
478
|
+
fail "Impossible: getPathWithBaseDir append to directory failed with AppendToFile",
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// This function is used rather than the above one when the base dir is needed
|
|
485
|
+
let getBaseDirAndPath = (baseDirPath, path) => {
|
|
486
|
+
match (baseDirPath) {
|
|
487
|
+
None =>
|
|
488
|
+
(
|
|
489
|
+
Path.parent(path),
|
|
490
|
+
Path.fromString(Option.unwrapWithDefault(".", Path.basename(path))),
|
|
491
|
+
),
|
|
492
|
+
Some(base) => (base, path),
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
let wrapFileType = (filetype: File.Filetype) => match (filetype) {
|
|
497
|
+
x when x == File.Directory => Directory,
|
|
498
|
+
File.RegularFile => File,
|
|
499
|
+
File.SymbolicLink => SymbolicLink,
|
|
500
|
+
File.Unknown => Special(Unknown),
|
|
501
|
+
File.BlockDevice => Special(BlockDevice),
|
|
502
|
+
File.CharacterDevice => Special(CharacterDevice),
|
|
503
|
+
File.SocketDatagram => Special(SocketDatagram),
|
|
504
|
+
File.SocketStream => Special(SocketStream),
|
|
505
|
+
_ => fail "Impossible: filetype match",
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let rec removeRecursive = (parentFd, path) => {
|
|
509
|
+
let statsResult = fileResult(File.pathFilestats(parentFd, [], path))
|
|
510
|
+
// Attempt remove on all directory items but keep track of success
|
|
511
|
+
Result.flatMap((stats: File.Filestats) => {
|
|
512
|
+
if (stats.filetype == File.Directory) {
|
|
513
|
+
wrappingFileOp(
|
|
514
|
+
parentFd=Some(parentFd),
|
|
515
|
+
Path.fromString(path),
|
|
516
|
+
RmrfDir,
|
|
517
|
+
fd => {
|
|
518
|
+
use Result.{ (&&) }
|
|
519
|
+
let removeEntries = entries => {
|
|
520
|
+
Array.reduce(
|
|
521
|
+
(acc, entry: File.DirectoryEntry) => {
|
|
522
|
+
let result = removeRecursive(fd, entry.path)
|
|
523
|
+
acc && result
|
|
524
|
+
},
|
|
525
|
+
Ok(void),
|
|
526
|
+
entries
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
Result.flatMap(removeEntries, fileResult(File.fdReaddir(fd)))
|
|
530
|
+
&& fileResult(File.pathRemoveDirectory(parentFd, path))
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
} else {
|
|
534
|
+
fileResult(File.pathUnlink(parentFd, path))
|
|
535
|
+
}
|
|
536
|
+
}, statsResult)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Removes a file or directory.
|
|
541
|
+
*
|
|
542
|
+
* @param removeMode: The type of removal to perform; `RemoveFile` by default
|
|
543
|
+
* @param baseDirPath: The path to the directory in which path resolution starts
|
|
544
|
+
* @param path: The path of the file or directory to remove
|
|
545
|
+
* @returns `Ok(void)` if the operation succeeds, `Err(err)` if a file system error is encountered
|
|
546
|
+
*
|
|
547
|
+
* @example Fs.remove(Path.fromString("file.txt")) // removes a file
|
|
548
|
+
* @example Fs.remove(removeMode=Fs.RemoveEmptyDirectory, Path.fromString("dir")) // removes an empty directory
|
|
549
|
+
* @example Fs.remove(removeMode=Fs.RemoveRecursive, Path.fromString("dir")) // removes the directory and its contents
|
|
550
|
+
*
|
|
551
|
+
* @since v0.7.0
|
|
552
|
+
*/
|
|
553
|
+
provide let remove = (removeMode=RemoveFile, baseDirPath=None, path) => {
|
|
554
|
+
let (baseDirPath, path) = getBaseDirAndPath(baseDirPath, path)
|
|
555
|
+
match (removeMode) {
|
|
556
|
+
RemoveFile =>
|
|
557
|
+
fileOp(
|
|
558
|
+
baseDirPath,
|
|
559
|
+
Unlink,
|
|
560
|
+
baseDirFd => File.pathUnlink(baseDirFd, Path.toString(path))
|
|
561
|
+
),
|
|
562
|
+
RemoveEmptyDirectory =>
|
|
563
|
+
fileOp(
|
|
564
|
+
baseDirPath,
|
|
565
|
+
RmDir,
|
|
566
|
+
baseDirFd => File.pathRemoveDirectory(baseDirFd, Path.toString(path))
|
|
567
|
+
),
|
|
568
|
+
RemoveRecursive =>
|
|
569
|
+
wrappingFileOp(
|
|
570
|
+
baseDirPath,
|
|
571
|
+
RmrfDir,
|
|
572
|
+
baseDirFd => removeRecursive(baseDirFd, Path.toString(path))
|
|
573
|
+
),
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Reads the contents of a directory.
|
|
579
|
+
*
|
|
580
|
+
* @param baseDirPath: The path to the directory in which resolution should begin
|
|
581
|
+
* @param path: The path to the directory to read
|
|
582
|
+
* @returns `Ok(contents)` containing the directory contents or `Err(err)` if a file system error is encountered
|
|
583
|
+
*
|
|
584
|
+
* @since v0.7.0
|
|
585
|
+
*/
|
|
586
|
+
provide let readDir = (baseDirPath=None, path) => {
|
|
587
|
+
let path = joinPathOnBaseDir(baseDirPath, path)
|
|
588
|
+
let result = fileOp(path, ReadDir, File.fdReaddir)
|
|
589
|
+
Result.map(entries => {
|
|
590
|
+
let entries = Array.map(
|
|
591
|
+
(entry: File.DirectoryEntry) =>
|
|
592
|
+
{ name: entry.path, fileType: wrapFileType(entry.filetype) },
|
|
593
|
+
entries
|
|
594
|
+
)
|
|
595
|
+
Array.toList(entries)
|
|
596
|
+
}, result)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Creates a new empty directory at the given path.
|
|
601
|
+
*
|
|
602
|
+
* @param baseDirPath: The path to the directory in which resolution should begin
|
|
603
|
+
* @param path: The path to create the new directory, relative to the base directory
|
|
604
|
+
* @returns `Ok(void)` if the operation succeeds, `Err(err)` if a file system error is encountered
|
|
605
|
+
*
|
|
606
|
+
* @since v0.7.0
|
|
607
|
+
*/
|
|
608
|
+
provide let createDir = (baseDirPath=None, path) => {
|
|
609
|
+
let (baseDirPath, path) = getBaseDirAndPath(baseDirPath, path)
|
|
610
|
+
fileOp(
|
|
611
|
+
baseDirPath,
|
|
612
|
+
MakeDir,
|
|
613
|
+
dirFd => File.pathCreateDirectory(dirFd, Path.toString(path))
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Creates a new symbolic link with the given contents.
|
|
619
|
+
*
|
|
620
|
+
* @param linkContents: The path to store into the link
|
|
621
|
+
* @param targetBaseDirPath: The path to the directory in which the target path resolution starts
|
|
622
|
+
* @param targetPath: The path to the target of the link
|
|
623
|
+
* @returns `Ok(void)` if the operation succeeds, `Err(err)` if a file system error or relativization error is encountered
|
|
624
|
+
*
|
|
625
|
+
* @since v0.7.0
|
|
626
|
+
*/
|
|
627
|
+
provide let createSymlink = (linkContents, targetBaseDirPath=None, targetPath) => {
|
|
628
|
+
let (targetBaseDirPath, targetPath) = getBaseDirAndPath(
|
|
629
|
+
targetBaseDirPath,
|
|
630
|
+
targetPath
|
|
631
|
+
)
|
|
632
|
+
fileOp(
|
|
633
|
+
targetBaseDirPath,
|
|
634
|
+
MakeSymlink,
|
|
635
|
+
dirFd =>
|
|
636
|
+
File.pathSymlink(
|
|
637
|
+
dirFd,
|
|
638
|
+
Path.toString(linkContents),
|
|
639
|
+
Path.toString(targetPath)
|
|
640
|
+
)
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Queries information about a file.
|
|
646
|
+
*
|
|
647
|
+
* @param followSymlink: Whether to follow symlinks or not; if `true` then the stats of a valid symlink's underlying file will be returned. `true` by default
|
|
648
|
+
* @param baseDirPath: The path to the directory in which the path resolution starts
|
|
649
|
+
* @param path: The path of the file to query
|
|
650
|
+
* @returns `Ok(stats)` containing metadata or `Err(err)` if a file system error is encountered
|
|
651
|
+
*
|
|
652
|
+
* @since v0.7.0
|
|
653
|
+
*/
|
|
654
|
+
provide let stats = (followSymlink=true, baseDirPath=None, path) => {
|
|
655
|
+
let (baseDirPath, path) = getBaseDirAndPath(baseDirPath, path)
|
|
656
|
+
let statsResult = fileOp(baseDirPath, Stats, dirFd => {
|
|
657
|
+
File.pathFilestats(
|
|
658
|
+
dirFd,
|
|
659
|
+
if (followSymlink) [File.SymlinkFollow] else [],
|
|
660
|
+
Path.toString(path)
|
|
661
|
+
)
|
|
662
|
+
})
|
|
663
|
+
Result.map(
|
|
664
|
+
(stats: File.Filestats) =>
|
|
665
|
+
{
|
|
666
|
+
size: Int64.toNumber(stats.size),
|
|
667
|
+
fileType: wrapFileType(stats.filetype),
|
|
668
|
+
accessedTimestamp: Int64.toNumber(stats.accessed),
|
|
669
|
+
modifiedTimestamp: Int64.toNumber(stats.modified),
|
|
670
|
+
changedTimestamp: Int64.toNumber(stats.changed),
|
|
671
|
+
},
|
|
672
|
+
statsResult
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Polls whether or not a file or directory exists at the given path.
|
|
678
|
+
*
|
|
679
|
+
* @param baseDirPath: The path to the directory in which the path resolution starts
|
|
680
|
+
* @param path: The path of the file to query
|
|
681
|
+
* @returns `true` if a file or directory exists at the path or `false` otherwise
|
|
682
|
+
*
|
|
683
|
+
* @since v0.7.0
|
|
684
|
+
*/
|
|
685
|
+
provide let exists = (baseDirPath=None, path) => {
|
|
686
|
+
stats(baseDirPath=baseDirPath, path, followSymlink=false)
|
|
687
|
+
!= Err(NoSuchFileOrDirectory)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let readLinkHelper = (dirFd, path, stats: File.Filestats) => {
|
|
691
|
+
// Need to strip trailing null terminator
|
|
692
|
+
let linkContentsResult = File.pathReadlink(
|
|
693
|
+
dirFd,
|
|
694
|
+
Path.toString(path),
|
|
695
|
+
Int64.toNumber(stats.size) + 1
|
|
696
|
+
)
|
|
697
|
+
let fst = ((contents, _)) =>
|
|
698
|
+
String.slice(0, end=String.byteLength(contents) - 1, contents)
|
|
699
|
+
Result.map(fst, linkContentsResult)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Reads the contents of a symbolic link.
|
|
704
|
+
*
|
|
705
|
+
* @param baseDirPath: The path to the directory to begin path resolution
|
|
706
|
+
* @param path: The path to the link to read
|
|
707
|
+
* @returns `Ok(path)` containing the link contents or `Err(err)` if a file system error is encountered
|
|
708
|
+
*
|
|
709
|
+
* @since v0.7.0
|
|
710
|
+
*/
|
|
711
|
+
provide let readLink = (baseDirPath=None, path) => {
|
|
712
|
+
let (baseDirPath, path) = getBaseDirAndPath(baseDirPath, path)
|
|
713
|
+
wrappingFileOp(baseDirPath, ReadLink, dirFd => {
|
|
714
|
+
let statsResult = File.pathFilestats(dirFd, [], Path.toString(path))
|
|
715
|
+
let linkContentsResult = fileResult(
|
|
716
|
+
Result.flatMap(stats => readLinkHelper(dirFd, path, stats), statsResult)
|
|
717
|
+
)
|
|
718
|
+
Result.map(x => Path.fromString(x), linkContentsResult)
|
|
719
|
+
})
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
let copySymlink = (
|
|
723
|
+
sourceParentFd,
|
|
724
|
+
sourcePath,
|
|
725
|
+
targetParentFd,
|
|
726
|
+
targetPath,
|
|
727
|
+
stats,
|
|
728
|
+
) => {
|
|
729
|
+
let linkContentsResult = readLinkHelper(sourceParentFd, sourcePath, stats)
|
|
730
|
+
fileResult(Result.flatMap(contents => {
|
|
731
|
+
File.pathSymlink(targetParentFd, contents, Path.toString(targetPath))
|
|
732
|
+
}, linkContentsResult))
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
let copyFile = (
|
|
736
|
+
sourceParentFd,
|
|
737
|
+
sourcePath,
|
|
738
|
+
targetParentFd,
|
|
739
|
+
targetPath,
|
|
740
|
+
followSymlink,
|
|
741
|
+
) => {
|
|
742
|
+
if (!followSymlink) {
|
|
743
|
+
match (File.pathFilestats(sourceParentFd, [], Path.toString(sourcePath))) {
|
|
744
|
+
Ok(stats) when stats.filetype == File.SymbolicLink => {
|
|
745
|
+
return copySymlink(
|
|
746
|
+
sourceParentFd,
|
|
747
|
+
sourcePath,
|
|
748
|
+
targetParentFd,
|
|
749
|
+
targetPath,
|
|
750
|
+
stats
|
|
751
|
+
)
|
|
752
|
+
},
|
|
753
|
+
_ => void,
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return fileOp2(
|
|
758
|
+
firstParentFd=Some(sourceParentFd),
|
|
759
|
+
sourcePath,
|
|
760
|
+
CopySource,
|
|
761
|
+
secondParentFd=Some(targetParentFd),
|
|
762
|
+
targetPath,
|
|
763
|
+
CopyTarget,
|
|
764
|
+
(srcFd, targetFd) => {
|
|
765
|
+
let _BUFSIZE = 8192
|
|
766
|
+
let rec copyChunk = () => {
|
|
767
|
+
match (File.fdRead(srcFd, _BUFSIZE)) {
|
|
768
|
+
Ok((data, numRead)) => {
|
|
769
|
+
if (numRead == 0) {
|
|
770
|
+
Ok(void)
|
|
771
|
+
} else {
|
|
772
|
+
let bufLen = Bytes.length(data)
|
|
773
|
+
let rec writeRemaining = startPos => {
|
|
774
|
+
if (startPos >= bufLen) {
|
|
775
|
+
Ok(void)
|
|
776
|
+
} else {
|
|
777
|
+
let toWrite = Bytes.slice(startPos, bufLen - startPos, data)
|
|
778
|
+
match (File.fdWrite(targetFd, toWrite)) {
|
|
779
|
+
Ok(numWritten) => writeRemaining(startPos + numWritten),
|
|
780
|
+
Err(err) => Err(err),
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
use Result.{ (&&) }
|
|
785
|
+
writeRemaining(0) && copyChunk()
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
Err(err) => Err(err),
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
copyChunk()
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
let rec copyRecursive = (
|
|
797
|
+
sourceParentFd,
|
|
798
|
+
sourcePath,
|
|
799
|
+
targetParentFd,
|
|
800
|
+
targetPath,
|
|
801
|
+
followSymlink,
|
|
802
|
+
) => {
|
|
803
|
+
let copyDirectoryContents = () => {
|
|
804
|
+
wrappingFileOp2(
|
|
805
|
+
firstParentFd=Some(sourceParentFd),
|
|
806
|
+
sourcePath,
|
|
807
|
+
CopySourceBase,
|
|
808
|
+
secondParentFd=Some(targetParentFd),
|
|
809
|
+
targetPath,
|
|
810
|
+
CopyTargetBase,
|
|
811
|
+
(sourceDirFd, targetDirFd) => {
|
|
812
|
+
let entries = fileResult(File.fdReaddir(sourceDirFd))
|
|
813
|
+
match (entries) {
|
|
814
|
+
Ok(entries) => {
|
|
815
|
+
Array.reduce(
|
|
816
|
+
(acc, entry: File.DirectoryEntry) => {
|
|
817
|
+
use Result.{ (&&) }
|
|
818
|
+
acc
|
|
819
|
+
&& copyRecursive(
|
|
820
|
+
sourceDirFd,
|
|
821
|
+
Path.fromString(entry.path),
|
|
822
|
+
targetDirFd,
|
|
823
|
+
Path.fromString(entry.path),
|
|
824
|
+
false
|
|
825
|
+
)
|
|
826
|
+
},
|
|
827
|
+
Ok(void),
|
|
828
|
+
entries
|
|
829
|
+
)
|
|
830
|
+
},
|
|
831
|
+
Err(err) => Err(err),
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
match (
|
|
838
|
+
fileResult(
|
|
839
|
+
File.pathFilestats(
|
|
840
|
+
sourceParentFd,
|
|
841
|
+
if (followSymlink) [File.SymlinkFollow] else [],
|
|
842
|
+
Path.toString(sourcePath)
|
|
843
|
+
)
|
|
844
|
+
)
|
|
845
|
+
) {
|
|
846
|
+
Ok(stats) => {
|
|
847
|
+
match (stats.filetype) {
|
|
848
|
+
x when x == File.Directory => {
|
|
849
|
+
use Result.{ (&&) }
|
|
850
|
+
fileResult(
|
|
851
|
+
File.pathCreateDirectory(targetParentFd, Path.toString(targetPath))
|
|
852
|
+
)
|
|
853
|
+
&& copyDirectoryContents()
|
|
854
|
+
},
|
|
855
|
+
File.SymbolicLink =>
|
|
856
|
+
copySymlink(
|
|
857
|
+
sourceParentFd,
|
|
858
|
+
sourcePath,
|
|
859
|
+
targetParentFd,
|
|
860
|
+
targetPath,
|
|
861
|
+
stats
|
|
862
|
+
),
|
|
863
|
+
_ =>
|
|
864
|
+
copyFile(
|
|
865
|
+
sourceParentFd,
|
|
866
|
+
sourcePath,
|
|
867
|
+
targetParentFd,
|
|
868
|
+
targetPath,
|
|
869
|
+
false
|
|
870
|
+
),
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
Err(err) => Err(err),
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Copies a file or directory.
|
|
879
|
+
*
|
|
880
|
+
* @param copyMode: The type of copy to perform; `CopyFile` by default
|
|
881
|
+
* @param followSymlink: Whether to follow symlinks or not; if `true` then the stats of a valid symlink's underlying file will be returned. `true` by default
|
|
882
|
+
* @param sourceBaseDirPath: The path to the directory in which the source path resolution starts
|
|
883
|
+
* @param sourcePath: The path of the file or directory to copy
|
|
884
|
+
* @param targetBaseDirPath: The path to the directory in which the target path resolution starts
|
|
885
|
+
* @param targetPath: The path to copy the file or directory to
|
|
886
|
+
* @returns `Ok(void)` if the operation succeeds, `Err(err)` if a file system error is encountered
|
|
887
|
+
*/
|
|
888
|
+
provide let copy = (
|
|
889
|
+
copyMode=CopyFile,
|
|
890
|
+
followSymlink=true,
|
|
891
|
+
sourceBaseDirPath=None,
|
|
892
|
+
sourcePath,
|
|
893
|
+
targetBaseDirPath=None,
|
|
894
|
+
targetPath,
|
|
895
|
+
) => {
|
|
896
|
+
let (sourceBaseDirPath, sourcePath) = getBaseDirAndPath(
|
|
897
|
+
sourceBaseDirPath,
|
|
898
|
+
sourcePath
|
|
899
|
+
)
|
|
900
|
+
let (targetBaseDirPath, targetPath) = getBaseDirAndPath(
|
|
901
|
+
targetBaseDirPath,
|
|
902
|
+
targetPath
|
|
903
|
+
)
|
|
904
|
+
wrappingFileOp2(
|
|
905
|
+
sourceBaseDirPath,
|
|
906
|
+
CopySourceBase,
|
|
907
|
+
targetBaseDirPath,
|
|
908
|
+
CopyTargetBase,
|
|
909
|
+
(sourceDirFd, targetDirFd) => {
|
|
910
|
+
let fn = match (copyMode) {
|
|
911
|
+
CopyFile => copyFile,
|
|
912
|
+
CopyRecursive => copyRecursive,
|
|
913
|
+
}
|
|
914
|
+
fn(sourceDirFd, sourcePath, targetDirFd, targetPath, followSymlink)
|
|
915
|
+
}
|
|
916
|
+
)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Renames a file or directory.
|
|
921
|
+
*
|
|
922
|
+
* @param sourceBaseDirPath: The path to the directory in which the source path resolution starts
|
|
923
|
+
* @param sourcePath: The path of the file to rename
|
|
924
|
+
* @param targetBaseDirPath: The path to the directory in which the target path resolution starts
|
|
925
|
+
* @param targetPath: The new path of the file
|
|
926
|
+
* @returns `Ok(void)` if the operation succeeds, `Err(err)` if a file system error is encountered
|
|
927
|
+
*
|
|
928
|
+
* @since v0.7.0
|
|
929
|
+
*/
|
|
930
|
+
provide let rename = (
|
|
931
|
+
sourceBaseDirPath=None,
|
|
932
|
+
sourcePath,
|
|
933
|
+
targetBaseDirPath=None,
|
|
934
|
+
targetPath,
|
|
935
|
+
) => {
|
|
936
|
+
let (sourceBaseDirPath, sourcePath) = getBaseDirAndPath(
|
|
937
|
+
sourceBaseDirPath,
|
|
938
|
+
sourcePath
|
|
939
|
+
)
|
|
940
|
+
let (targetBaseDirPath, targetPath) = getBaseDirAndPath(
|
|
941
|
+
targetBaseDirPath,
|
|
942
|
+
targetPath
|
|
943
|
+
)
|
|
944
|
+
wrappingFileOp2(
|
|
945
|
+
sourceBaseDirPath,
|
|
946
|
+
RenameSource,
|
|
947
|
+
targetBaseDirPath,
|
|
948
|
+
RenameTarget,
|
|
949
|
+
(srcDirFd, targetDirFd) => {
|
|
950
|
+
fileResult(
|
|
951
|
+
File.pathRename(
|
|
952
|
+
srcDirFd,
|
|
953
|
+
Path.toString(sourcePath),
|
|
954
|
+
targetDirFd,
|
|
955
|
+
Path.toString(targetPath)
|
|
956
|
+
)
|
|
957
|
+
)
|
|
958
|
+
}
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Functionality for reading and writing `Bytes` to files.
|
|
964
|
+
*
|
|
965
|
+
* @since v0.7.0
|
|
966
|
+
*/
|
|
967
|
+
provide module Binary {
|
|
968
|
+
/**
|
|
969
|
+
* Read the contents of a file as `Bytes`.
|
|
970
|
+
*
|
|
971
|
+
* @param sync: Whether to synchronously read; `true` by default
|
|
972
|
+
* @param baseDirPath: The path to the directory to begin path resolution
|
|
973
|
+
* @param path: The file path to read from
|
|
974
|
+
* @returns `Ok(contents)` containing the bytes read if successful or `Err(err)` if a file system error is encountered
|
|
975
|
+
*
|
|
976
|
+
* @since v0.7.0
|
|
977
|
+
*/
|
|
978
|
+
provide let readFile = (sync=true, baseDirPath=None, path) => {
|
|
979
|
+
let path = joinPathOnBaseDir(baseDirPath, path)
|
|
980
|
+
fileOp(path, Read(sync), fd => {
|
|
981
|
+
match (File.fdFilestats(fd)) {
|
|
982
|
+
Ok(stats) => {
|
|
983
|
+
let fst = ((contents, _)) => contents
|
|
984
|
+
Result.map(fst, File.fdRead(fd, Int64.toNumber(stats.size)))
|
|
985
|
+
},
|
|
986
|
+
Err(err) => Err(err),
|
|
987
|
+
}
|
|
988
|
+
})
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Write `Bytes` to a file.
|
|
993
|
+
*
|
|
994
|
+
* @param writeMode: The type of write operation to perform; `Truncate` by default
|
|
995
|
+
* @param sync: Whether to synchronously write; `true` by default
|
|
996
|
+
* @param baseDirPath: The path to the directory to begin path resolution
|
|
997
|
+
* @param path: The file path to write to
|
|
998
|
+
* @param data: The bytes to write to the file
|
|
999
|
+
* @returns `Ok(void)` if the operation is successful or `Err(err)` if a file system error is encountered
|
|
1000
|
+
*
|
|
1001
|
+
* @since v0.7.0
|
|
1002
|
+
*/
|
|
1003
|
+
provide let writeFile = (
|
|
1004
|
+
writeMode=Truncate,
|
|
1005
|
+
sync=true,
|
|
1006
|
+
baseDirPath=None,
|
|
1007
|
+
path,
|
|
1008
|
+
data,
|
|
1009
|
+
) => {
|
|
1010
|
+
let path = joinPathOnBaseDir(baseDirPath, path)
|
|
1011
|
+
let openMode = match (writeMode) {
|
|
1012
|
+
Truncate => WriteFile(sync),
|
|
1013
|
+
Append => AppendFile(sync),
|
|
1014
|
+
}
|
|
1015
|
+
let len = Bytes.length(data)
|
|
1016
|
+
Result.map(ignore, fileOp(path, openMode, fd => {
|
|
1017
|
+
let rec writeRemaining = startPos => {
|
|
1018
|
+
if (startPos >= len) {
|
|
1019
|
+
Ok(void)
|
|
1020
|
+
} else {
|
|
1021
|
+
let toWrite = Bytes.slice(startPos, len - startPos, data)
|
|
1022
|
+
match (File.fdWrite(fd, toWrite)) {
|
|
1023
|
+
Ok(numWritten) => writeRemaining(startPos + numWritten),
|
|
1024
|
+
Err(err) => Err(err),
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
writeRemaining(0)
|
|
1029
|
+
}))
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Functionality for reading and writing `String`s to files.
|
|
1035
|
+
*
|
|
1036
|
+
* @since v0.7.0
|
|
1037
|
+
*/
|
|
1038
|
+
provide module Utf8 {
|
|
1039
|
+
/**
|
|
1040
|
+
* Read the contents of a file as a `String`.
|
|
1041
|
+
*
|
|
1042
|
+
* @param sync: Whether to synchronously read; `true` by default
|
|
1043
|
+
* @param baseDirPath: The path to the directory to begin path resolution
|
|
1044
|
+
* @param path: The file path to read from
|
|
1045
|
+
* @returns `Ok(contents)` containing the string read if successful or `Err(err)` if a file system error is encountered
|
|
1046
|
+
*
|
|
1047
|
+
* @since v0.7.0
|
|
1048
|
+
*/
|
|
1049
|
+
provide let readFile = (sync=true, baseDirPath=None, path) => {
|
|
1050
|
+
let bytes = Binary.readFile(sync=sync, baseDirPath=baseDirPath, path)
|
|
1051
|
+
Result.map(bytes => String.decode(bytes, String.UTF8), bytes)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Write a `String` to a file.
|
|
1056
|
+
*
|
|
1057
|
+
* @param writeMode: The type of write operation to perform; `Truncate` by default
|
|
1058
|
+
* @param sync: Whether to synchronously write; `true` by default
|
|
1059
|
+
* @param baseDirPath: The path to the directory to begin path resolution
|
|
1060
|
+
* @param path: The file path to write to
|
|
1061
|
+
* @param data: The string to write to the file
|
|
1062
|
+
* @returns `Ok(void)` if the operation is successful or `Err(err)` if a file system error is encountered
|
|
1063
|
+
*
|
|
1064
|
+
* @since v0.7.0
|
|
1065
|
+
*/
|
|
1066
|
+
provide let writeFile = (
|
|
1067
|
+
writeMode=Truncate,
|
|
1068
|
+
sync=true,
|
|
1069
|
+
baseDirPath=None,
|
|
1070
|
+
path,
|
|
1071
|
+
data,
|
|
1072
|
+
) => {
|
|
1073
|
+
let bytes = String.encode(data, String.UTF8)
|
|
1074
|
+
Binary.writeFile(
|
|
1075
|
+
writeMode=writeMode,
|
|
1076
|
+
sync=sync,
|
|
1077
|
+
baseDirPath=baseDirPath,
|
|
1078
|
+
path,
|
|
1079
|
+
bytes
|
|
1080
|
+
)
|
|
1081
|
+
}
|
|
1082
|
+
}
|