Commit e976f964c3ee6a4f6faa836ecb38f3ff8b043114
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.
Showing
4 changed files
with
152 additions
and
76 deletions
CHANGELOG.md
include/cxxopts.hpp
| ... | ... | @@ -27,6 +27,7 @@ THE SOFTWARE. |
| 27 | 27 | #ifndef CXXOPTS_HPP_INCLUDED |
| 28 | 28 | #define CXXOPTS_HPP_INCLUDED |
| 29 | 29 | |
| 30 | +#include <cassert> | |
| 30 | 31 | #include <cctype> |
| 31 | 32 | #include <cstring> |
| 32 | 33 | #include <exception> |
| ... | ... | @@ -101,6 +102,7 @@ static constexpr struct { |
| 101 | 102 | #include <unicode/unistr.h> |
| 102 | 103 | |
| 103 | 104 | namespace cxxopts { |
| 105 | + | |
| 104 | 106 | using String = icu::UnicodeString; |
| 105 | 107 | |
| 106 | 108 | inline |
| ... | ... | @@ -248,6 +250,7 @@ end(const icu::UnicodeString& s) |
| 248 | 250 | #else |
| 249 | 251 | |
| 250 | 252 | namespace cxxopts { |
| 253 | + | |
| 251 | 254 | using String = std::string; |
| 252 | 255 | |
| 253 | 256 | template <typename T> |
| ... | ... | @@ -522,6 +525,7 @@ class incorrect_argument_type : public parsing |
| 522 | 525 | |
| 523 | 526 | } // namespace exceptions |
| 524 | 527 | |
| 528 | + | |
| 525 | 529 | template <typename T> |
| 526 | 530 | void throw_or_mimic(const std::string& text) |
| 527 | 531 | { |
| ... | ... | @@ -541,6 +545,8 @@ void throw_or_mimic(const std::string& text) |
| 541 | 545 | #endif |
| 542 | 546 | } |
| 543 | 547 | |
| 548 | +using OptionNames = std::vector<std::string>; | |
| 549 | + | |
| 544 | 550 | namespace values { |
| 545 | 551 | |
| 546 | 552 | namespace parser_tool { |
| ... | ... | @@ -624,28 +630,44 @@ inline bool IsFalseText(const std::string &text) |
| 624 | 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 | 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 | 673 | inline ArguDesc ParseArgument(const char *arg, bool &matched) |
| ... | ... | @@ -712,7 +734,8 @@ std::basic_regex<char> falsy_pattern |
| 712 | 734 | std::basic_regex<char> option_matcher |
| 713 | 735 | ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); |
| 714 | 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 | 740 | } // namespace |
| 718 | 741 | |
| ... | ... | @@ -755,19 +778,23 @@ inline bool IsFalseText(const std::string &text) |
| 755 | 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 | 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 | 800 | inline ArguDesc ParseArgument(const char *arg, bool &matched) |
| ... | ... | @@ -1221,13 +1248,22 @@ value(T& t) |
| 1221 | 1248 | |
| 1222 | 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 | 1260 | class OptionDetails |
| 1225 | 1261 | { |
| 1226 | 1262 | public: |
| 1227 | 1263 | OptionDetails |
| 1228 | 1264 | ( |
| 1229 | 1265 | std::string short_, |
| 1230 | - std::string long_, | |
| 1266 | + OptionNames long_, | |
| 1231 | 1267 | String desc, |
| 1232 | 1268 | std::shared_ptr<const Value> val |
| 1233 | 1269 | ) |
| ... | ... | @@ -1237,7 +1273,7 @@ class OptionDetails |
| 1237 | 1273 | , m_value(std::move(val)) |
| 1238 | 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 | 1279 | OptionDetails(const OptionDetails& rhs) |
| ... | ... | @@ -1278,16 +1314,23 @@ class OptionDetails |
| 1278 | 1314 | |
| 1279 | 1315 | CXXOPTS_NODISCARD |
| 1280 | 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 | 1322 | CXXOPTS_NODISCARD |
| 1287 | 1323 | const std::string& |
| 1288 | 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 | 1336 | size_t |
| ... | ... | @@ -1298,7 +1341,7 @@ class OptionDetails |
| 1298 | 1341 | |
| 1299 | 1342 | private: |
| 1300 | 1343 | std::string m_short{}; |
| 1301 | - std::string m_long{}; | |
| 1344 | + OptionNames m_long{}; | |
| 1302 | 1345 | String m_desc{}; |
| 1303 | 1346 | std::shared_ptr<const Value> m_value{}; |
| 1304 | 1347 | int m_count; |
| ... | ... | @@ -1309,7 +1352,7 @@ class OptionDetails |
| 1309 | 1352 | struct HelpOptionDetails |
| 1310 | 1353 | { |
| 1311 | 1354 | std::string s; |
| 1312 | - std::string l; | |
| 1355 | + OptionNames l; | |
| 1313 | 1356 | String desc; |
| 1314 | 1357 | bool has_default; |
| 1315 | 1358 | std::string default_value; |
| ... | ... | @@ -1340,7 +1383,7 @@ class OptionValue |
| 1340 | 1383 | ensure_value(details); |
| 1341 | 1384 | ++m_count; |
| 1342 | 1385 | m_value->parse(text); |
| 1343 | - m_long_name = &details->long_name(); | |
| 1386 | + m_long_names = &details->long_names(); | |
| 1344 | 1387 | } |
| 1345 | 1388 | |
| 1346 | 1389 | void |
| ... | ... | @@ -1348,14 +1391,14 @@ class OptionValue |
| 1348 | 1391 | { |
| 1349 | 1392 | ensure_value(details); |
| 1350 | 1393 | m_default = true; |
| 1351 | - m_long_name = &details->long_name(); | |
| 1394 | + m_long_names = &details->long_names(); | |
| 1352 | 1395 | m_value->parse(); |
| 1353 | 1396 | } |
| 1354 | 1397 | |
| 1355 | 1398 | void |
| 1356 | 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 | 1404 | #if defined(CXXOPTS_NULL_DEREF_IGNORE) |
| ... | ... | @@ -1388,7 +1431,7 @@ class OptionValue |
| 1388 | 1431 | { |
| 1389 | 1432 | if (m_value == nullptr) { |
| 1390 | 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 | 1437 | #ifdef CXXOPTS_NO_RTTI |
| ... | ... | @@ -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 | 1456 | // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, |
| 1414 | 1457 | // where the key has the string we point to. |
| 1415 | 1458 | std::shared_ptr<Value> m_value{}; |
| ... | ... | @@ -1797,12 +1840,28 @@ class Options |
| 1797 | 1840 | ( |
| 1798 | 1841 | const std::string& group, |
| 1799 | 1842 | const std::string& s, |
| 1800 | - const std::string& l, | |
| 1843 | + const OptionNames& l, | |
| 1801 | 1844 | std::string desc, |
| 1802 | 1845 | const std::shared_ptr<const Value>& value, |
| 1803 | 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 | 1865 | //parse positional arguments into the given option |
| 1807 | 1866 | void |
| 1808 | 1867 | parse_positional(std::string option); |
| ... | ... | @@ -1897,7 +1956,6 @@ class OptionAdder |
| 1897 | 1956 | }; |
| 1898 | 1957 | |
| 1899 | 1958 | namespace { |
| 1900 | - | |
| 1901 | 1959 | constexpr size_t OPTION_LONGEST = 30; |
| 1902 | 1960 | constexpr size_t OPTION_DESC_GAP = 2; |
| 1903 | 1961 | |
| ... | ... | @@ -1908,7 +1966,7 @@ format_option |
| 1908 | 1966 | ) |
| 1909 | 1967 | { |
| 1910 | 1968 | const auto& s = o.s; |
| 1911 | - const auto& l = o.l; | |
| 1969 | + const auto& l = first_or_empty(o.l); | |
| 1912 | 1970 | |
| 1913 | 1971 | String result = " "; |
| 1914 | 1972 | |
| ... | ... | @@ -2111,36 +2169,30 @@ OptionAdder::operator() |
| 2111 | 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 | 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 | 2191 | m_options.add_option |
| 2140 | 2192 | ( |
| 2141 | 2193 | m_group, |
| 2142 | - std::get<0>(option_names), | |
| 2143 | - std::get<1>(option_names), | |
| 2194 | + short_name, | |
| 2195 | + option_names, | |
| 2144 | 2196 | desc, |
| 2145 | 2197 | value, |
| 2146 | 2198 | std::move(arg_help) |
| ... | ... | @@ -2467,7 +2519,9 @@ OptionParser::finalise_aliases() |
| 2467 | 2519 | auto& detail = *option.second; |
| 2468 | 2520 | auto hash = detail.hash(); |
| 2469 | 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 | 2526 | m_parsed.emplace(hash, OptionValue()); |
| 2473 | 2527 | } |
| ... | ... | @@ -2490,7 +2544,7 @@ Options::add_option |
| 2490 | 2544 | ( |
| 2491 | 2545 | const std::string& group, |
| 2492 | 2546 | const std::string& s, |
| 2493 | - const std::string& l, | |
| 2547 | + const OptionNames& l, | |
| 2494 | 2548 | std::string desc, |
| 2495 | 2549 | const std::shared_ptr<const Value>& value, |
| 2496 | 2550 | std::string arg_help |
| ... | ... | @@ -2504,9 +2558,8 @@ Options::add_option |
| 2504 | 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 | 2565 | //add the help details |
| ... | ... | @@ -2561,7 +2614,8 @@ Options::help_one_group(const std::string& g) const |
| 2561 | 2614 | |
| 2562 | 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 | 2619 | !m_show_positional) |
| 2566 | 2620 | { |
| 2567 | 2621 | continue; |
| ... | ... | @@ -2583,7 +2637,8 @@ Options::help_one_group(const std::string& g) const |
| 2583 | 2637 | auto fiter = format.begin(); |
| 2584 | 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 | 2642 | !m_show_positional) |
| 2588 | 2643 | { |
| 2589 | 2644 | continue; | ... | ... |
src/example.cpp
| ... | ... | @@ -44,7 +44,7 @@ parse(int argc, const char* argv[]) |
| 44 | 44 | .set_tab_expansion() |
| 45 | 45 | .allow_unrecognised_options() |
| 46 | 46 | .add_options() |
| 47 | - ("a,apple", "an apple", cxxopts::value<bool>(apple)) | |
| 47 | + ("a,apple,ringo", "an apple", cxxopts::value<bool>(apple)) | |
| 48 | 48 | ("b,bob", "Bob") |
| 49 | 49 | ("char", "A character", cxxopts::value<char>()) |
| 50 | 50 | ("t,true", "True", cxxopts::value<bool>()->default_value("true")) | ... | ... |
test/options.cpp
| ... | ... | @@ -49,6 +49,9 @@ TEST_CASE("Basic options", "[options]") |
| 49 | 49 | options.add_options() |
| 50 | 50 | ("long", "a long option") |
| 51 | 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 | 55 | ("value", "an option with a value", cxxopts::value<std::string>()) |
| 53 | 56 | ("a,av", "a short option with a value", cxxopts::value<std::string>()) |
| 54 | 57 | ("6,six", "a short number option") |
| ... | ... | @@ -67,6 +70,14 @@ TEST_CASE("Basic options", "[options]") |
| 67 | 70 | "-6", |
| 68 | 71 | "-p", |
| 69 | 72 | "--space", |
| 73 | + "--quick", | |
| 74 | + "--ox", | |
| 75 | + "-f", | |
| 76 | + "--brown", | |
| 77 | + "-z", | |
| 78 | + "--over", | |
| 79 | + "--dog", | |
| 80 | + "--lazy" | |
| 70 | 81 | }); |
| 71 | 82 | |
| 72 | 83 | auto** actual_argv = argv.argv(); |
| ... | ... | @@ -83,9 +94,12 @@ TEST_CASE("Basic options", "[options]") |
| 83 | 94 | CHECK(result.count("6") == 1); |
| 84 | 95 | CHECK(result.count("p") == 2); |
| 85 | 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 | 101 | auto& arguments = result.arguments(); |
| 88 | - REQUIRE(arguments.size() == 7); | |
| 102 | + REQUIRE(arguments.size() == 15); | |
| 89 | 103 | CHECK(arguments[0].key() == "long"); |
| 90 | 104 | CHECK(arguments[0].value() == "true"); |
| 91 | 105 | CHECK(arguments[0].as<bool>() == true); |
| ... | ... | @@ -786,24 +800,30 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") { |
| 786 | 800 | |
| 787 | 801 | options.add_option("", option_1); |
| 788 | 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 | 805 | Argv argv_({ |
| 791 | 806 | "test", |
| 792 | 807 | "--test", |
| 793 | 808 | "5", |
| 794 | 809 | "-a", |
| 795 | - "4" | |
| 810 | + "4", | |
| 811 | + "--multilong-alias", | |
| 812 | + "6" | |
| 796 | 813 | }); |
| 797 | 814 | auto argc = argv_.argc(); |
| 798 | 815 | auto** argv = argv_.argv(); |
| 799 | 816 | auto result = options.parse(argc, argv); |
| 800 | 817 | |
| 801 | - CHECK(result.arguments().size()==2); | |
| 818 | + CHECK(result.arguments().size() == 3); | |
| 802 | 819 | CHECK(options.groups().size() == 2); |
| 803 | 820 | CHECK(result.count("address") == 0); |
| 804 | 821 | CHECK(result.count("aggregate") == 1); |
| 805 | 822 | CHECK(result.count("test") == 1); |
| 806 | 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 | 827 | CHECK(result["test"].as<int>() == 5); |
| 808 | 828 | } |
| 809 | 829 | ... | ... |