Skip to content

Commit 978c27d

Browse files
committed
WIP on issue jarro2783#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. Not sure if that should change. * No tests for this functionality yet * Not sure whether option lookup works already with names other than the first...
1 parent 7474a66 commit 978c27d

File tree

2 files changed

+108
-49
lines changed

2 files changed

+108
-49
lines changed

include/cxxopts.hpp

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ THE SOFTWARE.
2525
#ifndef CXXOPTS_HPP_INCLUDED
2626
#define CXXOPTS_HPP_INCLUDED
2727

28+
#include <cassert>
2829
#include <cctype>
2930
#include <cstring>
3031
#include <exception>
@@ -547,6 +548,8 @@ namespace cxxopts
547548
#endif
548549
}
549550

551+
using OptionNames = std::vector<std::string>;
552+
550553
namespace values
551554
{
552555
namespace parser_tool
@@ -718,7 +721,7 @@ namespace cxxopts
718721
std::basic_regex<char> option_matcher
719722
("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
720723
std::basic_regex<char> option_specifier
721-
("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?");
724+
("([[:alnum:]][-_[:alnum:]]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
722725

723726
} // namespace
724727

@@ -761,19 +764,40 @@ namespace cxxopts
761764
return !result.empty();
762765
}
763766

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)
765771
{
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))
769773
{
770774
throw_or_mimic<invalid_option_format_error>(text);
771775
}
772776

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();
775780

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;
777801
}
778802

779803
inline ArguDesc ParseArgument(const char *arg, bool &matched)
@@ -1225,13 +1249,22 @@ namespace cxxopts
12251249

12261250
class OptionAdder;
12271251

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+
12281261
class OptionDetails
12291262
{
12301263
public:
12311264
OptionDetails
12321265
(
12331266
std::string short_,
1234-
std::string long_,
1267+
OptionNames long_,
12351268
String desc,
12361269
std::shared_ptr<const Value> val
12371270
)
@@ -1241,7 +1274,7 @@ namespace cxxopts
12411274
, m_value(std::move(val))
12421275
, m_count(0)
12431276
{
1244-
m_hash = std::hash<std::string>{}(m_long + m_short);
1277+
m_hash = std::hash<std::string>{}(first_long_name() + m_short);
12451278
}
12461279

12471280
OptionDetails(const OptionDetails& rhs)
@@ -1282,16 +1315,24 @@ namespace cxxopts
12821315

12831316
CXXOPTS_NODISCARD
12841317
const std::string&
1285-
long_name() const
1318+
first_long_name() const
12861319
{
1287-
return m_long;
1320+
return first_or_empty(m_long);
12881321
}
12891322

1323+
12901324
CXXOPTS_NODISCARD
12911325
const std::string&
12921326
essential_name() const
12931327
{
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;
12951336
}
12961337

12971338
size_t
@@ -1302,7 +1343,7 @@ namespace cxxopts
13021343

13031344
private:
13041345
std::string m_short{};
1305-
std::string m_long{};
1346+
OptionNames m_long{};
13061347
String m_desc{};
13071348
std::shared_ptr<const Value> m_value{};
13081349
int m_count;
@@ -1313,7 +1354,7 @@ namespace cxxopts
13131354
struct HelpOptionDetails
13141355
{
13151356
std::string s;
1316-
std::string l;
1357+
OptionNames l;
13171358
String desc;
13181359
bool has_default;
13191360
std::string default_value;
@@ -1344,22 +1385,22 @@ namespace cxxopts
13441385
ensure_value(details);
13451386
++m_count;
13461387
m_value->parse(text);
1347-
m_long_name = &details->long_name();
1388+
m_long_names = &details->long_names();
13481389
}
13491390

13501391
void
13511392
parse_default(const std::shared_ptr<const OptionDetails>& details)
13521393
{
13531394
ensure_value(details);
13541395
m_default = true;
1355-
m_long_name = &details->long_name();
1396+
m_long_names = &details->long_names();
13561397
m_value->parse();
13571398
}
13581399

13591400
void
13601401
parse_no_value(const std::shared_ptr<const OptionDetails>& details)
13611402
{
1362-
m_long_name = &details->long_name();
1403+
m_long_names = &details->long_names();
13631404
}
13641405

13651406
#if defined(CXXOPTS_NULL_DEREF_IGNORE)
@@ -1392,7 +1433,7 @@ namespace cxxopts
13921433
{
13931434
if (m_value == nullptr) {
13941435
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));
13961437
}
13971438

