updates
This commit is contained in:
parent
816619a162
commit
d2fab41864
4 changed files with 298 additions and 206 deletions
|
@ -63,6 +63,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "src/util/defs.hpp"
|
#include "src/util/defs.hpp"
|
||||||
|
#include "src/util/error.hpp" // Added for Result type
|
||||||
#include "src/util/types.hpp"
|
#include "src/util/types.hpp"
|
||||||
|
|
||||||
#ifndef ARGPARSE_CUSTOM_STRTOF
|
#ifndef ARGPARSE_CUSTOM_STRTOF
|
||||||
|
@ -231,7 +232,7 @@ namespace argparse {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T, auto Param>
|
template <class T, auto Param>
|
||||||
fn do_from_chars(const StringView s) -> T {
|
fn do_from_chars(const StringView s) -> Result<T> {
|
||||||
T x { 0 };
|
T x { 0 };
|
||||||
auto [first, last] = pointer_range(s);
|
auto [first, last] = pointer_range(s);
|
||||||
auto [ptr, ec] = std::from_chars(first, last, x, Param);
|
auto [ptr, ec] = std::from_chars(first, last, x, Param);
|
||||||
|
@ -239,102 +240,94 @@ namespace argparse {
|
||||||
if (ec == std::errc()) {
|
if (ec == std::errc()) {
|
||||||
if (ptr == last)
|
if (ptr == last)
|
||||||
return x;
|
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)
|
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)
|
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 <class T, auto Param = 0>
|
template <class T, auto Param = 0>
|
||||||
struct parse_number {
|
struct parse_number {
|
||||||
static fn operator()(const StringView s)->T {
|
static fn operator()(const StringView s)->Result<T> {
|
||||||
return do_from_chars<T, Param>(s);
|
return do_from_chars<T, Param>(s);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T, radix_2> {
|
struct parse_number<T, radix_2> {
|
||||||
static fn operator()(const StringView s)->T {
|
static fn operator()(const StringView s)->Result<T> {
|
||||||
if (auto [ok, rest] = consume_binary_prefix(s); ok)
|
if (auto [ok, rest] = consume_binary_prefix(s); ok)
|
||||||
return do_from_chars<T, radix_2>(rest);
|
return do_from_chars<T, radix_2>(rest);
|
||||||
|
|
||||||
throw std::invalid_argument { "pattern not found" };
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "pattern not found"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T, radix_16> {
|
struct parse_number<T, radix_16> {
|
||||||
static fn operator()(const StringView s)->T {
|
static fn operator()(const StringView s)->Result<T> {
|
||||||
|
Result<T> result;
|
||||||
|
|
||||||
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
|
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
|
||||||
if (auto [ok, rest] = consume_hex_prefix(s); ok)
|
if (auto [ok, rest] = consume_hex_prefix(s); ok)
|
||||||
try {
|
result = do_from_chars<T, radix_16>(rest);
|
||||||
return do_from_chars<T, radix_16>(rest);
|
else
|
||||||
} catch (const std::invalid_argument& err) {
|
return Err(util::error::DracError(util::error::DracErrorCode::InternalError, std::format("Inconsistent hex prefix detection for '{}'", String(s))));
|
||||||
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());
|
|
||||||
}
|
|
||||||
} else
|
} else
|
||||||
// Allow passing hex numbers without prefix
|
result = do_from_chars<T, radix_16>(s);
|
||||||
// Shape 'x' already has to be specified
|
|
||||||
try {
|
|
||||||
return do_from_chars<T, radix_16>(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());
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::invalid_argument { "pattern '" + String(s) +
|
if (!result)
|
||||||
"' not identified as hexadecimal" };
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message)));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T> {
|
struct parse_number<T> {
|
||||||
static fn operator()(const StringView s)->T {
|
static fn operator()(const StringView s)->Result<T> {
|
||||||
auto [ok, rest] = consume_hex_prefix(s);
|
if (auto [ok, rest] = consume_hex_prefix(s); ok) {
|
||||||
|
Result<T> result = do_from_chars<T, radix_16>(rest);
|
||||||
|
|
||||||
if (ok)
|
if (!result)
|
||||||
try {
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal: {}", String(s), result.error().message)));
|
||||||
return do_from_chars<T, radix_16>(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 (auto [ok_binary, rest_binary] = consume_binary_prefix(s); ok_binary)
|
return result;
|
||||||
try {
|
|
||||||
return do_from_chars<T, radix_2>(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<T, radix_8>(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<T, radix_10>(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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto [ok_binary, rest_binary] = consume_binary_prefix(s); ok_binary) {
|
||||||
|
Result<T> result = do_from_chars<T, radix_2>(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<T> result = do_from_chars<T, radix_8>(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<T> result = do_from_chars<T, radix_10>(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<long double> = ARGPARSE_CUSTOM_STRTOLD;
|
inline const auto generic_strtod<long double> = ARGPARSE_CUSTOM_STRTOLD;
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
fn do_strtod(const String& s) -> T {
|
fn do_strtod(const String& s) -> Result<T> {
|
||||||
if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+')
|
if (isspace(static_cast<unsigned char>(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);
|
auto [first, last] = pointer_range(s);
|
||||||
|
|
||||||
|
@ -364,126 +357,103 @@ namespace argparse {
|
||||||
if (ptr == last)
|
if (ptr == last)
|
||||||
return x;
|
return x;
|
||||||
|
|
||||||
throw std::invalid_argument { "pattern '" + s +
|
return Err(util::error::DracError(util::error::DracErrorCode::ParseError, std::format("pattern '{}' does not match to the end", s)));
|
||||||
"' does not match to the end" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errno == ERANGE)
|
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 <class T>
|
template <class T>
|
||||||
struct parse_number<T, chars_format::general> {
|
struct parse_number<T, chars_format::general> {
|
||||||
fn operator()(const String& s)->T {
|
fn operator()(const String& s)->Result<T> {
|
||||||
if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::general does not parse hexfloat"));
|
||||||
"chars_format::general does not parse hexfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
if (auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::general does not parse binfloat"));
|
||||||
"chars_format::general does not parse binfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
Result<T> result = do_strtod<T>(s);
|
||||||
return do_strtod<T>(s);
|
if (!result)
|
||||||
} catch (const std::invalid_argument& err) {
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as number: {}", s, result.error().message)));
|
||||||
throw std::invalid_argument("Failed to parse '" + s + "' as number: " + err.what());
|
return result;
|
||||||
} catch (const std::range_error& err) {
|
|
||||||
throw std::range_error("Failed to parse '" + s + "' as number: " + err.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T, chars_format::hex> {
|
struct parse_number<T, chars_format::hex> {
|
||||||
fn operator()(const String& s)->T {
|
fn operator()(const String& s)->Result<T> {
|
||||||
if (auto [is_hex, rest] = consume_hex_prefix(s); !is_hex)
|
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)
|
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 {
|
Result<T> result = do_strtod<T>(s);
|
||||||
return do_strtod<T>(s);
|
if (!result)
|
||||||
} catch (const std::invalid_argument& err) {
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as hexadecimal float: {}", s, result.error().message)));
|
||||||
throw std::invalid_argument("Failed to parse '" + s + "' as hexadecimal: " + err.what());
|
return result;
|
||||||
} catch (const std::range_error& err) {
|
|
||||||
throw std::range_error("Failed to parse '" + s + "' as hexadecimal: " + err.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T, chars_format::binary> {
|
struct parse_number<T, chars_format::binary> {
|
||||||
fn operator()(const String& s)->T {
|
fn operator()(const String& s)->Result<T> {
|
||||||
if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::binary does not parse hexfloat"));
|
||||||
"chars_format::binary does not parse hexfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto [is_bin, rest] = consume_binary_prefix(s); !is_bin)
|
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<T>(s);
|
Result<T> result = do_strtod<T>(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 <class T>
|
template <class T>
|
||||||
struct parse_number<T, chars_format::scientific> {
|
struct parse_number<T, chars_format::scientific> {
|
||||||
fn operator()(const String& s)->T {
|
fn operator()(const String& s)->Result<T> {
|
||||||
if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific does not parse hexfloat"));
|
||||||
"chars_format::scientific does not parse hexfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific does not parse binfloat"));
|
||||||
"chars_format::scientific does not parse binfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (s.find_first_of("eE") == String::npos)
|
if (s.find_first_of("eE") == String::npos)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::scientific requires exponent part"));
|
||||||
"chars_format::scientific requires exponent part"
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
Result<T> result = do_strtod<T>(s);
|
||||||
return do_strtod<T>(s);
|
|
||||||
} catch (const std::invalid_argument& err) {
|
if (!result)
|
||||||
throw std::invalid_argument("Failed to parse '" + s + "' as scientific notation: " + err.what());
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as scientific notation: {}", s, result.error().message)));
|
||||||
} catch (const std::range_error& err) {
|
|
||||||
throw std::range_error("Failed to parse '" + s + "' as scientific notation: " + err.what());
|
return result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
struct parse_number<T, chars_format::fixed> {
|
struct parse_number<T, chars_format::fixed> {
|
||||||
fn operator()(const String& s)->T {
|
fn operator()(const String& s)->Result<T> {
|
||||||
if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse hexfloat"));
|
||||||
"chars_format::fixed does not parse hexfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
if (const auto [is_bin, rest] = consume_binary_prefix(s); is_bin)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse binfloat"));
|
||||||
"chars_format::fixed does not parse binfloat"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (s.find_first_of("eE") != String::npos)
|
if (s.find_first_of("eE") != String::npos)
|
||||||
throw std::invalid_argument {
|
return Err(util::error::DracError(util::error::DracErrorCode::InvalidArgument, "chars_format::fixed does not parse exponent part"));
|
||||||
"chars_format::fixed does not parse exponent part"
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
Result<T> result = do_strtod<T>(s);
|
||||||
return do_strtod<T>(s);
|
|
||||||
} catch (const std::invalid_argument& err) {
|
if (!result)
|
||||||
throw std::invalid_argument("Failed to parse '" + s + "' as fixed notation: " + err.what());
|
return Err(util::error::DracError(result.error().code, std::format("Failed to parse '{}' as fixed notation: {}", s, result.error().message)));
|
||||||
} catch (const std::range_error& err) {
|
|
||||||
throw std::range_error("Failed to parse '" + s + "' as fixed notation: " + err.what());
|
return result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -707,7 +677,12 @@ namespace argparse {
|
||||||
var = std::any_cast<T>(m_default_value);
|
var = std::any_cast<T>(m_default_value);
|
||||||
|
|
||||||
action([&var](const auto& s) {
|
action([&var](const auto& s) {
|
||||||
var = details::parse_number<T, details::radix_10>()(s);
|
Result<T> result = details::parse_number<T, details::radix_10>()(s);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
throw std::runtime_error(std::format("Failed to parse '{}' as decimal integer: {}", s, result.error().message));
|
||||||
|
|
||||||
|
var = *result;
|
||||||
return var;
|
return var;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -720,7 +695,12 @@ namespace argparse {
|
||||||
var = std::any_cast<T>(m_default_value);
|
var = std::any_cast<T>(m_default_value);
|
||||||
|
|
||||||
action([&var](const auto& s) {
|
action([&var](const auto& s) {
|
||||||
var = details::parse_number<T, details::chars_format::general>()(s);
|
Result<T> result = details::parse_number<T, details::chars_format::general>()(s);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
throw std::runtime_error(std::format("Failed to parse '{}' as number: {}", s, result.error().message));
|
||||||
|
|
||||||
|
var = *result;
|
||||||
return var;
|
return var;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -774,7 +754,13 @@ namespace argparse {
|
||||||
var.clear();
|
var.clear();
|
||||||
|
|
||||||
m_is_used = true;
|
m_is_used = true;
|
||||||
var.push_back(details::parse_number<int, details::radix_10>()(s));
|
|
||||||
|
Result<int> result = details::parse_number<int, details::radix_10>()(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;
|
return var;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -806,7 +792,13 @@ namespace argparse {
|
||||||
var.clear();
|
var.clear();
|
||||||
|
|
||||||
m_is_used = true;
|
m_is_used = true;
|
||||||
var.insert(details::parse_number<int, details::radix_10>()(s));
|
|
||||||
|
Result<int> result = details::parse_number<int, details::radix_10>()(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;
|
return var;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -833,25 +825,75 @@ namespace argparse {
|
||||||
};
|
};
|
||||||
|
|
||||||
if constexpr (Shape == 'd' && std::is_integral_v<T>)
|
if constexpr (Shape == 'd' && std::is_integral_v<T>)
|
||||||
action(details::parse_number<T, details::radix_10>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::radix_10>()(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<T>)
|
else if constexpr (Shape == 'i' && std::is_integral_v<T>)
|
||||||
action(details::parse_number<T>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T>()(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<T> && std::is_unsigned_v<T>))
|
else if constexpr (Shape == 'u' && (std::is_integral_v<T> && std::is_unsigned_v<T>))
|
||||||
action(details::parse_number<T, details::radix_10>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::radix_10>()(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<T> && std::is_unsigned_v<T>))
|
else if constexpr (Shape == 'b' && (std::is_integral_v<T> && std::is_unsigned_v<T>))
|
||||||
action(details::parse_number<T, details::radix_2>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::radix_2>()(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<T> && std::is_unsigned_v<T>))
|
else if constexpr (Shape == 'o' && (std::is_integral_v<T> && std::is_unsigned_v<T>))
|
||||||
action(details::parse_number<T, details::radix_8>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::radix_8>()(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<T> && std::is_unsigned_v<T>))
|
else if constexpr (is_one_of(Shape, 'x', 'X') && (std::is_integral_v<T> && std::is_unsigned_v<T>))
|
||||||
action(details::parse_number<T, details::radix_16>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::radix_16>()(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<T>)
|
else if constexpr (is_one_of(Shape, 'a', 'A') && std::is_floating_point_v<T>)
|
||||||
action(details::parse_number<T, details::chars_format::hex>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::chars_format::hex>()(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<T>)
|
else if constexpr (is_one_of(Shape, 'e', 'E') && std::is_floating_point_v<T>)
|
||||||
action(details::parse_number<T, details::chars_format::scientific>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::chars_format::scientific>()(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<T>)
|
else if constexpr (is_one_of(Shape, 'f', 'F') && std::is_floating_point_v<T>)
|
||||||
action(details::parse_number<T, details::chars_format::fixed>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::chars_format::fixed>()(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<T>)
|
else if constexpr (is_one_of(Shape, 'g', 'G') && std::is_floating_point_v<T>)
|
||||||
action(details::parse_number<T, details::chars_format::general>());
|
action([](const String& s) -> T {
|
||||||
|
Result<T> result = details::parse_number<T, details::chars_format::general>()(s);
|
||||||
|
if (!result)
|
||||||
|
throw std::runtime_error(std::format("Failed to parse '{}' as general float (scan '{}'): {}", s, Shape, result.error().message));
|
||||||
|
return *result;
|
||||||
|
});
|
||||||
else
|
else
|
||||||
static_assert(false, "No scan specification for T");
|
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<void> indicating success or failure
|
||||||
*/
|
*/
|
||||||
fn validate() const -> void {
|
[[nodiscard]] fn validate() const -> Result<void> {
|
||||||
if (m_is_optional) {
|
if (m_is_optional) {
|
||||||
// TODO: check if an implicit value was programmed for this argument
|
// TODO: check if an implicit value was programmed for this argument
|
||||||
if (!m_is_used && !m_default_value.has_value() && m_is_required)
|
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())
|
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 {
|
} else {
|
||||||
if (!m_num_args_range.contains(m_values.size()) &&
|
if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) {
|
||||||
!m_default_value.has_value())
|
String expected_str;
|
||||||
throw_nargs_range_validation_error();
|
|
||||||
|
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())
|
if (m_num_args_range.get_max() < m_values.size()) {
|
||||||
// Make sure the default value (if provided)
|
if (m_is_optional)
|
||||||
// is in the list of choices
|
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())));
|
||||||
find_default_value_in_choices_or_throw();
|
|
||||||
|
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<String>& 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<const String&>(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 {
|
[[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
|
// precondition: we have consumed or will consume at least one digit
|
||||||
fn consume_digits = [=](StringView sd) -> StringView {
|
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<usize>(it - std::begin(sd)));
|
return sd.substr(static_cast<usize>(it - std::begin(sd)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1848,7 +1937,8 @@ namespace argparse {
|
||||||
parse_args_internal(arguments);
|
parse_args_internal(arguments);
|
||||||
// Check if all arguments are parsed
|
// Check if all arguments are parsed
|
||||||
for (const auto& argument : m_argument_map | std::views::values)
|
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
|
// Check each mutually exclusive group and make sure
|
||||||
// there are no constraint violations
|
// there are no constraint violations
|
||||||
|
@ -1896,8 +1986,10 @@ namespace argparse {
|
||||||
fn parse_known_args(const Vec<String>& arguments) -> Vec<String> {
|
fn parse_known_args(const Vec<String>& arguments) -> Vec<String> {
|
||||||
Vec<String> unknown_arguments = parse_known_args_internal(arguments);
|
Vec<String> unknown_arguments = parse_known_args_internal(arguments);
|
||||||
|
|
||||||
for (const auto& argument : m_argument_map | std::views::values)
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
return unknown_arguments;
|
return unknown_arguments;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#define NOMINMAX
|
||||||
#include "weather.hpp"
|
#include "weather.hpp"
|
||||||
|
|
||||||
#include <chrono> // std::chrono::{duration, operator-}
|
#include <chrono> // std::chrono::{duration, operator-}
|
||||||
|
|
82
src/main.cpp
82
src/main.cpp
|
@ -24,55 +24,53 @@ using util::types::i32;
|
||||||
fn main(const i32 argc, char* argv[]) -> i32 {
|
fn main(const i32 argc, char* argv[]) -> i32 {
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
using os::SystemData;
|
using os::SystemData;
|
||||||
|
using util::types::Exception;
|
||||||
|
|
||||||
#ifdef _WIN32
|
// TODO: don't wrap all of this in a try-catch
|
||||||
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();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parser.parse_args(argc, argv);
|
#ifdef _WIN32
|
||||||
} catch (const util::types::Exception& err) {
|
winrt::init_apartment();
|
||||||
#ifdef __cpp_lib_print
|
|
||||||
std::println(stderr, "{}", err.what());
|
|
||||||
#else
|
|
||||||
std::cerr << err.what() << '\n';
|
|
||||||
#endif
|
#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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <system_error> // std::error_code
|
#include <system_error> // std::error_code
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
#include <guiddef.h> // GUID
|
||||||
#include <winerror.h> // error values
|
#include <winerror.h> // error values
|
||||||
#include <winrt/base.h> // winrt::hresult_error
|
#include <winrt/base.h> // winrt::hresult_error
|
||||||
#endif
|
#endif
|
||||||
|
@ -77,14 +78,14 @@ namespace util {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
explicit DracError(const winrt::hresult_error& e)
|
explicit DracError(const winrt::hresult_error& err)
|
||||||
: message(winrt::to_string(e.message())) {
|
: message(winrt::to_string(err.message())) {
|
||||||
using namespace matchit;
|
using namespace matchit;
|
||||||
using enum DracErrorCode;
|
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 | or_(E_ACCESSDENIED, fromWin32(ERROR_ACCESS_DENIED)) = PermissionDenied,
|
||||||
is | fromWin32(ERROR_FILE_NOT_FOUND) = NotFound,
|
is | fromWin32(ERROR_FILE_NOT_FOUND) = NotFound,
|
||||||
is | fromWin32(ERROR_PATH_NOT_FOUND) = NotFound,
|
is | fromWin32(ERROR_PATH_NOT_FOUND) = NotFound,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue