diff --git a/include/argparse.hpp b/include/argparse.hpp index b5403d3..58b3198 100644 --- a/include/argparse.hpp +++ b/include/argparse.hpp @@ -1,3 +1,15 @@ +/** + * @file argparse.hpp + * @brief Argument Parser for Modern C++ + * @author Pranav Srinivas Kumar + * @copyright Copyright (c) 2019-2022 Pranav Srinivas Kumar and other contributors + * @license MIT License + * + * A powerful, flexible, and easy-to-use command-line argument parser for modern C++. + * Provides a simple interface for defining, parsing, and validating command-line arguments. + * Supports both positional and optional arguments, subcommands, and more. + */ + #pragma once /* @@ -84,37 +96,83 @@ namespace argparse { using util::error::DracError, util::error::DracErrorCode; namespace details { + /** + * @brief Trait to check if a type has container-like properties + * @tparam T The type to check + * @tparam void SFINAE parameter + */ template struct HasContainerTraits : std::false_type {}; + /** + * @brief Specialization for std::string - not considered a container + */ template <> struct HasContainerTraits : std::false_type {}; + /** + * @brief Specialization for std::string_view - not considered a container + */ template <> struct HasContainerTraits : std::false_type {}; + /** + * @brief Specialization for types that have container-like properties + * @tparam T The type to check + */ template struct HasContainerTraits().begin()), decltype(std::declval().end()), decltype(std::declval().size())>> : std::true_type {}; + /** + * @brief Convenience variable template for checking if a type is a container + * @tparam T The type to check + */ template inline constexpr bool IsContainer = HasContainerTraits::value; + /** + * @brief Trait to check if a type can be streamed to std::ostream + * @tparam T The type to check + * @tparam void SFINAE parameter + */ template struct HasStreamableTraits : std::false_type {}; + /** + * @brief Specialization for types that can be streamed to std::ostream + * @tparam T The type to check + */ template struct HasStreamableTraits() << std::declval())>> : std::true_type {}; + /** + * @brief Convenience variable template for checking if a type is streamable + * @tparam T The type to check + */ template inline constexpr bool IsStreamable = HasStreamableTraits::value; + /** + * @brief Maximum number of elements to show when representing a container + */ constexpr usize repr_max_container_size = 5; + /** + * @brief Concept to check if a type can be formatted using std::format + * @tparam T The type to check + * @tparam CharT The character type for formatting + */ template concept Formattable = requires(const T& t, std::basic_format_context ctx) { std::formatter, CharT>().format(t, ctx); }; + /** + * @brief Convert a value to its string representation + * @tparam T The type of the value to convert + * @param val The value to convert + * @return String representation of the value + */ template static auto repr(const T& val) -> String { if constexpr (std::is_same_v) @@ -166,16 +224,40 @@ namespace argparse { return ""; } + /** + * @brief Radix constants for number parsing + */ constexpr i32 radix_2 = 2; constexpr i32 radix_8 = 8; constexpr i32 radix_10 = 10; constexpr i32 radix_16 = 16; + /** + * @brief Helper function to apply a function with an additional argument + * @tparam F Function type + * @tparam Tuple Tuple type containing the base arguments + * @tparam Extra Type of the additional argument + * @tparam I... Index sequence + * @param f Function to apply + * @param t Tuple of base arguments + * @param x Additional argument + * @return Result of applying the function + */ template constexpr fn apply_plus_one_impl(F&& f, Tuple&& t, Extra&& x, std::index_sequence /*unused*/) -> decltype(auto) { return std::invoke(std::forward(f), std::get(std::forward(t))..., std::forward(x)); } + /** + * @brief Wrapper for apply_plus_one_impl that creates the index sequence + * @tparam F Function type + * @tparam Tuple Tuple type containing the base arguments + * @tparam Extra Type of the additional argument + * @param f Function to apply + * @param t Tuple of base arguments + * @param x Additional argument + * @return Result of applying the function + */ template constexpr fn apply_plus_one(F&& f, Tuple&& t, Extra&& x) -> decltype(auto) { return details::apply_plus_one_impl( @@ -183,28 +265,52 @@ namespace argparse { ); } + /** + * @brief Get a tuple of pointers to the start and end of a string view + * @param s The string view to get pointers for + * @return Tuple of (start pointer, end pointer) + */ constexpr fn pointer_range(const StringView s) noexcept -> std::tuple { return { s.data(), s.data() + s.size() }; } + /** + * @brief Check if a string view starts with a given prefix + * @tparam CharT Character type + * @tparam Traits Character traits type + * @param prefix The prefix to check for + * @param s The string to check + * @return true if s starts with prefix, false otherwise + */ template constexpr fn starts_with(std::basic_string_view prefix, std::basic_string_view s) noexcept -> bool { return s.substr(0, prefix.size()) == prefix; } + /** + * @brief Format flags for number parsing + */ enum class chars_format : u8 { - scientific = 0xf1, - fixed = 0xf2, - hex = 0xf4, - binary = 0xf8, - general = fixed | scientific + scientific = 0xf1, ///< Scientific notation (e.g., 1.23e4) + fixed = 0xf2, ///< Fixed point notation (e.g., 123.45) + hex = 0xf4, ///< Hexadecimal notation (e.g., 0x1a) + binary = 0xf8, ///< Binary notation (e.g., 0b1010) + general = fixed | scientific ///< General format (either fixed or scientific) }; + /** + * @brief Result of checking for binary prefix + */ struct ConsumeBinaryPrefixResult { - bool is_binary; - StringView rest; + bool is_binary; ///< Whether the string had a binary prefix + StringView rest; ///< The string after removing the prefix }; + /** + * @brief Check if a string starts with a binary prefix and remove it + * @param s The string to check + * @return Result containing whether a binary prefix was found and the remaining string + */ constexpr fn consume_binary_prefix(StringView s) -> ConsumeBinaryPrefixResult { if (starts_with(StringView { "0b" }, s) || starts_with(StringView { "0B" }, s)) { @@ -215,13 +321,21 @@ namespace argparse { return { .is_binary = false, .rest = s }; } + /** + * @brief Result of checking for hexadecimal prefix + */ struct ConsumeHexPrefixResult { - bool is_hexadecimal; - StringView rest; + bool is_hexadecimal; ///< Whether the string had a hex prefix + StringView rest; ///< The string after removing the prefix }; using namespace std::literals; + /** + * @brief Check if a string starts with a hexadecimal prefix and remove it + * @param s The string to check + * @return Result containing whether a hex prefix was found and the remaining string + */ constexpr fn consume_hex_prefix(StringView s) -> ConsumeHexPrefixResult { if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { s.remove_prefix(2); @@ -231,6 +345,13 @@ namespace argparse { return { .is_hexadecimal = false, .rest = s }; } + /** + * @brief Parse a string into a number using std::from_chars + * @tparam T The type to parse into + * @tparam Param The radix or format to use + * @param s The string to parse + * @return Result containing the parsed number or an error + */ template fn do_from_chars(const StringView s) -> Result { T x { 0 }; @@ -253,15 +374,34 @@ namespace argparse { return Err(DracError(DracErrorCode::InternalError, std::format("Unknown parsing error for '{}'", String(s)))); } + /** + * @brief Functor for parsing numbers with a specific radix + * @tparam T The type to parse into + * @tparam Param The radix to use (defaults to 0 for automatic detection) + */ template struct parse_number { + /** + * @brief Parse a string into a number + * @param s The string to parse + * @return Result containing the parsed number or an error + */ static fn operator()(const StringView s)->Result { return do_from_chars(s); } }; + /** + * @brief Specialization for binary number parsing + * @tparam T The type to parse into + */ template struct parse_number { + /** + * @brief Parse a binary string into a number + * @param s The string to parse (must start with 0b or 0B) + * @return Result containing the parsed number or an error + */ static fn operator()(const StringView s)->Result { if (auto [ok, rest] = consume_binary_prefix(s); ok) return do_from_chars(rest); @@ -270,8 +410,17 @@ namespace argparse { } }; + /** + * @brief Specialization for hexadecimal number parsing + * @tparam T The type to parse into + */ template struct parse_number { + /** + * @brief Parse a hexadecimal string into a number + * @param s The string to parse (may start with 0x or 0X) + * @return Result containing the parsed number or an error + */ static fn operator()(const StringView s)->Result { Result result; @@ -290,8 +439,23 @@ namespace argparse { } }; + /** + * @brief Specialization for automatic number format detection + * @tparam T The type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a number, automatically detecting the format + * @param s The string to parse + * @return Result containing the parsed number or an error + * + * Supports: + * - Hexadecimal (0x/0X prefix) + * - Binary (0b/0B prefix) + * - Octal (0 prefix) + * - Decimal (no prefix) + */ static fn operator()(const StringView s)->Result { if (auto [ok, rest] = consume_hex_prefix(s); ok) { Result result = do_from_chars(rest); @@ -329,6 +493,10 @@ namespace argparse { } }; + /** + * @brief Custom string to number conversion functions + * @tparam T The type to convert to + */ template inline constexpr std::nullptr_t generic_strtod = nullptr; template <> @@ -338,6 +506,12 @@ namespace argparse { template <> inline const auto generic_strtod = ARGPARSE_CUSTOM_STRTOLD; + /** + * @brief Parse a string into a floating point number + * @tparam T The floating point type to parse into + * @param s The string to parse + * @return Result containing the parsed number or an error + */ template fn do_strtod(const String& s) -> Result { if (isspace(static_cast(s[0])) || s[0] == '+') @@ -364,8 +538,17 @@ namespace argparse { return Err(DracError(std::error_code(errno, std::system_category()))); } + /** + * @brief Specialization for general floating point format + * @tparam T The floating point type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a floating point number in general format + * @param s The string to parse + * @return Result containing the parsed number or an error + */ fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::general does not parse hexfloat")); @@ -380,8 +563,17 @@ namespace argparse { } }; + /** + * @brief Specialization for hexadecimal floating point format + * @tparam T The floating point type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a floating point number in hexadecimal format + * @param s The string to parse (must start with 0x or 0X) + * @return Result containing the parsed number or an error + */ fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); !is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::hex requires hexfloat format (e.g., 0x1.2p3)")); @@ -396,8 +588,17 @@ namespace argparse { } }; + /** + * @brief Specialization for binary floating point format + * @tparam T The floating point type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a floating point number in binary format + * @param s The string to parse (must start with 0b or 0B) + * @return Result containing the parsed number or an error + */ fn operator()(const String& s)->Result { if (auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::binary does not parse hexfloat")); @@ -412,8 +613,17 @@ namespace argparse { } }; + /** + * @brief Specialization for scientific floating point format + * @tparam T The floating point type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a floating point number in scientific notation + * @param s The string to parse (must contain e or E) + * @return Result containing the parsed number or an error + */ fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::scientific does not parse hexfloat")); @@ -433,8 +643,17 @@ namespace argparse { } }; + /** + * @brief Specialization for fixed point floating point format + * @tparam T The floating point type to parse into + */ template struct parse_number { + /** + * @brief Parse a string into a floating point number in fixed point notation + * @param s The string to parse (must not contain e or E) + * @return Result containing the parsed number or an error + */ fn operator()(const String& s)->Result { if (const auto [is_hex, rest] = consume_hex_prefix(s); is_hex) return Err(DracError(DracErrorCode::InvalidArgument, "chars_format::fixed does not parse hexfloat")); @@ -454,11 +673,23 @@ namespace argparse { } }; + /** + * @brief Concept to check if a type can be converted to a string + * @tparam T The type to check + */ template concept ToStringConvertible = std::convertible_to || std::convertible_to || requires(const T& t) { std::format("{}", t); }; + /** + * @brief Join a range of strings with a separator + * @tparam StrIt Iterator type for the string range + * @param first Iterator to the first string + * @param last Iterator past the last string + * @param separator The separator to use between strings + * @return The joined string + */ template fn join(StrIt first, StrIt last, const String& separator) -> String { if (first == last) @@ -476,12 +707,24 @@ namespace argparse { return value.str(); } + /** + * @brief Trait to check if a type can be converted using std::to_string + * @tparam T The type to check + */ template struct can_invoke_to_string { + /** + * @brief SFINAE test for std::to_string support + * @tparam U The type to test + */ template // ReSharper disable CppFunctionIsNotImplemented static fn test(int) -> decltype(std::to_string(std::declval()), std::true_type {}); + /** + * @brief Fallback for types without std::to_string support + * @tparam U The type to test + */ template static fn test(...) -> std::false_type; // ReSharper restore CppFunctionIsNotImplemented @@ -489,6 +732,10 @@ namespace argparse { static constexpr bool value = decltype(test(0))::value; }; + /** + * @brief Trait to check if a type is supported for choice arguments + * @tparam T The type to check + */ template struct IsChoiceTypeSupported { using CleanType = std::decay_t; @@ -498,6 +745,13 @@ namespace argparse { std::is_same_v; }; + /** + * @brief Calculate the Levenshtein distance between two strings + * @tparam StringType The string type to use + * @param s1 First string + * @param s2 Second string + * @return The Levenshtein distance between s1 and s2 + */ template fn get_levenshtein_distance(const StringType& s1, const StringType& s2) -> usize { Vec> dp( @@ -521,8 +775,16 @@ namespace argparse { return dp[s1.size()][s2.size()]; } - template - fn get_most_similar_string(const Map& map, const String& input) -> String { + /** + * @brief Find the most similar string in a map to a given input + * @tparam MapType The map-like container type + * @tparam ValueType The value type of the map + * @param map The map to search in + * @param input The input string to find matches for + * @return The most similar string from the map + */ + template + fn get_most_similar_string(const MapType& map, const String& input) -> String { String most_similar {}; usize min_distance = (std::numeric_limits::max)(); @@ -535,29 +797,58 @@ namespace argparse { return most_similar; } + /** + * @brief Trait to check if a type is a specialization of a template + * @tparam Test The type to check + * @tparam Ref The template to check against + */ template class Ref> struct is_specialization : std::false_type {}; + /** + * @brief Specialization for when Test is a specialization of Ref + * @tparam Ref The template + * @tparam Args The template arguments + */ template