Commit e976f964c3ee6a4f6faa836ecb38f3ff8b043114

Authored by Eyal Rozenberg
Committed by GitHub
1 parent 43ebb494

Fix issue #240: Multiple long option names / aliases (#349)

* Fixes #240: Multiple long option names / aliases

* We now use a vector of long option names instead of a single name
* When specifying an option, you can provide multiple names separated by commas, at most one of which may have a length of 1 (not necessarily the first specified name). The length-1 name is the single-hyphen switch (the "short name").
* Hashing uses the first long name
* Option help currently only uses the first long name.
CHANGELOG.md
@@ -7,6 +7,7 @@ options. The project adheres to semantic versioning. @@ -7,6 +7,7 @@ options. The project adheres to semantic versioning.
7 7
8 ### Added 8 ### Added
9 9
  10 +* Support for multiple long names for the same option (= multiple long aliases)
10 * Add a `program()` function to retrieve the program name. 11 * Add a `program()` function to retrieve the program name.
11 * Added a .clang-format file. 12 * Added a .clang-format file.
12 13
include/cxxopts.hpp
@@ -27,6 +27,7 @@ THE SOFTWARE. @@ -27,6 +27,7 @@ THE SOFTWARE.
27 #ifndef CXXOPTS_HPP_INCLUDED 27 #ifndef CXXOPTS_HPP_INCLUDED
28 #define CXXOPTS_HPP_INCLUDED 28 #define CXXOPTS_HPP_INCLUDED
29 29
  30 +#include <cassert>
