This commit is contained in:
Mars 2025-05-01 19:47:01 -04:00
parent 9a95178095
commit 631964469f
Signed by: pupbrained
GPG key ID: 0FF5B8826803F895
26 changed files with 482 additions and 335 deletions

216
src/util/cache.hpp Normal file
View file

@ -0,0 +1,216 @@
#pragma once
#include <filesystem> // std::filesystem
#include <fstream> // std::{ifstream, ofstream}
#include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{context, error_code, error_ctx}
#include <iterator> // std::istreambuf_iterator
#include <string> // std::string
#include <system_error> // std::error_code
#include <type_traits> // std::decay_t
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
namespace util::cache {
namespace fs = std::filesystem;
using error::DracError, error::DracErrorCode;
using types::Err, types::Exception, types::Result, types::String, types::isize;
/**
* @brief Gets the full path for a cache file based on a unique key.
* @param cache_key A unique identifier for the cache (e.g., "weather", "pkg_count_pacman").
* Should ideally only contain filesystem-safe characters.
* @return Result containing the filesystem path on success, or a DracError on failure.
*/
inline fn GetCachePath(const String& cache_key) -> Result<fs::path, DracError> {
if (cache_key.empty())
return Err(DracError(DracErrorCode::InvalidArgument, "Cache key cannot be empty."));
// Basic check for potentially problematic characters in the key for filename safety.
// You might want to expand this or implement more robust sanitization if needed.
if (cache_key.find_first_of("/\\:*?\"<>|") != String::npos)
return Err(
DracError(DracErrorCode::InvalidArgument, std::format("Cache key '{}' contains invalid characters.", cache_key))
);
std::error_code errc;
const fs::path cacheDir = fs::temp_directory_path(errc);
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to get system temporary directory: " + errc.message()));
// Use a consistent naming scheme
return cacheDir / (cache_key + "_cache.beve");
}
/**
* @brief Reads and deserializes data from a BEVE cache file.
* @tparam T The type of the object to deserialize from the cache. Must be Glaze-compatible.
* @param cache_key The unique identifier for the cache.
* @return Result containing the deserialized object of type T on success, or a DracError on failure.
*/
template <typename T>
fn ReadCache(const String& cache_key) -> Result<T, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
if (std::error_code exists_ec; !fs::exists(cachePath, exists_ec) || exists_ec) {
if (exists_ec) {
// Log if there was an error checking existence, but still return NotFound
warn_log("Error checking existence of cache file '{}': {}", cachePath.string(), exists_ec.message());
}
return Err(DracError(DracErrorCode::NotFound, "Cache file not found: " + cachePath.string()));
}
std::ifstream ifs(cachePath, std::ios::binary);
if (!ifs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open cache file for reading: " + cachePath.string()));
debug_log("Reading cache for key '{}' from: {}", cache_key, cachePath.string());
try {
// Read the entire file content
const String content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
ifs.close(); // Close the file as soon as content is read
if (content.empty())
return Err(DracError(DracErrorCode::ParseError, "BEVE cache file is empty: " + cachePath.string()));
// Ensure T is default constructible for Glaze
static_assert(std::is_default_constructible_v<T>, "Cache type T must be default constructible for Glaze.");
T result {};
if (glz::error_ctx glazeErr = glz::read_beve(result, content); glazeErr.ec != glz::error_code::none) {
return Err(DracError(
DracErrorCode::ParseError,
std::format(
"BEVE parse error reading cache '{}' (code {}): {}",
cachePath.string(),
static_cast<int>(glazeErr.ec),
glz::format_error(glazeErr, content)
)
));
}
debug_log("Successfully read cache for key '{}'.", cache_key);
return result;
} catch (const std::ios_base::failure& e) {
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (const Exception& e) {
// Catching std::exception or a project-specific base exception
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception reading cache file {}: {}", cachePath.string(), e.what())
));
} catch (...) {
return Err(DracError(DracErrorCode::Other, "Unknown error reading cache file: " + cachePath.string()));
}
}
/**
* @brief Serializes and writes data to a BEVE cache file safely.
* @tparam T The type of the object to serialize. Must be Glaze-compatible.
* @param cache_key The unique identifier for the cache.
* @param data The data object of type T to write to the cache.
* @return Result containing void on success, or a DracError on failure.
*/
template <typename T>
fn WriteCache(const String& cache_key, const T& data) -> Result<void, DracError> {
Result<fs::path, DracError> cachePathResult = GetCachePath(cache_key);
if (!cachePathResult)
return Err(cachePathResult.error());
const fs::path& cachePath = *cachePathResult;
fs::path tempPath = cachePath;
tempPath += ".tmp"; // Use a temporary file for atomic write
debug_log("Writing cache for key '{}' to: {}", cache_key, cachePath.string());
try {
String binaryBuffer;
// Use Glaze to serialize
// Need to decay T in case it's a reference type from the caller
using DecayedT = std::decay_t<T>;
DecayedT dataToSerialize = data; // Make a copy if needed for non-const Glaze operations
if (glz::error_ctx glazeErr = glz::write_beve(dataToSerialize, binaryBuffer); glazeErr) {
return Err(DracError(
DracErrorCode::ParseError,
std::format(
"BEVE serialization error writing cache for key '{}' (code {}): {}",
cache_key,
static_cast<int>(glazeErr.ec),
glz::format_error(glazeErr, binaryBuffer)
)
));
}
// Scope for ofstream to ensure it's closed before rename
{
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
return Err(DracError(DracErrorCode::IoError, "Failed to open temporary cache file: " + tempPath.string()));
ofs.write(binaryBuffer.data(), static_cast<isize>(binaryBuffer.size()));
if (!ofs) {
// Attempt cleanup before returning error
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string())
);
}
// ofstream automatically closed here
}
// Attempt to atomically replace the old cache file
std::error_code rename_ec;
fs::rename(tempPath, cachePath, rename_ec);
if (rename_ec) {
// If rename failed, attempt to clean up the temp file
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Ignore error on cleanup attempt
return Err(DracError(
DracErrorCode::IoError,
std::format(
"Failed to replace cache file '{}' with temporary file '{}': {}",
cachePath.string(),
tempPath.string(),
rename_ec.message()
)
));
}
debug_log("Successfully wrote cache for key '{}'.", cache_key);
return {}; // Success
} catch (const std::ios_base::failure& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(
DracErrorCode::IoError, std::format("Filesystem error writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (const Exception& e) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(
DracErrorCode::InternalError,
std::format("Standard exception writing cache file {}: {}", tempPath.string(), e.what())
));
} catch (...) {
std::error_code removeEc;
fs::remove(tempPath, removeEc); // Cleanup attempt
return Err(DracError(DracErrorCode::Other, "Unknown error writing cache file: " + tempPath.string()));
}
}
} // namespace util::cache

