flatten 20260225

This commit is contained in:
Timothy Prepscius
2026-02-25 12:39:24 -05:00
commit fa54be052a
315 changed files with 49791 additions and 0 deletions

98
tjp/core/file/Contents.h Normal file
View File

@@ -0,0 +1,98 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/containers/Vector.h>
#include <tjp/core/string/StringView.h>
#include <tjp/core/string/String.h>
#include <tjp/core/const_expr/Str.hpp>
namespace tjp::core::file {
using Contents = Vector<char>;
struct ContentsView
{
using value_type = char;
using pointer_type = const value_type *;
const char *data_;
const size_t size_;
constexpr
pointer_type data() const
{
return data_;
}
constexpr
size_t size() const
{
return size_;
}
constexpr ContentsView() :
data_((char *)nullptr),
size_(0)
{
}
template<typename T>
constexpr ContentsView(const T *data, const size_t size) :
data_((pointer_type)data),
size_(size * sizeof(T))
{
}
template<typename T>
ContentsView(const Vector<T> &v) :
ContentsView(v.data(), v.size())
{
}
template<typename T>
constexpr ContentsView(const T &v) :
ContentsView(v.data(), v.size())
{
}
constexpr ContentsView(const char *data) :
ContentsView(data, core::const_expr::length(data))
{
}
constexpr ContentsView(const ContentsView &v) :
ContentsView(v.data(), v.size())
{
}
constexpr ContentsView(const StringView &v) :
ContentsView(v.data(), v.size())
{
}
ContentsView(const String &v) :
ContentsView(v.c_str(), v.size())
{
}
operator Contents() const
{
return Contents(data_, data_ + size_);
}
operator StringView() const
{
return StringView(data_, size_);
}
operator String() const
{
return String(data_, size_);
}
} ;
} // namespace

View File

@@ -0,0 +1,24 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/system/System.h>
#if defined(SYS_APPLE)
#include "apple/Directories.h"
namespace tjp::core::file {
using namespace file::apple;
}
#else
#include "linux/Directories.h"
namespace tjp::core::file {
using namespace file::linux;
}
#endif

158
tjp/core/file/File.cpp Normal file
View File

@@ -0,0 +1,158 @@
// TJP COPYRIGHT HEADER
#include "File.h"
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <utime.h>
#include <tjp/core/exception/Exception.hpp>
#include <tjp/core/string/to_string.hpp>
#include <tjp/core/string/StringView+concat.hpp>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
Optional<size_t> size(const StringView& path)
{
std::ifstream file(String(path), std::ios::ate | std::ios::binary);
bool exists = (bool)file;
if (!exists || !file.is_open())
return {};
size_t fileSize = (size_t)file.tellg();
return fileSize;
}
TJP_CORE_HEADER_ONLY_INLINE
Optional<Contents> readRange(const StringView& path, s64 offset, size_t length)
{
std::ifstream file(String(path), std::ios::ate | std::ios::binary);
bool exists = (bool)file;
if (!exists || !file.is_open())
return {};
size_t fileSize = (size_t)file.tellg();
if (offset < 0)
offset += s64(fileSize);
if (offset < 0)
return {};
if (fileSize < offset + length)
return {};
Contents buffer(length);
file.seekg(offset);
file.read(buffer.data(), buffer.size());
file.close();
return buffer;
};
TJP_CORE_HEADER_ONLY_INLINE
bool pathExists (const StringView &file)
{
struct stat status;
return (stat (file.data(), &status)==0);
}
TJP_CORE_HEADER_ONLY_INLINE
bool ensurePath (const StringView &base_, const StringView &path)
{
char delimiter = '/';
// find the end of the last portion of the path we need to create
auto finalPos = path.rfind(delimiter);
if (finalPos == path.npos)
return true;
auto base = String(base_);
if (!base.empty() && base.back() != delimiter)
base += delimiter;
decltype(finalPos) pos = 0;
auto colonpos = path.find(':');
auto firstdelimiterpos = path.find(delimiter);
if (colonpos > 0 && firstdelimiterpos > 0 && colonpos < firstdelimiterpos)
{
// Skip the first one if there's a colon ('c:/blah/whaterver')
pos = path.find (delimiter, pos+1);
}
while (pos<finalPos)
{
// find the next part of the path to create
pos = path.find (delimiter, pos+1);
// does it already exist
#ifdef WIN32
// pull it out as a separate string
std::wstring directory_ = toLongPath(path.substr (0, pos));
std::wstring directory = base + directory_;
WIN32_FILE_ATTRIBUTE_DATA data;
bool exists = GetFileAttributesExW (base + directory.c_str(), GetFileExInfoStandard, &data) > 0;
if (exists)
{
// if it does exist, but is not a directory, ensurePath fails
if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
return false;
}
else
{
// if it does not exist create it, if this fails ensurePath fails
if (_wmkdir (directory.c_str())!=0)
return false;
}
#else
auto directory_ = path.substr (0, pos);
auto directory = base + directory_;
struct stat status;
int exists = (stat (directory.c_str(), &status)==0);
if (exists)
{
// if it does exist, but is not a directory, ensurePath fails
if (!(status.st_mode & S_IFDIR))
return false;
}
else
{
// if it does not exist create it, if this fails ensurePath fails
auto result = mkdir (directory.c_str(), (S_IRWXU | S_IRWXG | S_IRWXO));
if (result)
return false;
}
#endif
}
return true;
}
TJP_CORE_HEADER_ONLY_INLINE
bool ensurePath (const StringView &path)
{
return ensurePath("", path);
}
} // namespace