30 #include <cctype> 31 #include <cctype>
31 #include <cstring> 32 #include <cstring>
32 #include <exception> 33 #include <exception>
@@ -101,6 +102,7 @@ static constexpr struct { @@ -101,6 +102,7 @@ static constexpr struct {
101 #include <unicode/unistr.h> 102 #include <unicode/unistr.h>
102 103
103 namespace cxxopts { 104 namespace cxxopts {
  105 +
104 using String = icu::UnicodeString; 106 using String = icu::UnicodeString;
105 107
106 inline 108 inline
@@ -248,6 +250,7 @@ end(const icu::UnicodeString&amp; s) @@ -248,6 +250,7 @@ end(const icu::UnicodeString&amp; s)
248 #else 250 #else
249 251
250 namespace cxxopts { 252 namespace cxxopts {
  253 +
251 using String = std::string; 254 using String = std::string;
252 255
253 template <typename T> 256 template <typename T>
@@ -522,6 +525,7 @@ class incorrect_argument_type : public parsing @@ -522,6 +525,7 @@ class incorrect_argument_type : public parsing
522 525
523 } // namespace exceptions 526 } // namespace exceptions
524 527
  528 +
525 template <typename T> 529 template <typename T>
526 void throw_or_mimic(const std::string& text) 530 void throw_or_mimic(const std::string& text)
527 { 531 {
@@ -541,6 +545,8 @@ void throw_or_mimic(const std::string&amp; text) @@ -541,6 +545,8 @@ void throw_or_mimic(const std::string&amp; text)
541 #endif 545 #endif
542 } 546 }
543 547
  548 +using OptionNames = std::vector<std::string>;
  549 +
544 namespace values { 550 namespace values {
545 551
546 namespace parser_tool { 552 namespace parser_tool {
@@ -624,28 +630,44 @@ inline bool IsFalseText(const std::string &amp;text) @@ -624,28 +630,44 @@ inline bool IsFalseText(const std::string &amp;text)
624 return false; 630 return false;
625 } 631 }
626 632
627 -inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text) 633 +inline OptionNames split_option_names(const std::string &text)
628 { 634 {
629 - std::string short_sw, long_sw;  
630 - const char *pdata = text.c_str();  
631 - if (isalnum(*pdata) && *(pdata + 1) == ',') {  
632 - short_sw = std::string(1, *pdata);  
633 - pdata += 2;  
634 - }  
635 - while (*pdata == ' ') { pdata += 1; }  
636 - if (isalnum(*pdata)) {  
637 - const char *store = pdata;  
638 - pdata += 1;  
639 - while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') {  
640 - pdata += 1; 635 + OptionNames split_names;
  636 +
  637 + std::string::size_type token_start_pos = 0;
  638 + auto length = text.length();
  639 +
  640 + while (token_start_pos < length) {
  641 + const auto &npos = std::string::npos;
  642 + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos);
  643 + if (next_non_space_pos == npos) {
  644 + throw_or_mimic<exceptions::invalid_option_format>(text);
641 } 645 }
642 - if (*pdata == '\0') {  
643 - long_sw = std::string(store, pdata - store);  
644 - } else { 646 + token_start_pos = next_non_space_pos;
  647 + auto next_delimiter_pos = text.find(',', token_start_pos);
  648 + if (next_delimiter_pos == token_start_pos) {
645 throw_or_mimic<exceptions::invalid_option_format>(text); 649 throw_or_mimic<exceptions::invalid_option_format>(text);
646 } 650 }
  651 + if (next_delimiter_pos == npos) {
  652 + next_delimiter_pos = length;
  653 + }
  654 + auto token_length = next_delimiter_pos - token_start_pos;
  655 + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/
  656 + {
  657 + const char* option_name_valid_chars =
  658 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  659 + "abcdefghijklmnopqrstuvwxyz"
  660 + "0123456789"
  661 + "_-";
  662 + if (!std::isalnum(text[token_start_pos]) ||
  663 + text.find_first_not_of(option_name_valid_chars, token_start_pos) < next_delimiter_pos) {
  664 + throw_or_mimic<exceptions::invalid_option_format>(text);
  665 + }
  666 + }
  667 + split_names.emplace_back(text.substr(token_start_pos, token_length));
  668 + token_start_pos = next_delimiter_pos + 1;
647 } 669 }
648 - return std::pair<std::string, std::string>(short_sw, long_sw); 670 + return split_names;
649 } 671 }
650 672
651 inline ArguDesc ParseArgument(const char *arg, bool &matched) 673 inline ArguDesc ParseArgument(const char *arg, bool &matched)
@@ -712,7 +734,8 @@ std::basic_regex&lt;char&gt; falsy_pattern @@ -712,7 +734,8 @@ std::basic_regex&lt;char&gt; falsy_pattern
712 std::basic_regex<char> option_matcher 734 std::basic_regex<char> option_matcher
713 ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); 735 ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
714 std::basic_regex<char> option_specifier 736 std::basic_regex<char> option_specifier
715 - ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); 737 + ("([[:alnum:]][-_[:alnum:]]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
  738 +std::basic_regex<char> option_specifier_separator(", *");
716 739
717 } // namespace 740 } // namespace
718 741
@@ -755,19 +778,23 @@ inline bool IsFalseText(const std::string &amp;text) @@ -755,19 +778,23 @@ inline bool IsFalseText(const std::string &amp;text)
755 return !result.empty(); 778 return !result.empty();
756 } 779 }
757 780
758 -inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text) 781 +// Gets the option names specified via a single, comma-separated string,
  782 +// and returns the separate, space-discarded, non-empty names
  783 +// (without considering which or how many are single-character)
  784 +inline OptionNames split_option_names(const std::string &text)
