diff --git a/include/argparse.hpp b/include/argparse.hpp index 32ab8ae..5c7affa 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -63,6 +63,7 @@ #endif #include "src/util/defs.hpp" +#include "src/util/error.hpp" // Added for Result type #include "src/util/types.hpp" #ifndef ARGPARSE_CUSTOM_STRTOF @@ -231,7 +232,7 @@ namespace argparse { } template - fn do_from_chars(const StringView s) -> T { + fn do_from_chars(const StringView s) -> Result { T x { 0 }; auto [first, last] = pointer_range(s); auto [ptr, ec] = std::from_chars(first, last, x, Param); @@ -239,102 +240,94 @@ namespace argparse { if (ec == std::errc()) { if (ptr == last) return x; - throw std::invalid_argument { "pattern '" + String(s) + "' does not match to the end" }; + + return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("pattern '{}' does not match to the end", String(s)))); } if (ec == std::errc::invalid_argument) - throw std::invalid_argument { "pattern '" + String(s) + "' not found" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, std::format("pattern '{}' not found", String(s)))); if (ec == std::errc::result_out_of_range) - throw std::range_error { "'" + String(s) + "' not representable" }; + return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("'{}' not representable", String(s)))); - return x; // unreachable + // Should be unreachable, but handle potential unknown error codes + return Err(util::error::DracError(util::error::DracErrorCode::InternalError, std::format("Unknown parsing error for '{}'", String(s)))); } template struct parse_number { - static fn operator()(const StringView s)->T { + static fn operator()(const StringView s)->Result { return do_from_chars(s); } }; template struct parse_number { - static fn operator()(const StringView s)->T { + static fn operator()(const StringView s)->Result { if (auto [ok, rest] = consume_binary_prefix(s); ok) return do_from_chars(rest); - throw std::invalid_argument { "pattern not found" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "pattern not found")); } }; template struct parse_number { - static fn operator()(const StringView s)->T { + static fn operator()(const StringView s)->Result { + Result result; + if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { if (auto [ok, rest] = consume_hex_prefix(s); ok) - try { - return do_from_chars(rest); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } + result = do_from_chars(rest); + else + return Err(util::error::DracError(util::error::DracErrorCode::InternalError, std::format("Inconsistent hex prefix detection for '{}'", String(s)))); } else - // Allow passing hex numbers without prefix - // Shape 'x' already has to be specified - try { - return do_from_chars(s); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } + result = do_from_chars(s); - throw std::invalid_argument { "pattern '" + String(s) + - "' not identified as hexadecimal" }; + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message))); + + return result; } }; template struct parse_number { - static fn operator()(const StringView s)->T { - auto [ok, rest] = consume_hex_prefix(s); + static fn operator()(const StringView s)->Result { + if (auto [ok, rest] = consume_hex_prefix(s); ok) { + Result result = do_from_chars(rest); - if (ok) - try { - return do_from_chars(rest); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as hexadecimal: " + err.what()); - } + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message))); - if (auto [ok_binary, rest_binary] = consume_binary_prefix(s); ok_binary) - try { - return do_from_chars(rest_binary); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as binary: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as binary: " + err.what()); - } - - if (starts_with("0"sv, s)) - try { - return do_from_chars(rest); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as octal: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as octal: " + err.what()); - } - - try { - return do_from_chars(rest); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + String(s) + "' as decimal integer: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + String(s) + "' as decimal integer: " + err.what()); + return result; } + + if (auto [ok_binary, rest_binary] = consume_binary_prefix(s); ok_binary) { + Result result = do_from_chars(rest_binary); + + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as binary: {}", String(s), result.error().message))); + + return result; + } + + // Note: consume_hex_prefix already removed the prefix if present, so 'rest' is correct here for octal/decimal check. + if (starts_with("0"sv, s)) { // Check original string for octal prefix + Result result = do_from_chars(s); // Pass original string for octal + + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as octal: {}", String(s), result.error().message))); + + return result; + } + + Result result = do_from_chars(s); // Pass original string for decimal + + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as decimal integer: {}", String(s), result.error().message))); + + return result; } }; @@ -348,9 +341,9 @@ namespace argparse { inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; template - fn do_strtod(const String& s) -> T { + fn do_strtod(const String& s) -> Result { if (isspace(static_cast(s[0])) || s[0] == '+') - throw std::invalid_argument { "pattern '" + s + "' not found" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, std::format("pattern '{}' not found", s))); auto [first, last] = pointer_range(s); @@ -364,126 +357,103 @@ namespace argparse { if (ptr == last) return x; - throw std::invalid_argument { "pattern '" + s + - "' does not match to the end" }; + return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("pattern '{}' does not match to the end", s))); } if (errno == ERANGE) - throw std::range_error { "'" + s + "' not representable" }; + return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("'{}' not representable", s))); - return x; // unreachable + // Handle other potential errno values + return Err(util::error::DracError(std::error_code(errno, std::system_category()))); } template struct parse_number { - fn operator()(const String& s)->T { + fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) - throw std::invalid_argument { - "chars_format::general does not parse hexfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::general does not parse hexfloat")); if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin) - throw std::invalid_argument { - "chars_format::general does not parse binfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::general does not parse binfloat")); - try { - return do_strtod(s); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + s + "' as number: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + s + "' as number: " + err.what()); - } + Result result = do_strtod(s); + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as number: {}", s, result.error().message))); + return result; } }; template struct parse_number { - fn operator()(const String& s)->T { + fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); !is_hex) - throw std::invalid_argument { "chars_format::hex parses hexfloat" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::hex requires hexfloat format (e.g., 0x1.2p3)")); if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin) - throw std::invalid_argument { "chars_format::hex does not parse binfloat" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::hex does not parse binfloat")); - try { - return do_strtod(s); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + s + "' as hexadecimal: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + s + "' as hexadecimal: " + err.what()); - } + Result result = do_strtod(s); + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal float: {}", s, result.error().message))); + return result; } }; template struct parse_number { - fn operator()(const String& s)->T { + fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) - throw std::invalid_argument { - "chars_format::binary does not parse hexfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::binary does not parse hexfloat")); if (auto [is_bin, rest] = consume_binary_prefix(s); !is_bin) - throw std::invalid_argument { "chars_format::binary parses binfloat" }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::binary requires binfloat format (e.g., 0b1.01p2)")); - return do_strtod(s); + Result result = do_strtod(s); + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as binary float: {}", s, result.error().message))); + return result; } }; template struct parse_number { - fn operator()(const String& s)->T { + fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) - throw std::invalid_argument { - "chars_format::scientific does not parse hexfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific does not parse hexfloat")); if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin) - throw std::invalid_argument { - "chars_format::scientific does not parse binfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific does not parse binfloat")); if (s.find_first_of("eE") == String::npos) - throw std::invalid_argument { - "chars_format::scientific requires exponent part" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific requires exponent part")); - try { - return do_strtod(s); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + s + "' as scientific notation: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + s + "' as scientific notation: " + err.what()); - } + Result result = do_strtod(s); + + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as scientific notation: {}", s, result.error().message))); + + return result; } }; template struct parse_number { - fn operator()(const String& s)->T { + fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) - throw std::invalid_argument { - "chars_format::fixed does not parse hexfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse hexfloat")); if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin) - throw std::invalid_argument { - "chars_format::fixed does not parse binfloat" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse binfloat")); if (s.find_first_of("eE") != String::npos) - throw std::invalid_argument { - "chars_format::fixed does not parse exponent part" - }; + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse exponent part")); - try { - return do_strtod(s); - } catch (const std::invalid_argument& err) { - throw std::invalid_argument("Failed to parse '" + s + "' as fixed notation: " + err.what()); - } catch (const std::range_error& err) { - throw std::range_error("Failed to parse '" + s + "' as fixed notation: " + err.what()); - } + Result result = do_strtod(s); + + if (!result) + return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as fixed notation: {}", s, result.error().message))); + + return result; } }; @@ -707,7 +677,12 @@ namespace argparse { var = std::any_cast(m_default_value); action([&var](const auto& s) { - var = details::parse_number()(s); + Result result = details::parse_number()(s); + + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as decimal integer: {}", s, result.error().message)); + + var = *result; return var; }); @@ -720,7 +695,12 @@ namespace argparse { var = std::any_cast(m_default_value); action([&var](const auto& s) { - var = details::parse_number()(s); + Result result = details::parse_number()(s); + + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as number: {}", s, result.error().message)); + + var = *result; return var; }); @@ -774,7 +754,13 @@ namespace argparse { var.clear(); m_is_used = true; - var.push_back(details::parse_number()(s)); + + Result result = details::parse_number()(s); + + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as decimal integer for vector: {}", s, result.error().message)); + + var.push_back(*result); return var; }); @@ -806,7 +792,13 @@ namespace argparse { var.clear(); m_is_used = true; - var.insert(details::parse_number()(s)); + + Result result = details::parse_number()(s); + + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as decimal integer for set: {}", s, result.error().message)); + + var.insert(*result); return var; }); @@ -833,25 +825,75 @@ namespace argparse { }; if constexpr (Shape == 'd' && std::is_integral_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as decimal integer (scan 'd'): {}", s, result.error().message)); + return *result; + }); else if constexpr (Shape == 'i' && std::is_integral_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as integer (scan 'i'): {}", s, result.error().message)); + return *result; + }); else if constexpr (Shape == 'u' && (std::is_integral_v && std::is_unsigned_v)) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as unsigned decimal integer (scan 'u'): {}", s, result.error().message)); + return *result; + }); else if constexpr (Shape == 'b' && (std::is_integral_v && std::is_unsigned_v)) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as binary integer (scan 'b'): {}", s, result.error().message)); + return *result; + }); else if constexpr (Shape == 'o' && (std::is_integral_v && std::is_unsigned_v)) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as octal integer (scan 'o'): {}", s, result.error().message)); + return *result; + }); else if constexpr (is_one_of(Shape, 'x', 'X') && (std::is_integral_v && std::is_unsigned_v)) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as hexadecimal integer (scan '{}'): {}", s, Shape, result.error().message)); + return *result; + }); else if constexpr (is_one_of(Shape, 'a', 'A') && std::is_floating_point_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as hexadecimal float (scan '{}'): {}", s, Shape, result.error().message)); + return *result; + }); else if constexpr (is_one_of(Shape, 'e', 'E') && std::is_floating_point_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as scientific float (scan '{}'): {}", s, Shape, result.error().message)); + return *result; + }); else if constexpr (is_one_of(Shape, 'f', 'F') && std::is_floating_point_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as fixed float (scan '{}'): {}", s, Shape, result.error().message)); + return *result; + }); else if constexpr (is_one_of(Shape, 'g', 'G') && std::is_floating_point_v) - action(details::parse_number()); + action([](const String& s) -> T { + Result result = details::parse_number()(s); + if (!result) + throw std::runtime_error(std::format("Failed to parse '{}' as general float (scan '{}'): {}", s, Shape, result.error().message)); + return *result; + }); else static_assert(false, "No scan specification for T"); @@ -1070,26 +1112,72 @@ namespace argparse { } /* - * @throws std::runtime_error if argument values are not valid + * @returns Result indicating success or failure */ - fn validate() const -> void { + [[nodiscard]] fn validate() const -> Result { if (m_is_optional) { // TODO: check if an implicit value was programmed for this argument if (!m_is_used && !m_default_value.has_value() && m_is_required) - throw_required_arg_not_used_error(); + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, std::format("Required argument '{}' was not provided", m_names[0]))); if (m_is_used && m_is_required && m_values.empty()) - throw_required_arg_no_value_provided_error(); + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, std::format("Required argument '{}' requires a value, but none was provided", m_names[0]))); + + if (m_is_used && m_num_args_range.get_min() > m_values.size()) + return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, std::format("Too few arguments for optional argument '{}'. Expected at least {}, got {}.", m_names[0], m_num_args_range.get_min(), m_values.size()))); } else { - if (!m_num_args_range.contains(m_values.size()) && - !m_default_value.has_value()) - throw_nargs_range_validation_error(); + if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) { + String expected_str; + + if (m_num_args_range.is_exact()) + expected_str = std::to_string(m_num_args_range.get_min()); + else if (!m_num_args_range.is_right_bounded()) + expected_str = std::format("at least {}", m_num_args_range.get_min()); + else + expected_str = std::format("{} to {}", m_num_args_range.get_min(), m_num_args_range.get_max()); + + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Incorrect number of arguments for positional argument '{}'. Expected {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), expected_str, m_values.size()))); + } + + if (m_num_args_range.get_min() > m_values.size()) + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Too few arguments for positional argument '{}'. Expected at least {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), m_num_args_range.get_min(), m_values.size()))); } - if (m_choices.has_value()) - // Make sure the default value (if provided) - // is in the list of choices - find_default_value_in_choices_or_throw(); + if (m_num_args_range.get_max() < m_values.size()) { + if (m_is_optional) + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Too many arguments for optional argument '{}'. Expected at most {}, got {}.", m_names[0], m_num_args_range.get_max(), m_values.size()))); + + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Too many arguments for positional argument '{}'. Expected at most {}, got {}.", (m_metavar.empty() ? m_names[0] : m_metavar), m_num_args_range.get_max(), m_values.size()))); + } + + if (m_choices.has_value()) { + const Vec& choices = m_choices.value(); + + // Check default value + if (m_default_value.has_value()) + if (const String& default_val_str = m_default_value_str.value(); std::ranges::find(choices, default_val_str) == choices.end()) { + const String choices_as_csv = std::accumulate( + choices.begin(), choices.end(), String(), [](const String& option_a, const String& option_b) -> String { return option_a + (option_a.empty() ? "" : ", ") + option_b; } + ); + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Default value '{}' is not in the allowed choices: {{{}}}", default_val_str, choices_as_csv))); + } + + // Check provided values + for (const auto& value_any : m_values) { + if (value_any.type() != typeid(String)) + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Invalid argument type for choice validation - expected string, got '{}'", value_any.type().name()))); + + if (const String& value = std::any_cast(value_any); std::ranges::find(choices, value) == choices.end()) { + const String choices_as_csv = std::accumulate( + choices.begin(), choices.end(), String(), [](const String& option_a, const String& option_b) -> String { return std::format("{}{}{}", option_a, option_a.empty() ? "" : ", ", option_b); } + ); + + return Err(DracError(util::error::DracErrorCode::InvalidArgument, std::format("Invalid argument '{}' - allowed options: {{{}}}", value, choices_as_csv))); + } + } + } + + return {}; } [[nodiscard]] fn get_names_csv(const char separator = ',') const -> String { @@ -1432,7 +1520,8 @@ namespace argparse { // precondition: we have consumed or will consume at least one digit fn consume_digits = [=](StringView sd) -> StringView { - const char* const it = std::ranges::find_if_not(sd, is_digit); + const auto it = std::ranges::find_if_not(sd, is_digit); + return sd.substr(static_cast(it - std::begin(sd))); }; @@ -1848,7 +1937,8 @@ namespace argparse { parse_args_internal(arguments); // Check if all arguments are parsed for (const auto& argument : m_argument_map | std::views::values) - argument->validate(); + if (Result<> validation_result = argument->validate(); !validation_result) + throw std::runtime_error(validation_result.error().message); // Check each mutually exclusive group and make sure // there are no constraint violations @@ -1896,8 +1986,10 @@ namespace argparse { fn parse_known_args(const Vec& arguments) -> Vec { Vec unknown_arguments = parse_known_args_internal(arguments); - for (const auto& argument : m_argument_map | std::views::values) - argument->validate(); + for (const auto& argument : m_argument_map | std::views::values) { + if (Result<> validation_result = argument->validate(); !validation_result) + throw std::runtime_error(validation_result.error().message); + } return unknown_arguments; } diff --git a/src/config/weather.cpp b/src/config/weather.cpp index 9ac438e..69fe9ed 100644 --- a/src/config/weather.cpp +++ b/src/config/weather.cpp @@ -1,3 +1,4 @@ +#define NOMINMAX #include "weather.hpp" #include // std::chrono::{duration, operator-} diff --git a/src/main.cpp b/src/main.cpp index 71b2cf0..a02c2f4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,55 +24,53 @@ using util::types::i32; fn main(const i32 argc, char* argv[]) -> i32 { using namespace ftxui; using os::SystemData; + using util::types::Exception; -#ifdef _WIN32 - 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("trace", "debug", "info", "warn", "error", "fatal"); - - parser - .add_argument("-V", "--verbose") - .help("Enable verbose logging. Alias for --log-level=debug") - .flag(); - + // TODO: don't wrap all of this in a try-catch try { - parser.parse_args(argc, argv); - } catch (const util::types::Exception& err) { -#ifdef __cpp_lib_print - std::println(stderr, "{}", err.what()); -#else - std::cerr << err.what() << '\n'; +#ifdef _WIN32 + winrt::init_apartment(); #endif - std::cerr << parser; + argparse::ArgumentParser parser("draconis", "0.1.0"); + parser + .add_argument("--log-level") + .help("Set the log level") + .default_value("info") + .choices("trace", "debug", "info", "warn", "error", "fatal"); + + parser + .add_argument("-V", "--verbose") + .help("Enable verbose logging. Alias for --log-level=debug") + .flag(); + + parser.parse_args(argc, argv); + + if (parser["--verbose"] == true || parser["-v"] == true) + info_log("Verbose logging enabled"); + + const Config& config = Config::getInstance(); + const SystemData data = SystemData(config); + + Element document = ui::CreateUI(config, data); + + Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); + Render(screen, document); + screen.Print(); + +#ifdef __cpp_lib_print + std::println(); +#else + std::cout << '\n'; +#endif + } catch (const Exception& e) { + error_log("Exception: {}", e.what()); + return 1; + } catch (...) { + error_log("Unknown exception"); return 1; } - if (parser["--verbose"] == true || parser["-v"] == true) - info_log("Verbose logging enabled"); - - const Config& config = Config::getInstance(); - const SystemData data = SystemData(config); - - Element document = ui::CreateUI(config, data); - - Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document)); - Render(screen, document); - screen.Print(); - -#ifdef __cpp_lib_print - std::println(); -#else - std::cout << '\n'; -#endif - return 0; } diff --git a/src/util/error.hpp b/src/util/error.hpp index ebefa2c..268352e 100644 --- a/src/util/error.hpp +++ b/src/util/error.hpp @@ -6,6 +6,7 @@ #include // std::error_code #ifdef _WIN32 + #include // GUID #include // error values #include // winrt::hresult_error #endif @@ -77,14 +78,14 @@ namespace util { } #ifdef _WIN32 - explicit DracError(const winrt::hresult_error& e) - : message(winrt::to_string(e.message())) { + explicit DracError(const winrt::hresult_error& err) + : message(winrt::to_string(err.message())) { using namespace matchit; using enum DracErrorCode; - fn fromWin32 = [](const types::u32 x) -> HRESULT { return HRESULT_FROM_WIN32(x); }; + fn fromWin32 = [](const types::u32 win32) -> HRESULT { return HRESULT_FROM_WIN32(win32); }; - code = match(e.code())( + code = match(err.code())( is | or_(E_ACCESSDENIED, fromWin32(ERROR_ACCESS_DENIED)) = PermissionDenied, is | fromWin32(ERROR_FILE_NOT_FOUND) = NotFound, is | fromWin32(ERROR_PATH_NOT_FOUND) = NotFound,