From 55b28fb58bd3aa7603b672190a61f44a6c900455 Mon Sep 17 00:00:00 2001 From: tqcq <99722391+tqcq@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:03:29 +0800 Subject: [PATCH] feat add lyra --- 3party/lyra/lyra/arg.hpp | 113 +++ 3party/lyra/lyra/args.hpp | 68 ++ 3party/lyra/lyra/arguments.hpp | 527 ++++++++++++++ 3party/lyra/lyra/cli.hpp | 376 ++++++++++ 3party/lyra/lyra/cli_parser.hpp | 17 + 3party/lyra/lyra/command.hpp | 166 +++++ 3party/lyra/lyra/detail/bound.hpp | 193 ++++++ 3party/lyra/lyra/detail/choices.hpp | 149 ++++ .../deprecated_parser_customization.hpp | 63 ++ 3party/lyra/lyra/detail/from_string.hpp | 196 ++++++ 3party/lyra/lyra/detail/invoke_lambda.hpp | 51 ++ 3party/lyra/lyra/detail/parse.hpp | 29 + 3party/lyra/lyra/detail/print.hpp | 79 +++ 3party/lyra/lyra/detail/result.hpp | 165 +++++ 3party/lyra/lyra/detail/tokens.hpp | 295 ++++++++ 3party/lyra/lyra/detail/trait_utils.hpp | 74 ++ .../lyra/lyra/detail/unary_lambda_traits.hpp | 37 + 3party/lyra/lyra/exe_name.hpp | 155 +++++ 3party/lyra/lyra/group.hpp | 214 ++++++ 3party/lyra/lyra/help.hpp | 78 +++ 3party/lyra/lyra/literal.hpp | 149 ++++ 3party/lyra/lyra/lyra.hpp | 28 + 3party/lyra/lyra/main.hpp | 244 +++++++ 3party/lyra/lyra/opt.hpp | 372 ++++++++++ 3party/lyra/lyra/option_style.hpp | 169 +++++ 3party/lyra/lyra/parser.hpp | 647 ++++++++++++++++++ 3party/lyra/lyra/parser_result.hpp | 41 ++ 3party/lyra/lyra/val.hpp | 44 ++ 3party/lyra/lyra/version.hpp | 17 + CMakeLists.txt | 3 +- 30 files changed, 4758 insertions(+), 1 deletion(-) create mode 100644 3party/lyra/lyra/arg.hpp create mode 100644 3party/lyra/lyra/args.hpp create mode 100644 3party/lyra/lyra/arguments.hpp create mode 100644 3party/lyra/lyra/cli.hpp create mode 100644 3party/lyra/lyra/cli_parser.hpp create mode 100644 3party/lyra/lyra/command.hpp create mode 100644 3party/lyra/lyra/detail/bound.hpp create mode 100644 3party/lyra/lyra/detail/choices.hpp create mode 100644 3party/lyra/lyra/detail/deprecated_parser_customization.hpp create mode 100644 3party/lyra/lyra/detail/from_string.hpp create mode 100644 3party/lyra/lyra/detail/invoke_lambda.hpp create mode 100644 3party/lyra/lyra/detail/parse.hpp create mode 100644 3party/lyra/lyra/detail/print.hpp create mode 100644 3party/lyra/lyra/detail/result.hpp create mode 100644 3party/lyra/lyra/detail/tokens.hpp create mode 100644 3party/lyra/lyra/detail/trait_utils.hpp create mode 100644 3party/lyra/lyra/detail/unary_lambda_traits.hpp create mode 100644 3party/lyra/lyra/exe_name.hpp create mode 100644 3party/lyra/lyra/group.hpp create mode 100644 3party/lyra/lyra/help.hpp create mode 100644 3party/lyra/lyra/literal.hpp create mode 100644 3party/lyra/lyra/lyra.hpp create mode 100644 3party/lyra/lyra/main.hpp create mode 100644 3party/lyra/lyra/opt.hpp create mode 100644 3party/lyra/lyra/option_style.hpp create mode 100644 3party/lyra/lyra/parser.hpp create mode 100644 3party/lyra/lyra/parser_result.hpp create mode 100644 3party/lyra/lyra/val.hpp create mode 100644 3party/lyra/lyra/version.hpp diff --git a/3party/lyra/lyra/arg.hpp b/3party/lyra/lyra/arg.hpp new file mode 100644 index 0000000..c41bdab --- /dev/null +++ b/3party/lyra/lyra/arg.hpp @@ -0,0 +1,113 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_ARG_HPP +#define LYRA_ARG_HPP + +#include "lyra/detail/print.hpp" +#include "lyra/parser.hpp" + +namespace lyra { + +/* tag::reference[] + +[#lyra_arg] += `lyra::arg` + +A parser for regular arguments, i.e. not `--` or `-` prefixed. This is simply +a way to get values of arguments directly specified in the cli. + +Is-a <>. + +*/ // end::reference[] +class arg : public bound_parser +{ + public: + using bound_parser::bound_parser; + + virtual std::string get_usage_text(const option_style &) const override + { + std::ostringstream oss; + if (!m_hint.empty()) + { + auto c = cardinality(); + if (c.is_required()) + { + for (size_t i = 0; i < c.minimum; ++i) + oss << (i > 0 ? " " : "") << "<" << m_hint << ">"; + if (c.is_unbounded()) + oss << (c.is_required() ? " " : "") << "[<" << m_hint + << ">...]"; + } + else if (c.is_unbounded()) + { + oss << "[<" << m_hint << ">...]"; + } + else + { + oss << "<" << m_hint << ">"; + } + } + return oss.str(); + } + + virtual help_text get_help_text(const option_style & style) const override + { + return { { get_usage_text(style), m_description } }; + } + + using parser::parse; + + parse_result parse(detail::token_iterator const & tokens, + const option_style & style) const override + { + (void)style; + LYRA_PRINT_SCOPE("arg::parse"); + auto validationResult = validate(); + if (!validationResult) return parse_result(validationResult); + + if (!tokens) + { + // Nothing to match against. + return parse_result::ok( + detail::parse_state(parser_result_type::no_match, tokens)); + } + + auto const & token = tokens.argument(); + + auto valueRef = static_cast(m_ref.get()); + + if (value_choices) + { + auto choice_result = value_choices->contains_value(token.name); + if (!choice_result) + { + LYRA_PRINT_DEBUG( + "(!)", get_usage_text(style), "!=", token.name); + return parse_result(choice_result); + } + } + + auto set_result = valueRef->setValue(token.name); + if (!set_result) + { + LYRA_PRINT_DEBUG("(!)", get_usage_text(style), "!=", token.name); + return parse_result(set_result); + } + else + { + LYRA_PRINT_DEBUG("(=)", get_usage_text(style), "==", token.name); + auto remainingTokens = tokens; + remainingTokens.pop(token); + return parse_result::ok(detail::parse_state( + parser_result_type::matched, remainingTokens)); + } + } +}; + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/args.hpp b/3party/lyra/lyra/args.hpp new file mode 100644 index 0000000..f643f90 --- /dev/null +++ b/3party/lyra/lyra/args.hpp @@ -0,0 +1,68 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_ARGS_HPP +#define LYRA_ARGS_HPP + +#include +#include +#include + +namespace lyra { + +/* tag::reference[] + +[#lyra_args] += `lyra::args` + +Transport for raw args (copied from main args, supplied via init list, or from +a pair of iterators). + +*/ // end::reference[] +class args +{ + public: + // Construct from usual main() function arguments. + args(int argc, char const * const * argv) + : m_exeName(argv[0]) + , m_args(argv + 1, argv + argc) + {} + + // Construct directly from an initializer '{}'. + args(std::initializer_list args_list) + : m_exeName(*args_list.begin()) + , m_args(args_list.begin() + 1, args_list.end()) + {} + + // Construct from iterators. + template + args(const It & start, const It & end) + : m_exeName(*start) + , m_args(start + 1, end) + {} + + // The executable name taken from argument zero. + std::string exe_name() const { return m_exeName; } + + // Arguments, excluding the exe name. + std::vector::const_iterator begin() const + { + return m_args.begin(); + } + + std::vector::const_iterator end() const + { + return m_args.end(); + } + + private: + std::string m_exeName; + std::vector m_args; +}; + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/arguments.hpp b/3party/lyra/lyra/arguments.hpp new file mode 100644 index 0000000..1cedc98 --- /dev/null +++ b/3party/lyra/lyra/arguments.hpp @@ -0,0 +1,527 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_ARGUMENTS_HPP +#define LYRA_ARGUMENTS_HPP + +#include "lyra/detail/print.hpp" +#include "lyra/exe_name.hpp" +#include "lyra/parser.hpp" + +#include +#include + +namespace lyra { + +/* tag::reference[] + +[#lyra_arguments] += `lyra::arguments` + +A Combined parser made up of any number of parsers. Creating and using +one of these as a basis one can incrementally compose other parsers into this +one. For example: + +[source] +---- +auto p = lyra::arguments(); +std::string what; +float when = 0; +std::string where; +p |= lyra::opt(what, "what")["--make-it-so"]("Make it so.").required(); +p |= lyra::opt(when. "when")["--time"]("When to do .").optional(); +p.add_argument(lyra::opt(where, "where").name("--where") + .help("There you are.").optional()); +---- + +*/ // end::reference[] +class arguments : public parser +{ + public: + // How to evaluate the collection of arguments within the limits of the + // cardinality. + enum evaluation + { + // Any of the arguments, in any order, are valid. I.e. an inclusive-or. + any = 0, + // All arguments, in sequence, matched. I.e. conjunctive-and. + sequence = 1 + }; + + arguments() = default; + + arguments(evaluation e) + : eval_mode(e) + {} + + // Copy construction, needs to copy the the composed parsers. + arguments(const arguments & other); + + // Compose a regular parser. + arguments & add_argument(parser const & p); + arguments & operator|=(parser const & p); + + // Compose the parsers from another `arguments`. + arguments & add_argument(arguments const & other); + arguments & operator|=(arguments const & other); + + // Concat composition. + template + arguments operator|(T const & other) const + { + return arguments(*this) |= other; + } + + // Parsing mode. + arguments & sequential(); + arguments & inclusive(); + + // Access. + template + T & get(size_t i); + + // Internal.. + + virtual std::string get_usage_text( + const option_style & style) const override + { + std::ostringstream os; + for (auto const & p : parsers) + { + std::string usage_text = p->get_usage_text(style); + if (usage_text.size() > 0) + { + if (os.tellp() != std::ostringstream::pos_type(0)) os << " "; + if (p->is_group() && p->is_optional()) + os << "[ " << usage_text << " ]"; + else if (p->is_group()) + os << "{ " << usage_text << " }"; + else if (p->is_optional()) + os << "[" << usage_text << "]"; + else + os << usage_text; + } + } + return os.str(); + } + + virtual std::string get_description_text( + const option_style & style) const override + { + std::ostringstream os; + for (auto const & p : parsers) + { + if (p->is_group()) continue; + auto child_description = p->get_description_text(style); + if (!child_description.empty()) os << child_description << "\n"; + } + return os.str(); + } + + // Return a container of the individual help text for the composed parsers. + virtual help_text get_help_text(const option_style & style) const override + { + help_text text; + for (auto const & p : parsers) + { + if (p->is_group()) text.push_back({ "", "" }); + auto child_help = p->get_help_text(style); + text.insert(text.end(), child_help.begin(), child_help.end()); + } + return text; + } + + virtual detail::parser_cardinality cardinality() const override + { + return { 0, 0 }; + } + + virtual result validate() const override + { + for (auto const & p : parsers) + { + auto parse_valid = p->validate(); + if (!parse_valid) return parse_valid; + } + return result::ok(); + } + + parse_result parse(detail::token_iterator const & tokens, + const option_style & style) const override + { + switch (eval_mode) + { + case any: return parse_any(tokens, style); + case sequence: return parse_sequence(tokens, style); + } + return parse_result::error( + detail::parse_state(parser_result_type::no_match, tokens), + "Unknown evaluation mode; not one of 'any', or 'sequence'."); + } + + // Match in any order, any number of times. Returns an error if nothing + // matched. + parse_result parse_any( + detail::token_iterator const & tokens, const option_style & style) const + { + LYRA_PRINT_SCOPE("arguments::parse_any"); + LYRA_PRINT_DEBUG("(?)", get_usage_text(style), + "?=", tokens ? tokens.argument().name : "", ".."); + + struct ParserInfo + { + parser const * parser_p = nullptr; + size_t count = 0; + }; + std::vector parser_info(parsers.size()); + { + size_t i = 0; + for (auto const & p : parsers) parser_info[i++].parser_p = p.get(); + } + + auto p_result = parse_result::ok( + detail::parse_state(parser_result_type::matched, tokens)); + auto error_result = parse_result::ok( + detail::parse_state(parser_result_type::no_match, tokens)); + while (p_result.value().remainingTokens()) + { + bool token_parsed = false; + + for (auto & parse_info : parser_info) + { + auto parser_cardinality = parse_info.parser_p->cardinality(); + if (parser_cardinality.is_unbounded() + || parse_info.count < parser_cardinality.maximum) + { + auto subparse_result = parse_info.parser_p->parse( + p_result.value().remainingTokens(), style); + if (!subparse_result) + { + LYRA_PRINT_DEBUG("(!)", get_usage_text(style), "!=", + p_result.value().remainingTokens().argument().name); + // Is the subparse error bad enough to trigger an + // immediate return? For example for an option syntax + // error. + if (subparse_result.has_value() + && subparse_result.value().type() + == parser_result_type::short_circuit_all) + return subparse_result; + // For not severe errors, we save the error if it's + // the first so that in case no other parsers match + // we can report the earliest problem, as that's + // the likeliest issue. + if (error_result) + error_result = parse_result(subparse_result); + } + else if (subparse_result + && subparse_result.value().type() + != parser_result_type::no_match) + { + LYRA_PRINT_DEBUG("(=)", get_usage_text(style), "==", + p_result.value().remainingTokens().argument().name, + "==>", subparse_result.value().type()); + p_result = parse_result(subparse_result); + token_parsed = true; + parse_info.count += 1; + break; + } + } + } + + if (p_result.value().type() == parser_result_type::short_circuit_all) + return p_result; + // If something signaled and error, and hence we didn't match/parse + // anything, we indicate the error. + if (!token_parsed && !error_result) return error_result; + if (!token_parsed) break; + } + // Check missing required options. For bounded arguments we check + // bound min and max bounds against what we parsed. For the loosest + // required arguments we check for only the minimum. As the upper + // bound could be infinite. + for (auto & parseInfo : parser_info) + { + auto parser_cardinality = parseInfo.parser_p->cardinality(); + if ((parser_cardinality.is_bounded() + && (parseInfo.count < parser_cardinality.minimum + || parser_cardinality.maximum < parseInfo.count)) + || (parser_cardinality.is_required() + && (parseInfo.count < parser_cardinality.minimum))) + { + return parse_result::error(p_result.value(), + "Expected: " + parseInfo.parser_p->get_usage_text(style)); + } + } + return p_result; + } + + parse_result parse_sequence( + detail::token_iterator const & tokens, const option_style & style) const + { + LYRA_PRINT_SCOPE("arguments::parse_sequence"); + LYRA_PRINT_DEBUG("(?)", get_usage_text(style), + "?=", tokens ? tokens.argument().name : "", ".."); + + struct ParserInfo + { + parser const * parser_p = nullptr; + size_t count = 0; + }; + std::vector parser_info(parsers.size()); + { + size_t i = 0; + for (auto const & p : parsers) parser_info[i++].parser_p = p.get(); + } + + auto p_result = parse_result::ok( + detail::parse_state(parser_result_type::matched, tokens)); + + // Sequential parsing means we walk through the given parsers in order + // and exhaust the tokens as we match persers. + for (std::size_t parser_i = 0; parser_i < parsers.size(); ++parser_i) + { + auto & parse_info = parser_info[parser_i]; + auto parser_cardinality = parse_info.parser_p->cardinality(); + // This is a greedy sequential parsing algo. As it parsers the + // current argument as much as possible. + do + { + auto subresult = parse_info.parser_p->parse( + p_result.value().remainingTokens(), style); + if (!subresult) + { + break; + } + if (subresult.value().type() + == parser_result_type::short_circuit_all) + { + return subresult; + } + if (subresult.value().type() != parser_result_type::no_match) + { + LYRA_PRINT_DEBUG("(=)", get_usage_text(style), "==", + p_result.value().remainingTokens() + ? p_result.value().remainingTokens().argument().name + : "", + "==>", subresult.value().type()); + p_result = subresult; + parse_info.count += 1; + } + } + while (p_result.value().have_tokens() + && (parser_cardinality.is_unbounded() + || parse_info.count < parser_cardinality.maximum)); + // Check missing required options immediately as for sequential the + // argument is greedy and will fully match here. For bounded + // arguments we check bound min and max bounds against what we + // parsed. For the loosest required arguments we check for only the + // minimum. As the upper bound could be infinite. + if ((parser_cardinality.is_bounded() + && (parse_info.count < parser_cardinality.minimum + || parser_cardinality.maximum < parse_info.count)) + || (parser_cardinality.is_required() + && (parse_info.count < parser_cardinality.minimum))) + { + return parse_result::error(p_result.value(), + "Expected: " + parse_info.parser_p->get_usage_text(style)); + } + } + // The return is just the last state as it contains any remaining tokens + // to parse. + return p_result; + } + + virtual std::unique_ptr clone() const override + { + return make_clone(this); + } + + friend std::ostream & operator<<( + std::ostream & os, arguments const & parser) + { + const option_style & s + = parser.opt_style ? *parser.opt_style : option_style::posix(); + parser.print_help_text(os, s); + return os; + } + + virtual const parser * get_named(const std::string & n) const override + { + for (auto & p : parsers) + { + const parser * p_result = p->get_named(n); + if (p_result) return p_result; + } + return nullptr; + } + + protected: + std::shared_ptr opt_style; + + private: + std::vector> parsers; + evaluation eval_mode = any; +}; + +/* tag::reference[] + +[#lyra_arguments_ctor] +== Construction + +end::reference[] */ + +/* tag::reference[] + +[#lyra_arguments_ctor_default] +=== Default + +[source] +---- +arguments() = default; +---- + +Default constructing a `arguments` is the starting point to adding arguments +and options for parsing a arguments line. + +end::reference[] */ + +/* tag::reference[] + +[#lyra_arguments_ctor_copy] +=== Copy + +[source] +---- +arguments::arguments(const arguments& other); +---- + +end::reference[] */ +inline arguments::arguments(const arguments & other) + : parser(other) + , opt_style(other.opt_style) + , eval_mode(other.eval_mode) +{ + for (auto & other_parser : other.parsers) + { + parsers.push_back(other_parser->clone()); + } +} + +/* tag::reference[] + +[#lyra_arguments_specification] +== Specification + +end::reference[] */ + +// == + +/* tag::reference[] +[#lyra_arguments_add_argument] +=== `lyra::arguments::add_argument` + +[source] +---- +arguments& arguments::add_argument(parser const& p); +arguments& arguments::operator|=(parser const& p); +arguments& arguments::add_argument(arguments const& other); +arguments& arguments::operator|=(arguments const& other); +---- + +Adds the given argument parser to the considered arguments for this +`arguments`. Depending on the parser given it will be: directly added as an +argument (for `parser`), or add the parsers from another `arguments` to +this one. + +end::reference[] */ +inline arguments & arguments::add_argument(parser const & p) +{ + parsers.push_back(p.clone()); + return *this; +} +inline arguments & arguments::operator|=(parser const & p) +{ + return this->add_argument(p); +} +inline arguments & arguments::add_argument(arguments const & other) +{ + if (other.is_group()) + { + parsers.push_back(other.clone()); + } + else + { + for (auto & p : other.parsers) + { + parsers.push_back(p->clone()); + } + } + return *this; +} +inline arguments & arguments::operator|=(arguments const & other) +{ + return this->add_argument(other); +} + +/* tag::reference[] +=== `lyra::arguments::sequential` + +[source] +---- +arguments & arguments::sequential(); +---- + +Sets the parsing mode for the arguments to "sequential". When parsing the +arguments they will be, greedily, consumed in the order they where added. +This is useful for sub-commands and structured command lines. + +end::reference[] */ +inline arguments & arguments::sequential() +{ + eval_mode = sequence; + return *this; +} + +/* tag::reference[] +=== `lyra::arguments::inclusive` + +[source] +---- +arguments & arguments::inclusive(); +---- + +Sets the parsing mode for the arguments to "inclusively any". This is the +default that attempts to match each parsed argument with all the available +parsers. This means that there is no ordering enforced. + +end::reference[] */ +inline arguments & arguments::inclusive() +{ + eval_mode = any; + return *this; +} + +/* tag::reference[] +=== `lyra::arguments::get` + +[source] +---- +template +T & arguments::get(size_t i); +---- + +Get a modifyable reference to one of the parsers specified. + +end::reference[] */ +template +T & arguments::get(size_t i) +{ + return static_cast(*parsers.at(i)); +} + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/cli.hpp b/3party/lyra/lyra/cli.hpp new file mode 100644 index 0000000..447fff4 --- /dev/null +++ b/3party/lyra/lyra/cli.hpp @@ -0,0 +1,376 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_CLI_HPP +#define LYRA_CLI_HPP + +#include "lyra/arguments.hpp" +#include "lyra/detail/deprecated_parser_customization.hpp" +#include "lyra/detail/from_string.hpp" +#include "lyra/detail/print.hpp" +#include "lyra/exe_name.hpp" +#include "lyra/group.hpp" +#include "lyra/option_style.hpp" + +#include + +namespace lyra { + +/* tag::reference[] + +[#lyra_cli] += `lyra::cli` + +A Combined parser made up of any two or more other parsers. Creating and using +one of these as a basis one can incrementally compose other parsers into this +one. For example: + +[source] +---- +auto cli = lyra::cli(); +std::string what; +float when = 0; +std::string where; +cli |= lyra::opt(what, "what")["--make-it-so"]("Make it so.").required(); +cli |= lyra::opt(when. "when")["--time"]("When to do .").optional(); +cli.add_argument(lyra::opt(where, "where").name("--where") + .help("There you are.").optional()); +---- + +*/ // end::reference[] +class cli : protected arguments +{ + public: + cli() = default; + + // Copy construction, needs to copy the exe name and the composed parsers. + cli(const cli & other); + + // Compose the `exe_name` parser. + cli & add_argument(exe_name const & exe_name); + cli & operator|=(exe_name const & exe_name); + + // Compose a regular parser. + cli & add_argument(parser const & p); + cli & operator|=(parser const & p); + + // Compose a group, by adding it as a single argument. + cli & add_argument(group const & p); + cli & operator|=(group const & p); + + // Compose the parsers from another `cli`. + cli & add_argument(cli const & other); + cli & operator|=(cli const & other); + + // Concat composition. + template + cli operator|(T const & other) const; + + // Result reference wrapper to fetch and convert argument. + struct value_result + { + public: + explicit value_result(const parser * p) + : parser_ref(p) + {} + + template ::type>::value>:: + type * = nullptr> + operator T() const + { + typename detail::remove_cvref::type converted_value {}; + if (parser_ref) + detail::from_string::type>( + parser_ref->get_value(0), converted_value); + return converted_value; + } + + template + operator std::vector() const + { + std::vector converted_value; + if (parser_ref) + { + for (size_t i = 0; i < parser_ref->get_value_count(); ++i) + { + T v; + if (detail::from_string(parser_ref->get_value(i), v)) + converted_value.push_back(v); + } + } + return converted_value; + } + + operator std::string() const + { + if (parser_ref) return parser_ref->get_value(0); + return ""; + } + + private: + const parser * parser_ref = nullptr; + }; + + value_result operator[](const std::string & n); + + cli & style(const option_style & style); + cli & style(option_style && style); + + // Stream out generates the help output. + friend std::ostream & operator<<(std::ostream & os, cli const & parser) + { + return os << static_cast(parser); + } + + // Parse from arguments. + parse_result parse(args const & args) const + { + if (opt_style) + return parse(args, *opt_style); + else + return parse(args, option_style::posix()); + } + parse_result parse(args const & args, const option_style & style) const; + + // Backward compatability parse() that takes `parser_customization` and + // converts to `option_style`. + [[deprecated]] parse_result parse( + args const & args, const parser_customization & customize) const + { + return this->parse(args, + option_style(customize.token_delimiters(), + customize.option_prefix(), 2, customize.option_prefix(), 1)); + } + + // Internal.. + + using arguments::parse; + using arguments::get_named; + + virtual std::unique_ptr clone() const override + { + return std::unique_ptr(new cli(*this)); + } + + protected: + mutable exe_name m_exeName; + + virtual std::string get_usage_text( + const option_style & style) const override + { + if (!m_exeName.name().empty()) + return m_exeName.name() + " " + arguments::get_usage_text(style); + else + // We use an empty exe name as an indicator to remove USAGE text. + return ""; + } +}; + +/* tag::reference[] + +[#lyra_cli_ctor] +== Construction + +end::reference[] */ + +/* tag::reference[] + +[#lyra_cli_ctor_default] +=== Default + +[source] +---- +cli() = default; +---- + +Default constructing a `cli` is the starting point to adding arguments +and options for parsing a command line. + +end::reference[] */ + +/* tag::reference[] + +[#lyra_cli_ctor_copy] +=== Copy + +[source] +---- +cli::cli(const cli& other); +---- + +end::reference[] */ +inline cli::cli(const cli & other) + : arguments(other) + , m_exeName(other.m_exeName) +{} + +/* tag::reference[] + +[#lyra_cli_specification] +== Specification + +end::reference[] */ + +// == + +/* tag::reference[] +[#lyra_cli_add_argument] +=== `lyra::cli::add_argument` + +[source] +---- +cli& cli::add_argument(exe_name const& exe_name); +cli& cli::operator|=(exe_name const& exe_name); +cli& cli::add_argument(parser const& p); +cli& cli::operator|=(parser const& p); +cli& cli::add_argument(group const& p); +cli& cli::operator|=(group const& p); +cli& cli::add_argument(cli const& other); +cli& cli::operator|=(cli const& other); +---- + +Adds the given argument parser to the considered arguments for this +`cli`. Depending on the parser given it will be: recorded as the exe +name (for `exe_name` parser), directly added as an argument (for +`parser`), or add the parsers from another `cli` to this one. + +end::reference[] */ +inline cli & cli::add_argument(exe_name const & exe_name) +{ + m_exeName = exe_name; + return *this; +} +inline cli & cli::operator|=(exe_name const & exe_name) +{ + return this->add_argument(exe_name); +} +inline cli & cli::add_argument(parser const & p) +{ + arguments::add_argument(p); + return *this; +} +inline cli & cli::operator|=(parser const & p) +{ + arguments::add_argument(p); + return *this; +} +inline cli & cli::add_argument(group const & other) +{ + arguments::add_argument(static_cast(other)); + return *this; +} +inline cli & cli::operator|=(group const & other) +{ + return this->add_argument(other); +} +inline cli & cli::add_argument(cli const & other) +{ + arguments::add_argument(static_cast(other)); + return *this; +} +inline cli & cli::operator|=(cli const & other) +{ + return this->add_argument(other); +} + +template +inline cli cli::operator|(T const & other) const +{ + return cli(*this).add_argument(other); +} + +template +cli operator|(composable_parser const & thing, T const & other) +{ + return cli() | static_cast(thing) | other; +} + +/* tag::reference[] +[#lyra_cli_array_ref] +=== `lyra::cli::operator[]` + +[source] +---- +cli::value_result cli::operator[](const std::string & n) +---- + +Finds the given argument by either option name or hint name and returns a +convertible reference to the value, either the one provided by the user or the +default. + +end::reference[] */ +inline cli::value_result cli::operator[](const std::string & n) +{ + return value_result(this->get_named(n)); +} + +/* tag::reference[] +[#lyra_cli_parse] +=== `lyra::cli::parse` + +[source] +---- +parse_result cli::parse( + args const& args, const option_style& customize) const; +---- + +Parses given arguments `args` and optional option style. +The result indicates success or failure, and if failure what kind of failure +it was. The state of variables bound to options is unspecified and any bound +callbacks may have been called. + +end::reference[] */ +inline parse_result cli::parse( + args const & args, const option_style & style) const +{ + LYRA_PRINT_SCOPE("cli::parse"); + m_exeName.set(args.exe_name()); + detail::token_iterator args_tokens(args, style); + parse_result p_result = parse(args_tokens, style); + if (p_result + && (p_result.value().type() == parser_result_type::no_match + || p_result.value().type() == parser_result_type::matched)) + { + if (p_result.value().have_tokens()) + { + return parse_result::error(p_result.value(), + "Unrecognized token: " + + p_result.value().remainingTokens().argument().name); + } + } + return p_result; +} + +/* tag::reference[] +[#lyra_cli_style] +=== `lyra::cli::style` + +[source] +---- +lyra::cli & lyra::cli::style(const lyra::option_style & style) +lyra::cli & lyra::cli::style(lyra::option_style && style) +---- + +Specifies the <> to accept for this instance. + +end::reference[] */ +inline cli & cli::style(const option_style & style) +{ + opt_style = std::make_shared(style); + return *this; +} +inline cli & cli::style(option_style && style) +{ + opt_style = std::make_shared(std::move(style)); + return *this; +} + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/cli_parser.hpp b/3party/lyra/lyra/cli_parser.hpp new file mode 100644 index 0000000..acd0bb2 --- /dev/null +++ b/3party/lyra/lyra/cli_parser.hpp @@ -0,0 +1,17 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_CLI_PARSER_HPP +#define LYRA_CLI_PARSER_HPP + +#include "lyra/cli.hpp" + +namespace lyra { + +using cli_parser = cli; + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/command.hpp b/3party/lyra/lyra/command.hpp new file mode 100644 index 0000000..536f99e --- /dev/null +++ b/3party/lyra/lyra/command.hpp @@ -0,0 +1,166 @@ +// Copyright 2020-2022 René Ferdinand Rivera Morell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_COMMAND_HPP +#define LYRA_COMMAND_HPP + +#include "lyra/group.hpp" +#include "lyra/literal.hpp" +#include +#include + +namespace lyra { + +/* tag::reference[] + +[#lyra_command] += `lyra::command` + +A parser that encapsulates the pattern of parsing sub-commands. It provides a +quick wrapper for the equivalent arrangement of `group` and `literal` parsers. +For example: + +[source] +---- +lyra::command c = lyra::command("sub"); +---- + +Is equivalent to: + +[source] +---- +lyra::command c = lyra::group() + .sequential() + .add_argument(literal("sub")) + .add_argument(group()); +lyra::group & g = c.get(1); +---- + +I.e. it's conposed of a `literal` followed by the rest of the command arguments. + +Is-a <>. + +*/ // end::reference[] +class command : public group +{ + public: + // Construction, with and without, callback. + explicit command(const std::string & n); + command( + const std::string & n, const std::function & f); + + // Help description. + command & help(const std::string & text); + command & operator()(std::string const & description); + + // Add arguments. + template + command & add_argument(P const & p); + template + command & operator|=(P const & p); + + // Internal. + virtual std::unique_ptr clone() const override + { + return make_clone(this); + } +}; + +/* tag::reference[] + +[#lyra_command_ctor] +== Construction + +[source] +---- +command::command(const std::string & n); +command::command( + const std::string & n, const std::function& f); +---- + +To construct an `command` we need a name (`n`) that matches, and triggers, that +command. + + +end::reference[] */ +inline command::command(const std::string & n) +{ + this->sequential() + .add_argument(literal(n)) + .add_argument(group().required()); +} +inline command::command( + const std::string & n, const std::function & f) + : group(f) +{ + this->sequential() + .add_argument(literal(n)) + .add_argument(group().required()); +} + +/* tag::reference[] + +[#lyra_command_specification] +== Specification + +end::reference[] */ + +/* tag::reference[] + +[#lyra_command_help] +=== `lyra:command::help` + +[source] +---- +command & command::help(const std::string& text) +command & command::operator()(std::string const& description) +---- + +Specify a help description for the command. This sets the help for the +underlying literal of the command. + +end::reference[] */ +inline command & command::help(const std::string & text) +{ + this->get(0).help(text); + return *this; +} +inline command & command::operator()(std::string const & description) +{ + return this->help(description); +} + +/* tag::reference[] +[#lyra_command_add_argument] +=== `lyra::command::add_argument` + +[source] +---- +template +command & command::add_argument(P const & p); +template +command & command::operator|=(P const & p); +---- + +Adds the given argument parser to the considered arguments for this `comand`. +The argument is added to the sub-group argument instead of this one. Hence it +has the effect of adding arguments *after* the command name. + +end::reference[] */ +template +command & command::add_argument(P const & p) +{ + this->get(1).add_argument(p); + return *this; +} +template +command & command::operator|=(P const & p) +{ + return this->add_argument(p); +} + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/detail/bound.hpp b/3party/lyra/lyra/detail/bound.hpp new file mode 100644 index 0000000..9432a36 --- /dev/null +++ b/3party/lyra/lyra/detail/bound.hpp @@ -0,0 +1,193 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_BOUND_HPP +#define LYRA_DETAIL_BOUND_HPP + +#include "lyra/detail/from_string.hpp" +#include "lyra/detail/invoke_lambda.hpp" +#include "lyra/detail/parse.hpp" +#include "lyra/detail/unary_lambda_traits.hpp" +#include + +namespace lyra { namespace detail { + +struct NonCopyable +{ + NonCopyable() = default; + NonCopyable(NonCopyable const &) = delete; + NonCopyable(NonCopyable &&) = delete; + NonCopyable & operator=(NonCopyable const &) = delete; + NonCopyable & operator=(NonCopyable &&) = delete; +}; + +struct BoundRef : NonCopyable +{ + virtual ~BoundRef() = default; + virtual auto isContainer() const -> bool { return false; } + virtual auto isFlag() const -> bool { return false; } + + virtual size_t get_value_count() const { return 0; } + virtual std::string get_value(size_t) const { return ""; } +}; + +struct BoundValueRefBase : BoundRef +{ + virtual auto setValue(std::string const & arg) -> parser_result = 0; +}; + +struct BoundFlagRefBase : BoundRef +{ + virtual auto setFlag(bool flag) -> parser_result = 0; + virtual auto isFlag() const -> bool { return true; } +}; + +template +struct BoundValueRef : BoundValueRefBase +{ + T & m_ref; + + explicit BoundValueRef(T & ref) + : m_ref(ref) + {} + + auto setValue(std::string const & arg) -> parser_result override + { + return parse_string(arg, m_ref); + } + + virtual size_t get_value_count() const override { return 1; } + virtual std::string get_value(size_t i) const override + { + if (i == 0) + { + std::string result; + detail::to_string(m_ref, result); + return result; + } + return ""; + } +}; + +template +struct BoundValueRef> : BoundValueRefBase +{ + std::vector & m_ref; + + explicit BoundValueRef(std::vector & ref) + : m_ref(ref) + {} + + auto isContainer() const -> bool override { return true; } + + auto setValue(std::string const & arg) -> parser_result override + { + T temp; + auto str_result = parse_string(arg, temp); + if (str_result) m_ref.push_back(temp); + return str_result; + } + + virtual size_t get_value_count() const override { return m_ref.size(); } + virtual std::string get_value(size_t i) const override + { + if (i < m_ref.size()) + { + std::string str_result; + detail::to_string(m_ref[i], str_result); + return str_result; + } + return ""; + } +}; + +struct BoundFlagRef : BoundFlagRefBase +{ + bool & m_ref; + + explicit BoundFlagRef(bool & ref) + : m_ref(ref) + {} + + auto setFlag(bool flag) -> parser_result override + { + m_ref = flag; + return parser_result::ok(parser_result_type::matched); + } + + virtual size_t get_value_count() const override { return 1; } + virtual std::string get_value(size_t i) const override + { + if (i == 0) return m_ref ? "true" : "false"; + return ""; + } +}; + +template +struct BoundLambda : BoundValueRefBase +{ + L m_lambda; + + static_assert(unary_lambda_traits::isValid, + "Supplied lambda must take exactly one argument"); + explicit BoundLambda(L const & lambda) + : m_lambda(lambda) + {} + + auto setValue(std::string const & arg) -> parser_result override + { + return invokeLambda::ArgType>( + m_lambda, arg); + } +}; + +template +struct BoundFlagLambda : BoundFlagRefBase +{ + L m_lambda; + + static_assert(unary_lambda_traits::isValid, + "Supplied lambda must take exactly one argument"); + static_assert( + std::is_same::ArgType, bool>::value, + "flags must be boolean"); + + explicit BoundFlagLambda(L const & lambda) + : m_lambda(lambda) + {} + + auto setFlag(bool flag) -> parser_result override + { + return LambdaInvoker< + typename unary_lambda_traits::ReturnType>::invoke(m_lambda, + flag); + } +}; + +template +struct BoundVal : BoundValueRef +{ + T value; + + BoundVal(T && v) + : BoundValueRef(value) + , value(v) + {} + + BoundVal(BoundVal && other) + : BoundValueRef(value) + , value(std::move(other.value)) + {} + + std::shared_ptr move_to_shared() + { + return std::shared_ptr(new BoundVal(std::move(*this))); + } +}; + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/choices.hpp b/3party/lyra/lyra/detail/choices.hpp new file mode 100644 index 0000000..28dbf7a --- /dev/null +++ b/3party/lyra/lyra/detail/choices.hpp @@ -0,0 +1,149 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_CHOICES_HPP +#define LYRA_DETAIL_CHOICES_HPP + +#include "lyra/detail/from_string.hpp" +#include "lyra/detail/result.hpp" +#include "lyra/detail/unary_lambda_traits.hpp" +#include "lyra/parser_result.hpp" +#include +#include +#include +#include +#include + +namespace lyra { namespace detail { + +/* +Type erased base for set of choices. I.e. it's an "interface". +*/ +struct choices_base +{ + virtual ~choices_base() = default; + virtual parser_result contains_value(std::string const & val) const = 0; +}; + +/* +Stores a set of choice values and provides checking if a given parsed +string value is one of the choices. +*/ +template +struct choices_set : choices_base +{ + // The allowed values. + std::vector values; + + template + explicit choices_set(Vals... vals) + : choices_set({ vals... }) + {} + + explicit choices_set(const std::vector & vals) + : values(vals) + {} + + // Checks if the given string val exists in the set of + // values. Returns a parsing error if the value is not present. + parser_result contains_value(std::string const & val) const override + { + T value; + auto parse = parse_string(val, value); + if (!parse) + { + return parser_result::error( + parser_result_type::no_match, parse.message()); + } + bool result = std::count(values.begin(), values.end(), value) > 0; + if (result) + { + return parser_result::ok(parser_result_type::matched); + } + // We consider not finding a choice a parse error. + return parser_result::error(parser_result_type::no_match, + "Value '" + val + + "' not expected. Allowed values are: " + this->to_string()); + } + + // Returns a comma separated list of the allowed values. + std::string to_string() const + { + std::string result; + for (const T & val : values) + { + if (!result.empty()) result += ", "; + std::string val_string; + if (detail::to_string(val, val_string)) + { + result += val_string; + } + else + { + result += ""; + } + } + return result; + } + + protected: + explicit choices_set(std::initializer_list const & vals) + : values(vals) + {} +}; + +template <> +struct choices_set : choices_set +{ + template + explicit choices_set(Vals... vals) + : choices_set(vals...) + {} +}; + +/* +Calls a designated function to check if the choice is valid. +*/ +template +struct choices_check : choices_base +{ + static_assert(unary_lambda_traits::isValid, + "Supplied lambda must take exactly one argument"); + static_assert(std::is_same::ReturnType>::value, + "Supplied lambda must return bool"); + + Lambda checker; + using value_type = typename unary_lambda_traits::ArgType; + + explicit choices_check(Lambda const & checker_function) + : checker(checker_function) + {} + + // Checks if the given string val exists in the set of + // values. Returns a parsing error if the value is not present. + parser_result contains_value(std::string const & val) const override + { + value_type value; + auto parse = parse_string(val, value); + if (!parse) + { + return parser_result::error( + parser_result_type::no_match, parse.message()); + } + if (checker(value)) + { + return parser_result::ok(parser_result_type::matched); + } + // We consider not finding a choice a parse error. + return parser_result::error( + parser_result_type::no_match, "Value '" + val + "' not expected."); + } +}; + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/deprecated_parser_customization.hpp b/3party/lyra/lyra/detail/deprecated_parser_customization.hpp new file mode 100644 index 0000000..bd65472 --- /dev/null +++ b/3party/lyra/lyra/detail/deprecated_parser_customization.hpp @@ -0,0 +1,63 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_DEPRECATED_PARSER_CUSTOMIZATION_HPP +#define LYRA_DETAIL_DEPRECATED_PARSER_CUSTOMIZATION_HPP + +#include + +namespace lyra { + +/* tag::reference[] + +[#lyra_parser_customization] += `lyra::parser_customization` + +Customization interface for parsing of options. + +[source] +---- +virtual std::string token_delimiters() const = 0; +---- + +Specifies the characters to use for splitting a cli argument into the option +and its value (if any). + +[source] +---- +virtual std::string option_prefix() const = 0; +---- + +Specifies the characters to use as possible prefix, either single or double, +for all options. + +end::reference[] */ +struct parser_customization +{ + virtual std::string token_delimiters() const = 0; + virtual std::string option_prefix() const = 0; +}; + +/* tag::reference[] + +[#lyra_default_parser_customization] += `lyra::default_parser_customization` + +Is-a `lyra::parser_customization` that defines token delimiters as space (" ") +or equal (`=`). And specifies the option prefix character as dash (`-`) +resulting in long options with `--` and short options with `-`. + +This customization is used as the default if none is given. + +end::reference[] */ +struct default_parser_customization : parser_customization +{ + std::string token_delimiters() const override { return " ="; } + std::string option_prefix() const override { return "-"; } +}; + +} // namespace lyra + +#endif diff --git a/3party/lyra/lyra/detail/from_string.hpp b/3party/lyra/lyra/detail/from_string.hpp new file mode 100644 index 0000000..b8d5002 --- /dev/null +++ b/3party/lyra/lyra/detail/from_string.hpp @@ -0,0 +1,196 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_FROM_STRING_HPP +#define LYRA_DETAIL_FROM_STRING_HPP + +#include "lyra/detail/trait_utils.hpp" + +#include +#include +#include +#include + +#ifndef LYRA_CONFIG_OPTIONAL_TYPE +# ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# include +# define LYRA_CONFIG_OPTIONAL_TYPE std::optional +# endif +# endif +#endif + +namespace lyra { namespace detail { + +template +bool to_string(const T & source, std::string & target) +{ + std::stringstream ss; + ss << source; + ss >> target; + return !ss.fail(); +} + +inline bool to_string(const std::string & source, std::string & target) +{ + target = source; + return true; +} + +inline bool to_string(const char * source, std::string & target) +{ + target = source; + return true; +} + +inline bool to_string(bool source, std::string & target) +{ + target = source ? "true" : "false"; + return true; +} + +#ifdef LYRA_CONFIG_OPTIONAL_TYPE +template +inline bool to_string( + LYRA_CONFIG_OPTIONAL_TYPE & source, std::string & target) +{ + if (source) + return to_string(*source, target); + else + target = ""; + return true; +} +#endif // LYRA_CONFIG_OPTIONAL_TYPE + +template +struct is_convertible_from_string : std::false_type +{}; + +template +struct is_convertible_from_string::value>::type> + : std::true_type +{}; + +// Validates format of given value strings before conversion. This default +// template return true always. +template +struct validate_from_string +{ + static bool validate(const std::string &) { return true; } +}; + +// Validates that a to be parsed unsigned integer only contains number +// digits. +template +struct validate_from_string::type>::value>::type> +{ + static bool validate(const std::string & s) + { + return s.find_first_not_of("0123456789") == std::string::npos; + } +}; + +// Validates that a to be parsed signed integer only contains a sign and +// number digits. +template +struct validate_from_string::type>::value + && std::is_signed::type>::value>::type> +{ + static bool validate(const std::string & s) + { + return s.find_first_not_of("-0123456789") == std::string::npos; + } +}; + +template +inline bool from_string(S const & source, T & target) +{ + std::stringstream ss; + // Feed what we want to convert into the stream so that we can convert it + // on extraction to the target type. + ss << source; + // Check that the source string data is valid. This check depends on the + // target type. + if (!validate_from_string::validate(ss.str())) return false; + T temp {}; + ss >> temp; + if (!ss.fail() && ss.eof()) + { + target = temp; + return true; + } + return false; +} + +template +inline bool from_string(S const & source, std::basic_string & target) +{ + to_string(source, target); + return true; +} + +template +struct is_convertible_from_string::value>::type> + : std::true_type +{}; + +template +inline bool from_string(S const & source, bool & target) +{ + std::string srcLC; + to_string(source, srcLC); + std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), + [](char c) { return static_cast(::tolower(c)); }); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" + || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" + || srcLC == "off") + target = false; + else + return false; + return true; +} + +#ifdef LYRA_CONFIG_OPTIONAL_TYPE +template +struct is_convertible_from_string::value>::type> + : std::true_type +{}; + +template +inline bool from_string(S const & source, LYRA_CONFIG_OPTIONAL_TYPE & target) +{ + std::string srcLC; + to_string(source, srcLC); + std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), + [](char c) { return static_cast(::tolower(c)); }); + if (srcLC == "") + { + target.reset(); + return true; + } + else + { + T temp; + auto str_result = from_string(source, temp); + if (str_result) target = std::move(temp); + return str_result; + } +} +#endif // LYRA_CONFIG_OPTIONAL_TYPE + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/invoke_lambda.hpp b/3party/lyra/lyra/detail/invoke_lambda.hpp new file mode 100644 index 0000000..65f99ea --- /dev/null +++ b/3party/lyra/lyra/detail/invoke_lambda.hpp @@ -0,0 +1,51 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_INVOKE_LAMBDA_HPP +#define LYRA_DETAIL_INVOKE_LAMBDA_HPP + +#include "lyra/detail/parse.hpp" +#include "lyra/detail/result.hpp" +#include "lyra/detail/unary_lambda_traits.hpp" +#include "lyra/parser_result.hpp" + +namespace lyra { namespace detail { + +template +struct LambdaInvoker +{ + template + static parser_result invoke(L const & lambda, ArgType const & arg) + { + return lambda(arg); + } +}; + +template <> +struct LambdaInvoker +{ + template + static parser_result invoke(L const & lambda, ArgType const & arg) + { + lambda(arg); + return parser_result::ok(parser_result_type::matched); + } +}; + +template +inline parser_result invokeLambda(L const & lambda, std::string const & arg) +{ + ArgType temp {}; + auto p_result = parse_string(arg, temp); + return !p_result + ? p_result + : LambdaInvoker::ReturnType>::invoke( + lambda, temp); +} + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/parse.hpp b/3party/lyra/lyra/detail/parse.hpp new file mode 100644 index 0000000..ae8b0cb --- /dev/null +++ b/3party/lyra/lyra/detail/parse.hpp @@ -0,0 +1,29 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_PARSE_HPP +#define LYRA_DETAIL_PARSE_HPP + +#include "lyra/detail/from_string.hpp" +#include "lyra/parser_result.hpp" + +#include + +namespace lyra { namespace detail { + +template +parser_result parse_string(S const & source, T & target) +{ + if (from_string(source, target)) + return parser_result::ok(parser_result_type::matched); + else + return parser_result::error(parser_result_type::no_match, + "Unable to convert '" + source + "' to destination type"); +} + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/print.hpp b/3party/lyra/lyra/detail/print.hpp new file mode 100644 index 0000000..4cac790 --- /dev/null +++ b/3party/lyra/lyra/detail/print.hpp @@ -0,0 +1,79 @@ +// Copyright 2021-2022 René Ferdinand Rivera Morell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_PRINT_HPP +#define LYRA_DETAIL_PRINT_HPP + +#include +#include + +#ifndef LYRA_DEBUG +# define LYRA_DEBUG 0 +#endif + +namespace lyra { namespace detail { + +constexpr bool is_debug = LYRA_DEBUG; + +template +std::string to_string(T && t) +{ + return std::string(std::move(t)); +} + +using std::to_string; + +struct print +{ + print(const char * scope_name = nullptr) + : scope(scope_name) + { + if (is_debug) print::depth() += 1; + if (scope) debug(scope, "..."); + } + + ~print() + { + if (scope) debug("...", scope); + if (is_debug) print::depth() -= 1; + } + + template + void debug(A... arg) + { + if (is_debug) + { + std::cerr << "[DEBUG]" + << std::string((print::depth() - 1) * 2, ' '); + std::string args[] = { to_string(arg)... }; + for (auto & arg_string : args) + { + std::cerr << " " << arg_string; + } + std::cerr << "\n"; + } + } + + private: + const char * scope; + + static std::size_t & depth() + { + static std::size_t d = 0; + return d; + } +}; + +}} // namespace lyra::detail + +#if LYRA_DEBUG +# define LYRA_PRINT_SCOPE ::lyra::detail::print lyra_print_scope +# define LYRA_PRINT_DEBUG lyra_print_scope.debug +#else +# define LYRA_PRINT_SCOPE(...) while (false) +# define LYRA_PRINT_DEBUG(...) while (false) +#endif + +#endif diff --git a/3party/lyra/lyra/detail/result.hpp b/3party/lyra/lyra/detail/result.hpp new file mode 100644 index 0000000..0b393d6 --- /dev/null +++ b/3party/lyra/lyra/detail/result.hpp @@ -0,0 +1,165 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_RESULT_HPP +#define LYRA_DETAIL_RESULT_HPP + +#include +#include + +namespace lyra { namespace detail { + +class result_base +{ + public: + result_base const & base() const { return *this; } + explicit operator bool() const { return is_ok(); } + bool is_ok() const { return kind_ == result_kind::ok; } + std::string message() const { return message_; } + [[deprecated]] std::string errorMessage() const { return message(); } + + protected: + enum class result_kind + { + ok, + error + }; + + explicit result_base(result_kind kind, const std::string & message = "") + : kind_(kind) + , message_(message) + {} + + explicit result_base(const result_base & other) + : kind_(other.kind_) + , message_(other.message_) + {} + + virtual ~result_base() = default; + + result_base & operator=(const result_base &) = default; + + private: + result_kind kind_; + std::string message_; +}; + +template +class result_value_base : public result_base +{ + public: + using value_type = T; + + value_type const & value() const { return *value_; } + bool has_value() const { return bool(value_); } + + protected: + std::unique_ptr value_; + + explicit result_value_base( + result_kind kind, const std::string & message = "") + : result_base(kind, message) + {} + + explicit result_value_base(result_kind kind, + const value_type & val, + const std::string & message = "") + : result_base(kind, message) + { + value_.reset(new value_type(val)); + } + + explicit result_value_base(result_value_base const & other) + : result_base(other) + { + if (other.value_) value_.reset(new value_type(*other.value_)); + } + + explicit result_value_base(const result_base & other) + : result_base(other) + {} + + result_value_base & operator=(result_value_base const & other) + { + result_base::operator=(other); + if (other.value_) value_.reset(new T(*other.value_)); + return *this; + } +}; + +template <> +class result_value_base : public result_base +{ + public: + using value_type = void; + + protected: + // using result_base::result_base; + explicit result_value_base(const result_base & other) + : result_base(other) + {} + explicit result_value_base( + result_kind kind, const std::string & message = "") + : result_base(kind, message) + {} +}; + +template +class basic_result : public result_value_base +{ + public: + using value_type = typename result_value_base::value_type; + + explicit basic_result(result_base const & other) + : result_value_base(other) + {} + + // With-value results.. + + static basic_result ok(value_type const & val) + { + return basic_result(result_base::result_kind::ok, val); + } + + static basic_result error( + value_type const & val, std::string const & message) + { + return basic_result(result_base::result_kind::error, val, message); + } + + protected: + using result_value_base::result_value_base; +}; + +template <> +class basic_result : public result_value_base +{ + public: + using value_type = typename result_value_base::value_type; + + explicit basic_result(result_base const & other) + : result_value_base(other) + {} + + // Value-less results.. (only kind as void is a value-less kind) + + static basic_result ok() + { + return basic_result(result_base::result_kind::ok); + } + + static basic_result error(std::string const & message) + { + return basic_result(result_base::result_kind::error, message); + } + + protected: + using result_value_base::result_value_base; +}; + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/tokens.hpp b/3party/lyra/lyra/detail/tokens.hpp new file mode 100644 index 0000000..81d72d2 --- /dev/null +++ b/3party/lyra/lyra/detail/tokens.hpp @@ -0,0 +1,295 @@ +// Copyright 2018-2022 René Ferdinand Rivera Morell +// Copyright 2017 Two Blue Cubes Ltd. All rights reserved. +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_TOKENS_HPP +#define LYRA_DETAIL_TOKENS_HPP + +#include "lyra/option_style.hpp" + +#include +#include + +namespace lyra { namespace detail { + +// Wraps a token coming from a token stream. These may not directly +// correspond to strings as a single string may encode an option + its +// argument if the : or = form is used +enum class token_type +{ + unknown, + option, + argument +}; + +template > +class basic_token_name +{ + public: + using traits_type = Traits; + using value_type = Char; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using string_type = std::basic_string; + + basic_token_name() noexcept + : str { nullptr } + , len { 0 } + {} + + basic_token_name(const basic_token_name &) noexcept = default; + + basic_token_name(const_pointer s) noexcept + : str { s } + , len { traits_type::length(s) } + {} + + basic_token_name(const_pointer s, size_type count) noexcept + : str { s } + , len { count } + {} + + basic_token_name & operator=(const basic_token_name &) noexcept = default; + + void swap(basic_token_name & other) noexcept + { + auto tmp = *this; + *this = other; + other = tmp; + } + + const_iterator begin() const noexcept { return this->str; } + const_iterator end() const noexcept { return this->str + this->len; } + const_iterator cbegin() const noexcept { return this->str; } + const_iterator cend() const noexcept { return this->str + this->len; } + + size_type size() const noexcept { return this->len; } + size_type length() const noexcept { return this->len; } + bool empty() const noexcept { return this->len == 0; } + + friend string_type to_string(const basic_token_name & t) + { + return { t.str, t.len }; + } + + friend string_type operator+( + const_pointer lhs, const basic_token_name & rhs) + { + return lhs + to_string(rhs); + } + + private: + const_pointer str; + size_type len; +}; + +// using token_name = basic_token_name; +using token_name = std::string; + +struct token +{ + token_type type; + token_name name; + + token() + : type(token_type::unknown) + {} + token(const token & other) = default; + token(token_type t, const token_name & n) + : type(t) + , name(n) + {} + + explicit operator bool() const { return type != token_type::unknown; } +}; + +// Abstracts iterators into args with option arguments uniformly handled +class token_iterator +{ + public: + template + explicit token_iterator(Span const & args, const option_style & opt_style) + : style(opt_style) + , args_i(args.begin()) + , args_e(args.end()) + , args_i_sub(opt_style.short_option_size) + {} + + explicit operator bool() const noexcept { return args_i != args_e; } + + token_iterator & pop(const token & arg_or_opt) + { + if (arg_or_opt.type == token_type::option && has_short_option_prefix()) + { + // Multiple short options argument (-abc). Advance to the next + // short option possible, or the next arg entirely. + if (++args_i_sub >= args_i->size()) + { + ++args_i; + args_i_sub = style.short_option_size; + } + } + else + { + // Regular arg or long option, just advance to the next arg. + ++args_i; + args_i_sub = style.short_option_size; + } + return *this; + } + + token_iterator & pop(const token & /* opt */, const token & /* val */) + { + if (has_short_option_prefix() && args_i->size() > 2) + ++args_i; + else if (!has_value_delimiter()) + args_i += 2; + else + ++args_i; + args_i_sub = style.short_option_size; + return *this; + } + + // Current arg looks like an option, short or long. + bool has_option_prefix() const noexcept + { + return has_long_option_prefix() || has_short_option_prefix(); + } + + // Current arg looks like a short option (-o). + bool has_short_option_prefix() const noexcept + { + return (args_i != args_e) + && is_prefixed( + style.short_option_prefix, style.short_option_size, *args_i); + } + + // Current arg looks like a long option (--option). + bool has_long_option_prefix() const noexcept + { + return (args_i != args_e) + && is_prefixed( + style.long_option_prefix, style.long_option_size, *args_i); + } + + // Current arg looks like a delimited option+value (--option=x, -o=x) + bool has_value_delimiter() const noexcept + { + return (args_i != args_e) + && (args_i->find_first_of(style.value_delimiters) + != std::string::npos); + } + + // Extract the current option token. + token option() const + { + if (has_long_option_prefix()) + { + if (has_value_delimiter()) + // --option=x + return token(token_type::option, + args_i->substr( + 0, args_i->find_first_of(style.value_delimiters))); + else + // --option + return token(token_type::option, *args_i); + } + else if (has_short_option_prefix()) + { + // -o (or possibly -abco) + return { token_type::option, + prefix_value(style.short_option_prefix, style.short_option_size) + + (*args_i)[args_i_sub] }; + } + return token(); + } + + // Extracts the option value if available. This will do any needed + // lookahead through the args for the value. + token value() const + { + if (has_option_prefix() && has_value_delimiter()) + // --option=x, -o=x + return token(token_type::argument, + args_i->substr( + args_i->find_first_of(style.value_delimiters) + 1)); + else if (has_long_option_prefix()) + { + if (args_i + 1 != args_e) + // --option x + return token(token_type::argument, *(args_i + 1)); + } + else if (has_short_option_prefix()) + { + if (args_i_sub + 1 < args_i->size()) + // -ox + return token( + token_type::argument, args_i->substr(args_i_sub + 1)); + else if (args_i + 1 != args_e) + // -o x + return token(token_type::argument, *(args_i + 1)); + } + return token(); + } + + token argument() const { return token(token_type::argument, *args_i); } + + static bool is_prefixed( + const std::string & prefix, std::size_t size, const std::string & s) + { + if (!prefix.empty() && size > 0 && s.size() > size) + { + for (auto c : prefix) + { + // Checks that the option looks like "[]{size}[^]". + if (s[size] != c + && s.find_last_not_of(c, size - 1) == std::string::npos) + return true; + } + } + return false; + } + + private: + const option_style & style; + std::vector::const_iterator args_i; + std::vector::const_iterator args_e; + std::string::size_type args_i_sub; + + inline bool is_opt_prefix(char c) const noexcept + { + return is_prefix_char( + style.long_option_prefix, style.long_option_size, c) + || is_prefix_char( + style.short_option_prefix, style.short_option_size, c); + } + + inline bool is_prefix_char(const std::string & prefix, + std::size_t size, + std::string::value_type c) const noexcept + { + return !prefix.empty() && size > 0 + && prefix.find(c) != std::string::npos; + } + + inline std::string prefix_value( + const std::string & prefix, std::size_t size) const + { + return std::string( + static_cast(size), prefix[0]); + } +}; + +}} // namespace lyra::detail + +#endif diff --git a/3party/lyra/lyra/detail/trait_utils.hpp b/3party/lyra/lyra/detail/trait_utils.hpp new file mode 100644 index 0000000..49654a6 --- /dev/null +++ b/3party/lyra/lyra/detail/trait_utils.hpp @@ -0,0 +1,74 @@ +// Copyright 2020-2022 René Ferdinand Rivera Morell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef LYRA_DETAIL_TRAIT_UTILS_HPP +#define LYRA_DETAIL_TRAIT_UTILS_HPP + +#include +#include + +namespace lyra { namespace detail { + +// Checks that F can be called with arguments of type Args. +// Credit for the technique goes to Richard Hodges (in SO post). +template +struct is_callable +{ + template + static auto test(U * p) + -> decltype((*p)(std::declval()...), void(), std::true_type()); + + template + static auto test(...) -> decltype(std::false_type()); + + static constexpr bool value = decltype(test(0))::value; +}; + +template +struct remove_cvref +{ + typedef + typename std::remove_cv::type>::type + type; +}; + +// Checks that F can be called, with an unspecified set of arguments. +// +// Currently this only detects function objects, like lambdas. +// Where the operator() is not templated. +template +struct is_invocable +{ + template + static auto test(U * p) + -> decltype((&U::operator()), void(), std::true_type()); + + template + static auto test(...) -> decltype(std::false_type()); + + static constexpr bool value + = decltype(test::type>(0))::value; +}; + +// C++11 compatible void_t equivalent. +template +struct make_void +{ + typedef void type; +}; +template +using valid_t = typename make_void::type; + +// Borrowed from https://wg21.link/p2098r1 +template class Primary> +struct is_specialization_of : std::false_type +{}; +template