9
src/util/defs.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
// Fixes conflict in Windows with <windows.h>
#ifdef _WIN32
#undef ERROR
#endif // _WIN32
/// Macro alias for trailing return type functions.
#define fn auto

125
src/util/error.hpp Normal file
View file

@ -0,0 +1,125 @@
#pragma once
#include <format>
#include <source_location> // std::source_location
#include <system_error> // std::error_code
#ifdef _WIN32
#include <winerror.h> // error values
#include <winrt/base.h> // winrt::hresult_error
#endif
#include "src/util/types.hpp"
namespace util::error {
using types::u8, types::i32, types::String, types::StringView, types::Exception;
/**
* @enum DracErrorCode
* @brief Error codes for general OS-level operations.
*/
enum class DracErrorCode : u8 {
ApiUnavailable, ///< A required OS service/API is unavailable or failed unexpectedly at runtime.
InternalError, ///< An error occurred within the application's OS abstraction code logic.
InvalidArgument, ///< An invalid argument was passed to a function or method.
IoError, ///< General I/O error (filesystem, pipes, etc.).
NetworkError, ///< A network-related error occurred (e.g., DNS resolution, connection failure).
NotFound, ///< A required resource (file, registry key, device, API endpoint) was not found.
NotSupported, ///< The requested operation is not supported on this platform, version, or configuration.
Other, ///< A generic or unclassified error originating from the OS or an external library.
OutOfMemory, ///< The system ran out of memory or resources to complete the operation.
ParseError, ///< Failed to parse data obtained from the OS (e.g., file content, API output).
PermissionDenied, ///< Insufficient permissions to perform the operation.
PlatformSpecific, ///< An unmapped error specific to the underlying OS platform occurred (check message).
Timeout, ///< An operation timed out (e.g., waiting for IPC reply).
};
/**
* @struct DracError
* @brief Holds structured information about an OS-level error.
*
* Used as the error type in Result for many os:: functions.
*/
struct DracError {
// ReSharper disable CppDFANotInitializedField
String message; ///< A descriptive error message, potentially including platform details.
DracErrorCode code; ///< The general category of the error.
std::source_location location; ///< The source location where the error occurred (file, line, function).
// ReSharper restore CppDFANotInitializedField
DracError(const DracErrorCode errc, String msg, const std::source_location& loc = std::source_location::current())
: message(std::move(msg)), code(errc), location(loc) {}
explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current())
: message(exc.what()), code(DracErrorCode::InternalError), location(loc) {}
explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current())
: message(errc.message()), location(loc) {
using enum DracErrorCode;
using enum std::errc;
switch (static_cast<std::errc>(errc.value())) {
case permission_denied: code = PermissionDenied; break;
case no_such_file_or_directory: code = NotFound; break;
case timed_out: code = Timeout; break;
case io_error: code = IoError; break;
case network_unreachable:
case network_down:
case connection_refused: code = NetworkError; break;
case not_supported: code = NotSupported; break;
default: code = errc.category() == std::generic_category() ? InternalError : PlatformSpecific; break;
}
}
#ifdef _WIN32
explicit DraconisError(const winrt::hresult_error& e) : message(winrt::to_string(e.message())) {
switch (e.code()) {
case E_ACCESSDENIED: code = DraconisErrorCode::PermissionDenied; break;
case HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND):
case HRESULT_FROM_WIN32(ERROR_SERVICE_NOT_FOUND): code = DraconisErrorCode::NotFound; break;
case HRESULT_FROM_WIN32(ERROR_TIMEOUT):
case HRESULT_FROM_WIN32(ERROR_SEM_TIMEOUT): code = DraconisErrorCode::Timeout; break;
case HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED): code = DraconisErrorCode::NotSupported; break;
default: code = DraconisErrorCode::PlatformSpecific; break;
}
}
#else
DracError(const DracErrorCode code_hint, const int errno_val)
: message(std::system_category().message(errno_val)), code(code_hint) {
using enum DracErrorCode;
switch (errno_val) {
case EACCES: code = PermissionDenied; break;
case ENOENT: code = NotFound; break;
case ETIMEDOUT: code = Timeout; break;
case ENOTSUP: code = NotSupported; break;
default: code = PlatformSpecific; break;
}
}
static auto withErrno(const String& context, const std::source_location& loc = std::source_location::current())
-> DracError {
const i32 errNo = errno;
const String msg = std::system_category().message(errNo);
const String fullMsg = std::format("{}: {}", context, msg);
const DracErrorCode code = [&errNo] {
switch (errNo) {
case EACCES:
case EPERM: return DracErrorCode::PermissionDenied;
case ENOENT: return DracErrorCode::NotFound;
case ETIMEDOUT: return DracErrorCode::Timeout;
case ENOTSUP: return DracErrorCode::NotSupported;
case EIO: return DracErrorCode::IoError;
case ECONNREFUSED:
case ENETDOWN:
case ENETUNREACH: return DracErrorCode::NetworkError;
default: return DracErrorCode::PlatformSpecific;
}
}();
return DracError { code, fullMsg, loc };
}
#endif
};
} // namespace util::error

