Merge remote-tracking branch 'origin'

main
Brett 2025-04-06 17:23:37 -04:00
commit 685753b217
77 changed files with 8205 additions and 2717 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20)
include(cmake/color.cmake)
set(BLT_VERSION 3.0.6)
set(BLT_VERSION 5.2.34)
set(BLT_TARGET BLT)
@ -19,6 +19,7 @@ option(BUILD_PROFILING "Build the BLT profiler extension" ON)
option(BUILD_FS "Build the BLT FS utilities including the NBT + eNBT extension" ON)
option(BUILD_PARSE "Build the BLT parsers" ON)
option(BUILD_FORMAT "Build the BLT formatters" ON)
option(BUILD_LOGGING "Build the BLT logging utilities" ON)
option(BUILD_TESTS "Build the BLT test set" OFF)
@ -88,8 +89,14 @@ if (${BUILD_FORMAT})
message(STATUS "Building ${Yellow}format${ColourReset} cxx files")
file(GLOB_RECURSE FORMAT_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/format/*.cpp")
else ()
set(FORMAT_FILES ""
include/blt/std/iterator.h)
set(FORMAT_FILES "")
endif ()
if (${BUILD_LOGGING})
message(STATUS "Building ${Yellow}logging${ColourReset} cxx files")
file(GLOB_RECURSE LOGGING_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/blt/logging/*.cpp")
else ()
set(LOGGING_FILES "")
endif ()
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libraries/parallel-hashmap)
@ -111,7 +118,7 @@ endif ()
include_directories(include/)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/config/)
add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES})
add_library(${BLT_TARGET} ${STD_FILES} ${PROFILING_FILES} ${FS_FILES} ${PARSE_FILES} ${FORMAT_FILES} ${LOGGING_FILES})
string(REPLACE "+" "\\+" escaped_source ${CMAKE_CURRENT_SOURCE_DIR})
string(APPEND escaped_source "/src/blt/.*/")
@ -166,8 +173,9 @@ install(TARGETS ${BLT_TARGET}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
macro(blt_add_project name source type)
macro(blt_add_test name source type)
message("Adding project ${name} of type ${type}" DEBUG)
project(${name}-${type})
add_executable(${name}-${type} ${source})
@ -178,8 +186,10 @@ macro(blt_add_project name source type)
target_link_libraries(${name}-${type} PRIVATE BLT)
target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
if (NOT MSVC)
target_compile_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
target_link_options(${name}-${type} PRIVATE -Wall -Wextra -Wpedantic -Wno-comment)
endif()
target_compile_definitions(${name}-${type} PRIVATE BLT_DEBUG_LEVEL=${DEBUG_LEVEL})
if (${TRACK_ALLOCATIONS})
@ -212,7 +222,9 @@ endmacro()
if (${BUILD_TESTS})
message("Building tests for version ${BLT_VERSION}")
blt_add_project(blt-iterator tests/iterator_tests.cpp test)
blt_add_test(blt_iterator tests/iterator_tests.cpp test)
blt_add_test(blt_argparse tests/argparse_tests.cpp test)
blt_add_test(blt_logging tests/logger_tests.cpp test)
message("Built tests")
endif ()

View File

@ -1,24 +1,50 @@
# **BLT v0.20**
# **BLT v5.1**
A C++17 common utilities library to make thing easy!
![Icon](icon_large.png)
---
# ***Features***
- ## blt/fs
- ### loader.h
- std::string blt::fs::getFile(std::string_view path)
- Gets the entire file as a string.
- std::vector\<std::string> blt::fs::getLinesFromFile(std::string_view path)
- Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines
- std::vector\<std::string> blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector<include_guard> guards);
- Recursively include in order based on the include string (include_header) marked by the include guards
- Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL)
- std::string blt::fs::loadBrainFuckFile(const std::string& path)
- Load a brainfuck file recursively, uses ~ to mark the include path
- ### nbt.h
- probably needs to be remade (TODO)
# Features
## BLT Format
This module provides general string formatting utilities. *Note: this folder contains mostly outdated library files and will be updated in the future.*
### Files
- **boxing.h**
Simple utility for drawing boxes around blocks of text.
- **format.h**
Legacy library file containing various utilities for creating formatted output. Also includes methods for writing Java UTF8 strings.
## BLT Filesystem
This module provides helper classes for filesystem objects. It seeks to offer an interface that is simpler than the one provided by the standard library.
Specifically, the number of functions required to implement is significantly lower,
and the interface is generally cleaner. Eventually, this module aims to support various file formats,
such as Minecraft's NBT system. Currently, there is an existing NBT file, but it was written when I was first learning C++.
### Files
- **filesystem.h**
This is the base file which includes all other files. You should use the other options as this can be a heavy file to include
- **path_helper.h**
This file provides functions for interfacing with paths. Specifically, as of this moment it only provides an interface for getting the base name of a file path.
- **loader.h**
- `std::string blt::fs::getFile(std::string_view path)`
- Gets the entire file as a string.
- `std::vector\<std::string> blt::fs::getLinesFromFile(std::string_view path)`
- Gets the entire file as a string, then splits on the new line character. Then returns a vector of those lines
- `std::vector\<std::string> blt::fs::recursiveInclude(std::string_view path, std::string include_header, std::vector<include_guard> guards)`
- Recursively include in order based on the include string (include_header) marked by the include guards
- Defaults to C/C++/GLSL preprocessor style (Was designed for GLSL)
- `std::string blt::fs::loadBrainFuckFile(const std::string& path)`
- Load a brainfuck file recursively, uses ~ to mark the include path
- ### nbt.h
- probably needs to be remade (TODO)
- ## blt/math
- ### averages.h
- blt::averagizer_o_matic

0
cloc.sh Normal file → Executable file
View File

View File

@ -23,7 +23,9 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
message(STATUS "GCC libs: ${Green}stdc++fs${ColourReset}")
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always)
target_link_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -fdiagnostics-color=always)
target_link_options(${PROJECT_NAME} PUBLIC -rdynamic)
if (NOT WIN32)
target_link_options(${PROJECT_NAME} PUBLIC -rdynamic)
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC stdc++fs)
include(GNUInstallDirs)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")

View File

@ -1,74 +0,0 @@
#!/usr/bin/python3
import subprocess
#---------------------------------------
# CONFIG
#---------------------------------------
VERSION_BEGIN_STR = "set(BLT_VERSION "
VERSION_END_STR = ")"
#---------------------------------------
# DO NOT TOUCH
#---------------------------------------
type = input("What kind of commit is this ((M)ajor, (m)inor, (p)atch)? ")
def load_cmake():
cmake_file = open("CMakeLists.txt", 'r')
cmake_text = cmake_file.read()
cmake_file.close()
return cmake_text
def write_cmake(cmake_text):
cmake_file = open("CMakeLists.txt", 'w')
cmake_file.write(cmake_text)
cmake_file.close()
def get_version(cmake_text):
begin = cmake_text.find(VERSION_BEGIN_STR) + len(find_text)
end = cmake_text.find(VERSION_END_STR, begin)
return (cmake_text[begin:end], begin, end)
def split_version(cmake_text):
version, begin, end = get_version(cmake_text)
version_parts = version.split('.')
return (version_parts, begin, end)
def recombine(cmake_text, version_parts, begin, end):
constructed_version = version_parts[0] + '.' + version_parts[1] + '.' + version_parts[2]
constructed_text_begin = cmake_text[0:begin]
constrcuted_text_end = cmake_text[end::]
return constructed_text_begin + constructed_version + constrcuted_text_end
def inc_major(cmake_text):
version_parts, begin, end = split_version(cmake_text)
version_parts[0] = str(int(version_parts[0]) + 1)
return recombine(cmake_text, version_parts, begin, end)
def inc_minor(cmake_text):
version_parts, begin, end = split_version(cmake_text)
version_parts[1] = str(int(version_parts[1]) + 1)
return recombine(cmake_text, version_parts, begin, end)
def inc_patch(cmake_text):
version_parts, begin, end = split_version(cmake_text)
version_parts[2] = str(int(version_parts[2]) + 1)
return recombine(cmake_text, version_parts, begin, end)
if type.startswith('M'):
print("Selected major")
write_cmake(inc_major(load_cmake()))
elif type.startswith('m'):
print("Selected minor")
write_cmake(inc_minor(load_cmake()))
elif type.startswith('p') or type.startswith('P') or len(type) == 0:
print("Selected patch")
write_cmake(inc_patch(load_cmake()))
#subprocess.call("./py_commit_helper.sh")
subprocess.call("git", "add", "*")
subprocess.call("git", "commit")
subprocess.call("sh -e 'git remote | xargs -L1 git push --all'")

View File

@ -51,4 +51,12 @@
#error Filesystem ops not supported!\
#endif
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define BLT_OS_WINDOWS
#elif defined(__linux__) || defined(__unix__)
#define BLT_OS_LINUX
#else
#define BLT_OS_UNKNOWN
#endif
#endif //BLT_COMPATIBILITY_H

View File

@ -20,7 +20,7 @@
#define BLT_BOXING_H
#include <blt/std/types.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <string>
namespace blt
@ -84,25 +84,6 @@ namespace blt
Logger& logger;
};
template<>
class log_box_t<blt::logging::logger> : detail::log_box_base_t
{
public:
log_box_t(blt::logging::logger logger, std::string_view title, blt::size_t padding = 0): detail::log_box_base_t(title, padding), logger(logger)
{
make_full_title(logger);
logger << '\n';
}
~log_box_t()
{
make_full_width_line(logger);
logger << '\n';
}
private:
blt::logging::logger logger;
};
template<typename Logger>
log_box_t(Logger&& logger, std::string_view, blt::size_t) -> log_box_t<Logger>;

View File

@ -0,0 +1,146 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_FS_BOUNDED_WRITER_H
#define BLT_FS_BOUNDED_WRITER_H
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <blt/fs/fwddecl.h>
#include <blt/std/types.h>
namespace blt::fs
{
inline auto basic_naming_function = [](const size_t invocation, std::string prefix) {
prefix += '-';
prefix += std::to_string(invocation);
prefix += ".txt";
return prefix;
};
using naming_function_t = std::function<std::string(size_t, std::string)>;
class fwriter_t : public writer_t
{
public:
explicit fwriter_t(const std::string& name, std::string mode = "ab"): m_mode(std::move(mode))
{
fwriter_t::newfile(name);
}
// create a writer without creating a new file. Writing without calling newfile is UB
explicit fwriter_t(std::string mode = "ab"): m_mode(std::move(mode))
{}
i64 write(const char* buffer, size_t bytes) override;
virtual void newfile(const std::string& new_name);
void flush() override;
protected:
std::string m_mode;
FILE* m_file = nullptr;
};
// ReSharper disable once CppClassCanBeFinal
class buffered_writer : public fwriter_t
{
public:
explicit buffered_writer(const std::string& name, size_t buffer_size = 1024 * 128);
explicit buffered_writer(size_t buffer_size = 1024 * 128);
i64 write(const char* buffer, size_t bytes) override;
void flush() override;
void newfile(const std::string& new_name) override;
protected:
size_t m_current_pos = 0;
std::vector<char> m_buffer;
};
/**
* Creates a bounded writer where after a specified number of bytes a new file will be opened and written to instead.
*/
// ReSharper disable once CppClassCanBeFinal
class bounded_writer : public fwriter_t
{
public:
explicit bounded_writer(fwriter_t& writer, std::optional<std::string> base_name, size_t max_size = 1024 * 1024 * 10,
naming_function_t naming_function = basic_naming_function);
i64 write(const char* buffer, size_t bytes) override;
void newfile(const std::string& new_name) override;
void flush() override;
private:
fwriter_t* m_writer;
std::optional<std::string> m_base_name;
size_t m_current_invocation = 0;
size_t m_max_size;
size_t m_currently_written = 0;
// inputs: current invocation, then basename string
// returns: name of the file to write to
naming_function_t m_naming_function;
};
struct time_t
{
i32 year = 0, month = 0, day = 1, hour = -1;
time_t(const i32 year, const i32 month, const i32 day, const i32 hour) : year{year}, month{month}, day{day}, hour{hour}
{}
time_t(const i32 year, const i32 month, const i32 day) : year{year}, month{month}, day{day}
{}
time_t() = default;
};
// ReSharper disable once CppClassCanBeFinal
class rotating_writer : public fwriter_t
{
public:
rotating_writer(fwriter_t& writer, time_t period);
i64 write(const char* buffer, size_t bytes) override;
void flush() override;
void newfile(const std::string& new_name) override;
void newfile();
void check_for_time();
static time_t get_current_time();
private:
fwriter_t* m_writer;
time_t m_period;
time_t m_last_time;
};
}
#endif //BLT_FS_BOUNDED_WRITER_H

View File

@ -19,145 +19,15 @@
#ifndef BLT_FILESYSTEM_H
#define BLT_FILESYSTEM_H
#include <fstream>
#include <ios>
#include <iosfwd>
#include <sstream>
#include <blt/fs/fwddecl.h>
#include <blt/fs/file_writers.h>
#include <blt/fs/stream_wrappers.h>
namespace blt::fs
{
/**
* A simple interface which provides a way of reading the next block of data from a resource.
* The interface provides a single function "read" which will read a specified number of bytes into the buffer.
* The implementation for this could be fstreams, zlib deflate, or any method filesystem access.
* Reading of a large number of bytes ( > block size) is guaranteed to not significantly increase the read time and will likely result in a
* direct passthrough to the underlying system. Small reads will be buffered, hence the name "block" reader.
*/
class block_reader
{
protected:
// 32768 block size seems the fastest on my system
unsigned long m_bufferSize;
public:
explicit block_reader(size_t bufferSize): m_bufferSize(bufferSize)
{}
/**
* Reads bytes from the internal filesystem implementation
* @param buffer buffer to copy the read bytes into
* @param bytes number of bytes to read
* @return status code. non-zero return codes indicates a failure has occurred.
*/
virtual int read(char* buffer, size_t bytes) = 0;
virtual size_t gcount() = 0;
virtual char get()
{
char c[1];
read(c, 1);
return c[0];
}
};
/**
* A buffered block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem.
*/
class block_writer
{
protected:
unsigned long m_bufferSize;
public:
explicit block_writer(unsigned long bufferSize): m_bufferSize(bufferSize)
{}
/**
* Writes the bytes to the filesystem backend implementation
* @param buffer bytes to write
* @param bytes number of bytes to write
* @return non-zero code if failure
*/
virtual int write(char* buffer, size_t bytes) = 0;
virtual int put(char c)
{
char a[1];
a[0] = c;
return write(a, 1);
}
/**
* Ensures that the internal buffer is written to the filesystem.
*/
virtual void flush() = 0;
};
/**
* fstream implementation of the block reader.
*/
class fstream_block_reader : public block_reader
{
private:
std::fstream& m_stream;
char* m_buffer = nullptr;
size_t readIndex = 0;
public:
explicit fstream_block_reader(std::fstream& stream, size_t bufferSize = 131072);
explicit fstream_block_reader(fstream_block_reader& copy) = delete;
explicit fstream_block_reader(fstream_block_reader&& move) = delete;
fstream_block_reader& operator=(const fstream_block_reader& copy) = delete;
fstream_block_reader& operator=(const fstream_block_reader&& move) = delete;
int read(char* buffer, size_t bytes) override;
size_t gcount() final
{
return m_stream.gcount();
}
~fstream_block_reader()
{
delete[] m_buffer;
}
};
class fstream_block_writer : public block_writer
{
private:
std::fstream& m_stream;
char* m_buffer;
size_t writeIndex = 0;
void flush_internal();
public:
explicit fstream_block_writer(std::fstream& stream, size_t bufferSize = 131072):
block_writer(bufferSize), m_stream(stream), m_buffer(new char[bufferSize])
{}
explicit fstream_block_writer(fstream_block_writer& copy) = delete;
explicit fstream_block_writer(fstream_block_writer&& move) = delete;
fstream_block_writer& operator=(const fstream_block_writer& copy) = delete;
fstream_block_writer& operator=(const fstream_block_writer&& move) = delete;
int write(char* buffer, size_t bytes) override;
inline void flush() override
{
flush_internal();
}
~fstream_block_writer()
{
flush_internal();
delete[] m_buffer;
}
};
}
#endif //BLT_FILESYSTEM_H

77
include/blt/fs/fwddecl.h Normal file
View File

@ -0,0 +1,77 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_FS_FWDDECL_H
#define BLT_FS_FWDDECL_H
#include <blt/std/types.h>
namespace blt::fs
{
/**
* A simple interface which provides a way of reading the next block of data from a resource. This is designed to replace the overly complex
* std::ostream
*/
class reader_t
{
public:
virtual ~reader_t() = default;
explicit reader_t() = default;
reader_t(const reader_t&) = delete;
reader_t& operator=(const reader_t&) = delete;
/**
* Reads bytes from the internal filesystem implementation
* @param buffer buffer to copy the read bytes into
* @param bytes number of bytes to read
* @return number of bytes read, or negative value if error. Errors are not required and can just return 0
*/
virtual i64 read(char* buffer, size_t bytes) = 0;
};
/**
* A block writer without a definite backend implementation. Exactly the same as a block_reader but for writing to the filesystem.
* this is designed to replace the overly complex std::istream
*/
class writer_t
{
public:
virtual ~writer_t() = default;
explicit writer_t() = default;
writer_t(const writer_t&) = delete;
writer_t& operator=(const writer_t&) = delete;
/**
* Writes the bytes to the filesystem backend implementation
* @param buffer bytes to write
* @param bytes number of bytes to write
* @return number of bytes, or negative value if error. Zero is also a valid return, not indicating error in itself but can be the result of one.
*/
virtual i64 write(const char* buffer, size_t bytes) = 0;
/**
* Optional flush command which syncs the underlying objects
*/
virtual void flush()
{};
};
}
#endif //BLT_FS_FWDDECL_H

View File

@ -13,7 +13,7 @@
#include <unordered_map>
#include <string>
#include <blt/std/string.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <unordered_set>
namespace blt::fs

View File

