Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CLI/src/FileUtils.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/FileUtils.h"
3
4
#include "Luau/Common.h"
5
6
#ifdef _WIN32
7
#ifndef WIN32_LEAN_AND_MEAN
8
#define WIN32_LEAN_AND_MEAN
9
#endif
10
#ifndef NOMINMAX
11
#define NOMINMAX
12
#endif
13
#include <direct.h>
14
#include <windows.h>
15
#else
16
#include <dirent.h>
17
#include <fcntl.h>
18
#include <unistd.h>
19
#include <sys/stat.h>
20
#endif
21
22
#include <string.h>
23
#include <string_view>
24
25
#ifdef _WIN32
26
static std::wstring fromUtf8(const std::string& path)
27
{
28
size_t result = MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0);
29
LUAU_ASSERT(result);
30
31
std::wstring buf(result, L'\0');
32
MultiByteToWideChar(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()));
33
34
return buf;
35
}
36
37
static std::string toUtf8(const std::wstring& path)
38
{
39
size_t result = WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), nullptr, 0, nullptr, nullptr);
40
LUAU_ASSERT(result);
41
42
std::string buf(result, '\0');
43
WideCharToMultiByte(CP_UTF8, 0, path.data(), int(path.size()), &buf[0], int(buf.size()), nullptr, nullptr);
44
45
return buf;
46
}
47
#endif
48
49
bool isAbsolutePath(std::string_view path)
50
{
51
#ifdef _WIN32
52
// Must either begin with "X:/", "X:\", "/", or "\", where X is a drive letter
53
return (path.size() >= 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) ||
54
(path.size() >= 1 && (path[0] == '/' || path[0] == '\\'));
55
#else
56
// Must begin with '/'
57
return path.size() >= 1 && path[0] == '/';
58
#endif
59
}
60
61
std::optional<std::string> getCurrentWorkingDirectory()
62
{
63
// 2^17 - derived from the Windows path length limit
64
constexpr size_t maxPathLength = 131072;
65
constexpr size_t initialPathLength = 260;
66
67
std::string directory(initialPathLength, '\0');
68
char* cstr = nullptr;
69
70
while (!cstr && directory.size() <= maxPathLength)
71
{
72
#ifdef _WIN32
73
cstr = _getcwd(directory.data(), static_cast<int>(directory.size()));
74
#else
75
cstr = getcwd(directory.data(), directory.size());
76
#endif
77
if (cstr)
78
{
79
directory.resize(strlen(cstr));
80
return directory;
81
}
82
else if (errno != ERANGE || directory.size() * 2 > maxPathLength)
83
{
84
return std::nullopt;
85
}
86
else
87
{
88
directory.resize(directory.size() * 2);
89
}
90
}
91
return std::nullopt;
92
}
93
94
std::string normalizePath(std::string_view path)
95
{
96
const std::vector<std::string_view> components = splitPath(path);
97
std::vector<std::string_view> normalizedComponents;
98
99
const bool isAbsolute = isAbsolutePath(path);
100
101
// 1. Normalize path components
102
const size_t startIndex = isAbsolute ? 1 : 0;
103
for (size_t i = startIndex; i < components.size(); i++)
104
{
105
std::string_view component = components[i];
106
if (component == "..")
107
{
108
if (normalizedComponents.empty())
109
{
110
if (!isAbsolute)
111
{
112
normalizedComponents.emplace_back("..");
113
}
114
}
115
else if (normalizedComponents.back() == "..")
116
{
117
normalizedComponents.emplace_back("..");
118
}
119
else
120
{
121
normalizedComponents.pop_back();
122
}
123
}
124
else if (!component.empty() && component != ".")
125
{
126
normalizedComponents.emplace_back(component);
127
}
128
}
129
130
std::string normalizedPath;
131
132
// 2. Add correct prefix to formatted path
133
if (isAbsolute)
134
{
135
normalizedPath += components[0];
136
normalizedPath += "/";
137
}
138
else if (normalizedComponents.empty() || normalizedComponents[0] != "..")
139
{
140
normalizedPath += "./";
141
}
142
143
// 3. Join path components to form the normalized path
144
for (auto iter = normalizedComponents.begin(); iter != normalizedComponents.end(); ++iter)
145
{
146
if (iter != normalizedComponents.begin())
147
normalizedPath += "/";
148
149
normalizedPath += *iter;
150
}
151
if (normalizedPath.size() >= 2 && normalizedPath[normalizedPath.size() - 1] == '.' && normalizedPath[normalizedPath.size() - 2] == '.')
152
normalizedPath += "/";
153
154
return normalizedPath;
155
}
156
157
std::optional<std::string> resolvePath(std::string_view path, std::string_view baseFilePath)
158
{
159
std::optional<std::string> baseFilePathParent = getParentPath(baseFilePath);
160
if (!baseFilePathParent)
161
return std::nullopt;
162
163
return normalizePath(joinPaths(*baseFilePathParent, path));
164
}
165
166
bool hasFileExtension(std::string_view name, const std::vector<std::string>& extensions)
167
{
168
for (const std::string& extension : extensions)
169
{
170
if (name.size() >= extension.size() && name.substr(name.size() - extension.size()) == extension)
171
return true;
172
}
173
return false;
174
}
175
176
std::optional<std::string> readFile(const std::string& name)
177
{
178
#ifdef _WIN32
179
FILE* file = _wfopen(fromUtf8(name).c_str(), L"rb");
180
#else
181
FILE* file = fopen(name.c_str(), "rb");
182
#endif
183
184
if (!file)
185
return std::nullopt;
186
187
fseek(file, 0, SEEK_END);
188
long length = ftell(file);
189
if (length < 0)
190
{
191
fclose(file);
192
return std::nullopt;
193
}
194
fseek(file, 0, SEEK_SET);
195
196
std::string result(length, 0);
197
198
size_t read = fread(result.data(), 1, length, file);
199
fclose(file);
200
201
if (read != size_t(length))
202
return std::nullopt;
203
204
// Skip first line if it's a shebang
205
if (length > 2 && result[0] == '#' && result[1] == '!')
206
result.erase(0, result.find('\n'));
207
208
return result;
209
}
210
211
std::optional<std::string> readStdin()
212
{
213
std::string result;
214
char buffer[4096] = {};
215
216
while (fgets(buffer, sizeof(buffer), stdin) != nullptr)
217
result.append(buffer);
218
219
// If eof was not reached for stdin, then a read error occurred
220
if (!feof(stdin))
221
return std::nullopt;
222
223
return result;
224
}
225
226
template<typename Ch>
227
static void joinPaths(std::basic_string<Ch>& str, const Ch* lhs, const Ch* rhs)
228
{
229
str = lhs;
230
if (!str.empty() && str.back() != '/' && str.back() != '\\' && *rhs != '/' && *rhs != '\\')
231
str += '/';
232
str += rhs;
233
}
234
235
#ifdef _WIN32
236
static bool traverseDirectoryRec(const std::wstring& path, const std::function<void(const std::string& name)>& callback)
237
{
238
std::wstring query = path + std::wstring(L"/*");
239
240
WIN32_FIND_DATAW data;
241
HANDLE h = FindFirstFileW(query.c_str(), &data);
242
243
if (h == INVALID_HANDLE_VALUE)
244
return false;
245
246
std::wstring buf;
247
248
do
249
{
250
if (wcscmp(data.cFileName, L".") != 0 && wcscmp(data.cFileName, L"..") != 0)
251
{
252
joinPaths(buf, path.c_str(), data.cFileName);
253
254
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
255
{
256
// Skip reparse points to avoid handling cycles
257
}
258
else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
259
{
260
traverseDirectoryRec(buf, callback);
261
}
262
else
263
{
264
callback(toUtf8(buf));
265
}
266
}
267
} while (FindNextFileW(h, &data));
268
269
FindClose(h);
270
271
return true;
272
}
273
274
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
275
{
276
return traverseDirectoryRec(fromUtf8(path), callback);
277
}
278
#else
279
static bool traverseDirectoryRec(const std::string& path, const std::function<void(const std::string& name)>& callback)
280
{
281
int fd = open(path.c_str(), O_DIRECTORY);
282
DIR* dir = fdopendir(fd);
283
284
if (!dir)
285
return false;
286
287
std::string buf;
288
289
while (dirent* entry = readdir(dir))
290
{
291
const dirent& data = *entry;
292
293
if (strcmp(data.d_name, ".") != 0 && strcmp(data.d_name, "..") != 0)
294
{
295
joinPaths(buf, path.c_str(), data.d_name);
296
297
#if defined(DTTOIF)
298
mode_t mode = DTTOIF(data.d_type);
299
#else
300
mode_t mode = 0;
301
#endif
302
303
// we need to stat an UNKNOWN to be able to tell the type
304
if ((mode & S_IFMT) == 0)
305
{
306
struct stat st = {};
307
#ifdef _ATFILE_SOURCE
308
fstatat(fd, data.d_name, &st, 0);
309
#else
310
lstat(buf.c_str(), &st);
311
#endif
312
313
mode = st.st_mode;
314
}
315
316
if (mode == S_IFDIR)
317
{
318
traverseDirectoryRec(buf, callback);
319
}
320
else if (mode == S_IFREG)
321
{
322
callback(buf);
323
}
324
else if (mode == S_IFLNK)
325
{
326
// Skip symbolic links to avoid handling cycles
327
}
328
}
329
}
330
331
closedir(dir);
332
333
return true;
334
}
335
336
bool traverseDirectory(const std::string& path, const std::function<void(const std::string& name)>& callback)
337
{
338
return traverseDirectoryRec(path, callback);
339
}
340
#endif
341
342
bool isFile(const std::string& path)
343
{
344
#ifdef _WIN32
345
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
346
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
347
return false;
348
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
349
#else
350
struct stat st = {};
351
lstat(path.c_str(), &st);
352
return (st.st_mode & S_IFMT) == S_IFREG;
353
#endif
354
}
355
356
bool isDirectory(const std::string& path)
357
{
358
#ifdef _WIN32
359
DWORD fileAttributes = GetFileAttributesW(fromUtf8(path).c_str());
360
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
361
return false;
362
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
363
#else
364
struct stat st = {};
365
lstat(path.c_str(), &st);
366
return (st.st_mode & S_IFMT) == S_IFDIR;
367
#endif
368
}
369
370
std::vector<std::string_view> splitPath(std::string_view path)
371
{
372
std::vector<std::string_view> components;
373
374
size_t pos = 0;
375
size_t nextPos = path.find_first_of("\\/", pos);
376
377
while (nextPos != std::string::npos)
378
{
379
components.push_back(path.substr(pos, nextPos - pos));
380
pos = nextPos + 1;
381
nextPos = path.find_first_of("\\/", pos);
382
}
383
components.push_back(path.substr(pos));
384
385
return components;
386
}
387
388
std::string joinPaths(std::string_view lhs, std::string_view rhs)
389
{
390
std::string result = std::string(lhs);
391
if (!result.empty() && result.back() != '/' && result.back() != '\\')
392
result += '/';
393
result += rhs;
394
return result;
395
}
396
397
std::optional<std::string> getParentPath(std::string_view path)
398
{
399
if (path == "" || path == "." || path == "/")
400
return std::nullopt;
401
402
#ifdef _WIN32
403
if (path.size() == 2 && path.back() == ':')
404
return std::nullopt;
405
#endif
406
407
size_t slash = path.find_last_of("\\/", path.size() - 1);
408
409
if (slash == 0)
410
return "/";
411
412
if (slash != std::string::npos)
413
return std::string(path.substr(0, slash));
414
415
return "";
416
}
417
418
static std::string getExtension(const std::string& path)
419
{
420
size_t dot = path.find_last_of(".\\/");
421
422
if (dot == std::string::npos || path[dot] != '.')
423
return "";
424
425
return path.substr(dot);
426
}
427
428
std::vector<std::string> getSourceFiles(int argc, char** argv)
429
{
430
std::vector<std::string> files;
431
432
for (int i = 1; i < argc; ++i)
433
{
434
// Early out once we reach --program-args,-a since the remaining args are passed to lua
435
if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
436
return files;
437
438
// Treat '-' as a special file whose source is read from stdin
439
// All other arguments that start with '-' are skipped
440
if (argv[i][0] == '-' && argv[i][1] != '\0')
441
continue;
442
443
std::string normalized = normalizePath(argv[i]);
444
445
if (isDirectory(normalized))
446
{
447
traverseDirectory(
448
normalized,
449
[&](const std::string& name)
450
{
451
std::string ext = getExtension(name);
452
453
if (ext == ".lua" || ext == ".luau")
454
files.push_back(name);
455
}
456
);
457
}
458
else
459
{
460
files.push_back(normalized);
461
}
462
}
463
464
return files;
465
}
466
467