diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d6aa06..2fea85e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.30) +set(BLT_VERSION 4.0.31) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 6462789..88713b4 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -231,8 +231,11 @@ namespace blt::argparse template auto ensure_is_string(T&& t) { - if constexpr (std::is_arithmetic_v> && !(std::is_same_v - || std::is_same_v || std::is_same_v)) + using NO_REF = meta::remove_cvref_t; + if constexpr (std::is_same_v) + return t ? "true" : "false"; + else if constexpr (std::is_arithmetic_v && !(std::is_same_v + || std::is_same_v || std::is_same_v)) return std::to_string(std::forward(t)); else return std::forward(t); @@ -582,7 +585,9 @@ namespace blt::argparse class argument_parser_t { friend argument_subparser_t; - + explicit argument_parser_t(const argument_subparser_t* parent): m_parent(parent) + { + } public: explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, const std::optional version = {}, @@ -739,6 +744,7 @@ namespace blt::argparse std::optional m_description; std::optional m_epilogue; std::optional m_version; + const argument_subparser_t* m_parent = nullptr; std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; @@ -748,6 +754,7 @@ namespace blt::argparse class argument_subparser_t { + friend argument_parser_t; public: explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) { @@ -760,9 +767,16 @@ namespace blt::argparse std::conjunction_v, std::is_constructible< std::string_view, Aliases>>...>, "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); - m_parsers.emplace(name, argument_parser_t{}); - ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); - return &m_parsers[name]; + m_parsers.emplace_back(new argument_parser_t{this}); + m_aliases[name] = m_parsers.back().get(); + ((m_aliases[std::string_view{aliases}] = m_parsers.back().get()), ...); + return m_parsers.back().get(); + } + + argument_subparser_t* set_help(const std::optional& help) + { + m_help = help; + return this; } @@ -781,10 +795,15 @@ namespace blt::argparse std::pair parse(argument_consumer_t& consumer); // NOLINT private: - [[nodiscard]] std::vector> get_allowed_strings() const; + [[nodiscard]] hashmap_t> get_allowed_strings() const; + + // annoying compatability because im lazy + static std::vector> to_vec(const hashmap_t>& map); const argument_parser_t* m_parent; - hashmap_t m_parsers; + std::optional m_last_parsed_parser; // bad hack + std::optional m_help; + std::vector> m_parsers; hashmap_t m_aliases; }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 3a2378b..86e282b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -76,7 +76,9 @@ namespace blt::argparse } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - return std::string() + t; + std::string str; + str += t; + return str; } else { @@ -400,7 +402,23 @@ namespace blt::argparse { help += "Subcommands:"; help.newline(); - + for (const auto& [key, value] : m_subparsers) + { + help += '\t'; + auto map = value.get_allowed_strings(); + // TODO: make an unzip? + for (const auto& [parser, strings] : map) + { + for (const auto& [i, str] : enumerate(strings)) + { + help += str; + if (i != strings.size() - 1) + help += ", "; + } + help += parser.he + help.newline(); + } + } } if (!m_flag_arguments.empty()) @@ -461,7 +479,7 @@ namespace blt::argparse { auto& [builder, flag_list] = pair; str += builder->m_help.value_or(""); - if (builder->m_default_value) + if (builder->m_default_value && !(builder->m_action == action_t::STORE_TRUE || builder->m_action == action_t::STORE_FALSE)) { if (!std::isblank(str.str().back())) str += " "; @@ -514,6 +532,17 @@ namespace blt::argparse aligner += m_name.value_or(""); aligner += ' '; + auto parent = m_parent; + while (parent != nullptr) + { + if (!parent->m_last_parsed_parser) + throw detail::missing_value_error( + "Error: Help called on subparser but unable to find parser chain. This condition should be impossible."); + aligner += parent->m_last_parsed_parser.value(); + aligner += ' '; + parent = parent->m_parent->m_parent; + } + hashmap_t> singleFlags; std::vector> compoundFlags; @@ -960,36 +989,28 @@ namespace blt::argparse throw detail::missing_argument_error("Subparser requires an argument."); const auto key = consumer.consume(); if (key.is_flag()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - const auto it = m_parsers.find(key.get_name()); - argument_parser_t* parser; - if (it == m_parsers.end()) - { - const auto it2 = m_aliases.find(key.get_name()); - if (it2 == m_aliases.end()) - throw detail::subparse_error(key.get_argument(), get_allowed_strings()); - parser = it2->second; - } - else - parser = &it->second; - parser->m_name = m_parent->m_name; - return {key, parser->parse(consumer)}; + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + const auto it = m_aliases.find(key.get_name()); + if (it == m_aliases.end()) + throw detail::subparse_error(key.get_argument(), to_vec(get_allowed_strings())); + it->second->m_name = m_parent->m_name; + m_last_parsed_parser = key.get_name(); + return {key, it->second->parse(consumer)}; } - std::vector> argument_subparser_t::get_allowed_strings() const + hashmap_t> argument_subparser_t::get_allowed_strings() const + { + hashmap_t> map; + for (const auto& [key, value] : m_aliases) + map[value].emplace_back(key); + return map; + } + + std::vector> argument_subparser_t::to_vec(const hashmap_t>& map) { std::vector> vec; - for (const auto& [key, value] : m_parsers) - { - std::vector aliases; - aliases.push_back(key); - for (const auto& [alias, parser] : m_aliases) - { - if (parser == &value) - aliases.push_back(alias); - } - vec.emplace_back(std::move(aliases)); - } + for (const auto& [key, value] : map) + vec.push_back(value); return vec; }