46
src/util/helpers.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
namespace util::helpers {
using error::DracError, error::DracErrorCode;
using types::Result, types::String, types::CStr, types::Err;
/**
* @brief Safely retrieves an environment variable.
* @param name The name of the environment variable to retrieve.
* @return A Result containing the value of the environment variable as a String,
* or an EnvError if an error occurred.
*/
[[nodiscard]] inline fn GetEnv(CStr name) -> Result<String, DracError> {
#ifdef _WIN32
using types::i32, types::usize, types::UniquePointer;
char* rawPtr = nullptr;
usize bufferSize = 0;
// Use _dupenv_s to safely retrieve environment variables on Windows
const i32 err = _dupenv_s(&rawPtr, &bufferSize, name);
const UniquePointer<char, decltype(&free)> ptrManager(rawPtr, free);
if (err != 0)
return Err(DraconisError(DraconisErrorCode::PermissionDenied, "Failed to retrieve environment variable"));
if (!ptrManager)
return Err(DraconisError(DraconisErrorCode::NotFound, "Environment variable not found"));
return ptrManager.get();
#else
// Use std::getenv to retrieve environment variables on POSIX systems
const CStr value = std::getenv(name);
if (!value)
return Err(DracError(DracErrorCode::NotFound, "Environment variable not found"));
return value;
#endif
}
} // namespace util::helpers