759 { 785 {
760 - std::match_results<const char*> result;  
761 - std::regex_match(text.c_str(), result, option_specifier);  
762 - if (result.empty()) 786 + if (!std::regex_match(text.c_str(), option_specifier))
763 { 787 {
764 throw_or_mimic<exceptions::invalid_option_format>(text); 788 throw_or_mimic<exceptions::invalid_option_format>(text);
765 } 789 }
766 790
767 - const std::string& short_sw = result[2];  
768 - const std::string& long_sw = result[3]; 791 + OptionNames split_names;
769 792
770 - return std::pair<std::string, std::string>(short_sw, long_sw); 793 + constexpr int use_non_matches { -1 };
  794 + auto token_iterator = std::sregex_token_iterator(
  795 + text.begin(), text.end(), option_specifier_separator, use_non_matches);
  796 + std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
  797 + return split_names;
771 } 798 }
772 799
773 inline ArguDesc ParseArgument(const char *arg, bool &matched) 800 inline ArguDesc ParseArgument(const char *arg, bool &matched)
@@ -1221,13 +1248,22 @@ value(T&amp; t) @@ -1221,13 +1248,22 @@ value(T&amp; t)
1221 1248
1222 class OptionAdder; 1249 class OptionAdder;
1223 1250
  1251 +inline
  1252 +CXXOPTS_NODISCARD
  1253 +const std::string&
  1254 +first_or_empty(const OptionNames& long_names)
  1255 +{
  1256 + static const std::string empty{""};
  1257 + return long_names.empty() ? empty : long_names.front();
  1258 +}
  1259 +