@ -15,26 +15,26 @@
#include "blt/format/format.h"
#include "blt/fs/filesystem.h"
#include "blt/std/logging.h"
#include "blt/logging/logging.h"
#include "blt/std/memory.h"
#include <blt/std/hashmap.h>
namespace blt::nbt {
void writeUTF8String(blt::fs::block_writer& stream, const std::string& str);
void writeUTF8String(blt::fs::writer_t& stream, const std::string& str);
std::string readUTF8String(blt::fs::block_reader& stream);
std::string readUTF8String(blt::fs::reader_t& stream);
template<typename T>
inline static void writeData(blt::fs::block_writer& out, const T& d){
inline static void writeData(blt::fs::writer_t& out, const T& d){
char data[sizeof(T)];
mem::toBytes(d, data);
out.write(data, sizeof(T));
}
template<typename T>
inline static void readData(blt::fs::block_reader& in, T& d) {
inline static void readData(blt::fs::reader_t& in, T& d) {
char data[sizeof(T)];
in.read(data, sizeof(T));
mem::fromBytes(data, &d);
@ -63,12 +63,12 @@ namespace blt::nbt {
public:
explicit tag_t(nbt_tag type): type(type) {};
explicit tag_t(nbt_tag type, std::string name): type(type), name(std::move(name)) {}
virtual void writePayload(blt::fs::block_writer& out) = 0;
virtual void readPayload(blt::fs::block_reader& in) = 0;
void writeName(blt::fs::block_writer& out) {
virtual void writePayload(blt::fs::writer_t& out) = 0;
virtual void readPayload(blt::fs::reader_t& in) = 0;
void writeName(blt::fs::writer_t& out) {
writeUTF8String(out, name);
}
void readName(blt::fs::block_reader& in) {
void readName(blt::fs::reader_t& in) {
name = readUTF8String(in);
}
[[nodiscard]] inline nbt_tag getType() const {
@ -87,11 +87,11 @@ namespace blt::nbt {
public:
explicit tag(nbt_tag type): tag_t(type) {}
tag(nbt_tag type, std::string name, T t): tag_t(type, std::move(name)), t(std::move(t)) {}
void writePayload(blt::fs::block_writer& out) override {
void writePayload(blt::fs::writer_t& out) override {
if constexpr(std::is_arithmetic<T>::value)
writeData(out, t);
}
void readPayload(blt::fs::block_reader& in) override {
void readPayload(blt::fs::reader_t& in) override {
if constexpr(std::is_arithmetic<T>::value)
readData(in, t);
}
@ -102,9 +102,9 @@ namespace blt::nbt {
class tag_end : public tag<char> {
public:
void writePayload(blt::fs::block_writer&) final {}
void writePayload(blt::fs::writer_t&) final {}
// nothing to read
void readPayload(blt::fs::block_reader&) final {}
void readPayload(blt::fs::reader_t&) final {}
};
class tag_byte : public tag<int8_t> {
@ -147,13 +147,13 @@ namespace blt::nbt {
public:
tag_byte_array(): tag(nbt_tag::BYTE_ARRAY) {}
tag_byte_array(const std::string& name, const std::vector<int8_t>& v): tag(nbt_tag::BYTE_ARRAY, name, v) {}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
auto length = (int32_t) t.size();
writeData(out, length);
// TODO on the writer (remove need for cast + more std::fstream functions)
out.write(reinterpret_cast<char*>(t.data()), length);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
int32_t length;
readData(in, length);
t.reserve(length);
@ -165,10 +165,10 @@ namespace blt::nbt {
public:
tag_string(): tag(nbt_tag::STRING) {}
tag_string(const std::string& name, const std::string& s): tag(nbt_tag::STRING, name, s) {}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
writeUTF8String(out, t);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
t = readUTF8String(in);
}
};
@ -177,13 +177,13 @@ namespace blt::nbt {
public:
tag_int_array(): tag(nbt_tag::INT_ARRAY) {}
tag_int_array(const std::string& name, const std::vector<int32_t>& v): tag(nbt_tag::INT_ARRAY, name, v) {}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
auto length = (int32_t) t.size();
writeData(out, length);
for (int i = 0; i < length; i++)
writeData(out, t[i]);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
int32_t length;
readData(in, length);
t.reserve(length);
@ -196,13 +196,13 @@ namespace blt::nbt {
public:
tag_long_array(): tag(nbt_tag::LONG_ARRAY) {}
tag_long_array(const std::string& name, const std::vector<int64_t>& v): tag(nbt_tag::LONG_ARRAY, name, v) {}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
auto length = (int32_t) t.size();
writeData(out, length);
for (int i = 0; i < length; i++)
writeData(out, t[i]);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
int32_t length;
readData(in, length);
t.reserve(length);
@ -262,7 +262,7 @@ namespace blt::nbt {
public:
tag_list(): tag(nbt_tag::LIST) {}
tag_list(const std::string& name, const std::vector<tag_t*>& v): tag(nbt_tag::LIST, name, v) {}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
if (t.empty())
writeData(out, (char)nbt_tag::END);
else
@ -273,7 +273,7 @@ namespace blt::nbt {
v->writePayload(out);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
char id;
int32_t length;
readData(in, id);
@ -305,7 +305,7 @@ namespace blt::nbt {
auto& tag = t[i];
T t;
if (tag->getType() != t.getType()) {
BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType());
BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType());
throw std::runtime_error("Requested Tag does not match stored type!");
}
return dynamic_cast<T*>(tag);
@ -342,7 +342,7 @@ namespace blt::nbt {
auto& tag = t[name];
T t;
if (tag->getType() != t.getType()) {
BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType());
BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType());
throw std::runtime_error("Requested Tag does not match stored type!");
}
return dynamic_cast<T*>(tag);
@ -356,18 +356,20 @@ namespace blt::nbt {
t[tag->getName()] = tag;
}
void writePayload(blt::fs::block_writer& out) final {
void writePayload(blt::fs::writer_t& out) final {
for (const auto& v : t){
auto tag = v.second;
out.put((char) tag->getType());
auto c = (char) tag->getType();
out.write(&c, 1);
tag->writeName(out);
tag->writePayload(out);
}
out.put('\0');
const char c = '\0';
out.write(&c, 1);
}
void readPayload(blt::fs::block_reader& in) final {
void readPayload(blt::fs::reader_t& in) final {
char type;
while ((type = in.get()) != (char)nbt_tag::END){
while ((in.read(&type, 1), type) != (char)nbt_tag::END){
auto* v = _internal_::toType(type);
v->readName(in);
v->readPayload(in);
@ -390,10 +392,10 @@ namespace blt::nbt {
class NBTReader {
private:
blt::fs::block_reader& reader;
blt::fs::reader_t& reader;
tag_compound* root = nullptr;
public:
explicit NBTReader(blt::fs::block_reader& reader): reader(reader) {}
explicit NBTReader(blt::fs::reader_t& reader): reader(reader) {}
void read();
@ -407,7 +409,7 @@ namespace blt::nbt {
auto& tag = root->get()[name];
T t;
if (tag->getType() != t.getType()) {
BLT_WARN("Expected tag of type %d but got tag of type %d", (char)t.getType(), (char)tag->getType());
BLT_WARN("Expected tag of type {:d} but got tag of type {:d}", (char)t.getType(), (char)tag->getType());
throw std::runtime_error("Requested Tag does not match stored type!");
}
return dynamic_cast<T*>(tag);
@ -419,9 +421,9 @@ namespace blt::nbt {
class NBTWriter {
private:
blt::fs::block_writer& writer;
blt::fs::writer_t& writer;
public:
explicit NBTWriter(blt::fs::block_writer& writer): writer(writer) {}
explicit NBTWriter(blt::fs::writer_t& writer): writer(writer) {}
/**
* Write a compound tag and then DELETES the tag. If you don't wish for the memory to be freed, please use the reference version!
* @param root root NBT tag to write, this function assumes ownership of this pointer.
@ -431,7 +433,8 @@ namespace blt::nbt {
delete root;
}
void write(tag_compound& root){
writer.put((char)nbt_tag::COMPOUND);
auto c = (char)nbt_tag::COMPOUND;
writer.write(&c, 1);
root.writeName(writer);
root.writePayload(writer);
}

View File

@ -0,0 +1,39 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_FS_PATH_HELPER_H
#define BLT_FS_PATH_HELPER_H
#include <string>
#include <string_view>
namespace blt::fs
{
std::string base_name(const std::string& str);
std::string_view base_name_sv(std::string_view str);
std::string filename(const std::string& str);
std::string_view filename_sv(std::string_view str);
std::string extension(const std::string& str);
std::string_view extension_sv(std::string_view str);
}
#endif //BLT_FS_PATH_HELPER_H

View File

@ -0,0 +1,143 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_FS_STREAM_WRAPPERS_H
#define BLT_FS_STREAM_WRAPPERS_H
#include <iosfwd>
#include <sstream>
#include <blt/fs/fwddecl.h>
namespace blt::fs
{
/**
* reader_t wrapper for fstream
*/
class fstream_reader_t final : public reader_t
{
public:
explicit fstream_reader_t(std::istream& stream);
explicit fstream_reader_t(fstream_reader_t& copy) = delete;
fstream_reader_t& operator=(const fstream_reader_t& copy) = delete;
i64 read(char* buffer, size_t bytes) override;
private:
std::istream* m_stream;
};
class fstream_writer_t final : public writer_t
{
public:
explicit fstream_writer_t(std::ostream& stream);
explicit fstream_writer_t(fstream_writer_t& copy) = delete;
fstream_writer_t& operator=(const fstream_writer_t& copy) = delete;
i64 write(const char* buffer, size_t bytes) override;
void flush() override;
virtual ~fstream_writer_t() override // NOLINT
{
flush();
}
private:
std::ostream* m_stream;
};
class reader_wrapper_t
{
public:
explicit reader_wrapper_t(reader_t& reader): m_reader(&reader)
{}
template <typename T>
void read(T& out)
{
if (!m_reader->read(reinterpret_cast<char*>(&out), sizeof(T)))
throw std::runtime_error("Failed to read from reader");
}
template <typename T>
friend reader_wrapper_t& operator>>(reader_wrapper_t& reader, T& t)
{
reader.read(t);
return reader;
}
private:
reader_t* m_reader;
};
class writer_wrapper_t
{
public:
explicit writer_wrapper_t(writer_t& writer): m_writer(&writer)
{}
template <typename T>
void write(const T& t)
{
static_assert(std::is_trivially_copyable_v<T>);
m_writer->write(reinterpret_cast<const char*>(&t), sizeof(T));
}
template <typename T>
friend writer_wrapper_t& operator<<(writer_wrapper_t& writer, const T& t)
{
writer.write(t);
return writer;
}
private:
writer_t* m_writer;
};
class writer_string_wrapper_t
{
public:
explicit writer_string_wrapper_t(writer_t& writer): m_writer(&writer)
{}
template <typename T>
void write(const T& t)
{
std::stringstream ss;
ss << t;
const auto str = ss.str();
m_writer->write(str.data(), str.size());
}
template <typename T>
friend writer_string_wrapper_t& operator<<(writer_string_wrapper_t& writer, const T& t)
{
writer.write(t);
return writer;
}
private:
writer_t* m_writer;
};
}
#endif //BLT_FS_STREAM_WRAPPERS_H

View File

@ -0,0 +1,42 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_FS_THREADED_WRITERS_H
#define BLT_FS_THREADED_WRITERS_H
#include <mutex>
#include <blt/fs/fwddecl.h>
namespace blt::fs
{
// ReSharper disable once CppClassCanBeFinal
class concurrent_file_writer : public writer_t
{
public:
explicit concurrent_file_writer(writer_t* writer);
i64 write(const char* buffer, size_t bytes) override;
void flush() override;
private:
writer_t* m_writer;
std::mutex m_mutex;
};
}
#endif //BLT_FS_THREADED_WRITERS_H

View File

@ -27,9 +27,42 @@
#include <blt/meta/iterator.h>
#include <functional>
#include <optional>
#include <blt/meta/type_traits.h>
namespace blt::iterator
{
namespace detail
{
template<typename T>
static auto forward_as_tuple(T&& t) -> decltype(auto)
{
using Decay = std::decay_t<T>;
static_assert(!(meta::is_tuple_v<Decay> || meta::is_pair_v<Decay>), "Tuple or pair passed to forward_as_tuple! Must not be a tuple!");
if constexpr (std::is_lvalue_reference_v<T>)
{
return std::forward_as_tuple(std::forward<T>(t));
}
else
{
return std::make_tuple(std::forward<T>(t));
}
}
template <typename Tuple>
static auto ensure_tuple(Tuple&& tuple) -> decltype(auto)
{
using Decay = std::decay_t<Tuple>;
if constexpr (meta::is_tuple_v<Decay> || meta::is_pair_v<Decay>)
{
return std::forward<Tuple>(tuple);
}
else
{
return forward_as_tuple(std::forward<Tuple>(tuple));
}
}
}
template <typename Derived>
struct base_wrapper
{
@ -42,19 +75,21 @@ namespace blt::iterator
base_wrapper operator--(int)
{
static_assert(meta::is_bidirectional_or_better_category_v<typename Derived::iterator_category>, "Iterator must allow random access");
static_assert(meta::is_bidirectional_or_better_category_v<typename Derived::iterator_category>,
"Iterator must allow bidirectional access");
auto tmp = *this;
--*this;
return tmp;
}
auto operator[](blt::ptrdiff_t n) const
auto operator[](ptrdiff_t n) const
{
static_assert(meta::is_random_access_iterator_category_v<typename Derived::iterator_category>, "Iterator must allow random access");
static_assert(meta::is_random_access_iterator_category_v<typename Derived::iterator_category>,
"Iterator must allow bidirectional access");
return *(*this + n);
}
friend base_wrapper operator+(blt::ptrdiff_t n, const base_wrapper& a)
friend base_wrapper operator+(ptrdiff_t n, const base_wrapper& a)
{
return a + n;
}
@ -95,9 +130,8 @@ namespace blt::iterator
};
template <typename Iter, typename Derived, bool dereference = false>
struct passthrough_wrapper : public base_wrapper<Derived>
struct passthrough_wrapper : base_wrapper<Derived>
{
public:
explicit passthrough_wrapper(Iter iter): iter(std::move(iter))
{
}
@ -107,7 +141,7 @@ namespace blt::iterator
return iter;
}
friend blt::ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b)
friend ptrdiff_t operator-(const passthrough_wrapper& a, const passthrough_wrapper& b)
{
return a.base() - b.base();
}
@ -117,7 +151,7 @@ namespace blt::iterator
};
template <typename Iter, typename Derived>
struct passthrough_wrapper<Iter, Derived, true> : public passthrough_wrapper<Iter, Derived>
struct passthrough_wrapper<Iter, Derived, true> : passthrough_wrapper<Iter, Derived>
{
using passthrough_wrapper<Iter, Derived>::passthrough_wrapper;
@ -215,6 +249,54 @@ namespace blt::iterator
Pred func;
};
template <typename Iter>
class const_wrapper : public deref_only_wrapper<Iter, const_wrapper<Iter>>
{
public:
using ref_return = meta::deref_return_t<Iter>;
using iterator_category = typename std::iterator_traits<Iter>::iterator_category;
using value_type = std::conditional_t<std::is_reference_v<ref_return>, std::remove_reference_t<ref_return>, ref_return>;
using difference_type = ptrdiff_t;
using pointer = const value_type*;
using reference = const value_type&;
explicit const_wrapper(Iter iter): deref_only_wrapper<Iter, const_wrapper>(std::move(iter))
{
}
template<typename T>
static auto make_const(T&& value) -> decltype(auto)
{
using Decay = std::decay_t<T>;
if constexpr (std::is_lvalue_reference_v<T>)
{
return const_cast<const Decay&>(value);
} else
{
return static_cast<const Decay>(value);
}
}
auto operator*() const
{
if constexpr (std::is_reference_v<ref_return>)
{
return const_cast<const value_type&>(*this->iter);
} else if constexpr (meta::is_tuple_v<value_type> || meta::is_pair_v<value_type>)
{
return std::apply([](auto&&... args)
{
return std::tuple<decltype(make_const(std::forward<decltype(args)>(args)))...>{make_const(std::forward<decltype(args)>(args))...};
}, *this->iter);
}
else
{
return *this->iter;
}
}
};
namespace impl
{
template <typename Derived>
@ -382,6 +464,30 @@ namespace blt::iterator
return enumerate_iterator_container{begin(), end(), static_cast<blt::size_t>(std::distance(begin(), end()))};
}
auto flatten() const
{
return iterator_container<flatten_wrapper<IterBase, false>>{
blt::iterator::flatten_wrapper<IterBase, false>{m_begin},
blt::iterator::flatten_wrapper<IterBase, false>{m_end}
};
}
auto flatten_all() const
{
return iterator_container<flatten_wrapper<IterBase, true>>{
blt::iterator::flatten_wrapper<IterBase, true>{m_begin},
blt::iterator::flatten_wrapper<IterBase, true>{m_end}
};
}
auto as_const() const
{
return iterator_container<const_wrapper<IterBase>>{
const_wrapper<IterBase>{m_begin},
const_wrapper<IterBase>{m_end}
};
}
template <typename Func>
auto map(Func func) const
{

View File

@ -20,120 +20,112 @@
#define BLT_ITERATOR_ENUMERATE_H
#include <blt/iterator/common.h>
#include <tuple>
namespace blt
{
namespace iterator
{
/**
* struct which is returned by the enumerator.
* @tparam T type to store.
*/
template<typename T>
struct enumerate_item
{
blt::size_t index;
T value;
};
template<typename Iter>
template <typename Iter>
class enumerate_wrapper : public passthrough_wrapper<Iter, enumerate_wrapper<Iter>>
{
public:
enumerate_wrapper(blt::size_t index, Iter iter): passthrough_wrapper<Iter, enumerate_wrapper<Iter>>(std::move(iter)), index(index)
{}
public:
enumerate_wrapper(const size_t index, Iter iter): passthrough_wrapper<Iter, enumerate_wrapper<Iter>>(std::move(iter)), index(index)
{
}
using iterator_category = typename std::iterator_traits<Iter>::iterator_category;
using value_type = enumerate_item<meta::deref_return_t<Iter>>;
using difference_type = blt::ptrdiff_t;
using pointer = value_type;
using reference = value_type;
using iterator_category = typename std::iterator_traits<Iter>::iterator_category;
using value_type = enumerate_item<meta::deref_return_t<Iter>>;
using difference_type = blt::ptrdiff_t;
using pointer = value_type;
using reference = value_type;
enumerate_item<meta::deref_return_t<Iter>> operator*() const
{
return {index, *this->iter};
}
enumerate_item<meta::deref_return_t<Iter>> operator*() const
{
return {index, *this->iter};
}
enumerate_wrapper& operator++()
{
++index;
++this->iter;
return *this;
}
enumerate_wrapper& operator++()
{
++index;
++this->iter;
return *this;
}
enumerate_wrapper& operator--()
{
--index;
--this->iter;
return *this;
}
enumerate_wrapper& operator--()
{
--index;
--this->iter;
return *this;
}
friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n)
{
static_assert(meta::is_random_access_iterator_v<Iter>, "Iterator must allow random access");
auto copy = a;
copy.index += n;
copy.iter = copy.iter + n;
return copy;
}
friend enumerate_wrapper operator+(const enumerate_wrapper& a, blt::ptrdiff_t n)
{
static_assert(meta::is_random_access_iterator_v<Iter>, "Iterator must allow random access");
auto copy = a;
copy.index += n;
copy.iter = copy.iter + n;
return copy;
}
friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n)
{
static_assert(meta::is_random_access_iterator_v<Iter>, "Iterator must allow random access");
auto copy = a;
copy.index -= n;
copy.iter = copy.iter - n;
return copy;
}
friend enumerate_wrapper operator-(const enumerate_wrapper& a, blt::ptrdiff_t n)
{
static_assert(meta::is_random_access_iterator_v<Iter>, "Iterator must allow random access");
auto copy = a;
copy.index -= n;
copy.iter = copy.iter - n;
return copy;
}
private:
blt::size_t index;
private:
blt::size_t index;
};
}
template<typename Iter>
class enumerate_iterator_container : public iterator::iterator_container<iterator::enumerate_wrapper<Iter>>
template <typename Iter>
class enumerate_iterator_container : public blt::iterator::iterator_container<blt::iterator::enumerate_wrapper<Iter>>
{
public:
using iterator::iterator_container<iterator::enumerate_wrapper<Iter>>::iterator_container;
public:
using blt::iterator::iterator_container<blt::iterator::enumerate_wrapper<Iter>>::iterator_container;
enumerate_iterator_container(Iter begin, Iter end, blt::size_t size):
iterator::iterator_container<iterator::enumerate_wrapper<Iter>>(
iterator::enumerate_wrapper<Iter>{0, std::move(begin)}, iterator::enumerate_wrapper<Iter>{size, std::move(end)})
{}
enumerate_iterator_container(Iter begin, Iter end, blt::size_t size):
blt::iterator::iterator_container<blt::iterator::enumerate_wrapper<Iter>>(
blt::iterator::enumerate_wrapper<Iter>{0, std::move(begin)}, blt::iterator::enumerate_wrapper<Iter>{size, std::move(end)})
{
}
};
template<typename Iter>
template <typename Iter>
enumerate_iterator_container(Iter, Iter, blt::size_t) -> enumerate_iterator_container<Iter>;
template<typename T>
template <typename T>
static inline auto enumerate(T& container)
{
return enumerate_iterator_container{container.begin(), container.end(),
static_cast<blt::size_t>(std::distance(container.begin(), container.end()))};
return enumerate_iterator_container{
container.begin(), container.end(),
static_cast<blt::size_t>(std::distance(container.begin(), container.end()))
};
}
template<typename T>
template <typename T>
static inline auto enumerate(const T& container)
{
return enumerate_iterator_container{container.begin(), container.end(),
static_cast<blt::size_t>(std::distance(container.begin(), container.end()))};
return enumerate_iterator_container{
container.begin(), container.end(),
static_cast<blt::size_t>(std::distance(container.begin(), container.end()))
};
}
template<typename T, blt::size_t size>
static inline auto enumerate(const T(& container)[size])
template <typename T, blt::size_t size>
static inline auto enumerate(const T (&container)[size])
{
return enumerate_iterator_container{&container[0], &container[size], size};
}
template<typename T, blt::size_t size>
static inline auto enumerate(T(& container)[size])
template <typename T, blt::size_t size>
static inline auto enumerate(T (&container)[size])
{
return enumerate_iterator_container{&container[0], &container[size], size};
}
}
#endif //BLT_ITERATOR_ENUMERATE_H

View File

@ -0,0 +1,89 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_ITERATOR_FLATTEN_H
#define BLT_ITERATOR_FLATTEN_H
#include <blt/iterator/common.h>
#include <blt/meta/type_traits.h>
#include <tuple>
namespace blt::iterator
{
namespace detail
{
template <typename Tuple>
static auto flatten_recursive(Tuple&& tuple) -> decltype(auto)
{
using Decay = std::decay_t<Tuple>;
if constexpr (meta::is_tuple_v<Decay> || meta::is_pair_v<Decay>)
{
return std::apply([](auto&&... args)
{
return std::tuple_cat(flatten_recursive(std::forward<decltype(args)>(args))...);
}, std::forward<Tuple>(tuple));
}
else
{
return forward_as_tuple(std::forward<Tuple>(tuple));
}
}
template <typename Tuple>
static auto flatten(Tuple&& tuple) -> decltype(auto)
{
return std::apply([](auto&&... args)
{
return std::tuple_cat(ensure_tuple(std::forward<decltype(args)>(args))...);
}, std::forward<Tuple>(tuple));
}
}
template <typename Iter, bool Recursive>
class flatten_wrapper : public deref_only_wrapper<Iter, flatten_wrapper<Iter, Recursive>>
{
public:
using iterator_category = typename std::iterator_traits<Iter>::iterator_category;
using value_type = std::conditional_t<Recursive,
std::remove_reference_t<decltype(detail::flatten_recursive(*std::declval<Iter>()))>,
std::remove_reference_t<decltype(detail::flatten(*std::declval<Iter>()))>>;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
explicit flatten_wrapper(Iter iter):
deref_only_wrapper<Iter, flatten_wrapper>(std::move(iter))
{
}
auto operator*() const -> decltype(auto)
{
if constexpr (Recursive)
{
return detail::flatten_recursive(*this->iter);
}
else
{
return detail::flatten(*this->iter);
}
}
};
}
#endif //BLT_ITERATOR_FLATTEN_H

View File

@ -19,6 +19,8 @@
#ifndef BLT_ITERATOR_FWDDECL_H
#define BLT_ITERATOR_FWDDECL_H
#include <utility>
namespace blt
{
template<typename... Iter>
@ -33,7 +35,7 @@ namespace blt
struct iterator_pair;
template<typename T>
struct enumerate_item;
using enumerate_item = std::pair<size_t, T>;
template<typename Iter>
class enumerate_wrapper;
@ -47,6 +49,12 @@ namespace blt
template<typename Iter, typename Pred>
class filter_wrapper;
template<typename Iter, bool Recursive>
class flatten_wrapper;
template<typename Iter>
class const_wrapper;
namespace impl
{
template<typename Derived>

View File

@ -22,6 +22,7 @@
#include <blt/iterator/common.h>
#include <blt/iterator/zip.h>
#include <blt/iterator/enumerate.h>
#include <blt/iterator/flatten.h>
#include <type_traits>
#include <iterator>
#include <tuple>

View File

@ -21,6 +21,7 @@
#include <blt/iterator/common.h>
#include <tuple>
#include <limits>
namespace blt
{
@ -112,11 +113,11 @@ namespace blt
class zip_iterator_container : public iterator::iterator_container<iterator::zip_wrapper<Iter...>>
{
public:
using iterator::iterator_container<iterator::zip_wrapper<Iter...>>::iterator_container;
using blt::iterator::iterator_container<blt::iterator::zip_wrapper<Iter...>>::iterator_container;
explicit zip_iterator_container(iterator::iterator_pair<Iter>... iterator_pairs):
iterator::iterator_container<iterator::zip_wrapper<Iter...>>(iterator::zip_wrapper<Iter...>{std::move(iterator_pairs.begin)...},
iterator::zip_wrapper<Iter...>{std::move(iterator_pairs.end)...})
explicit zip_iterator_container(blt::iterator::iterator_pair<Iter>... iterator_pairs):
blt::iterator::iterator_container<blt::iterator::zip_wrapper<Iter...>>(blt::iterator::zip_wrapper<Iter...>{std::move(iterator_pairs.begin)...},
blt::iterator::zip_wrapper<Iter...>{std::move(iterator_pairs.end)...})
{}
};

353
include/blt/logging/ansi.h Normal file
View File

@ -0,0 +1,353 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_COLORS_H
#define BLT_LOGGING_COLORS_H
#include <array>
#include <stdexcept>
#include <string>
#include <variant>
#include <blt/std/types.h>
#define BLT_ANSI_ESCAPE "\x1B"
#define BLT_ANSI_CSI BLT_ANSI_ESCAPE "["
#define BLT_ANSI_DSC BLT_ANSI_ESCAPE "P"
#define BLT_ANSI_OSC BLT_ANSI_ESCAPE "]"
// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
namespace blt::logging::ansi
{
namespace color
{
inline std::array<u8, 10> reset_sequences = {0, 22, 22, 23, 24, 25, 26, 27, 28, 29};
enum class color_mode : u8
{
RESET_ALL = 0,
BOLD = 1,
DIM = 2,
ITALIC = 3,
UNDERLINE = 4,
BLINK = 5,
REVERSE = 7,
HIDDEN = 8,
STRIKE_THROUGH = 9, };
enum class color8 : u8
{
BLACK = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4,
MAGENTA = 5,
CYAN = 6,
WHITE = 7,
DEFAULT = 9
};
enum class color8_bright : u8
{
BLACK = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4,
MAGENTA = 5,
CYAN = 6,
WHITE = 7
};
struct rgb_t
{
u8 r, g, b;
};
struct color256
{
explicit color256(u8 index) : color(index)
{}
color256(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b})
{
if (r > 5)
throw std::invalid_argument("r must be between 0 and 5");
if (g > 5)
throw std::invalid_argument("g must be between 0 and 5");
if (b > 5)
throw std::invalid_argument("b must be between 0 and 5");
}
[[nodiscard]] u8 index() const
{
if (std::holds_alternative<u8>(color))
return std::get<u8>(color);
const auto [r, g, b] = std::get<rgb_t>(color);
return (r * 36) + (g * 6) + b + 16;
}
private:
std::variant<u8, rgb_t> color;
};
struct color_rgb
{
color_rgb(const u8 r, const u8 g, const u8 b) : color(rgb_t{r, g, b})
{}
rgb_t color;
};
namespace detail
{
template <typename T>
struct color_holder
{
using value_type = T;
T color;
bool alt = false;
};
template <typename T>
struct color_converter
{};
template <>
struct color_converter<color8>
{
static std::string to_string(const color_holder<color8> color)
{
return (color.alt ? "4" : "3") + std::to_string(static_cast<u8>(color.color));
}
};
template <>
struct color_converter<color8_bright>
{
static std::string to_string(const color_holder<color8_bright> color)
{
return (color.alt ? "10" : "9") + std::to_string(static_cast<u8>(color.color));
}
};
template <>
struct color_converter<color_mode>
{
static std::string to_string(const color_holder<color_mode> color)
{
return color.alt ? std::to_string(reset_sequences[static_cast<u8>(color.color)]) : std::to_string(static_cast<u8>(color.color));
}
};
template <>
struct color_converter<color256>
{
static std::string to_string(const color_holder<color256> color)
{
return (color.alt ? "48;5;" : "38;5;") + std::to_string(color.color.index());
}
};
template <>
struct color_converter<color_rgb>
{
static std::string to_string(const color_holder<color_rgb> color)
{
return (color.alt ? "48;2;" : "38;2;") + std::to_string(color.color.color.r) + ";" + std::to_string(color.color.color.g) + ";" +
std::to_string(color.color.color.b);
}
};
template <typename T>
struct ensure_holder
{
static color_holder<T> make(const T& color)
{
return color_holder<T>{color, false};
}
};
template <typename T>
struct ensure_holder<color_holder<T>>
{
static color_holder<T> make(const color_holder<T>& color)
{
return color;
}
};
template<typename T>
struct decay
{
using type = std::decay_t<T>;
};
template<typename T>
struct decay<color_holder<T>>
{
using type = std::decay_t<T>;
};
template<typename T>
using decay_t = typename decay<T>::type;
}
template <typename T>
auto fg(const T& color)
{
return detail::color_holder<T>{color, false};
}
template <typename T>
auto bg(const T& color)
{
return detail::color_holder<T>{color, true};
}
template <typename... Args>
std::string build(const Args&... args)
{
std::string result = BLT_ANSI_CSI;
((result += detail::color_converter<detail::decay_t<Args>>::to_string(detail::ensure_holder<Args>::make(args)), result += ';'), ...);
return result.substr(0, result.size() - 1) + "m";
}
}
namespace general
{
inline const std::string bell = "\x07";
inline const std::string bs = "\x08";
inline const std::string horizontal_tab = "\x09";
inline const std::string linefeed = "\x0A";
inline const std::string vertical_tab = "\x0B";
inline const std::string form_feed = "\x0C";
inline const std::string carriage_return = "\x0D";
inline const std::string escape = BLT_ANSI_ESCAPE;
inline const std::string del = "\x7F";
inline const std::string csi = BLT_ANSI_CSI;
inline const std::string dsc = BLT_ANSI_DSC;
inline const std::string osc = BLT_ANSI_OSC;
}
namespace cursor
{
inline const std::string home = BLT_ANSI_CSI "H";
inline const std::string lower_left_corner = BLT_ANSI_ESCAPE " F";
inline const std::string hide_cursor = BLT_ANSI_CSI "?2 5l";
inline const std::string show_cursor = BLT_ANSI_CSI "?2 5h";
inline const std::string report_position = BLT_ANSI_CSI "6n";
template <bool UseH = true>
std::string move_to(const i64 line, const i64 column)
{
if constexpr (UseH)
return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "H";
else
return std::string(BLT_ANSI_CSI) + std::to_string(line) + ";" + std::to_string(column) + "f";
}
inline std::string move_up(const i64 lines)
{
return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "A";
}
inline std::string move_down(const i64 lines)
{
return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "B";
}
inline std::string move_right(const i64 columns)
{
return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "C";
}
inline std::string move_left(const i64 columns)
{
return std::string(BLT_ANSI_CSI) + std::to_string(columns) + "D";
}
inline std::string move_begin_down(const i64 lines)
{
return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "E";
}
inline std::string move_begin_up(const i64 lines)
{
return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "F";
}
inline std::string move_to(const i64 column)
{
return std::string(BLT_ANSI_CSI) + std::to_string(column) + "G";
}
inline const std::string request_cursor_position = BLT_ANSI_CSI "6n";
inline const std::string move_up_one_line = BLT_ANSI_ESCAPE " M";
inline const std::string save_cursor_position_dec = BLT_ANSI_ESCAPE " 7";
inline const std::string restore_cursor_position_dec = BLT_ANSI_ESCAPE " 8";
inline const std::string save_cursor_position_sco = BLT_ANSI_CSI "s";
inline const std::string restore_cursor_position_sco = BLT_ANSI_CSI "u";
}
namespace scroll
{
inline std::string scroll_up(const int lines)
{
return std::string(BLT_ANSI_CSI) + std::to_string(lines) + "S";
};
}
namespace erase
{
inline const std::string to_end_of_screen = BLT_ANSI_CSI "0J";
inline const std::string from_begin_of_screen = BLT_ANSI_CSI "1J";
inline const std::string entire_screen = BLT_ANSI_CSI "2J";
inline const std::string saved_lines = BLT_ANSI_CSI "3J";
inline const std::string to_end_of_line = BLT_ANSI_CSI "0K";
inline const std::string from_begin_of_line = BLT_ANSI_CSI "1K";
inline const std::string entire_line = BLT_ANSI_CSI "2K";
}
enum class mode : u8
{
mono40x25_text = 0,
color40x25_text = 1,
mono80x25_text = 2,
color80x25_text = 3,
color320x200_4color_graphics = 4,
mono320x200_graphics = 5,
mono640x200_graphics = 6,
line_wrapping = 7,
color320x200_graphics = 13,
color640x200_16color_graphics = 14,
mono640x350_2color_graphics = 15,
color640x350_16color_graphics = 16,
mono640x480_2color_graphics = 17,
color640x480_16color_graphics = 18,
color320_200_256color_graphics = 19
};
inline std::string use_mode(const mode mode)
{
return std::string(BLT_ANSI_CSI) + "=" + std::to_string(static_cast<u8>(mode)) + "h";
}
}
#endif //BLT_LOGGING_COLORS_H

View File

@ -0,0 +1,188 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_FMT_TOKENIZER_H
#define BLT_LOGGING_FMT_TOKENIZER_H
#include <functional>
#include <optional>
#include <string_view>
#include <vector>
#include <blt/std/types.h>
namespace blt::logging
{
enum class fmt_token_type : u8
{
STRING,
NUMBER,
SPACE,
COLON,
DOT,
MINUS,
PLUS,
POUND,
LEFT_CHEVRON,
RIGHT_CHEVRON,
OPEN_BRACKET,
CLOSE_BRACKET,
CARET
};
enum class fmt_align_t : u8
{
LEFT,
CENTER,
RIGHT
};
enum class fmt_sign_t : u8
{
SPACE,
PLUS,
MINUS
};
enum class fmt_type_t : u8
{
BINARY, // 'b'
CHAR, // 'c'
DECIMAL, // 'd'
OCTAL, // 'o'
HEX, // 'x'
HEX_FLOAT, // 'a'
EXPONENT, // 'e'
FIXED_POINT, // 'f'
GENERAL, // 'g'
TYPE, // 't'
UNSPECIFIED // default
};
struct fmt_spec_t
{
i64 arg_id = -1;
i64 width = -1;
i64 precision = -1;
fmt_type_t type = fmt_type_t::UNSPECIFIED;
fmt_sign_t sign = fmt_sign_t::MINUS;
fmt_align_t alignment = fmt_align_t::RIGHT;
std::optional<char> prefix_char;
bool uppercase = false;
bool alternate_form = false;
};
struct fmt_token_t
{
fmt_token_type type;
std::string_view value;
};
class fmt_tokenizer_t
{
public:
explicit fmt_tokenizer_t() = default;
static fmt_token_type get_type(char c);
std::optional<fmt_token_t> next();
std::vector<fmt_token_t> tokenize(std::string_view fmt);
private:
size_t m_pos = 0;
std::string_view m_fmt{};
};
class fmt_parser_t
{
public:
explicit fmt_parser_t(std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>>& handlers): m_handlers(handlers)
{
}
fmt_token_t& peek(const size_t offset)
{
return m_tokens[m_pos + offset];
}
fmt_token_t& peek()
{
return m_tokens[m_pos];
}
[[nodiscard]] bool has_next() const
{
return m_pos < m_tokens.size();
}
[[nodiscard]] bool has_next(const size_t offset) const
{
return (m_pos + offset) < m_tokens.size();
}
[[nodiscard]] fmt_token_t& next()
{
return m_tokens[m_pos++];
}
void consume()
{
++m_pos;
}
void consume(const size_t amount)
{
m_pos += amount;
}
const fmt_spec_t& parse(std::string_view fmt);
private:
static bool is_align_t(fmt_token_type type);
void parse_fmt_field();
void parse_arg_id();
std::string parse_arg_or_number();
void parse_fmt_spec();
void parse_fmt_spec_fill();
void parse_fmt_spec_align();
void parse_fmt_spec_sign();
void parse_fmt_spec_form();
void parse_fmt_spec_width();
void parse_fmt_spec_precision();
void parse_fill();
void parse_align();
void parse_sign();
void parse_form();
void parse_width();
void parse_precision();
void parse_type();
size_t m_pos = 0;
std::vector<fmt_token_t> m_tokens;
fmt_tokenizer_t m_tokenizer;
fmt_spec_t m_spec;
std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>>& m_handlers;
};
}
#endif //BLT_LOGGING_FMT_TOKENIZER_H

View File

@ -0,0 +1,37 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_FWDDECL_H
#define BLT_LOGGING_FWDDECL_H
namespace blt::logging
{
struct logger_t;
enum class fmt_token_type : u8;
enum class fmt_align_t : u8;
enum class fmt_sign_t : u8;
enum class fmt_type_t : u8;
struct fmt_spec_t;
struct fmt_token_t;
class fmt_tokenizer_t;
class fmt_parser_t;
class injector_t;
}
#endif //BLT_LOGGING_FWDDECL_H

View File

@ -0,0 +1,41 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_INJECTOR_H
#define BLT_LOGGING_INJECTOR_H
namespace blt::logging
{
struct injector_output_t
{
std::string new_logging_output;
// should we continue processing the injector call chain?
bool should_continue = true;
// should we log the resulting string at the end of the injector call chain? If false for any injector, it becomes false for all injectors.
bool should_log = true;
};
class injector_t
{
public:
virtual ~injector_t() = default;
virtual injector_output_t inject(const std::string& input) = 0;
};
}
#endif //BLT_LOGGING_INJECTOR_H

View File

@ -0,0 +1,286 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_LOGGING_H
#define BLT_LOGGING_LOGGING_H
#include <cstring>
#include <functional>
#include <string>
#include <string_view>
#include <vector>
#include <blt/logging/fmt_tokenizer.h>
#include <blt/logging/logging_config.h>
#include <blt/meta/is_streamable.h>
#include <blt/std/utility.h>
namespace blt::logging
{
struct logger_t
{
explicit logger_t(std::ostream& stream): m_stream(stream)
{}
template <typename... Args>
std::string log(std::string fmt, Args&&... args)
{
if (fmt.empty())
return fmt;
auto sequence = std::make_integer_sequence<size_t, sizeof...(Args)>{};
m_arg_print_funcs.clear();
m_arg_print_funcs.resize(sizeof...(Args));
create_conv_funcs(sequence, std::forward<Args>(args)...);
compile(std::move(fmt));
process_strings();
return to_string();
}
[[nodiscard]] std::string to_string() const;
private:
template <typename... Args, size_t... Indexes>
void create_conv_funcs(std::integer_sequence<size_t, Indexes...>, Args&&... args)
{
((handle_func<Indexes>(std::forward<Args>(args))), ...);
}
template <size_t index, typename T>
void handle_func(const T& t)
{
m_arg_print_funcs[index] = [&t, this](std::ostream& stream, const fmt_spec_t& type) {
switch (type.sign)
{
case fmt_sign_t::SPACE:
if constexpr (std::is_arithmetic_v<T>)
{
if (type.type != fmt_type_t::BINARY && static_cast<i64>(t) >= 0l)
stream << ' ';
}
break;
case fmt_sign_t::PLUS:
case fmt_sign_t::MINUS:
break;
}
switch (type.type)
{
case fmt_type_t::BINARY:
{
if constexpr (std::is_trivially_copyable_v<T>)
{
// copy bytes of type
char buffer[sizeof(T)];
std::memcpy(buffer, &t, sizeof(T));
// display prefix
if (type.alternate_form)
stream << '0' << (type.uppercase ? 'B' : 'b');
// print bytes
for (size_t i = 0; i < sizeof(T); ++i)
{
// print bits
for (size_t j = 0; j < 8; ++j)
stream << ((buffer[i] & (1 << j)) ? '1' : '0');
// special seperator defined via sign (weird hack, change?)
if (type.sign == fmt_sign_t::SPACE && i != sizeof(T) - 1)
stream << ' ';
}
} else
{
if constexpr (blt::meta::is_streamable_v<T>)
stream << t;
else
stream << "{INVALID TYPE}";
}
break;
}
case fmt_type_t::CHAR:
if constexpr (std::is_arithmetic_v<T> || std::is_convertible_v<T, char>)
{
stream << static_cast<char>(t);
} else
{
if constexpr (blt::meta::is_streamable_v<T>)
stream << t;
else
stream << "{INVALID TYPE}";
}
break;
case fmt_type_t::TYPE:
stream << blt::type_string<T>();
break;
default:
handle_type(stream, type);
if constexpr (blt::meta::is_streamable_v<T>)
{
if constexpr (std::is_same_v<T, char> || std::is_same_v<T, signed char>)
stream << static_cast<int>(t);
else if constexpr (std::is_same_v<T, unsigned char>)
stream << static_cast<unsigned int>(t);
else
stream << t;
} else
stream << "{INVALID TYPE}";
}
};
}
[[nodiscard]] size_t find_ending_brace(size_t begin) const;
void setup_stream(const fmt_spec_t& spec) const;
std::string process_string(std::string_view str);
void process_strings();
static void handle_type(std::ostream& stream, const fmt_spec_t& spec);
static void exponential(std::ostream& stream);
static void fixed(std::ostream& stream);
void compile(std::string fmt);
std::optional<std::pair<size_t, size_t>> consume_to_next_fmt();
std::string m_fmt;
std::ostream& m_stream;
fmt_parser_t m_parser{m_arg_print_funcs};
// normal sections of string
std::vector<std::string_view> m_string_sections;
// processed format specs
std::vector<fmt_spec_t> m_fmt_specs;
std::vector<std::function<void(std::ostream&, const fmt_spec_t&)>> m_arg_print_funcs;
size_t m_last_fmt_pos = 0;
size_t m_arg_pos = 0;
};
void print(std::string str);
void newline();
logger_t& get_global_logger();
logging_config_t& get_global_config();
std::ostream& get_local_stream();
void set_thread_name(const std::string& name);
const std::string& get_thread_name();
template <typename... Args>
void print(std::string fmt, Args&&... args)
{
auto& logger = get_global_logger();
print(logger.log(std::move(fmt), std::forward<Args>(args)...));
}
template <typename... Args>
void print(std::ostream& stream, std::string fmt, Args&&... args)
{
auto& logger = get_global_logger();
stream << logger.log(std::move(fmt), std::forward<Args>(args)...);
}
template <typename... Args>
void println(std::string fmt, Args&&... args)
{
print(std::move(fmt), std::forward<Args>(args)...);
newline();
}
template <typename... Args>
void println(std::ostream& stream, std::string fmt, Args&&... args)
{
print(stream, std::move(fmt), std::forward<Args>(args)...);
stream << std::endl;
}
template <typename... Args>
void log(const log_level_t level, const char* file, const i32 line, std::string fmt, Args&&... args)
{
auto& logger = get_global_logger();
const auto& config = get_global_config();
std::string user_str = logger.log(std::move(fmt), std::forward<Args>(args)...);
if (!user_str.empty() && user_str.back() == '\n')
user_str.pop_back();
if (level == log_level_t::NONE)
{
print(user_str);
newline();
return;
}
auto log_fmt_str = config.generate(user_str, get_thread_name(), level, file, line);
if (log_fmt_str)
print(std::move(*log_fmt_str));
}
namespace detail
{
}
}
#if defined(__clang__) || defined(__llvm__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif
#ifdef BLT_DISABLE_LOGGING
#define BLT_LOG(level, fmt, ...)
#else
#define BLT_LOG(level, fmt, ...) blt::logging::log(level, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
#ifdef BLT_DISABLE_TRACE
#define BLT_TRACE(format, ...)
#else
#define BLT_TRACE(format, ...) BLT_LOG(blt::logging::log_level_t::TRACE, format, ##__VA_ARGS__)
#endif
#ifdef BLT_DISABLE_DEBUG
#define BLT_DEBUG(format, ...)
#else
#define BLT_DEBUG(format, ...) BLT_LOG(blt::logging::log_level_t::DEBUG, format, ##__VA_ARGS__)
#endif
#ifdef BLT_DISABLE_INFO
#define BLT_INFO(format, ...)
#else
#define BLT_INFO(format, ...) BLT_LOG(blt::logging::log_level_t::INFO, format, ##__VA_ARGS__)
#endif
#ifdef BLT_DISABLE_WARN
#define BLT_WARN(format, ...)
#else
#define BLT_WARN(format, ...) BLT_LOG(blt::logging::log_level_t::WARN, format, ##__VA_ARGS__)
#endif
#ifdef BLT_DISABLE_ERROR
#define BLT_ERROR(format, ...)
#else
#define BLT_ERROR(format, ...) BLT_LOG(blt::logging::log_level_t::ERROR, format, ##__VA_ARGS__)
#endif
#ifdef BLT_DISABLE_FATAL
#define BLT_FATAL(format, ...)
#else
#define BLT_FATAL(format, ...) BLT_LOG(blt::logging::log_level_t::FATAL, format, ##__VA_ARGS__)
#endif
#endif
#if defined(__clang__) || defined(__llvm__)
#pragma clang diagnostic pop
#endif
#endif // BLT_LOGGING_LOGGING_H

View File

@ -0,0 +1,243 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_LOGGING_CONFIG_H
#define BLT_LOGGING_LOGGING_CONFIG_H
#include <array>
#include <optional>
#include <string>
#include <vector>
#include <blt/fs/fwddecl.h>
#include <blt/logging/fwddecl.h>
#include <blt/std/types.h>
namespace blt::logging
{
namespace tags
{
// Current Year
inline constexpr auto YEAR = "{YEAR}";
// Current Month
inline constexpr auto MONTH = "{MONTH}";
// Current Day
inline constexpr auto DAY = "{DAY}";
// Current Hour
inline constexpr auto HOUR = "{HOUR}";
// Current Minute
inline constexpr auto MINUTE = "{MINUTE}";
// Current Second
inline constexpr auto SECOND = "{SECOND}";
// Current Millisecond
inline constexpr auto MILLISECOND = "{MS}";
// Current Nanosecond
inline constexpr auto NANOSECOND = "{NS}";
// Current Unix time in milliseconds
inline constexpr auto UNIX_TIME = "{UNIX}";
// Current Unix time in nanosecond
inline constexpr auto UNIX_TIME_NANO = "{UNIX_NANO}";
// Formatted ISO year-month-day in a single variable
inline constexpr auto ISO_YEAR = "{ISO_YEAR}";
// Formatted hour:minute:second in a single variable
inline constexpr auto TIME = "{TIME}";
// Formatted year-month-day hour:minute:second in a single variable
inline constexpr auto FULL_TIME = "{FULL_TIME}";
// Color of the current log level, empty string if use_color = false
inline constexpr auto LOG_COLOR = "{LC}";
// Color of the error color, empty string if use_color = false
inline constexpr auto ERROR_COLOR = "{EC}";
// Empty is use_color = false or if log level is not an error. Otherwise, {EC}
inline constexpr auto CONDITIONAL_ERROR_COLOR = "{CEC}";
// Resets all ANSI sequences
inline constexpr auto RESET = "{RESET}";
// Current log level
inline constexpr auto LOG_LEVEL = "{LL}";
// Current thread name. Requires you to manually set the thread name using blt::logging::set_thread_name() from that thread.
inline constexpr auto THREAD_NAME = "{TN}";
// Current file from where the log call was invoked.
inline constexpr auto FILE = "{FILE}";
// Current line from where the log call was invoked
inline constexpr auto LINE = "{LINE}";
// User string input, formatted with provided args
inline constexpr auto STR = "{STR}";
namespace detail
{
enum class log_tag_token_t : u8
{
YEAR,
MONTH,
DAY,
HOUR,
MINUTE,
SECOND,
MS,
NS,
UNIX,
UNIX_NANO,
ISO_YEAR,
TIME,
FULL_TIME,
LC,
EC,
CEC,
RESET,
LL,
TN,
FILE,
LINE,
STR,
// token used to describe that a non-format token should be consumed. aka a normal string from the file.
CONTENT
};
}
}
enum class log_level_t : u8
{
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NONE
};
inline constexpr size_t LOG_LEVEL_COUNT = 6;
class logging_config_t
{
friend logger_t;
public:
logging_config_t()
{
compile();
}
void compile();
logging_config_t& add_log_output(fs::writer_t& writer)
{
m_log_outputs.push_back(&writer);
return *this;
}
logging_config_t& add_injector(injector_t& injector)
{
m_injectors.push_back(&injector);
return *this;
}
logging_config_t& set_log_format(std::string format)
{
m_log_format = std::move(format);
compile();
return *this;
}
logging_config_t& set_error_color(std::string color)
{
m_error_color = std::move(color);
compile();
return *this;
}
logging_config_t& set_log_level_colors(std::array<std::string, LOG_LEVEL_COUNT> colors)
{
m_log_level_colors = std::move(colors);
compile();
return *this;
}
logging_config_t& set_log_level_names(std::array<std::string, LOG_LEVEL_COUNT> names)
{
m_log_level_names = std::move(names);
return *this;
}
logging_config_t& set_level(const log_level_t level)
{
this->m_level = level;
return *this;
}
logging_config_t& set_use_color(const bool use_color)
{
this->m_use_color = use_color;
compile();
return *this;
}
logging_config_t& set_print_full_name(const bool print_full_name)
{
this->m_print_full_name = print_full_name;
return *this;
}
logging_config_t& set_ensure_alignment(const bool ensure_alignment)
{
this->m_ensure_alignment = ensure_alignment;
return *this;
}
[[nodiscard]] std::pair<const std::vector<tags::detail::log_tag_token_t>&, const std::vector<std::string>&> get_log_tag_tokens() const
{
return {m_log_tag_tokens, m_log_tag_content};
}
std::optional<std::string> generate(const std::string& user_str, const std::string& thread_name, log_level_t level, const char* file,
i32 line) const;
[[nodiscard]] const std::vector<injector_t*>& get_injectors() const
{
return m_injectors;
}
private:
std::vector<injector_t*> m_injectors;
// wrappers for streams exist in blt/fs/stream_wrappers.h
std::vector<fs::writer_t*> m_log_outputs = get_default_log_outputs();
std::string m_log_format = get_default_log_format();
std::string m_error_color = get_default_error_color();
std::array<std::string, LOG_LEVEL_COUNT> m_log_level_colors = get_default_log_level_colors();
std::array<std::string, LOG_LEVEL_COUNT> m_log_level_names = get_default_log_level_names();
log_level_t m_level = log_level_t::TRACE;
bool m_use_color = true;
// if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#)
bool m_print_full_name = false;
// this will attempt to use the maximum possible size for each printed element, then align to that.
// This creates output where the user message always starts at the same column.
bool m_ensure_alignment = true;
size_t m_longest_name_length = 0;
std::vector<std::string> m_log_tag_content;
std::vector<tags::detail::log_tag_token_t> m_log_tag_tokens;
static std::string get_default_log_format();
static std::vector<fs::writer_t*> get_default_log_outputs();
static std::array<std::string, LOG_LEVEL_COUNT> get_default_log_level_colors();
static std::array<std::string, LOG_LEVEL_COUNT> get_default_log_level_names();
static std::string get_default_error_color();
};
}
#endif //BLT_LOGGING_LOGGING_CONFIG_H

View File

@ -0,0 +1,105 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_LOGGING_STATUS_H
#define BLT_LOGGING_STATUS_H
#include <mutex>
#include <string>
#include <blt/logging/injector.h>
#include <blt/math/vectors.h>
#include <blt/std/types.h>
namespace blt::logging
{
class status_bar_t;
class status_item_t
{
public:
virtual ~status_item_t() = default;
void assign(status_bar_t* bar)
{
m_status = bar;
}
[[nodiscard]] virtual i32 lines_used() const
{
return 1;
}
[[nodiscard]] virtual std::string print(vec2i screen_size, i32 max_printed_length) const = 0;
protected:
status_bar_t* m_status = nullptr;
};
class status_progress_bar_t final : public status_item_t
{
public:
[[nodiscard]] std::string print(vec2i screen_size, i32 max_printed_length) const override;
void set_progress(double progress);
void add_progress(const double progress)
{
set_progress(get_progress() + progress);
}
[[nodiscard]] double get_progress() const
{
return m_progress;
}
private:
double m_progress = 0.0;
};
class status_bar_t final : public injector_t
{
public:
explicit status_bar_t();
injector_output_t inject(const std::string& input) override;
status_bar_t& add(status_item_t& item)
{
item.assign(this);
m_status_items.push_back(&item);
compute_size();
return *this;
}
void redraw() const;
~status_bar_t() override;
private:
void compute_size();
std::vector<status_item_t*> m_status_items;
i32 m_status_size = 0;
i32 m_max_printed_length = 0;
vec2i m_screen_size;
vec2i m_last_log_position;
vec2i m_begin_position;
std::mutex m_print_mutex;
};
}
#endif //BLT_LOGGING_STATUS_H

170
include/blt/math/aabb.h Normal file
View File

@ -0,0 +1,170 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_MATH_AABB_H
#define BLT_MATH_AABB_H
#include <array>
#include <blt/math/vectors.h>
#include <blt/std/types.h>
namespace blt
{
// yes I could use the vector (see tower defense game commit log for this)
// this feels nicer
template <typename T = float>
class axis_t
{
public:
axis_t(const T min, const T max): m_min(min), m_max(max)
{}
[[nodiscard]] bool intersects(const T p) const
{
return p >= m_min && p <= m_max;
}
template <typename G>
[[nodiscard]] bool intersects(const axis_t<G>& other) const
{
return static_cast<T>(other.m_min) <= m_max && static_cast<T>(other.m_max) >= m_min;
}
[[nodiscard]] T min() const
{
return m_min;
}
[[nodiscard]] T max() const
{
return m_max;
}
[[nodiscard]] T length() const
{
return m_max - m_min;
}
private:
T m_min, m_max;
};
namespace detail
{
template <u32 Axis, typename T>
class axis_aligned_bounding_box_base_t
{
public:
[[nodiscard]] vec<T, Axis> get_center() const
{
vec<T, Axis> min;
for (u32 i = 0; i < Axis; i++)
min[i] = m_axes[i].min();
const auto center = get_size() / 2.0f;
return min + center;
}
[[nodiscard]] vec<T, Axis> get_size() const
{
vec<T, Axis> size;
for (u32 i = 0; i < Axis; i++)
size[i] = m_axes[i].length();
return size;
}
template <typename G>
[[nodiscard]] bool intersects(const axis_aligned_bounding_box_base_t<Axis, G>& other) const
{
for (u32 i = 0; i < Axis; i++)
if (!m_axes[i].intersects(other.m_axes[i]))
return false;
return true;
}
template <typename G>
[[nodiscard]] bool intersects(const vec<G, Axis>& point) const
{
for (u32 i = 0; i < Axis; i++)
if (!m_axes[i].intersects(point[i]))
return false;
return true;
}
axis_t<T>& operator[](u32 i)
{
return m_axes[i];
}
axis_t<T>& axis(u32 i)
{
if (i >= Axis)
throw std::out_of_range("Axis index out of range");
return m_axes[i];
}
protected:
std::array<axis_t<T>, Axis> m_axes;
};
}
template <u32 Axis, typename T>
class axis_aligned_bounding_box_t : public detail::axis_aligned_bounding_box_base_t<Axis, T>
{
public:
using detail::axis_aligned_bounding_box_base_t<Axis, T>::axis_aligned_bounding_box_base_t;
};
template <typename T>
class axis_aligned_bounding_box_t<2, T> : public detail::axis_aligned_bounding_box_base_t<2, T>
{
public:
using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t;
[[nodiscard]] vec2 min() const
{
return {this->m_axes[0].min(), this->m_axes[1].min()};
}
[[nodiscard]] vec2 max() const
{
return {this->m_axes[0].max(), this->m_axes[1].max()};
}
};
template <typename T>
class axis_aligned_bounding_box_t<3, T> : public detail::axis_aligned_bounding_box_base_t<3, T>
{
public:
using detail::axis_aligned_bounding_box_base_t<2, T>::axis_aligned_bounding_box_base_t;
[[nodiscard]] vec3 min() const
{
return {this->m_axes[0].min(), this->m_axes[1].min(), this->m_axes[2].min()};
}
[[nodiscard]] vec3s max() const
{
return {this->m_axes[0].max(), this->m_axes[1].max(), this->m_axes[2].max()};
}
};
using aabb_2d_t = axis_aligned_bounding_box_t<2, float>;
using aabb_3d_t = axis_aligned_bounding_box_t<3, float>;
}
#endif //BLT_MATH_AABB_H

View File

@ -0,0 +1,28 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_MATH_BOUNDING_BOX_H
#define BLT_MATH_BOUNDING_BOX_H
#include <blt/math/aabb.h>
namespace blt {
}
#endif //BLT_MATH_BOUNDING_BOX_H

View File

@ -9,7 +9,7 @@
#include <blt/math/vectors.h>
#include <blt/math/matrix.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <blt/std/utility.h>
#include "blt/std/string.h"

View File

@ -21,9 +21,9 @@ namespace blt
constexpr float EPSILON = std::numeric_limits<float>::epsilon();
static inline constexpr bool f_equal(float v1, float v2)
static constexpr bool f_equal(const float v1, const float v2, const float range = 1)
{
return v1 >= v2 - EPSILON && v1 <= v2 + EPSILON;
return v1 >= v2 - (EPSILON * range) && v1 <= v2 + (EPSILON * range);
}
template <typename T, blt::u32 size>
@ -107,6 +107,11 @@ namespace blt
}
}
[[nodiscard]] const std::array<T, size>& to_array() const
{
return elements;
}
[[nodiscard]] constexpr inline T x() const
{
return elements[0];
@ -423,6 +428,50 @@ namespace blt
return true;
}
template <typename T, typename G, u32 size>
constexpr bool operator>=(const vec<T, size>& left, const vec<G, size>& right)
{
for (u32 i = 0; i < size; i++)
{
if (left[i] < right[i])
return false;
}
return true;
}
template <typename T, typename G, u32 size>
constexpr bool operator>(const vec<T, size>& left, const vec<G, size>& right)
{
for (u32 i = 0; i < size; i++)
{
if (left[i] <= right[i])
return false;
}
return true;
}
template <typename T, typename G, u32 size>
constexpr bool operator<(const vec<T, size>& left, const vec<G, size>& right)
{
for (u32 i = 0; i < size; i++)
{
if (left[i] >= right[i])
return false;
}
return true;
}
template <typename T, typename G, u32 size>
constexpr bool operator<=(const vec<T, size>& left, const vec<G, size>& right)
{
for (u32 i = 0; i < size; i++)
{
if (left[i] > right[i])
return false;
}
return true;
}
template <typename T, typename G, blt::u32 size>
inline constexpr bool operator!=(const vec<T, size>& left, const vec<G, size>& right)
{

View File

@ -0,0 +1,51 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_META_IS_STREAMABLE_H
#define BLT_META_IS_STREAMABLE_H
#include <ostream>
namespace blt::meta
{
// https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists
template<typename T>
class is_streamable
{
private:
template<typename Subs>
static auto test(int) -> decltype(std::declval<std::ostream&>() << std::declval<Subs>(), std::true_type())
{
return std::declval<std::true_type>();
}
template<typename>
static auto test(...) -> std::false_type
{
return std::declval<std::false_type>();
}
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
template<class T>
inline constexpr bool is_streamable_v = is_streamable<T>::value;
}
#endif //BLT_META_IS_STREAMABLE_H

View File

@ -23,6 +23,7 @@
#include <utility>
#include <type_traits>
#include <ostream>
#include <blt/meta/is_streamable.h>
namespace blt::meta
{
@ -71,30 +72,6 @@ namespace blt::meta
template<typename Lambda>
lambda_helper(Lambda) -> lambda_helper<Lambda, decltype(&Lambda::operator())>;
// https://stackoverflow.com/questions/66397071/is-it-possible-to-check-if-overloaded-operator-for-type-or-class-exists
template<typename T>
class is_streamable
{
private:
template<typename Subs>
static auto test(int) -> decltype(std::declval<std::ostream&>() << std::declval<Subs>(), std::true_type())
{
return std::declval<std::true_type>();
}
template<typename>
static auto test(...) -> std::false_type
{
return std::declval<std::false_type>();
}
public:
static constexpr bool value = decltype(test<T>(0))::value;
};
template<class T>
inline constexpr bool is_streamable_v = is_streamable<T>::value;
template<typename T>
struct arrow_return
{
@ -114,7 +91,7 @@ namespace blt::meta
template<typename T>
struct deref_return
{
using type = typename std::invoke_result_t<decltype(&T::operator*), T&>;
using type = std::invoke_result_t<decltype(&T::operator*), T&>;
};
template<typename T>

View File

@ -0,0 +1,73 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_META_TYPE_TRAITS_H
#define BLT_META_TYPE_TRAITS_H
#include <type_traits>
#include <tuple>
namespace blt::meta
{
namespace detail
{
template<typename... Args>
void empty_apply_function(Args...)
{
}
inline auto lambda = [](auto...)
{
};
}
template <typename T>
using remove_cvref_t = std::remove_volatile_t<std::remove_const_t<std::remove_reference_t<T>>>;
template <typename U>
using add_const_ref_t = std::conditional_t<std::is_reference_v<U>, const std::remove_reference_t<U>&, const U>;
template <typename>
struct is_tuple : std::false_type
{
};
template <typename... T>
struct is_tuple<std::tuple<T...>> : std::true_type
{
};
template <typename>
struct is_pair : std::false_type
{
};
template <typename T, typename G>
struct is_pair<std::pair<T, G>> : std::true_type
{
};
template <typename T>
static constexpr bool is_tuple_v = is_tuple<T>::value;
template <typename T>
static constexpr bool is_pair_v = is_pair<T>::value;
}
#endif // BLT_META_TYPE_TRAITS_H

View File

@ -0,0 +1,811 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* 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 BLT_PARSE_ARGPARSE_V2_H
#define BLT_PARSE_ARGPARSE_V2_H
#include <complex>
#include <blt/std/types.h>
#include <blt/std/hashmap.h>
#include <blt/meta/meta.h>
#include <string>
#include <string_view>
#include <vector>
#include <variant>
#include <optional>
#include <memory>
#include <functional>
#include <type_traits>
#include <blt/iterator/iterator.h>
#include <blt/meta/type_traits.h>
#include <blt/std/assert.h>
#include <blt/std/expected.h>
#include <blt/std/ranges.h>
#include <blt/std/utility.h>
namespace blt::argparse
{
class argument_string_t;
class argument_consumer_t;
class argument_parser_t;
class argument_subparser_t;
class argument_builder_t;
class argument_storage_t;
class argument_positional_storage_t;
enum class action_t
{
STORE,
STORE_CONST,
STORE_TRUE,
STORE_FALSE,
APPEND,
APPEND_CONST,
EXTEND,
COUNT,
HELP,
VERSION
};
enum class nargs_t
{
IF_POSSIBLE,
ALL,
ALL_AT_LEAST_ONE
};
using nargs_v = std::variant<nargs_t, i32>;
namespace detail
{
class bad_flag final : public std::runtime_error
{
public:
explicit bad_flag(const std::string& message): std::runtime_error(message)
{
}
};
class bad_positional final : public std::runtime_error
{
public:
explicit bad_positional(const std::string& message): std::runtime_error(message)
{
}
};
class missing_argument_error final : public std::runtime_error
{
public:
explicit missing_argument_error(const std::string& message): std::runtime_error(message)
{
}
};
class missing_value_error final : public std::runtime_error
{
public:
explicit missing_value_error(const std::string& message): std::runtime_error(message)
{
}
};
class type_error final : public std::runtime_error
{
public:
explicit type_error(const std::string& message): std::runtime_error(message)
{
}
};
class unexpected_argument_error final : public std::runtime_error
{
public:
explicit unexpected_argument_error(const std::string& message): std::runtime_error(message)
{
}
};
class bad_choice_error final : public std::runtime_error
{
public:
explicit bad_choice_error(const std::string& message): std::runtime_error(message)
{
}
};
class subparse_error final : public std::exception
{
public:
explicit subparse_error(const std::string_view found_string, std::vector<std::vector<std::string_view>> allowed_strings):
m_found_string(found_string),
m_allowed_strings(std::move(allowed_strings))
{
}
[[nodiscard]] const std::vector<std::vector<std::string_view>>& get_allowed_strings() const
{
return m_allowed_strings;
}
[[nodiscard]] std::string_view get_found_string() const
{
return m_found_string;
}
[[nodiscard]] std::string error_string() const;
[[nodiscard]] const char* what() const noexcept override
{
m_error_string = error_string();
return m_error_string.c_str();
}
private:
mutable std::string m_error_string;
std::string_view m_found_string;
std::vector<std::vector<std::string_view>> m_allowed_strings;
};
template <typename... Args>
struct arg_data_helper_t
{
using variant_t = std::variant<Args..., std::vector<Args>...>;
using arg_t = meta::arg_helper<Args...>;
using arg_vec_t = meta::arg_helper<std::vector<Args>...>;
template <typename T>
constexpr static bool is_type_stored_v = std::disjunction_v<std::is_same<T, Args>...>;
template <typename DefaultPrimitiveAction, typename DefaultListAction>
static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action)
{
return lambda_visitor{
([&primitive_action](Args& arg)
{
return primitive_action(arg);
})...,
([&list_action](std::vector<Args>& arg_vec)
{
return list_action(arg_vec);
})...
};
}
};
using arg_meta_type_helper_t = arg_data_helper_t<bool, i8, i16, i32, i64, u8, u16, u32, u64, float, double, std::string>;
using arg_data_t = arg_meta_type_helper_t::variant_t;
template <typename T>
struct arg_string_converter_t
{
static T convert(const std::string_view value)
{
static_assert(std::is_arithmetic_v<T> || std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string>,
"Type must be arithmetic, string_view or string!");
const std::string temp{value};
if constexpr (std::is_same_v<T, float>)
{
return std::stof(temp);
}
else if constexpr (std::is_same_v<T, double>)
{
return std::stod(temp);
}
else if constexpr (std::is_unsigned_v<T>)
{
return static_cast<T>(std::stoull(temp));
}
else if constexpr (std::is_signed_v<T>)
{
return static_cast<T>(std::stoll(temp));
}
else if constexpr (std::is_same_v<T, std::string_view>)
{
return value;
}
else if constexpr (std::is_same_v<T, std::string>)
{
return std::string(value);
}
BLT_UNREACHABLE;
}
};
template <typename T>
auto ensure_is_string(T&& t)
{
using NO_REF = meta::remove_cvref_t<T>;
if constexpr (std::is_same_v<NO_REF, bool>)
return t ? "true" : "false";
else if constexpr (std::is_arithmetic_v<NO_REF> && !(std::is_same_v<NO_REF, char>
|| std::is_same_v<NO_REF, unsigned char> || std::is_same_v<NO_REF, signed char>))
return std::to_string(std::forward<T>(t));
else
return std::forward<T>(t);
}
void test();
}
class argument_string_t
{
public:
explicit argument_string_t(const std::string_view input, const hashset_t<char>& allowed_flag_prefix): m_argument(input),
m_allowed_flag_prefix(&allowed_flag_prefix)
{
process_argument();
}
[[nodiscard]] std::string_view get_flag() const
{
return m_flag_section;
}
[[nodiscard]] std::string_view get_name() const
{
return m_name_section;
}
[[nodiscard]] std::string_view value() const
{
return get_name();
}
[[nodiscard]] bool is_flag() const
{
return !m_flag_section.empty();
}
[[nodiscard]] std::string_view get_argument() const
{
return m_argument;
}
private:
/**
* This function takes the command line argument represented by this class,
* stored in m_argument and converts into flag side and name side arguments.
*
* What this means is in the case of a flag provided, for example passing --foo or --bar, this function will split the argument into
*
* m_flag_section = "--"
* m_name_section = "foo" || "bar"
*
* If the argument is purely positional, meaning there is no flag prefix,
* this function will do nothing and m_flag_section will be true for .empty()
*
* For example, if you provide res/some/folder/or/file as a command line positional argument,
* this function will create the following internal state:
*
* m_flag_section = ""
* m_flag_section.empty() == true
* m_name_section = "res/some/folder/or/file"
*
* m_argument is not modified by this function
*/
void process_argument()
{
// user provides a list of allowed prefix characters to argument_parser_t, which is then provided to this class at construction time
// it is not the job of this class to validate flag prefixes beyond this. // TODO: requiring this pointer is a code smell.
size_t start = 0;
for (; start < m_argument.size() && m_allowed_flag_prefix->contains(m_argument[start]); start++)
{
}
m_flag_section = {m_argument.data(), start};
m_name_section = {m_argument.data() + start, m_argument.size() - start};
}
std::string_view m_argument;
std::string_view m_flag_section;
std::string_view m_name_section;
const hashset_t<char>* m_allowed_flag_prefix;
};
class argument_consumer_t
{
public:
explicit argument_consumer_t(const span<argument_string_t>& args): m_absolute_begin(args.data()), m_begin(args.data() + 1),
m_end(args.data() + args.size())
{
BLT_ASSERT(!args.empty() &&
"Argument consumer must have at least one argument allocated to it. First argument is always assumed to be program");
}
[[nodiscard]] const argument_string_t& absolute_first() const
{
return *m_absolute_begin;
}
[[nodiscard]] argument_string_t peek(const i32 offset = 0) const
{
return *(m_begin + offset);
}
[[nodiscard]] argument_string_t r_peek(const i32 offset = 0) const
{
return *(m_end - 1 - offset);
}
argument_string_t consume()
{
return *(m_begin++);
}
argument_string_t r_consume()
{
return *(--m_end);
}
[[nodiscard]] i32 remaining() const
{
return static_cast<i32>(size());
}
[[nodiscard]] bool can_consume(const i32 amount = 0) const
{
return amount < remaining();
}
private:
[[nodiscard]] ptrdiff_t size() const
{
return m_end - m_begin;
}
argument_string_t* m_absolute_begin;
argument_string_t* m_begin;
argument_string_t* m_end;
};
class argument_storage_t
{
friend argument_parser_t;
friend argument_subparser_t;
friend argument_builder_t;
public:
template <typename T>
[[nodiscard]] const T& get(const std::string_view key) const
{
return std::get<T>(m_data.at(key));
}
[[nodiscard]] const std::string& get(const std::string_view key) const
{
return std::get<std::string>(m_data.at(key));
}
[[nodiscard]] bool contains(const std::string_view key) const
{
return m_data.find(key) != m_data.end();
}
[[nodiscard]] size_t size() const
{
return m_data.size();
}
private:
void add(const argument_storage_t& values)
{
for (const auto& value : values.m_data)
m_data.insert(value);
}
hashmap_t<std::string, detail::arg_data_t> m_data;
};
class argument_builder_t
{
friend argument_parser_t;
public:
argument_builder_t()
{
m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value)
{
storage.m_data.emplace(std::string{dest}, std::string{value});
};
m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string>& values)
{
storage.m_data.emplace(std::string{dest}, values);
};
}
template <typename T>
argument_builder_t& as_type()
{
static_assert(detail::arg_data_helper_t<T>::template is_type_stored_v<T>, "Type is not valid to be stored/converted as an argument");
m_dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value)
{
storage.m_data.emplace(std::string{dest}, detail::arg_string_converter_t<T>::convert(value));
};
m_dest_vec_func = [](const std::string_view dest, argument_storage_t& storage, const std::vector<std::string>& values)
{
if (storage.m_data.contains(dest))
{
auto& data = storage.m_data[dest];
if (!std::holds_alternative<std::vector<T>>(data))
throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string<T>() +
" but this does not match existing type index '" + std::to_string(data.index()) + "'!");
auto& converted_values = std::get<std::vector<T>>(data);
for (const auto& value : values)
converted_values.push_back(detail::arg_string_converter_t<T>::convert(value));
}
else
{
std::vector<T> converted_values;
for (const auto& value : values)
converted_values.push_back(detail::arg_string_converter_t<T>::convert(value));
storage.m_data.emplace(std::string{dest}, std::move(converted_values));
}
};
return *this;
}
argument_builder_t& make_flag()
{
return set_action(action_t::STORE_TRUE);
}
argument_builder_t& set_action(action_t action);
argument_builder_t& set_required(const bool required)
{
m_required = required;
return *this;
}
argument_builder_t& set_nargs(const nargs_v nargs)
{
m_nargs = nargs;
return *this;
}
argument_builder_t& set_metavar(const std::string& metavar)
{
m_metavar = metavar;
return *this;
}
argument_builder_t& set_help(const std::string& help)
{
m_help = help;
return *this;
}
argument_builder_t& set_choices(const std::vector<std::string>& choices)
{
m_choices = hashset_t<std::string>{};
for (const auto& choice : choices)
m_choices->emplace(choice);
return *this;
}
argument_builder_t& set_choices(const hashset_t<std::string>& choices)
{
m_choices = choices;
return *this;
}
template <typename... Args>
argument_builder_t& set_choices(Args&&... args)
{
m_choices = hashset_t<std::string>{};
((m_choices->emplace(detail::ensure_is_string(std::forward<Args>(args)))), ...);
return *this;
}
argument_builder_t& set_default(const detail::arg_data_t& default_value)
{
m_default_value = default_value;
return *this;
}
argument_builder_t& set_const(const detail::arg_data_t& const_value)
{
m_const_value = const_value;
return *this;
}
argument_builder_t& set_dest(const std::string_view& dest)
{
m_dest = dest;
return *this;
}
private:
action_t m_action = action_t::STORE;
bool m_required = false; // do we require this argument to be provided as an argument?
nargs_v m_nargs = 1; // number of arguments to consume
std::optional<std::string> m_metavar; // variable name to be used in the help string
std::optional<std::string> m_help; // help string to be used in the help string
std::optional<hashset_t<std::string>> m_choices; // optional allowed choices for this argument
std::optional<detail::arg_data_t> m_default_value;
std::optional<detail::arg_data_t> m_const_value;
std::optional<std::string> m_dest;
// dest, storage, value input
std::function<void(std::string_view, argument_storage_t&, std::string_view)> m_dest_func;
// dest, storage, value input
std::function<void(std::string_view, argument_storage_t&, const std::vector<std::string>& values)> m_dest_vec_func;
};
class argument_positional_storage_t
{
public:
explicit argument_positional_storage_t(std::vector<std::pair<std::string, argument_builder_t>> storage): positional_arguments(
std::move(storage))
{
}
argument_builder_t& peek()
{
return positional_arguments[current_positional].second;
}
argument_builder_t& next()
{
return positional_arguments[current_positional++].second;
}
[[nodiscard]] bool has_positional() const
{
return current_positional < positional_arguments.size();
}
[[nodiscard]] auto remaining() const
{
return iterate(positional_arguments).skip(current_positional);
}
private:
std::vector<std::pair<std::string, argument_builder_t>> positional_arguments;
size_t current_positional = 0;
};
class argument_parser_t
{
friend argument_subparser_t;
explicit argument_parser_t(const argument_subparser_t* parent): m_parent(parent)
{
}
public:
explicit argument_parser_t(const std::optional<std::string_view> description = {}, const std::optional<std::string_view> epilogue = {},
const std::optional<std::string_view> version = {},
const std::optional<std::string_view> usage = {}, const std::optional<std::string_view> name = {}):
m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue), m_version(version)
{
}
template <typename... Aliases>
argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases)
{
static_assert(
std::conjunction_v<std::disjunction<std::is_convertible<Aliases, std::string>, std::is_constructible<
std::string, Aliases>>...>,
"Arguments must be of type string_view, convertible to string_view or be string_view constructable");
m_argument_builders.emplace_back(std::make_unique<argument_builder_t>());
m_argument_builders.back()->set_dest(arg);
m_flag_arguments.emplace(arg, m_argument_builders.back().get());
(m_flag_arguments.emplace(aliases, m_argument_builders.back().get()), ...);
return *m_argument_builders.back().get();
}
argument_builder_t& add_positional(const std::string_view arg)
{
auto& b = m_positional_arguments.emplace_back(arg, argument_builder_t{}).second;
b.set_dest(std::string{arg});
b.set_required(true);
b.set_nargs(1);
return b;
}
argument_subparser_t* add_subparser(std::string_view dest);
argument_parser_t& with_help()
{
add_flag("--help", "-h").set_action(action_t::HELP).set_help("Show this help menu and exit");
return *this;
}
argument_parser_t& with_version()
{
add_flag("--version").set_action(action_t::VERSION);
return *this;
}
argument_storage_t parse(argument_consumer_t& consumer); // NOLINT
argument_storage_t parse(const std::vector<std::string_view>& args)
{
std::vector<argument_string_t> arg_strings;
arg_strings.reserve(args.size());
for (const auto& arg : args)
arg_strings.emplace_back(arg, allowed_flag_prefixes);
argument_consumer_t consumer{arg_strings};
return parse(consumer);
}
argument_storage_t parse(const std::vector<std::string>& args)
{
std::vector<argument_string_t> arg_strings;
arg_strings.reserve(args.size());
for (const auto& arg : args)
arg_strings.emplace_back(arg, allowed_flag_prefixes);
argument_consumer_t consumer{arg_strings};
return parse(consumer);
}
argument_storage_t parse(const int argc, const char** argv)
{
std::vector<argument_string_t> arg_strings;
arg_strings.reserve(argc);
for (int i = 0; i < argc; ++i)
arg_strings.emplace_back(argv[i], allowed_flag_prefixes);
argument_consumer_t consumer{arg_strings};
return parse(consumer);
}
void print_help();
void print_usage();
void print_version() const;
argument_parser_t& set_name(const std::optional<std::string>& name)
{
m_name = name;
return *this;
}
argument_parser_t& set_usage(const std::optional<std::string>& usage)
{
m_usage = usage;
return *this;
}
[[nodiscard]] const std::optional<std::string>& get_usage() const
{
return m_usage;
}
argument_parser_t& set_description(const std::optional<std::string>& description)
{
m_description = description;
return *this;
}
[[nodiscard]] const std::optional<std::string>& get_description() const
{
return m_description;
}
argument_parser_t& set_epilogue(const std::optional<std::string>& epilogue)
{
m_epilogue = epilogue;
return *this;
}
[[nodiscard]] const std::optional<std::string>& get_epilogue() const
{
return m_epilogue;
}
argument_parser_t& set_version(const std::optional<std::string>& version)
{
m_epilogue = version;
return *this;
}
[[nodiscard]] const std::optional<std::string>& get_version() const
{
return m_version;
}
[[nodiscard]] const hashset_t<char>& get_allowed_flag_prefixes() const
{
return allowed_flag_prefixes;
}
private:
void handle_compound_flags(hashset_t<std::string>& found_flags, argument_storage_t& parsed_args, argument_consumer_t& consumer,
const argument_string_t& arg);
void parse_flag(argument_storage_t& parsed_args, argument_consumer_t& consumer, std::string_view arg);
void parse_positional(argument_storage_t& parsed_args, argument_consumer_t& consumer, argument_positional_storage_t& storage,
std::string_view arg);
static void handle_missing_and_default_args(hashmap_t<std::string_view, argument_builder_t*>& arguments,
const hashset_t<std::string>& found, argument_storage_t& parsed_args, std::string_view type);
static expected<std::vector<std::string>, std::string> consume_until_flag_or_end(argument_consumer_t& consumer,
hashset_t<std::string>* allowed_choices);
static std::vector<std::string> consume_argc(i32 argc, argument_consumer_t& consumer, hashset_t<std::string>* allowed_choices,
std::string_view arg);
std::optional<std::string> m_name;
std::optional<std::string> m_usage;
std::optional<std::string> m_description;
std::optional<std::string> m_epilogue;
std::optional<std::string> m_version;
const argument_subparser_t* m_parent = nullptr;
std::vector<std::pair<std::string_view, argument_subparser_t>> m_subparsers;
std::vector<std::unique_ptr<argument_builder_t>> m_argument_builders;
hashmap_t<std::string_view, argument_builder_t*> m_flag_arguments;
std::vector<std::pair<std::string, argument_builder_t>> m_positional_arguments;
hashset_t<char> allowed_flag_prefixes = {'-', '+', '/'};
};
class argument_subparser_t
{
friend argument_parser_t;
public:
explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent)
{
}
template <typename... Aliases>
argument_parser_t* add_parser(const std::string_view name, Aliases... aliases)
{
static_assert(
std::conjunction_v<std::disjunction<std::is_convertible<Aliases, std::string_view>, std::is_constructible<
std::string_view, Aliases>>...>,
"Arguments must be of type string_view, convertible to string_view or be string_view constructable");
m_parsers.emplace_back(new argument_parser_t{this});
m_aliases[name] = m_parsers.back().get();
((m_aliases[std::string_view{aliases}] = m_parsers.back().get()), ...);
return m_parsers.back().get();
}
argument_subparser_t* set_help(const std::optional<std::string>& help)
{
m_help = help;
return this;
}
/**
* Parses the next argument using the provided argument consumer.
*
* This function uses an argument consumer to extract and process the next argument.
* If the argument is a flag or if it cannot be matched against the available parsers,
* an exception is thrown.
*
* @param consumer Reference to an argument_consumer_t object, which handles argument parsing.
* The consumer provides the next argument to be parsed.
*
* @throws detail::subparse_error If the argument is a flag or does not match any known parser.
*/
std::pair<argument_string_t, argument_storage_t> parse(argument_consumer_t& consumer); // NOLINT
private:
[[nodiscard]] hashmap_t<argument_parser_t*, std::vector<std::string_view>> get_allowed_strings() const;
// annoying compatability because im lazy
static std::vector<std::vector<std::string_view>> to_vec(const hashmap_t<argument_parser_t*, std::vector<std::string_view>>& map);
const argument_parser_t* m_parent;
std::optional<std::string> m_last_parsed_parser; // bad hack
std::optional<std::string> m_help;
std::vector<std::unique_ptr<argument_parser_t>> m_parsers;
hashmap_t<std::string_view, argument_parser_t*> m_aliases;
};
}
#endif //BLT_PARSE_ARGPARSE_V2_H

View File

@ -27,7 +27,7 @@
#include <blt/std/hashmap.h>
#include <blt/std/types.h>
#include <blt/std/expected.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <variant>
namespace blt
@ -223,8 +223,12 @@ namespace blt
std::string_view from_last()
{
if (!hasNext())
return std::string_view(&raw_string[last_read_index], raw_string.size() - last_read_index);
if (!hasNext()) {
auto size = raw_string.size() - last_read_index;
if (size > 0)
return std::string_view(&raw_string[last_read_index], size);
return "";
}
auto token = storage[getCurrentIndex()];
auto len = ((&token.token.back()) - &raw_string[last_read_index]);
auto str = std::string_view(&raw_string[last_read_index], len);

View File

@ -11,7 +11,7 @@
#include <blt/std/queue.h>
#include <vector>
#include <unordered_map>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <fstream>
#include <cstdint>
@ -54,7 +54,7 @@ namespace blt::profiling {
profile getProfile(const std::string& profileName);
void printProfile(
const std::string& profileName, logging::log_level loggingLevel = logging::log_level::NONE,
const std::string& profileName, logging::log_level_t loggingLevel = logging::log_level_t::NONE,
bool averageHistory = false
);

View File

@ -11,7 +11,7 @@
#include <cstdint>
#include <string>
#include <vector>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
namespace blt
{
@ -98,7 +98,7 @@ namespace blt
void endInterval(interval_t* interval);
void printProfile(profile_t& profiler, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL,
sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE);
sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE);
void writeProfile(std::ostream& stream, profile_t& profiler,
std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL,
@ -113,7 +113,7 @@ namespace blt
void endInterval(const std::string& profile_name, const std::string& interval_name);
void printProfile(const std::string& profile_name, std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL,
sort_by sort = sort_by::CYCLES, blt::logging::log_level log_level = blt::logging::log_level::NONE);
sort_by sort = sort_by::CYCLES, blt::logging::log_level_t log_level = blt::logging::log_level_t::NONE);
void writeProfile(std::ostream& stream, const std::string& profile_name,
std::uint32_t flags = AVERAGE_HISTORY | PRINT_CYCLES | PRINT_THREAD | PRINT_WALL,

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@
#include <blt/std/mmap.h>
#include <blt/compatibility.h>
#include <stdexcept>
#include "logging.h"
#include "blt/logging/logging.h"
#include <cstdlib>
#include <atomic>

View File

@ -375,12 +375,12 @@ namespace blt
};
template<typename T, typename E>
class expected<T, E, false>
class expected<T, E, false> : public expected<T, E, true>
{
public:
using expected<T, E, true>::expected;
constexpr expected(const expected<T, E, false>& copy) = delete;
constexpr expected(const expected& copy) = delete;
expected& operator=(const expected& copy) = delete;
};

View File

@ -1,929 +0,0 @@
/*
* Created by Brett on 20/07/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#ifndef BLT_TESTS_LOGGING2_H
#define BLT_TESTS_LOGGING2_H
#include <string>
#include <type_traits>
#include <functional>
#include <sstream>
#include <blt/config.h>
#include <blt/std/types.h>
#include <iostream>
#include <cstdarg>
namespace blt::logging
{
namespace ansi
{
inline auto ESC(std::string_view str)
{
return std::string("\033") += str;
}
inline const auto CUR_HOME = ESC("[H");
inline auto CUR_MOVE(blt::size_t line, blt::size_t column)
{
return ESC("[{" + std::to_string(line) + "};{" + std::to_string(column) + "}H");
}
inline auto CUR_UP(blt::size_t lines)
{
return ESC("[" + std::to_string(lines) + "A");
}
inline auto CUR_DOWN(blt::size_t lines)
{
return ESC("[" + std::to_string(lines) + "B");
}
inline auto CUR_RIGHT(blt::size_t columns)
{
return ESC("[" + std::to_string(columns) + "C");
}
inline auto CUR_LEFT(blt::size_t columns)
{
return ESC("[" + std::to_string(columns) + "D");
}
inline auto CUR_BEGIN_NEXT(blt::size_t lines_down)
{
return ESC("[" + std::to_string(lines_down) + "E");
}
inline auto CUR_BEGIN_PREV(blt::size_t lines_up)
{
return ESC("[" + std::to_string(lines_up) + "F");
}
inline auto CUR_COLUMN(blt::size_t column)
{
return ESC("[" + std::to_string(column) + "G");
}
inline auto CUR_POS()
{
return ESC("[6n");
}
inline auto CUR_SCROLL_UP()
{
return ESC(" M");
}
inline auto CUR_SAVE_DEC()
{
return ESC(" 7");
}
inline auto CUR_LOAD_DEC()
{
return ESC(" 8");
}
inline auto CUR_SAVE_SCO()
{
return ESC("[s");
}
inline auto CUR_LOAD_SCO()
{
return ESC("[u");
}
inline auto RESET = ESC("[0m");
inline auto BOLD = "1";
inline auto RESET_BOLD = "22";
inline auto DIM = "2";
inline auto RESET_DIM = "22";
inline auto ITALIC = "3";
inline auto RESET_ITALIC = "23";
inline auto UNDERLINE = "4";
inline auto RESET_UNDERLINE = "24";
inline auto BLINKING = "5";
inline auto RESET_BLINKING = "25";
inline auto INVERSE = "7";
inline auto RESET_INVERSE = "27";
inline auto HIDDEN = "8";
inline auto RESET_HIDDEN = "28";
inline auto STRIKETHROUGH = "9";
inline auto RESET_STRIKETHROUGH = "29";
inline auto COLOR_DEFAULT = "39";
inline auto BACKGROUND_DEFAULT = "49";
inline auto BLACK = "30";
inline auto RED = "31";
inline auto GREEN = "32";
inline auto YELLOW = "33";
inline auto BLUE = "34";
inline auto MAGENTA = "35";
inline auto CYAN = "36";
inline auto WHITE = "37";
inline auto BLACK_BACKGROUND = "40";
inline auto RED_BACKGROUND = "41";
inline auto GREEN_BACKGROUND = "42";
inline auto YELLOW_BACKGROUND = "43";
inline auto BLUE_BACKGROUND = "44";
inline auto MAGENTA_BACKGROUND = "45";
inline auto CYAN_BACKGROUND = "46";
inline auto WHITE_BACKGROUND = "47";
inline auto BRIGHT_BLACK = "90";
inline auto BRIGHT_RED = "91";
inline auto BRIGHT_GREEN = "92";
inline auto BRIGHT_YELLOW = "93";
inline auto BRIGHT_BLUE = "94";
inline auto BRIGHT_MAGENTA = "95";
inline auto BRIGHT_CYAN = "96";
inline auto BRIGHT_WHITE = "97";
inline auto BRIGHT_BLACK_BACKGROUND = "100";
inline auto BRIGHT_RED_BACKGROUND = "101";
inline auto BRIGHT_GREEN_BACKGROUND = "102";
inline auto BRIGHT_YELLOW_BACKGROUND = "103";
inline auto BRIGHT_BLUE_BACKGROUND = "104";
inline auto BRIGHT_MAGENTA_BACKGROUND = "105";
inline auto BRIGHT_CYAN_BACKGROUND = "106";
inline auto BRIGHT_WHITE_BACKGROUND = "107";
template<typename... Args>
inline auto make_color(Args... colors)
{
std::string mode;
((mode += std::string(colors) + ";"), ...);
return ESC("[" + mode.substr(0, mode.size() - 1) + "m");
}
}
enum class log_level
{
// default
NONE,
// low level
TRACE0, TRACE1, TRACE2, TRACE3,
// normal
TRACE, DEBUG, INFO,
// errors
WARN, ERROR, FATAL,
};
struct tag_func_param
{
blt::logging::log_level level;
const std::string& file, line, raw_string, formatted_string;
};
struct tag
{
// tag without the ${{ or }}
std::string tag;
// function to run: log level, file, line, and raw user input string are provided
std::function<std::string(const tag_func_param&)> func;
};
struct log_format
{
/**
* the log output format is the string which will be used to create the log output string
*
* Available tags:
* - ${{YEAR}} // current year
* - ${{MONTH}} // current month
* - ${{DAY}} // current day
* - ${{HOUR}} // current hour
* - ${{MINUTE}} // current minute
* - ${{SECOND}} // current second
* - ${{MS}} // current unix time
* - ${{NS}} // current ns from the high resolution system timer
* - ${{ISO_YEAR}} // ISO formatted 'year-month-day' in a single variable
* - ${{TIME}} // 'hour:minute:second' formatted string in a single variable
* - ${{FULL_TIME}} // 'year-month-day hour:minute:second' in a single variable
* - ${{LF}} // log level color (ANSI color code)
* - ${{ER}} // Error red
* - ${{CNR}} // conditional error red (only outputs if log level is an error!)
* - ${{RC}} // ANSI color reset
* - ${{LOG_LEVEL}} // current log level
* - ${{THREAD_NAME}} // current thread name, NOTE: thread names must be set by calling "setThreadName()" from the thread in question!
* - ${{FILE}} // file from which the macro is invoked
* - ${{LINE}} // line in the from which the macro is invoked
* - ${{RAW_STR}} // raw user string without formatting applied (NOTE: format args are not provided!)
* - ${{STR}} // the user supplied string (format applied!)
*/
std::string logOutputFormat = "\033[94m[${{TIME}}]${{RC}} ${{LF}}[${{LOG_LEVEL}}]${{RC}} \033[35m(${{FILE}}:${{LINE}})${{RC}} ${{CNR}}${{STR}}${{RC}}\n";
std::string levelNames[11] = {"STDOUT", "TRACE0", "TRACE1", "TRACE2", "TRACE3", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
std::string levelColors[11] = {"\033[0m", "\033[22;97m", "\033[97m", "\033[97m", "\033[97m", "\033[97m", "\033[36m", "\033[92m", "\033[93m",
"\033[91m", "\033[97;41m"};
// if true prints the whole path to the file (eg /home/user/.../.../project/src/source.cpp:line#)
bool printFullFileName = false;
// the logging lib will keep track of the largest line found so far and try to keep the spacing accordingly
// this is not thread safe!
bool ensureAlignment = false;
// should we log to file?
bool logToFile = false;
// should we log to console?
bool logToConsole = true;
// where should we log? (empty for current binary directory) should end in a / if not empty!
std::string logFilePath;
// logs to a file called $fileName_$count.log where count is the number of rollover files
// this accepts any of the macros above, using level names and colors should work but isn't supported.
std::string logFileName = "${{ISO_YEAR}}";
// default limit on file size: 10mb;
size_t logMaxFileSize = 1024 * 1024 * 10;
/**
* Variables below this line should never be changed by the user!
*/
// the current alignment width found (you shouldn't chance this variable!)
size_t currentWidth = 0;
// current number of file roll-overs. you shouldn't change this either.
size_t currentRollover = 0;
std::string lastFile;
};
struct logger
{
log_level level;
const char* file;
int line;
};
struct empty_logger
{
};
void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args);
void log_stream_internal(const std::string& str, const logger& logger);
template<typename T>
inline std::string to_string_stream(const T& t)
{
std::stringstream stream;
stream << t;
return stream.str();
}
template<typename T>
inline static void log_stream(const T& t, const logger& logger)
{
if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, char>)
{
log_stream_internal(std::to_string(t), logger);
} else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const char*>)
{
log_stream_internal(t, logger);
} else
{
log_stream_internal(to_string_stream(t), logger);
}
}
template<typename T>
inline void log(T t, log_level level, const char* file, int line, ...)
{
std::va_list args;
va_start(args, line);
if constexpr (std::is_arithmetic_v<T>)
{
log_internal(std::to_string(t), level, file, line, args);
} else if constexpr (std::is_same_v<T, std::string>)
{
log_internal(t, level, file, line, args);
} else if constexpr (std::is_same_v<T, const char*>)
{
log_internal(std::string(t), level, file, line, args);
} else
{
log_internal(to_string_stream(t), level, file, line, args);
}
va_end(args);
}
template<typename T>
static inline const blt::logging::logger& operator<<(const blt::logging::logger& out, const T& t)
{
log_stream(t, out);
return out;
}
template<typename T>
static inline const blt::logging::empty_logger& operator<<(const blt::logging::empty_logger& out, const T&)
{
return out;
}
void flush();
void newline();
void setThreadName(const std::string& name);
void setLogFormat(const log_format& format);
void setLogColor(log_level level, const std::string& newFormat);
void setLogName(log_level level, const std::string& newFormat);
void setLogOutputFormat(const std::string& newFormat);
void setLogToFile(bool shouldLogToFile);
void setLogToConsole(bool shouldLogToConsole);
void setLogPath(const std::string& path);
void setLogFileName(const std::string& fileName);
void setMaxFileSize(size_t fileSize);
}
//#define BLT_LOGGING_IMPLEMENTATION
#ifdef BLT_LOGGING_IMPLEMENTATION
#include <iostream>
#include <chrono>
#include <ctime>
#include <unordered_map>
#include <thread>
#include <cstdarg>
#include <iostream>
#include <vector>
#if defined(CXX17_FILESYSTEM) || defined (CXX17_FILESYSTEM_LIBFS)
#include <filesystem>
#elif defined(CXX11_EXP_FILESYSTEM) || defined (CXX11_EXP_FILESYSTEM_LIBFS)
#include <experimental/filesystem>
#else
#include <filesystem>
#endif
#include <ios>
#include <fstream>
template<typename K, typename V>
using hashmap = std::unordered_map<K, V>;
namespace blt::logging {
/**
* Used to store fast associations between built in tags and their respective values
*/
class tag_map {
private:
tag* tags;
size_t size;
[[nodiscard]] static inline size_t hash(const tag& t) {
size_t h = t.tag[1] * 3 - t.tag[0];
return h - 100;
}
// TODO: fix
void expand() {
auto newSize = size * 2;
auto newTags = new tag[newSize];
for (size_t i = 0; i < size; i++)
newTags[i] = tags[i];
delete[] tags;
tags = newTags;
size = newSize;
}
public:
tag_map(std::initializer_list<tag> initial_tags){
size_t max = 0;
for (const auto& t : initial_tags)
max = std::max(max, hash(t));
tags = new tag[(size = max+1)];
for (const auto& t : initial_tags)
insert(t);
}
tag_map(const tag_map& copy) {
tags = new tag[(size = copy.size)];
for (size_t i = 0; i < size; i++)
tags[i] = copy.tags[i];
}
void insert(const tag& t) {
auto h = hash(t);
//if (h > size)
// expand();
if (!tags[h].tag.empty())
std::cerr << "Tag not empty! " << tags[h].tag << "!!!\n";
tags[h] = t;
}
tag& operator[](const std::string& name) const {
auto h = hash(tag{name, nullptr});
if (h > size)
std::cerr << "Tag out of bounds";
return tags[h];
}
~tag_map(){
delete[] tags;
tags = nullptr;
}
};
class LogFileWriter {
private:
std::string m_path;
std::fstream* output = nullptr;
public:
explicit LogFileWriter() = default;
void writeLine(const std::string& path, const std::string& line){
if (path != m_path || output == nullptr){
clear();
delete output;
output = new std::fstream(path, std::ios::out | std::ios::app);
if (!output->good()){
throw std::runtime_error("Unable to open console filestream!\n");
}
}
if (!output->good()){
std::cerr << "There has been an error in the logging file stream!\n";
output->clear();
}
*output << line;
}
void clear(){
if (output != nullptr) {
try {
output->flush();
output->close();
} catch (std::exception& e){
std::cerr << e.what() << "\n";
}
}
}
~LogFileWriter() {
clear();
delete(output);
}
};
#ifdef WIN32
#define BLT_NOW() auto t = std::time(nullptr); tm now{}; localtime_s(&now, &t)
#else
#define BLT_NOW() auto t = std::time(nullptr); auto now_ptr = std::localtime(&t); auto& now = *now_ptr
#endif
//#define BLT_NOW() auto t = std::time(nullptr); tm now; localtime_s(&now, &t); //auto now = std::localtime(&t)
#define BLT_ISO_YEAR(S) auto S = std::to_string(now.tm_year + 1900); \
S += '-'; \
S += ensureHasDigits(now.tm_mon+1, 2); \
S += '-'; \
S += ensureHasDigits(now.tm_mday, 2);
#define BLT_CUR_TIME(S) auto S = ensureHasDigits(now.tm_hour, 2); \
S += ':'; \
S += ensureHasDigits(now.tm_min, 2); \
S += ':'; \
S += ensureHasDigits(now.tm_sec, 2);
static inline std::string ensureHasDigits(int current, int digits) {
std::string asString = std::to_string(current);
auto length = digits - asString.length();
if (length <= 0)
return asString;
std::string zeros;
zeros.reserve(length);
for (unsigned int i = 0; i < length; i++){
zeros += '0';
}
return zeros + asString;
}
log_format loggingFormat {};
hashmap<std::thread::id, std::string> loggingThreadNames;
hashmap<std::thread::id, hashmap<blt::logging::log_level, std::string>> loggingStreamLines;
LogFileWriter writer;
const std::unique_ptr<tag_map> tagMap = std::make_unique<tag_map>(tag_map{
{"YEAR", [](const tag_func_param&) -> std::string {
BLT_NOW();
return std::to_string(now.tm_year);
}},
{"MONTH", [](const tag_func_param&) -> std::string {
BLT_NOW();
return ensureHasDigits(now.tm_mon+1, 2);
}},
{"DAY", [](const tag_func_param&) -> std::string {
BLT_NOW();
return ensureHasDigits(now.tm_mday, 2);
}},
{"HOUR", [](const tag_func_param&) -> std::string {
BLT_NOW();
return ensureHasDigits(now.tm_hour, 2);
}},
{"MINUTE", [](const tag_func_param&) -> std::string {
BLT_NOW();
return ensureHasDigits(now.tm_min, 2);
}},
{"SECOND", [](const tag_func_param&) -> std::string {
BLT_NOW();
return ensureHasDigits(now.tm_sec, 2);
}},
{"MS", [](const tag_func_param&) -> std::string {
return std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count()
);
}},
{"NS", [](const tag_func_param&) -> std::string {
return std::to_string(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()).count()
);
}},
{"ISO_YEAR", [](const tag_func_param&) -> std::string {
BLT_NOW();
BLT_ISO_YEAR(returnStr);
return returnStr;
}},
{"TIME", [](const tag_func_param&) -> std::string {
BLT_NOW();
BLT_CUR_TIME(returnStr);
return returnStr;
}},
{"FULL_TIME", [](const tag_func_param&) -> std::string {
BLT_NOW();
BLT_ISO_YEAR(ISO);
BLT_CUR_TIME(TIME);
ISO += ' ';
ISO += TIME;
return ISO;
}},
{"LF", [](const tag_func_param& f) -> std::string {
return loggingFormat.levelColors[(int)f.level];
}},
{"ER", [](const tag_func_param&) -> std::string {
return loggingFormat.levelColors[(int)log_level::ERROR];
}},
{"CNR", [](const tag_func_param& f) -> std::string {
return f.level >= log_level::ERROR ? loggingFormat.levelColors[(int)log_level::ERROR] : "";
}},
{"RC", [](const tag_func_param&) -> std::string {
return "\033[0m";
}},
{"LOG_LEVEL", [](const tag_func_param& f) -> std::string {
return loggingFormat.levelNames[(int)f.level];
}},
{"THREAD_NAME", [](const tag_func_param&) -> std::string {
if (loggingThreadNames.find(std::this_thread::get_id()) == loggingThreadNames.end())
return "UNKNOWN";
return loggingThreadNames[std::this_thread::get_id()];
}},
{"FILE", [](const tag_func_param& f) -> std::string {
return f.file;
}},
{"LINE", [](const tag_func_param& f) -> std::string {
return f.line;
}},
{"RAW_STR", [](const tag_func_param& f) -> std::string {
return f.raw_string;
}},
{"STR", [](const tag_func_param& f) -> std::string {
return f.formatted_string;
}}
});
static inline std::vector<std::string> split(std::string s, const std::string& delim) {
size_t pos = 0;
std::vector<std::string> tokens;
while ((pos = s.find(delim)) != std::string::npos) {
auto token = s.substr(0, pos);
tokens.push_back(token);
s.erase(0, pos + delim.length());
}
tokens.push_back(s);
return tokens;
}
inline std::string filename(const std::string& path){
if (loggingFormat.printFullFileName)
return path;
auto paths = split(path, "/");
auto final = paths[paths.size()-1];
if (final == "/")
return paths[paths.size()-2];
return final;
}
class string_parser {
private:
std::string _str;
size_t _pos;
public:
explicit string_parser(std::string str): _str(std::move(str)), _pos(0) {}
inline char next(){
return _str[_pos++];
}
[[nodiscard]] inline bool has_next() const {
return _pos < _str.size();
}
};
std::string stripANSI(const std::string& str){
string_parser parser(str);
std::string out;
while (parser.has_next()){
char c = parser.next();
if (c == '\033'){
while (parser.has_next() && parser.next() != 'm');
} else
out += c;
}
return out;
}
void applyCFormatting(const std::string& format, std::string& output, std::va_list& args){
// args must be copied because they will be consumed by the first vsnprintf
va_list args_copy;
va_copy(args_copy, args);
auto buffer_size = std::vsnprintf(nullptr, 0, format.c_str(), args_copy) + 1;
auto* buffer = new char[static_cast<unsigned long>(buffer_size)];
vsnprintf(buffer, buffer_size, format.c_str(), args);
output = std::string(buffer);
delete[] buffer;
va_end(args_copy);
}
/**
* Checks if the next character in the parser is a tag opening, if not output the buffer to the out string
*/
inline bool tagOpening(string_parser& parser, std::string& out){
char c = ' ';
if (parser.has_next() && (c = parser.next()) == '{')
if (parser.has_next() && (c = parser.next()) == '{')
return true;
else
out += c;
else
out += c;
return false;
}
void parseString(string_parser& parser, std::string& out, const std::string& userStr, log_level level, const char* file, int line){
while (parser.has_next()){
char c = parser.next();
std::string nonTag;
if (c == '$' && tagOpening(parser, nonTag)){
std::string tag;
while (parser.has_next()){
c = parser.next();
if (c == '}')
break;
tag += c;
}
c = parser.next();
if (parser.has_next() && c != '}') {
std::cerr << "Error processing tag, is not closed with two '}'!\n";
break;
}
if (loggingFormat.ensureAlignment && tag == "STR") {
auto currentOutputWidth = out.size();
auto& longestWidth = loggingFormat.currentWidth;
longestWidth = std::max(longestWidth, currentOutputWidth);
// pad with spaces
if (currentOutputWidth != longestWidth){
for (size_t i = currentOutputWidth; i < longestWidth; i++)
out += ' ';
}
}
tag_func_param param{
level, filename({file}), std::to_string(line), userStr, userStr
};
out += (*tagMap)[tag].func(param);
} else {
out += c;
out += nonTag;
}
}
}
std::string applyFormatString(const std::string& str, log_level level, const char* file, int line){
// this can be speedup by preprocessing the string into an easily callable class
// where all the variables are ready to be substituted in one step
// and all static information already entered
string_parser parser(loggingFormat.logOutputFormat);
std::string out;
parseString(parser, out, str, level, file, line);
return out;
}
void log_internal(const std::string& format, log_level level, const char* file, int line, std::va_list& args) {
std::string withoutLn = format;
auto len = withoutLn.length();
if (len > 0 && withoutLn[len - 1] == '\n')
withoutLn = withoutLn.substr(0, len-1);
std::string out;
applyCFormatting(withoutLn, out, args);
if (level == log_level::NONE){
std::cout << out << std::endl;
return;
}
std::string finalFormattedOutput = applyFormatString(out, level, file, line);
if (loggingFormat.logToConsole)
std::cout << finalFormattedOutput;
if (loggingFormat.logToFile){
string_parser parser(loggingFormat.logFileName);
std::string fileName;
parseString(parser, fileName, withoutLn, level, file, line);
auto path = loggingFormat.logFilePath;
if (!path.empty() && path[path.length()-1] != '/')
path += '/';
// if the file has changed (new day in default case) we should reset the rollover count
if (loggingFormat.lastFile != fileName){
loggingFormat.currentRollover = 0;
loggingFormat.lastFile = fileName;
}
path += fileName;
path += '-';
path += std::to_string(loggingFormat.currentRollover);
path += ".log";
if (std::filesystem::exists(path)) {
auto fileSize = std::filesystem::file_size(path);
// will start on next file
if (fileSize > loggingFormat.logMaxFileSize)
loggingFormat.currentRollover++;
}
writer.writeLine(path, stripANSI(finalFormattedOutput));
}
//std::cout.flush();
}
void log_stream_internal(const std::string& str, const logger& logger) {
auto& s = loggingStreamLines[std::this_thread::get_id()][logger.level];
// s += str;
for (char c : str){
s += c;
if (c == '\n'){
log(s, logger.level, logger.file, logger.line);
s = "";
}
}
}
void setThreadName(const std::string& name) {
loggingThreadNames[std::this_thread::get_id()] = name;
}
void setLogFormat(const log_format& format){
loggingFormat = format;
}
void setLogColor(log_level level, const std::string& newFormat){
loggingFormat.levelColors[(int)level] = newFormat;
}
void setLogName(log_level level, const std::string& newFormat){
loggingFormat.levelNames[(int)level] = newFormat;
}
void setLogOutputFormat(const std::string& newFormat){
loggingFormat.logOutputFormat = newFormat;
}
void setLogToFile(bool shouldLogToFile){
loggingFormat.logToFile = shouldLogToFile;
}
void setLogToConsole(bool shouldLogToConsole){
loggingFormat.logToConsole = shouldLogToConsole;
}
void setLogPath(const std::string& path){
loggingFormat.logFilePath = path;
}
void setLogFileName(const std::string& fileName){
loggingFormat.logFileName = fileName;
}
void setMaxFileSize(size_t fileSize) {
loggingFormat.logMaxFileSize = fileSize;
}
void flush() {
std::cerr.flush();
std::cout.flush();
}
void newline()
{
std::cout << std::endl;
}
}
#endif
#if defined(__clang__) || defined(__llvm__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#endif
#ifdef BLT_DISABLE_LOGGING
#define BLT_LOG(format, level, ...)
#define BLT_LOG_STREAM(level)
#define BLT_TRACE0_STREAM
#define BLT_TRACE1_STREAM
#define BLT_TRACE2_STREAM
#define BLT_TRACE3_STREAM
#define BLT_TRACE_STREAM
#define BLT_DEBUG_STREAM
#define BLT_INFO_STREAM
#define BLT_WARN_STREAM
#define BLT_ERROR_STREAM
#define BLT_FATAL_STREAM
#define BLT_TRACE(format, ...)
#define BLT_DEBUG(format, ...)
#define BLT_INFO(format, ...)
#define BLT_WARN(format, ...)
#define BLT_ERROR(format, ...)
#define BLT_FATAL(format, ...)
#else
#define BLT_NEWLINE() blt::logging::newline()
#define BLT_LOG(format, level, ...) blt::logging::log(format, level, __FILE__, __LINE__, ##__VA_ARGS__)
#define BLT_LOG_STREAM(level) blt::logging::logger{level, __FILE__, __LINE__}
#ifdef BLT_DISABLE_TRACE
#define BLT_TRACE(format, ...)
#define BLT_TRACE0_STREAM blt::logging::empty_logger{}
#define BLT_TRACE1_STREAM blt::logging::empty_logger{}
#define BLT_TRACE2_STREAM blt::logging::empty_logger{}
#define BLT_TRACE3_STREAM blt::logging::empty_logger{}
#define BLT_TRACE_STREAM blt::logging::empty_logger{}
#else
#define BLT_TRACE(format, ...) BLT_LOG(format, blt::logging::log_level::TRACE, ##__VA_ARGS__)
#define BLT_TRACE0_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE0)
#define BLT_TRACE1_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE1)
#define BLT_TRACE2_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE2)
#define BLT_TRACE3_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE3)
#define BLT_TRACE_STREAM BLT_LOG_STREAM(blt::logging::log_level::TRACE)
#endif
#ifdef BLT_DISABLE_DEBUG
#define BLT_DEBUG(format, ...)
#define BLT_DEBUG_STREAM blt::logging::empty_logger{}
#else
#define BLT_DEBUG(format, ...) BLT_LOG(format, blt::logging::log_level::DEBUG, ##__VA_ARGS__)
#define BLT_DEBUG_STREAM BLT_LOG_STREAM(blt::logging::log_level::DEBUG)
#endif
#ifdef BLT_DISABLE_INFO
#define BLT_INFO(format, ...)
#define BLT_INFO_STREAM blt::logging::empty_logger{}
#else
#define BLT_INFO(format, ...) BLT_LOG(format, blt::logging::log_level::INFO, ##__VA_ARGS__)
#define BLT_INFO_STREAM BLT_LOG_STREAM(blt::logging::log_level::INFO)
#endif
#ifdef BLT_DISABLE_WARN
#define BLT_WARN(format, ...)
#define BLT_WARN_STREAM blt::logging::empty_logger{}
#else
#define BLT_WARN(format, ...) BLT_LOG(format, blt::logging::log_level::WARN, ##__VA_ARGS__)
#define BLT_WARN_STREAM BLT_LOG_STREAM(blt::logging::log_level::WARN)
#endif
#ifdef BLT_DISABLE_ERROR
#define BLT_ERROR(format, ...)
#define BLT_ERROR_STREAM blt::logging::empty_logger{}
#else
#define BLT_ERROR(format, ...) BLT_LOG(format, blt::logging::log_level::ERROR, ##__VA_ARGS__)
#define BLT_ERROR_STREAM BLT_LOG_STREAM(blt::logging::log_level::ERROR)
#endif
#ifdef BLT_DISABLE_FATAL
#define BLT_FATAL(format, ...)
#define BLT_FATAL_STREAM blt::logging::empty_logger{}
#else
#define BLT_FATAL(format, ...) BLT_LOG(format, blt::logging::log_level::FATAL, ##__VA_ARGS__)
#define BLT_FATAL_STREAM BLT_LOG_STREAM(blt::logging::log_level::FATAL)
#endif
#endif
#if defined(__clang__) || defined(__llvm__)
#pragma clang diagnostic pop
#endif
#endif //BLT_TESTS_LOGGING2_H

View File

@ -33,13 +33,11 @@ namespace blt
{
public:
using element_type = T;
using value_type = typename std::iterator_traits<T>::value_type;
using pointer = typename std::iterator_traits<T>::pointer;
using const_pointer = const typename std::iterator_traits<T>::pointer;
using reference = typename std::iterator_traits<T>::reference;
using const_reference = const typename std::iterator_traits<T>::reference;
using difference_type = typename std::iterator_traits<T>::difference_type;
using iterator_category = typename std::iterator_traits<T>::iterator_category;
using value_type = std::remove_cv_t<T>;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = ptr_iterator<T>;
using const_iterator = ptr_iterator<const T>;

View File

@ -134,7 +134,7 @@ namespace blt::mem
return fromBytes<little_endian>(in, *out);
}
static std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16)
inline std::size_t next_byte_allocation(std::size_t prev_size, std::size_t default_allocation_block = 8192, std::size_t default_size = 16)
{
if (prev_size < default_size)
return default_size;

View File

@ -19,9 +19,11 @@
#ifndef BLT_MMAP_H
#define BLT_MMAP_H
#include <blt/std/logging.h>
#include <blt/std/types.h>
#include <cstdlib>
#include <exception>
#include <string>
#include <string_view>
// size of 2mb in bytes
inline constexpr blt::size_t BLT_2MB_SIZE = 2048 * 1024;
@ -41,7 +43,7 @@ namespace blt
public:
bad_alloc_t() = default;
explicit bad_alloc_t(std::string_view str): str(str)
explicit bad_alloc_t(const std::string_view str): str(str)
{}
explicit bad_alloc_t(std::string str): str(std::move(str))

View File

@ -31,7 +31,7 @@
#include <mutex>
#include <chrono>
#include <optional>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <condition_variable>
namespace blt

@ -1 +1 @@
Subproject commit 8a889d3699b3c09ade435641fb034427f3fd12b6
Subproject commit 154c63489e84d5569d3b466342a2ae8fd99e4734

View File

@ -6,7 +6,7 @@
#include <blt/format/format.h>
#include <blt/std/string.h>
#include <cmath>
#include "blt/std/logging.h"
#include "blt/logging/logging.h"
#include "blt/std/assert.h"
#include "blt/std/utility.h"
#include <stack>

158
src/blt/fs/file_writers.cpp Normal file
View File

@ -0,0 +1,158 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <cstdio>
#include <cstring>
#include <ctime>
#include <stdexcept>
#include <utility>
#include <blt/fs/file_writers.h>
namespace blt::fs
{
i64 fwriter_t::write(const char* buffer, const size_t bytes)
{
return static_cast<i64>(std::fwrite(buffer, 1, bytes, m_file));
}
void fwriter_t::flush()
{
writer_t::flush();
std::fflush(m_file);
}
void fwriter_t::newfile(const std::string& new_name)
{
if (m_file != nullptr)
std::fclose(m_file);
m_file = std::fopen(new_name.c_str(), m_mode.c_str());
if (!m_file)
throw std::runtime_error("Failed to open file for writing");
}
buffered_writer::buffered_writer(const std::string& name, const size_t buffer_size): fwriter_t{name, "ab"}
{
m_buffer.resize(buffer_size);
}
buffered_writer::buffered_writer(const size_t buffer_size)
{
m_buffer.resize(buffer_size);
}
void buffered_writer::newfile(const std::string& new_name)
{
fwriter_t::newfile(new_name);
setvbuf(this->m_file, nullptr, _IONBF, 0);
}
i64 buffered_writer::write(const char* buffer, const size_t bytes)
{
if (bytes > m_buffer.size())
return fwriter_t::write(buffer, bytes);
if (bytes + m_current_pos > m_buffer.size())
flush();
std::memcpy(m_buffer.data() + m_current_pos, buffer, bytes);
m_current_pos += bytes;
return static_cast<i64>(bytes);
}
void buffered_writer::flush()
{
fwriter_t::write(m_buffer.data(), m_current_pos);
fwriter_t::flush();
m_current_pos = 0;
}
bounded_writer::bounded_writer(fwriter_t& writer, std::optional<std::string> base_name, const size_t max_size, naming_function_t naming_function):
m_writer(&writer), m_base_name(std::move(base_name)), m_max_size(max_size), m_naming_function(std::move(naming_function))
{
bounded_writer::newfile(m_base_name.value_or(""));
}
i64 bounded_writer::write(const char* buffer, const size_t bytes)
{
m_currently_written += bytes;
if (m_currently_written > m_max_size)
this->newfile(m_base_name.value_or(""));
return m_writer->write(buffer, bytes);
}
void bounded_writer::newfile(const std::string& new_name)
{
++m_current_invocation;
m_currently_written = 0;
m_writer->newfile(m_naming_function(m_current_invocation, new_name));
}
void bounded_writer::flush()
{
m_writer->flush();
}
rotating_writer::rotating_writer(fwriter_t& writer, const time_t period): m_writer{&writer}, m_period{period}
{
newfile();
}
i64 rotating_writer::write(const char* buffer, const size_t bytes)
{
check_for_time();
return m_writer->write(buffer, bytes);
}
void rotating_writer::flush()
{
check_for_time();
m_writer->flush();
}
void rotating_writer::newfile(const std::string& new_name)
{
m_writer->newfile(new_name);
}
void rotating_writer::newfile()
{
m_last_time = get_current_time();
std::string name;
name += std::to_string(m_last_time.year);
name += "-" + std::to_string(m_last_time.month);
name += "-" + std::to_string(m_last_time.day);
if (m_period.hour >= 0)
name += "-" + std::to_string(m_last_time.hour);
name += ".txt";
newfile(name);
}
void rotating_writer::check_for_time()
{
const auto current_time = get_current_time();
if ((m_period.hour > 0 && current_time.hour > m_last_time.hour + m_period.hour) ||
(m_period.day > 0 && current_time.day > m_last_time.day + m_period.day) ||
(m_period.month > 0 && current_time.month > m_last_time.month + m_period.month) ||
(m_period.year > 0 && current_time.year > m_last_time.year + m_period.year))
newfile();
}
time_t rotating_writer::get_current_time()
{
const std::time_t time = std::time(nullptr);
const auto current_time = std::localtime(&time);
return {current_time->tm_year + 1900, current_time->tm_mon + 1, current_time->tm_mday, current_time->tm_hour};
}
}

View File

@ -17,63 +17,4 @@
*/
#include <blt/fs/filesystem.h>
#include <cstring>
#include <blt/std/logging.h>
namespace blt::fs
{
fstream_block_reader::fstream_block_reader(std::fstream& stream, size_t bufferSize):
block_reader(bufferSize), m_stream(stream), m_buffer(new char[bufferSize])
{
if (!m_stream.good() || m_stream.fail())
BLT_WARN("Provided std::fstream is not good! Clearing!");
m_stream.clear();
}
int fstream_block_reader::read(char* buffer, size_t bytes)
{
if (readIndex == 0)
m_stream.read(m_buffer, (long) m_bufferSize);
if (readIndex + bytes >= m_bufferSize)
{
// copy out all the data from the current buffer
auto bytesLeft = m_bufferSize - readIndex;
memcpy(buffer, m_buffer + readIndex, bytesLeft);
readIndex = 0;
// now to prevent large scale reading in small blocks, we should just read the entire thing into the buffer.
m_stream.read(buffer + bytesLeft, (long) (bytes - bytesLeft));
} else
{
// but in the case that the size of the data read is small, we should read in blocks and copy from that buffer
// that should be quicker since file operations are slow.
std::memcpy(buffer, m_buffer + readIndex, bytes);
readIndex += bytes;
}
return 0;
}
int fstream_block_writer::write(char* buffer, size_t bytes)
{
if (writeIndex + bytes >= m_bufferSize)
{
// in an attempt to stay efficient we write out the old buffer and the new buffer
// since there is a good chance there is more than a buffer's worth of data being written
// otherwise the buffer is almost full and can be written anyway. (this might be bad for performance especially if the FS wants round numbers)
m_stream.write(m_buffer, (long) writeIndex);
writeIndex = 0;
m_stream.write(buffer, (long) bytes);
} else
{
std::memcpy(m_buffer + writeIndex, buffer, bytes);
writeIndex += bytes;
}
return 0;
}
void fstream_block_writer::flush_internal()
{
m_stream.write(m_buffer, (long) writeIndex);
writeIndex = 0;
}
}

View File

@ -82,8 +82,8 @@ std::string blt::fs::getFile(std::string_view path)
file_contents = file_stream.str();
} catch (std::ifstream::failure& e)
{
BLT_WARN("Unable to read file '%s'!\n", std::string(path).c_str());
BLT_WARN("Exception: %s", e.what());
BLT_WARN("Unable to read file '{}'!\n", std::string(path).c_str());
BLT_WARN("Exception: {}", e.what());
BLT_THROW(std::runtime_error("Failed to read file!\n"));
}
return file_contents;

View File

@ -4,19 +4,19 @@
* See LICENSE file for license detail
*/
#include <blt/fs/nbt.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <cassert>
#include <type_traits>
namespace blt::nbt {
void writeUTF8String(blt::fs::block_writer& stream, const std::string& str) {
void writeUTF8String(blt::fs::writer_t& stream, const std::string& str) {
blt::string::utf8_string str8 = blt::string::createUTFString(str);
stream.write(str8.characters, str8.size);
delete[] str8.characters;
}
std::string readUTF8String(blt::fs::block_reader& stream) {
std::string readUTF8String(blt::fs::reader_t& stream) {
int16_t utflen;
readData(stream, utflen);
@ -33,9 +33,10 @@ namespace blt::nbt {
}
void NBTReader::read() {
char t = reader.get();
char t;
reader.read(&t, 1);
if (t != (char)nbt_tag::COMPOUND) {
BLT_WARN("Found %d", t);
BLT_WARN("Found {:d}", t);
throw std::runtime_error("Incorrectly formatted NBT data! Root tag must be a compound tag!");
}
root = new tag_compound;

View File

@ -0,0 +1,67 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <blt/fs/path_helper.h>
#include <blt/std/string.h>
#include <blt/compatibility.h>
namespace blt::fs
{
#ifdef BLT_WINDOWS
constexpr static char delim = '\\';
#else
constexpr static char delim = '/';
#endif
std::string base_name(const std::string& str)
{
return std::string(base_name_sv(str));
}
std::string_view base_name_sv(const std::string_view str)
{
const auto parts = string::split_sv(str, delim);
const auto file_parts = string::split_sv(parts.back(), '.');
return file_parts.front();
}
std::string filename(const std::string& str)
{
return std::string(filename_sv(str));
}
std::string_view filename_sv(const std::string_view str)
{
const auto parts = string::split_sv(str, delim);
return parts.back();
}
std::string extension(const std::string& str)
{
return std::string(extension_sv(str));
}
std::string_view extension_sv(const std::string_view str)
{
const auto parts = string::split_sv(str, delim);
const auto file_parts = parts.back().find_first_of('.');
return parts.back().substr(std::min(file_parts + 1, parts.back().size()));
}
}

View File

@ -0,0 +1,45 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <istream>
#include <ostream>
#include <blt/fs/stream_wrappers.h>
namespace blt::fs
{
fstream_reader_t::fstream_reader_t(std::istream& stream): m_stream{&stream}
{}
i64 fstream_reader_t::read(char* buffer, const size_t bytes)
{
return m_stream->readsome(buffer, static_cast<std::streamsize>(bytes));
}
fstream_writer_t::fstream_writer_t(std::ostream& stream): m_stream{&stream}
{}
i64 fstream_writer_t::write(const char* buffer, const size_t bytes)
{
m_stream->write(buffer, static_cast<std::streamsize>(bytes));
return static_cast<i64>(bytes);
}
void fstream_writer_t::flush()
{
m_stream->flush();
}
}

View File

@ -0,0 +1,36 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <blt/fs/threaded_writers.h>
namespace blt::fs
{
concurrent_file_writer::concurrent_file_writer(writer_t* writer): m_writer{writer}
{}
i64 concurrent_file_writer::write(const char* buffer, const size_t bytes)
{
std::scoped_lock lock{m_mutex};
return m_writer->write(buffer, bytes);
}
void concurrent_file_writer::flush()
{
std::scoped_lock lock{m_mutex};
m_writer->flush();
}
}

18
src/blt/logging/ansi.cpp Normal file
View File

@ -0,0 +1,18 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <blt/logging/ansi.h>

View File

@ -0,0 +1,593 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <iomanip>
#include <iostream>
#include <sstream>
#include <blt/logging/fmt_tokenizer.h>
namespace blt::logging
{
fmt_token_type fmt_tokenizer_t::get_type(const char c)
{
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return fmt_token_type::NUMBER;
case '+':
return fmt_token_type::PLUS;
case '-':
return fmt_token_type::MINUS;
case '.':
return fmt_token_type::DOT;
case ':':
return fmt_token_type::COLON;
case ' ':
return fmt_token_type::SPACE;
case '#':
return fmt_token_type::POUND;
case '<':
return fmt_token_type::LEFT_CHEVRON;
case '>':
return fmt_token_type::RIGHT_CHEVRON;
case '^':
return fmt_token_type::CARET;
case '{':
return fmt_token_type::OPEN_BRACKET;
case '}':
return fmt_token_type::CLOSE_BRACKET;
default:
return fmt_token_type::STRING;
}
}
std::optional<fmt_token_t> fmt_tokenizer_t::next()
{
if (m_pos >= m_fmt.size())
return {};
bool is_escaped = false;
if (m_fmt[m_pos] == '\\')
{
is_escaped = true;
++m_pos;
}
switch (const auto base_type = get_type(m_fmt[m_pos]))
{
case fmt_token_type::SPACE:
case fmt_token_type::PLUS:
case fmt_token_type::MINUS:
case fmt_token_type::DOT:
case fmt_token_type::COLON:
case fmt_token_type::POUND:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CARET:
case fmt_token_type::OPEN_BRACKET:
case fmt_token_type::CLOSE_BRACKET:
if (is_escaped)
return fmt_token_t{fmt_token_type::STRING, std::string_view{m_fmt.data() + m_pos++, 1}};
return fmt_token_t{base_type, std::string_view{m_fmt.data() + m_pos++, 1}};
default:
{
const auto begin = m_pos;
for (; m_pos < m_fmt.size() && get_type(m_fmt[m_pos]) == base_type; ++m_pos)
{
}
return fmt_token_t{base_type, std::string_view{m_fmt.data() + begin, m_pos - begin}};
}
}
}
std::vector<fmt_token_t> fmt_tokenizer_t::tokenize(const std::string_view fmt)
{
m_fmt = fmt;
m_pos = 0;
std::vector<fmt_token_t> tokens;
while (auto token = next())
tokens.push_back(*token);
return tokens;
}
const fmt_spec_t& fmt_parser_t::parse(const std::string_view fmt)
{
m_spec = {};
m_pos = 0;
m_tokens = m_tokenizer.tokenize(fmt);
parse_fmt_field();
return m_spec;
}
bool fmt_parser_t::is_align_t(const fmt_token_type type)
{
switch (type)
{
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CARET:
return true;
default:
return false;
}
}
void fmt_parser_t::parse_fmt_field()
{
if (!has_next())
throw std::runtime_error("Expected token when parsing format field");
const auto [type, value] = peek();
switch (type)
{
case fmt_token_type::NUMBER:
parse_arg_id();
if (has_next())
{
if (peek().type == fmt_token_type::COLON)
parse_fmt_spec();
else
throw std::runtime_error("Expected ':' when parsing format field after arg id!");
}
break;
case fmt_token_type::COLON:
parse_fmt_spec();
break;
default:
{
std::stringstream ss;
ss << "Expected unknown token '" << static_cast<u8>(type) << "' value '" << value << "' when parsing format field";
throw std::runtime_error(ss.str());
}
}
if (has_next())
parse_type();
}
void fmt_parser_t::parse_arg_id()
{
const auto [type, value] = next();
if (type != fmt_token_type::NUMBER)
{
std::stringstream ss;
ss << "Expected number when parsing arg id, unexpected value '" << value << '\'';
throw std::runtime_error(ss.str());
}
m_spec.arg_id = std::stoll(std::string(value));
}
std::string fmt_parser_t::parse_arg_or_number()
{
auto [type, value] = next();
if (type == fmt_token_type::NUMBER)
return std::string(value);
if (type == fmt_token_type::OPEN_BRACKET)
{
auto [next_type, next_value] = next();
if (next_type != fmt_token_type::NUMBER)
throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(next_value) + '\'');
if (next().type != fmt_token_type::CLOSE_BRACKET)
throw std::runtime_error("Expected closing bracket when parsing arg or number, unexpected value '" + std::string(next_value) + '\'');
// TODO: this feels like an evil hack.
const auto arg_id = std::stoul(std::string(next_value));
if (arg_id >= m_handlers.size())
throw std::runtime_error("Invalid arg id " + std::to_string(arg_id) + ", max arg supported: " + std::to_string(m_handlers.size()));
std::stringstream ss;
m_handlers[arg_id](ss, fmt_spec_t{});
return ss.str();
}
throw std::runtime_error("Expected number when parsing arg or number, unexpected value '" + std::string(value) + '\'');
}
void fmt_parser_t::parse_fmt_spec()
{
// consume :
consume();
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::STRING:
// if there is a token beyond this string, it is not a type string
if (has_next(1))
parse_fmt_spec_fill();
return;
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::CARET:
parse_fmt_spec_align();
break;
case fmt_token_type::COLON:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Begin)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
case fmt_token_type::SPACE:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
parse_fmt_spec_sign();
break;
case fmt_token_type::POUND:
parse_fmt_spec_form();
break;
}
}
void fmt_parser_t::parse_fmt_spec_fill()
{
parse_fill();
if (!has_next())
return;
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::CARET:
parse_fmt_spec_align();
break;
case fmt_token_type::NUMBER:
case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
case fmt_token_type::SPACE:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
parse_fmt_spec_sign();
break;
case fmt_token_type::POUND:
parse_fmt_spec_form();
break;
case fmt_token_type::STRING:
return;
case fmt_token_type::COLON:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Fill)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
}
}
void fmt_parser_t::parse_fmt_spec_align()
{
parse_align();
if (!has_next())
return;
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::STRING:
return;
case fmt_token_type::NUMBER:
case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
case fmt_token_type::SPACE:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
parse_fmt_spec_sign();
break;
case fmt_token_type::POUND:
parse_fmt_spec_form();
break;
case fmt_token_type::CARET:
case fmt_token_type::COLON:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Align)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
}
}
// handle start of fmt, with sign
void fmt_parser_t::parse_fmt_spec_sign()
{
parse_sign();
if (!has_next())
return;
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::STRING:
return;
case fmt_token_type::SPACE:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
case fmt_token_type::COLON:
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Sign)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
case fmt_token_type::POUND:
parse_fmt_spec_form();
break;
}
}
void fmt_parser_t::parse_fmt_spec_form()
{
parse_form();
if (!has_next())
return;
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::STRING:
return;
case fmt_token_type::SPACE:
case fmt_token_type::COLON:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
case fmt_token_type::POUND:
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Form)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::NUMBER:
case fmt_token_type::OPEN_BRACKET:
parse_fmt_spec_width();
break;
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
}
}
// handle width parsing
void fmt_parser_t::parse_fmt_spec_width()
{
parse_width();
if (!has_next())
return;
auto [type, value] = peek();
switch (type)
{
case fmt_token_type::STRING:
return;
case fmt_token_type::COLON:
case fmt_token_type::MINUS:
case fmt_token_type::PLUS:
case fmt_token_type::SPACE:
case fmt_token_type::POUND:
case fmt_token_type::NUMBER:
case fmt_token_type::CARET:
case fmt_token_type::LEFT_CHEVRON:
case fmt_token_type::RIGHT_CHEVRON:
case fmt_token_type::OPEN_BRACKET:
case fmt_token_type::CLOSE_BRACKET:
{
std::stringstream ss;
ss << "(Stage (Width)) Invalid token type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
case fmt_token_type::DOT:
parse_fmt_spec_precision();
break;
}
}
void fmt_parser_t::parse_fmt_spec_precision()
{
// consume .
consume();
parse_precision();
}
void fmt_parser_t::parse_fill()
{
auto [type, value] = next();
if (type != fmt_token_type::STRING)
{
std::stringstream ss;
ss << "Expected string when parsing fill, got " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
m_spec.prefix_char = value.front();
}
void fmt_parser_t::parse_align()
{
auto [type, value] = next();
switch (type)
{
case fmt_token_type::LEFT_CHEVRON:
m_spec.alignment = fmt_align_t::LEFT;
break;
case fmt_token_type::RIGHT_CHEVRON:
m_spec.alignment = fmt_align_t::RIGHT;
break;
case fmt_token_type::CARET:
m_spec.alignment = fmt_align_t::CENTER;
break;
default:
{
std::stringstream ss;
ss << "Invalid align type " << static_cast<u8>(type) << " value " << value;
throw std::runtime_error(ss.str());
}
}
}
void fmt_parser_t::parse_sign()
{
auto [_, value] = next();
if (value.size() > 1)
{
std::stringstream ss;
ss << "Sign contains more than one character, we are not sure how to interpret this. Value '" << value << "'";
throw std::runtime_error(ss.str());
}
switch (value[0])
{
case '+':
m_spec.sign = fmt_sign_t::PLUS;
break;
case '-':
m_spec.sign = fmt_sign_t::MINUS;
break;
case ' ':
m_spec.sign = fmt_sign_t::SPACE;
break;
default:
{
std::stringstream ss;
ss << "Invalid sign " << value[0];
throw std::runtime_error(ss.str());
}
}
}
void fmt_parser_t::parse_form()
{
consume();
m_spec.alternate_form = true;
}
void fmt_parser_t::parse_width()
{
const auto value = parse_arg_or_number();
if (value.front() == '0' && !m_spec.prefix_char.has_value())
m_spec.prefix_char = '0';
m_spec.width = std::stoll(value);
}
void fmt_parser_t::parse_precision()
{
if (!has_next())
throw std::runtime_error("Missing token when parsing precision");
auto value = parse_arg_or_number();
m_spec.precision = std::stoll(std::string(value));
}
void fmt_parser_t::parse_type()
{
auto [_, value] = next();
if (value.size() != 1)
{
std::stringstream ss;
ss << "Type contains more than one character, we are not sure how to interpret this value '" << value << "'";
throw std::runtime_error(ss.str());
}
m_spec.uppercase = std::isupper(value.front());
switch (value.front())
{
case 'b':
case 'B':
m_spec.type = fmt_type_t::BINARY;
break;
case 'c':
m_spec.type = fmt_type_t::CHAR;
break;
case 'd':
m_spec.type = fmt_type_t::DECIMAL;
break;
case 'o':
m_spec.type = fmt_type_t::OCTAL;
break;
case 'x':
case 'X':
m_spec.type = fmt_type_t::HEX;
break;
case 'a':
case 'A':
m_spec.type = fmt_type_t::HEX_FLOAT;
break;
case 'e':
case 'E':
m_spec.type = fmt_type_t::EXPONENT;
break;
case 'f':
case 'F':
m_spec.type = fmt_type_t::FIXED_POINT;
break;
case 'g':
case 'G':
m_spec.type = fmt_type_t::GENERAL;
break;
case 't':
case 'T':
m_spec.type = fmt_type_t::TYPE;
break;
default:
std::stringstream ss;
ss << "Invalid type " << value;
ss << std::endl << std::endl;
ss << "Expected one of: " << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "b | B" << std::setw(6) << ' ' << "Print as binary output" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "c" << std::setw(6) << ' ' << "Print as character output" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "d" << std::setw(6) << ' ' << "Print as decimal (base 10) output" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "o" << std::setw(6) << ' ' << "Print as octal (base 8) output" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "x | X" << std::setw(6) << ' ' << "Print as hexadecimal (base 16) output" <<
std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "a | A" << std::setw(6) << ' ' << "Print floats as hexadecimal (base 16) output"
<< std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "e | E" << std::setw(6) << ' ' << "Print floats in scientific notation" <<
std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "f | F" << std::setw(6) << ' ' <<
"Print floats in fixed point output, useful for setting precision of output" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "g | G" << std::setw(6) << ' ' <<
"Print floats in general output, switching between fixed point and scientific notation based on magnitude" << std::endl;
ss << std::setw(4) << ' ' << std::left << std::setw(5) << "t | T" << std::setw(6) << ' ' << "Print the type as a string" << std::endl;
throw std::runtime_error(ss.str());
}
}
}

284
src/blt/logging/logging.cpp Normal file
View File

@ -0,0 +1,284 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <iomanip>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>
#include <blt/logging/injector.h>
#include <blt/logging/logging.h>
#include <blt/std/hashmap.h>
#include <blt/std/types.h>
namespace blt::logging
{
struct global_context_t
{
logging_config_t global_config;
};
static global_context_t global_context;
struct logging_thread_context_t
{
std::stringstream stream;
std::stringstream logging_stream;
std::string thread_name;
logger_t logger{stream};
};
logging_thread_context_t& get_thread_context()
{
thread_local logging_thread_context_t context;
return context;
}
std::string logger_t::to_string() const
{
return dynamic_cast<std::stringstream&>(m_stream).str();
}
size_t logger_t::find_ending_brace(size_t begin) const
{
size_t braces = 0;
for (; begin < m_fmt.size(); ++begin)
{
if (m_fmt[begin] == '{')
++braces;
else if (m_fmt[begin] == '}')
--braces;
if (braces == 0)
return begin;
}
return std::string::npos;
}
void logger_t::setup_stream(const fmt_spec_t& spec) const
{
if (spec.prefix_char)
m_stream << std::setfill(*spec.prefix_char);
else
m_stream << std::setfill(' ');
switch (spec.alignment)
{
case fmt_align_t::LEFT:
m_stream << std::left;
break;
case fmt_align_t::CENTER:
// TODO?
break;
case fmt_align_t::RIGHT:
m_stream << std::right;
break;
}
if (spec.width > 0)
m_stream << std::setw(static_cast<i32>(spec.width));
else
m_stream << std::setw(0);
if (spec.precision > 0)
m_stream << std::setprecision(static_cast<i32>(spec.precision));
else
m_stream << std::setprecision(16);
if (spec.alternate_form)
m_stream << std::showbase;
else
m_stream << std::noshowbase;
if (spec.uppercase)
m_stream << std::uppercase;
else
m_stream << std::nouppercase;
if (spec.sign == fmt_sign_t::PLUS)
m_stream << std::showpos;
else
m_stream << std::noshowpos;
}
std::string logger_t::process_string(const std::string_view str)
{
auto result = std::string(str);
size_t pos = 0;
while (pos = result.find('{', pos), pos != std::string::npos)
{
if (pos > 0 && result[pos - 1] == '\\')
{
auto before = result.substr(0, pos - 1);
auto after = result.substr(pos);
result = before + after;
} else
++pos;
}
return result;
}
void logger_t::process_strings()
{
auto spec_it = m_fmt_specs.begin();
auto str_it = m_string_sections.begin();
for (; spec_it != m_fmt_specs.end(); ++spec_it, ++str_it)
{
m_stream << process_string(*str_it);
auto arg_pos = spec_it->arg_id;
if (arg_pos == -1)
arg_pos = static_cast<i64>(m_arg_pos++);
setup_stream(*spec_it);
m_arg_print_funcs[arg_pos](m_stream, *spec_it);
}
m_stream << process_string(*str_it);
}
void logger_t::handle_type(std::ostream& stream, const fmt_spec_t& spec)
{
switch (spec.type)
{
case fmt_type_t::DECIMAL:
stream << std::noboolalpha;
stream << std::dec;
break;
case fmt_type_t::OCTAL:
stream << std::oct;
break;
case fmt_type_t::HEX:
stream << std::hex;
break;
case fmt_type_t::HEX_FLOAT:
stream << std::hexfloat;
break;
case fmt_type_t::EXPONENT:
stream << std::scientific;
break;
case fmt_type_t::FIXED_POINT:
stream << std::fixed;
break;
case fmt_type_t::GENERAL:
stream << std::defaultfloat;
break;
case fmt_type_t::UNSPECIFIED:
stream << std::boolalpha;
stream << std::dec;
break;
default:
break;
}
}
void logger_t::exponential(std::ostream& stream)
{
stream << std::scientific;
}
void logger_t::fixed(std::ostream& stream)
{
stream << std::fixed;
}
void logger_t::compile(std::string fmt)
{
m_fmt = std::move(fmt);
m_last_fmt_pos = 0;
m_arg_pos = 0;
auto& ss = dynamic_cast<std::stringstream&>(m_stream);
ss.str("");
m_stream.clear();
m_string_sections.clear();
m_fmt_specs.clear();
ptrdiff_t last_pos = 0;
while (auto pair = consume_to_next_fmt())
{
const auto [begin, end] = *pair;
m_string_sections.emplace_back(m_fmt.data() + last_pos, begin - last_pos);
if (end - begin > 1)
m_fmt_specs.push_back(m_parser.parse(std::string_view{m_fmt.data() + static_cast<ptrdiff_t>(begin) + 1, end - begin - 1}));
else
m_fmt_specs.emplace_back();
last_pos = static_cast<ptrdiff_t>(end) + 1;
}
m_string_sections.emplace_back(m_fmt.data() + last_pos, m_fmt.size() - last_pos);
m_last_fmt_pos = 0;
}
std::optional<std::pair<size_t, size_t>> logger_t::consume_to_next_fmt()
{
auto begin = m_fmt.find('{', m_last_fmt_pos);
while (begin != std::string::npos && begin > 0 && m_fmt[begin - 1] == '\\')
begin = m_fmt.find('{', begin + 1);;
if (begin == std::string::npos)
return {};
const auto end = find_ending_brace(begin);
if (end == std::string::npos)
{
std::stringstream ss;
ss << "Invalid format string, missing closing '}' near " << m_fmt.substr(std::min(static_cast<i64>(begin) - 5, static_cast<i64>(0)));
throw std::runtime_error(ss.str());
}
m_last_fmt_pos = end + 1;
return std::pair{begin, end};
}
logger_t& get_global_logger()
{
return get_thread_context().logger;
}
void print(std::string str)
{
const auto& config = get_global_config();
bool should_print = true;
if (!config.get_injectors().empty())
{
for (const auto& injector : config.get_injectors())
{
auto [new_logging_output, should_continue, should_log] = injector->inject(str);
if (!should_log)
should_print = false;
str = std::move(new_logging_output);
if (!should_continue)
break;
}
}
if (should_print)
std::cout << str;
}
void newline()
{
std::cout << std::endl;
}
logging_config_t& get_global_config()
{
return global_context.global_config;
}
void set_thread_name(const std::string& name)
{
get_thread_context().thread_name = name;
}
const std::string& get_thread_name()
{
return get_thread_context().thread_name;
}
std::ostream& get_local_stream()
{
auto& context = get_thread_context();
context.logging_stream.str("");
return context.logging_stream;
}
}

View File

@ -0,0 +1,333 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <ctime>
#include <iostream>
#include <blt/fs/path_helper.h>
#include <blt/fs/stream_wrappers.h>
#include <blt/logging/ansi.h>
#include <blt/logging/logging_config.h>
#include <blt/std/hashmap.h>
#include <blt/std/system.h>
#include <blt/std/time.h>
namespace blt::logging
{
namespace tags::detail
{
hashmap_t<std::string_view, log_tag_token_t> make_map()
{
hashmap_t<std::string_view, log_tag_token_t> map{};
map[YEAR] = log_tag_token_t::YEAR;
map[MONTH] = log_tag_token_t::MONTH;
map[DAY] = log_tag_token_t::DAY;
map[HOUR] = log_tag_token_t::HOUR;
map[MINUTE] = log_tag_token_t::MINUTE;
map[SECOND] = log_tag_token_t::SECOND;
map[MILLISECOND] = log_tag_token_t::MS;
map[NANOSECOND] = log_tag_token_t::NS;
map[UNIX_TIME] = log_tag_token_t::UNIX;
map[UNIX_TIME_NANO] = log_tag_token_t::UNIX_NANO;
map[ISO_YEAR] = log_tag_token_t::ISO_YEAR;
map[TIME] = log_tag_token_t::TIME;
map[FULL_TIME] = log_tag_token_t::FULL_TIME;
map[LOG_COLOR] = log_tag_token_t::LC;
map[ERROR_COLOR] = log_tag_token_t::EC;
map[CONDITIONAL_ERROR_COLOR] = log_tag_token_t::CEC;
map[RESET] = log_tag_token_t::RESET;
map[LOG_LEVEL] = log_tag_token_t::LL;
map[THREAD_NAME] = log_tag_token_t::TN;
map[FILE] = log_tag_token_t::FILE;
map[LINE] = log_tag_token_t::LINE;
map[STR] = log_tag_token_t::STR;
return map;
}
}
void logging_config_t::compile()
{
static hashmap_t<std::string_view, tags::detail::log_tag_token_t> tag_map = tags::detail::make_map();
m_log_tag_content.clear();
m_log_tag_tokens.clear();
size_t i = 0;
for (; i < m_log_format.size(); ++i)
{
size_t start = i;
while (i < m_log_format.size() && m_log_format[i] != '{')
++i;
if (i == m_log_format.size() || (i < m_log_format.size() && (i - start) > 0))
{
m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + start, i - start));
m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT);
if (i == m_log_format.size())
break;
}
start = i;
while (i < m_log_format.size() && m_log_format[i] != '}')
++i;
const auto tag = std::string_view(m_log_format.data() + start, i - start + 1);
auto it = tag_map.find(tag);
if (it == tag_map.end())
throw std::runtime_error("Invalid log tag: " + std::string(tag));
m_log_tag_tokens.emplace_back(it->second);
}
if (i < m_log_format.size())
{
m_log_tag_content.emplace_back(std::string_view(m_log_format.data() + i, m_log_format.size() - i));
m_log_tag_tokens.emplace_back(tags::detail::log_tag_token_t::CONTENT);
}
m_longest_name_length = 0;
for (const auto& name : m_log_level_names)
m_longest_name_length = std::max(m_longest_name_length, name.size());
}
std::string add_year(const tm* current_time)
{
return std::to_string(current_time->tm_year + 1900);
}
std::string add_month(const tm* current_time, const bool ensure_alignment)
{
auto str = std::to_string(current_time->tm_mon + 1);
if (ensure_alignment && str.size() < 2)
str.insert(str.begin(), '0');
return str;
}
std::string add_day(const tm* current_time, const bool ensure_alignment)
{
auto str = std::to_string(current_time->tm_mday);
if (ensure_alignment && str.size() < 2)
str.insert(str.begin(), '0');
return str;
}
std::string add_hour(const tm* current_time, const bool ensure_alignment)
{
auto str = std::to_string(current_time->tm_hour);
if (ensure_alignment && str.size() < 2)
str.insert(str.begin(), '0');
return str;
}
std::string add_minute(const tm* current_time, const bool ensure_alignment)
{
auto str = std::to_string(current_time->tm_min);
if (ensure_alignment && str.size() < 2)
str.insert(str.begin(), '0');
return str;
}
std::string add_second(const tm* current_time, const bool ensure_alignment)
{
auto str = std::to_string(current_time->tm_sec);
if (ensure_alignment && str.size() < 2)
str.insert(str.begin(), '0');
return str;
}
std::optional<std::string> logging_config_t::generate(const std::string& user_str, const std::string& thread_name, const log_level_t level,
const char* file, const i32 line) const
{
if (level < m_level)
return {};
std::string fmt;
const std::time_t time = std::time(nullptr);
const auto current_time = std::localtime(&time);
const auto millis_time = system::getCurrentTimeMilliseconds();
const auto nano_time = system::getCurrentTimeNanoseconds();
size_t content = 0;
for (const auto& log_tag_token : m_log_tag_tokens)
{
switch (log_tag_token)
{
case tags::detail::log_tag_token_t::YEAR:
fmt += add_year(current_time);
break;
case tags::detail::log_tag_token_t::MONTH:
fmt += add_month(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::DAY:
fmt += add_day(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::HOUR:
fmt += add_hour(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::MINUTE:
fmt += add_minute(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::SECOND:
fmt += add_second(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::MS:
{
auto str = std::to_string(millis_time % 1000);
if (m_ensure_alignment)
{
for (size_t i = str.size(); i < 4; ++i)
str.insert(str.begin(), '0');
}
fmt += str;
break;
}
case tags::detail::log_tag_token_t::NS:
{
auto str = std::to_string(nano_time % 1000000000ul);
if (m_ensure_alignment)
{
for (size_t i = str.size(); i < 9; ++i)
str.insert(str.begin(), '0');
}
fmt += str;
break;
}
case tags::detail::log_tag_token_t::UNIX:
{
fmt += std::to_string(millis_time);
break;
}
case tags::detail::log_tag_token_t::UNIX_NANO:
{
fmt += std::to_string(nano_time);
break;
}
case tags::detail::log_tag_token_t::ISO_YEAR:
{
fmt += add_year(current_time);
fmt += '-';
fmt += add_month(current_time, m_ensure_alignment);
fmt += '-';
fmt += add_day(current_time, m_ensure_alignment);
break;
}
case tags::detail::log_tag_token_t::TIME:
fmt += add_hour(current_time, m_ensure_alignment);
fmt += ':';
fmt += add_minute(current_time, m_ensure_alignment);
fmt += ':';
fmt += add_second(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::FULL_TIME:
fmt += add_year(current_time);
fmt += '-';
fmt += add_month(current_time, m_ensure_alignment);
fmt += '-';
fmt += add_day(current_time, m_ensure_alignment);
fmt += ' ';
fmt += add_hour(current_time, m_ensure_alignment);
fmt += ':';
fmt += add_minute(current_time, m_ensure_alignment);
fmt += ':';
fmt += add_second(current_time, m_ensure_alignment);
break;
case tags::detail::log_tag_token_t::LC:
if (!m_use_color)
break;
fmt += m_log_level_colors[static_cast<u8>(level)];
break;
case tags::detail::log_tag_token_t::EC:
if (!m_use_color)
break;
fmt += m_error_color;
break;
case tags::detail::log_tag_token_t::CEC:
if (!m_use_color)
break;
if (static_cast<u8>(level) >= static_cast<u8>(log_level_t::ERROR))
fmt += m_error_color;
break;
case tags::detail::log_tag_token_t::RESET:
if (!m_use_color)
break;
fmt += build(ansi::color::color_mode::RESET_ALL);
break;
case tags::detail::log_tag_token_t::LL:
fmt += m_log_level_names[static_cast<u8>(level)];
break;
case tags::detail::log_tag_token_t::TN:
fmt += thread_name;
break;
case tags::detail::log_tag_token_t::FILE:
if (m_print_full_name)
fmt += file;
else
fmt += fs::filename_sv(file);
break;
case tags::detail::log_tag_token_t::LINE:
fmt += std::to_string(line);
break;
case tags::detail::log_tag_token_t::STR:
fmt += user_str;
break;
case tags::detail::log_tag_token_t::CONTENT:
fmt += m_log_tag_content[content++];
break;
}
}
return fmt;
}
std::string logging_config_t::get_default_log_format()
{
return build(fg(ansi::color::color8_bright::BLUE)) + "[" + tags::FULL_TIME + "]" + tags::RESET + " " + tags::LOG_COLOR + "[" + tags::LOG_LEVEL
+ "]" + tags::RESET + " " + build(fg(ansi::color::color8::MAGENTA)) + "(" + tags::FILE + ":" + tags::LINE + ")" + tags::RESET + " " +
tags::CONDITIONAL_ERROR_COLOR + tags::STR + tags::RESET + "\n";
}
std::vector<fs::writer_t*> logging_config_t::get_default_log_outputs()
{
static fs::fstream_writer_t cout_writer{std::cout};
std::vector<fs::writer_t*> outputs{};
outputs.push_back(&cout_writer);
return outputs;
}
std::array<std::string, LOG_LEVEL_COUNT> logging_config_t::get_default_log_level_colors()
{
return {
// TRACE
build(fg(ansi::color::color8_bright::WHITE)),
// DEBUG
build(fg(ansi::color::color8::CYAN)),
// INFO
build(fg(ansi::color::color8_bright::GREEN)),
// WARN
build(fg(ansi::color::color8_bright::YELLOW)),
// ERROR
build(fg(ansi::color::color8_bright::RED)),
// FATAL
build(fg(ansi::color::color8_bright::WHITE), bg(ansi::color::color8_bright::RED)),
};
}
std::array<std::string, LOG_LEVEL_COUNT> logging_config_t::get_default_log_level_names()
{
return {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL",};
}
std::string logging_config_t::get_default_error_color()
{
return build(fg(ansi::color::color8_bright::RED));
}
}

225
src/blt/logging/status.cpp Normal file
View File

@ -0,0 +1,225 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <cstdio>
#include <iostream>
#ifdef unix
#include <termios.h>
#include <unistd.h>
#endif
#include <blt/logging/ansi.h>
#include <blt/logging/fmt_tokenizer.h>
#include <blt/logging/logging.h>
#include <blt/logging/status.h>
#include <blt/math/vectors.h>
#include <blt/math/log_util.h>
namespace blt::logging
{
vec2i get_cursor_position()
{
#ifdef unix
termios save{}, raw{};
tcgetattr(0, &save);
cfmakeraw(&raw);
tcsetattr(0,TCSANOW, &raw);
char buf[32];
char cmd[] = "\033[6n";
int row = 0;
int col = 0;
if (isatty(fileno(stdin)))
{
ssize_t i = write(1, cmd, sizeof(cmd));
(void) i;
i = read(0, buf, sizeof(buf));
(void) i;
int sep = 0;
int end = 0;
for (int i = 2; i < 8; i++)
{
if (buf[i] == ';')
sep = i;
if (buf[i] == 'R')
{
end = i;
break;
}
}
row = std::stoi(std::string(buf + 2, buf + sep));
col = std::stoi(std::string(buf + sep + 1, buf + end));
}
tcsetattr(0,TCSANOW, &save);
return vec2i{row, col};
#else
return {0,0};
#endif
}
#define SIZE 100
vec2i get_screen_size()
{
#ifdef unix
char in[SIZE] = "";
int each = 0;
int ch = 0;
int rows = 0;
int cols = 0;
termios original, changed;
// change terminal settings
tcgetattr( STDIN_FILENO, &original);
changed = original;
changed.c_lflag &= ~( ICANON | ECHO);
changed.c_cc[VMIN] = 1;
changed.c_cc[VTIME] = 0;
tcsetattr( STDIN_FILENO, TCSANOW, &changed);
printf ( "\033[2J"); //clear screen
printf ( "\033[9999;9999H"); // cursor should move as far as it can
printf ( "\033[6n"); // ask for cursor position
while ( ( ch = getchar ()) != 'R') { // R terminates the response
if ( EOF == ch) {
break;
}
if ( isprint ( ch)) {
if ( each + 1 < SIZE) {
in[each] = ch;
each++;
in[each] = '\0';
}
}
}
printf ( "\033[1;1H"); // move to upper left corner
if ( 2 == sscanf ( in, "[%d;%d", &rows, &cols)) {
tcsetattr( STDIN_FILENO, TCSANOW, &original);
return {rows, cols};
}
throw std::runtime_error("Could not get screen size");
#else
return {0,0};
#endif
}
i32 get_size_no_ansi(const std::string& str)
{
i32 size = 0;
for (size_t i = 0; i < str.size(); i++)
{
if (str[i] == BLT_ANSI_ESCAPE[0])
{
while (i < str.size())
{
if (std::isalpha(str[i++]))
break;
}
}
++size;
}
return size - 1;
}
std::string status_progress_bar_t::print(const vec2i, const i32 max_printed_length) const
{
std::string output = "[";
output.reserve(max_printed_length);
const auto amount_filled = (max_printed_length - 2) * m_progress;
auto amount_filled_int = static_cast<i32>(amount_filled);
const auto frac = amount_filled - static_cast<double>(amount_filled_int);
for (i64 i = 0; i < amount_filled_int; i++)
output += '#';
if (frac >= 0.5)
{
output += '|';
++amount_filled_int;
}
for (i64 i = amount_filled_int; i < max_printed_length - 2; i++)
output += ' ';
output += ']';
return output;
}
void status_progress_bar_t::set_progress(const double progress)
{
if (std::isnan(progress) || progress < 0 || progress > 1 || std::isinf(progress))
throw std::invalid_argument("Progress must be between 0 and 1 (got: " + std::to_string(progress) + ")");
m_progress = progress;
// m_status->redraw();
}
status_bar_t::status_bar_t()
{
m_screen_size = get_screen_size();
std::cout << ansi::cursor::home << std::flush;
std::cout << ansi::erase::entire_screen << std::flush;
m_begin_position = m_last_log_position = get_cursor_position();
std::cout << ansi::cursor::hide_cursor << std::flush;
}
injector_output_t status_bar_t::inject(const std::string& input)
{
std::scoped_lock lock{m_print_mutex};
injector_output_t output{input, false, false};
if (output.new_logging_output.back() != '\n')
output.new_logging_output += '\n';
if (get_cursor_position() != m_begin_position)
{
for (int i = 0; i < m_status_size; i++)
std::cout << ansi::erase::entire_line << ansi::cursor::move_begin_up(1) << std::flush;
}
std::cout << ansi::erase::entire_line << std::flush;
std::cout << output.new_logging_output << std::flush;
m_max_printed_length = std::max(get_size_no_ansi(output.new_logging_output), m_max_printed_length);
m_last_log_position = get_cursor_position();
redraw();
return output;
}
void status_bar_t::compute_size()
{
m_status_size = 0;
for (const auto* ptr : m_status_items)
m_status_size += ptr->lines_used();
}
void status_bar_t::redraw() const
{
std::cout << ansi::cursor::move_to(m_last_log_position.x(), m_last_log_position.y());
for (const auto* ptr : m_status_items)
std::cout << ansi::erase::entire_line << ptr->print(m_screen_size, m_max_printed_length) << std::endl;
std::cout << std::flush;
}
status_bar_t::~status_bar_t()
{
std::cout << ansi::cursor::show_cursor << std::flush;
}
}

View File

@ -196,7 +196,7 @@ namespace blt
printUsage();
std::cout << getProgramName() << ": error: flag '" << flag << "' expected " << properties.a_nargs.args
<< " argument(s) but found '" << tokenizer.get() << "' instead!\n";
//BLT_WARN("Expected %d arguments, found flag instead!", properties.a_nargs.args);
//BLT_WARN("Expected {:d} arguments, found flag instead!", properties.a_nargs.args);
return false;
}
// get the value and advance

File diff suppressed because it is too large Load Diff

View File

@ -26,231 +26,231 @@
#include <charconv>
#include "blt/std/assert.h"
#include "blt/std/utility.h"
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
namespace blt::parse
{
class char_tokenizer
{
private:
std::string_view string;
std::size_t current_pos = 0;
public:
explicit char_tokenizer(std::string_view view): string(view)
{}
class char_tokenizer
{
private:
std::string_view string;
std::size_t current_pos = 0;
inline char advance()
{
return string[current_pos++];
}
public:
explicit char_tokenizer(std::string_view view): string(view)
{}
inline bool has_next(size_t offset = 0)
{
return current_pos + offset < string.size();
}
inline char advance()
{
return string[current_pos++];
}
inline std::string_view read_fully()
{
return blt::string::trim(string.substr(current_pos));
}
};
inline bool has_next(size_t offset = 0)
{
return current_pos + offset < string.size();
}
template<typename T = float>
T get(std::string_view str)
{
T x;
// TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point.
#if __cplusplus >= BLT_CPP20
inline std::string_view read_fully()
{
return blt::string::trim(string.substr(current_pos));
}
};
template <typename T = float>
T get(std::string_view str)
{
T x;
// TODO: GCC version. C++17 supports from_chars but GCC8.5 doesn't have floating point.
#if __cplusplus >= BLT_CPP20
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), x);
#else
auto ec = std::errc();
if constexpr (std::is_floating_point_v<T>)
{
x = static_cast<T>(std::stod(std::string(str)));
} else if constexpr (std::is_integral_v<T>)
{
x = static_cast<T>(std::stoll(std::string(str)));
} else
static_assert(
"You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!");
#endif
// probably not needed.
if (ec != std::errc())
{
// int i;
// const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i);
// if (ec2 == std::errc())
// {
// x = static_cast<float>(i);
// } else
// {
BLT_WARN("Unable to parse string '%s' into number!", std::string(str).c_str());
x = 0;
// }
}
return x;
}
#else
auto ec = std::errc();
if constexpr (std::is_floating_point_v<T>)
{
x = static_cast<T>(std::stod(std::string(str)));
} else if constexpr (std::is_integral_v<T>)
{
x = static_cast<T>(std::stoll(std::string(str)));
} else
static_assert(
"You are using a c++ version which does not support the required std::from_chars, manual conversion has failed to find a type!");
#endif
// probably not needed.
if (ec != std::errc())
{
// int i;
// const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i);
// if (ec2 == std::errc())
// {
// x = static_cast<float>(i);
// } else
// {
BLT_WARN("Unable to parse string '{}' into number!", std::string(str).c_str());
x = 0;
// }
}
return x;
}
void obj_loader::parse_vertex_line(char_tokenizer& tokenizer)
{
char type = tokenizer.advance();
void obj_loader::parse_vertex_line(char_tokenizer& tokenizer)
{
char type = tokenizer.advance();
if (type == 'p')
{
BLT_WARN("Unexpected type '%c' (not supported)", type);
return;
}
if (type == 'p')
{
BLT_WARN("Unexpected type '{:c}' (not supported)", type);
return;
}
auto elements = blt::string::split(std::string(tokenizer.read_fully()), " ");
BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!");
float x = get(elements[0]), y = get(elements[1]);
BLT_DEBUG_STREAM << "Loaded value of (" << x << ", " << y << ")";
if (elements.size() < 3)
{
if (type == 't')
uvs.push_back(uv_t{x, y});
else
BLT_ERROR("Unable to parse line '%s' type '%c' not recognized for arg count", std::string(tokenizer.read_fully()).c_str(), type);
} else
{
float z = get(elements[2]);
BLT_DEBUG_STREAM << " with z: " << z;
if (!handle_vertex_and_normals(x, y, z, type))
BLT_ERROR("Unable to parse line '%s' type '%c' not recognized", std::string(tokenizer.read_fully()).c_str(), type);
}
BLT_DEBUG_STREAM << "\n";
}
auto elements = string::split(tokenizer.read_fully(), " ");
BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!");
float x = get(elements[0]), y = get(elements[1]);
BLT_DEBUG("Loaded value of ({}, {})", x, y);
if (elements.size() < 3)
{
if (type == 't')
uvs.push_back(uv_t{x, y});
else
BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized for arg count", tokenizer.read_fully(), type);
} else
{
float z = get(elements[2]);
BLT_DEBUG(" with z: {}", z);
if (!handle_vertex_and_normals(x, y, z, type))
BLT_ERROR("Unable to parse line '{}' type '{:c}' not recognized", tokenizer.read_fully(), type);
}
}
bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type)
{
if (std::isspace(type))
{
vertices.push_back(vertex_t{x, y, z});
} else if (type == 'n')
{
normals.push_back(normal_t{x, y, z});
} else
return false;
return true;
}
bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type)
{
if (std::isspace(type))
{
vertices.push_back(vertex_t{x, y, z});
} else if (type == 'n')
{
normals.push_back(normal_t{x, y, z});
} else
return false;
return true;
}
obj_model_t quick_load(std::string_view file)
{
return obj_loader().parseFile(file);
}
obj_model_t quick_load(std::string_view file)
{
return obj_loader().parseFile(file);
}
obj_model_t obj_loader::parseFile(std::string_view file)
{
auto lines = blt::fs::getLinesFromFile(std::string(file));
for (const auto& [index, line] : blt::enumerate(lines))
{
current_line = index;
char_tokenizer token(line);
if (!token.has_next() || token.read_fully().empty())
continue;
switch (token.advance())
{
case '#':
continue;
case 'f':
parse_face(token);
break;
case 'v':
parse_vertex_line(token);
break;
case 'o':
{
current_object.object_names.emplace_back(token.read_fully());
BLT_TRACE("Setting object '%s'", std::string(current_object.object_name).c_str());
break;
}
case 'm':
{
while (token.has_next() && token.advance() != ' ')
{}
BLT_WARN("Material '%s' needs to be loaded!", std::string(token.read_fully()).c_str());
break;
}
case 'u':
{
if (!current_object.indices.empty())
data.push_back(current_object);
current_object = {};
while (token.has_next() && token.advance() != ' ')
{}
current_object.material = token.read_fully();
//BLT_WARN("Using material '%s'", std::string(token.read_fully()).c_str());
break;
}
case 's':
//BLT_WARN("Using shading: %s", std::string(token.read_fully()).c_str());
break;
}
}
data.push_back(current_object);
return {std::move(vertex_data), std::move(data), std::move(materials)};
}
obj_model_t obj_loader::parseFile(std::string_view file)
{
auto lines = blt::fs::getLinesFromFile(std::string(file));
for (const auto& [index, line] : blt::enumerate(lines))
{
current_line = index;
char_tokenizer token(line);
if (!token.has_next() || token.read_fully().empty())
continue;
switch (token.advance())
{
case '#':
continue;
case 'f':
parse_face(token);
break;
case 'v':
parse_vertex_line(token);
break;
case 'o':
{
current_object.object_names.emplace_back(token.read_fully());
BLT_TRACE("Setting object '{}'", std::string(current_object.object_name).c_str());
break;
}
case 'm':
{
while (token.has_next() && token.advance() != ' ')
{}
BLT_WARN("Material '{}' needs to be loaded!", std::string(token.read_fully()).c_str());
break;
}
case 'u':
{
if (!current_object.indices.empty())
data.push_back(current_object);
current_object = {};
while (token.has_next() && token.advance() != ' ')
{}
current_object.material = token.read_fully();
//BLT_WARN("Using material '{}'", std::string(token.read_fully()).c_str());
break;
}
case 's':
//BLT_WARN("Using shading: {}", std::string(token.read_fully()).c_str());
break;
}
}
data.push_back(current_object);
return {std::move(vertex_data), std::move(data), std::move(materials)};
}
void obj_loader::parse_face(char_tokenizer& tokenizer)
{
auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' ');
if (faces.size() == 3)
{
triangle_t triangle{};
handle_face_vertex(faces, triangle.v);
current_object.indices.push_back(triangle);
} else if (faces.size() == 4)
{
quad_t quad{};
handle_face_vertex(faces, quad.v);
triangle_t t1{};
triangle_t t2{};
void obj_loader::parse_face(char_tokenizer& tokenizer)
{
auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' ');
if (faces.size() == 3)
{
triangle_t triangle{};
handle_face_vertex(faces, triangle.v);
current_object.indices.push_back(triangle);
} else if (faces.size() == 4)
{
quad_t quad{};
handle_face_vertex(faces, quad.v);
triangle_t t1{};
triangle_t t2{};
for (int i = 0; i < 3; i++)
t1.v[i] = quad.v[i];
t2.v[0] = quad.v[0];
t2.v[1] = quad.v[2];
t2.v[2] = quad.v[3];
for (int i = 0; i < 3; i++)
t1.v[i] = quad.v[i];
t2.v[0] = quad.v[0];
t2.v[1] = quad.v[2];
t2.v[2] = quad.v[3];
current_object.indices.push_back(t1);
current_object.indices.push_back(t2);
} else
BLT_WARN("Unsupported face vertex count of %d on line %d!", faces.size(), current_line);
}
current_object.indices.push_back(t1);
current_object.indices.push_back(t2);
} else
BLT_WARN("Unsupported face vertex count of {:d} on line {:d}!", faces.size(), current_line);
}
void obj_loader::handle_face_vertex(const std::vector<std::string>& face_list, int32_t* arr)
{
for (const auto& [e_index, value] : blt::enumerate(face_list))
{
auto indices = blt::string::split(value, '/');
BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!");
void obj_loader::handle_face_vertex(const std::vector<std::string>& face_list, int32_t* arr)
{
for (const auto& [e_index, value] : blt::enumerate(face_list))
{
auto indices = blt::string::split(value, '/');
BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!");
auto vi = get<std::int32_t>(indices[0]) - 1;
auto ui = get<std::int32_t>(indices[1]) - 1;
auto ni = get<std::int32_t>(indices[2]) - 1;
auto vi = get<std::int32_t>(indices[0]) - 1;
auto ui = get<std::int32_t>(indices[1]) - 1;
auto ni = get<std::int32_t>(indices[2]) - 1;
BLT_DEBUG("Found vertex: %d, UV: %d, and normal: %d", vi, ui, ni);
BLT_DEBUG("Found vertex: {:d}, UV: {:d}, and normal: {:d}", vi, ui, ni);
face_t face{vi, ui, ni};
face_t face{vi, ui, ni};
auto loc = vertex_map.find(face);
if (loc == vertex_map.end())
{
BLT_DEBUG("DID NOT FIND FACE!");
auto index = static_cast<std::int32_t>(vertex_data.size());
vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]});
BLT_DEBUG("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", vertices[vi].x(), vertices[vi].y(), vertices[vi].z(),
uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z());
vertex_map.insert({face, index});
arr[e_index] = index;
} else
{
BLT_TRACE("Using cached data; %d; map size: %d", loc->second, vertex_data.size());
//const auto& d = vertex_data[loc->second];
BLT_TRACE("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", d.vertex.x(), d.vertex.y(), d.vertex.z(),
d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z());
arr[e_index] = loc->second;
}
}
}
auto loc = vertex_map.find(face);
if (loc == vertex_map.end())
{
BLT_DEBUG("DID NOT FIND FACE!");
auto index = static_cast<std::int32_t>(vertex_data.size());
vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]});
BLT_DEBUG("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", vertices[vi].x(), vertices[vi].y(),
vertices[vi].z(), uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z());
vertex_map.insert({face, index});
arr[e_index] = index;
} else
{
BLT_TRACE("Using cached data; {:d}; map size: {:d}", loc->second, vertex_data.size());
//const auto& d = vertex_data[loc->second];
BLT_TRACE("Vertex: ({.4f}, {.4f}, {.4f}), UV: ({.4f}, {.4f}), Normal: ({.4f}, {.4f}, {:.4f})", d.vertex.x(), d.vertex.y(),
d.vertex.z(), d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z());
arr[e_index] = loc->second;
}
}
}
}

