212 lines
4.9 KiB
C++
212 lines
4.9 KiB
C++
#include "breeze/Fs.hpp"
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <sstream>
|
|
#include <system_error>
|
|
|
|
#include "breeze/Error.hpp"
|
|
|
|
#if defined(_WIN32)
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <windows.h>
|
|
#elif defined(__APPLE__)
|
|
#include <mach-o/dyld.h>
|
|
#include <unistd.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if !defined(_WIN32)
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
namespace breeze::fs
|
|
{
|
|
|
|
std::optional<std::string> tryReadText(const path& file)
|
|
{
|
|
std::ifstream in(file, std::ios::binary);
|
|
if (!in)
|
|
return std::nullopt;
|
|
std::ostringstream buffer;
|
|
buffer << in.rdbuf();
|
|
return buffer.str();
|
|
}
|
|
|
|
std::string readText(const path& file)
|
|
{
|
|
std::optional<std::string> text = tryReadText(file);
|
|
if (!text)
|
|
throw Error("cannot read '" + generic(file) + "'");
|
|
return *text;
|
|
}
|
|
|
|
void writeText(const path& file, const std::string& text)
|
|
{
|
|
std::error_code ec;
|
|
if (file.has_parent_path())
|
|
std::filesystem::create_directories(file.parent_path(), ec);
|
|
std::ofstream out(file, std::ios::binary | std::ios::trunc);
|
|
if (!out)
|
|
throw Error("cannot write '" + generic(file) + "'");
|
|
out << text;
|
|
if (!out)
|
|
throw Error("cannot write '" + generic(file) + "'");
|
|
}
|
|
|
|
void copyTree(const path& from, const path& to, const std::vector<std::string>& skipNames)
|
|
{
|
|
namespace sfs = std::filesystem;
|
|
std::error_code ec;
|
|
sfs::create_directories(to, ec);
|
|
for (const sfs::directory_entry& entry : sfs::directory_iterator(from))
|
|
{
|
|
std::string name = entry.path().filename().string();
|
|
if (std::find(skipNames.begin(), skipNames.end(), name) != skipNames.end())
|
|
continue;
|
|
path target = to / name;
|
|
if (entry.is_symlink())
|
|
{
|
|
sfs::copy_symlink(entry.path(), target, ec);
|
|
if (ec)
|
|
throw Error("cannot copy '" + generic(entry.path()) + "': " + ec.message());
|
|
}
|
|
else if (entry.is_directory())
|
|
{
|
|
copyTree(entry.path(), target, skipNames);
|
|
}
|
|
else
|
|
{
|
|
sfs::copy_file(entry.path(), target, sfs::copy_options::overwrite_existing, ec);
|
|
if (ec)
|
|
throw Error("cannot copy '" + generic(entry.path()) + "': " + ec.message());
|
|
}
|
|
}
|
|
}
|
|
|
|
void removeTree(const path& p)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(p, ec);
|
|
if (ec)
|
|
throw Error("cannot remove '" + generic(p) + "': " + ec.message());
|
|
}
|
|
|
|
path makeTempDir(const std::string& prefix)
|
|
{
|
|
std::random_device rd;
|
|
std::mt19937_64 gen(rd());
|
|
for (int attempt = 0; attempt < 100; ++attempt)
|
|
{
|
|
std::ostringstream name;
|
|
name << prefix << "-" << std::hex << gen();
|
|
path dir = std::filesystem::temp_directory_path() / name.str();
|
|
std::error_code ec;
|
|
if (std::filesystem::create_directory(dir, ec))
|
|
return dir;
|
|
}
|
|
throw Error("cannot create a temporary directory");
|
|
}
|
|
|
|
path homeDir()
|
|
{
|
|
#if defined(_WIN32)
|
|
if (const char* p = std::getenv("USERPROFILE"))
|
|
return path(p);
|
|
#endif
|
|
if (const char* p = std::getenv("HOME"))
|
|
return path(p);
|
|
throw Error("cannot tell where your home directory is (HOME is not set)");
|
|
}
|
|
|
|
path executablePath()
|
|
{
|
|
#if defined(_WIN32)
|
|
std::wstring buffer(32768, L'\0');
|
|
DWORD n = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
|
|
buffer.resize(n);
|
|
return path(buffer);
|
|
#elif defined(__APPLE__)
|
|
std::uint32_t size = 0;
|
|
_NSGetExecutablePath(nullptr, &size);
|
|
std::string buffer(size, '\0');
|
|
if (_NSGetExecutablePath(buffer.data(), &size) != 0)
|
|
throw Error("cannot tell where this program is");
|
|
buffer.resize(std::char_traits<char>::length(buffer.c_str()));
|
|
std::error_code ec;
|
|
path canonical = std::filesystem::canonical(buffer, ec);
|
|
return ec ? path(buffer) : canonical;
|
|
#else
|
|
std::error_code ec;
|
|
path p = std::filesystem::read_symlink("/proc/self/exe", ec);
|
|
if (ec)
|
|
throw Error("cannot tell where this program is: " + ec.message());
|
|
return p;
|
|
#endif
|
|
}
|
|
|
|
std::string hostPlatform()
|
|
{
|
|
#if defined(_WIN32)
|
|
return "windows";
|
|
#elif defined(__APPLE__)
|
|
return "macos";
|
|
#else
|
|
return "linux";
|
|
#endif
|
|
}
|
|
|
|
std::string hostArch()
|
|
{
|
|
#if defined(__x86_64__) || defined(_M_X64)
|
|
return "x64";
|
|
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
return "arm64";
|
|
#elif defined(__arm__) || defined(_M_ARM)
|
|
return "arm32";
|
|
#else
|
|
return "x86";
|
|
#endif
|
|
}
|
|
|
|
void makeExecutable(const path& file)
|
|
{
|
|
#if !defined(_WIN32)
|
|
std::error_code ec;
|
|
std::filesystem::permissions(file,
|
|
std::filesystem::perms::owner_exec |
|
|
std::filesystem::perms::group_exec |
|
|
std::filesystem::perms::others_exec,
|
|
std::filesystem::perm_options::add, ec);
|
|
if (ec)
|
|
throw Error("cannot make '" + generic(file) + "' executable: " + ec.message());
|
|
#else
|
|
(void)file;
|
|
#endif
|
|
}
|
|
|
|
bool isExecutable(const path& file)
|
|
{
|
|
std::error_code ec;
|
|
if (!std::filesystem::is_regular_file(file, ec))
|
|
return false;
|
|
#if defined(_WIN32)
|
|
return true;
|
|
#else
|
|
return ::access(file.c_str(), X_OK) == 0;
|
|
#endif
|
|
}
|
|
|
|
std::string generic(const path& p)
|
|
{
|
|
return p.generic_string();
|
|
}
|
|
|
|
}
|