-
Notifications
You must be signed in to change notification settings - Fork 0
/
filesystem.lua
346 lines (284 loc) · 9.53 KB
/
filesystem.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
--- A better alternative to the file_helper library. Each returned object is a
--- "file" object, that can be further used in other functions.
---
--- This library is a wrapper around the `fs` library, and provides a more
--- object-oriented approach to working with files and directories. It is very
--- quick and intuitive to use.
---
---
local expect = require "cc.expect".expect
---@alias FS_FilePath string|FS_Root
--- An instance of a file object. By default, this object refers to `/`.
---@class FS_Root
---@field path string The path to the file.
---@operator concat(FS_FilePath): FS_Root
---@operator len: integer
local filesystem = {
path = ""
}
local ROOT_SENTINEL = {}
local root_metatable
--- Create a new instance of the filesystem object.
---@param path FS_FilePath? The path to the file.
local function new(path)
return setmetatable({
path = path and tostring(path) or "",
__SENTINEL = ROOT_SENTINEL
}, root_metatable)
end
local function sentinel(obj)
if obj.__SENTINEL ~= ROOT_SENTINEL then
error("Filesystem objects use ':' syntax.", 3)
end
end
local function sentinel_other(arg_index, obj)
if type(obj) ~= "string" and (type(obj) == "table" and obj.__SENTINEL ~= ROOT_SENTINEL) then
error(
("bad argument #%d (expected string or filesystem, got %s)"):format(arg_index, type(obj)),
3
)
end
end
root_metatable = {
__index = filesystem,
__tostring = function(self)
return self.path
end,
__concat = function(self, other)
sentinel_other(2, other)
return new(fs.combine(tostring(self), tostring(other)))
end,
__len = function(self)
return #tostring(self)
end
}
--- Create an object instance based on the path. This method *extends* the
--- current path (i.e: `self.path .. path`), unless the given path is absolute.
---
--- Works the same as concatenating a filesystem object with a string (or
--- another filesystem object).
---
---
---@param path FS_FilePath The path to the file.
---@return FS_Root instance The object instance.
function filesystem:at(path)
sentinel(self)
sentinel_other(1, path)
return self .. path
end
--- Create an object instance based on an absolute path.
---@param path FS_FilePath The path to the file.
---@return FS_Root instance The object instance.
function filesystem:absolute(path)
sentinel(self)
sentinel_other(1, path)
return new(path)
end
--- Create an object instance based on the directory the program is running in.
---@return FS_Root instance The object instance.
function filesystem:programPath()
sentinel(self)
local dir = fs.getDir(shell.getRunningProgram())
return new(dir)
end
--- Get a file object for a given filename within the instance directory.
---@todo this``
---@param path FS_FilePath The path to the file.
---@return FS_File file The file object.
function filesystem:file(path)
sentinel(self)
sentinel_other(1, path)
--- An instance of a file object. This object refers to a literal file or
--- directory on the filesystem.
---@class FS_File : FS_Root
local file = self .. path
--- Get the entire contents of the file.
---@return string contents The contents of the file.
---@overload fun():nil, string Upon error.
function file:readAll()
sentinel(self)
local handle, err = fs.open(tostring(self), "r")
if not handle then
---@cast err string
return nil, err
end
local contents = handle.readAll()
handle.close()
return contents
end
--- Write data to the file. This method will overwrite the file if it already
--- exists.
---@param data string The data to write to the file.
function file:write(data)
sentinel(self)
expect(1, data, "string")
local handle, err = fs.open(tostring(self), "w")
if not handle then
---@cast err string
error(err, 2)
end
handle.write(data)
handle.close()
end
--- Open the file in the given mode.
---@param mode "r"|"rb"|"w"|"wb"|"a"|"ab" The mode to open the file in.
---@return ReadHandle|BinaryReadHandle|WriteHandle|BinaryWriteHandle? handle The file handle.
---@return string? error The error message if the file could not be opened.
---@overload fun(mode: "r"):ReadHandle?, string?
---@overload fun(mode: "rb"):BinaryReadHandle?, string?
---@overload fun(mode: "w"|"a"):WriteHandle?, string?
---@overload fun(mode: "wb"|"ab"):BinaryWriteHandle?, string?
---@nodiscard
function file:open(mode)
sentinel(self)
expect(1, mode, "string")
return fs.open(tostring(self), mode)
end
--- Delete the file or directory.
function file:delete()
sentinel(self)
fs.delete(tostring(self))
end
--- Get the size of the file in bytes.
---@return integer size The size of the file in bytes.
function file:size()
sentinel(self)
return fs.getSize(tostring(self))
end
--- Get the attributes of the file
---@return fileAttributes attributes The attributes of the file.
function file:attributes()
sentinel(self)
return fs.attributes(tostring(self))
end
--- Rename/Move the file to the given path.
---@param path FS_FilePath The new path for the file.
function file:moveTo(path)
sentinel(self)
sentinel_other(1, path)
fs.move(tostring(self), tostring(path))
end
--- Copy the file to the given path.
---@param path FS_FilePath The path to copy the file to.
function file:copyTo(path)
sentinel(self)
sentinel_other(1, path)
fs.copy(tostring(self), tostring(path))
end
--- Create this file, if it doesn't exist. Does nothing if the file already exists.
function file:touch()
sentinel(self)
if not fs.exists(tostring(self)) then
local handle, err = fs.open(tostring(self), "w")
if handle then handle.close() return end
error(err, 2)
end
end
--- Serialize data into the file.
---@param data any The data to serialize.
---@param opts table Options for serialization, same as on https://tweaked.cc/module/textutils.html#v:serialize
function file:serialize(data, opts)
sentinel(self)
self:write(textutils.serialize(data, opts))
end
--- Unserialize data from the file.
---@param default any The default value to return if the file is empty.
---@return any data The unserialized data.
function file:unserialize(default)
sentinel(self)
local contents = self:readAll()
if not contents then
return default
end
return textutils.unserialize(contents)
end
--- Empty the file. This does not delete the file, but rather overwrites it with
--- an empty string. Creates the file if it doesn't exist.
function file:nullify()
sentinel(self)
local handle, err = fs.open(tostring(self), "w")
if handle then handle.close() return end
error(err, 2)
end
return file
end
--- Create a directory at the given path within the instance directory.
---@param path FS_FilePath? The path to the directory. If not provided, creates the directory at the instance path.
function filesystem:mkdir(path)
sentinel(self)
sentinel_other(1, path)
if path then
fs.makeDir(fs.combine(tostring(self), tostring(path)))
else
fs.makeDir(tostring(self))
end
end
--- Remove a file or directory at the given path within the instance directory.
---@param path FS_FilePath? The path to the file or directory. If not provided, removes the instance path.
function filesystem:rm(path)
sentinel(self)
sentinel_other(1, path)
if path then
fs.delete(fs.combine(tostring(self), tostring(path)))
else
fs.delete(tostring(self))
end
end
--- Check if a file or directory exists at the given path within the instance directory.
---@param path FS_FilePath? The path to the file or directory. If not provided, checks if the instance path exists.
---@return boolean exists Whether the file or directory exists.
function filesystem:exists(path)
sentinel(self)
sentinel_other(1, path)
if not path then
return fs.exists(tostring(self))
end
return fs.exists(fs.combine(tostring(self), tostring(path)))
end
--- Check if the given path is a directory within the instance directory.
---@param path FS_FilePath? The path to the file or directory. If not provided, checks if the instance path is a directory.
---@return boolean is_directory Whether the path is a directory.
function filesystem:isDirectory(path)
sentinel(self)
sentinel_other(1, path)
if not path then
return fs.isDir(tostring(self))
end
return fs.isDir(fs.combine(tostring(self), tostring(path)))
end
--- Check if the given path is a file within the instance directory.
---@param path FS_FilePath? The path to the file or directory. If not provided, checks if the instance path is a file.
---@return boolean is_file Whether the path is a file.
function filesystem:isFile(path)
sentinel(self)
sentinel_other(1, path)
if not path then
return not fs.isDir(tostring(self))
end
return not fs.isDir(fs.combine(tostring(self), tostring(path)))
end
--- List the files and directories in the directory.
---@param path FS_FilePath? The path to the directory. If not provided, lists the instance directory.
---@return FS_File[] files The files and directories in the directory.
function filesystem:list(path)
sentinel(self)
sentinel_other(1, path)
local files
if path then
files = fs.list(fs.combine(tostring(self), tostring(path)))
else
files = fs.list(tostring(self))
end
local result = {}
for _, file in ipairs(files) do
table.insert(result, self:file(file))
end
return result
end
--- Get the parent directory of the file.
---@return FS_Root parent The parent directory of the file.
function filesystem:parent()
sentinel(self)
return new(fs.getDir(tostring(self)))
end
return new()