From 89f59c75f3aea38cf44aec748e4c73f745ae0fc9 Mon Sep 17 00:00:00 2001 From: Medusa Slockbower Date: Mon, 14 Jul 2025 21:15:39 -0400 Subject: [PATCH] - Wrote and Debugged Unit Tests for fennec::file --- CMakeLists.txt | 8 +- README.md | 24 +- .../fproc/{io => filesystem}/detail/__stdio.h | 0 .../fennec/fproc/{io => filesystem}/file.h | 43 ++- include/fennec/fproc/filesystem/path.h | 228 +++++++++++++++ include/fennec/fproc/strings/string.h | 43 ++- source/fproc/{io => filesystem}/file.cpp | 274 ++++++++++++++++-- .../fproc/filesystem/path.cpp | 38 ++- source/fproc/io/common.cpp | 267 ----------------- source/memory/new.cpp | 2 +- test/CMakeLists.txt | 1 + test/printing.h | 74 +++++ test/test.h | 25 +- test/tests/fproc/test_io.h | 11 +- 14 files changed, 669 insertions(+), 369 deletions(-) rename include/fennec/fproc/{io => filesystem}/detail/__stdio.h (100%) rename include/fennec/fproc/{io => filesystem}/file.h (87%) create mode 100644 include/fennec/fproc/filesystem/path.h rename source/fproc/{io => filesystem}/file.cpp (76%) rename include/fennec/fproc/io/common.h => source/fproc/filesystem/path.cpp (65%) delete mode 100644 source/fproc/io/common.cpp create mode 100644 test/printing.h 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);