lots of new stuff waow

This commit is contained in:
Mars 2025-05-11 00:38:34 -04:00
parent bb4ccb5d42
commit 867bab1050
38 changed files with 704 additions and 452 deletions

View file

@ -73,9 +73,9 @@
#include <vector>
#endif
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
#ifndef ARGPARSE_CUSTOM_STRTOF
#define ARGPARSE_CUSTOM_STRTOF strtof

View file

@ -17,7 +17,7 @@
#include <utility>
#include <variant>
#include "src/util/defs.hpp"
#include "Util/Definitions.hpp"
// NOLINTBEGIN(readability-identifier-*, cppcoreguidelines-special-member-functions)
namespace matchit {

View file

@ -4,8 +4,8 @@
project(
'draconis++',
'cpp',
version: '0.1.0',
default_options: [
version : '0.1.0',
default_options : [
'default_library=static',
'buildtype=debugoptimized',
'b_vscrt=mt',
@ -35,13 +35,13 @@ common_warning_flags = [
]
common_cpp_flags = {
'common': [
'common' : [
'-fno-strict-enums',
'-fvisibility=hidden',
'-fvisibility-inlines-hidden',
'-std=c++26',
],
'msvc': [
'msvc' : [
'-DNOMINMAX', '/MT',
'/Zc:__cplusplus',
'/Zc:preprocessor',
@ -49,20 +49,20 @@ common_cpp_flags = {
'/external:anglebrackets',
'/std:c++latest',
],
'unix_extra': '-march=native',
'windows_extra': '-DCURL_STATICLIB',
'unix_extra' : '-march=native',
'windows_extra' : '-DCURL_STATICLIB',
}
# Configure Objective-C++ for macOS
if host_system == 'darwin'
add_languages('objcpp', native: false)
add_languages('objcpp', native : false)
objcpp = meson.get_compiler('objcpp')
objcpp_flags = common_warning_flags + [
'-std=c++26',
'-fvisibility=hidden',
'-fvisibility-inlines-hidden',
]
add_project_arguments(objcpp.get_supported_arguments(objcpp_flags), language: 'objcpp')
add_project_arguments(objcpp.get_supported_arguments(objcpp_flags), language : 'objcpp')
endif
# Apply C++ compiler arguments
@ -80,29 +80,36 @@ else
endif
endif
add_project_arguments(common_cpp_args, language: 'cpp')
add_project_arguments(common_cpp_args, language : 'cpp')
# --------------------- #
# Include Directories #
# --------------------- #
project_internal_includes = include_directories('src')
project_public_includes = include_directories('include', is_system : true)
# ------- #
# Files #
# ------- #
base_sources = files(
'src/config/config.cpp',
'src/config/weather.cpp',
'src/core/package.cpp',
'src/core/system_data.cpp',
'src/Config/Config.cpp',
'src/Core/SystemData.cpp',
'src/Services/Weather/OpenMeteoService.cpp',
'src/Services/Weather/OpenWeatherMapService.cpp',
'src/Services/PackageCounting.cpp',
'src/UI/UI.cpp',
'src/main.cpp',
'src/ui/ui.cpp',
)
platform_sources = {
'darwin': ['src/os/macos.cpp', 'src/os/macos/bridge.mm'],
'dragonfly': ['src/os/bsd.cpp'],
'freebsd': ['src/os/bsd.cpp'],
'haiku': ['src/os/haiku.cpp'],
'linux': ['src/os/linux.cpp'],
'netbsd': ['src/os/bsd.cpp'],
'serenity': ['src/os/serenity.cpp'],
'windows': ['src/os/windows.cpp'],
'darwin' : ['src/OS/macOS.cpp', 'src/OS/macOS/bridge.mm'],
'dragonfly' : ['src/OS/bsd.cpp'],
'freebsd' : ['src/OS/bsd.cpp'],
'haiku' : ['src/OS/haiku.cpp'],
'linux' : ['src/OS/linux.cpp'],
'netbsd' : ['src/OS/bsd.cpp'],
'serenity' : ['src/OS/serenity.cpp'],
'windows' : ['src/OS/Windows.cpp'],
}
sources = base_sources + files(platform_sources.get(host_system, []))
@ -111,9 +118,9 @@ sources = base_sources + files(platform_sources.get(host_system, []))
# Dependencies Config #
# --------------------- #
common_deps = [
dependency('libcurl', include_type: 'system', static: true),
dependency('tomlplusplus', include_type: 'system', static: true),
dependency('openssl', include_type: 'system', static: true, required: false),
dependency('libcurl', include_type : 'system', static : true),
dependency('tomlplusplus', include_type : 'system', static : true),
dependency('openssl', include_type : 'system', static : true, required : false),
]
# Platform-specific dependencies
@ -124,8 +131,8 @@ if host_system == 'darwin'
dependency('SQLiteCpp'),
dependency(
'appleframeworks',
modules: ['foundation', 'mediaplayer', 'systemconfiguration'],
static: true,
modules : ['foundation', 'mediaplayer', 'systemconfiguration'],
static : true,
),
dependency('iconv'),
]
@ -136,11 +143,11 @@ elif host_system == 'windows'
]
elif host_system != 'serenity' and host_system != 'haiku'
# Make dbus, x11, and wayland dependencies optional
dbus_dep = dependency('dbus-1', required: false)
xcb_dep = dependency('xcb', required: false)
xau_dep = dependency('xau', required: false)
xdmcp_dep = dependency('xdmcp', required: false)
wayland_dep = dependency('wayland-client', required: false)
dbus_dep = dependency('dbus-1', required : false)
xcb_dep = dependency('xcb', required : false)
xau_dep = dependency('xau', required : false)
xdmcp_dep = dependency('xdmcp', required : false)
wayland_dep = dependency('wayland-client', required : false)
platform_deps += [
dependency('SQLiteCpp'),
@ -149,15 +156,15 @@ elif host_system != 'serenity' and host_system != 'haiku'
if dbus_dep.found()
platform_deps += dbus_dep
add_project_arguments('-DHAVE_DBUS', language: 'cpp')
add_project_arguments('-DHAVE_DBUS', language : 'cpp')
endif
if xcb_dep.found() and xau_dep.found() and xdmcp_dep.found()
platform_deps += [xcb_dep, xau_dep, xdmcp_dep]
add_project_arguments('-DHAVE_XCB', language: 'cpp')
add_project_arguments('-DHAVE_XCB', language : 'cpp')
endif
if wayland_dep.found()
platform_deps += wayland_dep
add_project_arguments('-DHAVE_WAYLAND', language: 'cpp')
add_project_arguments('-DHAVE_WAYLAND', language : 'cpp')
endif
endif
@ -165,28 +172,28 @@ endif
ftxui_components = ['ftxui::screen', 'ftxui::dom', 'ftxui::component']
ftxui_dep = dependency(
'ftxui',
modules: ftxui_components,
include_type: 'system',
static: true,
required: false,
modules : ftxui_components,
include_type : 'system',
static : true,
required : false,
)
if not ftxui_dep.found()
ftxui_dep = declare_dependency(
dependencies: [
dependency('ftxui-dom', fallback: ['ftxui', 'dom_dep']),
dependency('ftxui-screen', fallback: ['ftxui', 'screen_dep']),
dependency('ftxui-component', fallback: ['ftxui', 'component_dep']),
dependencies : [
dependency('ftxui-dom', fallback : ['ftxui', 'dom_dep']),
dependency('ftxui-screen', fallback : ['ftxui', 'screen_dep']),
dependency('ftxui-component', fallback : ['ftxui', 'component_dep']),
],
)
endif
glaze_dep = dependency('glaze', include_type: 'system', required: false)
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')
glaze_dep = glaze_proj.dependency('glaze_glaze', include_type : 'system')
endif
# Combine all dependencies
@ -212,8 +219,9 @@ endif
executable(
'draconis++',
sources,
objc_args: objc_args,
link_args: link_args,
dependencies: deps,
install: true,
include_directories : [project_internal_includes, project_public_includes],
objc_args : objc_args,
link_args : link_args,
dependencies : deps,
install : true,
)

View file

@ -1,4 +1,4 @@
#include "config.hpp"
#include "Config.hpp"
#include <filesystem> // std::filesystem::{path, operator/, exists, create_directories}
#include <format> // std::{format, format_error}
@ -8,15 +8,10 @@
#include <toml++/impl/parser.hpp> // toml::{parse_file, parse_result}
#include <toml++/impl/table.hpp> // toml::table
#ifndef _WIN32
#include <pwd.h> // passwd, getpwuid
#include <unistd.h> // getuid
#endif
#include "src/util/defs.hpp"
#include "src/util/helpers.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "Util/Definitions.hpp"
#include "Util/Env.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
namespace fs = std::filesystem;

View file

@ -1,5 +1,6 @@
#pragma once
#include <memory> // std::{make_unique, unique_ptr}
#include <toml++/impl/node.hpp> // toml::node
#include <toml++/impl/node_view.hpp> // toml::node_view
#include <toml++/impl/table.hpp> // toml::table
@ -11,15 +12,16 @@
#include <pwd.h> // getpwuid, passwd
#include <unistd.h> // getuid
#include "src/util/helpers.hpp"
#include "Util/Env.hpp"
#endif
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "weather.hpp"
#include "../Services/Weather/OpenMeteoService.hpp"
#include "../Services/Weather/OpenWeatherMapService.hpp"
#include "Services/Weather.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
using util::error::DracError;
using util::types::CStr, util::types::String, util::types::Array, util::types::Option, util::types::Result;
@ -103,8 +105,9 @@ struct Weather {
String apiKey; ///< API key for the weather service.
String units; ///< Units for temperature, either "metric" or "imperial".
bool enabled = false; ///< Flag to enable or disable the Weather feature.
bool showTownName = false; ///< Flag to show the town name in the output.
bool enabled = false; ///< Flag to enable or disable the Weather feature.
bool showTownName = false; ///< Flag to show the town name in the output.
std::unique_ptr<weather::IWeatherService> service = nullptr; ///< Pointer to the weather service.
/**
* @brief Parses a TOML table to create a Weather instance.
@ -112,11 +115,9 @@ struct Weather {
* @return A Weather instance with the parsed values, or defaults otherwise.
*/
static fn fromToml(const toml::table& tbl) -> Weather {
Weather weather;
Weather weather;
const Option<String> apiKey = tbl["api_key"].value<String>();
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
weather.enabled = tbl["enabled"].value_or<bool>(false) && apiKey;
if (!weather.enabled)
return weather;
@ -125,6 +126,9 @@ struct Weather {
weather.showTownName = tbl["show_town_name"].value_or(false);
weather.units = tbl["units"].value_or("metric");
// Read provider (default to "openweathermap" if not set)
String provider = tbl["provider"].value_or("openweathermap");
if (const toml::node_view<const toml::node> location = tbl["location"]) {
if (location.is_string())
weather.location = *location.value<String>();
@ -137,20 +141,27 @@ struct Weather {
error_log("Invalid location format in config.");
weather.enabled = false;
}
} else {
error_log("No location provided in config.");
weather.enabled = false;
}
if (weather.enabled) {
if (provider == "openmeteo") {
if (std::holds_alternative<weather::Coords>(weather.location)) {
const auto& coords = std::get<weather::Coords>(weather.location);
weather.service = std::make_unique<weather::OpenMeteoService>(coords.lat, coords.lon, weather.units);
} else {
error_log("OpenMeteo requires coordinates for location.");
weather.enabled = false;
}
} else {
weather.service = std::make_unique<weather::OpenWeatherMapService>(weather.location, weather.apiKey, weather.units);
}
}
return weather;
}
/**
* @brief Retrieves the weather information based on the configuration.
* @return The weather information as a WeatherOutput object.
*
* This function fetches the weather data based on the configured location,
* API key, and units. It returns a WeatherOutput object containing the
* retrieved weather data.
*/
[[nodiscard]] fn getWeatherInfo() const -> Result<weather::Output>;
};
/**

View file

@ -1,19 +1,18 @@
#include "system_data.hpp"
#include "SystemData.hpp"
#include <chrono> // std::chrono::system_clock
#include <ctime> // localtime_r/s, strftime, time_t, tm
#include <format> // std::format
#include <future> // std::{async, launch}
#include <chrono> // std::chrono::system_clock
#include <ctime> // localtime_r/s, strftime, time_t, tm
#include <format> // std::format
#include <future> // std::{async, launch}
#include <matchit.hpp> // matchit::{match, is, in, _}
#include "src/config/config.hpp"
#include "src/config/weather.hpp"
#include "src/core/package.hpp"
#include "src/os/os.hpp"
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "include/matchit.hpp"
#include "Config/Config.hpp"
#include "OS/OperatingSystem.hpp"
#include "Services/PackageCounting.hpp"
#include "Services/Weather.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
using util::error::DracError, util::error::DracErrorCode;
@ -73,18 +72,20 @@ namespace os {
using package::GetTotalCount;
using util::types::Future, util::types::Err;
Future<Result<String>> hostFut = std::async(async, GetHost);
Future<Result<String>> kernelFut = std::async(async, GetKernelVersion);
Future<Result<String>> osFut = std::async(async, GetOSVersion);
Future<Result<u64>> memFut = std::async(async, GetMemInfo);
Future<Result<String>> deFut = std::async(async, GetDesktopEnvironment);
Future<Result<String>> wmFut = std::async(async, GetWindowManager);
Future<Result<DiskSpace>> diskFut = std::async(async, GetDiskUsage);
Future<Result<String>> shellFut = std::async(async, GetShell);
Future<Result<u64>> pkgFut = std::async(async, GetTotalCount);
Future<Result<MediaInfo>> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying);
Future<Result<weather::Output>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config] {
return config.weather.getWeatherInfo();
Future<Result<String>> hostFut = std::async(async, GetHost);
Future<Result<String>> kernelFut = std::async(async, GetKernelVersion);
Future<Result<String>> osFut = std::async(async, GetOSVersion);
Future<Result<u64>> memFut = std::async(async, GetMemInfo);
Future<Result<String>> deFut = std::async(async, GetDesktopEnvironment);
Future<Result<String>> wmFut = std::async(async, GetWindowManager);
Future<Result<DiskSpace>> diskFut = std::async(async, GetDiskUsage);
Future<Result<String>> shellFut = std::async(async, GetShell);
Future<Result<u64>> pkgFut = std::async(async, GetTotalCount);
Future<Result<MediaInfo>> npFut = std::async(config.nowPlaying.enabled ? async : deferred, GetNowPlaying);
Future<Result<weather::WeatherReport>> wthrFut = std::async(config.weather.enabled ? async : deferred, [&config]() -> Result<weather::WeatherReport> {
return config.weather.enabled && config.weather.service
? config.weather.service->getWeatherInfo()
: Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Weather API disabled"));
});
{
@ -100,8 +101,8 @@ namespace os {
this->diskUsage = diskFut.get();
this->shell = shellFut.get();
this->packageCount = pkgFut.get();
this->weather = config.weather.enabled ? wthrFut.get() : Err(DracError(ApiUnavailable, "Weather API disabled"));
this->nowPlaying = config.nowPlaying.enabled ? npFut.get() : Err(DracError(ApiUnavailable, "Now Playing API disabled"));
this->weather = config.weather.enabled ? wthrFut.get() : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Weather API disabled"));
this->nowPlaying = config.nowPlaying.enabled ? npFut.get() : Err(DracError(util::error::DracErrorCode::ApiUnavailable, "Now Playing API disabled"));
}
}
} // namespace os

View file

@ -2,10 +2,10 @@
#include <format> // std::{formatter, format_to}
#include "src/config/config.hpp"
#include "src/config/weather.hpp"
#include "src/util/defs.hpp"
#include "src/util/types.hpp"
#include "Services/Weather.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
struct Config;
@ -26,7 +26,8 @@ struct BytesToGiB {
* @brief Constructor for BytesToGiB.
* @param value The byte value to be converted.
*/
explicit constexpr BytesToGiB(u64 value) : value(value) {}
explicit constexpr BytesToGiB(u64 value)
: value(value) {}
};
/// @brief Conversion factor from bytes to GiB
@ -72,18 +73,18 @@ namespace os {
* in order to display it at all at once during runtime.
*/
struct SystemData {
Result<String> date; ///< Current date (e.g., "April 26th").
Result<String> host; ///< Host/product family (e.g., "MacBook Air").
Result<String> kernelVersion; ///< OS kernel version (e.g., "6.14.4").
Result<String> osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS").
Result<u64> memInfo; ///< Total physical RAM in bytes.
Result<String> desktopEnv; ///< Desktop environment (e.g., "KDE").
Result<String> windowMgr; ///< Window manager (e.g., "KWin").
Result<DiskSpace> diskUsage; ///< Used/Total disk space for root filesystem.
Result<String> shell; ///< Name of the current user shell (e.g., "zsh").
Result<u64> packageCount; ///< Total number of packages installed.
Result<MediaInfo> nowPlaying; ///< Result of fetching media info.
Result<weather::Output> weather; ///< Result of fetching weather info.
Result<String> date; ///< Current date (e.g., "April 26th").
Result<String> host; ///< Host/product family (e.g., "MacBook Air").
Result<String> kernelVersion; ///< OS kernel version (e.g., "6.14.4").
Result<String> osVersion; ///< OS pretty name (e.g., "Ubuntu 24.04.2 LTS").
Result<u64> memInfo; ///< Total physical RAM in bytes.
Result<String> desktopEnv; ///< Desktop environment (e.g., "KDE").
Result<String> windowMgr; ///< Window manager (e.g., "KWin").
Result<DiskSpace> diskUsage; ///< Used/Total disk space for root filesystem.
Result<String> shell; ///< Name of the current user shell (e.g., "zsh").
Result<u64> packageCount; ///< Total number of packages installed.
Result<MediaInfo> nowPlaying; ///< Result of fetching media info.
Result<weather::WeatherReport> weather; ///< Result of fetching weather info.
/**
* @brief Constructs a SystemData object and initializes its members.

View file

@ -1,8 +1,8 @@
#pragma once
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
/**
* @namespace os

View file

@ -11,18 +11,15 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.Media.Control.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.System.Profile.h>
#include <winrt/base.h>
#include <winrt/impl/Windows.Media.Control.2.h>
#include "src/core/package.hpp"
#include "src/util/error.hpp"
#include "src/util/helpers.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "Services/PackageCounting.hpp"
#include "Util/Env.hpp"
#include "Util/Error.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
#include "os.hpp"
#include "OperatingSystem.hpp"
// clang-format on
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
@ -65,8 +62,7 @@ namespace {
String value((type == REG_SZ || type == REG_EXPAND_SZ) ? dataSize - 1 : dataSize, '\0');
// NOLINTNEXTLINE(*-pro-type-reinterpret-cast) - required here
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(value.data()), &dataSize) !=
ERROR_SUCCESS) {
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, reinterpret_cast<LPBYTE>(value.data()), &dataSize) != ERROR_SUCCESS) {
RegCloseKey(key);
return "";
}
@ -175,10 +171,7 @@ namespace os {
if (GlobalMemoryStatusEx(&memInfo))
return memInfo.ullTotalPhys;
DWORD lastError = GetLastError();
return Err(DracError(
DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", lastError)
));
return Err(DracError(DracErrorCode::PlatformSpecific, std::format("GlobalMemoryStatusEx failed with error code {}", GetLastError())));
}
fn GetNowPlaying() -> Result<MediaInfo> {
@ -216,18 +209,15 @@ namespace os {
if (const Option<u64> buildNumberOpt = GetBuildNumber()) {
if (const u64 buildNumber = *buildNumberOpt; buildNumber >= 22000) {
if (const size_t pos = productName.find("Windows 10"); pos != String::npos) {
const bool startBoundary = (pos == 0 || !isalnum(static_cast<unsigned char>(productName[pos - 1])));
const bool endBoundary =
(pos + 10 == productName.length() || !isalnum(static_cast<unsigned char>(productName[pos + 10])));
const bool startBoundary = (pos == 0 || !isalnum(static_cast<u8>(productName[pos - 1])));
const bool endBoundary = (pos + 10 == productName.length() || !isalnum(static_cast<u8>(productName[pos + 10])));
if (startBoundary && endBoundary) {
if (startBoundary && endBoundary)
productName.replace(pos, 10, "Windows 11");
}
}
}
} else {
} else
debug_log("Warning: Could not get build number via WinRT; Win11 detection might be inaccurate.");
}
return displayVersion.empty() ? productName : productName + " " + displayVersion;
} catch (const std::exception& e) { return Err(DracError(e)); }
@ -245,9 +235,7 @@ namespace os {
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
if (rtlGetVersion(&osInfo) == 0)
return std::format(
"{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId
);
return std::format("{}.{}.{}.{}", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwPlatformId);
}
}

View file

@ -1,4 +1,4 @@
#include "package.hpp"
#include "PackageCounting.hpp"
#if !defined(__serenity__) && !defined(_WIN32)
#include <SQLiteCpp/Database.h> // SQLite::{Database, OPEN_READONLY}
@ -14,15 +14,14 @@
#include <filesystem> // std::filesystem
#include <format> // std::format
#include <future> // std::{async, future, launch}
#include <matchit.hpp> // matchit::{match, is, or_, _}
#include <system_error> // std::{errc, error_code}
#include "src/util/cache.hpp"
#include "src/util/error.hpp"
#include "src/util/helpers.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "include/matchit.hpp"
#include "Util/Caching.hpp"
#include "Util/Env.hpp"
#include "Util/Error.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
namespace {
namespace fs = std::filesystem;
@ -454,12 +453,11 @@ namespace package {
totalCount += *result;
oneSucceeded = true;
debug_log("Added {} packages. Current total: {}", *result, totalCount);
} else {
} else
match(result.error().code)(
is | or_(NotFound, ApiUnavailable, NotSupported) = [&] -> void { debug_at(result.error()); },
is | _ = [&] -> void { error_at(result.error()); }
);
}
} catch (const Exception& exc) {
error_log("Caught exception while getting package count future: {}", exc.what());
} catch (...) { error_log("Caught unknown exception while getting package count future."); }

View file

@ -4,9 +4,9 @@
#include <glaze/core/common.hpp> // glz::object
#include <glaze/core/meta.hpp> // glz::detail::Object
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
namespace package {
namespace fs = std::filesystem;
@ -22,7 +22,8 @@ namespace package {
i64 timestampEpochSeconds {};
PkgCountCacheData() = default;
PkgCountCacheData(u64 count, i64 timestampEpochSeconds) : count(count), timestampEpochSeconds(timestampEpochSeconds) {}
PkgCountCacheData(u64 count, i64 timestampEpochSeconds)
: count(count), timestampEpochSeconds(timestampEpochSeconds) {}
// NOLINTBEGIN(readability-identifier-naming)
struct [[maybe_unused]] glaze {
@ -132,8 +133,7 @@ namespace package {
fn GetSerenityCount() -> Result<u64>;
#endif
// Common (potentially cross-platform)
#ifndef _WIN32
#if defined(__linux__) || defined(__APPLE__)
fn CountNix() -> Result<u64>;
#endif
fn CountCargo() -> Result<u64>;

49
src/Services/Weather.hpp Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <glaze/core/common.hpp> // object
#include <glaze/core/meta.hpp> // Object
#include "Util/Types.hpp"
namespace weather {
using glz::detail::Object, glz::object;
using util::types::String, util::types::Vec, util::types::f64, util::types::usize;
// NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze`
/**
* @struct WeatherReport
* @brief Represents a weather report.
*
* Contains temperature, conditions, and timestamp.
*/
struct WeatherReport {
f64 temperature; ///< Degrees (C/F)
util::types::Option<String> name; ///< Optional town/city name (may be missing for some providers)
String description; ///< Weather description (e.g., "clear sky", "rain")
usize timestamp; ///< Seconds since epoch
/**
* @brief Glaze serialization and deserialization for WeatherReport.
*/
struct [[maybe_unused]] glaze {
using T = WeatherReport;
static constexpr Object value = object(
"temperature",
&T::temperature,
"name",
&T::name,
"description",
&T::description,
"timestamp",
&T::timestamp
);
};
};
struct Coords {
f64 lat;
f64 lon;
};
// NOLINTEND(readability-identifier-naming)
} // namespace weather

View file

@ -0,0 +1,24 @@
#pragma once
#include "Services/Weather.hpp"
#include "Util/Error.hpp"
namespace weather {
using util::types::Result;
class IWeatherService {
public:
IWeatherService(const IWeatherService&) = delete;
IWeatherService(IWeatherService&&) = delete;
fn operator=(const IWeatherService&)->IWeatherService& = delete;
fn operator=(IWeatherService&&)->IWeatherService& = delete;
virtual ~IWeatherService() = default;
[[nodiscard]] virtual fn getWeatherInfo() const -> Result<WeatherReport> = 0;
protected:
IWeatherService() = default;
};
} // namespace weather

View file

@ -0,0 +1,156 @@
#define NOMINMAX
#include "OpenMeteoService.hpp"
#include <chrono>
#include <curl/curl.h>
#include <curl/easy.h>
#include <format>
#include <glaze/json/read.hpp>
#include <sstream>
#include <string>
#include "Util/Caching.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
using weather::OpenMeteoService;
using weather::WeatherReport;
namespace weather {
using util::types::f64, util::types::i32, util::types::String;
struct OpenMeteoResponse {
struct CurrentWeather {
f64 temperature;
i32 weathercode;
String time;
} currentWeather;
};
struct OpenMeteoGlaze {
using T = OpenMeteoResponse;
// clang-format off
static constexpr auto value = glz::object(
"current_weather", &T::currentWeather
);
// clang-format on
};
struct CurrentWeatherGlaze {
using T = OpenMeteoResponse::CurrentWeather;
// clang-format off
static constexpr auto value = glz::object(
"temperature", &T::temperature,
"weathercode", &T::weathercode,
"time", &T::time
);
// clang-format on
};
} // namespace weather
template <>
struct glz::meta<weather::OpenMeteoResponse> : weather::OpenMeteoGlaze {};
template <>
struct glz::meta<weather::OpenMeteoResponse::CurrentWeather> : weather::CurrentWeatherGlaze {};
namespace {
using glz::opts;
using util::error::DracError, util::error::DracErrorCode;
using util::types::usize, util::types::Err;
constexpr opts glazeOpts = { .error_on_unknown_keys = false };
fn WriteCallback(void* contents, size_t size, size_t nmemb, std::string* str) -> size_t {
size_t totalSize = size * nmemb;
str->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
fn parse_iso8601_to_epoch(const std::string& iso8601) -> usize {
std::tm time = {};
std::istringstream stream(iso8601);
stream >> std::get_time(&time, "%Y-%m-%dT%H:%M");
if (stream.fail())
return 0;
#ifdef _WIN32
return static_cast<util::types::usize>(_mkgmtime(&time));
#else
return static_cast<util::types::usize>(timegm(&tm));
#endif
}
} // namespace
OpenMeteoService::OpenMeteoService(double lat, double lon, std::string units)
: m_lat(lat), m_lon(lon), m_units(std::move(units)) {}
fn OpenMeteoService::getWeatherInfo() const -> util::types::Result<WeatherReport> {
using glz::error_ctx, glz::error_code, glz::read, glz::format_error;
using util::cache::ReadCache, util::cache::WriteCache;
using util::types::Array, util::types::String, util::types::Result, util::types::None;
if (Result<WeatherReport> data = ReadCache<WeatherReport>("weather")) {
using std::chrono::system_clock, std::chrono::minutes, std::chrono::seconds;
const WeatherReport& dataVal = *data;
if (const auto cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.timestamp)); cacheAge < minutes(60))
return dataVal;
}
String url = std::format(
"https://api.open-meteo.com/v1/forecast?latitude={:.4f}&longitude={:.4f}&current_weather=true&temperature_unit={}",
m_lat,
m_lon,
m_units == "imperial" ? "fahrenheit" : "celsius"
);
CURL* curl = curl_easy_init();
if (!curl)
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL"));
String responseBuffer;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res))));
OpenMeteoResponse apiResp {};
if (error_ctx errc = read<glazeOpts>(apiResp, responseBuffer); errc)
return Err(DracError(DracErrorCode::ParseError, "Failed to parse Open-Meteo JSON response"));
static constexpr Array<const char*, 9> CODE_DESC = {
"clear sky",
"mainly clear",
"partly cloudy",
"overcast",
"fog",
"drizzle",
"rain",
"snow",
"thunderstorm"
};
WeatherReport out = {
.temperature = apiResp.currentWeather.temperature,
.name = None,
.description = CODE_DESC.at(apiResp.currentWeather.weathercode),
.timestamp = parse_iso8601_to_epoch(apiResp.currentWeather.time),
};
if (Result<> writeResult = WriteCache("weather", out); !writeResult)
return Err(writeResult.error());
return out;
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "IWeatherService.hpp"
namespace weather {
class OpenMeteoService : public IWeatherService {
public:
OpenMeteoService(f64 lat, f64 lon, String units = "metric");
[[nodiscard]] fn getWeatherInfo() const -> Result<WeatherReport> override;
private:
double m_lat;
double m_lon;
String m_units;
};
} // namespace weather

View file

@ -0,0 +1,184 @@
#define NOMINMAX
#include "OpenWeatherMapService.hpp"
#include <chrono>
#include <curl/curl.h>
#include <curl/easy.h>
#include <format>
#include <glaze/core/meta.hpp>
#include <glaze/json/read.hpp>
#include <matchit.hpp>
#include <variant>
#include "Util/Caching.hpp"
#include "Util/Error.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
using weather::OpenWeatherMapService;
using weather::WeatherReport;
namespace weather {
using util::types::f64, util::types::i64, util::types::String, util::types::Vec;
struct OWMResponse {
struct Main {
f64 temp;
} main;
struct Weather {
String description;
};
Vec<Weather> weather;
String name;
i64 dt;
};
struct OWMMainGlaze {
using T = OWMResponse::Main;
static constexpr auto value = glz::object("temp", &T::temp);
};
struct OWMWeatherGlaze {
using T = OWMResponse::Weather;
static constexpr auto value = glz::object("description", &T::description);
};
struct OWMResponseGlaze {
using T = OWMResponse;
// clang-format off
static constexpr auto value = glz::object(
"main", &T::main,
"weather", &T::weather,
"name", &T::name,
"dt", &T::dt
);
// clang-format on
};
} // namespace weather
template <>
struct glz::meta<weather::OWMResponse::Main> : weather::OWMMainGlaze {};
template <>
struct glz::meta<weather::OWMResponse::Weather> : weather::OWMWeatherGlaze {};
template <>
struct glz::meta<weather::OWMResponse> : weather::OWMResponseGlaze {};
namespace {
using glz::opts, glz::error_ctx, glz::error_code, glz::read, glz::format_error;
using util::error::DracError, util::error::DracErrorCode;
using util::types::usize, util::types::Err, util::types::Exception, util::types::Result;
using namespace util::cache;
constexpr opts glaze_opts = { .error_on_unknown_keys = false };
fn WriteCallback(void* contents, const usize size, const usize nmemb, weather::String* str) -> usize {
const usize totalSize = size * nmemb;
str->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
fn MakeApiRequest(const weather::String& url) -> Result<WeatherReport> {
debug_log("Making API request to URL: {}", url);
CURL* curl = curl_easy_init();
weather::String responseBuffer;
if (!curl)
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL"));
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
const CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res))));
weather::OWMResponse owm;
if (const error_ctx errc = read<glaze_opts>(owm, responseBuffer); errc.ec != error_code::none)
return Err(DracError(DracErrorCode::ParseError, std::format("Failed to parse JSON response: {}", format_error(errc, responseBuffer))));
WeatherReport report = {
.temperature = owm.main.temp,
.name = owm.name.empty() ? std::nullopt : util::types::Option<std::string>(owm.name),
.description = !owm.weather.empty() ? owm.weather[0].description : "",
.timestamp = static_cast<usize>(owm.dt),
};
return report;
}
} // namespace
OpenWeatherMapService::OpenWeatherMapService(std::variant<String, Coords> location, String apiKey, String units)
: m_location(std::move(location)), m_apiKey(std::move(apiKey)), m_units(std::move(units)) {}
fn OpenWeatherMapService::getWeatherInfo() const -> Result<WeatherReport> {
using namespace std::chrono;
if (Result<WeatherReport> data = ReadCache<WeatherReport>("weather")) {
const WeatherReport& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.timestamp)); cacheAge < 60min)
return dataVal;
debug_log("Cache expired");
} else {
using matchit::match, matchit::is, matchit::_;
using enum DracErrorCode;
DracError err = data.error();
match(err.code)(
is | NotFound = [&] { debug_at(err); },
is | _ = [&] { error_at(err); }
);
}
fn handleApiResult = [](const Result<WeatherReport>& result) -> Result<WeatherReport> {
if (!result)
return Err(result.error());
if (Result<> writeResult = WriteCache("weather", *result); !writeResult)
return Err(writeResult.error());
return *result;
};
if (std::holds_alternative<String>(m_location)) {
using util::types::i32;
const auto& city = std::get<String>(m_location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(city.length()));
const String apiUrl =
std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, m_apiKey, m_units);
curl_free(escaped);
return handleApiResult(MakeApiRequest(apiUrl));
}
if (std::holds_alternative<Coords>(m_location)) {
const auto& [lat, lon] = std::get<Coords>(m_location);
const String apiUrl = std::format(
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, m_apiKey, m_units
);
return handleApiResult(MakeApiRequest(apiUrl));
}
return util::types::Err(util::error::DracError(util::error::DracErrorCode::ParseError, "Invalid location type in configuration."));
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <variant>
#include "IWeatherService.hpp"
namespace weather {
class OpenWeatherMapService : public IWeatherService {
public:
OpenWeatherMapService(std::variant<String, Coords> location, String apiKey, String units);
fn getWeatherInfo() const -> Result<WeatherReport> override;
private:
std::variant<String, Coords> m_location;
String m_apiKey;
String m_units;
};
} // namespace weather