280
src/util/logging.hpp Normal file
View file

@ -0,0 +1,280 @@
#pragma once
#include <chrono> // std::chrono::{days, floor, seconds, system_clock}
#include <filesystem> // std::filesystem::path
#include <format> // std::format
#include <ftxui/screen/color.hpp> // ftxui::Color
#include <print> // std::print
#include <utility> // std::forward
#ifndef NDEBUG
#include <source_location> // std::source_location
#endif
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
namespace util::logging {
using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,
types::Option, types::None;
// Store all compile-time constants in a struct
struct LogLevelConst {
// ANSI color codes
// clang-format off
static constexpr Array<StringView, 16> COLOR_CODE_LITERALS = {
"\033[38;5;0m", "\033[38;5;1m", "\033[38;5;2m", "\033[38;5;3m",
"\033[38;5;4m", "\033[38;5;5m", "\033[38;5;6m", "\033[38;5;7m",
"\033[38;5;8m", "\033[38;5;9m", "\033[38;5;10m", "\033[38;5;11m",
"\033[38;5;12m", "\033[38;5;13m", "\033[38;5;14m", "\033[38;5;15m",
};
// clang-format on
// ANSI formatting constants
static constexpr const char* RESET_CODE = "\033[0m";
static constexpr const char* BOLD_START = "\033[1m";
static constexpr const char* BOLD_END = "\033[22m";
static constexpr const char* ITALIC_START = "\033[3m";
static constexpr const char* ITALIC_END = "\033[23m";
// Log level text constants
static constexpr StringView DEBUG_STR = "DEBUG";
static constexpr StringView INFO_STR = "INFO ";
static constexpr StringView WARN_STR = "WARN ";
static constexpr StringView ERROR_STR = "ERROR";
// Log level color constants
static constexpr ftxui::Color::Palette16 DEBUG_COLOR = ftxui::Color::Palette16::Cyan;
static constexpr ftxui::Color::Palette16 INFO_COLOR = ftxui::Color::Palette16::Green;
static constexpr ftxui::Color::Palette16 WARN_COLOR = ftxui::Color::Palette16::Yellow;
static constexpr ftxui::Color::Palette16 ERROR_COLOR = ftxui::Color::Palette16::Red;
static constexpr ftxui::Color::Palette16 DEBUG_INFO_COLOR = ftxui::Color::Palette16::GrayLight;
static constexpr CStr TIMESTAMP_FORMAT = "%X";
static constexpr CStr LOG_FORMAT = "{} {} {}";
#ifndef NDEBUG
static constexpr CStr DEBUG_INFO_FORMAT = "{}{}{}\n";
static constexpr CStr FILE_LINE_FORMAT = "{}:{}";
static constexpr CStr DEBUG_LINE_PREFIX = " ╰── ";
#endif
};
/**
* @enum LogLevel
* @brief Represents different log levels.
*/
enum class LogLevel : u8 { Debug, Info, Warn, Error };
/**
* @brief Directly applies ANSI color codes to text
* @param text The text to colorize
* @param color The FTXUI color
* @return Styled string with ANSI codes
*/
inline fn Colorize(const StringView text, const ftxui::Color::Palette16& color) -> String {
return std::format("{}{}{}", LogLevelConst::COLOR_CODE_LITERALS.at(color), text, LogLevelConst::RESET_CODE);
}
/**
* @brief Make text bold with ANSI codes
* @param text The text to make bold
* @return Bold text
*/
inline fn Bold(const StringView text) -> String {
return std::format("{}{}{}", LogLevelConst::BOLD_START, text, LogLevelConst::BOLD_END);
}
/**
* @brief Make text italic with ANSI codes
* @param text The text to make italic
* @return Italic text
*/
inline fn Italic(const StringView text) -> String {
return std::format("{}{}{}", LogLevelConst::ITALIC_START, text, LogLevelConst::ITALIC_END);
}
/**
* @brief Returns the pre-formatted and styled log level strings.
* @note Uses function-local static for lazy initialization to avoid
* static initialization order issues and CERT-ERR58-CPP warnings.
*/
inline fn GetLevelInfo() -> const Array<String, 4>& {
static const Array<String, 4> LEVEL_INFO_INSTANCE = {
Bold(Colorize(LogLevelConst::DEBUG_STR, LogLevelConst::DEBUG_COLOR)),
Bold(Colorize(LogLevelConst::INFO_STR, LogLevelConst::INFO_COLOR)),
Bold(Colorize(LogLevelConst::WARN_STR, LogLevelConst::WARN_COLOR)),
Bold(Colorize(LogLevelConst::ERROR_STR, LogLevelConst::ERROR_COLOR)),
};
return LEVEL_INFO_INSTANCE;
}
/**
* @brief Returns FTXUI color representation for a log level
* @param level The log level
* @return FTXUI color code
*/
constexpr fn GetLevelColor(const LogLevel level) -> ftxui::Color::Palette16 {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_COLOR;
case LogLevel::Info: return LogLevelConst::INFO_COLOR;
case LogLevel::Warn: return LogLevelConst::WARN_COLOR;
case LogLevel::Error: return LogLevelConst::ERROR_COLOR;
default: std::unreachable();
}
}
/**
* @brief Returns string representation of a log level
* @param level The log level
* @return String representation
*/
constexpr fn GetLevelString(const LogLevel level) -> StringView {
switch (level) {
case LogLevel::Debug: return LogLevelConst::DEBUG_STR;
case LogLevel::Info: return LogLevelConst::INFO_STR;
case LogLevel::Warn: return LogLevelConst::WARN_STR;
case LogLevel::Error: return LogLevelConst::ERROR_STR;
default: std::unreachable();
}
}
// ReSharper disable once CppDoxygenUnresolvedReference
/**
* @brief Logs a message with the specified log level, source location, and format string.
* @tparam Args Parameter pack for format arguments.
* @param level The log level (DEBUG, INFO, WARN, ERROR).
* \ifnot NDEBUG
* @param loc The source location of the log message (only in Debug builds).
* \endif
* @param fmt The format string.
* @param args The arguments for the format string.
*/
template <typename... Args>
fn LogImpl(
const LogLevel level,
#ifndef NDEBUG
const std::source_location& loc,
#endif
std::format_string<Args...> fmt,
Args&&... args
) {
using namespace std::chrono;
using std::filesystem::path;
const auto nowTp = system_clock::now();
const std::time_t nowTt = system_clock::to_time_t(nowTp);
std::tm localTm;
String timestamp;
#ifdef _WIN32
if (localtime_s(&local_tm, &now_tt) == 0) {
#else
if (localtime_r(&nowTt, &localTm) != nullptr) {
#endif
Array<char, 64> timeBuffer {};
if (std::strftime(timeBuffer.data(), sizeof(timeBuffer), LogLevelConst::TIMESTAMP_FORMAT, &localTm) > 0)
timestamp = timeBuffer.data();
else
timestamp = "??:??:?? (strf_err)";
} else
timestamp = "??:??:?? (conv_err)";
const String message = std::format(fmt, std::forward<Args>(args)...);
Array<char, 128> buffer {};
// Use the locally formatted timestamp string here
auto* iter = std::format_to(
buffer.begin(),
LogLevelConst::LOG_FORMAT, // "{timestamp} {level} {message}"
Colorize("[" + timestamp + "]", LogLevelConst::DEBUG_INFO_COLOR),
GetLevelInfo().at(static_cast<usize>(level)),
message
);
#ifndef NDEBUG
const String fileLine = std::format("{}:{}", path(loc.file_name()).lexically_normal().string(), loc.line());
const String fullDebugLine = std::format("{}{}", LogLevelConst::DEBUG_LINE_PREFIX, fileLine);
iter = std::format_to(iter, "\n{}", Italic(Colorize(fullDebugLine, LogLevelConst::DEBUG_INFO_COLOR)));
#endif
const usize length = std::distance(buffer.begin(), iter);
std::println("{}", StringView(buffer.data(), length));
}
template <typename ErrorType>
fn LogError(const LogLevel level, const ErrorType& error_obj) {
using DecayedErrorType = std::decay_t<ErrorType>;
#ifndef NDEBUG
std::source_location logLocation;
#endif
String errorMessagePart;
if constexpr (std::is_same_v<DecayedErrorType, error::DracError>) {
#ifndef NDEBUG
logLocation = error_obj.location;
#endif
errorMessagePart = error_obj.message;
} else {
#ifndef NDEBUG
logLocation = std::source_location::current();
#endif
if constexpr (std::is_base_of_v<std::exception, DecayedErrorType>)
errorMessagePart = error_obj.what();
else if constexpr (requires { error_obj.message; })
errorMessagePart = error_obj.message;
else
errorMessagePart = "Unknown error type logged";
}
#ifndef NDEBUG
LogImpl(level, logLocation, "{}", errorMessagePart);
#else
LogImpl(level, "{}", errorMessagePart);
#endif
}
#ifndef NDEBUG
#define debug_log(fmt, ...) \
::util::logging::LogImpl( \
::util::logging::LogLevel::Debug, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
)
#define debug_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Debug, error_obj);
#else
#define debug_log(...) ((void)0)
#define debug_at(...) ((void)0)
#endif
#define info_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Info, error_obj);
#define warn_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Warn, error_obj);
#define error_at(error_obj) ::util::logging::LogError(::util::logging::LogLevel::Error, error_obj);
#ifdef NDEBUG
#define info_log(fmt, ...) ::util::logging::LogImpl(::util::logging::LogLevel::Info, fmt __VA_OPT__(, ) __VA_ARGS__)
#define warn_log(fmt, ...) ::util::logging::LogImpl(::util::logging::LogLevel::Warn, fmt __VA_OPT__(, ) __VA_ARGS__)
#define error_log(fmt, ...) ::util::logging::LogImpl(::util::logging::LogLevel::Error, fmt __VA_OPT__(, ) __VA_ARGS__)
#else
#define info_log(fmt, ...) \
::util::logging::LogImpl( \
::util::logging::LogLevel::Info, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
)
#define warn_log(fmt, ...) \
::util::logging::LogImpl( \
::util::logging::LogLevel::Warn, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
)
#define error_log(fmt, ...) \
::util::logging::LogImpl( \
::util::logging::LogLevel::Error, std::source_location::current(), fmt __VA_OPT__(, ) __VA_ARGS__ \
)
#endif
} // namespace util::logging

