From 55819ebfe0c44276cc619a2d5f23f2b168dc97e1 Mon Sep 17 00:00:00 2001 From: pupbrained Date: Thu, 24 Apr 2025 23:17:11 -0400 Subject: [PATCH] more stuffs --- _sources/generated.nix | 3 +- flake.lock | 6 +- flake.nix | 2 +- meson.build | 3 - src/core/system_data.h | 76 ++++++++--- src/main.cpp | 125 ++++++++++-------- src/os/linux.cpp | 106 ++++++++-------- src/os/linux/display_guards.cpp | 37 +++--- src/os/linux/display_guards.h | 71 +++++++++-- src/util/macros.h | 218 +++++++++++++++++++++----------- 10 files changed, 410 insertions(+), 237 deletions(-) diff --git a/_sources/generated.nix b/_sources/generated.nix index 38eba1c..6cb27da 100644 --- a/_sources/generated.nix +++ b/_sources/generated.nix @@ -1,7 +1,6 @@ # This file was generated by nvfetcher, please do not modify it manually. +{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }: { - fetchFromGitHub, -}: { dbus-cxx = { pname = "dbus-cxx"; version = "2.5.2"; diff --git a/flake.lock b/flake.lock index 5f462e4..eb8e8a7 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1744868846, - "narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=", + "lastModified": 1745377448, + "narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c", + "rev": "507b63021ada5fee621b6ca371c4fca9ca46f52c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2aea20a..5683c7f 100644 --- a/flake.nix +++ b/flake.nix @@ -67,7 +67,7 @@ dbus-cxx libsigcxx30 sqlitecpp - xorg.libX11 + xorg.libxcb wayland ])); in diff --git a/meson.build b/meson.build index d4983e6..f16abbb 100644 --- a/meson.build +++ b/meson.build @@ -130,7 +130,6 @@ elif host_system == 'windows' elif host_system == 'linux' platform_deps += [ dependency('SQLiteCpp'), - dependency('x11'), dependency('xcb'), dependency('xau'), dependency('xdmcp'), @@ -164,9 +163,7 @@ glaze_dep = dependency('glaze', include_type : 'system', required : false) if not glaze_dep.found() cmake = import('cmake') - glaze_proj = cmake.subproject('glaze') - glaze_dep = glaze_proj.dependency('glaze_glaze', include_type : 'system') endif diff --git a/src/core/system_data.h b/src/core/system_data.h index ab360fc..0f011b8 100644 --- a/src/core/system_data.h +++ b/src/core/system_data.h @@ -3,34 +3,80 @@ #include "src/config/config.h" #include "src/util/types.h" +/** + * @struct BytesToGiB + * @brief Helper struct to format a byte value to GiB (Gibibytes). + * + * Encapsulates a byte value and provides a custom formatter + * to convert it to GiB for display purposes. + */ struct BytesToGiB { - u64 value; + u64 value; ///< The byte value to be converted. }; +/// @brief Conversion factor from bytes to GiB constexpr u64 GIB = 1'073'741'824; +/** + * @brief Custom formatter for BytesToGiB. + * + * Allows formatting BytesToGiB values using std::format. + * Outputs the value in GiB with two decimal places. + * + * @code{.cpp} + * #include + * #include "system_data.h" // Assuming BytesToGiB is defined here + * + * i32 main() { + * BytesToGiB data_size{2'147'483'648}; // 2 GiB + * String formatted = std::format("Size: {}", data_size); + * std::println("{}", formatted); // formatted will be "Size: 2.00GiB" + * return 0; + * } + * @endcode + */ template <> struct std::formatter : std::formatter { + /** + * @brief Formats the BytesToGiB value. + * @param BTG The BytesToGiB instance to format. + * @param ctx The formatting context. + * @return An iterator to the end of the formatted output. + */ fn format(const BytesToGiB& BTG, auto& ctx) const { return std::format_to(ctx.out(), "{:.2f}GiB", static_cast(BTG.value) / GIB); } }; -// Structure to hold the collected system data +/** + * @struct SystemData + * @brief Holds various pieces of system information collected from the OS. + * + * This structure aggregates information about the system, + * in order to display it at all at once during runtime. + */ struct SystemData { - String date; - String host; - String kernel_version; - Result os_version; - Result mem_info; - Option desktop_environment; - String window_manager; - Option> now_playing; - Option weather_info; - u64 disk_used; - u64 disk_total; - String shell; + using NowPlayingResult = Option>; - // Static function to fetch the data + // clang-format off + String date; ///< Current date (e.g., "April 24th"). + String host; ///< Host or product family name (e.g., "MacBook Pro"). + String kernel_version; ///< OS kernel version (e.g., "5.15.0-generic"). + Result os_version; ///< OS pretty name (e.g., "Ubuntu 22.04 LTS") or an error message. + Result mem_info; ///< Total physical RAM in bytes or an error message. + Option desktop_environment; ///< Detected desktop environment (e.g., "GNOME", "KDE", "Fluent (Windows 11)"). Might be None. + String window_manager; ///< Detected window manager (e.g., "Mutter", "KWin", "DWM"). + NowPlayingResult now_playing; ///< Currently playing media ("Artist - Title") or an error/None if disabled/unavailable. + Option weather_info; ///< Weather information or None if disabled/unavailable. + u64 disk_used; ///< Used disk space in bytes for the root filesystem. + u64 disk_total; ///< Total disk space in bytes for the root filesystem. + String shell; ///< Name of the current user shell (e.g., "Bash", "Zsh", "PowerShell"). + // clang-format on + + /** + * @brief Fetches all system data asynchronously. + * @param config The application configuration. + * @return A populated SystemData object. + */ static fn fetchSystemData(const Config& config) -> SystemData; }; diff --git a/src/main.cpp b/src/main.cpp index 711296d..2830cb6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,19 +33,19 @@ namespace ui { }; struct Icons { - [[maybe_unused]] StringView user; - [[maybe_unused]] StringView palette; - [[maybe_unused]] StringView calendar; - [[maybe_unused]] StringView host; - [[maybe_unused]] StringView kernel; - [[maybe_unused]] StringView os; - [[maybe_unused]] StringView memory; - [[maybe_unused]] StringView weather; - [[maybe_unused]] StringView music; - [[maybe_unused]] StringView disk; - [[maybe_unused]] StringView shell; - [[maybe_unused]] StringView desktop; - [[maybe_unused]] StringView window_manager; + StringView user; + StringView palette; + StringView calendar; + StringView host; + StringView kernel; + StringView os; + StringView memory; + StringView weather; + StringView music; + StringView disk; + StringView shell; + StringView desktop; + StringView window_manager; }; static constexpr Icons EMPTY_ICONS = { @@ -105,21 +105,25 @@ namespace { content.push_back(text(String(userIcon) + "Hello " + name + "! ") | bold | color(Color::Cyan)); content.push_back(separator() | color(ui::DEFAULT_THEME.border)); - content.push_back(hbox({ - text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon), - CreateColorCircles(), - })); + content.push_back(hbox( + { + text(String(paletteIcon)) | color(ui::DEFAULT_THEME.icon), + CreateColorCircles(), + } + )); content.push_back(separator() | color(ui::DEFAULT_THEME.border)); // Helper function for aligned rows fn createRow = [&](const auto& icon, const auto& label, const auto& value) { - return hbox({ - text(String(icon)) | color(ui::DEFAULT_THEME.icon), - text(String(static_cast(label))) | color(ui::DEFAULT_THEME.label), - filler(), - text(String(value)) | color(ui::DEFAULT_THEME.value), - text(" "), - }); + return hbox( + { + text(String(icon)) | color(ui::DEFAULT_THEME.icon), + text(String(static_cast(label))) | color(ui::DEFAULT_THEME.label), + filler(), + text(String(value)) | color(ui::DEFAULT_THEME.value), + text(" "), + } + ); }; // System info rows @@ -130,31 +134,39 @@ namespace { const WeatherOutput& weatherInfo = data.weather_info.value(); if (weather.show_town_name) - content.push_back(hbox({ - text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon), - text("Weather") | color(ui::DEFAULT_THEME.label), - filler(), + content.push_back(hbox( + { + text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon), + text("Weather") | color(ui::DEFAULT_THEME.label), + filler(), - hbox({ - text(std::format("{}°F ", std::lround(weatherInfo.main.temp))), - text("in "), - text(weatherInfo.name), - text(" "), - }) | - color(ui::DEFAULT_THEME.value), - })); + hbox( + { + text(std::format("{}°F ", std::lround(weatherInfo.main.temp))), + text("in "), + text(weatherInfo.name), + text(" "), + } + ) | + color(ui::DEFAULT_THEME.value), + } + )); else - content.push_back(hbox({ - text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon), - text("Weather") | color(ui::DEFAULT_THEME.label), - filler(), + content.push_back(hbox( + { + text(String(weatherIcon)) | color(ui::DEFAULT_THEME.icon), + text("Weather") | color(ui::DEFAULT_THEME.label), + filler(), - hbox({ - text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), - text(" "), - }) | - color(ui::DEFAULT_THEME.value), - })); + hbox( + { + text(std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)), + text(" "), + } + ) | + color(ui::DEFAULT_THEME.value), + } + )); } content.push_back(separator() | color(ui::DEFAULT_THEME.border)); @@ -196,14 +208,16 @@ namespace { const String& npText = *nowPlayingResult; content.push_back(separator() | color(ui::DEFAULT_THEME.border)); - content.push_back(hbox({ - text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon), - text("Playing") | color(ui::DEFAULT_THEME.label), - text(" "), - filler(), - paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH), - text(" "), - })); + content.push_back(hbox( + { + text(String(musicIcon)) | color(ui::DEFAULT_THEME.icon), + text("Playing") | color(ui::DEFAULT_THEME.label), + text(" "), + filler(), + paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, ui::MAX_PARAGRAPH_LENGTH), + text(" "), + } + )); } else { const NowPlayingError& error = nowPlayingResult.error(); @@ -229,6 +243,9 @@ namespace { } fn main() -> i32 { + std::locale::global(std::locale("")); + DEBUG_LOG("Global locale set to: {}", std::locale().name()); + const Config& config = Config::getInstance(); const SystemData data = SystemData::fetchSystemData(config); diff --git a/src/os/linux.cpp b/src/os/linux.cpp index 06e07bb..f4113b1 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -3,8 +3,7 @@ // clang-format off #include // needs to be at top for Success/None // clang-format on -#include -#include +#include #include #include #include @@ -13,6 +12,7 @@ #include #include #include +#include #include "os.h" #include "src/os/linux/display_guards.h" @@ -22,77 +22,69 @@ namespace fs = std::filesystem; using namespace std::string_view_literals; namespace { - using os::linux::DisplayGuard; - using os::linux::WaylandDisplayGuard; - - constexpr auto Trim(StringView sv) -> StringView { + constexpr auto Trim(StringView sview) -> StringView { using namespace std::ranges; constexpr auto isSpace = [](const char character) { return std::isspace(static_cast(character)); }; - const borrowed_iterator_t start = find_if_not(sv, isSpace); - const borrowed_iterator_t> rstart = find_if_not(sv | views::reverse, isSpace); + const borrowed_iterator_t start = find_if_not(sview, isSpace); + const borrowed_iterator_t> rstart = find_if_not(sview | views::reverse, isSpace); - return sv.substr(start - sv.begin(), sv.size() - (rstart - sv.rbegin())); + return sview.substr(start - sview.begin(), sview.size() - (rstart - sview.rbegin())); } fn GetX11WindowManager() -> String { - const DisplayGuard display; + using os::linux::XcbReplyGuard; + using os::linux::XorgDisplayGuard; - if (!display) + const XorgDisplayGuard conn; + if (!conn) return ""; - const Atom supportingWmCheck = XInternAtom(display.get(), "_NET_SUPPORTING_WM_CHECK", False); - const Atom wmName = XInternAtom(display.get(), "_NET_WM_NAME", False); - const Atom utf8String = XInternAtom(display.get(), "UTF8_STRING", False); + fn internAtom = [&conn](const StringView name) -> XcbReplyGuard { + const auto cookie = xcb_intern_atom(conn.get(), 0, static_cast(name.size()), name.data()); + return XcbReplyGuard(xcb_intern_atom_reply(conn.get(), cookie, nullptr)); + }; - const Window root = display.defaultRootWindow(); + const XcbReplyGuard supportingWmCheck = internAtom("_NET_SUPPORTING_WM_CHECK"); + const XcbReplyGuard wmName = internAtom("_NET_WM_NAME"); + const XcbReplyGuard utf8String = internAtom("UTF8_STRING"); - Atom actualType = 0; - i32 actualFormat = 0; - u64 nitems = 0, bytesAfter = 0; - u8* data = nullptr; + if (!supportingWmCheck || !wmName || !utf8String) + return "Unknown (X11)"; - if (XGetWindowProperty( - display.get(), - root, - supportingWmCheck, - 0, - 1, - False, - XA_WINDOW, - &actualType, - &actualFormat, - &nitems, - &bytesAfter, - &data - ) == Success && - data) { - const UniquePointer dataGuard(data, XFree); + const xcb_window_t root = conn.rootScreen()->root; - u8* nameData = nullptr; + fn getProperty = [&conn]( + const xcb_window_t window, + const xcb_atom_t property, + const xcb_atom_t type, + const uint32_t offset, + const uint32_t length + ) -> XcbReplyGuard { + const xcb_get_property_cookie_t cookie = xcb_get_property(conn.get(), 0, window, property, type, offset, length); + return XcbReplyGuard(xcb_get_property_reply(conn.get(), cookie, nullptr)); + }; - if (XGetWindowProperty( - display.get(), - *reinterpret_cast(data), - wmName, - 0, - 1024, - False, - utf8String, - &actualType, - &actualFormat, - &nitems, - &bytesAfter, - &nameData - ) == Success && - nameData) { - const UniquePointer nameGuard(nameData, XFree); - return reinterpret_cast(nameData); - } - } + const XcbReplyGuard wmWindowReply = + getProperty(root, supportingWmCheck->atom, XCB_ATOM_WINDOW, 0, 1); - return "Unknown (X11)"; + if (!wmWindowReply || wmWindowReply->type != XCB_ATOM_WINDOW || wmWindowReply->format != 32 || + xcb_get_property_value_length(wmWindowReply.get()) == 0) + return "Unknown (X11)"; + + const xcb_window_t wmWindow = *static_cast(xcb_get_property_value(wmWindowReply.get())); + + const XcbReplyGuard wmNameReply = + getProperty(wmWindow, wmName->atom, utf8String->atom, 0, 1024); + + if (!wmNameReply || wmNameReply->type != utf8String->atom || xcb_get_property_value_length(wmNameReply.get()) == 0) + return "Unknown (X11)"; + + const char* nameData = static_cast(xcb_get_property_value(wmNameReply.get())); + const usize length = xcb_get_property_value_length(wmNameReply.get()); + + return { nameData, length }; } fn ReadProcessCmdline(const i32 pid) -> String { @@ -124,6 +116,8 @@ namespace { } fn GetWaylandCompositor() -> String { + using os::linux::WaylandDisplayGuard; + if (const Option hypr = DetectHyprlandSpecific()) return *hypr; diff --git a/src/os/linux/display_guards.cpp b/src/os/linux/display_guards.cpp index 9a25635..c417e7e 100644 --- a/src/os/linux/display_guards.cpp +++ b/src/os/linux/display_guards.cpp @@ -7,35 +7,36 @@ #include "src/util/macros.h" namespace os::linux { - DisplayGuard::DisplayGuard(const CStr name) : m_Display(XOpenDisplay(name)) {} + XorgDisplayGuard::XorgDisplayGuard(const CStr name) : m_Connection(xcb_connect(name, nullptr)) {} - DisplayGuard::~DisplayGuard() { - if (m_Display) - XCloseDisplay(m_Display); + XorgDisplayGuard::~XorgDisplayGuard() { + if (m_Connection) + xcb_disconnect(m_Connection); } - DisplayGuard::DisplayGuard(DisplayGuard&& other) noexcept : m_Display(std::exchange(other.m_Display, nullptr)) {} + XorgDisplayGuard::XorgDisplayGuard(XorgDisplayGuard&& other) noexcept + : m_Connection(std::exchange(other.m_Connection, nullptr)) {} - fn DisplayGuard::operator=(DisplayGuard&& other) noexcept -> DisplayGuard& { + fn XorgDisplayGuard::operator=(XorgDisplayGuard&& other) noexcept -> XorgDisplayGuard& { if (this != &other) { - if (m_Display) - XCloseDisplay(m_Display); - - m_Display = std::exchange(other.m_Display, nullptr); + if (m_Connection) + xcb_disconnect(m_Connection); + m_Connection = std::exchange(other.m_Connection, nullptr); } - return *this; } - DisplayGuard::operator bool() const { return m_Display != nullptr; } + XorgDisplayGuard::operator bool() const { return m_Connection && !xcb_connection_has_error(m_Connection); } - fn DisplayGuard::get() const -> Display* { return m_Display; } + fn XorgDisplayGuard::get() const -> xcb_connection_t* { return m_Connection; } - fn DisplayGuard::defaultRootWindow() const -> Window { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" - return DefaultRootWindow(m_Display); -#pragma clang diagnostic pop + fn XorgDisplayGuard::setup() const -> const xcb_setup_t* { + return m_Connection ? xcb_get_setup(m_Connection) : nullptr; + } + + fn XorgDisplayGuard::rootScreen() const -> xcb_screen_t* { + const xcb_setup_t* setup = this->setup(); + return setup ? xcb_setup_roots_iterator(setup).data : nullptr; } WaylandDisplayGuard::WaylandDisplayGuard() : m_Display(wl_display_connect(nullptr)) {} diff --git a/src/os/linux/display_guards.h b/src/os/linux/display_guards.h index 6e97b6e..8733e2f 100644 --- a/src/os/linux/display_guards.h +++ b/src/os/linux/display_guards.h @@ -2,8 +2,8 @@ #ifdef __linux__ -#include #include +#include #include "src/util/macros.h" @@ -12,28 +12,70 @@ namespace os::linux { * RAII wrapper for X11 Display connections * Automatically handles resource acquisition and cleanup */ - class DisplayGuard { - Display* m_Display; + class XorgDisplayGuard { + xcb_connection_t* m_Connection = nullptr; public: /** - * Opens an X11 display connection + * Opens an XCB connection * @param name Display name (nullptr for default) */ - explicit DisplayGuard(CStr name = nullptr); - ~DisplayGuard(); + explicit XorgDisplayGuard(CStr name = nullptr); + ~XorgDisplayGuard(); // Non-copyable - DisplayGuard(const DisplayGuard&) = delete; - fn operator=(const DisplayGuard&)->DisplayGuard& = delete; + XorgDisplayGuard(const XorgDisplayGuard&) = delete; + fn operator=(const XorgDisplayGuard&)->XorgDisplayGuard& = delete; // Movable - DisplayGuard(DisplayGuard&& other) noexcept; - fn operator=(DisplayGuard&& other) noexcept -> DisplayGuard&; + XorgDisplayGuard(XorgDisplayGuard&& other) noexcept; + fn operator=(XorgDisplayGuard&& other) noexcept -> XorgDisplayGuard&; [[nodiscard]] explicit operator bool() const; - [[nodiscard]] fn get() const -> Display*; - [[nodiscard]] fn defaultRootWindow() const -> Window; + + [[nodiscard]] fn get() const -> xcb_connection_t*; + [[nodiscard]] fn setup() const -> const xcb_setup_t*; + [[nodiscard]] fn rootScreen() const -> xcb_screen_t*; + }; + + /** + * RAII wrapper for XCB replies + * Handles automatic cleanup of various XCB reply objects + */ + template + class XcbReplyGuard { + T* m_Reply = nullptr; + + public: + XcbReplyGuard() = default; + explicit XcbReplyGuard(T* reply) : m_Reply(reply) {} + + ~XcbReplyGuard() { + if (m_Reply) + free(m_Reply); + } + + // Non-copyable + XcbReplyGuard(const XcbReplyGuard&) = delete; + fn operator=(const XcbReplyGuard&)->XcbReplyGuard& = delete; + + // Movable + XcbReplyGuard(XcbReplyGuard&& other) noexcept : m_Reply(std::exchange(other.m_Reply, nullptr)) {} + fn operator=(XcbReplyGuard&& other) noexcept -> XcbReplyGuard& { + if (this != &other) { + if (m_Reply) + free(m_Reply); + + m_Reply = std::exchange(other.m_Reply, nullptr); + } + return *this; + } + + [[nodiscard]] explicit operator bool() const { return m_Reply != nullptr; } + + [[nodiscard]] fn get() const -> T* { return m_Reply; } + [[nodiscard]] fn operator->() const->T* { return m_Reply; } + [[nodiscard]] fn operator*() const->T& { return *m_Reply; } }; /** @@ -59,8 +101,9 @@ namespace os::linux { fn operator=(WaylandDisplayGuard&& other) noexcept -> WaylandDisplayGuard&; [[nodiscard]] explicit operator bool() const; - [[nodiscard]] fn get() const -> wl_display*; - [[nodiscard]] fn fd() const -> i32; + + [[nodiscard]] fn get() const -> wl_display*; + [[nodiscard]] fn fd() const -> i32; }; } diff --git a/src/util/macros.h b/src/util/macros.h index 16b9dc9..741cddf 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -1,5 +1,7 @@ +// ReSharper disable CppDFAConstantParameter #pragma once +// Fixes conflict in Windows with #ifdef _WIN32 #undef ERROR #endif @@ -13,139 +15,213 @@ #include "types.h" +/// Macro alias for trailing return type functions. #define fn auto -#ifdef None -#undef None -#endif - +/// Macro alias for std::nullopt, represents an empty optional value. #define None std::nullopt +/** + * @namespace term + * @brief Provides terminal-related utilities, including color and style formatting. + */ namespace term { - enum class Emphasis : u8 { none = 0, bold = 1, italic = 2 }; + /** + * @enum Emphasis + * @brief Represents text emphasis styles. + * + * Enum values can be combined using bitwise OR to apply multiple styles at once. + */ + enum class Emphasis : u8 { + Bold, ///< Bold text. + Italic ///< Italic text. + }; + /** + * @enum Color + * @brief Represents ANSI color codes for terminal output. + * + * Color codes can be used to format terminal output. + */ + enum class Color : u8 { + Black = 30, ///< Black color. + Red = 31, ///< Red color. + Green = 32, ///< Green color. + Yellow = 33, ///< Yellow color. + Blue = 34, ///< Blue color. + Magenta = 35, ///< Magenta color. + Cyan = 36, ///< Cyan color. + White = 37, ///< White color. + BrightBlack = 90, ///< Bright black (gray) color. + BrightRed = 91, ///< Bright red color. + BrightGreen = 92, ///< Bright green color. + BrightYellow = 93, ///< Bright yellow color. + BrightBlue = 94, ///< Bright blue color. + BrightMagenta = 95, ///< Bright magenta color. + BrightCyan = 96, ///< Bright cyan color. + BrightWhite = 97, ///< Bright white color. + }; + + /** + * @brief Combines two emphasis styles using bitwise OR. + * @param emphA The first emphasis style. + * @param emphB The second emphasis style. + * @return The combined emphasis style. + */ constexpr fn operator|(Emphasis emphA, Emphasis emphB)->Emphasis { - return static_cast(static_cast(emphA) | static_cast(emphB)); + return static_cast(static_cast(emphA) | static_cast(emphB)); } - // clang-format off - enum class Color : u8 { - black [[maybe_unused]] = 30, - red [[maybe_unused]] = 31, - green [[maybe_unused]] = 32, - yellow [[maybe_unused]] = 33, - blue [[maybe_unused]] = 34, - magenta [[maybe_unused]] = 35, - cyan [[maybe_unused]] = 36, - white [[maybe_unused]] = 37, - bright_black [[maybe_unused]] = 90, - bright_red [[maybe_unused]] = 91, - bright_green [[maybe_unused]] = 92, - bright_yellow [[maybe_unused]] = 93, - bright_blue [[maybe_unused]] = 94, - bright_magenta [[maybe_unused]] = 95, - bright_cyan [[maybe_unused]] = 96, - bright_white [[maybe_unused]] = 97 - }; - // clang-format on - - struct FgColor { - Color col; - - constexpr explicit FgColor(const Color color) : col(color) {} - - [[nodiscard]] fn ansiCode() const -> String { return std::format("\033[{}m", static_cast(col)); } - }; + /** + * @brief Checks if two emphasis styles are equal using bitwise AND. + * @param emphA The first emphasis style. + * @param emphB The second emphasis style. + * @return The result of the bitwise AND operation. + */ + constexpr fn operator&(Emphasis emphA, Emphasis emphB)->u8 { return static_cast(emphA) & static_cast(emphB); } + /** + * @struct Style + * @brief Represents a combination of text styles. + * + * Emphasis and color are both optional, allowing for flexible styling. + */ struct Style { - Emphasis emph = Emphasis::none; - FgColor fg_col = FgColor(static_cast(-1)); + Option emph; ///< Optional emphasis style. + Option fg_col; ///< Optional foreground color style. + /** + * @brief Generates the ANSI escape code for the combined styles. + * @return The ANSI escape code for the combined styles. + */ [[nodiscard]] fn ansiCode() const -> String { String result; - if (emph != Emphasis::none) { - if ((static_cast(emph) & static_cast(Emphasis::bold)) != 0) + if (emph) { + if ((*emph & Emphasis::Bold) != 0) result += "\033[1m"; - if ((static_cast(emph) & static_cast(Emphasis::italic)) != 0) + if ((*emph & Emphasis::Italic) != 0) result += "\033[3m"; } - if (static_cast(fg_col.col) != -1) - result += fg_col.ansiCode(); + if (fg_col) + result += std::format("\033[{}m", static_cast(*fg_col)); return result; } }; - constexpr fn operator|(const Emphasis emph, const FgColor fgColor)->Style { + /** + * @brief Combines an emphasis style and a foreground color into a Style. + * @param emph The emphasis style to apply. + * @param fgColor The foreground color to apply. + * @return The combined style. + */ + constexpr fn operator|(const Emphasis emph, const Color fgColor)->Style { return { .emph = emph, .fg_col = fgColor }; } - constexpr fn operator|(const FgColor fgColor, const Emphasis emph)->Style { + /** + * @brief Combines a foreground color and an emphasis style into a Style. + * @param fgColor The foreground color to apply. + * @param emph The emphasis style to apply. + * @return The combined style. + */ + constexpr fn operator|(const Color fgColor, const Emphasis emph)->Style { return { .emph = emph, .fg_col = fgColor }; } - constexpr CStr reset = "\033[0m"; - + /** + * @brief Prints formatted text with the specified style. + * @tparam Args Parameter pack for format arguments. + * @param style The Style object containing emphasis and/or color. + * @param fmt The format string. + * @param args The arguments for the format string. + */ template fn Print(const Style& style, std::format_string fmt, Args&&... args) -> void { - std::print("{}{}{}", style.ansiCode(), std::format(fmt, std::forward(args)...), reset); + if (const String styleCode = style.ansiCode(); styleCode.empty()) + std::print(fmt, std::forward(args)...); + else + std::print("{}{}{}", styleCode, std::format(fmt, std::forward(args)...), "\033[0m"); } + /** + * @brief Prints formatted text with the specified foreground color. + * @tparam Args Parameter pack for format arguments. + * @param fgColor The foreground color to apply. + * @param fmt The format string. + * @param args The arguments for the format string. + */ template - fn Print(const FgColor& fgColor, std::format_string fmt, Args&&... args) -> void { - std::print("{}{}{}", fgColor.ansiCode(), std::format(fmt, std::forward(args)...), reset); + fn Print(const Color& fgColor, std::format_string fmt, Args&&... args) -> void { + Print({ .emph = None, .fg_col = fgColor }, fmt, std::forward(args)...); } + /** + * @brief Prints formatted text with the specified emphasis style. + * @tparam Args Parameter pack for format arguments. + * @param emph The emphasis style to apply. + * @param fmt The format string. + * @param args The arguments for the format string. + */ template - fn Print(Emphasis emph, std::format_string fmt, Args&&... args) -> void { - Print({ .emph = emph }, fmt, std::forward(args)...); + fn Print(const Emphasis emph, std::format_string fmt, Args&&... args) -> void { + Print({ .emph = emph, .fg_col = None }, fmt, std::forward(args)...); } + /** + * @brief Prints formatted text with no specific style (default terminal style). + * @tparam Args Parameter pack for format arguments. + * @param fmt The format string. + * @param args The arguments for the format string. + */ template fn Print(std::format_string fmt, Args&&... args) -> void { + // Directly use std::print for unstyled output std::print(fmt, std::forward(args)...); } } -namespace log_colors { - using term::Color; - - constexpr Color debug = Color::cyan, info = Color::green, warn = Color::yellow, error = Color::red, - timestamp = Color::bright_white, file_info = Color::bright_white; -} - +/** + * @enum LogLevel + * @brief Represents different log levels. + */ enum class LogLevel : u8 { DEBUG, INFO, WARN, ERROR }; +/** + * @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). + * @param loc The source location of the log message. + * @param fmt The format string. + * @param args The arguments for the format string. + */ template -fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string fmt, Args&&... args) - -> void { +fn LogImpl(const LogLevel level, const std::source_location& loc, std::format_string fmt, Args&&... args) { using namespace std::chrono; - - const time_point> now = std::chrono::floor(system_clock::now()); + using namespace term; + using enum Color; const auto [color, levelStr] = [&] { switch (level) { - case LogLevel::DEBUG: return std::make_pair(log_colors::debug, "DEBUG"); - case LogLevel::INFO: return std::make_pair(log_colors::info, "INFO "); - case LogLevel::WARN: return std::make_pair(log_colors::warn, "WARN "); - case LogLevel::ERROR: return std::make_pair(log_colors::error, "ERROR"); + case LogLevel::DEBUG: return std::make_pair(Cyan, "DEBUG"); + case LogLevel::INFO: return std::make_pair(Green, "INFO "); + case LogLevel::WARN: return std::make_pair(Yellow, "WARN "); + case LogLevel::ERROR: return std::make_pair(Red, "ERROR"); default: std::unreachable(); } }(); const String filename = std::filesystem::path(loc.file_name()).lexically_normal().string(); - using namespace term; - - Print(FgColor(log_colors::timestamp), "[{:%H:%M:%S}] ", now); - Print(Emphasis::bold | FgColor(color), "{} ", levelStr); + Print(BrightWhite, "[{:%X}] ", zoned_time { current_zone(), std::chrono::floor(system_clock::now()) }); + Print(Emphasis::Bold | color, "{} ", levelStr); Print(fmt, std::forward(args)...); #ifndef NDEBUG - Print(FgColor(log_colors::file_info), "\n{:>14} ", "╰──"); - Print(Emphasis::italic | FgColor(log_colors::file_info), "{}:{}", filename, loc.line()); + Print(BrightWhite, "\n{:>14} ", "╰──"); + Print(Emphasis::Italic | BrightWhite, "{}:{}", filename, loc.line()); #endif Print("\n");