- Wrote and Debugged Unit Tests for fennec::file

This commit is contained in:
2025-07-14 21:15:39 -04:00
parent 6ae682aff6
commit 89f59c75f3
14 changed files with 669 additions and 369 deletions

View File

@@ -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

View File

@@ -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.<sup>[[1]](#f1)</sup>
- **DO NOT USE C++ EXCEPTIONS** they will not be supported because they are shit.<sup>[[1]](#f1)</sup>
* Most behaviours should be type independent. Specifically interactions with the core systems of the engine.
<br><br>
<a id="f1"></a>
<sup>[1]</sup> 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.
<sup>[1]</sup> 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.
<br>

View File

@@ -19,7 +19,7 @@
#ifndef FENNEC_FPROC_IO_FILE_H
#define FENNEC_FPROC_IO_FILE_H
#include <asm-generic/errno-base.h>
#include <fennec/fproc/filesystem/path.h>
#include <fennec/fproc/strings/cstring.h>
#include <fennec/fproc/strings/string.h>
@@ -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;
};

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_IO_PATH_H
#define FENNEC_FPROC_IO_PATH_H
#include <fennec/fproc/strings/string.h>
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

View File

@@ -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());

View File

@@ -16,14 +16,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#include <stdlib.h>
#include <errno.h>
#include <fennec/fproc/io/file.h>
#include <fennec/fproc/io/common.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <fennec/fproc/filesystem/file.h>
#include <fennec/fproc/filesystem/path.h>
#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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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<align_t>(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) {

View File

@@ -16,18 +16,42 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_IO_COMMON_H
#define FENNEC_FPROC_IO_COMMON_H
#include <fennec/fproc/strings/string.h>
#include <fennec/fproc/filesystem/path.h>
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 <unistd.h>
#include <linux/limits.h>
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
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#include <fennec/fproc/io/common.h>
#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 <unistd.h>
#include <linux/limits.h>
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<size_t>(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<size_t>(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
}

View File

@@ -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); }

View File

@@ -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}"

74
test/printing.h Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_TEST_PRINTING_H
#define FENNEC_TEST_PRINTING_H
#include <iostream>
#include <fennec/fproc/filesystem/path.h>
#include <fennec/fproc/strings/string.h>
#include <fennec/math/common.h>
#include <fennec/math/matrix.h>
#include <fennec/math/relational.h>
namespace fennec
{
namespace test
{
// Helper for printing vectors
template<typename ScalarT, size_t...IndicesV>
inline std::ostream& operator<<(std::ostream& os, const vector<ScalarT, IndicesV...>& v)
{
os << "< ";
((os << v[IndicesV] << " "), ...);
os << ">";
return os;
}
// Helper for printing matrices
template<typename ScalarT, size_t RowsV, size_t...ColIndicesV>
inline std::ostream& operator<<(std::ostream& os, const matrix<ScalarT, RowsV, ColIndicesV...>& 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

View File

@@ -24,37 +24,16 @@
#include <string>
#include <fennec/lang/limits.h>
#include <fennec/math/common.h>
#include <fennec/math/matrix.h>
#include <fennec/math/relational.h>
#include <fennec/math/vector_traits.h>
#include "printing.h"
namespace fennec
{
namespace test
{
// Helper for printing vectors
template<typename ScalarT, size_t...IndicesV>
inline std::ostream& operator<<(std::ostream& os, const vector<ScalarT, IndicesV...>& v)
{
os << "< ";
((os << v[IndicesV] << " "), ...);
os << ">";
return os;
}
// Helper for printing matrices
template<typename ScalarT, size_t RowsV, size_t...ColIndicesV>
inline std::ostream& operator<<(std::ostream& os, const matrix<ScalarT, RowsV, ColIndicesV...>& m)
{
os << "[ ";
((os << m[ColIndicesV] << " "), ...);
os << "]";
return os;
}
// Core test function
template<typename ResultT>
inline void __fennec_test_run(const std::string& expression, const ResultT& result, const ResultT& expected)

View File

@@ -21,8 +21,8 @@
#include "../../test.h"
#include <fennec/fproc/io/common.h>
#include <fennec/fproc/io/file.h>
#include <fennec/fproc/filesystem/file.h>
#include <fennec/fproc/filesystem/path.h>
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);