13981439
#ifdef CXXOPTS_NO_RTTI
@@ -1413,7 +1454,7 @@ namespace cxxopts
14131454
}
14141455

14151456

1416-
const std::string* m_long_name = nullptr;
1457+
const OptionNames * m_long_names = nullptr;
14171458
// Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
14181459
// where the key has the string we point to.
14191460
std::shared_ptr<Value> m_value{};
@@ -1801,12 +1842,28 @@ namespace cxxopts
18011842
(
18021843
const std::string& group,
18031844
const std::string& s,
1804-
const std::string& l,
1845+
const OptionNames& l,
18051846
std::string desc,
18061847
const std::shared_ptr<const Value>& value,
18071848
std::string arg_help
18081849
);
18091850

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+
18101867
//parse positional arguments into the given option
18111868
void
18121869
parse_positional(std::string option);
@@ -1912,7 +1969,7 @@ namespace cxxopts
19121969
)
19131970
{
19141971
const auto& s = o.s;
1915-
const auto& l = o.l;
1972+
const auto& l = first_or_empty(o.l);
19161973

19171974
String result = " ";
19181975

@@ -2114,36 +2171,34 @@ OptionAdder::operator()
21142171
std::string arg_help
21152172
)
21162173
{
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);
21232190
}
2124-
else if (long_sw.length() == 1 && short_sw.length())
2191+
if (short_sw.empty() && long_option_names.empty())
21252192
{
21262193
throw_or_mimic<invalid_option_format_error>(opts);
21272194
}
21282195

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);
21412196

21422197
m_options.add_option
21432198
(
21442199
m_group,
2145-
std::get<0>(option_names),
2146-
std::get<1>(option_names),
2200+
short_sw,
2201+
long_option_names,
21472202
desc,
21482203
value,
21492204
std::move(arg_help)
@@ -2470,7 +2525,9 @@ OptionParser::finalise_aliases()
24702525
auto& detail = *option.second;
24712526
auto hash = detail.hash();
24722527
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+
}
24742531

24752532
m_parsed.emplace(hash, OptionValue());
24762533
}
@@ -2493,7 +2550,7 @@ Options::add_option
24932550
(
24942551
const std::string& group,
24952552
const std::string& s,
2496-
const std::string& l,
2553+
const OptionNames& l,
24972554
std::string desc,
24982555
const std::shared_ptr<const Value>& value,
24992556
std::string arg_help
@@ -2509,7 +2566,7 @@ Options::add_option
25092566

25102567
if (!l.empty())
25112568
{
2512-
add_one_option(l, option);
2569+
add_one_option(first_or_empty(l), option);
25132570
}
25142571

25152572
//add the help details
@@ -2564,7 +2621,8 @@ Options::help_one_group(const std::string& g) const
25642621

25652622
for (const auto& o : group->second.options)
25662623
{
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() &&
25682626
!m_show_positional)
25692627
{
25702628
continue;
@@ -2586,7 +2644,8 @@ Options::help_one_group(const std::string& g) const
25862644
auto fiter = format.begin();
25872645
for (const auto& o : group->second.options)
25882646
{
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() &&
25902649
!m_show_positional)
25912650
{
25922651
continue;

src/example.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ parse(int argc, const char* argv[])
4343
.set_tab_expansion()
4444
.allow_unrecognised_options()
4545
.add_options()
46-
("a,apple", "an apple", cxxopts::value<bool>(apple))
46+
("a,apple,ringo", "an apple", cxxopts::value<bool>(apple))
4747
("b,bob", "Bob")
4848
("char", "A character", cxxopts::value<char>())
4949
("t,true", "True", cxxopts::value<bool>()->default_value("true"))

0 commit comments

Comments
 (0)