1224 class OptionDetails 1260 class OptionDetails
1225 { 1261 {
1226 public: 1262 public:
1227 OptionDetails 1263 OptionDetails
1228 ( 1264 (
1229 std::string short_, 1265 std::string short_,
1230 - std::string long_, 1266 + OptionNames long_,
1231 String desc, 1267 String desc,
1232 std::shared_ptr<const Value> val 1268 std::shared_ptr<const Value> val
1233 ) 1269 )
@@ -1237,7 +1273,7 @@ class OptionDetails @@ -1237,7 +1273,7 @@ class OptionDetails
1237 , m_value(std::move(val)) 1273 , m_value(std::move(val))
1238 , m_count(0) 1274 , m_count(0)
1239 { 1275 {
1240 - m_hash = std::hash<std::string>{}(m_long + m_short); 1276 + m_hash = std::hash<std::string>{}(first_long_name() + m_short);
1241 } 1277 }
1242 1278
1243 OptionDetails(const OptionDetails& rhs) 1279 OptionDetails(const OptionDetails& rhs)
@@ -1278,16 +1314,23 @@ class OptionDetails @@ -1278,16 +1314,23 @@ class OptionDetails
1278 1314
1279 CXXOPTS_NODISCARD 1315 CXXOPTS_NODISCARD
1280 const std::string& 1316 const std::string&
1281 - long_name() const 1317 + first_long_name() const
1282 { 1318 {
1283 - return m_long; 1319 + return first_or_empty(m_long);
1284 } 1320 }
1285 1321
1286 CXXOPTS_NODISCARD 1322 CXXOPTS_NODISCARD
1287 const std::string& 1323 const std::string&
1288 essential_name() const 1324 essential_name() const
1289 { 1325 {
1290 - return m_long.empty() ? m_short : m_long; 1326 + return m_long.empty() ? m_short : m_long.front();
  1327 + }
  1328 +
  1329 + CXXOPTS_NODISCARD
  1330 + const OptionNames &
  1331 + long_names() const
  1332 + {
  1333 + return m_long;
1291 } 1334 }
1292 1335
1293 size_t 1336 size_t
@@ -1298,7 +1341,7 @@ class OptionDetails @@ -1298,7 +1341,7 @@ class OptionDetails
1298 1341
1299 private: 1342 private:
1300 std::string m_short{}; 1343 std::string m_short{};
1301 - std::string m_long{}; 1344 + OptionNames m_long{};
1302 String m_desc{}; 1345 String m_desc{};
1303 std::shared_ptr<const Value> m_value{}; 1346 std::shared_ptr<const Value> m_value{};
1304 int m_count; 1347 int m_count;
@@ -1309,7 +1352,7 @@ class OptionDetails @@ -1309,7 +1352,7 @@ class OptionDetails
1309 struct HelpOptionDetails 1352 struct HelpOptionDetails
1310 { 1353 {
1311 std::string s; 1354 std::string s;
1312 - std::string l; 1355 + OptionNames l;
1313 String desc; 1356 String desc;
1314 bool has_default; 1357 bool has_default;
1315 std::string default_value; 1358 std::string default_value;
@@ -1340,7 +1383,7 @@ class OptionValue @@ -1340,7 +1383,7 @@ class OptionValue
1340 ensure_value(details); 1383 ensure_value(details);
1341 ++m_count; 1384 ++m_count;
1342 m_value->parse(text); 1385 m_value->parse(text);
1343 - m_long_name = &details->long_name(); 1386 + m_long_names = &details->long_names();
1344 } 1387 }
1345 1388
1346 void 1389 void
@@ -1348,14 +1391,14 @@ class OptionValue @@ -1348,14 +1391,14 @@ class OptionValue
1348 { 1391 {
1349 ensure_value(details); 1392 ensure_value(details);
1350 m_default = true; 1393 m_default = true;
1351 - m_long_name = &details->long_name(); 1394 + m_long_names = &details->long_names();
1352 m_value->parse(); 1395 m_value->parse();
1353 } 1396 }
1354 1397
1355 void 1398 void
1356 parse_no_value(const std::shared_ptr<const OptionDetails>& details) 1399 parse_no_value(const std::shared_ptr<const OptionDetails>& details)
1357 { 1400 {
1358 - m_long_name = &details->long_name(); 1401 + m_long_names = &details->long_names();
1359 } 1402 }
1360 1403
1361 #if defined(CXXOPTS_NULL_DEREF_IGNORE) 1404 #if defined(CXXOPTS_NULL_DEREF_IGNORE)
@@ -1388,7 +1431,7 @@ class OptionValue @@ -1388,7 +1431,7 @@ class OptionValue
1388 { 1431 {
1389 if (m_value == nullptr) { 1432 if (m_value == nullptr) {
1390 throw_or_mimic<exceptions::option_has_no_value>( 1433 throw_or_mimic<exceptions::option_has_no_value>(
1391 - m_long_name == nullptr ? "" : *m_long_name); 1434 + m_long_names == nullptr ? "" : first_or_empty(*m_long_names));
1392 } 1435 }
1393 1436
1394 #ifdef CXXOPTS_NO_RTTI 1437 #ifdef CXXOPTS_NO_RTTI
@@ -1409,7 +1452,7 @@ class OptionValue @@ -1409,7 +1452,7 @@ class OptionValue
1409 } 1452 }
1410 1453
1411 1454
1412 - const std::string* m_long_name = nullptr; 1455 + const OptionNames * m_long_names = nullptr;
1413 // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, 1456 // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
1414 // where the key has the string we point to. 1457 // where the key has the string we point to.
1415 std::shared_ptr<Value> m_value{}; 1458 std::shared_ptr<Value> m_value{};
@@ -1797,12 +1840,28 @@ class Options @@ -1797,12 +1840,28 @@ class Options
1797 ( 1840 (
1798 const std::string& group, 1841 const std::string& group,
1799 const std::string& s, 1842 const std::string& s,
1800 - const std::string& l, 1843 + const OptionNames& l,
1801 std::string desc, 1844 std::string desc,
1802 const std::shared_ptr<const Value>& value, 1845 const std::shared_ptr<const Value>& value,
1803 std::string arg_help 1846 std::string arg_help
1804 ); 1847 );
1805 1848
  1849 + void
  1850 + add_option
  1851 + (
  1852 + const std::string& group,
  1853 + const std::string& short_name,
  1854 + const std::string& single_long_name,
  1855 + std::string desc,
  1856 + const std::shared_ptr<const Value>& value,
  1857 + std::string arg_help
  1858 + )
  1859 + {
  1860 + OptionNames long_names;
  1861 + long_names.emplace_back(single_long_name);
  1862 + add_option(group, short_name, long_names, desc, value, arg_help);
  1863 + }
  1864 +
1806 //parse positional arguments into the given option 1865 //parse positional arguments into the given option
1807 void 1866 void
1808 parse_positional(std::string option); 1867 parse_positional(std::string option);
@@ -1897,7 +1956,6 @@ class OptionAdder @@ -1897,7 +1956,6 @@ class OptionAdder
1897 }; 1956 };
1898 1957
1899 namespace { 1958 namespace {
1900 -  
1901 constexpr size_t OPTION_LONGEST = 30; 1959 constexpr size_t OPTION_LONGEST = 30;
1902 constexpr size_t OPTION_DESC_GAP = 2; 1960 constexpr size_t OPTION_DESC_GAP = 2;
1903 1961
@@ -1908,7 +1966,7 @@ format_option @@ -1908,7 +1966,7 @@ format_option
1908 ) 1966 )
1909 { 1967 {
1910 const auto& s = o.s; 1968 const auto& s = o.s;
1911 - const auto& l = o.l; 1969 + const auto& l = first_or_empty(o.l);
1912 1970
1913 String result = " "; 1971 String result = " ";
1914 1972
@@ -2111,36 +2169,30 @@ OptionAdder::operator() @@ -2111,36 +2169,30 @@ OptionAdder::operator()
2111 std::string arg_help 2169 std::string arg_help
2112 ) 2170 )
2113 { 2171 {
2114 - std::string short_sw, long_sw;  
2115 - std::tie(short_sw, long_sw) = values::parser_tool::SplitSwitchDef(opts);  
2116 -  
2117 - if (!short_sw.length() && !long_sw.length())  
2118 - { 2172 + OptionNames option_names = values::parser_tool::split_option_names(opts);
  2173 + // Note: All names will be non-empty; but we must separate the short
  2174 + // (length-1) and longer names
  2175 + std::string short_name {""};
  2176 + auto first_short_name_iter =
  2177 + std::partition(option_names.begin(), option_names.end(),
  2178 + [&](const std::string& name) { return name.length() > 1; }
  2179 + );
  2180 + auto num_length_1_names = (option_names.end() - first_short_name_iter);
  2181 + switch(num_length_1_names) {
  2182 + case 1:
  2183 + short_name = *first_short_name_iter;
  2184 + option_names.erase(first_short_name_iter);
  2185 + case 0:
  2186 + break;
  2187 + default:
2119 throw_or_mimic<exceptions::invalid_option_format>(opts); 2188 throw_or_mimic<exceptions::invalid_option_format>(opts);
2120 - }  
2121 - else if (long_sw.length() == 1 && short_sw.length())  
2122 - {  
2123 - throw_or_mimic<exceptions::invalid_option_format>(opts);  
2124 - }  
2125 -  
2126 - auto option_names = []  
2127 - (  
2128 - const std::string &short_,  
2129 - const std::string &long_  
2130 - )  
2131 - {  
2132 - if (long_.length() == 1)  
2133 - {  
2134 - return std::make_tuple(long_, short_);  
2135 - }  
2136 - return std::make_tuple(short_, long_);  
2137 - }(short_sw, long_sw); 2189 + };
2138 2190
2139 m_options.add_option 2191 m_options.add_option
2140 ( 2192 (
2141 m_group, 2193 m_group,
2142 - std::get<0>(option_names),  
2143 - std::get<1>(option_names), 2194 + short_name,
  2195 + option_names,
2144 desc, 2196 desc,
2145 value, 2197 value,
2146 std::move(arg_help) 2198 std::move(arg_help)
@@ -2467,7 +2519,9 @@ OptionParser::finalise_aliases() @@ -2467,7 +2519,9 @@ OptionParser::finalise_aliases()
2467 auto& detail = *option.second; 2519 auto& detail = *option.second;
2468 auto hash = detail.hash(); 2520 auto hash = detail.hash();
2469 m_keys[detail.short_name()] = hash; 2521 m_keys[detail.short_name()] = hash;
2470 - m_keys[detail.long_name()] = hash; 2522 + for(const auto& long_name : detail.long_names()) {
  2523 + m_keys[long_name] = hash;
  2524 + }
2471 2525
2472 m_parsed.emplace(hash, OptionValue()); 2526 m_parsed.emplace(hash, OptionValue());
2473 } 2527 }
@@ -2490,7 +2544,7 @@ Options::add_option @@ -2490,7 +2544,7 @@ Options::add_option
2490 ( 2544 (
2491 const std::string& group, 2545 const std::string& group,
2492 const std::string& s, 2546 const std::string& s,
2493 - const std::string& l, 2547 + const OptionNames& l,
2494 std::string desc, 2548 std::string desc,
2495 const std::shared_ptr<const Value>& value, 2549 const std::shared_ptr<const Value>& value,
2496 std::string arg_help 2550 std::string arg_help
@@ -2504,9 +2558,8 @@ Options::add_option @@ -2504,9 +2558,8 @@ Options::add_option
2504 add_one_option(s, option); 2558 add_one_option(s, option);
2505 } 2559 }
2506 2560
2507 - if (!l.empty())  
2508 - {  
2509 - add_one_option(l, option); 2561 + for(const auto& long_name : l) {
  2562 + add_one_option(long_name, option);
2510 } 2563 }
2511 2564
2512 //add the help details 2565 //add the help details
@@ -2561,7 +2614,8 @@ Options::help_one_group(const std::string&amp; g) const @@ -2561,7 +2614,8 @@ Options::help_one_group(const std::string&amp; g) const
2561 2614
2562 for (const auto& o : group->second.options) 2615 for (const auto& o : group->second.options)
2563 { 2616 {
2564 - if (m_positional_set.find(o.l) != m_positional_set.end() && 2617 + assert(!o.l.empty());
  2618 + if (m_positional_set.find(o.l.front()) != m_positional_set.end() &&
2565 !m_show_positional) 2619 !m_show_positional)
2566 { 2620 {
2567 continue; 2621 continue;
@@ -2583,7 +2637,8 @@ Options::help_one_group(const std::string&amp; g) const @@ -2583,7 +2637,8 @@ Options::help_one_group(const std::string&amp; g) const
2583 auto fiter = format.begin(); 2637 auto fiter = format.begin();
2584 for (const auto& o : group->second.options) 2638 for (const auto& o : group->second.options)
2585 { 2639 {
2586 - if (m_positional_set.find(o.l) != m_positional_set.end() && 2640 + assert(!o.l.empty());
  2641 + if (m_positional_set.find(o.l.front()) != m_positional_set.end() &&
2587 !m_show_positional) 2642 !m_show_positional)
2588 { 2643 {
2589 continue; 2644 continue;
src/example.cpp
@@ -44,7 +44,7 @@ parse(int argc, const char* argv[]) @@ -44,7 +44,7 @@ parse(int argc, const char* argv[])
44 .set_tab_expansion() 44 .set_tab_expansion()
45 .allow_unrecognised_options() 45 .allow_unrecognised_options()
46 .add_options() 46 .add_options()
47 - ("a,apple", "an apple", cxxopts::value<bool>(apple)) 47 + ("a,apple,ringo", "an apple", cxxopts::value<bool>(apple))
48 ("b,bob", "Bob") 48 ("b,bob", "Bob")
49 ("char", "A character", cxxopts::value<char>()) 49 ("char", "A character", cxxopts::value<char>())
50 ("t,true", "True", cxxopts::value<bool>()->default_value("true")) 50 ("t,true", "True", cxxopts::value<bool>()->default_value("true"))
test/options.cpp
@@ -49,6 +49,9 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;) @@ -49,6 +49,9 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;)
49 options.add_options() 49 options.add_options()
50 ("long", "a long option") 50 ("long", "a long option")
51 ("s,short", "a short option") 51 ("s,short", "a short option")
  52 + ("quick,brown", "An option with multiple long names and no short name")
  53 + ("f,ox,jumped", "An option with multiple long names and a short name")
  54 + ("over,z,lazy,dog", "An option with multiple long names and a short name, not listed first")
52 ("value", "an option with a value", cxxopts::value<std::string>()) 55 ("value", "an option with a value", cxxopts::value<std::string>())
53 ("a,av", "a short option with a value", cxxopts::value<std::string>()) 56 ("a,av", "a short option with a value", cxxopts::value<std::string>())
54 ("6,six", "a short number option") 57 ("6,six", "a short number option")
@@ -67,6 +70,14 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;) @@ -67,6 +70,14 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;)
67 "-6", 70 "-6",
68 "-p", 71 "-p",
69 "--space", 72 "--space",
  73 + "--quick",
  74 + "--ox",
  75 + "-f",
  76 + "--brown",
  77 + "-z",
  78 + "--over",
  79 + "--dog",
  80 + "--lazy"
70 }); 81 });
71 82
72 auto** actual_argv = argv.argv(); 83 auto** actual_argv = argv.argv();
@@ -83,9 +94,12 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;) @@ -83,9 +94,12 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;)
83 CHECK(result.count("6") == 1); 94 CHECK(result.count("6") == 1);
84 CHECK(result.count("p") == 2); 95 CHECK(result.count("p") == 2);
85 CHECK(result.count("space") == 2); 96 CHECK(result.count("space") == 2);
  97 + CHECK(result.count("quick") == 2);
  98 + CHECK(result.count("f") == 2);
  99 + CHECK(result.count("z") == 4);
