changeset 662:c13e190b2a74

options: allow '!' in format string to stop at first argument
author David Demelier <markand@malikania.fr>
date Tue, 16 Jul 2019 21:07:25 +0200
parents a7ef5d868275
children c3d758f1d640
files cpp/options/options.hpp cpp/options/test/main.cpp
diffstat 2 files changed, 43 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- 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 <typename InputIt>
-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 <typename String>
 inline auto parse(std::initializer_list<String> 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);
 }
 
 
--- 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)