diff --git a/.clang-format b/.clang-format index 391e65c..079241b 100644 --- a/.clang-format +++ b/.clang-format @@ -1,12 +1,14 @@ --- -Language: Cpp +AlignConsecutiveAssignments: true +AllowShortLoopsOnASingleLine: true BasedOnStyle: Chromium IndentAccessModifiers: false +IndentExternBlock: Indent +Language: Cpp NamespaceIndentation: All SpaceBeforeCpp11BracedList: true SpacesBeforeTrailingComments: 1 -AlignConsecutiveAssignments: true -IndentExternBlock: Indent + IncludeBlocks: Regroup IncludeCategories: - Regex: '".*"' diff --git a/.clang-tidy b/.clang-tidy index 95811f7..0c7feb1 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,29 +1,22 @@ Checks: > - bugprone-*, - clang-analyzer-*, - clang-diagnostic-*, - misc-const-correctness, - misc-definitions-in-headers, - modernize-*, - performance-*, - portability-*, - readability-*, - cppcoreguidelines-prefer-member-initializer, - modernize-use-designated-initializers, + *, -altera-*, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, -concurrency-mt-unsafe, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-type-member-init, -fuchsia-*, -google-*, -hicpp-*, -llvm-include-order, -llvm-include-order, -llvmlibc-*, + -misc-non-private-member-variables-in-classes, -modernize-use-trailing-return-type, -readability-braces-around-statements, - -readability-magic-numbers, - -readability-implicit-bool-conversion + -readability-implicit-bool-conversion, + -readability-magic-numbers CheckOptions: readability-identifier-naming.ClassCase: CamelCase readability-identifier-naming.EnumCase: CamelCase diff --git a/flake.lock b/flake.lock index 9eb57a1..9985190 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1717430266, - "narHash": "sha256-EWy2Qbkl/HUwmO8KDBzgDQf+4rl+fLiPFvp3nUSWcxc=", + "lastModified": 1717646450, + "narHash": "sha256-KE+UmfSVk5PG8jdKdclPVcMrUB8yVZHbsjo7ZT1Bm3c=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d125f0e4d85f1517b639d4a8f848175da46fcd3e", + "rev": "818dbe2f96df233d2041739d6079bb616d3e5597", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e3d1dd3..2f4b6da 100644 --- a/flake.nix +++ b/flake.nix @@ -58,21 +58,21 @@ ); # TODO: Remove when fixed on darwin [ - coost + curl fmt glib - libcpr tomlplusplus + yyjson ]; linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs.llvmPackages_18; [ + systemdLibs sdbus-cpp valgrind ]); darwinPkgs = nixpkgs.lib.optionals stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ Foundation - MediaPlayer ]); in with pkgs; { @@ -89,7 +89,6 @@ ]; propagatedBuildInputs = [ - libcpr tomlplusplus ]; diff --git a/include/util/numtypes.h b/include/util/numtypes.h new file mode 100644 index 0000000..93438c6 --- /dev/null +++ b/include/util/numtypes.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +// Unsigned integers +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +// Signed integers +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; + +// Floating-points +using f32 = float; +using f64 = double; + +// Size types +using usize = std::size_t; +using isize = std::ptrdiff_t; diff --git a/include/util/result.h b/include/util/result.h new file mode 100644 index 0000000..903ad08 --- /dev/null +++ b/include/util/result.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include + +// Define an error type +class Error { + public: + explicit Error(std::string message) : m_Message(std::move(message)) {} + [[nodiscard]] const std::string& message() const { return m_Message; } + + private: + std::string m_Message; +}; + +// Primary template for Result with a default type of void +template +class Result; + +// Specialization for Result +template <> +class Result { + public: + // Constructors for success and error + Result() : m_Result(std::monostate {}) {} + Result(const Error& error) : m_Result(error) {} + Result(Error&& error) : m_Result(std::move(error)) {} + + // Check if the result is an error + [[nodiscard]] bool isOk() const { + return std::holds_alternative(m_Result); + } + [[nodiscard]] bool isErr() const { + return std::holds_alternative(m_Result); + } + + // Throw an exception if it is an error + void value() const { + if (isErr()) { + throw std::logic_error("Attempted to access value of an error Result"); + } + } + + // Get the error or throw an exception if it is a value + [[nodiscard]] const Error& error() const { + if (isOk()) { + throw std::logic_error("Attempted to access error of an ok Result"); + } + return std::get(m_Result); + } + + private: + std::variant m_Result; +}; + +// Primary template for Result +template +class Result { + public: + // Constructors for success and error + Result(const T& value) : m_Result(value) {} + Result(T&& value) : m_Result(std::move(value)) {} + Result(const Error& error) : m_Result(error) {} + Result(Error&& error) : m_Result(std::move(error)) {} + + // Check if the result is an error + [[nodiscard]] bool isOk() const { + return std::holds_alternative(m_Result); + } + [[nodiscard]] bool isErr() const { + return std::holds_alternative(m_Result); + } + + // Get the value or throw an exception if it is an error + const T& value() const { + if (isErr()) { + throw std::logic_error("Attempted to access value of an error Result"); + } + return std::get(m_Result); + } + + // Get the error or throw an exception if it is a value + [[nodiscard]] const Error& error() const { + if (isOk()) { + throw std::logic_error("Attempted to access error of an ok Result"); + } + return std::get(m_Result); + } + + // Optional: Get the value or provide a default + T valueOr(const T& defaultValue) const { + return isOk() ? std::get(m_Result) : defaultValue; + } + + private: + std::variant m_Result; +}; + +template +auto Ok(T&& value) { + return Result>(std::forward(value)); +} + +inline auto Ok() { + return Result(); +} diff --git a/meson.build b/meson.build index d95c069..6f26ace 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,7 @@ if host_machine.system() == 'darwin' '-Wno-c++20-compat', '-Wno-c++98-compat', '-Wno-c++98-compat-pedantic', + '-Wno-disabled-macro-expansion', '-Wno-missing-prototypes', '-Wno-padded', '-Wno-pre-c++20-compat-pedantic', @@ -40,6 +41,7 @@ add_project_arguments( '-Wno-c++20-compat', '-Wno-c++98-compat', '-Wno-c++98-compat-pedantic', + '-Wno-disabled-macro-expansion', '-Wno-missing-prototypes', '-Wno-padded', '-Wno-pre-c++20-compat-pedantic', @@ -77,15 +79,13 @@ endforeach deps = [] -deps += cpp.find_library('cpr') -deps += cpp.find_library('curl') -deps += cpp.find_library('tomlplusplus') -deps += dependency('coost') deps += dependency('fmt') +deps += dependency('libcurl') +deps += dependency('tomlplusplus') +deps += dependency('yyjson') if host_machine.system() == 'darwin' deps += dependency('Foundation') - deps += dependency('MediaPlayer') endif if host_machine.system() == 'linux' diff --git a/src/config/config.cpp b/src/config/config.cpp index 04c65b0..010c785 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -8,17 +8,15 @@ DEFINE_GETTER(Config, const General, General) DEFINE_GETTER(Config, const NowPlaying, NowPlaying) DEFINE_GETTER(Config, const Weather, Weather) -DEFINE_GETTER(General, const string, Name) +DEFINE_GETTER(General, const std::string, Name) DEFINE_GETTER(NowPlaying, bool, Enabled) DEFINE_GETTER(Weather, const Weather::Location, Location) -DEFINE_GETTER(Weather, const string, ApiKey) -DEFINE_GETTER(Weather, const string, Units) +DEFINE_GETTER(Weather, const std::string, ApiKey) +DEFINE_GETTER(Weather, const std::string, Units) const Config& Config::getInstance() { static const auto* INSTANCE = new Config(rfl::toml::load("./config.toml").value()); - static std::once_flag Flag; - std::call_once(Flag, [] { std::atexit([] { delete INSTANCE; }); }); return *INSTANCE; } @@ -27,11 +25,11 @@ Config::Config(General general, NowPlaying now_playing, Weather weather) m_NowPlaying(now_playing), m_Weather(std::move(weather)) {} -General::General(string name) : m_Name(std::move(name)) {} +General::General(std::string name) : m_Name(std::move(name)) {} NowPlaying::NowPlaying(bool enable) : m_Enabled(enable) {} -Weather::Weather(Location location, string api_key, string units) +Weather::Weather(Location location, std::string api_key, std::string units) : m_Location(std::move(location)), m_ApiKey(std::move(api_key)), m_Units(std::move(units)) {} diff --git a/src/config/config.h b/src/config/config.h index 00b7121..f8adea9 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -1,40 +1,102 @@ -#include -#include +#pragma once + #include #include #include #include #include -using std::string; +#include "util/numtypes.h" class Weather { public: + using percentage = rfl::Validator, rfl::Maximum<100>>; + using degrees = rfl::Validator, rfl::Maximum<360>>; + + struct Condition { + usize id; + rfl::Rename<"main", std::string> group; + std::string description; + rfl::Rename<"icon", std::string> icon_id; + }; + + struct Main { + f64 temp; + f64 temp_max; + f64 temp_min; + f64 feels_like; + isize pressure; + std::optional sea_level; + std::optional grnd_level; + percentage humidity; + }; + + struct Wind { + f64 speed; + degrees deg; + std::optional gust; + }; + + struct Precipitation { + rfl::Rename<"1h", f64> one_hour; + rfl::Rename<"3h", f64> three_hours; + }; + + struct Sys { + std::string country; + usize id; + usize sunrise; + usize sunset; + usize type; + }; + + struct Clouds { + percentage all; + }; + struct Coords { double lat; double lon; }; - using Location = std::variant; + struct WeatherOutput { + isize timezone; + isize visibility; + Main main; + Clouds clouds; + rfl::Rename<"coord", Coords> coords; + std::optional rain; + std::optional snow; + std::vector weather; + std::string base; + std::string name; + Sys sys; + usize cod; + usize dt; + usize id; + Wind wind; + }; + + using Location = std::variant; private: Location m_Location; - string m_ApiKey; - string m_Units; + std::string m_ApiKey; + std::string m_Units; public: - Weather(Location location, string api_key, string units); + Weather(Location location, std::string api_key, std::string units); - [[nodiscard]] co::Json getWeatherInfo() const; + [[nodiscard]] WeatherOutput getWeatherInfo() const; [[nodiscard]] const Location getLocation() const; - [[nodiscard]] const string getApiKey() const; - [[nodiscard]] const string getUnits() const; + [[nodiscard]] const std::string getApiKey() const; + [[nodiscard]] const std::string getUnits() const; }; struct WeatherImpl { Weather::Location location; - string api_key; - string units; + std::string api_key; + std::string units; static WeatherImpl from_class(const Weather& weather) noexcept; @@ -43,16 +105,16 @@ struct WeatherImpl { class General { private: - string m_Name; + std::string m_Name; public: - General(string name); + General(std::string name); - [[nodiscard]] const string getName() const; + [[nodiscard]] const std::string getName() const; }; struct GeneralImpl { - string name; + std::string name; static GeneralImpl from_class(const General& general) noexcept; diff --git a/src/config/weather.cpp b/src/config/weather.cpp index 9ad216d..ca66293 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -1,92 +1,113 @@ +#include +#include +#include +#include + #include "config.h" +using WeatherOutput = Weather::WeatherOutput; + // Function to read cache from file -std::optional> -ReadCacheFromFile() { - const string cacheFile = "/tmp/weather_cache.json"; +Result ReadCacheFromFile() { + const std::string cacheFile = "/tmp/weather_cache.json"; std::ifstream ifs(cacheFile); - if (!ifs.is_open()) { - fmt::println("Cache file not found."); - return std::nullopt; - } + if (!ifs.is_open()) + return Error("Cache file not found."); fmt::println("Reading from cache file..."); - co::Json val; - std::chrono::system_clock::time_point timestamp; + WeatherOutput val; try { std::stringstream buf; + buf << ifs.rdbuf(); - val.parse_from(buf.str()); - - string tsStr = val["timestamp"].as_string().c_str(); - timestamp = std::chrono::system_clock::time_point( - std::chrono::milliseconds(stoll(tsStr))); - - val.erase("timestamp"); - } catch (...) { - fmt::println(stderr, "Failed to read from cache file."); - - return std::nullopt; + val = rfl::json::read(buf.str()).value(); + } catch (Error& e) { + return e; } fmt::println("Successfully read from cache file."); - return make_pair(val, timestamp); + + return Ok(val); } // Function to write cache to file -void WriteCacheToFile(const co::Json& data) { - const string cacheFile = "/tmp/weather_cache.json"; +Result<> WriteCacheToFile(const WeatherOutput& data) { + const std::string cacheFile = "/tmp/weather_cache.json"; fmt::println("Writing to cache file..."); std::ofstream ofs(cacheFile); - if (!ofs.is_open()) { - fmt::println(stderr, "Failed to open cache file for writing."); - return; - } + if (!ofs.is_open()) + return Error("Failed to open cache file for writing."); - data["timestamp"] = - std::to_string(duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count()); - - ofs << data.as_string(); + ofs << rfl::json::write(data); fmt::println("Successfully wrote to cache file."); + + return Ok(); +} + +size_t WriteCallback(void* contents, + size_t size, + size_t nmemb, + std::string* buffer) { + size_t realsize = size * nmemb; + + buffer->append(static_cast(contents), realsize); + + return realsize; } // Function to make API request -co::Json MakeApiRequest(const string& url) { - using namespace cpr; - +Result MakeApiRequest(const std::string& url) { fmt::println("Making API request..."); - const Response res = Get(Url {url}); - fmt::println("Received response from API."); - co::Json json = json::parse(res.text); - return json; + CURL* curl = curl_easy_init(); + + std::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); + 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."); + // Parse the JSON response + WeatherOutput output = + rfl::json::read(responseBuffer).value(); + + return Ok(output); + } + + return Error("Failed to initialize cURL."); } // Core function to get weather information -co::Json Weather::getWeatherInfo() const { - using namespace cpr; +WeatherOutput Weather::getWeatherInfo() const { + using namespace std::chrono; - const Location loc = m_Location; - const string apiKey = m_ApiKey; - const string units = m_Units; + const Location loc = m_Location; + const std::string apiKey = m_ApiKey; + const std::string units = m_Units; // Check if cache is valid - if (auto cachedData = ReadCacheFromFile()) { - auto& [data, timestamp] = *cachedData; + if (Result data = ReadCacheFromFile(); data.isOk()) { + WeatherOutput dataVal = data.value(); - if (std::chrono::system_clock::now() - timestamp < - std::chrono::minutes( - 10)) { // Assuming cache duration is always 10 minutes + if (system_clock::now() - system_clock::time_point(seconds(dataVal.dt)) < + minutes(10)) { // Assuming cache duration is always 10 minutes fmt::println("Cache is valid. Returning cached data."); - return data; + + return dataVal; } fmt::println("Cache is expired."); @@ -94,17 +115,17 @@ co::Json Weather::getWeatherInfo() const { fmt::println("No valid cache found."); } - co::Json result; + WeatherOutput result; - if (holds_alternative(loc)) { - const string city = get(loc); + if (holds_alternative(loc)) { + const std::string city = get(loc); const char* location = curl_easy_escape(nullptr, city.c_str(), static_cast(city.length())); fmt::println("City: {}", location); - const string apiUrl = format( + const std::string apiUrl = fmt::format( "https://api.openweathermap.org/data/2.5/" "weather?q={}&appid={}&units={}", location, apiKey, units); @@ -115,7 +136,7 @@ co::Json Weather::getWeatherInfo() const { fmt::println("Coordinates: lat = {:.3f}, lon = {:.3f}", lat, lon); - const string apiUrl = format( + const std::string apiUrl = fmt::format( "https://api.openweathermap.org/data/2.5/" "weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, apiKey, units); diff --git a/src/main.cpp b/src/main.cpp index 337f4d6..aa8c802 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,16 +1,14 @@ -#include #include #include #include #include "config/config.h" -#include "os/macos/NowPlayingBridge.h" #include "os/os.h" using std::string; struct BytesToGiB { - uint64_t value; + u64 value; }; template <> @@ -42,9 +40,7 @@ DateNum ParseDate(string const& input) { return Default; } -int main(int argc, char** argv) { - flag::parse(argc, argv); - +int main() { const Config& config = Config::getInstance(); if (config.getNowPlaying().getEnabled()) @@ -52,22 +48,17 @@ int main(int argc, char** argv) { fmt::println("Hello {}!", config.getGeneral().getName()); - const uint64_t memInfo = GetMemInfo(); + const u64 memInfo = GetMemInfo(); fmt::println("{:.2f}", BytesToGiB {memInfo}); const std::tm localTime = fmt::localtime(time(nullptr)); - auto trimStart = [](std::string& str) { - auto start = str.begin(); - while (start != str.end() && std::isspace(*start)) - ++start; - str.erase(str.begin(), start); - }; - string date = fmt::format("{:%e}", localTime); - trimStart(date); + auto start = date.begin(); + while (start != date.end() && std::isspace(*start)) ++start; + date.erase(date.begin(), start); switch (ParseDate(date)) { case Ones: @@ -89,11 +80,10 @@ int main(int argc, char** argv) { fmt::println("{:%B} {}, {:%-I:%0M %p}", localTime, date, localTime); - co::Json json = config.getWeather().getWeatherInfo(); + Weather::WeatherOutput json = config.getWeather().getWeatherInfo(); - const int temp = json.get("main", "temp").as_int(); - const char* townName = - json["name"].is_string() ? json["name"].as_string().c_str() : "Unknown"; + const long temp = std::lround(json.main.temp); + const string townName = json.name; fmt::println("It is {}°F in {}", temp, townName); diff --git a/src/os/linux.cpp b/src/os/linux.cpp index e12377e..21aae2e 100644 --- a/src/os/linux.cpp +++ b/src/os/linux.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "os.h" @@ -18,44 +17,41 @@ static const char *DBUS_INTERFACE = "org.freedesktop.DBus", *PLAYER_OBJECT_PATH = "/org/mpris/MediaPlayer2", *PLAYER_INTERFACE_NAME = "org.mpris.MediaPlayer2.Player"; -uint64_t ParseLineAsNumber(const string& input) { +u64 ParseLineAsNumber(const string& input) { // Find the first number string::size_type start = 0; // Skip leading non-numbers - while (!isdigit(input[++start])) - ; + while (!isdigit(input[++start])); // Start searching from the start of the number string::size_type end = start; // Increment to the end of the number - while (isdigit(input[++end])) - ; + while (isdigit(input[++end])); // Return the substring containing the number return std::stoul(input.substr(start, end - start)); } -uint64_t MeminfoParse(std::ifstream input) { +u64 MeminfoParse(std::ifstream input) { string line; // Skip every line before the one that starts with "MemTotal" - while (std::getline(input, line) && !line.starts_with("MemTotal")) - ; + while (std::getline(input, line) && !line.starts_with("MemTotal")); // Parse the number from the line - const uint64_t num = ParseLineAsNumber(line); + const u64 num = ParseLineAsNumber(line); return num; } -uint64_t GetMemInfo() { +u64 GetMemInfo() { return MeminfoParse(std::ifstream("/proc/meminfo")) * 1024; } std::vector GetMprisPlayers(sdbus::IConnection& connection) { - auto dbusProxy = + std::unique_ptr dbusProxy = sdbus::createProxy(connection, DBUS_INTERFACE, DBUS_OBJECT_PATH); std::vector names; @@ -66,7 +62,7 @@ std::vector GetMprisPlayers(sdbus::IConnection& connection) { std::vector mprisPlayers; - for (const auto& name : names) + for (const std::basic_string& name : names) if (name.find(MPRIS_INTERFACE_NAME) != std::string::npos) mprisPlayers.push_back(name); @@ -82,8 +78,10 @@ std::string GetActivePlayer(const std::vector& mprisPlayers) { std::string GetNowPlaying() { try { - auto connection = sdbus::createSessionBusConnection(); - auto mprisPlayers = GetMprisPlayers(*connection); + std::unique_ptr connection = + sdbus::createSessionBusConnection(); + + std::vector mprisPlayers = GetMprisPlayers(*connection); if (mprisPlayers.empty()) return ""; @@ -93,7 +91,7 @@ std::string GetNowPlaying() { if (activePlayer.empty()) return ""; - auto playerProxy = + std::unique_ptr playerProxy = sdbus::createProxy(*connection, activePlayer, PLAYER_OBJECT_PATH); std::map metadata = diff --git a/src/os/macos.cpp b/src/os/macos.cpp index 2f72c5e..fcc2e82 100644 --- a/src/os/macos.cpp +++ b/src/os/macos.cpp @@ -6,7 +6,7 @@ #include "macos/NowPlayingBridge.h" #include "os.h" -uint64_t GetMemInfo() { +u64 GetMemInfo() { uint64_t mem = 0; size_t size = sizeof(mem); diff --git a/src/os/macos/NowPlayingBridge.h b/src/os/macos/NowPlayingBridge.h index a592547..e1fcd6d 100644 --- a/src/os/macos/NowPlayingBridge.h +++ b/src/os/macos/NowPlayingBridge.h @@ -1,5 +1,6 @@ -// NowPlayingBridge.h +#pragma once +#ifdef __APPLE__ #ifdef __OBJC__ #import @@ -12,3 +13,4 @@ extern "C" { const char* GetCurrentPlayingArtist(); } #endif +#endif diff --git a/src/os/macos/NowPlayingBridge.mm b/src/os/macos/NowPlayingBridge.mm index 5868a0f..0ca7bd1 100644 --- a/src/os/macos/NowPlayingBridge.mm +++ b/src/os/macos/NowPlayingBridge.mm @@ -1,4 +1,4 @@ -// NowPlayingBridge.mm +#ifdef __APPLE__ #import "NowPlayingBridge.h" #import @@ -91,3 +91,5 @@ const char *GetCurrentPlayingArtist() { return nullptr; } } + +#endif diff --git a/src/os/os.h b/src/os/os.h index 9a4a44b..560acb2 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -1,7 +1,8 @@ #pragma once -#include #include -uint64_t GetMemInfo(); +#include "util/numtypes.h" + +u64 GetMemInfo(); std::string GetNowPlaying();