-
Notifications
You must be signed in to change notification settings - Fork 385
/
filesystem.lua
171 lines (143 loc) · 3.64 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
local vfs = require "vfs"
local path_mt = {}
path_mt.__index = path_mt
local function constructor(str)
return setmetatable({ _value = str or "" }, path_mt)
end
local function normalize(fullname)
if fullname == "/" then
return "/"
end
local first = (fullname:sub(1, 1) == "/") and "/" or ""
local last = (fullname:sub(-1, -1) == "/") and "/" or ""
local t = {}
for m in fullname:gmatch "([^/]+)/?" do
if m == ".." and #t > 0 then
t[#t] = nil
elseif m ~= "." then
t[#t+1] = m
end
end
return first .. table.concat(t, "/") .. last
end
local function concat(a, b)
if b:sub(1, 1) == "/" then
return constructor(b)
end
local value = a:gsub("/?$", "/")
return constructor(value .. b)
end
function path_mt:__tostring()
return self._value
end
function path_mt:__div(other)
if type(other) ~= "string" then
other = other._value
end
return concat(self._value, other)
end
function path_mt:__concat(other)
if type(other) ~= "string" then
other = other._value
end
return constructor(self._value .. other)
end
function path_mt:__eq(other)
local lft = normalize(self._value)
local rht = normalize(other._value)
return lft == rht
end
function path_mt:string()
return self._value
end
function path_mt:filename()
return constructor(self._value:match "/?([^/]*)$")
end
function path_mt:parent_path()
return constructor(self._value:match "^(.+)/[^/]*$")
end
function path_mt:stem()
return constructor(self._value:match "/?([%w*?_.%-]+)%.[%w*?_%-]*$" or self._value:match "/?(.?[%w*?_%-]*)$")
end
function path_mt:extension()
return self._value:match "[^/](%.[%w*?_%-]*)$" or ""
end
function path_mt:remove_filename()
self._value = self._value:match "^(.+/)[%w*?_.%-]*$" or ""
return self
end
function path_mt:replace_extension(ext)
local stem = self:stem()
self:remove_filename()
if ext:sub(1, 1) ~= "." then
ext = "." .. ext
end
self._value = self._value .. stem._value .. ext
return self
end
function path_mt:is_absolute()
return self._value:sub(1,1) == "/"
end
function path_mt:is_relative()
return self._value:sub(1,1) ~= "/"
end
function path_mt:lexically_normal()
return constructor(normalize(self._value))
end
local fs = {}
function fs.path(str)
if type(str) ~= "string" then
str = str._value
end
return setmetatable({ _value = str }, path_mt)
end
function fs.exists(path)
if type(path) ~= "string" then
path = path._value
end
return vfs.type(path) ~= nil
end
function fs.is_directory(path)
if type(path) ~= "string" then
path = path._value
end
local status = vfs.type(path)
return status == "d" or status == "r"
end
function fs.is_regular_file(path)
if type(path) ~= "string" then
path = path._value
end
return vfs.type(path) == "f"
end
local status_mt = {}
status_mt.__index = status_mt
function status_mt:is_directory()
local status = self[1]
return status == "d" or status == "r"
end
function status_mt:is_regular_file()
local status = self[1]
return status == "f"
end
function fs.pairs(path)
if type(path) ~= "string" then
path = path._value
end
local list = vfs.list(path:gsub("/?$", "/"))
if not list then
return function ()
end
end
local name, status
local status_obj = setmetatable({}, status_mt)
return function()
name, status = next(list, name)
if not name then
return
end
status_obj[1] = status
return concat(path, name), status_obj
end
end
return fs