@@ -25,6 +25,7 @@ THE SOFTWARE.
25
25
#ifndef CXXOPTS_HPP_INCLUDED
26
26
#define CXXOPTS_HPP_INCLUDED
27
27
28
+ #include < cassert>
28
29
#include < cctype>
29
30
#include < cstring>
30
31
#include < exception>
@@ -547,6 +548,8 @@ namespace cxxopts
547
548
#endif
548
549
}
549
550
551
+ using OptionNames = std::vector<std::string>;
552
+
550
553
namespace values
551
554
{
552
555
namespace parser_tool
@@ -718,7 +721,7 @@ namespace cxxopts
718
721
std::basic_regex<char > option_matcher
719
722
(" --([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)" );
720
723
std::basic_regex<char > option_specifier
721
- (" (( [[:alnum:]]),)? [ ]*( [[:alnum:]][-_[:alnum:]]*)? " );
724
+ (" ([[:alnum:]][-_[:alnum:]]*)(, [ ]*[[:alnum:]][-_[:alnum:]]*)* " );
722
725
723
726
} // namespace
724
727
@@ -761,19 +764,40 @@ namespace cxxopts
761
764
return !result.empty ();
762
765
}
763
766
764
- inline std::pair<std::string, std::string> SplitSwitchDef (const std::string &text)
767
+ // Gets the option names specified via a single, comma-separated string,
768
+ // and returns the separate, space-discarded, non-empty names
769
+ // (without considering which or how many are single-character)
770
+ inline OptionNames split_option_names (const std::string &text)
765
771
{
766
- std::match_results<const char *> result;
767
- std::regex_match (text.c_str (), result, option_specifier);
768
- if (result.empty ())
772
+ if (not std::regex_match (text.c_str (), option_specifier))
769
773
{
770
774
throw_or_mimic<invalid_option_format_error>(text);
771
775
}
772
776
773
- const std::string& short_sw = result[2 ];
774
- const std::string& long_sw = result[3 ];
777
+ OptionNames split_names;
778
+ std::string::size_type token_start_pos = 0 ;
779
+ auto length = text.length ();
775
780
776
- return std::pair<std::string, std::string>(short_sw, long_sw);
781
+ // TODO: Try avoiding having to tokenize ourselves
782
+ while (token_start_pos < length)
783
+ {
784
+ auto next_non_space_pos = text.find_first_not_of (' ' , token_start_pos);
785
+ if (next_non_space_pos == std::string::npos) {
786
+ throw_or_mimic<invalid_option_format_error>(text);
787
+ }
788
+ token_start_pos = next_non_space_pos;
789
+ auto next_delimiter_pos = text.find (' ,' , token_start_pos);
790
+ assert (next_delimiter_pos != token_start_pos && " empty option name" );
791
+ if (next_delimiter_pos == std::string::npos)
792
+ {
793
+ next_delimiter_pos = length;
794
+ }
795
+ auto token_length = next_delimiter_pos - token_start_pos;
796
+ split_names.emplace_back (text.substr (token_start_pos, token_length));
797
+
798
+ token_start_pos = next_delimiter_pos + 1 ;
799
+ }
800
+ return split_names;
777
801
}
778
802
779
803
inline ArguDesc ParseArgument (const char *arg, bool &matched)
@@ -1225,13 +1249,22 @@ namespace cxxopts
1225
1249
1226
1250
class OptionAdder ;
1227
1251
1252
+ inline
1253
+ CXXOPTS_NODISCARD
1254
+ const std::string&
1255
+ first_or_empty (const OptionNames& long_names)
1256
+ {
1257
+ static const std::string empty{" " };
1258
+ return long_names.empty () ? empty : long_names.front ();
1259
+ }
1260
+
1228
1261
class OptionDetails
1229
1262
{
1230
1263
public:
1231
1264
OptionDetails
1232
1265
(
1233
1266
std::string short_,
1234
- std::string long_,
1267
+ OptionNames long_,
1235
1268
String desc,
1236
1269
std::shared_ptr<const Value> val
1237
1270
)
@@ -1241,7 +1274,7 @@ namespace cxxopts
1241
1274
, m_value(std::move(val))
1242
1275
, m_count(0 )
1243
1276
{
1244
- m_hash = std::hash<std::string>{}(m_long + m_short);
1277
+ m_hash = std::hash<std::string>{}(first_long_name () + m_short);
1245
1278
}
1246
1279
1247
1280
OptionDetails (const OptionDetails& rhs)
@@ -1282,16 +1315,24 @@ namespace cxxopts
1282
1315
1283
1316
CXXOPTS_NODISCARD
1284
1317
const std::string&
1285
- long_name () const
1318
+ first_long_name () const
1286
1319
{
1287
- return m_long;
1320
+ return first_or_empty ( m_long) ;
1288
1321
}
1289
1322
1323
+
1290
1324
CXXOPTS_NODISCARD
1291
1325
const std::string&
1292
1326
essential_name () const
1293
1327
{
1294
- return m_long.empty () ? m_short : m_long;
1328
+ return m_long.empty () ? m_short : m_long.front ();
1329
+ }
1330
+
1331
+ CXXOPTS_NODISCARD
1332
+ const OptionNames &
1333
+ long_names () const
1334
+ {
1335
+ return m_long;
1295
1336
}
1296
1337
1297
1338
size_t
@@ -1302,7 +1343,7 @@ namespace cxxopts
1302
1343
1303
1344
private:
1304
1345
std::string m_short{};
1305
- std::string m_long{};
1346
+ OptionNames m_long{};
1306
1347
String m_desc{};
1307
1348
std::shared_ptr<const Value> m_value{};
1308
1349
int m_count;
@@ -1313,7 +1354,7 @@ namespace cxxopts
1313
1354
struct HelpOptionDetails
1314
1355
{
1315
1356
std::string s;
1316
- std::string l;
1357
+ OptionNames l;
1317
1358
String desc;
1318
1359
bool has_default;
1319
1360
std::string default_value;
@@ -1344,22 +1385,22 @@ namespace cxxopts
1344
1385
ensure_value (details);
1345
1386
++m_count;
1346
1387
m_value->parse (text);
1347
- m_long_name = &details->long_name ();
1388
+ m_long_names = &details->long_names ();
1348
1389
}
1349
1390
1350
1391
void
1351
1392
parse_default (const std::shared_ptr<const OptionDetails>& details)
1352
1393
{
1353
1394
ensure_value (details);
1354
1395
m_default = true ;
1355
- m_long_name = &details->long_name ();
1396
+ m_long_names = &details->long_names ();
1356
1397
m_value->parse ();
1357
1398
}
1358
1399
1359
1400
void
1360
1401
parse_no_value (const std::shared_ptr<const OptionDetails>& details)
1361
1402
{
1362
- m_long_name = &details->long_name ();
1403
+ m_long_names = &details->long_names ();
1363
1404
}
1364
1405
1365
1406
#if defined(CXXOPTS_NULL_DEREF_IGNORE)
@@ -1392,7 +1433,7 @@ namespace cxxopts
1392
1433
{
1393
1434
if (m_value == nullptr ) {
1394
1435
throw_or_mimic<option_has_no_value_exception>(
1395
- m_long_name == nullptr ? " " : *m_long_name );
1436
+ m_long_names == nullptr ? " " : first_or_empty (*m_long_names) );
1396
1437
}
1397
1438
1398
1439
#ifdef CXXOPTS_NO_RTTI
@@ -1413,7 +1454,7 @@ namespace cxxopts
1413
1454
}
1414
1455
1415
1456
1416
- const std::string* m_long_name = nullptr ;
1457
+ const OptionNames * m_long_names = nullptr ;
1417
1458
// Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
1418
1459
// where the key has the string we point to.
1419
1460
std::shared_ptr<Value> m_value{};
@@ -1801,12 +1842,28 @@ namespace cxxopts
1801
1842
(
1802
1843
const std::string& group,
1803
1844
const std::string& s,
1804
- const std::string & l,
1845
+ const OptionNames & l,
1805
1846
std::string desc,
1806
1847
const std::shared_ptr<const Value>& value,
1807
1848
std::string arg_help
1808
1849
);
1809
1850
1851
+ void
1852
+ add_option
1853
+ (
1854
+ const std::string& group,
1855
+ const std::string& short_name,
1856
+ const std::string& single_long_name,
1857
+ std::string desc,
1858
+ const std::shared_ptr<const Value>& value,
1859
+ std::string arg_help
1860
+ )
1861
+ {
1862
+ OptionNames long_names;
1863
+ long_names.emplace_back (single_long_name);
1864
+ add_option (group, short_name, long_names, desc, value, arg_help);
1865
+ }
1866
+
1810
1867
// parse positional arguments into the given option
1811
1868
void
1812
1869
parse_positional (std::string option);
@@ -1912,7 +1969,7 @@ namespace cxxopts
1912
1969
)
1913
1970
{
1914
1971
const auto & s = o.s ;
1915
- const auto & l = o.l ;
1972
+ const auto & l = first_or_empty ( o.l ) ;
1916
1973
1917
1974
String result = " " ;
1918
1975
@@ -2114,36 +2171,34 @@ OptionAdder::operator()
2114
2171
std::string arg_help
2115
2172
)
2116
2173
{
2117
- std::string short_sw, long_sw;
2118
- std::tie (short_sw, long_sw) = values::parser_tool::SplitSwitchDef (opts);
2119
-
2120
- if (!short_sw.length () && !long_sw.length ())
2121
- {
2122
- throw_or_mimic<invalid_option_format_error>(opts);
2174
+ OptionNames all_option_names = values::parser_tool::split_option_names (opts);
2175
+ // Note: All names will be non-empty!
2176
+ std::string short_sw {" " };
2177
+ OptionNames long_option_names = all_option_names;
2178
+ auto first_length_1_name_it = std::find_if (long_option_names.cbegin (), long_option_names.cend (),
2179
+ [&](const std::string& name) { return name.length () == 1 ; }
2180
+ );
2181
+ if (first_length_1_name_it != long_option_names.cend ()) {
2182
+ auto have_second_length_1_name = std::any_of (first_length_1_name_it + 1 , long_option_names.cend (),
2183
+ [&](const std::string& name) { return name.length () == 1 ; }
2184
+ );
2185
+ if (have_second_length_1_name) {
2186
+ throw_or_mimic<invalid_option_format_error>(opts);
2187
+ }
2188
+ short_sw = *first_length_1_name_it;
2189
+ long_option_names.erase (first_length_1_name_it);
2123
2190
}
2124
- else if (long_sw. length () == 1 && short_sw. length ())
2191
+ if (short_sw. empty () && long_option_names. empty ())
2125
2192
{
2126
2193
throw_or_mimic<invalid_option_format_error>(opts);
2127
2194
}
2128
2195
2129
- auto option_names = []
2130
- (
2131
- const std::string &short_,
2132
- const std::string &long_
2133
- )
2134
- {
2135
- if (long_.length () == 1 )
2136
- {
2137
- return std::make_tuple (long_, short_);
2138
- }
2139
- return std::make_tuple (short_, long_);
2140
- }(short_sw, long_sw);
2141
2196
2142
2197
m_options.add_option
2143
2198
(
2144
2199
m_group,
2145
- std::get< 0 >(option_names) ,
2146
- std::get< 1 >(option_names) ,
2200
+ short_sw ,
2201
+ long_option_names ,
2147
2202
desc,
2148
2203
value,
2149
2204
std::move (arg_help)
@@ -2470,7 +2525,9 @@ OptionParser::finalise_aliases()
2470
2525
auto & detail = *option.second ;
2471
2526
auto hash = detail.hash ();
2472
2527
m_keys[detail.short_name ()] = hash;
2473
- m_keys[detail.long_name ()] = hash;
2528
+ for (const auto & long_name : detail.long_names ()) {
2529
+ m_keys[long_name] = hash;
2530
+ }
2474
2531
2475
2532
m_parsed.emplace (hash, OptionValue ());
2476
2533
}
@@ -2493,7 +2550,7 @@ Options::add_option
2493
2550
(
2494
2551
const std::string& group,
2495
2552
const std::string& s,
2496
- const std::string & l,
2553
+ const OptionNames & l,
2497
2554
std::string desc,
2498
2555
const std::shared_ptr<const Value>& value,
2499
2556
std::string arg_help
@@ -2509,7 +2566,7 @@ Options::add_option
2509
2566
2510
2567
if (!l.empty ())
2511
2568
{
2512
- add_one_option (l , option);
2569
+ add_one_option (first_or_empty (l) , option);
2513
2570
}
2514
2571
2515
2572
// add the help details
@@ -2564,7 +2621,8 @@ Options::help_one_group(const std::string& g) const
2564
2621
2565
2622
for (const auto & o : group->second .options )
2566
2623
{
2567
- if (m_positional_set.find (o.l ) != m_positional_set.end () &&
2624
+ assert (not o.l .empty ());
2625
+ if (m_positional_set.find (o.l .front ()) != m_positional_set.end () &&
2568
2626
!m_show_positional)
2569
2627
{
2570
2628
continue ;
@@ -2586,7 +2644,8 @@ Options::help_one_group(const std::string& g) const
2586
2644
auto fiter = format.begin ();
2587
2645
for (const auto & o : group->second .options )
2588
2646
{
2589
- if (m_positional_set.find (o.l ) != m_positional_set.end () &&
2647
+ assert (not o.l .empty ());
2648
+ if (m_positional_set.find (o.l .front ()) != m_positional_set.end () &&
2590
2649
!m_show_positional)
2591
2650
{
2592
2651
continue ;
0 commit comments