# HG changeset patch # User David Demelier # Date 1563304045 -7200 # Node ID c13e190b2a74683021ffa28d181fec338fe8884a # Parent a7ef5d86827579a83a574197cf17bd408517ace0 options: allow '!' in format string to stop at first argument diff -r a7ef5d868275 -r c13e190b2a74 cpp/options/options.hpp --- a/cpp/options/options.hpp Tue Jul 16 20:31:36 2019 +0200 +++ b/cpp/options/options.hpp Tue Jul 16 21:07:25 2019 +0200 @@ -55,6 +55,12 @@ * If a -- option appears in the argument list, it stops option parsing and all * next tokens are considered arguments even if they start with an hyphen. * + * If the exlamation mark appears in the fmt argument, the function will stop + * parsing tokens immediately when one argument is not an option. + * + * This function explicitly takes references to it and end parameters to allow + * the user to determine the number of tokens actually parsed. + * * Example of format strings: * * - "abc": are all three boolean options, @@ -74,20 +80,29 @@ * \return the result */ template -inline auto parse(InputIt it, InputIt end, std::string_view fmt) -> pack +inline auto parse(InputIt&& it, InputIt&& end, std::string_view fmt) -> pack { pack result; for (; it != end; ++it) { const std::string_view token(*it); + /* + * Special token that stops parsing options, all next tokens + * will be considered as positional arguments. + */ if (token == "--") { for (++it; it != end; ++it) std::get<0>(result).push_back(std::string(*it)); break; } - if (token.compare(0, 1, "-") != 0) { + // Is this a positional argument? + if (token.compare(0U, 1U, "-") != 0) { + // Stop parsing in case of '!' in format string. + if (fmt.find('!') != std::string_view::npos) + break; + std::get<0>(result).push_back(std::string(token)); continue; } @@ -100,16 +115,22 @@ if (idx == std::string_view::npos) throw std::runtime_error("invalid option"); - if (fmt.compare(idx + 1U, 1, ":") != 0) { + // This is a boolean value. + if (fmt.compare(idx + 1U, 1U, ":") != 0) { std::get<1>(result).emplace(sub[i], ""); continue; } + /* + * The value is adjacent to the option (e.g. + * -csuper.conf). + */ if (idx + 1U < sub.size()) { std::get<1>(result).emplace(sub[i], std::string(sub.substr(i + 1))); break; } + // Option is the next token (e.g. -c super.conf). if (++it == end || std::string_view(*it).compare(0U, 1U, "-") == 0) throw std::runtime_error("option require a value"); @@ -131,7 +152,10 @@ template inline auto parse(std::initializer_list args, std::string_view fmt) -> pack { - return parse(args.begin(), args.end(), fmt); + auto begin = args.begin(); + auto end = args.end(); + + return parse(begin, end, fmt); } /** @@ -149,7 +173,10 @@ for (int i = 0; i < argc; ++i) args[i] = argv[i]; - return parse(args.begin(), args.end(), fmt); + auto begin = args.begin(); + auto end = args.end(); + + return parse(begin, end, fmt); } diff -r a7ef5d868275 -r c13e190b2a74 cpp/options/test/main.cpp --- a/cpp/options/test/main.cpp Tue Jul 16 20:31:36 2019 +0200 +++ b/cpp/options/test/main.cpp Tue Jul 16 21:07:25 2019 +0200 @@ -130,6 +130,17 @@ BOOST_TEST(options.find('f')->second.empty()); } +BOOST_AUTO_TEST_CASE(exclam) +{ + const auto [ args, options ] = options::parse({"-v", "install", "-p"}, "pv!"); + + BOOST_TEST(args.size() == 0U); + BOOST_TEST(options.size() == 1U); + BOOST_TEST(options.count('v') == 1U); + BOOST_TEST(options.find('v')->second.empty()); + BOOST_TEST(options.count('p') == 0U); +} + BOOST_AUTO_TEST_SUITE(errors) BOOST_AUTO_TEST_CASE(invalid_option)