46
tjp/core/file/File.h Normal file
View File

@@ -0,0 +1,46 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/header_only/compile.h>
#include "Contents.h"
#include <tjp/core/string/StringView.h>
#include <tjp/core/string/String.h>
#include <tjp/core/containers/Vector.h>
#include <tjp/core/containers/Optional.h>
#include <tjp/core/types/Types.h>
#include <time.h>
#include "file_exists.hpp"
#include "read.hpp"
#include "write.hpp"
#include "times.hpp"
namespace tjp::core::file {
Optional<size_t> size(const StringView &path);
Optional<Contents> readRange(const StringView &path, s64 offset, size_t length);
Contents readFile(const StringView &path);
bool writeFile(const StringView &path, const ContentsView &contents, const WriteOptions &options = {});
bool pathExists (const StringView &path);
bool ensurePath (const StringView &path);
bool ensurePath (const StringView &base, const StringView &path);
bool deleteFile(const StringView &path);
Vector<String> getFiles(const StringView &path, const Optional<String> &extension);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "File.cpp"
#endif

View File

@@ -0,0 +1,328 @@
// TJP COPYRIGHT HEADER
#include "File.h"
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <tjp/core/exception/Exception.hpp>
#include <tjp/core/system/System.h>
#include <tjp/core/string/to_string.hpp>
#include "../random/Random.h"
#include <tjp/core/log/Log.h>
#include <tjp/core/log/LogOf.h>
#include <dirent.h>
#include <sys/types.h>
#include <string>
#include <queue>
#include <filesystem>
#include <utime.h>
namespace tjp::core::file {
std::vector<char> readFile(const StringView& path)
{
std::ifstream file(String(path), std::ios::ate | std::ios::binary);
bool exists = (bool)file;
if (!exists || !file.is_open()) {
throw Exception { "failed to open file " + String(path) };
}
size_t fileSize = (size_t)file.tellg();
std::vector<char> buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), fileSize);
file.close();
return buffer;
};
bool writeFile(const StringView &path_, const ContentsView &contents, const WriteOptions &options)
{
auto path = String(path_);
auto extension = "." + core::to_string(core::StandardRandom.next<u32>());
auto temporary = path + extension;
std::ofstream file(temporary, std::ios::binary);
if (!file)
return false;
file.write(contents.data(), contents.size());
file.close();
remove(path.c_str());
if (rename(temporary.c_str(), path.c_str()))
return false;
if (options.lastModifiedTime)
{
setFileLastModifiedTime(path, options.lastModifiedTime);
}
return true;
}
/*
bool ensurePath (const StringView &path)
{
return ensurePath("", path);
}
bool ensurePath (const StringView &base_, const StringView &path_)
{
sLogDebug("core::file", logVar(base_) << logVar(path_));
char delimiter = '/';
auto path = String(path_);
auto base = String(base_);
if (!base.empty() && base.back() != delimiter)
base += delimiter;
// find the end of the last portion of the path we need to create
auto finalPos = path.rfind (delimiter);
decltype(finalPos) pos = 0;
auto colonpos = path.find(':');
auto firstdelimiterpos = path.find(delimiter);
if (colonpos > 0 && firstdelimiterpos > 0 && colonpos < firstdelimiterpos)
{
// Skip the first one if there's a colon ('c:/blah/whaterver')
pos = path.find (delimiter, pos+1);
}
while (pos<finalPos)
{
// find the next part of the path to create
pos = path.find (delimiter, pos+1);
// does it already exist
#ifdef WIN32
// pull it out as a separate string
std::wstring directory_ = toLongPath(path.substr (0, pos));
std::wstring directory = base + directory_;
WIN32_FILE_ATTRIBUTE_DATA data;
bool exists = GetFileAttributesExW (base + directory.c_str(), GetFileExInfoStandard, &data) > 0;
if (exists)
{
// if it does exist, but is not a directory, ensurePath fails
if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
return false;
}
else
{
// if it does not exist create it, if this fails ensurePath fails
if (_wmkdir (directory.c_str())!=0)
return false;
}
#else
std::string directory_ = path.substr (0, pos);
std::string directory = String(base) + directory_;
sLogDebug("core::file", logVar(path) << logVar(directory));
struct stat status;
int exists = (stat (directory.c_str(), &status)==0);
if (exists)
{
// if it does exist, but is not a directory, ensurePath fails
if (!(status.st_mode & S_IFDIR))
return false;
}
else
{
// if it does not exist create it, if this fails ensurePath fails
auto result = mkdir (directory.c_str(), (S_IRWXU | S_IRWXG | S_IRWXO));
if (result)
return false;
}
#endif
}
return true;
}
bool fileExists (const StringView &file)
{
#ifdef WIN32
WIN32_FILE_ATTRIBUTE_DATA data;
bool exists = GetFileAttributesExW (toLongPath(file).c_str(), GetFileExInfoStandard, &data) > 0;
return exists && !(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
#else
struct stat status;
if (stat (file.data(), &status)==0)
{
// if it is not a directory return true
if (!(status.st_mode & S_IFDIR))
return true;
}
return false;
#endif
}
bool pathExists (const StringView &file)
{
#ifdef WIN32
WIN32_FILE_ATTRIBUTE_DATA data;
bool exists = GetFileAttributesExW (toLongPath(file).c_str(), GetFileExInfoStandard, &data) > 0;
return exists;
#else
struct stat status;
return (stat (file.data(), &status)==0);
#endif
}
*/
bool deleteFile(const StringView &file)
{
return remove(file.data()) == 0;
}
#ifndef SYS_IOS
Vector<String> getFiles(const StringView &path, const Optional<String> &extension)
{
Vector<String> files;
try
{
std::queue<std::filesystem::path> dirs;
std::filesystem::path basePath = std::filesystem::canonical(path);
dirs.push(basePath);
while (!dirs.empty())
{
auto current = dirs.front();
dirs.pop();
for (const auto &entry : std::filesystem::directory_iterator(current))
{
if (entry.is_directory())
{
dirs.push(entry.path());
}
else
if (entry.is_regular_file())
{
if (!extension || entry.path().extension() == *extension) {
files.push_back(std::filesystem::relative(entry.path(), basePath).string());
}
}
}
}
}
catch (std::filesystem::filesystem_error &)
{
std::cerr << "caught std::filesystem::filesystem_error " << std::endl;
}
catch (std::exception &)
{
std::cerr << "caught std::exception " << std::endl;
}
catch (...)
{
std::cerr << "caught unknown exception " << std::endl;
}
return files;
}
#else
Vector<String> getFiles(const StringView &path_, const Optional<String> &extension)
{
auto path = String(path_);
Vector<String> files;
std::queue<std::string> dirs;
dirs.push("");
while (!dirs.empty())
{
auto relativeDir = dirs.front();
std::string absoluteDir = path + relativeDir;
dirs.pop();
DIR *dir = opendir(absoluteDir.c_str());
if (!dir) {
continue;
}
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
std::string name(entry->d_name);
// skip "." and ".."
if (name == "." || name == "..") {
continue;
}
std::string relativePath =
relativeDir.empty() ?
name :
relativeDir + "/" + name;
if (entry->d_type == DT_DIR)
{
// enqueue subdirectory
dirs.push(relativePath);
}
else
{
if (extension.has_value())
{
if (name.size() >= extension->size() &&
name.compare(name.size() - extension->size(), extension->size(), *extension) == 0)
{
files.emplace_back(relativePath.c_str());
}
}
else
{
files.emplace_back(relativePath.c_str());
}
}
}
closedir(dir);
}
return files;
}
#endif
} // namespace