View File

@ -18,7 +18,7 @@
#include <blt/parse/templating.h>
#include <blt/std/string.h>
#include <cctype>
#include "blt/std/logging.h"
#include "blt/logging/logging.h"
namespace blt
{
@ -275,7 +275,7 @@ namespace blt
values.push_back(b1 ^ b2);
break;
default:
BLT_WARN("Unexpected token '%s'", std::string(next.token).c_str());
BLT_WARN("Unexpected token '{}'", std::string(next.token).c_str());
return blt::unexpected(template_parser_failure_t::BOOL_TYPE_NOT_FOUND);
}
}

View File

@ -7,7 +7,7 @@
#include <mutex>
#include <vector>
#include <blt/std/time.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <iostream>
#include <algorithm>
#include <blt/format/format.h>
@ -32,9 +32,9 @@ namespace blt::profiling {
difference(difference), name(std::move(name)), total(total) {}
};
inline void println(const std::vector<std::string>&& lines, logging::log_level level) {
inline void println(const std::vector<std::string>&& lines, const logging::log_level_t level) {
for (const auto& line : lines)
BLT_LOG_STREAM(level) << line << "\n";
BLT_LOG(level, "{}", line);
// auto& logger = logging::getLoggerFromLevel(level);
// for (const auto& line : lines)
// logger << line << "\n";
@ -108,7 +108,7 @@ namespace blt::profiling {
}
void printProfile(
const std::string& profileName, logging::log_level loggingLevel, bool averageHistory
const std::string& profileName, const logging::log_level_t loggingLevel, const bool averageHistory
) {
auto& profile = profiles[profileName];
const auto& intervals = profile.intervals;

View File

@ -187,11 +187,11 @@ namespace blt
stream << line << "\n";
}
void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level log_level)
void printProfile(profile_t& profiler, const std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level)
{
std::stringstream stream;
writeProfile(stream, profiler, flags, sort);
BLT_LOG_STREAM(log_level) << stream.str();
BLT_LOG(log_level, "{}", stream.str());
}
profile_t::~profile_t()
@ -237,7 +237,7 @@ namespace blt
profiles.erase(profile_name);
}
void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level log_level)
void _internal::printProfile(const std::string& profile_name, std::uint32_t flags, sort_by sort, blt::logging::log_level_t log_level)
{
if (profiles.find(profile_name) == profiles.end())
return;

View File

@ -3,15 +3,15 @@
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#include <blt/std/assert.h>
#include <blt/std/utility.h>
#include <blt/std/logging.h>
#include <blt/std/string.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <exception>
#include <cstring>
#include <exception>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <blt/logging/logging.h>
#include <blt/std/assert.h>
#include <blt/std/string.h>
#include <blt/std/utility.h>
struct abort_exception final : public std::exception
{
@ -48,8 +48,8 @@ struct abort_exception final : public std::exception
#ifdef IS_GNU_BACKTRACE
#include <execinfo.h>
#include <cstdlib>
#include <execinfo.h>
#endif
@ -86,7 +86,7 @@ namespace blt
#ifdef IS_GNU_BACKTRACE
BLT_STACK_TRACE(50);
BLT_ERROR("An exception '%s' has occurred in file '%s:%d'", what, path, line);
BLT_ERROR("An exception '{}' has occurred in file '{}:{:d}'", what, path, line);
BLT_ERROR("Stack Trace:");
printStacktrace(messages, size, path, line);
@ -103,7 +103,7 @@ namespace blt
#ifdef IS_GNU_BACKTRACE
BLT_STACK_TRACE(50);
BLT_ERROR("The assertion '%s' has failed in file '%s:%d'", expression, path, line);
BLT_ERROR("The assertion '{}' has failed in file '{}:{:d}'", expression, path, line);
if (msg != nullptr)
BLT_ERROR(msg);
BLT_ERROR("Stack Trace:");
@ -175,8 +175,8 @@ namespace blt
BLT_STACK_TRACE(50);
#endif
BLT_FATAL("----{BLT ABORT}----");
BLT_FATAL("\tWhat: %s", what);
BLT_FATAL("\tCalled from %s:%d", path, line);
BLT_FATAL("\tWhat: {}", what);
BLT_FATAL("\tCalled from {}:{:d}", path, line);
#ifdef IS_GNU_BACKTRACE
printStacktrace(messages, size, path, line);

View File

@ -15,68 +15,52 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/std/error.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
namespace blt::error
{
void print_socket_error()
{
switch (errno)
{
case EINVAL:
BLT_WARN("Invalid argument");
break;
case EACCES:
BLT_WARN("Permission denied");
break;
case EPERM:
BLT_WARN("Operation not permitted");
break;
case EADDRINUSE:
BLT_WARN("Address already in use");
break;
case EADDRNOTAVAIL:
BLT_WARN("Cannot copy_fast requested address");
break;
case EAFNOSUPPORT:
BLT_WARN("Address family not supported by protocol");
break;
case EAGAIN:
BLT_WARN("Try again");
break;
case EALREADY:
BLT_WARN("Operation already in progress");
break;
case EBADF:
BLT_WARN("Bad file number");
break;
case ECONNREFUSED:
BLT_WARN("Connection refused");
break;
case EFAULT:
BLT_WARN("Bad address");
break;
case EINPROGRESS:
BLT_WARN("Operation now in progress");
break;
case EINTR:
BLT_WARN("Interrupted system call");
break;
case EISCONN:
BLT_WARN("Transport endpoint is already connected");
break;
case ENETUNREACH:
BLT_WARN("Network is unreachable");
break;
case ENOTSOCK:
BLT_WARN("Socket operation_t on non-socket");
break;
case EPROTOTYPE:
BLT_WARN("Protocol wrong type for socket");
break;
case ETIMEDOUT:
BLT_WARN("Connection timed out");
break;
}
}
void print_socket_error()
{
switch (errno)
{
case EINVAL: BLT_WARN("Invalid argument");
break;
case EACCES: BLT_WARN("Permission denied");
break;
case EPERM: BLT_WARN("Operation not permitted");
break;
case EADDRINUSE: BLT_WARN("Address already in use");
break;
case EADDRNOTAVAIL: BLT_WARN("Cannot copy_fast requested address");
break;
case EAFNOSUPPORT: BLT_WARN("Address family not supported by protocol");
break;
case EAGAIN: BLT_WARN("Try again");
break;
case EALREADY: BLT_WARN("Operation already in progress");
break;
case EBADF: BLT_WARN("Bad file number");
break;
case ECONNREFUSED: BLT_WARN("Connection refused");
break;
case EFAULT: BLT_WARN("Bad address");
break;
case EINPROGRESS: BLT_WARN("Operation now in progress");
break;
case EINTR: BLT_WARN("Interrupted system call");
break;
case EISCONN: BLT_WARN("Transport endpoint is already connected");
break;
case ENETUNREACH: BLT_WARN("Network is unreachable");
break;
case ENOTSOCK: BLT_WARN("Socket operation_t on non-socket");
break;
case EPROTOTYPE: BLT_WARN("Protocol wrong type for socket");
break;
case ETIMEDOUT: BLT_WARN("Connection timed out");
break;
default:
break;
}
}
}

View File

@ -1,7 +0,0 @@
/*
* Created by Brett on 20/07/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#define BLT_LOGGING_IMPLEMENTATION
#include <blt/std/logging.h>

View File

@ -15,6 +15,7 @@
* 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 <blt/logging/logging.h>
#include <blt/std/mmap.h>
#ifdef __unix__
@ -112,7 +113,7 @@ namespace blt
#ifdef __unix__
if (munmap(ptr, bytes))
{
BLT_ERROR_STREAM << "Failed to deallocate\n";
BLT_ERROR("Failed to deallocate");
throw bad_alloc_t(handle_mmap_error());
}
#else

View File

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <blt/std/simd.h>
#include <blt/std/logging.h>
namespace blt
{

View File

@ -4,7 +4,7 @@
* See LICENSE file for license detail
*/
#include <blt/std/system.h>
#include <blt/std/logging.h>
#include <blt/logging/logging.h>
#if !defined(_MSC_VER) && !defined(WIN32)
#include <sys/time.h> /* for struct timeval */
@ -94,7 +94,7 @@ namespace blt
if (GetProcessTimes(GetCurrentProcess(),
&starttime, &exittime, &kerneltime, &usertime) == 0)
{
BLT_WARN("Unable to get process resource usage, error: %d", GetLastError());
BLT_WARN("Unable to get process resource usage, error: {:d}", GetLastError());
return {};
}
@ -111,7 +111,7 @@ namespace blt
#else
if (getrusage(who, (struct rusage*) &usage) != 0)
{
BLT_ERROR("Failed to get rusage %d", errno);
BLT_ERROR("Failed to get rusage {:d}", errno);
return {};
}
#endif

3
test.txt Normal file
View File

@ -0,0 +1,3 @@
This is a println with a stream
This is a mixed print 25 with multiple types 34.233400
What about just a new line character?

23
tests/argparse_tests.cpp Normal file
View File

@ -0,0 +1,23 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <blt/parse/argparse_v2.h>
int main(){
blt::argparse::detail::test();
return 0;
}

View File

@ -15,7 +15,7 @@
* 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 <blt/std/logging.h>
#include <blt/logging/logging.h>
#include <blt/std/types.h>
#include <blt/std/assert.h>
#include <blt/math/vectors.h>
@ -64,18 +64,18 @@ void test_enumerate()
{
blt::log_box_t box(std::cout, "Enumerate Tests", 25);
for (const auto& [index, item] : blt::enumerate(array_1))
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_TRACE("");
for (const auto& [index, item] : blt::enumerate(array_1).rev())
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_TRACE("");
for (const auto& [index, item] : blt::enumerate(array_1).take(3))
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index < 3);
}
@ -83,7 +83,7 @@ void test_enumerate()
for (const auto& [index, item] : blt::enumerate(array_1).take(3).rev())
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index < 3);
}
@ -91,7 +91,7 @@ void test_enumerate()
for (const auto& [index, item] : blt::enumerate(array_1).skip(3))
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index >= 3);
}
@ -99,7 +99,7 @@ void test_enumerate()
for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev())
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index >= 3);
}
@ -107,7 +107,7 @@ void test_enumerate()
for (const auto& [index, item] : blt::enumerate(array_1).skip(3).take(5))
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index >= 3 && index < (array_1.size() - 5) + 3);
}
@ -115,7 +115,7 @@ void test_enumerate()
for (const auto& [index, item] : blt::enumerate(array_1).skip(3).rev().take(5))
{
BLT_TRACE_STREAM << index << " : " << item << "\n";
BLT_TRACE("{}, : {}", index, item);
BLT_ASSERT(index >= 5);
}
}
@ -125,7 +125,7 @@ void test_pairs()
blt::log_box_t box(std::cout, "Pairs Tests", 25);
for (auto [a1, a2] : blt::in_pairs(array_1, array_2))
{
BLT_TRACE_STREAM << a1 << " : " << a2 << "\n";
BLT_TRACE("{}, : {}", a1, a2);
}
}
@ -134,32 +134,32 @@ void test_zip()
blt::log_box_t box(std::cout, "Zip Tests", 25);
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1))
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
BLT_TRACE("================================");
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, list_1).take(3))
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
BLT_TRACE("================================");
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take(3).rev())
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
BLT_TRACE("================================");
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).take_or(13))
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
BLT_TRACE("================================");
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).rev().take(3))
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
BLT_TRACE("================================");
for (auto [a1, a2, a3] : blt::zip(array_1, array_2, array_3).skip(2).rev())
{
BLT_TRACE_STREAM << a1 << " : " << a2 << " : " << a3 << "\n";
BLT_TRACE("{:.4} : {:.4} : {:.4}", a1, a2, a3);
}
}
@ -168,33 +168,33 @@ void test_iterate()
blt::log_box_t box(std::cout, "Iterate Tests", 25);
for (auto v : blt::iterate(array_1))
{
BLT_TRACE_STREAM << "Element: " << v << "\n";
BLT_TRACE("Element: {:.4f}", v);
}
BLT_TRACE("================================");
for (auto v : blt::iterate(array_1).skip(5))
{
BLT_TRACE_STREAM << "Element: " << v << "\n";
BLT_TRACE("Element: {:.4f}", v);
}
BLT_TRACE("================================");
for (auto v : blt::iterate(array_1).take(5))
{
BLT_TRACE_STREAM << "Element: " << v << "\n";
BLT_TRACE("Element: {:.4f}", v);
}
BLT_TRACE("================================");
for (auto v : blt::iterate(array_1).rev())
{
BLT_TRACE_STREAM << "Element: " << v << "\n";
BLT_TRACE("Element: {:.4f}", v);
}
BLT_TRACE("================================");
for (auto [a, b] : blt::iterate(array_1).zip(list_1))
{
BLT_TRACE_STREAM << "Zip: " << a << " " << b << "\n";
BLT_TRACE("Zip: {:.4f} {:.4f}", a, b);
}
BLT_TRACE("================================");
for (auto [i, data] : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); }).zip(list_1).skip(3).take(4).enumerate())
{
auto [a, b] = data;
BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n";
BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b);
}
BLT_TRACE("================================");
for (auto [i, data] : blt::iterate(array_1).map(
@ -203,7 +203,7 @@ void test_iterate()
}).zip(list_1).skip(3).take(4).enumerate())
{
auto [a, b] = data;
BLT_TRACE_STREAM << "Map + Zip + Skip + Take + Enumerate (Index: " << i << ")> " << a << " " << b << "\n";
BLT_TRACE("Map + Zip + Skip + Take + Enumerate (Index: {})> {:.4f} {:.4f}", i, a, b);
}
BLT_TRACE("================================");
for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); })
@ -212,16 +212,16 @@ void test_iterate()
if (!a)
continue;
auto v = *a;
BLT_TRACE_STREAM << " So this one works? " << v << "\n";
BLT_TRACE(" So this one works? {:.4f}", v);
}
BLT_TRACE("================================");
for (auto a : blt::iterate(array_1).map([](const blt::vec2& in) { return in.normalize(); })
.enumerate().filter([](const auto& f) { return f.value.x() > 0.5; }))
.enumerate().filter([](const auto& f) { return f.second.x() > 0.5; }))
{
if (!a)
continue;
auto [index, v] = *a;
BLT_TRACE_STREAM << " So this one works? (" << index << ")" << v << "\n";
BLT_TRACE(" So this one works? ({}) {:.4f}", index, v);
}
BLT_TRACE("================================");
// for (auto a : blt::iterate(array_1).filter([](const auto& f) { return f.x() > 3 && f.y() < 6; }).take(2))
@ -233,7 +233,7 @@ void test_iterate()
// }
for (auto a : blt::iterate(array_1).map([](const auto& f) { return f.x() > 3 && f.y() < 6; }))
{
BLT_TRACE_STREAM << " How about this one?? " << a << "\n";
BLT_TRACE(" How about this one?? ({}) {:.4f}", a);
}
// for (auto [value, a] : blt::iterate(array_1).map(

