diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1fd44b0..f8ce8e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -125,11 +125,9 @@ add_library(fennec STATIC
include/fennec/fproc/strings/detail/__ctype.h
- # IO
- include/fennec/fproc/io/file.h source/fproc/io/file.cpp
- include/fennec/fproc/io/common.h
- source/fproc/io/common.cpp
-
+ # Filesystem
+ include/fennec/fproc/filesystem/file.h source/fproc/filesystem/file.cpp
+ include/fennec/fproc/filesystem/path.h source/fproc/filesystem/path.cpp
)
# add metaprogramming templates as a dependency and also force documentation to be generated when fennec is compiled
diff --git a/README.md b/README.md
index 43730c7..9df1a7b 100644
--- a/README.md
+++ b/README.md
@@ -65,24 +65,24 @@ fennec Standards:
called `detail`. Helper functions should be documented with C-Style comments, however it is not necessary to provide
Doxygen documentation.
- - **DO NOT USE C++ EXCEPTIONS** they will not be supported because they are shit. No, I won't elaborate.[[1]](#f1)
+ - **DO NOT USE C++ EXCEPTIONS** they will not be supported because they are shit.[[1]](#f1)
* Most behaviours should be type independent. Specifically interactions with the core systems of the engine.
-[1] Okay, I will elaborate. If we were to use the exception paradigm for all erroneous behaviour, we couldn't
- guarantee that the state will not be corrupted when an exception is thrown. The behaviour afterward is
- undefined because of this, and we also don't really know when, how, or where that exception will be handled.
- The assertion paradigm is better at handling this because you are defining erroneous behaviour in the code and how
- it is handled. In a debug build we can immediately halt the program, we don't care about the state afterward, only
- beforehand. Now for a release build, this is first and foremost a game engine, so we want to crash as gracefully as
- possible, prevent data loss, and get some debug information for it. fennec defines its own `assert` macro to
- be used, defining a hook in the private version of the function. This hook is used to clean up any state information
- within the engine and may be used to send immediate events to listeners so that outside functionality may decide
- how to handle the impending crash. In Debug Mode there is nothing that can be done to stop the crash, as soon
- as the branch finishes, `abort()` will be called.
+[1] If we were to use the exception paradigm for all erroneous behaviour, we couldn't
+ guarantee that the state will not be corrupted when an exception is thrown. The behaviour afterward is
+ undefined because of this, and we also don't really know when, how, or where that exception will be handled.
+ The assertion paradigm is better at handling this because you are defining erroneous behaviour in the code and how
+ it is handled. In a debug build we can immediately halt the program, we don't care about the state afterward, only
+ beforehand. Now for a release build, this is first and foremost a game engine, so we want to crash as gracefully as
+ possible, prevent data loss, and get some debug information for it. fennec defines its own `assert` macro to
+ be used, defining a hook in the private version of the function. This hook is used to clean up any state information
+ within the engine and may be used to send immediate events to listeners so that outside functionality may decide
+ how to handle the impending crash. In Debug Mode there is nothing that can be done to stop the crash, as soon
+ as the branch finishes, `abort()` will be called.
diff --git a/include/fennec/fproc/io/detail/__stdio.h b/include/fennec/fproc/filesystem/detail/__stdio.h
similarity index 100%
rename from include/fennec/fproc/io/detail/__stdio.h
rename to include/fennec/fproc/filesystem/detail/__stdio.h
diff --git a/include/fennec/fproc/io/file.h b/include/fennec/fproc/filesystem/file.h
similarity index 87%
rename from include/fennec/fproc/io/file.h
rename to include/fennec/fproc/filesystem/file.h
index 43e25a3..3b349c7 100644
--- a/include/fennec/fproc/io/file.h
+++ b/include/fennec/fproc/filesystem/file.h
@@ -19,7 +19,7 @@
#ifndef FENNEC_FPROC_IO_FILE_H
#define FENNEC_FPROC_IO_FILE_H
-#include
+#include
#include
#include
@@ -127,7 +127,7 @@ public:
///
/// \returns the path the stream
- const string& path() const {
+ const path& get_path() const {
return _path;
}
@@ -151,14 +151,21 @@ public:
/// \param path the path to the file
/// \param mode the mode flags to open the file with
/// \returns false on success, true on error
- bool open(const cstring& path, uint8_t mode);
+ bool open(const cstring& p, uint8_t mode);
///
/// \brief open a file
/// \param path the path to the file
/// \param mode the mode flags to open the file with
/// \returns false on success, true on error
- bool open(const string& path, uint8_t mode);
+ bool open(const string& p, uint8_t mode);
+
+ ///
+ /// \brief open a file
+ /// \param path the path to the file
+ /// \param mode the mode flags to open the file with
+ /// \returns false on success, true on error
+ bool open(const path& p, uint8_t mode);
///
/// \brief close a stream
@@ -188,7 +195,7 @@ public:
/// copies the contents of this file to the new stream,
/// reopen the new stream with the flags of this file and binds to it,
/// closes the old file.
- bool rename(const cstring& path);
+ bool rename(const cstring& p);
///
/// \brief rebinds the stream, copying contents to path, and erasing the old file
@@ -200,19 +207,37 @@ public:
/// copies the contents of this file to the new stream,
/// reopen the new stream with the flags of this file and binds to it,
/// closes the old file.
- bool rename(const string& path);
+ bool rename(const string& p);
+
+ ///
+ /// \brief rebinds the stream, copying contents to path, and erasing the old file
+ /// \param path the new path
+ /// \returns false on success, true on error
+ ///
+ /// \details attempts to open a write-only stream at path,
+ /// attempts to reopen this file as read-only,
+ /// copies the contents of this file to the new stream,
+ /// reopen the new stream with the flags of this file and binds to it,
+ /// closes the old file.
+ bool rename(const path& p);
///
/// \brief copies the contents of this file to path.
/// \param path the path to copy to
/// \returns a file at the new path with the copied contents
- file copy(const cstring& path);
+ file copy(const cstring& p);
///
/// \brief copies the contents of this file to path.
/// \param path the path to copy to
/// \returns a file at the new path with the copied contents
- file copy(const string& path);
+ file copy(const string& p);
+
+ ///
+ /// \brief copies the contents of this file to path.
+ /// \param path the path to copy to
+ /// \returns a file at the new path with the copied contents
+ file copy(const path& p);
// File Positioning ====================================================================================================
@@ -269,7 +294,7 @@ public:
private:
FILE* _handle;
- string _path;
+ path _path;
uint8_t _mode;
char* _error;
};
diff --git a/include/fennec/fproc/filesystem/path.h b/include/fennec/fproc/filesystem/path.h
new file mode 100644
index 0000000..7528a24
--- /dev/null
+++ b/include/fennec/fproc/filesystem/path.h
@@ -0,0 +1,228 @@
+// =====================================================================================================================
+// fennec, a free and open source game engine
+// Copyright © 2025 Medusa Slockbower
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+// =====================================================================================================================
+
+#ifndef FENNEC_FPROC_IO_PATH_H
+#define FENNEC_FPROC_IO_PATH_H
+
+#include
+
+namespace fennec
+{
+
+///
+/// \brief struct for handling file paths
+struct path
+{
+public:
+// Static Functions ====================================================================================================
+
+ /// \brief Get the current working directory
+ /// \returns a path containing the absolute path to the working directory
+ static path current();
+
+ /// \brief Set the current working directory
+ /// \param path the path to the new working directory
+ /// \returns a path containing the absolute path to the working directory
+ static path current(const path& path);
+
+
+// Constructors ========================================================================================================
+
+ ///
+ /// \brief Default Constructor, returns the root of the current working directory
+ path() : _str("/") { }
+
+ ///
+ /// \brief C-String Conversion Constructor
+ /// \param str the cstring to convert
+ path(const cstring& str) : _str(str) {
+ while (not _str.empty() && _str[_str.size() - 1] == '/') {
+ _str = _str.substring(0, str.size() - 1);
+ }
+ }
+
+ ///
+ /// \brief String Conversion Constructor
+ /// \param str the string to convert
+ path(const string& str) : _str(str) {
+ while (_str[_str.size() - 1] == '/') {
+ _str = _str.substring(0, str.size() - 1);
+ }
+ }
+
+ ///
+ /// \brief Path Copy Constructor
+ /// \param p the path to copy
+ path(const path& p) : _str(p._str) { }
+
+ ///
+ /// \brief Path Move Constructor
+ /// \param p the path to take ownership of
+ path(path&& p) noexcept : _str(move(p._str)) { }
+
+
+// Assignment Operators ================================================================================================
+
+ ///
+ /// \brief C-String Assignment Operator
+ /// \param p the cstring to assign
+ /// \returns a reference to `this` after assigning `p`
+ path& operator=(const cstring& p) {
+ _str = p;
+ return *this;
+ }
+
+ ///
+ /// \brief String Assignment Operator
+ /// \param p the cstring to assign
+ /// \returns a reference to `this` after assigning `p`
+ path& operator=(const string& p) {
+ _str = p;
+ return *this;
+ }
+
+ ///
+ /// \brief Path Copy Assignment Operator
+ /// \param p the path to copy
+ /// \returns a reference to `this` after copying `p`
+ path& operator=(const path& p) {
+ _str = p._str;
+ return *this;
+ }
+
+ ///
+ /// \brief Path Move Assignment Operator
+ /// \param p the path to take ownership of
+ /// \returns a reference to `this` after taking ownership of `p`
+ path& operator=(path&& p) noexcept {
+ _str = move(p._str);
+ return *this;
+ }
+
+
+// Append Operators ====================================================================================================
+
+ ///
+ /// \brief
+ /// \param str
+ /// \return
+ path operator/(const cstring& str) const {
+ return path(_str + '/' + str);
+ }
+
+ path operator/(const string& str) const {
+ return path(_str + '/' + str);
+ }
+
+ path operator/(const path& p) const {
+ return path(_str + '/' + p._str);
+ }
+
+ bool operator==(const path& p) const {
+ return _str == p._str;
+ }
+
+ const string& str() const { return _str; }
+
+ bool empty() {
+ size_t size = _str.size();
+#ifdef _WIN32
+ return (_str[1] == ':' && size == 3) || size == 0;
+#else
+ return (_str[0] == '/' && size == 1) || size == 0;
+#endif
+ }
+
+ path parent() const {
+#ifdef _WIN32
+ size_t start = _str.size() - 1;
+ start = _str[start] == '/' || _str[start] == '\\' ? start - 1 : start;
+
+ size_t r = _str.rfind('/', start);
+ size_t l = _str.rfind('\\', start);
+ if (r == _str.size()) {
+ start = l;
+ }
+ else if (l == _str.size()) {
+ start = r;
+ }
+ else {
+ start = max(r, l);
+ }
+ return _str.substring(0, start);
+#else
+ size_t start = _str.size() - 1;
+ start = _str[start] == '/' ? start - 1 : start;
+ return _str.substring(0, _str.rfind('/', start));
+#endif
+ }
+
+ path absolute() const {
+ path parse = *this;
+ path working; working._str.resize(0);
+
+// Check if this is a rooted path;
+#ifdef _WIN32
+ if (_str[1] != ':') {
+#else
+ if (_str[0] != '/') {
+#endif
+ working = current();
+ }
+
+ while (not parse.empty()) {
+ // Handle dots
+ while (parse._str[0] == '.') {
+ // Check for ".."
+ if (parse._str[1] == '.') {
+ // ".."
+ if (parse._str.size() == 2) {
+ parse = path();
+ working = working.parent();
+ }
+ // "../"
+ else if (parse._str[2] == '/') {
+ working = working.parent();
+ parse._str = parse._str.substring(3);
+ }
+ }
+ // "./"
+ else if (parse._str[1] == '/') {
+ parse._str = parse._str.substring(2);
+ }
+ }
+
+ if (parse.empty()) break;
+
+ // Push the path
+ size_t loc = parse._str.find('/');
+ working._str += '/';
+ working._str += parse._str.substring(0, loc);
+ parse._str = parse._str.substring(loc + 1);
+ }
+
+ return working;
+ }
+
+private:
+ string _str;
+};
+
+}
+
+#endif // FENNEC_FPROC_IO_PATH_H
diff --git a/include/fennec/fproc/strings/string.h b/include/fennec/fproc/strings/string.h
index ed538e7..171bc66 100644
--- a/include/fennec/fproc/strings/string.h
+++ b/include/fennec/fproc/strings/string.h
@@ -330,7 +330,7 @@ public:
/// \brief Copy Assignment Operator
/// \param str the string to copy
/// \returns a reference to `this`
- constexpr string& operator=(const cstring& str) {
+ constexpr _string& operator=(const cstring& str) {
resize(str.size());
fennec::memcpy(_str.data(), str, str.size());
_str[str.size()] = '\0';
@@ -341,7 +341,7 @@ public:
/// \brief Copy Assignment Operator
/// \param str the string to copy
/// \returns a reference to `this`
- constexpr string& operator=(const string& str) {
+ constexpr _string& operator=(const string& str) {
resize(str.size());
fennec::memcpy(_str.data(), str, str.size());
_str[str.size()] = '\0';
@@ -352,64 +352,75 @@ public:
/// \brief Move Assignment Operator
/// \param str the string to move
/// \returns a reference to `this`
- constexpr string& operator=(string&& str) noexcept {
- _str = fennec::move(str._str);
+ constexpr _string& operator=(string&& str) noexcept {
+ _str = move(str._str);
return *this;
}
+ ///
+ /// \brief Replace all instances of `x` with `y`
+ /// \param x the character to search for
+ /// \param y the character to replace with
+ void replace(char x, char y) {
+ size_t i = 0;
+ while ((i = find(x, 0)) != size()) {
+ _str[i] = y;
+ }
+ }
+
///
/// \brief Retrieve a substring of a string
/// \param i the start index
/// \param n the number of characters
/// \return
- constexpr string substring(size_t i, size_t n = npos) const {
+ constexpr _string substring(size_t i, size_t n = npos) const {
if (i >= size()) {
- return string("");
+ return _string("");
}
n = min(n, size() - i);
- return string(_str.data() + i, n);
+ return _string(_str.data() + i, n);
}
///
/// \brief Returns a string with `c` appended to it
/// \param c
/// \returns
- constexpr string operator+(char c) const {
+ constexpr _string operator+(char c) const {
// Copy contents with one additional byte.
- string res(_str, _str.size());
+ _string res(_str.data(), _str.size());
res[size()] = c; // Set the last character to c
return res;
}
- constexpr string operator+(const cstring& str) const {
- string res(size() + str.size()); // Make a new string with the size of this + str
+ constexpr _string operator+(const cstring& str) const {
+ _string res(size() + str.size()); // Make a new string with the size of this + str
fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this
fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
return res;
}
- constexpr string operator+(const string& str) const {
- string res(size() + str.size()); // Make a new string with the size of this + str
+ constexpr _string operator+(const _string& str) const {
+ _string res(size() + str.size()); // Make a new string with the size of this + str
fennec::memcpy(&res[0], _str.data(), size()); // Copy the contents of this
fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
return res;
}
- constexpr string& operator+=(char c) {
+ constexpr _string& operator+=(char c) {
size_t x = size();
resize(x + 1);
_str[x] = c;
return *this;
}
- constexpr string& operator+=(const cstring& str) {
+ constexpr _string& operator+=(const cstring& str) {
size_t x = size();
resize(x + str.size());
fennec::memcpy(&_str[x], str, str.size());
return *this;
}
- constexpr string& operator+=(const string& str) {
+ constexpr _string& operator+=(const _string& str) {
size_t x = size();
resize(x + str.size());
fennec::memcpy(&_str[x], str, str.size());
diff --git a/source/fproc/io/file.cpp b/source/fproc/filesystem/file.cpp
similarity index 76%
rename from source/fproc/io/file.cpp
rename to source/fproc/filesystem/file.cpp
index b68ed8a..bb1e10a 100644
--- a/source/fproc/io/file.cpp
+++ b/source/fproc/filesystem/file.cpp
@@ -16,14 +16,15 @@
// along with this program. If not, see .
// =====================================================================================================================
-#include
#include
-#include
-#include
-
#include
+#include
-#ifdef _WIN32
+#include
+#include
+
+
+#ifdef _MSC_VER
@@ -143,7 +144,7 @@ file& file::operator=(file&& file) noexcept {
return *this;
}
-bool file::open(const cstring& filename, uint8_t mode) {
+bool file::open(const cstring& p, uint8_t mode) {
assert(_error == nullptr, "Attempted Operation on a File in an Errored State");
// Ensure validity of the mode
@@ -157,7 +158,7 @@ bool file::open(const cstring& filename, uint8_t mode) {
}
// Attempt to open the file
- _handle = fopen(filename, fmode_translate(mode));
+ _handle = fopen(p, fmode_translate(mode));
if (_handle == nullptr) {
_error = strerror(errno);
@@ -173,11 +174,12 @@ bool file::open(const cstring& filename, uint8_t mode) {
}
_mode = mode;
- _path = absolute(filename);
+ _path = p;
+ _path = _path.absolute();
return false;
}
-bool file::open(const string& filename, uint8_t mode) {
+bool file::open(const string& p, uint8_t mode) {
assert(_error == nullptr, "Attempted Operation on a File in an Errored State");
// Ensure validity of the mode
@@ -191,7 +193,7 @@ bool file::open(const string& filename, uint8_t mode) {
}
// Attempt to open the file
- _handle = fopen(filename, fmode_translate(mode));
+ _handle = fopen(p, fmode_translate(mode));
// Validate the file
if (_handle == nullptr) {
@@ -211,7 +213,47 @@ bool file::open(const string& filename, uint8_t mode) {
fwide(_handle, mode & fmode_wide ? 1 : -1);
_mode = mode;
- _path = absolute(filename);
+ _path = p;
+ _path = _path.absolute();
+
+ return false;
+}
+
+bool file::open(const path& p, uint8_t mode) {
+ assert(_error == nullptr, "Attempted Operation on a File in an Errored State");
+
+ // Ensure validity of the mode
+ if (not is_valid(mode)) {
+ return true;
+ }
+
+ // Close the file if already open
+ if (_handle) {
+ close();
+ }
+
+ // Attempt to open the file
+ _handle = fopen(p.str(), fmode_translate(mode));
+
+ // Validate the file
+ if (_handle == nullptr) {
+ _error = strerror(errno);
+ return true;
+ }
+
+ // Attempt to lock the file
+ if (flock(fileno(_handle), LOCK_EX)) {
+ _error = strerror(errno);
+ fclose(_handle);
+ _handle = nullptr;
+ return true;
+ }
+
+ // Set the orientation
+ fwide(_handle, mode & fmode_wide ? 1 : -1);
+
+ _mode = mode;
+ _path = p.absolute();
return false;
}
@@ -265,13 +307,13 @@ bool file::erase() {
}
// Close the file
- string path = move(_path);
+ path path = move(_path);
if (close()) {
return true;
}
// Erase the file
- remove(path);
+ remove(path.str());
return false;
}
@@ -286,13 +328,14 @@ bool file::rename(const cstring& str) {
}
// Validate path
- string fpath = absolute(str);
+ path fpath = str;
+ fpath = fpath.absolute();
if (_path == fpath) {
return false;
}
// Attempt to open the new file
- FILE* fnew = fopen(fpath, "wx");
+ FILE* fnew = fopen(fpath.str(), "wx");
// Check for open failure
if (fnew == nullptr) {
@@ -352,7 +395,7 @@ bool file::rename(const cstring& str) {
fclose(_handle);
// Reopen the new file
- _handle = freopen(&fpath[0], fmode_translate(_mode), fnew);
+ _handle = freopen(fpath.str(), fmode_translate(_mode), fnew);
// Check for open failure
if (_handle == nullptr) {
@@ -361,7 +404,7 @@ bool file::rename(const cstring& str) {
}
// Erase the old file
- remove(_path);
+ remove(_path.str());
// Set the new path
_path = fpath;
@@ -379,13 +422,14 @@ bool file::rename(const string& str) {
}
// Validate path
- string fpath = absolute(str);
+ path fpath = str;
+ fpath = fpath.absolute();
if (_path == fpath) {
return false;
}
// Attempt to open the new file
- FILE* fnew = fopen(fpath, "wx");
+ FILE* fnew = fopen(fpath.str(), "wx");
// Check for open failure
if (fnew == nullptr) {
@@ -444,7 +488,96 @@ bool file::rename(const string& str) {
fclose(_handle);
// Reopen the new file
- _handle = freopen(&_path[0], fmode_translate(_mode), fnew);
+ _handle = freopen(_path.str(), fmode_translate(_mode), fnew);
+
+ // Check for open failure
+ if (_handle == nullptr) {
+ _error = strerror(errno);
+ return true;
+ }
+
+ // Set the new path
+ _path = fpath;
+
+ return false;
+}
+
+bool file::rename(const path& p) {
+ static const size_t page_size = pagesize();
+ assert(_error == nullptr, "Attempted an Operation on a File in an Errored State");
+
+ // Check if there is a file
+ if (_handle == nullptr) {
+ return false;
+ }
+
+ // Validate path
+ path fpath = p.absolute();
+ if (_path == fpath) {
+ return false;
+ }
+
+ // Attempt to open the new file
+ FILE* fnew = fopen(fpath.str(), "wx");
+
+ // Check for open failure
+ if (fnew == nullptr) {
+ _error = strerror(errno);
+ return true;
+ }
+
+ // Reopen this file as read
+ _handle = freopen(nullptr, "r", _handle);
+
+ // Check if it failed to reopen
+ if (_handle == nullptr) {
+ _error = strerror(errno);
+ return true;
+ }
+
+ // Initialize buffer
+ void* buffer = operator new(page_size, static_cast(page_size));
+
+ // Copy contents
+ size_t read = 0;
+ while ((read = fread(buffer, 1, page_size, _handle)) == page_size) {
+ if (fwrite(buffer, 1, page_size, fnew) != page_size) {
+ break;
+ }
+ }
+
+ // Handle eof
+ if (feof(_handle)) {
+ fwrite(buffer, 1, read, fnew);
+ }
+
+ // Check new file for errors
+ if (ferror(fnew)) {
+ _error = strerror(errno);
+ fclose(fnew);
+ _mode = fmode_read;
+ return true;
+ }
+
+ // Check the original file for errors
+ if (ferror(_handle)) {
+ _error = strerror(errno);
+ fclose(fnew);
+ fclose(_handle);
+ _handle = nullptr;
+ _mode = 0;
+ _path = "";
+ return true;
+ }
+
+ // Cleanup the buffer
+ operator delete(buffer, page_size, static_cast(page_size));
+
+ // Close old file
+ fclose(_handle);
+
+ // Reopen the new file
+ _handle = freopen(_path.str(), fmode_translate(_mode), fnew);
// Check for open failure
if (_handle == nullptr) {
@@ -468,13 +601,14 @@ file file::copy(const cstring& str) {
}
// Validate path
- string fpath = absolute(str);
+ path fpath = str;
+ fpath = fpath.absolute();
if (_path == fpath) {
return file();
}
// Attempt to open the new file
- FILE* fnew = fopen(fpath, "wx");
+ FILE* fnew = fopen(fpath.str(), "wx");
// Check for open failure
if (fnew == nullptr) {
@@ -557,13 +691,105 @@ file file::copy(const string& str) {
}
// Validate path
- string fpath = absolute(str);
+ path fpath = str;
+ fpath = fpath.absolute();
if (_path == fpath) {
return file();
}
// Attempt to open the new file
- FILE* fnew = fopen(fpath, "wx");
+ FILE* fnew = fopen(fpath.str(), "wx");
+
+ // Check for open failure
+ if (fnew == nullptr) {
+ _error = strerror(errno);
+ return file();
+ }
+
+ // Reopen this file as read
+ _handle = freopen(nullptr, "r", _handle);
+
+ // Check if it failed to reopen
+ if (_handle == nullptr) {
+ _error = strerror(errno);
+ return file();
+ }
+
+ // Initialize buffer
+ void* buffer = operator new(page_size, static_cast(page_size));
+
+ // Copy contents
+ size_t read = 0;
+ while ((read = fread(buffer, 1, page_size, _handle)) == page_size) {
+ if (fwrite(buffer, 1, page_size, fnew) != page_size) {
+ break;
+ }
+ }
+
+ // Handle eof
+ if (feof(_handle)) {
+ fwrite(buffer, 1, read, fnew);
+ }
+
+ // Check new file for errors
+ if (ferror(fnew)) {
+ operator delete(buffer, page_size, static_cast(page_size));
+ _error = strerror(errno);
+ fclose(fnew);
+ _mode = fmode_read;
+ return file();
+ }
+
+ // Check the original file for errors
+ if (ferror(_handle)) {
+ operator delete(buffer, page_size, static_cast(page_size));
+ _error = strerror(errno);
+ fclose(fnew);
+ fclose(_handle);
+ _handle = nullptr;
+ _mode = 0;
+ _path = "";
+ return file();
+ }
+
+ // Cleanup the buffer
+ operator delete(buffer, page_size, static_cast(page_size));
+
+ // Reopen the new file
+ _handle = freopen(nullptr, fmode_translate(_mode), _handle);
+
+ // Check for open failure
+ if (_handle == nullptr) {
+ _error = strerror(errno);
+ return file();
+ }
+
+ // Set the new path
+ file res;
+ res._handle = fnew;
+ res._path = fpath;
+ res._mode = fmode_write;
+
+ return res;
+}
+
+file file::copy(const path& p) {
+ static const size_t page_size = pagesize();
+ assert(_error == nullptr, "Attempted an Operation on a File in an Errored State");
+
+ // Check if there is a file
+ if (_handle == nullptr) {
+ return file();
+ }
+
+ // Validate path
+ path fpath = p.absolute();
+ if (_path == fpath) {
+ return file();
+ }
+
+ // Attempt to open the new file
+ FILE* fnew = fopen(fpath.str(), "wx");
// Check for open failure
if (fnew == nullptr) {
diff --git a/include/fennec/fproc/io/common.h b/source/fproc/filesystem/path.cpp
similarity index 65%
rename from include/fennec/fproc/io/common.h
rename to source/fproc/filesystem/path.cpp
index d4fe02b..844121c 100644
--- a/include/fennec/fproc/io/common.h
+++ b/source/fproc/filesystem/path.cpp
@@ -16,18 +16,42 @@
// along with this program. If not, see .
// =====================================================================================================================
-#ifndef FENNEC_FPROC_IO_COMMON_H
-#define FENNEC_FPROC_IO_COMMON_H
-#include
+#include
namespace fennec
{
-string getcwd();
-string absolute(const cstring& path);
-string absolute(const string& path);
+#ifdef _MSC_VER
+path path::current() {
+ char cstr[MAX_PATH];
+ if (GetCurrentDirectory(sizeof(str), str) == 0) {
+ return string("");
+ }
+ return path(cstr);
}
-#endif // FENNEC_FPROC_IO_COMMON_H
+#else
+
+#include
+#include
+
+path path::current() {
+ char cstr[PATH_MAX];
+ if (getcwd(cstr, sizeof(cstr)) == nullptr) {
+ return string("");
+ }
+ return path(cstring(cstr, strlen(cstr) + 1));
+}
+
+path path::current(const path& path) {
+ if (chdir(path._str)) {
+ return fennec::path("");
+ }
+ return current();
+}
+
+#endif
+
+}
\ No newline at end of file
diff --git a/source/fproc/io/common.cpp b/source/fproc/io/common.cpp
deleted file mode 100644
index ccf99ef..0000000
--- a/source/fproc/io/common.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-// =====================================================================================================================
-// fennec, a free and open source game engine
-// Copyright © 2025 Medusa Slockbower
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-// =====================================================================================================================
-
-#include
-
-#ifdef _WIN32
-
-namespace fennec
-{
-
-string getcwd() {
- char cstr[MAX_PATH];
- if (GetCurrentDirectory(sizeof(str), str) == 0) {
- return string("");
- }
- string result(cstr);
- return result;
-}
-
-}
-
-#else
-
-#include
-#include
-
-namespace fennec
-{
-
-string getcwd() {
- char cstr[PATH_MAX];
- if (::getcwd(cstr, sizeof(cstr)) == NULL) {
- return string("");
- }
- return string(cstr, strlen(cstr));
-}
-
-string absolute(const cstring& path) {
-
- // first determine if this is already an absolute path
-#ifdef _WIN32
- if (path[1] == ':') {
- return path;
- }
-#else
- if (path[0] == '/') {
- return path;
- }
-#endif
-
- string parse = path;
- string res = getcwd();
-
- size_t len = res.length();
-#ifdef _WIN32
- if (res[len - 1] == '/' || res[len - 1] == '\\') {
-#else
- if (res[len - 1] == '/') {
-#endif
- res = res.substring(0, len);
- }
-
- while (parse.size() > 1) {
-
- // Parse for relative symbols
- while (parse[0] == '.') {
-
-#ifdef _WIN32
- // up
- if (parse[1] == '.' && (parse[2] == '/' || parse[2] == '\\')) {
- size_t r = res.rfind('/');
- size_t l = res.rfind('\\');
- size_t size = res.size();
- if (r == size) {
- size = l;
- }
- else if (l == size) {
- size = r;
- }
- else {
- size = max(r, l);
- }
- size = max(size, 1);
- res = res.substring(0, size);
- }
- // rel
- else if (parse[1] == '/' || parse[1] == '\\') {
- parse = parse.substring(2);
- }
-#else
- // up
- if (parse[1] == '.' && parse[2] == '/') {
- size_t r = res.rfind('/');
- res = res.substring(0, max(res.rfind('/'), static_cast(1)));
- parse = parse.substring(2);
- }
- // rel
- else if (parse[1] == '/') {
- parse = parse.substring(1);
- }
-#endif
- else {
- break;
- }
- }
-
- // get the pos of the first directory separator
-#ifdef _WIN32
- size_t pos = min(parse.find('\\', 1), parse.find('/', 1));
-#else
- size_t pos = parse.find('/', 1);
- size_t off = 0;
-#endif
- if (pos != parse.size()) {
- if (parse[pos + 1] == '.') {
- if (parse[pos + 2] == '/') {
- off = 1;
- }
- else if (parse[pos + 2] == '.' && parse[pos + 3] == '/') {
- off = 1;
- }
- }
- else if (parse[pos + 1] == '/') {
- off = 1;
- }
- }
-
- // add this part
- string sub = parse.substring(0, pos);
- parse = parse.substring(pos + off);
- res += sub;
- }
-
- return res;
-}
-
-string absolute(const string& path) {
-
- // first determine if this is already an absolute path
-#ifdef _WIN32
- if (path[1] == ':') {
- return path;
- }
-#else
- if (path[0] == '/') {
- return path;
- }
-#endif
-
- string parse = path;
- string res = getcwd();
-
- size_t len = res.length();
-#ifdef _WIN32
- if (res[len - 1] == '/' || res[len - 1] == '\\') {
-#else
- if (res[len - 1] == '/') {
-#endif
- res = res.substring(0, len);
- }
-
- while (parse.size() > 1) {
-
- // Parse for relative symbols
- while (parse[0] == '.') {
-
-#ifdef _WIN32
- // up
- if (parse[1] == '.' && (parse[2] == '/' || parse[2] == '\\')) {
- size_t r = res.rfind('/');
- size_t l = res.rfind('\\');
- size_t size = res.size();
- if (r == size) {
- size = l;
- }
- else if (l == size) {
- size = r;
- }
- else {
- size = max(r, l);
- }
- size = max(size, 1);
- res = res.substring(0, size);
- }
- // rel
- else if (parse[1] == '/' || parse[1] == '\\') {
- parse = parse.substring(2);
- }
-#else
- // up
- if (parse[1] == '.' && parse[2] == '/') {
- size_t r = res.rfind('/');
- res = res.substring(0, max(res.rfind('/'), static_cast(1)));
- parse = parse.substring(2);
- }
- // rel
- else if (parse[1] == '/') {
- parse = parse.substring(1);
- }
-#endif
- else {
- break;
- }
- }
-
- // get the pos of the first directory separator
-#ifdef _WIN32
- size_t pos = min(parse.find('\\', 1), parse.find('/', 1));
- size_t off = 0;
- if (pos != parse.size()) {
- if (parse[pos + 1] == '.') {
- if (parse[pos + 2] == '/' || parse[pos + 2] == '\\') {
- off = 1;
- }
- else if (parse[pos + 2] == '.' && (parse[pos + 3] == '/' || parse[pos + 3] == '\\')) {
- off = 1;
- }
- }
- else if (parse[pos + 1] == '/' || parse[pos + 1] == '\\') {
- off = 1;
- }
- }
-#else
- size_t pos = parse.find('/', 1);
- size_t off = 0;
- if (pos != parse.size()) {
- if (parse[pos + 1] == '.') {
- if (parse[pos + 2] == '/') {
- off = 1;
- }
- else if (parse[pos + 2] == '.' && parse[pos + 3] == '/') {
- off = 1;
- }
- }
- else if (parse[pos + 1] == '/') {
- off = 1;
- }
- }
-#endif
-
- // add this part
- string sub = parse.substring(0, pos);
- parse = parse.substring(pos + off);
- res += sub;
- }
-
- return res;
-}
-
-#endif
-
-}
diff --git a/source/memory/new.cpp b/source/memory/new.cpp
index 4e4dcfd..b03b997 100644
--- a/source/memory/new.cpp
+++ b/source/memory/new.cpp
@@ -29,7 +29,7 @@
#else
// Windows does not define ISO C aligned allocation functions
-#ifdef _WIN32
+#ifdef _MSC_VER
void operator delete (void* ptr) noexcept { _aligned_free(ptr); }
void operator delete[](void* ptr) noexcept { _aligned_free(ptr); }
void operator delete (void* ptr, fennec::align_t) noexcept { ::_aligned_free(ptr); }
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1702d3d..4ccf89d 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -24,6 +24,7 @@ add_executable(fennec-test main.cpp
tests/fproc/strings/test_cstring.h
tests/test_fproc.h
tests/fproc/test_io.h
+ printing.h
)
target_compile_definitions(fennec-test PUBLIC FENNEC_TEST_CWD="${CMAKE_SOURCE_DIR}/bin/${FENNEC_BUILD_NAME}"
diff --git a/test/printing.h b/test/printing.h
new file mode 100644
index 0000000..b61f52a
--- /dev/null
+++ b/test/printing.h
@@ -0,0 +1,74 @@
+// =====================================================================================================================
+// fennec, a free and open source game engine
+// Copyright © 2025 Medusa Slockbower
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+// =====================================================================================================================
+
+#ifndef FENNEC_TEST_PRINTING_H
+#define FENNEC_TEST_PRINTING_H
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace fennec
+{
+
+namespace test
+{
+
+// Helper for printing vectors
+template
+inline std::ostream& operator<<(std::ostream& os, const vector& v)
+{
+ os << "< ";
+ ((os << v[IndicesV] << " "), ...);
+ os << ">";
+ return os;
+}
+
+// Helper for printing matrices
+template
+inline std::ostream& operator<<(std::ostream& os, const matrix& m) {
+ os << "[ ";
+ ((os << m[ColIndicesV] << " "), ...);
+ os << "]";
+ return os;
+}
+
+// Helper for printing strings
+inline std::ostream& operator<<(std::ostream& os, const cstring& str) {
+ return os << *str;
+}
+
+// Helper for printing strings
+inline std::ostream& operator<<(std::ostream& os, const string& str) {
+ return os << *str;
+}
+
+// Helper for printing strings
+inline std::ostream& operator<<(std::ostream& os, const path& str) {
+ return os << str.str();
+}
+
+}
+
+}
+
+#endif // FENNEC_TEST_PRINTING_H
\ No newline at end of file
diff --git a/test/test.h b/test/test.h
index d8fb8e6..063b6a4 100644
--- a/test/test.h
+++ b/test/test.h
@@ -24,37 +24,16 @@
#include
#include
-#include
-#include
-#include
#include
+#include "printing.h"
+
namespace fennec
{
namespace test
{
-// Helper for printing vectors
-template
-inline std::ostream& operator<<(std::ostream& os, const vector& v)
-{
- os << "< ";
- ((os << v[IndicesV] << " "), ...);
- os << ">";
- return os;
-}
-
-// Helper for printing matrices
-template
-inline std::ostream& operator<<(std::ostream& os, const matrix& m)
-{
- os << "[ ";
- ((os << m[ColIndicesV] << " "), ...);
- os << "]";
- return os;
-}
-
// Core test function
template
inline void __fennec_test_run(const std::string& expression, const ResultT& result, const ResultT& expected)
diff --git a/test/tests/fproc/test_io.h b/test/tests/fproc/test_io.h
index 1e7b7c0..cedd5dd 100644
--- a/test/tests/fproc/test_io.h
+++ b/test/tests/fproc/test_io.h
@@ -21,8 +21,8 @@
#include "../../test.h"
-#include
-#include
+#include
+#include
namespace fennec
{
@@ -36,9 +36,10 @@ inline void fennec_test_fproc_io() {
fennec_test_spacer(1);
- fennec_test_run(getcwd(), string(FENNEC_TEST_CWD));
- fennec_test_run(absolute("../" FENNEC_BUILD_NAME "/./test.sh"), string(FENNEC_TEST_CWD) + "/test.sh");
- fennec_test_run(absolute("./test/../test.sh"), string(FENNEC_TEST_CWD) + "/test.sh");
+ fennec_test_run(path::current(), path(FENNEC_TEST_CWD));
+ fennec_test_run(path("../" FENNEC_BUILD_NAME "/./test.sh").absolute(), path(FENNEC_TEST_CWD) / "test.sh");
+ fennec_test_run(path("./test/../test.sh").absolute(), path(FENNEC_TEST_CWD) / "test.sh");
+ fennec_test_run(path::current().parent(), path("../").absolute());
fennec_test_spacer(2);