View file

@ -1,7 +1,6 @@
#include "ui.hpp"
#include "UI.hpp"
#include "src/os/os.hpp"
#include "src/util/types.hpp"
#include "Util/Types.hpp"
namespace ui {
using namespace ftxui;
@ -175,11 +174,11 @@ namespace ui {
initialRows.push_back({ .icon = calendarIcon, .label = "Date", .value = *data.date });
if (weather.enabled && data.weather) {
const weather::Output& weatherInfo = *data.weather;
const weather::WeatherReport& weatherInfo = *data.weather;
String weatherValue = weather.showTownName
? std::format("{}°F in {}", std::lround(weatherInfo.main.temp), weatherInfo.name)
: std::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description);
String weatherValue = weather.showTownName && weatherInfo.name
? std::format("{}°F in {}", std::lround(weatherInfo.temperature), *weatherInfo.name)
: std::format("{}°F, {}", std::lround(weatherInfo.temperature), weatherInfo.description);
initialRows.push_back({ .icon = weatherIcon, .label = "Weather", .value = std::move(weatherValue) });
} else if (weather.enabled && !data.weather.has_value())

View file

@ -3,9 +3,9 @@
#include <ftxui/dom/elements.hpp> // ftxui::Element
#include <ftxui/screen/color.hpp> // ftxui::Color
#include "src/config/config.hpp"
#include "src/core/system_data.hpp"
#include "src/util/types.hpp"
#include "Config/Config.hpp"
#include "Core/SystemData.hpp"
#include "Util/Types.hpp"
namespace ui {
struct Theme {

View file

@ -9,10 +9,10 @@
#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"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
namespace util::cache {
namespace fs = std::filesystem;
@ -40,8 +40,7 @@ namespace util::cache {
if (!fs::exists(cacheDir, errc)) {
if (errc)
return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message())
);
return Err(DracError(DracErrorCode::IoError, "Failed to check existence of cache directory: " + errc.message()));
fs::create_directories(cacheDir, errc);
@ -162,8 +161,7 @@ namespace util::cache {
if (!ofs) {
std::error_code removeEc;
fs::remove(tempPath, removeEc);
return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string())
);
return Err(DracError(DracErrorCode::IoError, "Failed to write to temporary cache file: " + tempPath.string()));
}
}

View file

@ -1,12 +1,15 @@
#pragma once
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#ifdef _WIN32
#include <stdlib.h> // NOLINT(*-deprecated-headers)
#endif
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
namespace util::helpers {
using error::DracError, error::DracErrorCode;
using types::Result, types::String, types::CStr, types::Err;
using types::Result, types::String, types::CStr;
/**
* @brief Safely retrieves an environment variable.
@ -15,6 +18,9 @@ namespace util::helpers {
* or an EnvError if an error occurred.
*/
[[nodiscard]] inline fn GetEnv(CStr name) -> Result<String> {
using error::DracError, error::DracErrorCode;
using types::Err;
#ifdef _WIN32
using types::i32, types::usize, types::UniquePointer;

View file

@ -1,7 +1,7 @@
#pragma once
#include <expected> // std::{unexpected, expected}
#include <format> // std::format
#include <matchit.hpp> // matchit::{match, is, or_, _}
#include <source_location> // std::source_location
#include <system_error> // std::error_code
@ -9,11 +9,11 @@
#include <guiddef.h> // GUID
#include <winerror.h> // error values
#include <winrt/base.h> // winrt::hresult_error
#else
#include <format> // std::format
#endif
#include "src/util/types.hpp"
#include "include/matchit.hpp"
#include "Util/Types.hpp"
namespace util {
namespace error {
@ -48,15 +48,15 @@ namespace util {
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).
DracErrorCode code; ///< The general category of the error.
// 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) {}
: message(std::move(msg)), location(loc), code(errc) {}
explicit DracError(const Exception& exc, const std::source_location& loc = std::source_location::current())
: message(exc.what()), code(DracErrorCode::InternalError), location(loc) {}
: message(exc.what()), location(loc), code(DracErrorCode::InternalError) {}
explicit DracError(const std::error_code& errc, const std::source_location& loc = std::source_location::current())
: message(errc.message()), location(loc) {

View file

@ -17,9 +17,9 @@
#include <source_location> // std::source_location
#endif
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/types.hpp"
#include "Util/Definitions.hpp"
#include "Util/Error.hpp"
#include "Util/Types.hpp"
namespace util::logging {
using types::usize, types::u8, types::i32, types::i64, types::CStr, types::String, types::StringView, types::Array,

View file

@ -1,124 +0,0 @@
#define NOMINMAX
#include "weather.hpp"
#include <chrono> // std::chrono::{duration, operator-}
#include <curl/curl.h> // curl_easy_setopt
#include <curl/easy.h> // curl_easy_init, curl_easy_perform, curl_easy_cleanup
#include <expected> // std::{expected (Result), unexpected (Err)}
#include <format> // std::format
#include <glaze/beve/read.hpp> // glz::read_beve
#include <glaze/beve/write.hpp> // glz::write_beve
#include <glaze/core/context.hpp> // glz::{error_ctx, error_code}
#include <glaze/core/opts.hpp> // glz::opts
#include <glaze/core/reflect.hpp> // glz::format_error
#include <glaze/json/read.hpp> // NOLINT(misc-include-cleaner) - glaze/json/read.hpp is needed for glz::read<glz::opts>
#include <variant> // std::{get, holds_alternative}
#include "src/util/cache.hpp"
#include "src/util/defs.hpp"
#include "src/util/error.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "config.hpp"
using weather::Output;
namespace {
using glz::opts, glz::error_ctx, glz::error_code, glz::read, glz::format_error;
using util::error::DracError, util::error::DracErrorCode;
using util::types::usize, util::types::Err, util::types::Exception;
using weather::Coords;
using namespace util::cache;
constexpr opts glaze_opts = { .error_on_unknown_keys = false };
fn WriteCallback(void* contents, const usize size, const usize nmemb, String* str) -> usize {
const usize totalSize = size * nmemb;
str->append(static_cast<char*>(contents), totalSize);
return totalSize;
}
fn MakeApiRequest(const String& url) -> Result<Output> {
debug_log("Making API request to URL: {}", url);
CURL* curl = curl_easy_init();
String responseBuffer;
if (!curl)
return Err(DracError(DracErrorCode::ApiUnavailable, "Failed to initialize cURL"));
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
const CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK)
return Err(DracError(DracErrorCode::ApiUnavailable, std::format("cURL error: {}", curl_easy_strerror(res))));
Output output;
if (const error_ctx errc = read<glaze_opts>(output, responseBuffer); errc.ec != error_code::none)
return Err(DracError(
DracErrorCode::ParseError, std::format("Failed to parse JSON response: {}", format_error(errc, responseBuffer))
));
return output;
}
} // namespace
fn Weather::getWeatherInfo() const -> Result<Output> {
using namespace std::chrono;
using util::types::i32;
if (Result<Output> data = ReadCache<Output>("weather")) {
const Output& dataVal = *data;
if (const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
cacheAge < 60min) // NOLINT(misc-include-cleaner) - inherited from <chrono>
return dataVal;
debug_log("Cache expired");
} else if (data.error().code == DracErrorCode::NotFound) {
debug_at(data.error());
} else
error_at(data.error());
fn handleApiResult = [](const Result<Output>& result) -> Result<Output> {
if (!result)
return Err(result.error());
if (Result writeResult = WriteCache("weather", *result); !writeResult)
return Err(writeResult.error());
return *result;
};
if (std::holds_alternative<String>(location)) {
const auto& city = std::get<String>(location);
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<i32>(city.length()));
const String apiUrl =
std::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, apiKey, units);
curl_free(escaped);
return handleApiResult(MakeApiRequest(apiUrl));
}
if (std::holds_alternative<Coords>(location)) {
const auto& [lat, lon] = std::get<Coords>(location);
const String apiUrl = std::format(
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, apiKey, units
);
return handleApiResult(MakeApiRequest(apiUrl));
}
return Err(DracError(DracErrorCode::ParseError, "Invalid location type in configuration."));
}

View file

@ -1,78 +0,0 @@
#pragma once
#include <glaze/core/common.hpp> // object
#include <glaze/core/meta.hpp> // Object
#include "src/util/types.hpp"
namespace weather {
using glz::detail::Object, glz::object;
using util::types::String, util::types::Vec, util::types::f64, util::types::usize;
// NOLINTBEGIN(readability-identifier-naming) - Needs to specifically use `glaze`
/**
* @struct Condition
* @brief Represents weather conditions.
*/
struct Condition {
String description; ///< Weather condition description (e.g., "clear sky", "light rain").
/**
* @brief Glaze serialization and deserialization for Condition.
*/
struct [[maybe_unused]] glaze {
using T = Condition;
static constexpr Object value = object("description", &T::description);
};
};
/**
* @struct Main
* @brief Represents the main weather data.
*/
struct Main {
f64 temp; ///< Temperature in degrees (C/F, depending on config).
/**
* @brief Glaze serialization and deserialization for Main.
*/
struct [[maybe_unused]] glaze {
using T = Main;
static constexpr Object value = object("temp", &T::temp);
};
};
/**
* @struct Coords
* @brief Represents geographical coordinates.
*/
struct Coords {
double lat; ///< Latitude coordinate.
double lon; ///< Longitude coordinate.
};
/**
* @struct Output
* @brief Represents the output of the weather API.
*
* Contains main weather data, location name, and weather conditions.
*/
struct Output {
Main main; ///< Main weather data (temperature, etc.).
String name; ///< Location name (e.g., city name).
Vec<Condition> weather; ///< List of weather conditions (e.g., clear, rain).
usize dt; ///< Timestamp of the weather data (in seconds since epoch).
/**
* @brief Glaze serialization and deserialization for WeatherOutput.
*/
struct [[maybe_unused]] glaze {
using T = Output;
static constexpr Object value = object("main", &T::main, "name", &T::name, "weather", &T::weather, "dt", &T::dt);
};
};
// NOLINTEND(readability-identifier-naming)
} // namespace weather

View file

@ -1,23 +1,21 @@
#include <argparse.hpp> // argparse::ArgumentParser
#include <cstdlib> // EXIT_FAILURE, EXIT_SUCCESS
#include <ftxui/dom/elements.hpp> // ftxui::{Element, hbox, vbox, text, separator, filler, etc.}
#include <ftxui/dom/node.hpp> // ftxui::{Render}
#include <ftxui/screen/screen.hpp> // ftxui::{Screen, Dimension::Full}
#include "src/ui/ui.hpp"
#ifdef __cpp_lib_print
#include <print> // std::print
#else
#include <iostream> // std::cout
#endif
#include "src/config/config.hpp"
#include "src/core/system_data.hpp"
#include "src/util/defs.hpp"
#include "src/util/logging.hpp"
#include "src/util/types.hpp"
#include "include/argparse.hpp"
#include "Config/Config.hpp"
#include "Core/SystemData.hpp"
#include "UI/UI.hpp"
#include "Util/Definitions.hpp"
#include "Util/Logging.hpp"
#include "Util/Types.hpp"
using util::types::i32, util::types::Exception;
@ -27,45 +25,49 @@ fn main(const i32 argc, char* argv[]) -> i32 {
winrt::init_apartment();
#endif
argparse::ArgumentParser parser("draconis", "0.1.0");
parser
.add_argument("--log-level")
.help("Set the log level")
.default_value("info")
.choices("debug", "info", "warn", "error");
parser
.add_argument("-V", "--verbose")
.help("Enable verbose logging. Overrides --log-level.")
.flag();
if (Result result = parser.parse_args(argc, argv); !result) {
error_at(result.error());
return EXIT_FAILURE;
}
bool verbose = parser.get<bool>("-V").value_or(false) || parser.get<bool>("--verbose").value_or(false);
Result logLevelStr = verbose ? "debug" : parser.get<String>("--log-level");
{
using matchit::match, matchit::is, matchit::_;
using util::logging::LogLevel;
using enum util::logging::LogLevel;
using argparse::ArgumentParser;
LogLevel minLevel = match(logLevelStr)(
is | "debug" = Debug,
is | "info" = Info,
is | "warn" = Warn,
is | "error" = Error,
ArgumentParser parser("draconis", "0.1.0");
parser
.add_argument("--log-level")
.help("Set the log level")
.default_value("info")
.choices("debug", "info", "warn", "error");
parser
.add_argument("-V", "--verbose")
.help("Enable verbose logging. Overrides --log-level.")
.flag();
if (Result result = parser.parse_args(argc, argv); !result) {
error_at(result.error());
return EXIT_FAILURE;
}
bool verbose = parser.get<bool>("-V").value_or(false) || parser.get<bool>("--verbose").value_or(false);
Result logLevelStr = verbose ? "debug" : parser.get<String>("--log-level");
{
using matchit::match, matchit::is, matchit::_;
using util::logging::LogLevel;
using enum util::logging::LogLevel;
LogLevel minLevel = match(logLevelStr)(
is | "debug" = Debug,
is | "info" = Info,
is | "warn" = Warn,
is | "error" = Error,
#ifndef NDEBUG
is | _ = Debug
is | _ = Debug
#else
is | _ = Info
is | _ = Info
#endif
);
);
SetRuntimeLogLevel(minLevel);
SetRuntimeLogLevel(minLevel);
}
}
const Config& config = Config::getInstance();