86 100
87 auto& arguments = result.arguments(); 101 auto& arguments = result.arguments();
88 - REQUIRE(arguments.size() == 7); 102 + REQUIRE(arguments.size() == 15);
89 CHECK(arguments[0].key() == "long"); 103 CHECK(arguments[0].key() == "long");
90 CHECK(arguments[0].value() == "true"); 104 CHECK(arguments[0].value() == "true");
91 CHECK(arguments[0].as<bool>() == true); 105 CHECK(arguments[0].as<bool>() == true);
@@ -786,24 +800,30 @@ TEST_CASE(&quot;Option add with add_option(string, Option)&quot;, &quot;[options]&quot;) { @@ -786,24 +800,30 @@ TEST_CASE(&quot;Option add with add_option(string, Option)&quot;, &quot;[options]&quot;) {
786 800
787 options.add_option("", option_1); 801 options.add_option("", option_1);
788 options.add_option("TEST", {"a,aggregate", "test option 2", cxxopts::value<int>(), "AGGREGATE"}); 802 options.add_option("TEST", {"a,aggregate", "test option 2", cxxopts::value<int>(), "AGGREGATE"});
  803 + options.add_option("TEST", {"multilong,m,multilong-alias", "test option 3", cxxopts::value<int>(), "An option with multiple long names"});
789 804
790 Argv argv_({ 805 Argv argv_({
791 "test", 806 "test",
792 "--test", 807 "--test",
793 "5", 808 "5",
794 "-a", 809 "-a",
795 - "4" 810 + "4",
  811 + "--multilong-alias",
  812 + "6"
796 }); 813 });
797 auto argc = argv_.argc(); 814 auto argc = argv_.argc();
798 auto** argv = argv_.argv(); 815 auto** argv = argv_.argv();
799 auto result = options.parse(argc, argv); 816 auto result = options.parse(argc, argv);
800 817
801 - CHECK(result.arguments().size()==2); 818 + CHECK(result.arguments().size() == 3);
802 CHECK(options.groups().size() == 2); 819 CHECK(options.groups().size() == 2);
803 CHECK(result.count("address") == 0); 820 CHECK(result.count("address") == 0);
804 CHECK(result.count("aggregate") == 1); 821 CHECK(result.count("aggregate") == 1);
805 CHECK(result.count("test") == 1); 822 CHECK(result.count("test") == 1);
806 CHECK(result["aggregate"].as<int>() == 4); 823 CHECK(result["aggregate"].as<int>() == 4);
  824 + CHECK(result["multilong"].as<int>() == 6);
  825 + CHECK(result["multilong-alias"].as<int>() == 6);
  826 + CHECK(result["m"].as<int>() == 6);
807 CHECK(result["test"].as<int>() == 5); 827 CHECK(result["test"].as<int>() == 5);
808 } 828 }
809 829