diff --git a/flake.nix b/flake.nix index 6f7548f..8d721f0 100644 --- a/flake.nix +++ b/flake.nix @@ -139,6 +139,7 @@ clang-tools_19 cmake lldb + hyperfine meson ninja nvfetcher diff --git a/meson.build b/meson.build index 77052dc..947340f 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'draconis++', 'cpp', version: '0.1.0', default_options: [ - 'cpp_std=c++20', + 'cpp_std=c++26', 'default_library=static', 'warning_level=everything', 'buildtype=debugoptimized' diff --git a/src/config/weather.cpp b/src/config/weather.cpp index 33e23f4..36fd6dc 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,87 +9,111 @@ using rfl::Error; using rfl::Result; +namespace fs = std::filesystem; -// Function to read cache from file -fn ReadCacheFromFile() -> Result { -#ifdef __WIN32__ - const char* tempPath = getenv("TEMP"); - const string path = string(tempPath) + "\\weather_cache.json"; - std::ifstream ifs(path); -#else - std::ifstream ifs("/tmp/weather_cache.json"); -#endif +namespace { + // Common function to get cache path + fn GetCachePath() -> Result { + std::error_code errc; + fs::path cachePath = fs::temp_directory_path(errc); - if (!ifs.is_open()) - return Error("Cache file not found."); + if (errc) + return Error("Failed to get temp directory: " + errc.message()); - fmt::println("Reading from cache file..."); - - std::stringstream buf; - - buf << ifs.rdbuf(); - - Result val = rfl::json::read(buf.str()); - - fmt::println("Successfully read from cache file."); - - return val; -} - -// Function to write cache to file -fn WriteCacheToFile(const WeatherOutput& data) -> Result { - fmt::println("Writing to cache file..."); - -#ifdef __WIN32__ - const char* tempPath = getenv("TEMP"); - const string path = string(tempPath) + "\\weather_cache.json"; - std::ofstream ofs(path); -#else - std::ofstream ofs("/tmp/weather_cache.json"); -#endif - - if (!ofs.is_open()) - return Error("Failed to open cache file for writing."); - - ofs << rfl::json::write(data); - - fmt::println("Successfully wrote to cache file."); - - return 0; -} - -fn WriteCallback(void* contents, const usize size, const usize nmemb, string* str) -> usize { - const usize totalSize = size * nmemb; - str->append(static_cast(contents), totalSize); - return totalSize; -} - -// Function to make API request -fn MakeApiRequest(const string& url) -> Result { - fmt::println("Making API request to URL: {}", url); - - CURL* curl = curl_easy_init(); - string responseBuffer; - - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer); - const CURLcode res = curl_easy_perform(curl); - curl_easy_cleanup(curl); - - if (res != CURLE_OK) - return Error(fmt::format("Failed to perform cURL request: {}", curl_easy_strerror(res))); - - fmt::println("Received response from API. Response size: {}", responseBuffer.size()); - fmt::println("Response: {}", responseBuffer); - - WeatherOutput output = rfl::json::read(responseBuffer).value(); - - return output; // Return an empty result for now + cachePath /= "weather_cache.json"; + return cachePath; } - return Error("Failed to initialize cURL."); + // Function to read cache from file + fn ReadCacheFromFile() -> Result { + Result cachePath = GetCachePath(); + if (!cachePath) + return Error(cachePath.error()->what()); + + std::ifstream ifs(*cachePath, std::ios::binary); + if (!ifs.is_open()) + return Error("Cache file not found: " + cachePath.value().string()); + + DEBUG_LOG("Reading from cache file..."); + + std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + + Result result = rfl::json::read(content); + + DEBUG_LOG("Successfully read from cache file."); + return result; + } + + // Function to write cache to file + fn WriteCacheToFile(const WeatherOutput& data) -> Result { + Result cachePath = GetCachePath(); + if (!cachePath) + return Error(cachePath.error()->what()); + + DEBUG_LOG("Writing to cache file..."); + + // Write to temporary file first + fs::path tempPath = *cachePath; + tempPath += ".tmp"; + + { + std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc); + if (!ofs.is_open()) + return Error("Failed to open temp file: " + tempPath.string()); + + auto json = rfl::json::write(data); + ofs << json; + + if (!ofs) + return Error("Failed to write to temp file"); + } // File stream closes here + + // Atomic replace + std::error_code errc; + fs::rename(tempPath, *cachePath, errc); + + if (errc) { + fs::remove(tempPath, errc); + return Error("Failed to replace cache file: " + errc.message()); + } + + DEBUG_LOG("Successfully wrote to cache file."); + return 0; + } + + fn WriteCallback(void* contents, const usize size, const usize nmemb, string* str) -> usize { + const usize totalSize = size * nmemb; + str->append(static_cast(contents), totalSize); + return totalSize; + } + + // Function to make API request + fn MakeApiRequest(const string& url) -> Result { + DEBUG_LOG("Making API request to URL: {}", url); + + CURL* curl = curl_easy_init(); + string responseBuffer; + + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer); + const CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) + return Error(fmt::format("Failed to perform cURL request: {}", curl_easy_strerror(res))); + + DEBUG_LOG("Received response from API. Response size: {}", responseBuffer.size()); + DEBUG_LOG("Response: {}", responseBuffer); + + WeatherOutput output = rfl::json::read(responseBuffer).value(); + + return output; // Return an empty result for now + } + + return Error("Failed to initialize cURL."); + } } // Core function to get weather information @@ -100,14 +125,14 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { WeatherOutput dataVal = *data; if (system_clock::now() - system_clock::time_point(seconds(dataVal.dt)) < minutes(10)) { - fmt::println("Cache is valid. Returning cached data."); + DEBUG_LOG("Cache is valid. Returning cached data."); return dataVal; } - fmt::println("Cache is expired."); + DEBUG_LOG("Cache is expired."); } else { - fmt::println("No valid cache found."); + DEBUG_LOG("No valid cache found."); } WeatherOutput result; @@ -117,7 +142,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { const char* loc = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); - fmt::println("City: {}", loc); + DEBUG_LOG("City: {}", loc); const string apiUrl = fmt::format( "https://api.openweathermap.org/data/2.5/" @@ -131,7 +156,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { } else { const auto [lat, lon] = get(location); - fmt::println("Coordinates: lat = {:.3f}, lon = {:.3f}", lat, lon); + DEBUG_LOG("Coordinates: lat = {:.3f}, lon = {:.3f}", lat, lon); const string apiUrl = fmt::format( "https://api.openweathermap.org/data/2.5/" @@ -148,7 +173,7 @@ fn Weather::getWeatherInfo() const -> WeatherOutput { // Update the cache with the new data WriteCacheToFile(result); - fmt::println("Returning new data."); + DEBUG_LOG("Returning new data."); return result; } diff --git a/src/config/weather.h b/src/config/weather.h index db3e342..585e026 100644 --- a/src/config/weather.h +++ b/src/config/weather.h @@ -6,7 +6,7 @@ #include "../util/types.h" using degrees = rfl::Validator, rfl::Maximum<360>>; -using percentage = rfl::Validator, rfl::Maximum<100>>; +using percentage = rfl::Validator, rfl::Maximum<100>>; struct Condition { string description; diff --git a/src/main.cpp b/src/main.cpp index 560ead1..16b50fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,6 @@ struct fmt::formatter : fmt::formatter { }; namespace { - fn GetDate() -> std::string { // Get current local time std::time_t now = std::time(nullptr); @@ -97,8 +96,8 @@ namespace { return hbox({ text(emoji), text(label) | color(labelColor), filler(), - text(value), - text(" ") | color(valueColor) }); + text(value) | color(valueColor), + text(" ") }); }; // System info rows @@ -142,6 +141,10 @@ namespace { } fn main() -> i32 { + INFO_LOG("productFamily: {}", GetProductFamily()); + WARN_LOG("productFamily: {}", GetProductFamily()); + ERROR_LOG("productFamily: {}", GetProductFamily()); + const Config& config = Config::getInstance(); auto document = hbox({ SystemInfoBox(config), filler() }); diff --git a/src/os/freebsd.cpp b/src/os/freebsd.cpp index 099e393..f5fdb2b 100644 --- a/src/os/freebsd.cpp +++ b/src/os/freebsd.cpp @@ -3,8 +3,9 @@ #include #include #include -#include #include +#include + #include "os.h" fn GetMemInfo() -> u64 { diff --git a/src/os/linux.cpp b/src/os/linux.cpp index e3c4a64..8850ed2 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -9,7 +9,7 @@ #include "os.h" -enum SessionType { Wayland, X11, TTY, Unknown }; +enum SessionType : u8 { Wayland, X11, TTY, Unknown }; fn ParseLineAsNumber(const std::string& input) -> u64 { usize start = input.find_first_of("0123456789"); @@ -132,4 +132,23 @@ fn GetNowPlaying() -> string { return ""; } +fn GetShell() -> string { + const char* shell = std::getenv("SHELL"); + + return shell ? shell : ""; +} + +fn GetProductFamily() -> string { + std::ifstream file("/sys/class/dmi/id/product_family"); + + if (!file.is_open()) + throw std::runtime_error("Failed to open /sys/class/dmi/id/product_family"); + + std::string productFamily; + + std::getline(file, productFamily); + + return productFamily; +} + #endif diff --git a/src/os/os.h b/src/os/os.h index 0235933..877e2d4 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -27,3 +27,13 @@ fn GetDesktopEnvironment() -> string; * @brief Get the current window manager. */ fn GetWindowManager() -> string; + +/** + * @brief Get the current shell. + */ +fn GetShell() -> string; + +/** + * @brief Get the product family + */ +fn GetProductFamily() -> string; diff --git a/src/util/macros.h b/src/util/macros.h index 0238467..8ab0d35 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -1,32 +1,96 @@ #pragma once -/** - * @brief Allows for rust-style function definitions - */ -#define fn auto +#include +#include +#include +#include +#include -/** - * @brief Allows for easy getter creation - * - * @param class_name The class to use - * @param type Type of the getter - * @param name Name of the getter - */ -#define DEFINE_GETTER(class_name, type, name) \ - fn class_name::get##name() const->type { return m_##name; } +#include "types.h" -/** - * @brief Helper for making reflect-cpp impls - * - * @param struct_name The struct name - * @param lower_name The arg name - * @param ... Values of the class to convert - */ -#define DEF_IMPL(struct_name, ...) \ - struct struct_name##Impl { \ - __VA_ARGS__; \ - \ - static fn from_class(const struct_name& instance) noexcept -> struct_name##Impl; \ - \ - [[nodiscard]] fn to_class() const -> struct_name; \ - }; +#define fn auto // Rust-style function shorthand + +namespace log_colors { + using fmt::terminal_color; + constexpr fmt::terminal_color debug = terminal_color::cyan, info = terminal_color::green, + warn = terminal_color::yellow, error = terminal_color::red, + timestamp = terminal_color::bright_white, + file_info = terminal_color::bright_white; +} + +enum class LogLevel : u8 { DEBUG, INFO, WARN, ERROR }; + +template +fn LogImpl( + LogLevel level, + const std::source_location& loc, + fmt::format_string fmt, + Args&&... args +) -> void { + const time_t now = std::time(nullptr); + 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"); + } + }(); + + const std::string filename = std::filesystem::path(loc.file_name()).lexically_normal().string(); + const u32 line = loc.line(); + const struct tm time = *std::localtime(&now); + + // Timestamp section + fmt::print(fg(log_colors::timestamp), "[{:%H:%M:%S}] ", time); + + // Level section + fmt::print(fmt::emphasis::bold | fg(color), "{}", levelStr); + + // Message section + fmt::print(" "); + fmt::print(fmt, std::forward(args)...); + + // File info section +#ifndef NDEBUG + fmt::print(fg(log_colors::file_info), "\n{:>14} ", "╰──"); + const std::string fileInfo = fmt::format("{}:{}", filename.c_str(), line); + fmt::print(fmt::emphasis::italic | fg(log_colors::file_info), "{}", fileInfo); +#endif + + fmt::print("\n"); +} + +// Logging utility wrapper to replace macros +// Logging utility wrapper to replace macros +template +struct LogWrapper { + std::source_location m_loc; // Changed to m_loc + + constexpr LogWrapper(const std::source_location& loc = std::source_location::current()) + : m_loc(loc) {} // Initialize member with parameter + + template + void operator()(fmt::format_string fmt, Args&&... args) const { + LogImpl(level, m_loc, fmt, std::forward(args)...); // Use m_loc + } +}; + +// Debug logging is conditionally compiled +#ifdef NDEBUG +struct { + template + void operator()(fmt::format_string, Args&&...) const {} +} DEBUG_LOG; +#else +constexpr LogWrapper DEBUG_LOG; +#endif + +// Define loggers for other levels +constexpr LogWrapper INFO_LOG; +constexpr LogWrapper WARN_LOG; +constexpr LogWrapper ERROR_LOG;