151
src/util/types.hpp Normal file
View file

@ -0,0 +1,151 @@
#pragma once
#include <array> // std::array (Array)
#include <expected> // std::expected (Result)
#include <future> // std::future (Future)
#include <map> // std::map (Map)
#include <memory> // std::shared_ptr and std::unique_ptr (SharedPointer, UniquePointer)
#include <optional> // std::optional (Option)
#include <string> // std::string (String, StringView)
#include <string_view> // std::string_view (StringView)
#include <utility> // std::pair (Pair)
#include <vector> // std::vector (Vec)
namespace util::types {
using u8 = std::uint8_t; ///< 8-bit unsigned integer.
using u16 = std::uint16_t; ///< 16-bit unsigned integer.
using u32 = std::uint32_t; ///< 32-bit unsigned integer.
using u64 = std::uint64_t; ///< 64-bit unsigned integer.
using i8 = std::int8_t; ///< 8-bit signed integer.
using i16 = std::int16_t; ///< 16-bit signed integer.
using i32 = std::int32_t; ///< 32-bit signed integer.
using i64 = std::int64_t; ///< 64-bit signed integer.
using f32 = float; ///< 32-bit floating-point number.
using f64 = double; ///< 64-bit floating-point number.
using usize = std::size_t; ///< Unsigned size type (result of sizeof).
using isize = std::ptrdiff_t; ///< Signed size type (result of pointer subtraction).
using String = std::string; ///< Owning, mutable string.
using StringView = std::string_view; ///< Non-owning view of a string.
using CStr = const char*; ///< Pointer to a null-terminated C-style string.
using Exception = std::exception; ///< Standard exception type.
inline constexpr std::nullopt_t None = std::nullopt; ///< Represents an empty optional value.
/**
* @typedef Result
* @brief Alias for std::expected<Tp, Er>. Represents a value that can either be
* a success value of type Tp or an error value of type Er.
* @tparam Tp The type of the success value.
* @tparam Er The type of the error value.
*/
template <typename Tp, typename Er>
using Result = std::expected<Tp, Er>;
/**
* @typedef Err
* @brief Alias for std::unexpected<Er>. Used to construct a Result in an error state.
* @tparam Er The type of the error value.
*/
template <typename Er>
using Err = std::unexpected<Er>;
/**
* @typedef Option
* @brief Alias for std::optional<Tp>. Represents a value that may or may not be present.
* @tparam Tp The type of the potential value.
*/
template <typename Tp>
using Option = std::optional<Tp>;
/**
* @typedef Array
* @brief Alias for std::array<Tp, sz>. Represents a fixed-size array.
* @tparam Tp The element type.
* @tparam sz The size of the array.
*/
template <typename Tp, usize sz>
using Array = std::array<Tp, sz>;
/**
* @typedef Vec
* @brief Alias for std::vector<Tp>. Represents a dynamic-size array (vector).
* @tparam Tp The element type.
*/
template <typename Tp>
using Vec = std::vector<Tp>;
/**
* @typedef Pair
* @brief Alias for std::pair<T1, T2>. Represents a pair of values.
* @tparam T1 The type of the first element.
* @tparam T2 The type of the second element.
*/
template <typename T1, typename T2>
using Pair = std::pair<T1, T2>;
/**
* @typedef Map
* @brief Alias for std::map<Key, Val>. Represents an ordered map (dictionary).
* @tparam Key The key type.
* @tparam Val The value type.
*/
template <typename Key, typename Val>
using Map = std::map<Key, Val>;
/**
* @typedef SharedPointer
* @brief Alias for std::shared_ptr<Tp>. Manages shared ownership of a dynamically allocated object.
* @tparam Tp The type of the managed object.
*/
template <typename Tp>
using SharedPointer = std::shared_ptr<Tp>;
/**
* @typedef UniquePointer
* @brief Alias for std::unique_ptr<Tp, Dp>. Manages unique ownership of a dynamically allocated object.
* @tparam Tp The type of the managed object.
* @tparam Dp The deleter type (defaults to std::default_delete<Tp>).
*/
template <typename Tp, typename Dp = std::default_delete<Tp>>
using UniquePointer = std::unique_ptr<Tp, Dp>;
/**
* @typedef Future
* @brief Alias for std::future<Tp>. Represents a value that will be available in the future.
* @tparam Tp The type of the value.
*/
template <typename Tp>
using Future = std::future<Tp>;
/**
* @struct DiskSpace
* @brief Represents disk usage information.
*
* Used as the success type for os::GetDiskUsage.
*/
struct DiskSpace {
u64 used_bytes; ///< Currently used disk space in bytes.
u64 total_bytes; ///< Total disk space in bytes.
};
/**
* @struct MediaInfo
* @brief Holds structured metadata about currently playing media.
*
* Used as the success type for os::GetNowPlaying.
* Using Option<> for fields that might not always be available.
*/
struct MediaInfo {
Option<String> title; ///< Track title.
Option<String> artist; ///< Track artist(s).
MediaInfo() = default;
MediaInfo(Option<String> title, Option<String> artist) : title(std::move(title)), artist(std::move(artist)) {}
};
} // namespace util::types