Skip to content

Commit 589d558

Browse files
Eyal Rozenbergvprus
authored andcommitted
Fixes #1: Support for multiple long names per option
An unintrusive implementation - no existing interfaces changed, and a single addition for obtaining all of the different long names. Notes: * Tests added for the new functionality, and existing tests expanded to take it into account. * It is now impossible to specify long names with commas in them (but then, that wasn't properly supported before either, more of an oversight). * The multiple long options are not included in the usage information - just the first one of them is printed
1 parent 4b81bea commit 589d558

File tree

9 files changed

+475
-144
lines changed

9 files changed

+475
-144
lines changed

include/boost/program_options/options_description.hpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <set>
2323
#include <map>
2424
#include <stdexcept>
25+
#include <utility>
2526

2627
#include <iosfwd>
2728

@@ -106,14 +107,16 @@ namespace program_options {
106107
/** Returns the canonical name for the option description to enable the user to
107108
recognised a matching option.
108109
1) For short options ('-', '/'), returns the short name prefixed.
109-
2) For long options ('--' / '-') returns the long name prefixed
110-
3) All other cases, returns the long name (if present) or the short name,
111-
unprefixed.
110+
2) For long options ('--' / '-') returns the first long name prefixed
111+
3) All other cases, returns the first long name (if present) or the short
112+
name, unprefixed.
112113
*/
113114
std::string canonical_display_name(int canonical_option_style = 0) const;
114115

115116
const std::string& long_name() const;
116117

118+
const std::pair<const std::string*, std::size_t> long_names() const;
119+
117120
/// Explanation of this option
118121
const std::string& description() const;
119122

@@ -129,9 +132,24 @@ namespace program_options {
129132

130133
private:
131134

132-
option_description& set_name(const char* name);
135+
option_description& set_names(const char* name);
136+
137+
/**
138+
* a one-character "switch" name - with its prefix,
139+
* so that this is either empty or has length 2 (e.g. "-c"
140+
*/
141+
std::string m_short_name;
142+
143+
/**
144+
* one or more names by which this option may be specified
145+
* on a command-line or in a config file, which are not
146+
* a single-letter switch. The names here are _without_
147+
* any prefix.
148+
*/
149+
std::vector<std::string> m_long_names;
150+
151+
std::string m_description;
133152

134-
std::string m_short_name, m_long_name, m_description;
135153
// shared_ptr is needed to simplify memory management in
136154
// copy ctor and destructor.
137155
shared_ptr<const value_semantic> m_value_semantic;

src/options_description.cpp

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,21 @@ namespace boost { namespace program_options {
4949
}
5050

5151
option_description::
52-
option_description(const char* name,
52+
option_description(const char* names,
5353
const value_semantic* s)
5454
: m_value_semantic(s)
5555
{
56-
this->set_name(name);
56+
this->set_names(names);
5757
}
5858

5959

6060
option_description::
61-
option_description(const char* name,
61+
option_description(const char* names,
6262
const value_semantic* s,
6363
const char* description)
6464
: m_description(description), m_value_semantic(s)
6565
{
66-
this->set_name(name);
66+
this->set_names(names);
6767
}
6868

6969
option_description::~option_description()
@@ -77,38 +77,42 @@ namespace boost { namespace program_options {
7777
bool short_ignore_case) const
7878
{
7979
match_result result = no_match;
80+
std::string local_option = (long_ignore_case ? tolower_(option) : option);
8081

81-
std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name));
82-
83-
if (!local_long_name.empty()) {
84-
85-
std::string local_option = (long_ignore_case ? tolower_(option) : option);
82+
for(std::vector<std::string>::const_iterator it(m_long_names.begin()); it != m_long_names.end(); it++)
83+
{
84+
std::string local_long_name((long_ignore_case ? tolower_(*it) : *it));
8685

87-
if (*local_long_name.rbegin() == '*')
88-
{
89-
// The name ends with '*'. Any specified name with the given
90-
// prefix is OK.
91-
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
92-
== 0)
93-
result = approximate_match;
94-
}
86+
if (!local_long_name.empty()) {
9587

96-
if (local_long_name == local_option)
97-
{
98-
result = full_match;
99-
}
100-
else if (approx)
101-
{
102-
if (local_long_name.find(local_option) == 0)
88+
89+
if ((result == no_match) && (*local_long_name.rbegin() == '*'))
90+
{
91+
// The name ends with '*'. Any specified name with the given
92+
// prefix is OK.
93+
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
94+
== 0)
95+
result = approximate_match;
96+
}
97+
98+
if (local_long_name == local_option)
99+
{
100+
result = full_match;
101+
break;
102+
}
103+
else if (approx)
103104
{
104-
result = approximate_match;
105+
if (local_long_name.find(local_option) == 0)
106+
{
107+
result = approximate_match;
108+
}
105109
}
106110
}
111+
107112
}
108-
113+
109114
if (result != full_match)
110115
{
111-
std::string local_option(short_ignore_case ? tolower_(option) : option);
112116
std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);
113117

114118
if (local_short_name == local_option)
@@ -122,30 +126,35 @@ namespace boost { namespace program_options {
122126

123127
const std::string&
124128
option_description::key(const std::string& option) const
125-
{
126-
if (!m_long_name.empty())
127-
if (m_long_name.find('*') != string::npos)
129+
{
130+
// We make the arbitrary choise of using the first long
131+
// name as the key, regardless of anything else
132+
if (!m_long_names.empty()) {
133+
const std::string& first_long_name = *m_long_names.begin();
134+
if (first_long_name.find('*') != string::npos)
128135
// The '*' character means we're long_name
129136
// matches only part of the input. So, returning
130137
// long name will remove some of the information,
131138
// and we have to return the option as specified
132139
// in the source.
133140
return option;
134141
else
135-
return m_long_name;
142+
return first_long_name;
143+
}
136144
else
137145
return m_short_name;
138146
}
139147

140148
std::string
141149
option_description::canonical_display_name(int prefix_style) const
142150
{
143-
if (!m_long_name.empty())
151+
// We prefer the first long name over any others
152+
if (!m_long_names.empty())
144153
{
145154
if (prefix_style == command_line_style::allow_long)
146-
return "--" + m_long_name;
155+
return "--" + *m_long_names.begin();
147156
if (prefix_style == command_line_style::allow_long_disguise)
148-
return "-" + m_long_name;
157+
return "-" + *m_long_names.begin();
149158
}
150159
// sanity check: m_short_name[0] should be '-' or '/'
151160
if (m_short_name.length() == 2)
@@ -155,8 +164,8 @@ namespace boost { namespace program_options {
155164
if (prefix_style == command_line_style::allow_dash_for_short)
156165
return string("-") + m_short_name[1];
157166
}
158-
if (!m_long_name.empty())
159-
return m_long_name;
167+
if (!m_long_names.empty())
168+
return *m_long_names.begin();
160169
else
161170
return m_short_name;
162171
}
@@ -165,21 +174,46 @@ namespace boost { namespace program_options {
165174
const std::string&
166175
option_description::long_name() const
167176
{
168-
return m_long_name;
177+
static std::string empty_string("");
178+
return m_long_names.empty() ? empty_string : *m_long_names.begin();
179+
}
180+
181+
const std::pair<const std::string*, std::size_t>
182+
option_description::long_names() const
183+
{
184+
return (m_long_names.empty())
185+
? std::pair<const std::string*, size_t>( NULL, 0 )
186+
: std::pair<const std::string*, size_t>( &(*m_long_names.begin()), m_long_names.size());
169187
}
170188

171189
option_description&
172-
option_description::set_name(const char* _name)
190+
option_description::set_names(const char* _names)
173191
{
174-
std::string name(_name);
175-
string::size_type n = name.find(',');
176-
if (n != string::npos) {
177-
assert(n == name.size()-2);
178-
m_long_name = name.substr(0, n);
179-
m_short_name = '-' + name.substr(n+1,1);
180-
} else {
181-
m_long_name = name;
192+
m_long_names.clear();
193+
std::istringstream iss(_names);
194+
std::string name;
195+
196+
while(std::getline(iss, name, ',')) {
197+
m_long_names.push_back(name);
198+
}
199+
assert(!m_long_names.empty() && "No option names were specified");
200+
201+
bool try_interpreting_last_name_as_a_switch = m_long_names.size() > 1;
202+
if (try_interpreting_last_name_as_a_switch) {
203+
const std::string& last_name = *m_long_names.rbegin();
204+
if (last_name.length() == 1) {
205+
m_short_name = '-' + last_name;
206+
m_long_names.pop_back();
207+
// The following caters to the (valid) input of ",c" for some
208+
// character c, where the caller only wants this option to have
209+
// a short name.
210+
if (m_long_names.size() == 1 && (*m_long_names.begin()).empty()) {
211+
m_long_names.clear();
212+
}
213+
}
182214
}
215+
// We could theoretically also ensure no remaining long names
216+
// are empty, or that none of them have length 1
183217
return *this;
184218
}
185219

@@ -200,12 +234,12 @@ namespace boost { namespace program_options {
200234
{
201235
if (!m_short_name.empty())
202236
{
203-
return m_long_name.empty()
237+
return m_long_names.empty()
204238
? m_short_name
205239
: string(m_short_name).append(" [ --").
206-
append(m_long_name).append(" ]");
240+
append(*m_long_names.begin()).append(" ]");
207241
}
208-
return string("--").append(m_long_name);
242+
return string("--").append(*m_long_names.begin());
209243
}
210244

211245
std::string

test/cmdline_test.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,23 +463,27 @@ void test_additional_parser()
463463
desc.add_options()
464464
("response-file", value<string>(), "response file")
465465
("foo", value<int>(), "foo")
466+
("bar,baz", value<int>(), "bar")
466467
;
467468

468469
vector<string> input;
469470
input.push_back("@config");
470471
input.push_back("--foo=1");
472+
input.push_back("--baz=11");
471473

472474
cmdline cmd(input);
473475
cmd.set_options_description(desc);
474476
cmd.set_additional_parser(at_option_parser);
475477

476478
vector<option> result = cmd.run();
477479

478-
BOOST_REQUIRE(result.size() == 2);
480+
BOOST_REQUIRE(result.size() == 3);
479481
BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
480482
BOOST_CHECK_EQUAL(result[0].value[0], "config");
481483
BOOST_CHECK_EQUAL(result[1].string_key, "foo");
482484
BOOST_CHECK_EQUAL(result[1].value[0], "1");
485+
BOOST_CHECK_EQUAL(result[2].string_key, "bar");
486+
BOOST_CHECK_EQUAL(result[2].value[0], "11");
483487

484488
// Test that invalid options returned by additional style
485489
// parser are detected.

test/config_test.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ b = true
55

66
[m1]
77
v1 = 1
8-
98
v2 = 2
9+
v3 = 3

0 commit comments

Comments
 (0)