229
tests/logger_tests.cpp Normal file
View File

@ -0,0 +1,229 @@
/*
* <Short Description>
* Copyright (C) 2025 Brett Terpstra
*
* 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 <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#include <thread>
#include <blt/fs/filesystem.h>
#include <blt/logging/ansi.h>
#include <blt/logging/logging.h>
#include <blt/logging/status.h>
#include <blt/std/assert.h>
#include <blt/std/utility.h>
struct some_silly_type_t
{};
auto expected_str = std::string(R"(This is a println!
This is a println with args '42'
This is a println with multiple args '42' '32.342311859130859375' 'Hello World!'
This is a 'Well so am I except cooler :3' fmt string with positionals 'I am a string!'
This is a println with a sign +4120
This is a println with a sign -4120
This is a println with a space 4120
This is a println with a space -4120
This is a println with a minus 4120
This is a println with a minus -4120
This is a println with a with 4120
This is a println with a with leading zeros 0000004120
This is a println with a precision 42.2323423490
This is a println with hex 109a
This is a println with hex with leading 0x109a
This is a println with binary 0b00110010000110100101011000000000
This is a println with binary with space 0b10110010 00011010 01010110 00000000
This is a println with binary with space 10100010 00000000 00000000 00000000
This is a println with octal 015015
This is a println with hexfloat 0x1.926e978d4fdf4p+8
This is a println with exponent 4.4320902431999996e+07
This is a println with exponent 9.5324342340423400e+15
This is a println with general 953243.49
This is a println with general 9.532433240234033e+17
This is a println with a char B
This is a println with type some_silly_type_t
This is a println with boolean true
This is a println with boolean as int 0
This is a println with boolean as hex 0x1
This is a println with boolean as octal 1
This is a println with alignment left 64 end value
This is a println with alignment right 46 end value
This is a println with alignment left (fill) 46******** end value
This is a println with alignment right (fill) ********46 end value
This is a println with alignment right (fill with reserved character) ^^^^^^^^46 end value
This is a println with fill no alignment %%%%%%%%%%%%%%%%%%46 end value
This is a println with arg reference 46.02
This is a println with arg reference &&&&&&&&&&&&&&&&&&&&
)");
std::pair<bool, std::string> compare_strings(const std::string& s1, const std::string& s2)
{
const auto size = std::min(s1.size(), s2.size());
size_t index = 0;
for (; index < size; ++index)
{
if (s1[index] != s2[index])
{
std::stringstream ss;
const auto i1 = std::max(static_cast<blt::i64>(index) - 32, 0l);
const auto l1 = std::min(static_cast<blt::i64>(size) - i1, 65l);
ss << "Strings differ at index " << index << "!\n";
ss << "'" << s1.substr(i1, l1) << "' vs '" << s2.substr(i1, l1) << "'" << std::endl;
return {false, ss.str()};
}
}
if (s1.size() != s2.size())
return {false, "Strings size do not match '" + std::to_string(s1.size()) + "' vs '" + std::to_string(s2.size()) + "'"};
return {true, ""};
}
int main()
{
std::stringstream ss;
blt::logging::println(ss, "This is a println!");
blt::logging::println(ss, "This is a println with args '{}'", 42);
blt::logging::println(ss, "This is a println with multiple args '{}' '{:.100}' '{}'", 42, 32.34231233f, "Hello World!");
blt::logging::println(ss, "This is a '{1}' fmt string with positionals '{0}'", "I am a string!", "Well so am I except cooler :3");
blt::logging::println(ss, "This is a println with a sign {:+}", 4120);
blt::logging::println(ss, "This is a println with a sign {:+}", -4120);
blt::logging::println(ss, "This is a println with a space {: }", 4120);
blt::logging::println(ss, "This is a println with a space {: }", -4120);
blt::logging::println(ss, "This is a println with a minus {:-}", 4120);
blt::logging::println(ss, "This is a println with a minus {:-}", -4120);
blt::logging::println(ss, "This is a println with a with {:10}", 4120);
blt::logging::println(ss, "This is a println with a with leading zeros {:010}", 4120);
blt::logging::println(ss, "This is a println with a precision {:.10f}", 42.232342349);
blt::logging::println(ss, "This is a println with hex {:.10x}", 4250);
blt::logging::println(ss, "This is a println with hex with leading {:#.10x}", 4250);
blt::logging::println(ss, "This is a println with binary {:#b}", 6969420);
blt::logging::println(ss, "This is a println with binary with space {: #b}", 6969421);
blt::logging::println(ss, "This is a println with binary with space {: b}", 69);
blt::logging::println(ss, "This is a println with octal {:#o}", 6669);
blt::logging::println(ss, "This is a println with hexfloat {:a}", 402.4320);
blt::logging::println(ss, "This is a println with exponent {:e}", 44320902.4320);
blt::logging::println(ss, "This is a println with exponent {:e}", 9532434234042340.0);
blt::logging::println(ss, "This is a println with general {:g}", 953243.49);
blt::logging::println(ss, "This is a println with general {:g}", 953243324023403240.49);
blt::logging::println(ss, "This is a println with a char {:c}", 66);
blt::logging::println(ss, "This is a println with type {:t}", some_silly_type_t{});
blt::logging::println(ss, "This is a println with boolean {}", true);
blt::logging::println(ss, "This is a println with boolean as int {:d}", false);
blt::logging::println(ss, "This is a println with boolean as hex {:#x}", true);
blt::logging::println(ss, "This is a println with boolean as octal {:o}", true);
blt::logging::println(ss, "This is a println with alignment left {:<10} end value", 64);
blt::logging::println(ss, "This is a println with alignment right {:>10} end value", 46);
blt::logging::println(ss, "This is a println with alignment left (fill) {:*<10} end value", 46);
blt::logging::println(ss, "This is a println with alignment right (fill) {:*>10} end value", 46);
blt::logging::println(ss, "This is a println with alignment right (fill with reserved character) {:\\^>10} end value", 46);
blt::logging::println(ss, "This is a println with fill no alignment {:%20} end value", 46);
blt::logging::println(ss, "This is a println with arg reference {0:{1}.{2}f}", 46.0232, 20, 2);
blt::logging::println(ss, "This is a println with arg reference {0:&{1}}", "", 20);
// blt::logging::print(ss.str());
auto [passed, error_msg] = compare_strings(expected_str, ss.str());
BLT_ASSERT_MSG(passed && "Logger logged string doesn't match precomputed expected string!", error_msg.c_str());
namespace ansi = blt::logging::ansi;
namespace color = ansi::color;
// for (blt::u8 r = 0; r < 6; r++)
// {
// for (blt::u8 g = 0; g < 6; g++)
// {
// for (blt::u8 b = 0; b < 6; b++)
// {
// blt::logging::println("{}This is a println with a color {:#3x} {:#3x} {:#3x}{}",
// build(fg(color::color256{r, g, b}), bg(color::color256{
// static_cast<unsigned char>(5 - r),
// static_cast<unsigned char>(5 - g),
// static_cast<unsigned char>(5 - b)
// })), r, g, b, build(color::color_mode::RESET_ALL));
// }
// }
// }
// blt::logging::println("{}This is a color now with background{}",
// build(color::color_mode::BOLD, fg(color::color8::RED), color::color_mode::DIM, bg(color::color_rgb(0, 100, 255))),
// build(color::color_mode::RESET_ALL));
std::ofstream os("test.txt");
blt::fs::fstream_writer_t wtr(os);
blt::fs::writer_string_wrapper_t writer(wtr);
writer.write("This is a println with a stream\n");
writer.write("This is a mixed print ");
writer.write(std::to_string(25));
writer.write(" with multiple types ");
writer.write(std::to_string(34.23340));
writer.write('\n');
writer.write("What about just a new line character?\n");
// blt::logging::println("Logged {} characters", charCount);
//
// BLT_TRACE("Hello this is am empty trace!");
// BLT_TRACE("This is a trace with data {} {} {}", "bad at code", 413, "boy");
//
// BLT_DEBUG("This is complete? {}", "this is working!");
// BLT_INFO("Hello there!");
// BLT_WARN("This is a warning!");
// BLT_ERROR("This is an error!");
// BLT_FATAL("This is a fatal error!");
// BLT_TRACE("This is a pointer {:f}", &charCount);
//
// BLT_TRACE("Now time to test the logger status box");
blt::logging::status_progress_bar_t progress;
blt::logging::status_bar_t status;
status.add(progress);
blt::logging::get_global_config().add_injector(status);
progress.set_progress(1.0 / 103.0);
BLT_TRACE("Hello There!");
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
progress.set_progress(2.0 / 103.0);
BLT_TRACE("I am printing stuff!");
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
progress.set_progress(3.0 / 103.0);
BLT_TRACE("How are you!?");
for (int i = 0; i < 100; i++)
{
progress.set_progress((4.0 + i) / 103.0);
BLT_INFO("I am printing some output {} times!", i + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
/*std::cout << "\033[2J";
constexpr int totalRows = 24;
// std::cout << "\033[1;" << (totalRows - 1) << "r";
std::cout << use_mode(ansi::mode::color80x25_text);
for (int i = 1; i <= 10; ++i)
{
std::cout << "\033[1;1H";
std::cout << "printed line " << i << std::endl;
std::cout << "\033[" << totalRows << ";1H";
std::cout << "[----status----]" << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "\033[r";*/
// blt::logging::println("This is println {}\twith a std::endl in the middle of it");
}