Renamed NBCoreUtils to just NBCOre
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
include_directories(./.)
|
||||
|
||||
add_library(NBCore
|
||||
./src/Errors.cpp
|
||||
)
|
||||
|
||||
add_subdirectory(./tests)
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
#ifndef _NB_DATASINK
|
||||
#define _NB_DATASINK
|
||||
|
||||
namespace nb {
|
||||
|
||||
template <typename T>
|
||||
class SinkBase {
|
||||
using InputType = T;
|
||||
public:
|
||||
virtual bool in(const T&) = 0;
|
||||
|
||||
protected:
|
||||
SinkBase();
|
||||
|
||||
};
|
||||
|
||||
/* template <typename T>
|
||||
class Sink : public SinkBase {
|
||||
public:
|
||||
Sink& operator=(const Sink&) = 0;
|
||||
|
||||
template<typename U>
|
||||
virtual bool in(const U&) = 0;
|
||||
|
||||
protected:
|
||||
Sink();
|
||||
|
||||
}; */
|
||||
|
||||
template <typename T>
|
||||
class SourceBase {
|
||||
using OutputType = T;
|
||||
public:
|
||||
SourceBase& operator=(const SourceBase&) = 0;
|
||||
|
||||
virtual bool out(T*) = 0;
|
||||
|
||||
|
||||
protected:
|
||||
SourceBase();
|
||||
SinkBase<T>* _sink;
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class Pipe : public SinkBase<T>, public SourceBase<T>{
|
||||
using Input = SourceBase<T>;
|
||||
using Output = SinkBase<T>;
|
||||
public:
|
||||
Pipe(Sink<T>& sink) : Input::_sink{&sink} {}
|
||||
|
||||
bool in(const T&) = 0;
|
||||
|
||||
protected:
|
||||
using Input::_sink;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* template <typename T>
|
||||
class Source : SourceBase {
|
||||
public:
|
||||
Source& operator=(const Source&) = 0;
|
||||
|
||||
template<typename U>
|
||||
virtual bool out(const U&) = 0;
|
||||
|
||||
protected:
|
||||
Source();
|
||||
|
||||
}; */
|
||||
|
||||
} // namespace nb
|
||||
#endif // _NB_DATASINK
|
||||
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
#ifndef _NB_ERROR
|
||||
#define _NB_ERROR
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef THROW_WITH_INFO
|
||||
#ifdef CODE_ERROR_LOCATIONS
|
||||
#define THROW_WITH_INFO(type, ...) throw type(__VA_ARGS__, __LINE__, __FILE__)
|
||||
#else
|
||||
#define THROW_WITH_INFO(type, ...) throw type(__VA_ARGS__)
|
||||
#endif // CODE_ERROR_LOCATIONS
|
||||
#endif // THROW_WITH_INFO
|
||||
|
||||
#ifndef THROW
|
||||
#ifdef LOGGING
|
||||
#define THROW_WTIH_INFO(type, ...) throw type(__VA_ARGS__, __LINE__, __FILE__)
|
||||
#else
|
||||
#define THROW(...) THROW_WITH_INFO(__VA_ARGS__)
|
||||
#endif // LOGGING
|
||||
#endif // THROW
|
||||
|
||||
namespace nb {
|
||||
|
||||
typedef std::unordered_map<unsigned int, const char*> ErrorCodeMap;
|
||||
|
||||
template<class ErrorType>
|
||||
class ErrorBase : public std::exception {
|
||||
public:
|
||||
ErrorBase(
|
||||
const unsigned int code,
|
||||
unsigned int line=0,
|
||||
std::string filename=""
|
||||
) noexcept : ErrorBase(
|
||||
code,
|
||||
ErrorBase<ErrorType>::lookup(code),
|
||||
line,
|
||||
filename
|
||||
) {}
|
||||
ErrorBase(
|
||||
const unsigned int code,
|
||||
const std::exception& trace,
|
||||
unsigned int line=0,
|
||||
std::string filename=""
|
||||
) noexcept : ErrorBase(
|
||||
code,
|
||||
ErrorBase<ErrorType>::lookup(code),
|
||||
trace,
|
||||
line,
|
||||
filename
|
||||
) {}
|
||||
|
||||
static std::string lookup(unsigned int);
|
||||
unsigned int code() const noexcept {
|
||||
return static_cast<const ErrorType*>(this)->_code;
|
||||
};
|
||||
virtual const char* what() const noexcept override final { return _msg.c_str(); };
|
||||
|
||||
protected:
|
||||
ErrorBase() = default;
|
||||
ErrorBase(
|
||||
const unsigned int code,
|
||||
std::string msg,
|
||||
unsigned int line=0,
|
||||
std::string filename=""
|
||||
) noexcept : _code{code}, _msg{""} {
|
||||
static_assert(std::is_same<const std::unordered_map<unsigned int, const char*>, decltype(ErrorType::ErrorMessages)>::value,
|
||||
"const std::unordered_map<unsigned int, const char*> ErrorMessages must be "
|
||||
"a class member."
|
||||
);
|
||||
static_assert(std::is_enum_v<typename ErrorType::ErrorCodes>, "enum ErrorCodes must be a class member.");
|
||||
static_assert(std::is_same<std::underlying_type_t<typename ErrorType::ErrorCodes>, unsigned int>::value,
|
||||
"enum ErrorCodes must be of underlying type unsigned int."
|
||||
);
|
||||
static_assert(std::is_same<const std::string, decltype(ErrorType::type)>::value,
|
||||
"const std::string type must be a class member."
|
||||
);
|
||||
|
||||
_msg += std::string(ErrorType::type);
|
||||
_msg += "[" + std::to_string(_code) + "]";
|
||||
if (line && filename.size()) {
|
||||
_msg += " in \'" + filename + "\':" + std::to_string(line);
|
||||
}
|
||||
_msg += "\n " + msg;
|
||||
}
|
||||
ErrorBase(
|
||||
const unsigned int code,
|
||||
std::string msg,
|
||||
const std::exception& trace,
|
||||
unsigned int line=0,
|
||||
std::string filename=""
|
||||
) noexcept : ErrorBase(code, msg, line, filename) {
|
||||
std::string what_msg(trace.what());
|
||||
std::string::size_type newline_pos=-1;
|
||||
while((newline_pos=what_msg.find("\n", newline_pos+1))!= std::string::npos) {
|
||||
what_msg.replace(newline_pos, 1, "\n ");
|
||||
}
|
||||
_msg += "\n Trace - " + what_msg;
|
||||
}
|
||||
|
||||
unsigned int _code;
|
||||
std::string _msg;
|
||||
};
|
||||
|
||||
class Error : public ErrorBase<Error> {
|
||||
public:
|
||||
enum ErrorCodes : unsigned int {
|
||||
GENERAL, UNDEFINED, BADERRORCODE
|
||||
};
|
||||
|
||||
using ErrorBase<Error>::ErrorBase;
|
||||
|
||||
friend ErrorBase<Error>;
|
||||
|
||||
protected:
|
||||
static const std::string type;
|
||||
static const ErrorCodeMap ErrorMessages;
|
||||
|
||||
};
|
||||
|
||||
template<class ErrorType>
|
||||
std::string ErrorBase<ErrorType>::lookup(unsigned int code) {
|
||||
for (auto kv : ErrorType::ErrorMessages) {
|
||||
if (kv.first==code) {
|
||||
return std::string(kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(Error::ErrorCodes::BADERRORCODE);
|
||||
}
|
||||
|
||||
} // namespace nb
|
||||
|
||||
|
||||
#endif // _NB_ERROR
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#ifndef _NB_LOGGER
|
||||
#define _NB_LOGGER
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ThreadSafeQueue.hpp"
|
||||
|
||||
namespace nb {
|
||||
|
||||
typedef std::chrono::time_point<
|
||||
std::chrono::system_clock,
|
||||
std::chrono::nanoseconds
|
||||
> LoggerTimePoint;
|
||||
|
||||
struct LogEvent{
|
||||
const LoggerTimePoint time;
|
||||
const unsigned char lvl;
|
||||
const std::string msg;
|
||||
};
|
||||
|
||||
typedef std::string (*LogProcessFunction)(const LoggerTimePoint&, const std::string&);
|
||||
typedef std::unordered_map<uint8_t, LogProcessFunction> LogProcessFunctionMap;
|
||||
|
||||
template<typename StreamType>
|
||||
class Logger {
|
||||
public:
|
||||
Logger(StreamType& stream) : _ostream(&stream) {}
|
||||
|
||||
bool isRunning() const;
|
||||
unsigned int count() const;
|
||||
|
||||
bool run();
|
||||
bool stop();
|
||||
LogEvent pop();
|
||||
void flush() {
|
||||
while(_queue.size()) {
|
||||
pop();
|
||||
}
|
||||
}
|
||||
void deleteAll() {
|
||||
_queue.empty();
|
||||
}
|
||||
void msg(const std::string& _msg, uint8_t lvl=0) {
|
||||
_queue.push(LogEvent{std::chrono::system_clock::now(), lvl, _msg});
|
||||
}
|
||||
void warn(const std::string& _msg, uint8_t lvl=1) {
|
||||
msg(_msg, lvl);
|
||||
}
|
||||
void warn(const std::exception& _err, uint8_t lvl=1) {
|
||||
msg(_err.what(), lvl);
|
||||
}
|
||||
void error(const std::string& _msg, uint8_t lvl=0xFF) {
|
||||
msg(_msg, lvl);
|
||||
}
|
||||
void error(const std::exception& _err, uint8_t lvl=0xFF) {
|
||||
msg(_err.what(), lvl);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void process(const LogEvent& event) {
|
||||
uint8_t lvl = event.lvl;
|
||||
try {
|
||||
*_ostream << _function_map.at(lvl)(event.time, event.msg).c_str();
|
||||
} catch (std::out_of_range e) {
|
||||
*_ostream << _function_map.at(0)(event.time, event.msg).c_str();
|
||||
//*_ostream << event.msg;
|
||||
}
|
||||
*_ostream << "\n";
|
||||
}
|
||||
|
||||
static const LogProcessFunctionMap _function_map;
|
||||
|
||||
ThreadsafeQueue<LogEvent> _queue;
|
||||
StreamType* const _ostream;
|
||||
std::atomic<bool> _running;
|
||||
std::shared_ptr<std::thread> _runningThread = nullptr;
|
||||
};
|
||||
|
||||
template <typename ST>
|
||||
const LogProcessFunctionMap Logger<ST>::_function_map = {
|
||||
{0, [](const LoggerTimePoint& tp, const std::string& msg){
|
||||
return msg;
|
||||
}}
|
||||
};
|
||||
|
||||
template <typename ST>
|
||||
bool Logger<ST>::isRunning() const {
|
||||
return _running && bool(_runningThread);
|
||||
}
|
||||
|
||||
template <typename ST>
|
||||
unsigned int Logger<ST>::count() const {
|
||||
return _queue.size();
|
||||
}
|
||||
|
||||
template <typename ST>
|
||||
bool Logger<ST>::run() {
|
||||
if (!isRunning()) {
|
||||
_runningThread = std::make_shared<std::thread>([&]{
|
||||
while(this->isRunning()) {
|
||||
this->flush();
|
||||
}
|
||||
});
|
||||
}
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
template <typename ST>
|
||||
bool Logger<ST>::stop() {
|
||||
if (isRunning()) {
|
||||
_running = false;
|
||||
_runningThread->join();
|
||||
_runningThread = nullptr;
|
||||
flush();
|
||||
}
|
||||
return isRunning();
|
||||
}
|
||||
|
||||
template <typename ST>
|
||||
LogEvent Logger<ST>::pop() {
|
||||
std::shared_ptr<LogEvent> event;
|
||||
_queue.pop(event);
|
||||
process(*event);
|
||||
return *event;
|
||||
}
|
||||
|
||||
} // namespace nb
|
||||
#endif // _NB_LOGGER
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#ifndef _NB_SMARTSTREAM
|
||||
#define _NB_SMARTSTREAM
|
||||
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace nb {
|
||||
|
||||
using RGB = std::array<unsigned char, 3>;
|
||||
|
||||
std::string toANSI(const RGB& color) {
|
||||
return "\x1b[38;2;"+std::to_string(color[0])+";"+std::to_string(color[1])+
|
||||
";"+std::to_string(color[2])+"m";
|
||||
}
|
||||
|
||||
std::string toANSI(const unsigned int& color) {
|
||||
return "\x1b[38;5;"+std::to_string(color)+"m";
|
||||
}
|
||||
|
||||
template <typename U, typename Derived=void>
|
||||
struct SmartText {
|
||||
SmartText(const U& val) : msg(val) {}
|
||||
|
||||
const U msg;
|
||||
|
||||
template<typename C, typename T>
|
||||
void process(std::basic_ostream<C,T>& stream) const;
|
||||
};
|
||||
|
||||
template <typename Derived>
|
||||
struct SmartText<std::string, Derived> {
|
||||
template <std::size_t N>
|
||||
SmartText(char const(&val) [N]) : msg(val) {}
|
||||
SmartText(const std::string& val) : msg(val) {}
|
||||
SmartText(const char* val) : msg(val) {}
|
||||
|
||||
const std::string msg;
|
||||
|
||||
template<typename C, typename T>
|
||||
void process(std::basic_ostream<C,T>& stream) const;
|
||||
};
|
||||
|
||||
template <typename U, typename Derived>
|
||||
template <typename C, typename T>
|
||||
void SmartText<U,Derived>::process(std::basic_ostream<C,T>& stream) const {
|
||||
if (std::is_void<Derived>::value) {
|
||||
stream << msg;
|
||||
} else {
|
||||
static_cast<Derived*>(this)->process(stream);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename C, typename T, typename U, typename D>
|
||||
std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& stream, const SmartText<U,D>& smt) {
|
||||
if (std::is_void<D>::value) {
|
||||
stream << smt.msg;
|
||||
} else {
|
||||
static_cast<const D*>(&smt)->process(stream);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
struct TerminalColor : public SmartText<U, TerminalColor<U>> {
|
||||
typedef SmartText<U, TerminalColor<U>> Base;
|
||||
using Base::msg;
|
||||
|
||||
explicit TerminalColor(unsigned int color, const U& val)
|
||||
: Base(val), colorSequence(toANSI(color)) { }
|
||||
explicit TerminalColor(const RGB& color, const U& val)
|
||||
: Base(val), colorSequence(toANSI(color)) { }
|
||||
|
||||
const std::string colorSequence;
|
||||
|
||||
template <typename C, typename T>
|
||||
void process(std::basic_ostream<C, T>& stream) const {
|
||||
if (&stream == &std::cout) {
|
||||
stream << colorSequence << msg << "\x1b[0m";
|
||||
} else {
|
||||
stream << msg;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // _NB_SMARTSTREAM
|
||||
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
#ifndef _NB_UTIL_THREADSAFE_QUEUE
|
||||
#define _NB_UTIL_THREADSAFE_QUEUE
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <type_traits>
|
||||
|
||||
namespace nb {
|
||||
|
||||
template<typename T, bool isPoly>
|
||||
class ThreadsafeQueue_impl;
|
||||
|
||||
// Non-polymorphic
|
||||
template<typename T>
|
||||
class ThreadsafeQueue_impl<T, false> {
|
||||
public:
|
||||
bool empty() const {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _queue.empty();
|
||||
}
|
||||
unsigned int size() const {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _queue.size();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
void push(U&& val) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_queue.push(std::forward<U>(val));
|
||||
_cond.notify_one();
|
||||
}
|
||||
bool pop(std::shared_ptr<T>& dest) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_queue.empty()) { return false; }
|
||||
dest = std::make_shared<T>(std::move(_queue.front()));
|
||||
_queue.pop();
|
||||
return true;
|
||||
}
|
||||
std::shared_ptr<T> popBlock() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_cond.wait(lock, [&]{ return !_queue.empty(); });
|
||||
auto ret = std::make_shared<T>(std::move(_queue.front()));
|
||||
_queue.pop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected:
|
||||
ThreadsafeQueue_impl() {}
|
||||
|
||||
std::queue<T> _queue;
|
||||
std::condition_variable _cond;
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
};
|
||||
|
||||
// Polymorphic
|
||||
template<typename T>
|
||||
class ThreadsafeQueue_impl<T, true> {
|
||||
public:
|
||||
bool empty() const {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _queue.empty();
|
||||
}
|
||||
unsigned int size() const {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
return _queue.size();
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
void push(U&& val) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
std::shared_ptr<T> newval = std::make_shared<T>(std::forward<U>(val));
|
||||
_queue.push(newval);
|
||||
_cond.notify_one();
|
||||
}
|
||||
bool pop(std::shared_ptr<T>& dest) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_queue.empty()) { return false; }
|
||||
dest = std::move(_queue.front());
|
||||
_queue.pop();
|
||||
return true;
|
||||
}
|
||||
std::shared_ptr<T> popBlock() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_cond.wait(lock, [&]{ return !_queue.empty(); });
|
||||
auto ret = std::move(_queue.front());
|
||||
_queue.pop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected:
|
||||
ThreadsafeQueue_impl() {}
|
||||
|
||||
std::queue<std::shared_ptr<T>> _queue;
|
||||
std::condition_variable _cond;
|
||||
mutable std::mutex _mutex;
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ThreadsafeQueue : public ThreadsafeQueue_impl<T, std::is_polymorphic<T>::value> {
|
||||
public:
|
||||
using Implementation = ThreadsafeQueue_impl<T, std::is_polymorphic<T>::value>;
|
||||
|
||||
ThreadsafeQueue() = default;
|
||||
~ThreadsafeQueue() = default;
|
||||
|
||||
ThreadsafeQueue(ThreadsafeQueue& rhs) = delete;
|
||||
ThreadsafeQueue& operator=(ThreadsafeQueue& rhs) = delete;
|
||||
ThreadsafeQueue(ThreadsafeQueue&& rhs) = delete;
|
||||
ThreadsafeQueue& operator=(ThreadsafeQueue&& rhs) = delete;
|
||||
|
||||
operator bool() const { return !Implementation::empty(); }
|
||||
|
||||
};
|
||||
|
||||
} // namespace nb
|
||||
|
||||
|
||||
#endif // _NB_UTIL_THREADSAFE_QUEUE
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "Errors.hpp"
|
||||
|
||||
namespace nb {
|
||||
|
||||
const std::string Error::type = "Error";
|
||||
const ErrorCodeMap Error::ErrorMessages = {
|
||||
{ErrorCodes::GENERAL, "General std::exception."},
|
||||
{ErrorCodes::UNDEFINED, "Undefined / general error."},
|
||||
{ErrorCodes::BADERRORCODE, "Unrecognized error code."}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.26.0)
|
||||
project(gtest_Graphics VERSION 0.1.0 LANGUAGES C CXX)
|
||||
|
||||
add_subdirectory(./ErrorTest)
|
||||
add_subdirectory(./SmartStreamTest)
|
||||
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.26.0)
|
||||
project(gtest_Graphics VERSION 0.1.0 LANGUAGES C CXX)
|
||||
|
||||
add_executable(ErrorTest main.cpp)
|
||||
target_link_libraries(ErrorTest NBCore)
|
||||
@@ -0,0 +1,54 @@
|
||||
#define CODE_ERROR_LOCATIONS
|
||||
|
||||
#include "Errors.hpp"
|
||||
#include <iostream>
|
||||
#include "Logger.hpp"
|
||||
|
||||
using namespace nb;
|
||||
|
||||
class TestError : public ErrorBase<TestError> {
|
||||
public:
|
||||
using ErrorBase<TestError>::ErrorBase;
|
||||
|
||||
enum ErrorCodes : unsigned int {
|
||||
A, B, C, D
|
||||
};
|
||||
static const std::string type;
|
||||
static const ErrorCodeMap ErrorMessages;
|
||||
};
|
||||
|
||||
const std::string TestError::type="TestError";
|
||||
const ErrorCodeMap TestError::ErrorMessages{
|
||||
{TestError::ErrorCodes::A, "Hey!"},
|
||||
{TestError::ErrorCodes::B, "How"},
|
||||
{TestError::ErrorCodes::C, "You"},
|
||||
{TestError::ErrorCodes::D, "Doin"}
|
||||
};
|
||||
|
||||
int main() {
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
THROW(TestError, TestError::ErrorCodes::D);
|
||||
} catch (const std::exception& e){
|
||||
THROW(TestError, TestError::ErrorCodes::C, e);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
THROW(TestError, TestError::ErrorCodes::B, e);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
THROW(TestError, TestError::ErrorCodes::A, e);
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
// THROW(Error, Error::ErrorCodes::UNDEFINED, e);
|
||||
Logger log(std::cout);
|
||||
std::cout << "Logger is starting: " << log.run() << std::endl;
|
||||
std::cout << "Logger is running: " << log.isRunning() << std::endl;
|
||||
log.error(e);
|
||||
log.stop();
|
||||
std::cout << "Logger is running: " << log.isRunning() << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.26.0)
|
||||
project(gtest_Graphics VERSION 0.1.0 LANGUAGES C CXX)
|
||||
|
||||
add_executable(SmartStreamTest main.cpp)
|
||||
target_link_libraries(SmartStreamTest NBCore)
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
#include "SmartStreams.hpp"
|
||||
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
auto test = nb::TerminalColor(9, 2030);
|
||||
|
||||
std::cout << test;
|
||||
|
||||
std::ofstream file;
|
||||
file.open("test.txt");
|
||||
if (file.is_open()) {
|
||||
file << test;
|
||||
}
|
||||
file.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user