* @file
*
* This file implements file util related apis.
*/
#include "cangjie/Utils/FileUtil.h"
#include <fstream>
#include <istream>
#include <optional>
#include <queue>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <vector>
#include "cangjie/Basic/Print.h"
#include "cangjie/Basic/Utils.h"
#include "cangjie/Driver/StdlibMap.h"
#include "cangjie/Lex/Lexer.h"
#include "cangjie/Modules/ImportManager.h"
#include "cangjie/Utils/Unicode.h"
#ifdef _WIN32
#include "cangjie/Basic/StringConvertor.h"
#include <direct.h>
#include <fileapi.h>
#include <io.h>
#include <windows.h>
#include <winerror.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#elif defined(__linux__) || defined(__APPLE__)
#include <dirent.h>
#endif
namespace Cangjie::FileUtil {
namespace {
constexpr int SHORT_HASH_NUM = 8;
constexpr int BOM_ENCODING_LEN = 3;
constexpr char SLASH = '/';
constexpr char BACKSLASH = '\\';
constexpr char BACKTICK = '`';
constexpr std::string_view TEST_FILE_NAME{"_test.cj"};
#ifdef __arm__
const size_t FILE_LEN_LIMIT = 2ULL * 1024 * 1024 * 1024;
const std::string FILE_LEN_LIMIT_STR = "2 GB";
#else
const size_t FILE_LEN_LIMIT = 4ULL * 1024 * 1024 * 1024;
const std::string FILE_LEN_LIMIT_STR = "4 GB";
#endif
#ifdef _WIN32
inline int Mkdir(const std::string& path)
{
return _mkdir(path.c_str());
}
#else
inline int Mkdir(const std::string& path)
{
return mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
}
#endif
* Find last of dir split, if found return true and the pos.
*/
inline std::optional<size_t> FindLastOfDirSplit(const std::string& str)
{
auto lastDirSepPos = str.find_last_of(DIR_SEPARATOR);
if (lastDirSepPos != std::string::npos) {
return {lastDirSepPos};
}
return std::nullopt;
}
* Whether the last char is '/\\'.
*/
inline bool IsLastCharDirSplit(const std::string& str)
{
if (str.empty()) {
return false;
}
return str.find_last_of(DIR_SEPARATOR) == str.size() - 1;
}
inline std::string AppendPath(const std::string& p1, const std::string& p2)
{
if (IsLastCharDirSplit(p1)) {
return p1 + p2;
}
return p1 + DIR_SEPARATOR[0] + p2;
}
* Find last of '.', if found returns the pos, otherwise returns nullopt.
*/
inline std::optional<size_t> FindLastOfDot(const std::string& str)
{
auto pos = str.find_last_of('.');
if (pos != std::string::npos) {
return {pos};
}
return std::nullopt;
}
* Whether the @p str contain '/\\'.
*/
inline bool HasDirSplit(const std::string& str)
{
return str.find_first_of(DIR_SEPARATOR) != std::string::npos;
}
inline bool CanExecute(const std::string& path)
{
if (FileExist(path)) {
return Access(path, FM_EXE);
}
return false;
}
#ifdef _WIN32
bool RemoveDirRecursivelyInternal(const std::wstring& dirPath)
{
WIN32_FIND_DATAW findData;
std::wstring searchPath = dirPath;
if (!searchPath.empty() && searchPath.back() != L'\\' && searchPath.back() != L'/') {
searchPath += L'\\';
}
searchPath += L"*";
HANDLE handle = FindFirstFileW(searchPath.c_str(), &findData);
if (handle == INVALID_HANDLE_VALUE) {
return RemoveDirectoryW(dirPath.c_str()) != 0;
}
auto closeHandle = [&handle]() {
if (handle != INVALID_HANDLE_VALUE) {
FindClose(handle);
handle = INVALID_HANDLE_VALUE;
}
};
do {
std::wstring name(findData.cFileName);
if (name == L"." || name == L"..") {
continue;
}
std::wstring childPath = dirPath;
if (!childPath.empty() && childPath.back() != L'\\' && childPath.back() != L'/') {
childPath += L'\\';
}
childPath += name;
bool isDirectory = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
bool isReparse = (findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
if (isReparse) {
bool removed = isDirectory ? (RemoveDirectoryW(childPath.c_str()) != 0)
: (DeleteFileW(childPath.c_str()) != 0);
if (!removed) {
closeHandle();
return false;
}
continue;
}
if (isDirectory) {
if (!RemoveDirRecursivelyInternal(childPath)) {
closeHandle();
return false;
}
} else {
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {
SetFileAttributesW(childPath.c_str(), findData.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
}
if (!DeleteFileW(childPath.c_str())) {
closeHandle();
return false;
}
}
} while (FindNextFileW(handle, &findData));
closeHandle();
DWORD attr = GetFileAttributesW(dirPath.c_str());
if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) != 0) {
SetFileAttributesW(dirPath.c_str(), attr & ~FILE_ATTRIBUTE_READONLY);
}
return RemoveDirectoryW(dirPath.c_str()) != 0;
}
bool RemoveDirRecursively(const std::string& dirPath)
{
std::optional<std::wstring> widePath = StringConvertor::StringToWString(dirPath);
if (!widePath.has_value()) {
return false;
}
return RemoveDirRecursivelyInternal(widePath.value());
}
#else
bool RemoveDirRecursively(const std::string& dirPath)
{
DIR* dir = opendir(dirPath.c_str());
if (dir == nullptr) {
return rmdir(dirPath.c_str()) == 0;
}
struct dirent* entry = nullptr;
bool ok = true;
while ((entry = readdir(dir)) != nullptr) {
std::string name(entry->d_name);
if (name == "." || name == "..") {
continue;
}
std::string childPath = JoinPath(dirPath, name);
struct stat st {};
if (lstat(childPath.c_str(), &st) != 0) {
ok = false;
break;
}
if (S_ISDIR(st.st_mode)) {
if (!RemoveDirRecursively(childPath)) {
ok = false;
break;
}
} else {
if (unlink(childPath.c_str()) != 0) {
ok = false;
break;
}
}
}
closedir(dir);
if (!ok) {
return false;
}
return rmdir(dirPath.c_str()) == 0;
}
#endif
}
bool IsSlash(char c)
{
#ifdef _WIN32
return c == BACKSLASH || c == SLASH;
#else
return c == SLASH;
#endif
}
bool CheckCommandLineInjection(const std::string& cmd)
{
if (cmd.find_first_of(INJECTION_STRING) != std::string::npos) {
return true;
}
bool isEven = true;
size_t pos = 0;
while ((pos = cmd.find(BACKTICK, pos)) != std::string::npos) {
isEven = !isEven;
pos++;
}
return !isEven;
}
std::string TransferEscapeBacktick(const std::string& s)
{
#ifdef _WIN32
return s;
#else
std::string ret = s;
auto cnt = std::count(s.begin(), s.end(), BACKTICK);
ret.reserve(s.size() + static_cast<size_t>(cnt));
size_t pos = s.size() - 1;
while ((pos = s.rfind(BACKTICK, pos)) != std::string::npos) {
ret.insert(pos, 1, BACKSLASH);
if (pos == 0) {
break;
}
pos--;
}
return ret;
#endif
}
std::string GetFileName(const std::string& filePath)
{
std::string path = filePath;
path = Normalize(path);
auto posOpt = FindLastOfDirSplit(path);
if (posOpt) {
return path.substr(posOpt.value() + 1);
}
return path;
}
std::string GetQuoted(const std::string& str)
{
std::stringstream ss;
ss << std::quoted(str);
return ss.str();
}
std::string GetDirName(const std::string& filePath)
{
std::string path = filePath;
if (IsLastCharDirSplit(path)) {
path.pop_back();
}
if (path == "." || path == "..") {
auto absolutePath = GetAbsPath(path);
path = absolutePath.has_value() ? absolutePath.value() : path;
}
return GetFileName(path);
}
std::string GetDirPath(const std::string& filePath)
{
auto posOpt = FindLastOfDirSplit(filePath);
if (posOpt) {
return Normalize(filePath.substr(0, posOpt.value()));
}
return ".";
}
std::string GetFileExtension(const std::string& filePath)
{
auto dotPos = GetFileExtensionSeparatorPos(filePath).value_or(filePath.size());
if (dotPos < filePath.size()) {
return filePath.substr(dotPos + 1);
} else {
return "";
}
}
bool HasExtension(const std::string& filePath, const std::string& extension)
{
return GetFileExtension(filePath) == extension;
}
bool HasCJDExtension(std::string_view path)
{
auto fileIt = path.rbegin();
auto fileEnd = path.rend();
auto extensionIt = CJ_D_FILE_EXTENSION.rbegin();
auto extensionEnd = CJ_D_FILE_EXTENSION.rend();
while (fileIt != fileEnd && extensionIt != extensionEnd) {
if (*fileIt != *extensionIt) {
return false;
}
++fileIt;
++extensionIt;
}
return extensionIt == extensionEnd && fileIt != fileEnd;
}
std::optional<size_t> GetFileExtensionSeparatorPos(const std::string& filePath)
{
auto dotOpt = FindLastOfDot(filePath);
if (!dotOpt.has_value() || dotOpt.value() == 0) {
return std::nullopt;
}
size_t dotPos = dotOpt.value();
auto dirSplitOpt = FindLastOfDirSplit(filePath);
if (dirSplitOpt.has_value()) {
auto dirSplitPos = dirSplitOpt.value();
if ((dotPos == dirSplitPos + 1) || (dirSplitPos > dotPos)) {
return std::nullopt;
}
}
return {dotPos};
}
std::string GetFileNameWithoutExtension(const std::string& filePath)
{
auto posOpt = FindLastOfDot(filePath);
size_t dotPos = posOpt.value_or(filePath.size());
posOpt = FindLastOfDirSplit(filePath);
size_t dirSplitPos = posOpt ? posOpt.value() + 1 : 0;
if (dotPos == dirSplitPos) {
dotPos = filePath.size();
}
return filePath.substr(dirSplitPos, dotPos - dirSplitPos);
}
std::string GetFileBase(const std::string& filePath)
{
auto dotPos = GetFileExtensionSeparatorPos(filePath).value_or(filePath.size());
return filePath.substr(0, dotPos);
}
std::string Normalize(const std::string& input, const bool toUnix)
{
std::string output = "";
output.reserve(input.size());
#ifdef _WIN32
const char slash = toUnix ? SLASH : BACKSLASH;
#else
(void)toUnix;
const char slash = SLASH;
#endif
bool previousIsSlash = false;
for (auto c : input) {
if (previousIsSlash && IsSlash(c)) {
continue;
}
if (c == '\0') {
break;
}
if (IsSlash(c)) {
output.push_back(slash);
previousIsSlash = true;
} else {
output.push_back(c);
previousIsSlash = false;
}
}
return output;
}
bool IsDir(const std::string& filePath)
{
#ifdef _WIN32
DWORD dwAttr = GetFileAttributesA(filePath.c_str());
if (dwAttr == INVALID_FILE_ATTRIBUTES) {
return false;
}
if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
return true;
}
#else
if (AccessWithResult(filePath, FM_READ) == AccessResultType::NO_PERMISSION) {
struct stat statbuf;
if (stat(filePath.c_str(), &statbuf) != 0) {
return false;
}
return S_ISDIR(statbuf.st_mode);
}
DIR* dp = opendir(filePath.c_str());
if (dp != nullptr) {
closedir(dp);
return true;
}
#endif
return false;
}
bool IsAbsolutePath(const std::string& filePath)
{
if (filePath.empty()) {
return false;
}
#ifdef _WIN32
if (filePath.size() < 3) {
return false;
}
return std::isalpha(filePath[0]) && filePath[1] == ':' && filePath.find_first_of(DIR_SEPARATOR) == 2;
#else
return filePath.find_first_of(DIR_SEPARATOR) == 0;
#endif
}
bool Remove(const std::string& itemPath)
{
#ifdef _WIN32
DWORD dwAttr = GetFileAttributesA(itemPath.c_str());
if (dwAttr == INVALID_FILE_ATTRIBUTES) {
return false;
}
if ((dwAttr & FILE_ATTRIBUTE_READONLY) == 1 || (dwAttr & FILE_ATTRIBUTE_SYSTEM) == 1 ||
(dwAttr & FILE_ATTRIBUTE_DEVICE) == 1 || (dwAttr & FILE_ATTRIBUTE_VIRTUAL) == 1) {
return false;
}
return (IsDir(itemPath) ? _rmdir(itemPath.c_str()) : remove(itemPath.c_str())) == 0;
#else
struct stat statBuf;
if (lstat(itemPath.c_str(), &statBuf) != 0) {
return false;
}
if (!S_ISREG(statBuf.st_mode) && !S_ISDIR(statBuf.st_mode) && !S_ISLNK(statBuf.st_mode)) {
return false;
}
return remove(itemPath.c_str()) == 0;
#endif
}
bool RemoveDirectoryRecursively(const std::string& dirPath)
{
if (dirPath.empty()) {
return false;
}
#ifdef _WIN32
DWORD attr = GetFileAttributesA(dirPath.c_str());
if (attr == INVALID_FILE_ATTRIBUTES) {
return false;
}
if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
std::optional<std::wstring> widePath = StringConvertor::StringToWString(dirPath);
if (!widePath.has_value()) {
return false;
}
if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
DWORD dirAttr = GetFileAttributesW(widePath.value().c_str());
if (dirAttr != INVALID_FILE_ATTRIBUTES && (dirAttr & FILE_ATTRIBUTE_READONLY) != 0) {
SetFileAttributesW(widePath.value().c_str(), dirAttr & ~FILE_ATTRIBUTE_READONLY);
}
return RemoveDirectoryW(widePath.value().c_str()) != 0;
}
return DeleteFileA(dirPath.c_str()) != 0;
}
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
return false;
}
return RemoveDirRecursively(dirPath);
#else
struct stat st {};
if (lstat(dirPath.c_str(), &st) != 0) {
return false;
}
if (S_ISLNK(st.st_mode)) {
return unlink(dirPath.c_str()) == 0;
}
if (!S_ISDIR(st.st_mode)) {
return false;
}
return RemoveDirRecursively(dirPath);
#endif
}
bool ReadBinaryFileToBuffer(const std::string& filePath, std::vector<uint8_t>& buffer, std::string& failedReason)
{
auto realFilePath = GetAbsPath(filePath);
failedReason.clear();
if (!realFilePath.has_value()) {
failedReason = "open file failed";
return false;
}
std::ifstream is(realFilePath.value(), std::ifstream::in | std::ifstream::binary);
if (!is.is_open()) {
failedReason = "open file failed";
return false;
}
size_t fileLength = GetFileSize(realFilePath.value());
if (fileLength == 0) {
failedReason = "empty binary file";
}
if (fileLength >= FILE_LEN_LIMIT) {
failedReason = "exceed the max file length: " + FILE_LEN_LIMIT_STR;
}
if (!failedReason.empty()) {
is.close();
return false;
}
buffer.resize(fileLength);
is.read(reinterpret_cast<char*>(buffer.data()), static_cast<std::streamsize>(fileLength));
size_t realLen = static_cast<size_t>(is.gcount());
if (is.bad()) {
failedReason = "only " + std::to_string(realLen) + " could be read";
} else if (is.eof() || realLen < fileLength) {
buffer.resize(realLen);
}
is.close();
return failedReason.empty();
}
bool WriteToFile(const std::string& filePath, const std::string& data)
{
if (!FileExist(filePath)) {
if (CreateDirs(filePath) != 0) {
Errorln("Failed to create file " + filePath);
return false;
}
}
auto normalizedDir = GetAbsPath(GetDirPath(filePath));
if (!normalizedDir.has_value()) {
Errorln("File path illegal: ", filePath);
return false;
}
auto normalizedFilePath = JoinPath(normalizedDir.value(), GetFileName(filePath));
std::ofstream file(normalizedFilePath);
if (!file.is_open()) {
Errorln("Failed to open file " + normalizedFilePath);
return false;
}
file << data;
bool didSucceed = !file.fail();
if (!didSucceed) {
Errorln("Failed to write to file " + normalizedFilePath);
}
file.close();
return didSucceed;
}
bool WriteBufferToASTFile(const std::string& filePath, const std::vector<uint8_t>& buffer)
{
std::string baseDir = GetDirPath(filePath);
if (!FileExist(baseDir)) {
if (CreateDirs(baseDir + "/") != 0) {
return false;
}
}
auto realFilePath = GetAbsPath(baseDir);
if (!realFilePath.has_value()) {
return false;
}
auto fileName = GetFileName(filePath);
std::ofstream outStream(JoinPath(realFilePath.value(), fileName), std::ofstream::out | std::ofstream::binary);
if (!outStream.is_open()) {
return false;
}
auto length = buffer.size();
outStream.write(reinterpret_cast<const char*>(buffer.data()), static_cast<std::streamsize>(length));
bool success = !outStream.fail();
outStream.close();
return success;
}
std::optional<std::string> ReadFileContent(const std::string& filePath, std::string& failedReason)
{
if (!FileExist(filePath)) {
failedReason = "file not exist";
return std::nullopt;
}
auto realFilePath = GetAbsPath(filePath);
failedReason.clear();
if (!realFilePath.has_value()) {
failedReason = "file path illegal";
return std::nullopt;
}
std::ifstream is(realFilePath.value(), std::ios::in | std::ios::binary);
if (!is) {
failedReason = "open file failed";
return std::nullopt;
}
size_t len = GetFileSize(realFilePath.value());
if (len >= FILE_LEN_LIMIT) {
failedReason = "exceed the max file length: " + FILE_LEN_LIMIT_STR;
is.close();
return std::nullopt;
}
std::string content;
if (len >= BOM_ENCODING_LEN) {
content.resize(BOM_ENCODING_LEN);
is.read(content.data(), BOM_ENCODING_LEN);
if (content[0] == '\xEF' && content[1] == '\xBB' && content[2] == '\xBF') {
is.seekg(BOM_ENCODING_LEN, std::ifstream::beg);
len -= BOM_ENCODING_LEN;
} else {
is.seekg(0, std::ifstream::beg);
}
}
content.resize(len);
is.read(content.data(), static_cast<std::streamsize>(len));
size_t realLen = static_cast<size_t>(is.gcount());
if (is.bad()) {
failedReason = "only " + std::to_string(realLen) + " could be read";
} else if (is.eof() || realLen < len) {
content.resize(realLen);
}
is.close();
if (!failedReason.empty()) {
return std::nullopt;
}
return content;
}
std::string AppendExtension(const std::string& file, const std::string& backup, const std::string& extension)
{
std::string result = GetFileBase(file);
return (result.empty() ? (backup + extension) : (result + extension));
}
bool IsAbsolutePathAboveLengthLimit(const std::string& path)
{
if (path.length() > PATH_MAX) {
return true;
}
#ifdef _WIN32
char resolvedPath[PATH_MAX] = {0};
int retval = GetFullPathNameA(path.c_str(), PATH_MAX, resolvedPath, NULL);
if (retval == 0 && GetLastError() == ERROR_FILENAME_EXCED_RANGE) {
return true;
}
#endif
return false;
}
std::optional<std::string> GetAbsPath(const std::string& path)
{
if (path.length() > PATH_MAX) {
return {};
}
char resolvedPath[PATH_MAX] = {0};
#ifdef _WIN32
int retval = GetFullPathNameA(path.c_str(), PATH_MAX, resolvedPath, NULL);
if (retval == 0) {
return {};
}
if (!FileExist(resolvedPath)) {
return {};
}
#else
if (!realpath(path.c_str(), resolvedPath)) {
return {};
}
#endif
return Normalize(std::string(resolvedPath));
}
std::optional<std::string> GetRelativePath(const std::string& basePath, const std::string& path)
{
if (basePath.empty() || path.empty()) {
return {};
}
std::string::size_type lastDelimPos = 0;
std::string::size_type pos = 0;
for (; pos != basePath.length() && pos != path.length() && basePath[pos] == path[pos]; ++pos) {
if (IsSlash(basePath[pos])) {
lastDelimPos = pos;
}
}
#ifdef _WIN32
if (lastDelimPos == 0 && !IsSlash(path[0])) {
return {};
}
#endif
size_t parentCount = static_cast<size_t>(
std::count_if(basePath.begin() + static_cast<std::ptrdiff_t>(lastDelimPos + 1), basePath.end(), IsSlash));
size_t suffixLength = (path.length() - lastDelimPos) - 1;
if (pos == path.length() && !IsSlash(path.back())) {
parentCount = (parentCount == 0) ? 0 : (parentCount - 1);
suffixLength = 0;
}
std::string result;
if (parentCount == 0) {
result.reserve(suffixLength + 2);
result += "./";
} else {
result.reserve(parentCount * 3 + suffixLength);
for (size_t i = 0; i < parentCount; ++i) {
result += "../";
}
}
result.append(path, lastDelimPos + 1, suffixLength);
return Normalize(result);
}
std::vector<std::string> GetAllDirsUnderCurrentPath(const std::string& path)
{
std::string root = Normalize(path);
std::vector<std::string> allDirs;
#ifdef _WIN32
GetAllDirsUnderCurrentPath(root, allDirs);
#else
std::queue<std::string> pathQueue;
pathQueue.push(root);
while (!pathQueue.empty()) {
auto tmpPath = pathQueue.front();
pathQueue.pop();
DIR* dir = opendir(tmpPath.c_str());
if (!dir) {
return allDirs;
}
for (auto entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
if (entry->d_type != DT_DIR) {
continue;
}
auto dirName = std::string(entry->d_name);
if (dirName != "." && dirName != "..") {
allDirs.push_back(JoinPath(tmpPath, dirName));
pathQueue.push(JoinPath(tmpPath, dirName));
}
}
closedir(dir);
}
#endif
return allDirs;
}
size_t GetFileSize(const std::string& filePath)
{
#ifdef _WIN32
struct __stat64 statBuf;
int rc = _stat64(filePath.c_str(), &statBuf);
return rc == 0 ? static_cast<size_t>(statBuf.st_size) : 0;
#else
struct stat statBuf;
int rc = stat(filePath.c_str(), &statBuf);
return rc == 0 ? static_cast<size_t>(statBuf.st_size) : 0;
#endif
}
inline bool AddFileIfNeeded(const std::string& fileName, std::vector<std::string>& allFiles, bool shouldSkipTestFiles,
bool shouldSkipRegularFiles)
{
auto findTestSuffix = fileName.rfind(TEST_FILE_NAME);
if (shouldSkipTestFiles && findTestSuffix != std::string::npos &&
fileName.size() - findTestSuffix == TEST_FILE_NAME.size()) {
return false;
} else if (shouldSkipRegularFiles && findTestSuffix == std::string::npos) {
return false;
}
allFiles.emplace_back(fileName);
return true;
}
#ifdef _WIN32
void GetAllDirsUnderCurrentPath(const std::string& dir, std::vector<std::string>& allFiles)
{
HANDLE hFind;
WIN32_FIND_DATA findData;
std::string dirNew = JoinPath(dir, "*.*");
hFind = FindFirstFileA(dirNew.c_str(), &findData);
do {
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && strcmp(findData.cFileName, "") != 0 &&
strcmp(findData.cFileName, ".") != 0 && strcmp(findData.cFileName, "..") != 0) {
dirNew = JoinPath(dir, std::string(findData.cFileName));
allFiles.push_back(dirNew);
GetAllDirsUnderCurrentPath(dirNew, allFiles);
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
std::vector<std::string> GetAllFilesUnderCurrentPath(
const std::string& path, const std::string& extension, bool shouldSkipTestFiles, bool shouldSkipRegularFiles)
{
std::vector<std::string> allFiles;
HANDLE hFind;
WIN32_FIND_DATAA findData;
std::string dirNew = JoinPath(path, "*.*");
hFind = FindFirstFileA(dirNew.c_str(), &findData);
do {
auto fileName = std::string(findData.cFileName);
bool isFile = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 &&
strcmp(findData.cFileName, ".") != 0 && HasExtension(fileName, extension);
if (!isFile) {
continue;
}
if (!AddFileIfNeeded(fileName, allFiles, shouldSkipTestFiles, shouldSkipRegularFiles)) {
continue;
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
return allFiles;
}
std::vector<Directory> GetDirectories(const std::string& path)
{
std::vector<Directory> directories;
HANDLE hFind;
WIN32_FIND_DATAA findData;
std::string dirNew = JoinPath(path, "*");
hFind = FindFirstFileA(dirNew.c_str(), &findData);
do {
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && strcmp(findData.cFileName, "") != 0 &&
strcmp(findData.cFileName, ".") != 0 && strcmp(findData.cFileName, "..") != 0) {
Directory d;
d.name = findData.cFileName;
d.path = JoinPath(path, std::string(findData.cFileName));
directories.emplace_back(d);
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
return directories;
}
#else
std::vector<std::string> GetAllFilesUnderCurrentPath(
const std::string& path, const std::string& extension, bool shouldSkipTestFiles, bool shouldSkipRegularFiles)
{
std::vector<std::string> allFiles;
DIR* dir = opendir(path.c_str());
if (!dir) {
return allFiles;
}
for (auto entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
if (entry->d_type != DT_REG) {
continue;
}
std::string fileName(entry->d_name);
if (fileName[0] == '.' || !HasExtension(fileName, extension)) {
continue;
}
if (!AddFileIfNeeded(fileName, allFiles, shouldSkipTestFiles, shouldSkipRegularFiles)) {
continue;
}
}
closedir(dir);
return allFiles;
}
std::vector<Directory> GetDirectories(const std::string& path)
{
std::vector<Directory> directories;
struct dirent* entry;
DIR* dir = opendir(path.c_str());
if (dir) {
while ((entry = readdir(dir)) != nullptr) {
if (entry->d_type != DT_DIR && entry->d_type != DT_LNK) {
continue;
}
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
Directory d;
d.name = entry->d_name;
d.path = JoinPath(path, entry->d_name);
(void)directories.emplace_back(d);
}
closedir(dir);
}
return directories;
}
#endif
std::string JoinPath(const std::string& base, const std::string& append)
{
std::string joinedPath = base;
if (base.find_last_of(DIR_SEPARATOR) != base.size() - 1) {
joinedPath += DIR_SEPARATOR[0];
}
joinedPath += append;
return Normalize(joinedPath);
}
std::string GetShortHash(uint64_t fullHash)
{
std::string hash = std::to_string(fullHash);
return hash.size() <= SHORT_HASH_NUM ? hash : hash.substr(0, SHORT_HASH_NUM);
}
std::vector<std::string> SplitStr(const std::string& str, const char del)
{
std::vector<std::string> res;
size_t slow = 0;
size_t fast = 0;
size_t len = str.size();
while (slow < len && fast < len) {
if (str[fast] == del && str[slow] == del) {
slow = ++fast;
} else if (str[fast] != del && str[slow] != del) {
fast++;
} else if (str[fast] == del && str[slow] != del) {
res.emplace_back(str.substr(slow, fast - slow));
slow = fast;
}
}
if (slow != fast) {
res.emplace_back(str.substr(slow, fast));
}
return res;
}
std::string IdenticalFunc(const std::string& str)
{
return str;
}
std::string GetPkgNameFromRelativePath(const std::string& path)
{
std::string packageName = Normalize(path);
size_t pos = 0;
if (packageName.find(UPPERLEVEL_PATH) != std::string::npos) {
return DEFAULT_PACKAGE_NAME;
}
while ((pos = packageName.find(CURRENT_PATH, pos)) != std::string::npos) {
packageName.replace(pos, CURRENT_PATH.size(), "");
}
pos = 0;
while ((pos = packageName.find_first_of(DIR_SEPARATOR, pos)) != std::string::npos) {
packageName.replace(pos, 1, ".");
pos += 1;
}
while (!packageName.empty() && packageName.back() == '.') {
packageName.pop_back();
}
return packageName.empty() ? DEFAULT_PACKAGE_NAME : packageName;
}
namespace {
inline bool IsKeyword(const std::string& str)
{
static std::unordered_set<std::string> keywordsSet{};
if (keywordsSet.empty()) {
for (unsigned char i = 0; i < static_cast<unsigned char>(TokenKind::IDENTIFIER); i++) {
keywordsSet.emplace(std::string(TOKENS[i]));
}
keywordsSet.emplace("true");
keywordsSet.emplace("false");
}
return keywordsSet.count(str) != 0;
}
}
using namespace Unicode;
bool IsIdentifier(const std::string& str, bool useContextualKeyWords)
{
if (str.empty()) {
return false;
}
auto start{reinterpret_cast<const UTF8*>(str.data())};
auto end{reinterpret_cast<const UTF8*>(str.data() + str.size())};
if (str[0] == BACKTICK) {
++start;
--end;
if ((str.size() < 3 || *(str.end() - 1) != BACKTICK)) {
return false;
}
}
bool isFirst{true};
UTF32 codepoint;
UTF32* targetEnd{&codepoint + 1};
while (start < end) {
UTF32* targetStart{&codepoint};
auto cr = ConvertUTF8toUTF32(&start, end, &targetStart, targetEnd);
if (cr != ConversionResult::OK) {
return false;
}
if (isFirst) {
if (!IsASCIIIdStart(codepoint) && !IsCJXIDStart(codepoint)) {
return false;
}
} else if (!IsASCIIIdContinue(codepoint) && !IsXIDContinue(codepoint)) {
return false;
}
isFirst = false;
}
if (IsKeyword(str)) {
if (useContextualKeyWords && IsContextualKeyword(str)) {
return true;
}
return false;
}
return true;
}
std::string NormalizePath(const std::string& path)
{
if (path.empty()) {
return path;
}
if (GetDirPath(path) == ".") {
return JoinPath(".", GetFileName(path));
}
return Normalize(path);
}
std::string ConvertPackageNameToLibCangjieBaseFormat(const std::string& fullPackageName)
{
if (auto found = STANDARD_LIBS.find(fullPackageName); found != STANDARD_LIBS.end()) {
auto names = Utils::SplitQualifiedName(fullPackageName);
if (names.size() == 1) {
return "cangjie-" + found->second;
}
auto moduleName = names.front();
return "cangjie-" + moduleName + "-" + found->second;
}
return {};
}
#ifdef CANGJIE_CODEGEN_CJNATIVE_BACKEND
std::string ConvertFilenameToLibCangjieBaseFormat(const std::string& objectFile)
{
auto fullPackageName = GetFileNameWithoutExtension(objectFile);
return ConvertPackageNameToLibCangjieBaseFormat(fullPackageName);
}
std::string ConvertFilenameToLibCangjieFormat(const std::string& objectFile, const std::string& extension)
{
auto fullPackageName = GetFileNameWithoutExtension(objectFile);
auto base = ConvertPackageNameToLibCangjieBaseFormat(fullPackageName);
return base.empty() ? std::string() : "lib" + base + extension;
}
#endif
std::string ConvertFilenameToLtoLibCangjieFormat(const std::string& objectFile)
{
auto dotOptional = FindLastOfDot(objectFile);
auto dirSplitOptional = FindLastOfDirSplit(objectFile);
size_t dotPos = dotOptional.value_or(objectFile.size());
size_t dirSplitPos = dirSplitOptional.value_or(0);
std::string path = objectFile.substr(0, dirSplitPos);
std::string libName = objectFile.substr(dirSplitPos + 1, dotPos - (dirSplitPos + 1));
std::stringstream ss;
ss << path << DIR_SEPARATOR << "lib" << libName;
ss << ".bc";
return ss.str();
}
AccessResultType AccessWithResult(const std::string& path, FileMode mode)
{
#ifdef _WIN32
return Access(path, mode) ? AccessResultType::OK : AccessResultType::FAILED_WITH_UNKNOWN_REASON;
#else
if (access(path.c_str(), static_cast<int>(mode)) == 0) {
return AccessResultType::OK;
}
if (errno == ENOENT) {
return AccessResultType::NOT_EXIST;
} else if (errno == EACCES) {
return AccessResultType::NO_PERMISSION;
}
return AccessResultType::FAILED_WITH_UNKNOWN_REASON;
#endif
}
bool Access(const std::string& path, FileMode mode)
{
#ifdef _WIN32
return _access(path.c_str(), static_cast<int>(mode)) == 0;
#else
return access(path.c_str(), static_cast<int>(mode)) == 0;
#endif
}
bool FileExist(const std::string& path, [[maybe_unused]] bool caseSensitive)
{
#if defined(_WIN32)
if (!Access(path, FM_EXIST)) {
return false;
}
if (!caseSensitive) {
return true;
}
std::string pathWithoutSep = path;
if (auto pos = path.find_last_not_of(DIR_SEPARATOR); pos != std::string::npos) {
pathWithoutSep = path.substr(0, pos + 1);
}
const std::string filename = GetFileName(pathWithoutSep);
HANDLE hFind;
WIN32_FIND_DATAA findData;
std::string dirNew = JoinPath(GetDirPath(pathWithoutSep), "*.*");
hFind = FindFirstFileA(dirNew.c_str(), &findData);
do {
if (filename == findData.cFileName) {
FindClose(hFind);
return true;
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
return false;
#elif defined(__APPLE__) || defined(__linux__)
if (!Access(path, FM_EXIST)) {
return false;
}
if (!caseSensitive) {
return true;
}
size_t lastSlash = path.find_last_of(DIR_SEPARATOR);
std::string fileName = (lastSlash == std::string::npos) ? path : path.substr(lastSlash + 1);
std::string dirPath = (lastSlash == std::string::npos) ? "." : path.substr(0, lastSlash);
DIR* dir = opendir(dirPath.c_str());
if (!dir) {
return false;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (fileName == std::string(entry->d_name)) {
closedir(dir);
return true;
}
}
closedir(dir);
return false;
#else
return Access(path, FM_EXIST);
#endif
}
std::optional<std::string> FindFileByName(
const std::string& fileName, const std::vector<std::string>& searchPaths, bool caseSensitive)
{
if (fileName.empty()) {
return std::nullopt;
}
for (const auto& path : searchPaths) {
if (path.empty()) {
continue;
}
auto filepath = JoinPath(path, fileName);
if (FileExist(filepath, caseSensitive)) {
return {filepath};
}
}
return std::nullopt;
}
std::string FindProgramByName(const std::string& name, const std::vector<std::string>& paths)
{
if (name.empty()) {
return "";
}
if (HasDirSplit(name)) {
return name;
}
std::string exePath;
for (const auto& path : paths) {
if (path.empty()) {
continue;
}
exePath = AppendPath(path, name);
if (CanExecute(exePath)) {
return exePath;
}
}
return "";
}
int32_t CreateDirs(const std::string& directoryPath)
{
size_t dirPathLen = directoryPath.length();
if (dirPathLen > DIR_PATH_MAX_LENGTH) {
std::string msg = "Failed to create directory: " + directoryPath + "\nthe path cannot be longer than " +
std::to_string(DIR_PATH_MAX_LENGTH) + " characters.";
Errorln(msg);
return -1;
}
char tmpDirPath[DIR_PATH_MAX_LENGTH + 1] = {0};
size_t fileNameStartIdx = 0;
for (size_t i = 0; i < dirPathLen; ++i) {
tmpDirPath[i] = directoryPath[i];
#ifdef _WIN32
if (!Utils::In(tmpDirPath[i], {BACKSLASH, SLASH})) {
#elif defined(__linux__) || defined(__APPLE__)
if (tmpDirPath[i] != SLASH) {
#endif
continue;
}
if (i - fileNameStartIdx > FILE_NAME_MAX_LENGTH) {
std::string msg = "Failed to create directory: " + std::string(tmpDirPath) +
"\nthe directory name cannot be longer than " + std::to_string(FILE_NAME_MAX_LENGTH) + " characters.";
Errorln(msg);
return -1;
}
fileNameStartIdx = i + 1;
if (Access(tmpDirPath, FM_EXIST)) {
continue;
}
int32_t ret = Mkdir(tmpDirPath);
if (ret != 0 && !Access(tmpDirPath, FM_EXIST)) {
return ret;
}
}
return 0;
}
std::string FindSerializationFile(
const std::string& fullPackageName, const std::string& suffix, const std::vector<std::string>& searchPaths)
{
auto names = Utils::SplitQualifiedName(fullPackageName);
auto packageName = ToCjoFileName(fullPackageName);
auto fileName = ToCjoFileName(names.front()) + DIR_SEPARATOR + packageName;
auto filePath = FindFileByName(fileName + suffix, searchPaths, true).value_or("");
if (filePath.empty()) {
return FindFileByName(packageName + suffix, searchPaths, true).value_or("");
}
return filePath;
}
std::vector<std::string> SplitEnvironmentPaths(const std::string& pathsString)
{
#ifdef _WIN32
const std::string separator = ";";
#else
const std::string separator = ":";
#endif
auto strings = Utils::SplitString(pathsString, separator);
std::vector<std::string> paths;
for (const std::string& s : strings) {
if (s.empty()) {
continue;
}
auto maybePath = GetAbsPath(s);
if (maybePath.has_value()) {
paths.emplace_back(maybePath.value());
}
}
return paths;
}
bool CanWrite(const std::string& path)
{
#ifdef _WIN32
std::optional<std::wstring> tempPath = StringConvertor::StringToWString(path);
if (!tempPath.has_value()) {
return false;
}
std::wstring wPath = tempPath.value();
WIN32_FILE_ATTRIBUTE_DATA wfad;
BOOL res = GetFileAttributesExW(wPath.c_str(), GetFileExInfoStandard, &wfad);
if (!res) {
return false;
}
return (wfad.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY;
#else
return access(path.c_str(), W_OK) == 0;
#endif
}
std::string RemovePathPrefix(const std::string& absolutePath, const std::vector<std::string>& removedPathPrefix)
{
#ifdef _WIN32
char split = '\\';
std::string res = absolutePath + split;
std::string tempRes{};
tempRes.reserve(res.size());
std::transform(res.begin(), res.end(), std::back_inserter(tempRes), &tolower);
for (auto prefix : removedPathPrefix) {
std::transform(prefix.begin(), prefix.end(), prefix.begin(), &tolower);
auto pos = tempRes.find(prefix);
#else
char split = '/';
std::string res = absolutePath + split;
for (auto& prefix : removedPathPrefix) {
auto pos = res.find(prefix);
#endif
if (pos == 0) {
res = res.substr(prefix.length());
if (res.empty()) {
return res;
}
res = res[0] == split ? res.substr(1) : res;
break;
}
}
return res.empty() ? res : res.substr(0, res.size() - 1);
}
#ifdef _WIN32
void HideFile(const std::string& path)
{
(void)SetFileAttributesA(path.c_str(), FILE_ATTRIBUTE_HIDDEN | GetFileAttributesA(path.c_str()));
}
#endif
std::string ToCjoFileName(std::string_view fullPackageName)
{
auto dc = TOKENS[static_cast<int>(TokenKind::DOUBLE_COLON)];
constexpr std::string::difference_type dcSize = 2;
if (auto it = fullPackageName.find(dc); it != std::string::npos) {
return std::string{fullPackageName.substr(it + dcSize)} + std::string{ORG_NAME_SEPARATOR} +
std::string{fullPackageName.substr(0, it)};
}
return std::string{fullPackageName};
}
std::string ToPackageName(std::string_view cjoName)
{
auto it = cjoName.find(ORG_NAME_SEPARATOR);
if (it != std::string::npos) {
return std::string{cjoName.substr(it + 1)} + TOKENS[static_cast<int>(TokenKind::DOUBLE_COLON)] +
std::string{cjoName.substr(0, it)};
}
return std::string{cjoName};
}
}