169
tjp/core/file/Path.cpp Normal file
View File

@@ -0,0 +1,169 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include "Path.h"
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
std::string_view removeExtensions(const std::string_view &s)
{
auto lastSlash = s.rfind('/');
auto firstDotAfterLastSlash = s.find('.', lastSlash+1);
return s.substr(0, firstDotAfterLastSlash);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string joinDirs(const std::vector<std::string_view> &dirs)
{
std::string joined;
for (auto &dir : dirs)
{
if (dir.empty())
continue;
bool first = joined.empty();
if (dir.front() == '/' && !first)
joined += dir.substr(1);
else
joined += dir;
if (joined.back() != '/')
joined.push_back('/');
}
return joined;
}
TJP_CORE_HEADER_ONLY_INLINE
std::string joinPaths(const std::vector<std::string_view> &dirs)
{
std::string joined;
for (auto &dir : dirs)
{
if (dir.empty())
continue;
bool first = joined.empty();
if (!first)
if (joined.back() != '/')
joined.push_back('/');
if (dir.front() == '/' && !first)
joined += dir.substr(1);
else
joined += dir;
}
return joined;
}
TJP_CORE_HEADER_ONLY_INLINE
std::string joinDirsAndFile(const std::vector<std::string_view> &dirs, const std::string &fileName)
{
return joinDirs(dirs) + fileName;
}
TJP_CORE_HEADER_ONLY_INLINE
std::string convertDelimiter (const std::string &str, char from, char to)
{
if (from==to) return str;
if (str.empty()) return str;
std::string copy = str;
for (auto &c : copy)
if (c == from)
c = to;
return copy;
}
TJP_CORE_HEADER_ONLY_INLINE
std::string convertDelimiter (const std::string &path)
{
return path;
}
template<typename T>
T popFileName_(const T &path)
{
auto lastDelimiter = path.rfind('/');
return path.substr(0, lastDelimiter+1);
}
template<typename T>
T popLast_(const T &path)
{
if (path.empty())
return {};
if (path.back() != '/')
return popFileName(path);
if (path.size() < 2)
return {};
auto lastDelimiter = path.rfind('/', path.size()-2);
return path.substr(0, lastDelimiter+1);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string_view popFileName(const std::string_view &path)
{
return popFileName_(path);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string_view popLast(const std::string_view &path)
{
return popLast_(path);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string popFileName(const std::string &path)
{
return popFileName_(path);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string popLast(const std::string &path)
{
return popLast_(path);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string fileName(const std::string &path)
{
auto lastDelimiter = path.rfind('/');
if (lastDelimiter == std::string::npos)
return path;
return path.substr(lastDelimiter+1);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string_view fileName(const std::string_view &path)
{
auto lastDelimiter = path.rfind('/');
if (lastDelimiter == std::string::npos)
return path;
return path.substr(lastDelimiter+1);
}
TJP_CORE_HEADER_ONLY_INLINE
std::string extension(const std::string &path)
{
auto lastDelimiter = path.rfind('.');
if (lastDelimiter == std::string::npos)
return "";
return path.substr(lastDelimiter);
}
} // namespace

44
tjp/core/file/Path.h Normal file
View File

@@ -0,0 +1,44 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <vector>
#include <string>
#include <tjp/core/header_only/compile.h>
namespace tjp::core::file {
std::string joinDirs(const std::vector<std::string_view> &dirs);
std::string joinPaths(const std::vector<std::string_view> &paths);
std::string joinDirsAndFile(const std::vector<std::string_view> &dirs, const std::string_view &fileName);
std::string fileName(const std::string &path);
std::string_view fileName(const std::string_view &path);
std::string extension(const std::string &path);
std::string_view removeExtensions(const std::string_view &path);
std::string_view popFileName(const std::string_view &path);
std::string_view popLast(const std::string_view &path);
std::string popFileName(const std::string &path);
std::string popLast(const std::string &path);
#ifdef WIN32
const char LocalDirectoryDelimiter = '\\';
#else
const char LocalDirectoryDelimiter = '/';
#endif
const char ServerDirectoryDelimiter = '/';
std::string convertDelimiter (const std::string &path);
std::string convertDelimiter (const std::string &path, char from, char to);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "Path.cpp"
#endif

View File

@@ -0,0 +1,31 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "VirtualFileSystem.hpp"
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
Contents readFile(const VirtualFileSystem &system, const StringView &path)
{
return system.get(path);
}
TJP_CORE_HEADER_ONLY_INLINE
void writeFile(VirtualFileSystem &system, const StringView &path, ContentsView contents)
{
return system.put(path, contents);
}
TJP_CORE_HEADER_ONLY_INLINE
bool fileExists(const VirtualFileSystem &system, const StringView &path)
{
return system.has(path);
}
} // namespace

View File

@@ -0,0 +1,20 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "Contents.h"
#include <tjp/core/string/StringView.h>
namespace tjp::core::file {
struct VirtualFileSystem;
Contents readFile(const VirtualFileSystem &system, const StringView &path);
void writeFile(VirtualFileSystem &system, const StringView &path, ContentsView);
bool fileExists(const VirtualFileSystem &system, const StringView &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "VirtualFileSystem.cpp"
#endif

View File

@@ -0,0 +1,108 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "File.h"
#include "Path.h"
namespace tjp::core::file {
struct VirtualFileSystem
{
virtual ~VirtualFileSystem () {};
virtual String pathFor(const StringView &path) const = 0;
virtual Optional<Contents> get_if(const StringView &path) const = 0;
virtual Contents get(const StringView &path) const = 0;
virtual void put(const StringView &path, ContentsView) = 0;
virtual bool has(const StringView &path) const = 0;
} ;
struct VirtualFileSystemRoot : VirtualFileSystem
{
String base;
VirtualFileSystemRoot(const StringView &base_) :
base(base_)
{}
inline
String pathFor(const StringView &path) const override
{
return joinPaths({ base, String(path) });
}
inline
Optional<Contents> get_if(const StringView &path) const override
{
return file::read(pathFor(path));
}
inline
Contents get(const StringView &path) const override
{
return file::require_read(pathFor(path));
}
inline
void put(const StringView &path, ContentsView contents) override
{
file::ensurePath(base, path);
file::require_write(pathFor(path), contents);
}
inline
bool has(const StringView &path) const override
{
return file::fileExists(pathFor(path));
}
} ;
struct VirtualFileSystemRelative : VirtualFileSystem
{
VirtualFileSystem *to;
String base;
VirtualFileSystemRelative(VirtualFileSystem *to_, const StringView &base_) :
to(to_),
base(base_)
{}
String join(const StringView &path) const
{
return joinPaths({ base, String(path) });
}
String pathFor(const StringView &path) const override
{
return to->pathFor(join(path));
}
inline
Optional<Contents> get_if(const StringView &path) const override
{
return to->get_if(join(path));
}
inline
Contents get(const StringView &path) const override
{
return to->get(join(path));
}
inline
void put(const StringView &path, ContentsView contents) override
{
to->put(join(path), contents);
}
inline
bool has(const StringView &path) const override
{
return to->has(join(path));
}
} ;
} // namespace

View File

@@ -0,0 +1,32 @@
// TJP COPYRIGHT HEADER
#include <tjp/core/testing/catch.hpp>
#include <tjp/core/containers/Vector.hpp>
#include <tjp/core/containers/Tuple.hpp>
#include <tjp/core/string/String.hpp>
#include "../Path.h"
namespace tjp::core::file {
namespace {
SCENARIO("core::file::Path")
{
GIVEN("fileNames")
{
Vector<Tuple<String, String>> tests {
{ "hello.cpp", "hello" },
{ "asdf.123.345.456", "asdf" },
{ "/asdf/123.asdf.sdf", "/asdf/123" },
{ "/asdf.zxc/123.asdf.sdf", "/asdf.zxc/123" }
};
for (auto &[t, e] : tests)
{
auto v = removeExtensions(t);
REQUIRE(v == e);
}
}
}
} // namespace
} // namespace

View File

@@ -0,0 +1,71 @@
// TJP COPYRIGHT HEADER
#include "Directories.h"
#include "../Path.h"
#include <tjp/core/system/System.h>
#ifdef SYS_MAC
#include <mach-o/dyld.h>
#include "CoreFoundation/CFBundle.h"
#endif
#ifdef SYS_IOS
#include <mach-o/dyld.h>
#include "CoreFoundation/CFBundle.h"
#endif
#include <stdlib.h>
namespace tjp {
namespace core {
namespace file {
namespace apple {
std::string getHomeDirectory ()
{
const char *homeDir = getenv("HOME");
if (homeDir)
return homeDir;
return {};
}
std::string getExecutablePath ()
{
char path_[PATH_MAX+1];
uint32_t size=PATH_MAX;
_NSGetExecutablePath(path_, &size);
if (size > 0)
{
auto path = std::string(path_);
return path;
}
return {};
}
std::string getExecutableDirectory ()
{
auto path = getExecutablePath();
return popFileName(path);
}
std::string getExecutableBundleDirectory ()
{
return popLast(getExecutableDirectory());
}
std::string getExecutableResourceDirectory ()
{
auto path = getExecutableBundleDirectory();
return joinDirs({ path, "Resources"});
}
} // namespace
} // namespace
} // namespace
} // namespace

View File

@@ -0,0 +1,22 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <vector>
#include <string>
namespace tjp {
namespace core {
namespace file {
namespace apple {
std::string getExecutablePath();
std::string getExecutableDirectory();
std::string getExecutableBundleDirectory();
std::string getExecutableResourceDirectory();
std::string getHomeDirectory();
} // namespace
} // namespace
} // namespace
} // namespace

28
tjp/core/file/copy.cpp Normal file
View File

@@ -0,0 +1,28 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "copy.hpp"
#include "read.hpp"
#include "write.hpp"
#include <cstdio>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool copy(const String &from, const String &to)
{
if (auto contents = read(from))
if (write(to, *contents))
return true;
return false;
}
} // namespace

15
tjp/core/file/copy.hpp Normal file
View File

@@ -0,0 +1,15 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
namespace tjp::core::file {
bool copy(const String &path, const String &to);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "copy.cpp"
#endif

View File

@@ -0,0 +1,28 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "directory_exists.hpp"
#include <sys/stat.h>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool directory_exists (const String &file)
{
struct stat status;
if (stat (file.c_str(), &status)==0)
{
// if it is not a directory return true
if (status.st_mode & S_IFDIR)
return true;
}
return false;
}
} // namespace

View File

@@ -0,0 +1,15 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
namespace tjp::core::file {
bool directory_exists(const String &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "directory_exists.cpp"
#endif

View File

@@ -0,0 +1,20 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "directory_of.hpp"
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
StringView directory_of(const StringView &path)
{
auto lastDelimiter = path.rfind('/');
return path.substr(0, lastDelimiter+1);
}
} // namespace

View File

@@ -0,0 +1,15 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/StringView.hpp>
namespace tjp::core::file {
StringView directory_of(const StringView &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "directory_of.cpp"
#endif

View File

@@ -0,0 +1,34 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "file_exists.hpp"
#include <sys/stat.h>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool fileExists (const String &file)
{
struct stat status;
if (stat (file.c_str(), &status)==0)
{
// if it is not a directory return true
if (!(status.st_mode & S_IFDIR))
return true;
}
return false;
}
TJP_CORE_HEADER_ONLY_INLINE
bool fileExists (const StringView &file)
{
return fileExists(String(file));
}
} // namespace

View File

@@ -0,0 +1,17 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
#include <tjp/core/string/StringView.hpp>
namespace tjp::core::file {
bool fileExists(const String &path);
bool fileExists(const StringView &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "file_exists.cpp"
#endif

135
tjp/core/file/get_files.cpp Normal file
View File

@@ -0,0 +1,135 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "get_files.hpp"
#include <tjp/core/system/System.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <queue>
#include <filesystem>
namespace tjp::core::file {
#ifndef SYS_IOS
TJP_CORE_HEADER_ONLY_INLINE
Vector<String> get_files(const String &path, const Optional<String> &extension, bool recursive)
{
Vector<String> files;
try
{
std::queue<std::filesystem::path> dirs;
std::filesystem::path basePath = std::filesystem::canonical(path);
dirs.push(basePath);
while (!dirs.empty())
{
auto current = dirs.front();
dirs.pop();
for (const auto &entry : std::filesystem::directory_iterator(current))
{
if (entry.is_directory())
{
if (recursive)
dirs.push(entry.path());
}
else
if (entry.is_regular_file())
{
if (!extension || entry.path().extension() == *extension) {
files.push_back(std::filesystem::relative(entry.path(), basePath).string());
}
}
}
}
}
catch (std::filesystem::filesystem_error &)
{
}
catch (std::exception &)
{
}
catch (...)
{
}
return files;
}
#else
TJP_CORE_HEADER_ONLY_INLINE
Vector<String> get_files(const String &path_, const Optional<String> &extension, bool recursive)
{
auto path = String(path_);
Vector<String> files;
std::queue<std::string> dirs;
dirs.push("");
while (!dirs.empty())
{
auto relativeDir = dirs.front();
std::string absoluteDir = path + relativeDir;
dirs.pop();
DIR *dir = opendir(absoluteDir.c_str());
if (!dir) {
continue;
}
struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
std::string name(entry->d_name);
// skip "." and ".."
if (name == "." || name == "..") {
continue;
}
std::string relativePath =
relativeDir.empty() ?
name :
relativeDir + "/" + name;
if (entry->d_type == DT_DIR)
{
// enqueue subdirectory
if (recursive)
dirs.push(relativePath);
}
else
{
if (extension.has_value())
{
if (name.size() >= extension->size() &&
name.compare(name.size() - extension->size(), extension->size(), *extension) == 0)
{
files.emplace_back(relativePath.c_str());
}
}
else
{
files.emplace_back(relativePath.c_str());
}
}
}
closedir(dir);
}
return files;
}
#endif
} // namespace

View File

@@ -0,0 +1,17 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
#include <tjp/core/containers/Vector.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
Vector<String> get_files(const String &path, const Optional<String> &extension, bool recursive);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "get_files.cpp"
#endif

View File

@@ -0,0 +1,15 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/StringView.hpp>
namespace tjp::core::file {
inline
bool is_relative_path(const StringView &p)
{
return p.empty() || p[0] != '/';
}
} // namespace

View File

@@ -0,0 +1,47 @@
// TJP COPYRIGHT HEADER
#include "Directories.h"
#include "../Path.h"
#include <stdlib.h>
namespace tjp {
namespace core {
namespace file {
namespace linux {
std::string getHomeDirectory()
{
const char *homeDir = getenv("HOME");
if (homeDir)
return homeDir;
return {};
}
std::string getExecutablePath ()
{
return "./";
}
std::string getExecutableDirectory ()
{
auto path = getExecutablePath();
return popFileName(path);
}
std::string getExecutableBundleDirectory ()
{
return getExecutableDirectory();
}
std::string getExecutableResourceDirectory ()
{
return getExecutableDirectory();
}
} // namespace
} // namespace
} // namespace
} // namespace

View File

@@ -0,0 +1,22 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <vector>
#include <string>
namespace tjp {
namespace core {
namespace file {
namespace linux {
std::string getExecutablePath();
std::string getExecutableDirectory();
std::string getExecutableBundleDirectory();
std::string getExecutableResourceDirectory();
std::string getHomeDirectory();
} // namespace
} // namespace
} // namespace
} // namespace

32
tjp/core/file/move.cpp Normal file
View File

@@ -0,0 +1,32 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "move.hpp"
#include <tjp/core/exception/Exception.hpp>
#include <cstdio>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool move(const String &from, const String &to)
{
return std::rename(from.c_str(), to.c_str()) != 0;
}
TJP_CORE_HEADER_ONLY_INLINE
void require_move(const String &from, const String &to)
{
if (!move(from, to))
{
throw Exception { "Move failed" };
}
}
} // namespace

16
tjp/core/file/move.hpp Normal file
View File

@@ -0,0 +1,16 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
namespace tjp::core::file {
void require_move(const String &path, const String &to);
bool move(const String &path, const String &to);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "move.cpp"
#endif

View File

@@ -0,0 +1,21 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
template<typename T>
Optional<T> pop_first(const T &path)
{
auto s = path.find('/');
if (s != path.npos)
return path.substr(s+1);
return {};
}
} // namespace

62
tjp/core/file/read.cpp Normal file
View File

@@ -0,0 +1,62 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "read.hpp"
#include <tjp/core/exception/Exception.hpp>
#include <cstdio>
#include <fstream>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
Optional<Contents> read(const String& path)
{
std::ifstream file(path, std::ios::ate | std::ios::binary);
bool exists = (bool)file;
if (!exists || !file.is_open())
return {};
size_t fileSize = (size_t)file.tellg();
Contents buffer(fileSize);
file.seekg(0);
file.read(buffer.data(), buffer.size());
file.close();
return buffer;
};
TJP_CORE_HEADER_ONLY_INLINE
Contents require_read(const String& path)
{
if (auto result = read(path))
return *result;
throw Exception { "failed to read file " + String(path) };
}
TJP_CORE_HEADER_ONLY_INLINE
Optional<Contents> read(const StringView& path)
{
return read(String(path));
}
TJP_CORE_HEADER_ONLY_INLINE
Contents require_read(const StringView& path)
{
return require_read(String(path));
}
} // namespace

21
tjp/core/file/read.hpp Normal file
View File

@@ -0,0 +1,21 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "Contents.h"
#include <tjp/core/string/String.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
Optional<Contents> read(const String &path);
Contents require_read(const String &path);
Optional<Contents> read(const StringView &path);
Contents require_read(const StringView &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "read.cpp"
#endif

View File

@@ -0,0 +1,28 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "split_first_directory.hpp"
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
Optional<Tuple<StringView, StringView>> split_first_directory(const StringView &path)
{
auto slash = path.find('/');
if (slash != path.npos)
{
return Tuple<StringView,StringView> {
path.substr(0, slash),
path.substr(slash+1)
} ;
}
return {};
}
} // namespace

View File

@@ -0,0 +1,17 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/StringView.hpp>
#include <tjp/core/containers/Tuple.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
Optional<Tuple<StringView, StringView>> split_first_directory(const StringView &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "split_first_directory.cpp"
#endif

74
tjp/core/file/times.cpp Normal file
View File

@@ -0,0 +1,74 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "times.hpp"
#include <tjp/core/exception/Exception.hpp>
#include <cstdio>
#include <sys/stat.h>
#include <utime.h>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
time_t getFileLastModifiedTime(const String & fullFileName)
{
struct stat status;
int result = stat(fullFileName.c_str(), &status);
if (result != 0)
return 0;
return status.st_mtime;
}
TJP_CORE_HEADER_ONLY_INLINE
bool setFileLastModifiedTime(const String & fullFileName, time_t time)
{
struct utimbuf timbuf;
timbuf.actime = getFileAccessTime(fullFileName);
timbuf.modtime = time;
int result = utime(fullFileName.c_str(), &timbuf);
if (result != 0)
return false;
return true;
}
TJP_CORE_HEADER_ONLY_INLINE
time_t getFileCreationTime (const String & fullFileName)
{
struct stat status;
int result = stat(fullFileName.c_str(), &status);
if (result != 0)
return 0;
return status.st_ctime;
}
TJP_CORE_HEADER_ONLY_INLINE
time_t getFileAccessTime (const String & fullFileName)
{
struct stat status;
int result = stat(fullFileName.c_str(), &status);
if (result != 0)
return 0;
return status.st_atime;
}
} // namespace

22
tjp/core/file/times.hpp Normal file
View File

@@ -0,0 +1,22 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <time.h>
#include <tjp/core/string/String.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
time_t getFileLastModifiedTime(const String & fullFileName);
bool setFileLastModifiedTime(const String & fullFileName, time_t);
time_t getFileCreationTime (const String & fullFileName);
time_t getFileAccessTime (const String & fullFileName);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "times.cpp"
#endif

24
tjp/core/file/unlink.cpp Normal file
View File

@@ -0,0 +1,24 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "unlink.hpp"
#include <tjp/core/exception/Exception.hpp>
#include <cstdio>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool unlink (const String &file)
{
return std::remove(file.data()) == 0;
}
} // namespace

15
tjp/core/file/unlink.hpp Normal file
View File

@@ -0,0 +1,15 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/string/String.hpp>
namespace tjp::core::file {
bool unlink(const String &path);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "unlink.cpp"
#endif

View File

@@ -0,0 +1,42 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "validate_safe_relative_path.hpp"
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool is_safe_relative_path(const StringView &s)
{
if (s.find("..") == -1)
return false;
if (s.empty())
return false;
if (s[0] == '/')
return false;
return true;
}
TJP_CORE_HEADER_ONLY_INLINE
void validate_safe_relative_path(const StringView &s)
{
if (s.find("..") == -1)
throw Exception { "Path has dots" };
if (s.empty())
throw Exception { "Path is empty" };
if (s[0] == '/')
throw Exception { "Path begins with slash" };
}
} // namespace

View File

@@ -0,0 +1,17 @@
// TJP COPYRIGHT HEADER
#pragma once
#include <tjp/core/exception/Exception.hpp>
#include <tjp/core/string/StringView.hpp>
namespace tjp::core::file {
bool is_safe_relative_path(const StringView &);
void validate_safe_relative_path(const StringView &);
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "validate_safe_relative_path.cpp"
#endif

66
tjp/core/file/write.cpp Normal file
View File

@@ -0,0 +1,66 @@
// TJP COPYRIGHT HEADER
#ifdef TJP_CORE_HEADER_ONLY
#pragma once
#endif
#include <tjp/core/header_only/compile.h>
#include "write.hpp"
#include <tjp/core/exception/Exception.hpp>
#include "../random/file_name.hpp"
#include "times.hpp"
#include <cstdio>
#include <fstream>
namespace tjp::core::file {
TJP_CORE_HEADER_ONLY_INLINE
bool write(const String &path_, const ContentsView &contents, const WriteOptions &options)
{
auto path = String(path_);
auto random_ = random::file_name();
auto extension = "." + random_;
auto temporary = path + extension;
std::ofstream file(temporary, std::ios::binary);
if (!file)
return false;
file.write(contents.data(), contents.size());
file.close();
remove(path.c_str());
if (rename(temporary.c_str(), path.c_str()))
return false;
if (options.lastModifiedTime)
setFileLastModifiedTime(path, options.lastModifiedTime);
return true;
}
TJP_CORE_HEADER_ONLY_INLINE
void require_write(const String &path, const ContentsView &contents, const WriteOptions &options)
{
if (!write(path, contents, options))
throw Exception { "failed to write file " + String(path) };
}
TJP_CORE_HEADER_ONLY_INLINE
bool write(const StringView &path, const ContentsView &contents, const WriteOptions &options)
{
return write(String(path), contents, options);
}
TJP_CORE_HEADER_ONLY_INLINE
void require_write(const StringView &path, const ContentsView &contents, const WriteOptions &options)
{
return require_write(String(path), contents, options);
}
} // namespace

25
tjp/core/file/write.hpp Normal file
View File

@@ -0,0 +1,25 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "Contents.h"
#include <tjp/core/string/String.hpp>
#include <tjp/core/containers/Optional.hpp>
namespace tjp::core::file {
struct WriteOptions {
time_t lastModifiedTime = 0;
} ;
bool write(const String &path, const ContentsView &, const WriteOptions &options = {});
void require_write(const String &path, const ContentsView &, const WriteOptions &options = {});
bool write(const StringView &path, const ContentsView &, const WriteOptions &options = {});
void require_write(const StringView &path, const ContentsView &, const WriteOptions &options = {});
} // namespace
#ifdef TJP_CORE_HEADER_ONLY
#include "write.cpp"
#endif

View File

@@ -0,0 +1,53 @@
// TJP COPYRIGHT HEADER
#include "writeSegmentsToFile.h"
#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <tjp/core/exception/Exception.hpp>
#include <tjp/core/system/System.h>
#include <tjp/core/string/to_string.hpp>
#include "../random/Random.h"
#include <utime.h>
namespace tjp {
namespace core {
namespace file {
bool writeSegmentsToFile(const StringView &path_, const StackArray<containers::MemorySegment<u8>> &segments, const WriteOptions &options)
{
auto path = String(path_);
auto extension = "." + core::to_string(core::StandardRandom.next<u32>());
auto temporary = path + extension;
std::ofstream file(temporary, std::ios::binary);
if (!file)
return false;
for (auto &contents: segments)
file.write((char *)contents.data(), contents.size());
file.close();
remove(path.c_str());
if (rename(temporary.c_str(), path.c_str()))
return false;
if (options.lastModifiedTime)
{
setFileLastModifiedTime(path, options.lastModifiedTime);
}
return true;
}
} // namespace
} // namespace
} // namespace

View File

@@ -0,0 +1,18 @@
// TJP COPYRIGHT HEADER
#pragma once
#include "File.h"
#include <tjp/core/containers/MemorySegment.hpp>
#include <tjp/core/containers/StackArray.hpp>
namespace tjp {
namespace core {
namespace file {
bool writeSegmentsToFile(const StringView &path, const StackArray<containers::MemorySegment<u8>> &contents, const WriteOptions &options = {});
} // namespace
} // namespace
} // namespace