diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ea6c6f..43e57f2 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.23) +set(BLT_VERSION 4.0.24) set(BLT_TARGET BLT) diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index 4eac0ff..1f42898 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -432,10 +431,8 @@ namespace blt::argparse { auto& data = storage.m_data[dest]; if (!std::holds_alternative>(data)) - { throw detail::type_error("Invalid type conversion. Trying to add type " + blt::type_string() + " but this does not match existing type index '" + std::to_string(data.index()) + "'!"); - } auto& converted_values = std::get>(data); for (const auto& value : values) converted_values.push_back(detail::arg_string_converter_t::convert(value)); @@ -456,41 +453,7 @@ namespace blt::argparse return set_action(action_t::STORE_TRUE); } - argument_builder_t& set_action(const action_t action) - { - m_action = action; - switch (m_action) - { - case action_t::STORE_TRUE: - set_nargs(0); - as_type(); - set_default(false); - break; - case action_t::STORE_FALSE: - set_nargs(0); - as_type(); - set_default(true); - break; - case action_t::STORE_CONST: - case action_t::APPEND_CONST: - set_nargs(0); - break; - case action_t::COUNT: - set_nargs(0); - as_type(); - break; - case action_t::EXTEND: - set_nargs(nargs_t::ALL); - break; - case action_t::HELP: - case action_t::VERSION: - set_nargs(0); - break; - default: - break; - } - return *this; - } + argument_builder_t& set_action(action_t action); argument_builder_t& set_required(const bool required) { @@ -602,9 +565,10 @@ namespace blt::argparse friend argument_subparser_t; public: - explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, - const std::optional description = {}, const std::optional epilogue = {}): - m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) + explicit argument_parser_t(const std::optional description = {}, const std::optional epilogue = {}, + const std::optional version = {}, + const std::optional usage = {}, const std::optional name = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue), m_version(version) { } @@ -633,6 +597,18 @@ namespace blt::argparse argument_subparser_t& add_subparser(std::string_view dest); + argument_parser_t& with_help() + { + add_flag("--help", "-h").set_action(action_t::HELP); + return *this; + } + + argument_parser_t& with_version() + { + add_flag("--version").set_action(action_t::VERSION); + return *this; + } + argument_storage_t parse(argument_consumer_t& consumer); // NOLINT argument_storage_t parse(const std::vector& args) @@ -669,15 +645,15 @@ namespace blt::argparse void print_usage(); - void print_version(); + void print_version() const; - argument_parser_t& set_name(const std::string_view name) + argument_parser_t& set_name(const std::optional& name) { m_name = name; return *this; } - argument_parser_t& set_usage(const std::string_view usage) + argument_parser_t& set_usage(const std::optional& usage) { m_usage = usage; return *this; @@ -688,7 +664,7 @@ namespace blt::argparse return m_usage; } - argument_parser_t& set_description(const std::string_view description) + argument_parser_t& set_description(const std::optional& description) { m_description = description; return *this; @@ -699,7 +675,7 @@ namespace blt::argparse return m_description; } - argument_parser_t& set_epilogue(const std::string_view epilogue) + argument_parser_t& set_epilogue(const std::optional& epilogue) { m_epilogue = epilogue; return *this; @@ -710,6 +686,17 @@ namespace blt::argparse return m_epilogue; } + argument_parser_t& set_version(const std::optional& version) + { + m_epilogue = version; + return *this; + } + + [[nodiscard]] const std::optional& get_version() const + { + return m_version; + } + [[nodiscard]] const hashset_t& get_allowed_flag_prefixes() const { return allowed_flag_prefixes; @@ -732,6 +719,7 @@ namespace blt::argparse std::optional m_usage; std::optional m_description; std::optional m_epilogue; + std::optional m_version; std::vector> m_subparsers; std::vector> m_argument_builders; hashmap_t m_flag_arguments; diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index b4fd55f..058495b 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace blt::argparse { @@ -89,6 +91,60 @@ namespace blt::argparse return std::vector{"./program", strings...}; } + template + void add(std::string& out, T&& value, size_t line_size = 60) + { + const auto lines = string::split(out, '\n'); + auto str = ensure_is_string(std::forward(value)); + if (lines.empty()) + { + out = str; + return; + } + if (lines.back().size() + str.size() > line_size) + { + out += '\n'; + out += '\t'; + } + out += str; + } + + argument_builder_t& argument_builder_t::set_action(const action_t action) + { + m_action = action; + switch (m_action) + { + case action_t::STORE_TRUE: + set_nargs(0); + as_type(); + set_default(false); + break; + case action_t::STORE_FALSE: + set_nargs(0); + as_type(); + set_default(true); + break; + case action_t::STORE_CONST: + case action_t::APPEND_CONST: + set_nargs(0); + break; + case action_t::COUNT: + set_nargs(0); + as_type(); + break; + case action_t::EXTEND: + set_nargs(nargs_t::ALL); + break; + case action_t::HELP: + case action_t::VERSION: + set_nargs(0); + break; + default: + break; + } + return *this; + } + argument_subparser_t& argument_parser_t::add_subparser(const std::string_view dest) { m_subparsers.emplace_back(dest, argument_subparser_t{*this}); @@ -98,7 +154,7 @@ namespace blt::argparse argument_storage_t argument_parser_t::parse(argument_consumer_t& consumer) { if (!m_name) - m_name = consumer.absolute_first().get_argument(); + m_name = fs::base_name_sv(consumer.absolute_first().get_argument()); argument_positional_storage_t positional_storage{m_positional_arguments}; hashset_t found_flags; argument_storage_t parsed_args; @@ -146,14 +202,105 @@ namespace blt::argparse void argument_parser_t::print_help() { + print_usage(); + std::cout << std::endl; + std::string help; + if (!m_flag_arguments.empty()) + { + help += "Options:\n"; + hashmap_t> same_flags; + for (const auto& [key, value] : m_flag_arguments) + same_flags[value].emplace_back(key); + for (const auto& [builder, flag_list] : same_flags) + { + // find max size and algin? + add(help, '\t'); + for (const auto& [i, flag] : enumerate(flag_list)) + { + add(help, flag); + if (i != flag_list.size() - 1) + add(help, ", "); + } + } + } } void argument_parser_t::print_usage() { + if (!m_usage) + { + std::string usage = m_name.value_or(""); + + hashmap_t> singleFlags; + std::vector> compoundFlags; + + for (const auto& [key, value] : m_flag_arguments) + { + argument_string_t arg{key, allowed_flag_prefixes}; + if (arg.get_flag().size() == 1) + { + if (std::holds_alternative(value->m_nargs) && std::get(value->m_nargs) == 0) + singleFlags[arg.get_flag()].emplace_back(arg.get_name()); + else + compoundFlags.emplace_back(arg, value); + } else + compoundFlags.emplace_back(arg, value); + } + + for (const auto& [i, kv] : enumerate(singleFlags)) + { + const auto& [key, value] = kv; + add(usage, "["); + add(usage, key); + for (const auto& name : value) + add(usage, name); + add(usage, "]"); + if (i != singleFlags.size() - 1) + add(usage, " "); + } + + for (const auto& [i, kv] : enumerate(compoundFlags)) + { + const auto& [name, builder] = kv; + add(usage, "["); + add(usage, name.get_argument()); + auto lambda = [&]() + { + add(usage, " "); + add(usage, builder->m_metavar.value_or(string::toUpperCase(name.get_name()))); + }; + std::visit(lambda_visitor{[&](const nargs_t) + { + lambda(); + }, [&](const int argc) + { + if (argc == 0) + return; + lambda(); + }}, builder->m_nargs); + add(usage, "]"); + if (i != compoundFlags.size() - 1) + add(usage, " "); + } + + for (const auto& [i, pair] : enumerate(m_positional_arguments)) + { + const auto& [name, _] = pair; + add(usage, "<"); + add(usage, name); + add(usage, ">"); + if (i != m_positional_arguments.size() - 1) + add(usage, " "); + } + + m_usage = usage; + } + std::cout << "Usage: " << *m_usage << std::endl; } - void argument_parser_t::print_version() + void argument_parser_t::print_version() const { + std::cout << m_name.value_or("NO NAME") << " " << m_version.value_or("NO VERSION") << std::endl; } void argument_parser_t::handle_compound_flags(hashset_t& found_flags, argument_storage_t& parsed_args,