Start working on build system.
This commit is contained in:
parent
4b308f6c32
commit
521486be62
88 changed files with 1065 additions and 349 deletions
36
bootstrap.sh
36
bootstrap.sh
|
@ -2,19 +2,29 @@
|
|||
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
SOURCE_DIR=$SCRIPT_DIR/src
|
||||
CORE_SRC_DIR=$SOURCE_DIR/base/core
|
||||
|
||||
g++ $SOURCE_DIR/main.cpp \
|
||||
$SOURCE_DIR/base/core/base_types/Error.cpp \
|
||||
$SOURCE_DIR/base/core/base_types/Index.cpp \
|
||||
$SOURCE_DIR/base/core/data_structures/String.cpp \
|
||||
$SOURCE_DIR/base/core/file_utilities/FileSystemPath.cpp \
|
||||
$SOURCE_DIR/base/core/file_utilities/File.cpp \
|
||||
$SOURCE_DIR/base/core/file_utilities/Directory.cpp \
|
||||
$SOURCE_DIR/base/core/encoding/CharUtils.cpp \
|
||||
$CORE_SRC_DIR/base_types/Error.cpp \
|
||||
$CORE_SRC_DIR/base_types/Index.cpp \
|
||||
$SOURCE_DIR/base/compiler/BuildLibrary.cpp \
|
||||
$SOURCE_DIR/base/compiler/BuildSession.cpp \
|
||||
$CORE_SRC_DIR/data_structures/String.cpp \
|
||||
$CORE_SRC_DIR/encoding/CharUtils.cpp \
|
||||
$CORE_SRC_DIR/filesystem/FileSystemPath.cpp \
|
||||
$CORE_SRC_DIR/filesystem/File.cpp \
|
||||
$CORE_SRC_DIR/filesystem/Directory.cpp \
|
||||
$CORE_SRC_DIR/logging/ConsoleLogger.cpp \
|
||||
$CORE_SRC_DIR/logging/Logger.cpp \
|
||||
$CORE_SRC_DIR/time/Time.cpp \
|
||||
-o builder -g -fno-exceptions -fno-rtti \
|
||||
-I$SOURCE_DIR/base/core/data_structures \
|
||||
-I$SOURCE_DIR/base/core/base_types \
|
||||
-I$SOURCE_DIR/base/core/memory \
|
||||
-I$SOURCE_DIR/base/core/loggers \
|
||||
-I$SOURCE_DIR/base/core/encoding \
|
||||
-I$SOURCE_DIR/base/core/file_utilities
|
||||
-I$CORE_SRC_DIR/base_types \
|
||||
-I$SOURCE_DIR/base/compiler \
|
||||
-I$CORE_SRC_DIR/data_structures \
|
||||
-I$CORE_SRC_DIR/encoding \
|
||||
-I$CORE_SRC_DIR/filesystem \
|
||||
-I$CORE_SRC_DIR/logging \
|
||||
-I$CORE_SRC_DIR/memory \
|
||||
-I$CORE_SRC_DIR/system/process \
|
||||
-I$CORE_SRC_DIR/time
|
||||
|
||||
|
|
25
src/base/compiler/BuildLibrary.cpp
Normal file
25
src/base/compiler/BuildLibrary.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "BuildLibrary.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Directory.h"
|
||||
|
||||
BuildLibrary::BuildLibrary(const FileSystemPath& build_config)
|
||||
: m_build_config(build_config)
|
||||
{
|
||||
}
|
||||
|
||||
Status BuildLibrary::scan()
|
||||
{
|
||||
LOG_INFO("Scanning build file at: " << m_build_config);
|
||||
const auto search_dir = m_build_config.parent_path();
|
||||
const auto status = Directory::getFilesWithExtension(search_dir,
|
||||
".cpp",
|
||||
m_sources,
|
||||
true);
|
||||
return status;
|
||||
}
|
||||
|
||||
const Vector<FileSystemPath>& BuildLibrary::get_sources() const
|
||||
{
|
||||
return m_sources;
|
||||
}
|
22
src/base/compiler/BuildLibrary.h
Normal file
22
src/base/compiler/BuildLibrary.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "FileSystemPath.h"
|
||||
#include "String.h"
|
||||
|
||||
class BuildLibrary
|
||||
{
|
||||
public:
|
||||
BuildLibrary() = default;
|
||||
|
||||
BuildLibrary(const FileSystemPath& build_config);
|
||||
|
||||
Status scan();
|
||||
|
||||
const Vector<FileSystemPath>& get_sources() const;
|
||||
|
||||
private:
|
||||
FileSystemPath m_build_config;
|
||||
Vector<FileSystemPath> m_sources;
|
||||
Vector<FileSystemPath> m_includes;
|
||||
String m_name;
|
||||
};
|
73
src/base/compiler/BuildSession.cpp
Normal file
73
src/base/compiler/BuildSession.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "BuildSession.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Process.h"
|
||||
#include "Directory.h"
|
||||
|
||||
BuildSession::BuildSession(const String& source_dir,
|
||||
const String& build_dir)
|
||||
: m_source_dir(source_dir)
|
||||
{
|
||||
if (build_dir.empty())
|
||||
{
|
||||
m_build_dir = FileSystemPath::current_dir().value();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_build_dir = build_dir;
|
||||
}
|
||||
}
|
||||
|
||||
Status BuildSession::scan()
|
||||
{
|
||||
LOG_INFO("Scanning sources at:" << m_source_dir);
|
||||
Vector<FileSystemPath> toml_files;
|
||||
STATUS_CHECK(Directory::getFilesWithExtension(
|
||||
m_source_dir,
|
||||
".toml",
|
||||
toml_files,
|
||||
true), "Error looking for build files");
|
||||
|
||||
for(const auto& toml_file : toml_files)
|
||||
{
|
||||
if (toml_file.file_name() == "build")
|
||||
{
|
||||
STATUS_CHECK(add_library(toml_file),
|
||||
"Error adding library");
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
Status BuildSession::add_library(const FileSystemPath& config_path)
|
||||
{
|
||||
LOG_INFO("Adding library at: " << config_path);
|
||||
BuildLibrary lib(config_path);
|
||||
STATUS_CHECK(lib.scan(), "Error scanning library");
|
||||
m_libraries.push_back(lib);
|
||||
return {};
|
||||
}
|
||||
|
||||
Status BuildSession::build()
|
||||
{
|
||||
for(const auto& library : m_libraries)
|
||||
{
|
||||
for(const auto& source : library.get_sources())
|
||||
{
|
||||
String compiler_command = m_compiler_command + " -c ";
|
||||
compiler_command += source.str() + " ";
|
||||
LOG_INFO("Running command: " << compiler_command);
|
||||
|
||||
const auto self_name = Process::get_self_name();
|
||||
if (!self_name.ok())
|
||||
{
|
||||
return Status(self_name.error());
|
||||
}
|
||||
LOG_INFO("Self name is: " << self_name.value());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
24
src/base/compiler/BuildSession.h
Normal file
24
src/base/compiler/BuildSession.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "BuildLibrary.h"
|
||||
#include "Error.h"
|
||||
|
||||
class BuildSession
|
||||
{
|
||||
public:
|
||||
BuildSession(const String& source_dir,
|
||||
const String& build_dir = {});
|
||||
|
||||
Status scan();
|
||||
|
||||
Status build();
|
||||
|
||||
Status add_library(const FileSystemPath& config_path);
|
||||
|
||||
private:
|
||||
String m_compiler_command{"g++"};
|
||||
String m_compiler_flags{"-g -fno-exceptions -fno-rtti"};
|
||||
FileSystemPath m_source_dir;
|
||||
FileSystemPath m_build_dir;
|
||||
Vector<BuildLibrary> m_libraries;
|
||||
};
|
|
@ -1,5 +1,8 @@
|
|||
#include "Error.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
Error::Error(const String& msg)
|
||||
: m_message(msg)
|
||||
{
|
||||
|
@ -9,3 +12,32 @@ const String& Error::msg() const
|
|||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
String Error::from_errno()
|
||||
{
|
||||
return ::strerror(errno);
|
||||
}
|
||||
|
||||
Status::Status(const Error& err)
|
||||
: m_error(err),
|
||||
m_ok(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Status::on_errno(const String& prefix_msg)
|
||||
{
|
||||
String errno_msg(::strerror(errno));
|
||||
m_error = Error(prefix_msg + " | " + errno_msg);
|
||||
m_ok = false;
|
||||
}
|
||||
|
||||
const Error& Status::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
bool Status::ok() const
|
||||
{
|
||||
return m_ok;
|
||||
}
|
|
@ -2,6 +2,31 @@
|
|||
|
||||
#include "String.h"
|
||||
|
||||
#define IF_OK_AND_TRUE(PRED) \
|
||||
const auto _result = PRED; \
|
||||
if (!_result.ok()){return Status(_result.error());} \
|
||||
if (_result.value())
|
||||
|
||||
#define ERROR_IF_NOT_OK_OR_TRUE(PRED, msg) \
|
||||
{const auto _result = PRED; \
|
||||
if (!_result.ok()){return {_result.error()};} \
|
||||
if (!_result.value()){return {Error(msg)};}}
|
||||
|
||||
#define ON_ERRNO(msg) \
|
||||
{Status _status; \
|
||||
_status.on_errno("Failed to get directory content in opendir"); \
|
||||
return _status;} \
|
||||
|
||||
#define ON_ERRNO_RESULT(msg) \
|
||||
return {Error(_s(msg) + " | " + Error::from_errno())};
|
||||
|
||||
#define STATUS_CHECK(PRED, error_msg) \
|
||||
{const auto _status = PRED; \
|
||||
if (!_status.ok()){ \
|
||||
const auto message = _s(error_msg) + "|" + _status.error().msg(); \
|
||||
return Status(Error(message)); \
|
||||
}}
|
||||
|
||||
class Error
|
||||
{
|
||||
public:
|
||||
|
@ -10,6 +35,26 @@ public:
|
|||
Error() = default;
|
||||
|
||||
const String& msg() const;
|
||||
|
||||
static String from_errno();
|
||||
private:
|
||||
String m_message;
|
||||
};
|
||||
|
||||
class Status
|
||||
{
|
||||
public:
|
||||
Status() = default;
|
||||
|
||||
Status(const Error& err);
|
||||
|
||||
void on_errno(const String& prefix_msg);
|
||||
|
||||
const Error& error() const;
|
||||
|
||||
bool ok() const;
|
||||
|
||||
private:
|
||||
Error m_error;
|
||||
bool m_ok{true};
|
||||
};
|
|
@ -6,7 +6,7 @@ template<typename T>
|
|||
class Result
|
||||
{
|
||||
public:
|
||||
Result(const T& val)
|
||||
Result(const T& val = T())
|
||||
: m_value(val)
|
||||
{
|
||||
|
||||
|
@ -19,6 +19,12 @@ public:
|
|||
|
||||
}
|
||||
|
||||
void on_error(const Error& error)
|
||||
{
|
||||
m_error = error;
|
||||
m_ok = false;
|
||||
}
|
||||
|
||||
const Error& error() const
|
||||
{
|
||||
return m_error;
|
||||
|
@ -34,6 +40,11 @@ public:
|
|||
return m_value;
|
||||
}
|
||||
|
||||
T& value()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
T m_value;
|
||||
Error m_error;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "String.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
String::String()
|
||||
{
|
||||
|
@ -17,6 +18,49 @@ String::String(const char* data)
|
|||
append(data);
|
||||
}
|
||||
|
||||
String String::fmt(const char* fmt, ...)
|
||||
{
|
||||
String ret;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
char format_delim = '%';
|
||||
bool in_format = false;
|
||||
while(*fmt != '\0')
|
||||
{
|
||||
if (*fmt == format_delim)
|
||||
{
|
||||
in_format = true;
|
||||
}
|
||||
else if(in_format && *fmt == 's')
|
||||
{
|
||||
in_format = false;
|
||||
const auto s = va_arg(args, char*);
|
||||
ret.append(s);
|
||||
}
|
||||
else if(in_format && *fmt == 'd')
|
||||
{
|
||||
in_format = false;
|
||||
const auto i = va_arg(args, int);
|
||||
ret += to_string(i);
|
||||
}
|
||||
else if(in_format && *fmt == 'c')
|
||||
{
|
||||
in_format = false;
|
||||
const auto c = va_arg(args, int);
|
||||
ret +=c;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret += *fmt;
|
||||
}
|
||||
++fmt;
|
||||
}
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void String::append(const char* data)
|
||||
{
|
||||
if (data == nullptr)
|
||||
|
@ -26,9 +70,18 @@ void String::append(const char* data)
|
|||
}
|
||||
|
||||
auto loc = data;
|
||||
bool first=true;
|
||||
while(*loc != '\0')
|
||||
{
|
||||
m_data.push_back(*loc);
|
||||
if (!m_data.empty() && first)
|
||||
{
|
||||
m_data[m_data.size() - 1] = *loc;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data.push_back(*loc);
|
||||
}
|
||||
first = false;
|
||||
loc++;
|
||||
}
|
||||
m_data.push_back('\0');
|
||||
|
@ -46,7 +99,7 @@ bool String::empty() const
|
|||
|
||||
void String::append(const Vector<Byte>& data)
|
||||
{
|
||||
if (data.capacity() == 0)
|
||||
if (data.empty())
|
||||
{
|
||||
m_data.push_back('\0');
|
||||
return;
|
||||
|
@ -79,7 +132,7 @@ Pair<String, String> String::rsplit(char c) const
|
|||
slice(0, index.value(), left);
|
||||
|
||||
String right;
|
||||
slice(index.value(), size(), right);
|
||||
slice(index.value() + 1, size(), right);
|
||||
return {left, right};
|
||||
}
|
||||
return {*this, {}};
|
||||
|
@ -87,7 +140,7 @@ Pair<String, String> String::rsplit(char c) const
|
|||
|
||||
bool String::slice(std::size_t idx, String& out) const
|
||||
{
|
||||
if (idx >= m_data.size() - 1)
|
||||
if (idx >= m_data.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -102,7 +155,7 @@ bool String::slice(std::size_t idx, String& out) const
|
|||
|
||||
bool String::slice(std::size_t start, std::size_t end, String& out) const
|
||||
{
|
||||
if (end >= m_data.size() - 1)
|
||||
if (end >= m_data.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -181,6 +234,12 @@ String& String::operator<<(const char* body)
|
|||
return *this;
|
||||
}
|
||||
|
||||
String& String::operator<<(const String& body)
|
||||
{
|
||||
*this += body;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool String::operator==(const String& other) const
|
||||
{
|
||||
return m_data == other.m_data;
|
||||
|
@ -193,12 +252,7 @@ bool String::operator!=(const String& other) const
|
|||
|
||||
String& String::operator<<(size_t idx)
|
||||
{
|
||||
/*
|
||||
const auto num_digits = static_cast<unsigned>(log10(double(idx))) + 1;
|
||||
char body[num_digits+1];
|
||||
snprintf(body, num_digits+1, "%d", static_cast<int>(idx));
|
||||
append(body);
|
||||
*/
|
||||
*this += to_string(idx);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -225,6 +279,10 @@ String String::operator+(const String& str) const
|
|||
|
||||
String& String::operator+=(char c)
|
||||
{
|
||||
if (m_data.empty())
|
||||
{
|
||||
m_data.push_back('\0');
|
||||
}
|
||||
m_data.push_back('\0');
|
||||
m_data[m_data.size()-2] = c;
|
||||
return *this;
|
||||
|
|
|
@ -14,6 +14,10 @@ public:
|
|||
|
||||
String(const char* data);
|
||||
|
||||
static String fmt(const char* fmt, ...);
|
||||
|
||||
void append(const Vector<Byte>& data);
|
||||
|
||||
const Vector<char>& data() const;
|
||||
|
||||
bool empty() const;
|
||||
|
@ -36,10 +40,19 @@ public:
|
|||
|
||||
char operator[](std::size_t idx) const;
|
||||
|
||||
String& operator<<(const String& body);
|
||||
|
||||
String& operator<<(const char* body);
|
||||
|
||||
String& operator<<(size_t idx);
|
||||
|
||||
template<typename T>
|
||||
String& operator<<(const T& stringable)
|
||||
{
|
||||
*this += stringable.str();
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& operator+=(const String& str);
|
||||
|
||||
String& operator+=(char c);
|
||||
|
@ -51,9 +64,9 @@ public:
|
|||
bool operator!=(const String& other) const;
|
||||
|
||||
private:
|
||||
void append(const Vector<Byte>& data);
|
||||
|
||||
void append(const char* data);
|
||||
|
||||
Vector<char> m_data;
|
||||
};
|
||||
|
||||
using _s = String;
|
|
@ -12,7 +12,6 @@ public:
|
|||
Vector(std::size_t size)
|
||||
{
|
||||
resize(size);
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
Vector(const Vector& v)
|
||||
|
@ -56,22 +55,20 @@ public:
|
|||
{
|
||||
v.m_data[idx] = m_data[idx];
|
||||
}
|
||||
v.m_size = slice_idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool slice(std::size_t slice_start, std::size_t slice_end, Vector& v) const
|
||||
{
|
||||
if (slice_end >= m_size)
|
||||
if (slice_end > m_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
v.resize(slice_end - slice_start);
|
||||
for(std::size_t idx=slice_start; idx<slice_end;idx++)
|
||||
{
|
||||
v.m_data[idx] = m_data[idx];
|
||||
v.m_data[idx - slice_start] = m_data[idx];
|
||||
}
|
||||
v.m_size = slice_end;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -90,36 +87,46 @@ public:
|
|||
return m_size == 0;
|
||||
}
|
||||
|
||||
void clear()
|
||||
void clear(bool update_sizes=true)
|
||||
{
|
||||
if (has_allocated())
|
||||
{
|
||||
m_allocator.delete_array(m_data);
|
||||
m_data = nullptr;
|
||||
m_capacity = 0;
|
||||
m_size = 0;
|
||||
if (update_sizes)
|
||||
{
|
||||
m_capacity = 0;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void resize(std::size_t size)
|
||||
{
|
||||
resize_capacity(size);
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void resize(std::size_t size, const T& value)
|
||||
{
|
||||
resize_capacity(size);
|
||||
m_size = size;
|
||||
fill_with(value);
|
||||
}
|
||||
|
||||
void extend(const Vector& other)
|
||||
{
|
||||
resize(m_size + other.m_size);
|
||||
for(std::size_t idx=0;idx<other.m_size;idx++)
|
||||
size_t resize_delta{0};
|
||||
if (other.m_size > remaining_capacity())
|
||||
{
|
||||
resize_delta = other.m_size - remaining_capacity();
|
||||
resize_capacity(m_capacity + resize_delta);
|
||||
}
|
||||
for(size_t idx=0;idx<other.m_size;idx++)
|
||||
{
|
||||
m_data[idx + m_size] = other[idx];
|
||||
}
|
||||
m_size = m_size + other.m_size;
|
||||
m_size += other.m_size;
|
||||
}
|
||||
|
||||
T pop_back()
|
||||
|
@ -162,7 +169,6 @@ public:
|
|||
{
|
||||
m_data[idx] = v.m_data[idx];
|
||||
}
|
||||
m_size = v.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -196,6 +202,11 @@ private:
|
|||
return m_capacity > 0;
|
||||
}
|
||||
|
||||
size_t remaining_capacity() const
|
||||
{
|
||||
return m_capacity - m_size;
|
||||
}
|
||||
|
||||
void resize_capacity(std::size_t new_capacity)
|
||||
{
|
||||
if (!has_allocated())
|
||||
|
@ -206,23 +217,18 @@ private:
|
|||
else if (new_capacity != m_capacity)
|
||||
{
|
||||
auto temp = m_allocator.alloc_array(new_capacity);
|
||||
for(std::size_t idx=0; idx<new_capacity; idx++)
|
||||
auto min_capacity = new_capacity;
|
||||
if (m_capacity < min_capacity)
|
||||
{
|
||||
min_capacity = m_capacity;
|
||||
}
|
||||
for(size_t idx=0; idx<min_capacity; idx++)
|
||||
{
|
||||
temp[idx] = m_data[idx];
|
||||
}
|
||||
auto old_size = m_size;
|
||||
clear();
|
||||
clear(false);
|
||||
m_data = temp;
|
||||
m_capacity = new_capacity;
|
||||
m_size = old_size;
|
||||
if (old_size > m_capacity)
|
||||
{
|
||||
m_size = m_capacity;
|
||||
}
|
||||
}
|
||||
else if(m_size != m_capacity)
|
||||
{
|
||||
m_size = m_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
#include "Directory.h"
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
Vector<FileSystemPath> Directory::getSubdirectories(const FileSystemPath& path)
|
||||
{
|
||||
Vector<FileSystemPath> ret;
|
||||
auto dirp = ::opendir(path.as_string().raw());
|
||||
while(auto ep = ::readdir(dirp))
|
||||
{
|
||||
auto rel_path = String(ep->d_name);
|
||||
if (rel_path == "." || rel_path == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto full_path = path.join(rel_path);
|
||||
struct stat sb;
|
||||
auto rc = lstat(full_path.as_string().raw(), &sb);
|
||||
if (rc == -1)
|
||||
{
|
||||
printf("Got error");
|
||||
}
|
||||
if (S_ISDIR(sb.st_mode))
|
||||
{
|
||||
printf("Adding full path: %s\n", full_path.as_string().raw());
|
||||
ret.push_back(full_path);
|
||||
}
|
||||
}
|
||||
::closedir(dirp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector<FileSystemPath> Directory::getFiles(const FileSystemPath& path, bool recursive)
|
||||
{
|
||||
Vector<FileSystemPath> paths;
|
||||
if (path.is_directory())
|
||||
{
|
||||
for (const auto& entry : getSubdirectories(path))
|
||||
{
|
||||
if (entry.is_regular_file())
|
||||
{
|
||||
paths.push_back(entry);
|
||||
}
|
||||
else if(recursive && entry.is_directory())
|
||||
{
|
||||
const auto child_paths = getFiles(entry, recursive);
|
||||
paths.extend(child_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
Vector<FileSystemPath> Directory::getFilesWithExtension(const FileSystemPath& path, const String& extension, bool recursive)
|
||||
{
|
||||
Vector<FileSystemPath> paths;
|
||||
if (path.is_directory())
|
||||
{
|
||||
for (const auto& entry : getSubdirectories(path))
|
||||
{
|
||||
if (entry.is_regular_file() && entry.extension() == extension)
|
||||
{
|
||||
paths.push_back(entry);
|
||||
}
|
||||
else if(recursive && entry.is_directory())
|
||||
{
|
||||
const auto child_paths = getFilesWithExtension(entry, extension, recursive);
|
||||
paths.extend(child_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void Directory::create(const FileSystemPath& path, bool existsOk)
|
||||
{
|
||||
(void)existsOk;
|
||||
FileSystemPath working_path;
|
||||
if (path.is_directory())
|
||||
{
|
||||
working_path = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
working_path = path.parent_path();
|
||||
}
|
||||
|
||||
if (!working_path.exists())
|
||||
{
|
||||
//std::filesystem::create_directories(working_path);
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "Vector.h"
|
||||
#include "FileSystemPath.h"
|
||||
|
||||
class Directory
|
||||
{
|
||||
public:
|
||||
static Vector<FileSystemPath> getSubdirectories(const FileSystemPath& path);
|
||||
|
||||
static void create(const FileSystemPath& path, bool existsOK = false);
|
||||
|
||||
static Vector<FileSystemPath> getFiles(const FileSystemPath& path, bool recursive=false);
|
||||
|
||||
static Vector<FileSystemPath> getFilesWithExtension(const FileSystemPath& path, const String& extension, bool recursive=false);
|
||||
};
|
128
src/base/core/filesystem/Directory.cpp
Normal file
128
src/base/core/filesystem/Directory.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
#include "Directory.h"
|
||||
#include "ConsoleLogger.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
Status Directory::getDirectoryContents(const FileSystemPath& path, Vector<FileSystemPath>& ret)
|
||||
{
|
||||
Status status;
|
||||
errno = 0;
|
||||
auto dirp = ::opendir(path.str().raw());
|
||||
if (dirp == nullptr)
|
||||
{
|
||||
ON_ERRNO("Failed to get directory content in opendir");
|
||||
}
|
||||
|
||||
while(auto ep = ::readdir(dirp))
|
||||
{
|
||||
auto rel_path = String(ep->d_name);
|
||||
if (rel_path == "." || rel_path == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto full_path = path.join(rel_path);
|
||||
IF_OK_AND_TRUE(full_path.is_regular_file_or_directory())
|
||||
{
|
||||
ret.push_back(full_path);
|
||||
}
|
||||
}
|
||||
String errno_msg;
|
||||
if (errno != 0)
|
||||
{
|
||||
errno_msg = "Failed traversing dirp structure | ";
|
||||
errno_msg += Error::from_errno();
|
||||
}
|
||||
errno = 0;
|
||||
const auto rc = ::closedir(dirp);
|
||||
if (rc != 0)
|
||||
{
|
||||
errno_msg += "Failed to close dirp";
|
||||
errno_msg += Error::from_errno();
|
||||
}
|
||||
if (!errno_msg.empty())
|
||||
{
|
||||
return Status(errno_msg);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Status Directory::getFiles(const FileSystemPath& path,
|
||||
Vector<FileSystemPath>& ret,
|
||||
bool recursive,
|
||||
const String& extension)
|
||||
{
|
||||
const auto is_dir = path.is_directory();
|
||||
if (!is_dir.ok())
|
||||
{
|
||||
return Status(is_dir.error());
|
||||
}
|
||||
|
||||
if (!is_dir.value())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<FileSystemPath> dir_contents;
|
||||
STATUS_CHECK(getDirectoryContents(path, dir_contents),
|
||||
"Failed to get directory contents");
|
||||
|
||||
for (const auto& entry : dir_contents)
|
||||
{
|
||||
IF_OK_AND_TRUE(entry.is_regular_file())
|
||||
{
|
||||
if (!extension.empty())
|
||||
{
|
||||
if (entry.extension() == extension)
|
||||
{
|
||||
ret.push_back(entry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_INFO("Adding entry " << entry);
|
||||
ret.push_back(entry);
|
||||
}
|
||||
}
|
||||
else if(recursive)
|
||||
{
|
||||
IF_OK_AND_TRUE(entry.is_directory())
|
||||
{
|
||||
Vector<FileSystemPath> child_paths;
|
||||
STATUS_CHECK(getFiles(entry, child_paths, recursive), "Failed to get files");
|
||||
ret.extend(child_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Status Directory::getFilesWithExtension(const FileSystemPath& path,
|
||||
const String& extension,
|
||||
Vector<FileSystemPath>& ret,
|
||||
bool recursive)
|
||||
{
|
||||
return getFiles(path, ret, recursive, extension);
|
||||
}
|
||||
|
||||
Status Directory::create(const FileSystemPath& path, bool existsOk)
|
||||
{
|
||||
(void)existsOk;
|
||||
FileSystemPath working_path;
|
||||
if (path.is_directory().value())
|
||||
{
|
||||
working_path = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
working_path = path.parent_path();
|
||||
}
|
||||
|
||||
if (!working_path.exists())
|
||||
{
|
||||
//std::filesystem::create_directories(working_path);
|
||||
}
|
||||
return {};
|
||||
}
|
24
src/base/core/filesystem/Directory.h
Normal file
24
src/base/core/filesystem/Directory.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "Vector.h"
|
||||
#include "FileSystemPath.h"
|
||||
#include "Result.h"
|
||||
|
||||
class Directory
|
||||
{
|
||||
public:
|
||||
static Status getDirectoryContents(const FileSystemPath& path,
|
||||
Vector<FileSystemPath>& content);
|
||||
|
||||
static Status create(const FileSystemPath& path, bool existsOK = false);
|
||||
|
||||
static Status getFiles(const FileSystemPath& path,
|
||||
Vector<FileSystemPath>& content,
|
||||
bool recursive=false,
|
||||
const String& extension = {});
|
||||
|
||||
static Status getFilesWithExtension(const FileSystemPath& path,
|
||||
const String& extension,
|
||||
Vector<FileSystemPath>& content,
|
||||
bool recursive=false);
|
||||
};
|
|
@ -16,7 +16,7 @@
|
|||
class FileImpl
|
||||
{
|
||||
public:
|
||||
void do_open(const FileSystemPath& path, File::AccessMode accessMode)
|
||||
Status do_open(const FileSystemPath& path, File::AccessMode accessMode)
|
||||
{
|
||||
int flags{0};
|
||||
if (accessMode == File::AccessMode::Read)
|
||||
|
@ -28,8 +28,15 @@ public:
|
|||
flags |= O_WRONLY;
|
||||
flags |= O_CREAT;
|
||||
}
|
||||
errno = 0;
|
||||
m_fd = ::open(path.str().raw(), flags);
|
||||
if (m_fd < 0)
|
||||
{
|
||||
Status ret;
|
||||
ret.on_errno("Failed to open file with");
|
||||
return ret;
|
||||
}
|
||||
|
||||
m_fd = ::open(path.as_string().raw(), flags);
|
||||
if (accessMode == File::AccessMode::Read)
|
||||
{
|
||||
m_open_for_read = true;
|
||||
|
@ -38,13 +45,22 @@ public:
|
|||
{
|
||||
m_open_for_write = true;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void do_close()
|
||||
Status do_close()
|
||||
{
|
||||
::close(m_fd);
|
||||
errno = 0;
|
||||
const auto rc = ::close(m_fd);
|
||||
if (rc < 0)
|
||||
{
|
||||
Status ret;
|
||||
ret.on_errno("Failed to close file with");
|
||||
return ret;
|
||||
}
|
||||
m_open_for_read = false;
|
||||
m_open_for_write = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool is_ok() const
|
||||
|
@ -68,35 +84,56 @@ public:
|
|||
const auto rc = ::read(m_fd, bytes.data(), bytes.capacity());
|
||||
if (rc < 0)
|
||||
{
|
||||
const auto last_err = errno;
|
||||
String msg(::strerror(last_err));
|
||||
const auto msg = _s("Error in read impl | ") + Error::from_errno();
|
||||
return Result<std::size_t>(Error(msg));
|
||||
}
|
||||
return Result<std::size_t>(rc);
|
||||
}
|
||||
|
||||
std::size_t do_write(const VecBytes& bytes)
|
||||
Result<std::size_t> do_write(const VecBytes& bytes)
|
||||
{
|
||||
return ::write(m_fd, bytes.data(), bytes.size());
|
||||
errno = 0;
|
||||
const auto rc = ::write(m_fd, bytes.data(), bytes.size());
|
||||
if (rc < 0)
|
||||
{
|
||||
const auto msg = _s("Error in write impl | ") + Error::from_errno();
|
||||
return Result<std::size_t>(Error(msg));
|
||||
}
|
||||
return Result<std::size_t>(rc);
|
||||
}
|
||||
|
||||
std::size_t do_write(const Vector<char>& bytes, int size = -1)
|
||||
Result<std::size_t> do_write(const Vector<char>& bytes, int size = -1)
|
||||
{
|
||||
errno = 0;
|
||||
int rc = 0;
|
||||
if (size > -1)
|
||||
{
|
||||
return ::write(m_fd, bytes.data(), size);
|
||||
rc = ::write(m_fd, bytes.data(), size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::write(m_fd, bytes.data(), bytes.size());
|
||||
rc = ::write(m_fd, bytes.data(), bytes.size());
|
||||
}
|
||||
if (rc < 0)
|
||||
{
|
||||
const auto msg = _s("Error in write impl | ") + Error::from_errno();
|
||||
return Result<std::size_t>(Error(msg));
|
||||
}
|
||||
return Result<std::size_t>(rc);
|
||||
}
|
||||
|
||||
void update_size()
|
||||
Status update_size()
|
||||
{
|
||||
struct stat buf;
|
||||
::fstat(m_fd, &buf);
|
||||
const auto rc = ::fstat(m_fd, &buf);
|
||||
if (rc != 0)
|
||||
{
|
||||
Status ret;
|
||||
ret.on_errno("Failed to get size with fstat");
|
||||
return ret;
|
||||
}
|
||||
m_size = buf.st_size;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool m_open_for_write{false};
|
||||
|
@ -123,41 +160,77 @@ String File::getExtension() const
|
|||
return m_path.extension();
|
||||
}
|
||||
|
||||
bool File::readBinary(VecBytes& buffer)
|
||||
Status File::readBinary(VecBytes& buffer)
|
||||
{
|
||||
auto ok = open(AccessMode::Read, true);
|
||||
if (!ok)
|
||||
auto status = open(AccessMode::Read, true);
|
||||
if (!status.ok())
|
||||
{
|
||||
return false;
|
||||
return status;
|
||||
}
|
||||
|
||||
m_impl->update_size();
|
||||
buffer.resize(m_impl->m_size);
|
||||
const auto result = m_impl->do_read(buffer);
|
||||
buffer.resize(result.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
String File::readText()
|
||||
{
|
||||
VecBytes buffer;
|
||||
auto ok = open(AccessMode::Read, true);
|
||||
if (!ok)
|
||||
status = m_impl->update_size();
|
||||
if (!status.ok())
|
||||
{
|
||||
return {};
|
||||
return status;
|
||||
}
|
||||
|
||||
m_impl->update_size();
|
||||
buffer.resize(m_impl->m_size);
|
||||
const auto result = m_impl->do_read(buffer);
|
||||
if (!result.ok())
|
||||
{
|
||||
printf("Got error: %s\n", result.error().msg());
|
||||
return {};
|
||||
return Status(result.error());
|
||||
}
|
||||
buffer.resize(result.value());
|
||||
return {};
|
||||
}
|
||||
|
||||
Status File::readText(String& ret)
|
||||
{
|
||||
auto status = open(AccessMode::Read, true);
|
||||
if (!status.ok())
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
buffer.resize(result.value());
|
||||
return String(buffer);
|
||||
VecBytes buffer;
|
||||
status = m_impl->update_size();
|
||||
if (status.ok() && m_impl->m_size > 0)
|
||||
{
|
||||
buffer.resize(m_impl->m_size);
|
||||
const auto result = m_impl->do_read(buffer);
|
||||
if (!result.ok())
|
||||
{
|
||||
return Status(result.error());
|
||||
}
|
||||
buffer.resize(result.value());
|
||||
ret.append(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.resize(1024);
|
||||
while(true)
|
||||
{
|
||||
const auto result = m_impl->do_read(buffer);
|
||||
if (!result.ok())
|
||||
{
|
||||
return Status(result.error());
|
||||
}
|
||||
if (result.value() < 1024)
|
||||
{
|
||||
if (result.value() > 0)
|
||||
{
|
||||
buffer.resize(result.value());
|
||||
ret.append(buffer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.append(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
String File::dumpBinary()
|
||||
|
@ -192,7 +265,7 @@ String File::dumpBinary()
|
|||
|
||||
Optional<Byte> File::readNextByte()
|
||||
{
|
||||
if (!open(AccessMode::Read))
|
||||
if (!open(AccessMode::Read).ok())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
@ -216,20 +289,19 @@ Optional<Byte> File::readNextByte()
|
|||
}
|
||||
}
|
||||
|
||||
bool File::open(AccessMode accessMode, bool binary)
|
||||
Status File::open(AccessMode accessMode, bool binary)
|
||||
{
|
||||
if (m_path.is_absolute() && !m_path.parent_path().exists())
|
||||
{
|
||||
Directory::create(m_path.parent_path(), true);
|
||||
}
|
||||
|
||||
m_impl->do_open(m_path, accessMode);
|
||||
return true;
|
||||
return m_impl->do_open(m_path, accessMode);
|
||||
}
|
||||
|
||||
void File::close()
|
||||
Status File::close()
|
||||
{
|
||||
m_impl->do_close();
|
||||
return m_impl->do_close();
|
||||
}
|
||||
|
||||
FileFormat::Format File::inferFormat() const
|
||||
|
@ -239,27 +311,34 @@ FileFormat::Format File::inferFormat() const
|
|||
return {};
|
||||
}
|
||||
|
||||
void File::writeText(const String& text)
|
||||
Status File::writeText(const String& text)
|
||||
{
|
||||
bool had_to_open{false};
|
||||
Status status;
|
||||
if (!m_impl->is_open_for_write())
|
||||
{
|
||||
had_to_open = true;
|
||||
m_impl->do_open(m_path, File::AccessMode::Write);
|
||||
status = m_impl->do_open(m_path, File::AccessMode::Write);
|
||||
if (!status.ok())
|
||||
{
|
||||
return status;
|
||||
}
|
||||
}
|
||||
const auto result = m_impl->do_write(text.data(), text.data().size() - 1);
|
||||
if (!result.ok())
|
||||
{
|
||||
return Status(result.error());
|
||||
}
|
||||
|
||||
m_impl->do_write(text.data(), text.data().size() - 1);
|
||||
|
||||
if (had_to_open)
|
||||
{
|
||||
m_impl->do_close();
|
||||
status = m_impl->do_close();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Vector<String> File::readLines()
|
||||
Status File::readLines(Vector<String>& lines)
|
||||
{
|
||||
Vector<String> content;
|
||||
|
||||
/*
|
||||
if (!pathExists())
|
||||
{
|
||||
|
@ -279,10 +358,10 @@ Vector<String> File::readLines()
|
|||
|
||||
close();
|
||||
*/
|
||||
return content;
|
||||
return {};
|
||||
}
|
||||
|
||||
String File::read()
|
||||
Status File::read(String& ret)
|
||||
{
|
||||
/*
|
||||
if (!pathExists())
|
||||
|
@ -300,11 +379,10 @@ String File::read()
|
|||
|
||||
close();
|
||||
*/
|
||||
String buffer;
|
||||
return buffer;
|
||||
return {};
|
||||
}
|
||||
|
||||
bool File::pathExists() const
|
||||
bool File::exists() const
|
||||
{
|
||||
return m_path.exists();
|
||||
}
|
|
@ -23,7 +23,7 @@ public:
|
|||
|
||||
~File();
|
||||
|
||||
void close();
|
||||
Status close();
|
||||
|
||||
String dumpBinary();
|
||||
|
||||
|
@ -31,21 +31,21 @@ public:
|
|||
|
||||
FileFormat::Format inferFormat() const;
|
||||
|
||||
String readText();
|
||||
Status readText(String& buffer);
|
||||
|
||||
Vector<String> readLines();
|
||||
Status readLines(Vector<String>& lines);
|
||||
|
||||
String read();
|
||||
Status read(String& buffer);
|
||||
|
||||
bool pathExists() const;
|
||||
bool exists() const;
|
||||
|
||||
bool open(AccessMode mode, bool binary = false);
|
||||
Status open(AccessMode mode, bool binary = false);
|
||||
|
||||
bool readBinary(VecBytes& bytes);
|
||||
Status readBinary(VecBytes& bytes);
|
||||
|
||||
Optional<Byte> readNextByte();
|
||||
|
||||
void writeText(const String& text);
|
||||
Status writeText(const String& text);
|
||||
|
||||
private:
|
||||
Ptr<FileImpl> m_impl;
|
|
@ -4,26 +4,25 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
FileSystemPath::FileSystemPath()
|
||||
{
|
||||
}
|
||||
|
||||
FileSystemPath::FileSystemPath(const String& path)
|
||||
: m_path(path)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const String& FileSystemPath::as_string() const
|
||||
const String& FileSystemPath::str() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
Result<bool> FileSystemPath::is_empty() const
|
||||
{
|
||||
if (!is_regular_file())
|
||||
{
|
||||
return {Error("Requested empty check but target is not regular file.")};
|
||||
}
|
||||
|
||||
ERROR_IF_NOT_OK_OR_TRUE(is_regular_file(),
|
||||
"Requested empty check but target is not regular file.");
|
||||
const auto size_result = get_size();
|
||||
if (!size_result.ok())
|
||||
{
|
||||
|
@ -34,21 +33,24 @@ Result<bool> FileSystemPath::is_empty() const
|
|||
|
||||
Result<size_t> FileSystemPath::get_size() const
|
||||
{
|
||||
if (!is_regular_file())
|
||||
{
|
||||
return {Error("Requested size but target is not regular file.")};
|
||||
}
|
||||
ERROR_IF_NOT_OK_OR_TRUE(is_regular_file(),
|
||||
"Requested size check but target is not regular file.");
|
||||
|
||||
struct stat buf;
|
||||
::stat(m_path.raw(), &buf);
|
||||
errno = 0;
|
||||
const auto rc = ::stat(m_path.raw(), &buf);
|
||||
if (rc != 0)
|
||||
{
|
||||
ON_ERRNO_RESULT("Failed to stat file");
|
||||
}
|
||||
return buf.st_size;
|
||||
}
|
||||
|
||||
FileSystemPath FileSystemPath::current_dir()
|
||||
Result<FileSystemPath> FileSystemPath::current_dir()
|
||||
{
|
||||
errno = 0;
|
||||
const auto path_max = ::pathconf(".", _PC_PATH_MAX);
|
||||
std::size_t size{0};
|
||||
size_t size{0};
|
||||
if (path_max == -1)
|
||||
{
|
||||
size = 1024;
|
||||
|
@ -63,24 +65,48 @@ FileSystemPath FileSystemPath::current_dir()
|
|||
}
|
||||
|
||||
Vector<char> buffer(path_max);
|
||||
errno = 0;
|
||||
const auto ret = ::getcwd(buffer.data(), path_max);
|
||||
if (ret == nullptr)
|
||||
{
|
||||
ON_ERRNO_RESULT("Failed to get cwd");
|
||||
}
|
||||
return FileSystemPath(String(buffer.data()));
|
||||
}
|
||||
|
||||
bool FileSystemPath::is_regular_file() const
|
||||
Result<bool> FileSystemPath::is_regular_file() const
|
||||
{
|
||||
struct stat path_stat;
|
||||
::stat(m_path.raw(), &path_stat);
|
||||
const auto rc = ::stat(m_path.raw(), &path_stat);
|
||||
if (rc != 0)
|
||||
{
|
||||
ON_ERRNO_RESULT("Failed to stat file");
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode);
|
||||
}
|
||||
|
||||
bool FileSystemPath::is_directory() const
|
||||
Result<bool> FileSystemPath::is_directory() const
|
||||
{
|
||||
struct stat path_stat;
|
||||
::stat(m_path.raw(), &path_stat);
|
||||
const auto rc = ::stat(m_path.raw(), &path_stat);
|
||||
if (rc != 0)
|
||||
{
|
||||
ON_ERRNO_RESULT("Failed to stat file");
|
||||
}
|
||||
return S_ISDIR(path_stat.st_mode);
|
||||
}
|
||||
|
||||
Result<bool> FileSystemPath::is_regular_file_or_directory() const
|
||||
{
|
||||
struct stat path_stat;
|
||||
const auto rc = ::stat(m_path.raw(), &path_stat);
|
||||
if (rc != 0)
|
||||
{
|
||||
ON_ERRNO_RESULT("Failed to stat file");
|
||||
}
|
||||
return S_ISREG(path_stat.st_mode) || S_ISDIR(path_stat.st_mode);
|
||||
}
|
||||
|
||||
bool FileSystemPath::is_absolute() const
|
||||
{
|
||||
return false;
|
||||
|
@ -100,6 +126,21 @@ String FileSystemPath::extension() const
|
|||
return result;
|
||||
}
|
||||
|
||||
String FileSystemPath::file_name() const
|
||||
{
|
||||
String name_and_ext;;
|
||||
const auto split = m_path.rsplit('/');
|
||||
if (split.second().empty())
|
||||
{
|
||||
name_and_ext = split.first();
|
||||
}
|
||||
else
|
||||
{
|
||||
name_and_ext = split.second();
|
||||
}
|
||||
return name_and_ext.rsplit('.').first();
|
||||
}
|
||||
|
||||
bool FileSystemPath::exists() const
|
||||
{
|
||||
return ::access(m_path.raw(), F_OK) == 0;
|
||||
|
@ -108,7 +149,7 @@ bool FileSystemPath::exists() const
|
|||
FileSystemPath FileSystemPath::join(const String& entry) const
|
||||
{
|
||||
auto new_path = *this;
|
||||
new_path.m_path+=m_delimiter;
|
||||
new_path.m_path+=entry;
|
||||
new_path.m_path += m_delimiter;
|
||||
new_path.m_path += entry;
|
||||
return new_path;
|
||||
}
|
|
@ -6,16 +6,18 @@
|
|||
class FileSystemPath
|
||||
{
|
||||
public:
|
||||
FileSystemPath() = default;
|
||||
FileSystemPath();
|
||||
|
||||
FileSystemPath(const String& path);
|
||||
|
||||
const String& as_string() const;
|
||||
const String& str() const;
|
||||
|
||||
static FileSystemPath current_dir();
|
||||
static Result<FileSystemPath> current_dir();
|
||||
|
||||
String extension() const;
|
||||
|
||||
String file_name() const;
|
||||
|
||||
bool exists() const;
|
||||
|
||||
Result<bool> is_empty() const;
|
||||
|
@ -24,9 +26,11 @@ public:
|
|||
|
||||
FileSystemPath join(const String& entry) const;
|
||||
|
||||
bool is_regular_file() const;
|
||||
Result<bool> is_regular_file() const;
|
||||
|
||||
bool is_directory() const;
|
||||
Result<bool> is_directory() const;
|
||||
|
||||
Result<bool> is_regular_file_or_directory() const;
|
||||
|
||||
bool is_absolute() const;
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#include "ConsoleLogger.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
void ConsoleLogger::logLine(const String& msg)
|
||||
{
|
||||
printf("%s\n", msg.raw());
|
||||
}
|
||||
|
||||
void ConsoleLogger::logLine(const char* fmt, ...)
|
||||
{
|
||||
va_list(args);
|
||||
va_start(args, fmt);
|
||||
vprintf((String(fmt) + "\n").raw(), args);
|
||||
va_end(args);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "String.h"
|
||||
|
||||
class ConsoleLogger
|
||||
{
|
||||
public:
|
||||
static void logLine(const String& msg);
|
||||
|
||||
static void logLine(const char* fmt, ...);
|
||||
};
|
24
src/base/core/logging/ConsoleLogger.cpp
Normal file
24
src/base/core/logging/ConsoleLogger.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include "ConsoleLogger.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void ConsoleLogger::log_line(Level level,
|
||||
const String& msg,
|
||||
const String& fileName,
|
||||
const String& functionName,
|
||||
int lineNumber)
|
||||
{
|
||||
const auto log_msg = build_log_message(level,
|
||||
msg,
|
||||
fileName,
|
||||
functionName,
|
||||
lineNumber);
|
||||
if (level == Level::INFO)
|
||||
{
|
||||
printf("%s\n", log_msg.raw());
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s\n", log_msg.raw());
|
||||
}
|
||||
}
|
13
src/base/core/logging/ConsoleLogger.h
Normal file
13
src/base/core/logging/ConsoleLogger.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "Logger.h"
|
||||
|
||||
class ConsoleLogger : public Logger
|
||||
{
|
||||
public:
|
||||
void log_line(Level level,
|
||||
const String& line,
|
||||
const String& fileName,
|
||||
const String& functionName,
|
||||
int lineNumber) override;
|
||||
};
|
|
@ -1,12 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#define MLOG_ALL(msg, level) {String mt_logstream;\
|
||||
mt_logstream << msg; \
|
||||
FileLogger::GetInstance().LogLine(level, mt_logstream, __FILE__, __FUNCTION__, __LINE__);};
|
||||
|
||||
#define MLOG_INFO(msg) MLOG_ALL(msg, "Info");
|
||||
#define MLOG_ERROR(msg) MLOG_ALL(msg, "Error");
|
||||
|
||||
#include "Pointer.h"
|
||||
#include "String.h"
|
||||
|
48
src/base/core/logging/Logger.cpp
Normal file
48
src/base/core/logging/Logger.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "Logger.h"
|
||||
|
||||
#include "Time.h"
|
||||
#include "ConsoleLogger.h"
|
||||
|
||||
static Ptr<Logger> s_logger;
|
||||
|
||||
Logger* Logger::get_instance()
|
||||
{
|
||||
if (s_logger.get() == nullptr)
|
||||
{
|
||||
s_logger = Ptr<ConsoleLogger>::create();
|
||||
}
|
||||
return s_logger.get();
|
||||
}
|
||||
|
||||
String Logger::build_log_message(Level level,
|
||||
const String& msg,
|
||||
const String& fileName,
|
||||
const String& functionName,
|
||||
int lineNumber)
|
||||
{
|
||||
String log_msg;
|
||||
if (level == Level::INFO)
|
||||
{
|
||||
log_msg += "Info|";
|
||||
}
|
||||
else if (level == Level::ERROR)
|
||||
{
|
||||
log_msg += "Error|";
|
||||
}
|
||||
log_msg += Time::get_now_str() + "|";
|
||||
|
||||
String cleaned_filename;
|
||||
if (auto index = fileName.rindex('/'); index.valid())
|
||||
{
|
||||
fileName.slice(index.value()+1, fileName.size(), cleaned_filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
cleaned_filename = fileName;
|
||||
}
|
||||
log_msg += cleaned_filename + "::";
|
||||
log_msg += functionName + "::";
|
||||
log_msg += String::to_string(lineNumber) + "|";
|
||||
log_msg += msg;
|
||||
return log_msg;
|
||||
}
|
36
src/base/core/logging/Logger.h
Normal file
36
src/base/core/logging/Logger.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "String.h"
|
||||
#include "Pointer.h"
|
||||
|
||||
#define LOG_ALL(msg, level) {String mt_logstream;\
|
||||
mt_logstream << msg; \
|
||||
Logger::get_instance()->log_line(level, mt_logstream, __FILE__, __FUNCTION__, __LINE__);};
|
||||
#define LOG_INFO(msg) LOG_ALL(msg, Logger::Level::INFO);
|
||||
#define LOG_ERROR(msg) LOG_ALL(msg, Logger::Level::ERROR);
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
enum class Level
|
||||
{
|
||||
INFO,
|
||||
ERROR
|
||||
};
|
||||
|
||||
virtual ~Logger() = default;
|
||||
virtual void log_line(Level level,
|
||||
const String& line,
|
||||
const String& fileName,
|
||||
const String& functionName,
|
||||
int lineNumber){};
|
||||
|
||||
static Logger* get_instance();
|
||||
|
||||
protected:
|
||||
static String build_log_message(Level level,
|
||||
const String& line,
|
||||
const String& fileName,
|
||||
const String& functionName,
|
||||
int lineNumber);
|
||||
};
|
|
@ -12,7 +12,7 @@ public:
|
|||
return new T;
|
||||
}
|
||||
|
||||
void do_delete(T** p)
|
||||
void do_delete(T* p)
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
|
|
|
@ -27,17 +27,28 @@ public:
|
|||
{
|
||||
if (m_raw != nullptr)
|
||||
{
|
||||
m_allocator.do_delete(&m_raw);
|
||||
m_allocator.do_delete(m_raw);
|
||||
}
|
||||
}
|
||||
|
||||
Ptr(const Ptr& other) = delete;
|
||||
|
||||
Ptr(Ptr&& other)
|
||||
: m_allocator(std::move(other.m_allocator)),
|
||||
m_raw(other.m_raw)
|
||||
{
|
||||
other.m_raw = nullptr;
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
Ptr<T>& operator=(const Ptr<T>& other) = delete;
|
||||
|
||||
template<typename U>
|
||||
Ptr<T>& operator=(Ptr<U>&& other)
|
||||
{
|
||||
if (this->m_raw != other.get())
|
||||
{
|
||||
this->m_raw = dynamic_cast<T*>(other.get());
|
||||
other.clear_raw();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
T* get()
|
||||
|
@ -55,6 +66,11 @@ public:
|
|||
return m_raw;
|
||||
}
|
||||
|
||||
void clear_raw()
|
||||
{
|
||||
m_raw = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Allocator<T> m_allocator;
|
||||
T* m_raw{nullptr};
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
#include "UnicodeUtils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "Win32BaseIncludes.h"
|
||||
#endif
|
||||
|
||||
CommandLineArgs::CommandLineArgs()
|
||||
: mArugments(),
|
||||
|
@ -46,7 +48,7 @@ FileSystemPath CommandLineArgs::getLaunchPath()
|
|||
|
||||
void CommandLineArgs::recordLaunchPath()
|
||||
{
|
||||
mLaunchPath = FileSystemPath::current_dir();
|
||||
mLaunchPath = FileSystemPath::current_dir().value();
|
||||
}
|
||||
|
||||
void CommandLineArgs::process(int argc, char *argv[])
|
40
src/base/core/system/process/Process.h
Normal file
40
src/base/core/system/process/Process.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "String.h"
|
||||
|
||||
#include "File.h"
|
||||
#include "Result.h"
|
||||
#include <unistd.h>
|
||||
|
||||
class Process
|
||||
{
|
||||
public:
|
||||
Status launch(const String& command)
|
||||
{
|
||||
const auto pid = fork();
|
||||
if (pid < 0)
|
||||
{
|
||||
ON_ERRNO("Failed to fork");
|
||||
}
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
//pass
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static Result<String> get_self_name()
|
||||
{
|
||||
FileSystemPath path(String("/proc/self/cmdline"));
|
||||
File sys_file(path);
|
||||
|
||||
Result<String> ret;
|
||||
const auto rc = sys_file.readText(ret.value());
|
||||
if (!rc.ok())
|
||||
{
|
||||
ret.on_error(rc.error());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
29
src/base/core/time/Time.cpp
Normal file
29
src/base/core/time/Time.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "Time.h"
|
||||
#include <time.h>
|
||||
|
||||
String Time::get_now_str()
|
||||
{
|
||||
time_t time_buf{0};
|
||||
::time(&time_buf);
|
||||
if (time_buf == -1)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
struct tm tm_buf;
|
||||
auto rc = ::gmtime_r(&time_buf, &tm_buf);
|
||||
if (rc == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
String ret("T");
|
||||
ret += String::to_string(tm_buf.tm_mday) + ":";
|
||||
ret += String::to_string(tm_buf.tm_mon) + ":";
|
||||
const auto year = (tm_buf.tm_year - 100) + 2000;
|
||||
ret += String::to_string(year) + "Z";
|
||||
ret += String::to_string(tm_buf.tm_hour) + ":";
|
||||
ret += String::to_string(tm_buf.tm_min) + ":";
|
||||
ret += String::to_string(tm_buf.tm_sec);
|
||||
return ret;
|
||||
}
|
9
src/base/core/time/Time.h
Normal file
9
src/base/core/time/Time.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "String.h"
|
||||
|
||||
class Time
|
||||
{
|
||||
public:
|
||||
static String get_now_str();
|
||||
};
|
45
src/main.cpp
45
src/main.cpp
|
@ -1,43 +1,16 @@
|
|||
#include "Vector.h"
|
||||
#include "String.h"
|
||||
#include "FileSystemPath.h"
|
||||
#include "Directory.h"
|
||||
#include "File.h"
|
||||
#include "BuildSession.h"
|
||||
#include "ConsoleLogger.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
/*
|
||||
FileSystemPath main_path(__FILE__);
|
||||
printf("Starting build.\n");
|
||||
|
||||
auto main_directory = main_path.parent_path();
|
||||
for(const auto& subdir : Directory::getSubdirectories(main_directory))
|
||||
if (argc == 1)
|
||||
{
|
||||
for(const auto& subsubdir : Directory::getSubdirectories(subdir))
|
||||
{
|
||||
const auto build_file = subsubdir.join("build.toml");
|
||||
if (build_file.exists())
|
||||
{
|
||||
printf("Found dir file: %s\n", build_file.as_string().raw());
|
||||
// Gather all files
|
||||
//const auto header_files = Directory::getFilesWithExtension(subsubdir, ".h", true);
|
||||
//for(const auto& header : header_files)
|
||||
//{
|
||||
// printf("Found header file: %s\n", header.as_string().raw());
|
||||
// }
|
||||
|
||||
// Gather all directories
|
||||
|
||||
// Build library
|
||||
|
||||
}
|
||||
}
|
||||
LOG_ERROR("Missing arg with path to source dir");
|
||||
return -1;
|
||||
}
|
||||
*/
|
||||
|
||||
printf("Finished build.\n");
|
||||
|
||||
BuildSession build(argv[1]);
|
||||
build.scan();
|
||||
build.build();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -3,21 +3,26 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|||
CORE_SRC_DIR=$SCRIPT_DIR/../src/base/core
|
||||
|
||||
g++ $SCRIPT_DIR/test_runner.cpp \
|
||||
$SCRIPT_DIR/../src/base/core/CommandLineArgs.cpp \
|
||||
$CORE_SRC_DIR/system/process/CommandLineArgs.cpp \
|
||||
$CORE_SRC_DIR/base_types/Error.cpp \
|
||||
$CORE_SRC_DIR/base_types/Index.cpp \
|
||||
$CORE_SRC_DIR/data_structures/String.cpp \
|
||||
$CORE_SRC_DIR/file_utilities/FileSystemPath.cpp \
|
||||
$CORE_SRC_DIR/loggers/ConsoleLogger.cpp \
|
||||
$CORE_SRC_DIR/filesystem/FileSystemPath.cpp \
|
||||
$CORE_SRC_DIR/logging/Logger.cpp \
|
||||
$CORE_SRC_DIR/logging/ConsoleLogger.cpp \
|
||||
$CORE_SRC_DIR/time/Time.cpp \
|
||||
$SCRIPT_DIR/test_utils/TestCaseRunner.cpp \
|
||||
$SCRIPT_DIR/core/TestFileSystemPath.cpp \
|
||||
$SCRIPT_DIR/core/TestString.cpp \
|
||||
$SCRIPT_DIR/core/TestVector.cpp \
|
||||
-o bootstrap_tests -g \
|
||||
-o test_runner -g \
|
||||
-I$SCRIPT_DIR/test_utils \
|
||||
-I$CORE_SRC_DIR \
|
||||
-I$CORE_SRC_DIR/encoding \
|
||||
-I$CORE_SRC_DIR/loggers \
|
||||
-I$CORE_SRC_DIR/logging \
|
||||
-I$CORE_SRC_DIR/data_structures \
|
||||
-I$CORE_SRC_DIR/base_types \
|
||||
-I$CORE_SRC_DIR/memory \
|
||||
-I$CORE_SRC_DIR/file_utilities
|
||||
-I$CORE_SRC_DIR/time \
|
||||
-I$CORE_SRC_DIR/system/process \
|
||||
-I$CORE_SRC_DIR/filesystem
|
18
test/core/TestFileSystemPath.cpp
Normal file
18
test/core/TestFileSystemPath.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include "FileSystemPath.h"
|
||||
|
||||
#include "TestFramework.h"
|
||||
//#include "TestUtils.h"
|
||||
#include <iostream>
|
||||
|
||||
TEST_CASE(FileSystemPath_Join, "core")
|
||||
{
|
||||
FileSystemPath path("/home/jgrogan/code/compilz/src/src");
|
||||
auto new_path = path.join("test");
|
||||
REQUIRE(new_path.str() == "/home/jgrogan/code/compilz/src/src/test");
|
||||
}
|
||||
|
||||
TEST_CASE(FileSystemPath_Extension, "core")
|
||||
{
|
||||
FileSystemPath path("test.dat");
|
||||
REQUIRE(path.extension() == ".dat");
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
//#include "TestUtils.h"
|
||||
#include <iostream>
|
||||
|
||||
TEST_CASE(TestBasicStringOps, "core")
|
||||
TEST_CASE(String_Append, "core")
|
||||
{
|
||||
String str;
|
||||
str += 'a';
|
||||
|
@ -12,9 +12,10 @@ TEST_CASE(TestBasicStringOps, "core")
|
|||
str += 'c';
|
||||
str += 'd';
|
||||
REQUIRE(str == "abcd");
|
||||
String long_string("abc/def/ghi/jkl");
|
||||
}
|
||||
|
||||
TEST_CASE(TestStringReverse, "core")
|
||||
TEST_CASE(String_Reverse, "core")
|
||||
{
|
||||
String str0;
|
||||
str0.reverse();
|
||||
|
@ -37,6 +38,35 @@ TEST_CASE(TestStringReverse, "core")
|
|||
REQUIRE(str4 == "dcba");
|
||||
}
|
||||
|
||||
TEST_CASE(String_Extend, "core")
|
||||
{
|
||||
String str("/home/jgrogan/code/compilz/src/src");
|
||||
auto str_cpy = str;
|
||||
str += "/";
|
||||
str += "test";
|
||||
REQUIRE(str == String("/home/jgrogan/code/compilz/src/src/test"));
|
||||
|
||||
str_cpy += "/";
|
||||
str_cpy += "test";
|
||||
REQUIRE(str == str_cpy);
|
||||
}
|
||||
|
||||
TEST_CASE(String_Slice, "core")
|
||||
{
|
||||
String str("test.dat");
|
||||
const auto rindex = str.rindex('.');
|
||||
REQUIRE(rindex.valid());
|
||||
REQUIRE(rindex.value() == 4);
|
||||
|
||||
String right;
|
||||
str.slice(rindex.value(), str.size(), right);
|
||||
REQUIRE(right == ".dat");
|
||||
|
||||
const auto split = str.rsplit('.');
|
||||
REQUIRE(split.first() == "test");
|
||||
REQUIRE(split.second() == "dat");
|
||||
}
|
||||
|
||||
/*
|
||||
TEST_CASE(TestStringUtils_StripSurroundingWhitepsace, "core")
|
||||
{
|
||||
|
|
|
@ -4,15 +4,38 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
TEST_CASE(TestVectorOps, "core")
|
||||
TEST_CASE(TestVectorExtend, "core")
|
||||
{
|
||||
Vector<size_t> vec;
|
||||
for(size_t idx=0;idx<100;idx++)
|
||||
for(size_t idx=0; idx<16; idx++)
|
||||
{
|
||||
vec.push_back(idx);
|
||||
}
|
||||
for(size_t idx=0; idx<100; idx++)
|
||||
REQUIRE(vec.size() == 16);
|
||||
|
||||
Vector<size_t> vec0;
|
||||
for(size_t idx=16; idx<19; idx++)
|
||||
{
|
||||
REQUIRE(vec[idx] == idx);
|
||||
vec0.push_back(idx);
|
||||
}
|
||||
vec.extend(vec0);
|
||||
REQUIRE(vec.size() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE(TestVectorSlize, "core")
|
||||
{
|
||||
Vector<size_t> vec;
|
||||
for(size_t idx=0; idx<8; idx++)
|
||||
{
|
||||
vec.push_back(idx);
|
||||
}
|
||||
|
||||
Vector<size_t> bottom_half;
|
||||
vec.slice(4, bottom_half);
|
||||
REQUIRE(bottom_half.size() == 4);
|
||||
|
||||
Vector<size_t> top_half;
|
||||
vec.slice(4, 8, top_half);
|
||||
REQUIRE(top_half.size() == 4);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "TestFramework.h"
|
||||
|
||||
#include "CommandLineArgs.h"
|
||||
#include "ConsoleLogger.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
@ -17,9 +17,9 @@ int main(int argc, char *argv[])
|
|||
CommandLineArgs args;
|
||||
args.process(argc, argv);
|
||||
|
||||
ConsoleLogger::logLine("Starting test run.");
|
||||
LOG_INFO("Starting test run.");
|
||||
TestCaseRunner::getInstance().run(args.getUserArgs());
|
||||
ConsoleLogger::logLine("Finished test run.");
|
||||
LOG_INFO("Finished test run.");
|
||||
|
||||
#ifdef _WIN32
|
||||
CoUninitialize();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "TestCaseRunner.h"
|
||||
|
||||
#include "FileLogger.h"
|
||||
#include "ConsoleLogger.h"
|
||||
#include "Logger.h"
|
||||
//#include "TestUiApplication.h"
|
||||
|
||||
bool TestCaseRunner::sLastTestFailed = false;
|
||||
|
@ -66,22 +66,22 @@ bool TestCaseRunner::run(const Vector<String>& args)
|
|||
}
|
||||
|
||||
sLastTestFailed = false;
|
||||
ConsoleLogger::logLine("TestFramework: Running Test - %s", test_case->getName().raw());
|
||||
LOG_INFO("TestFramework: Running Test - " << test_case->getName());
|
||||
test_case->run();
|
||||
|
||||
if (sLastTestFailed)
|
||||
{
|
||||
ConsoleLogger::logLine("Failed at line %s", sFailureLine.raw());
|
||||
LOG_INFO("Failed at line: " << sFailureLine);
|
||||
mFailingTests.push_back(test_case->getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (mFailingTests.size() > 0)
|
||||
{
|
||||
ConsoleLogger::logLine("%d failing tests", mFailingTests.size());
|
||||
LOG_INFO(String::fmt("%d failing tests", mFailingTests.size()));
|
||||
for(const auto& name : mFailingTests)
|
||||
{
|
||||
ConsoleLogger::logLine(name);
|
||||
LOG_INFO(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ struct Holder
|
|||
if(!bool(predicate)) \
|
||||
{ \
|
||||
const auto msg = String::to_string(__LINE__) + String(" with check: '") + String(#predicate) + String("'"); \
|
||||
TestCaseRunner::getInstance().markTestFailure(msg); \
|
||||
TestCaseRunner::getInstance().markTestFailure(msg); \
|
||||
return; \
|
||||
} \
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue