Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple long names #53

Merged
merged 1 commit into from Jun 27, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -22,6 +22,7 @@
#include <set>
#include <map>
#include <stdexcept>
#include <utility>

#include <iosfwd>

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

const std::string& long_name() const;

const std::pair<const std::string*, std::size_t> long_names() const;

/// Explanation of this option
const std::string& description() const;

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

private:

option_description& set_name(const char* name);
option_description& set_names(const char* name);

/**
* a one-character "switch" name - with its prefix,
* so that this is either empty or has length 2 (e.g. "-c"
*/
std::string m_short_name;

/**
* one or more names by which this option may be specified
* on a command-line or in a config file, which are not
* a single-letter switch. The names here are _without_
* any prefix.
*/
std::vector<std::string> m_long_names;

std::string m_description;

std::string m_short_name, m_long_name, m_description;
// shared_ptr is needed to simplify memory management in
// copy ctor and destructor.
shared_ptr<const value_semantic> m_value_semantic;
@@ -49,21 +49,21 @@ namespace boost { namespace program_options {
}

option_description::
option_description(const char* name,
option_description(const char* names,
const value_semantic* s)
: m_value_semantic(s)
{
this->set_name(name);
this->set_names(names);
}


option_description::
option_description(const char* name,
option_description(const char* names,
const value_semantic* s,
const char* description)
: m_description(description), m_value_semantic(s)
{
this->set_name(name);
this->set_names(names);
}

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

std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name));

if (!local_long_name.empty()) {

std::string local_option = (long_ignore_case ? tolower_(option) : option);
for(std::vector<std::string>::const_iterator it(m_long_names.begin()); it != m_long_names.end(); it++)
{
std::string local_long_name((long_ignore_case ? tolower_(*it) : *it));

if (*local_long_name.rbegin() == '*')
{
// The name ends with '*'. Any specified name with the given
// prefix is OK.
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
== 0)
result = approximate_match;
}
if (!local_long_name.empty()) {

if (local_long_name == local_option)
{
result = full_match;
}
else if (approx)
{
if (local_long_name.find(local_option) == 0)

if ((result == no_match) && (*local_long_name.rbegin() == '*'))
{
// The name ends with '*'. Any specified name with the given
// prefix is OK.
if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
== 0)
result = approximate_match;
}

if (local_long_name == local_option)
{
result = full_match;
break;
}
else if (approx)
{
result = approximate_match;
if (local_long_name.find(local_option) == 0)
{
result = approximate_match;
}
}
}

}

if (result != full_match)
{
std::string local_option(short_ignore_case ? tolower_(option) : option);
std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);

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

const std::string&
option_description::key(const std::string& option) const
{
if (!m_long_name.empty())
if (m_long_name.find('*') != string::npos)
{
// We make the arbitrary choise of using the first long
// name as the key, regardless of anything else
if (!m_long_names.empty()) {
const std::string& first_long_name = *m_long_names.begin();
if (first_long_name.find('*') != string::npos)
// The '*' character means we're long_name
// matches only part of the input. So, returning
// long name will remove some of the information,
// and we have to return the option as specified
// in the source.
return option;
else
return m_long_name;
return first_long_name;
}
else
return m_short_name;
}

std::string
option_description::canonical_display_name(int prefix_style) const
{
if (!m_long_name.empty())
// We prefer the first long name over any others
if (!m_long_names.empty())
{
if (prefix_style == command_line_style::allow_long)
return "--" + m_long_name;
return "--" + *m_long_names.begin();
if (prefix_style == command_line_style::allow_long_disguise)
return "-" + m_long_name;
return "-" + *m_long_names.begin();
}
// sanity check: m_short_name[0] should be '-' or '/'
if (m_short_name.length() == 2)
@@ -155,8 +164,8 @@ namespace boost { namespace program_options {
if (prefix_style == command_line_style::allow_dash_for_short)
return string("-") + m_short_name[1];
}
if (!m_long_name.empty())
return m_long_name;
if (!m_long_names.empty())
return *m_long_names.begin();
else
return m_short_name;
}
@@ -165,21 +174,46 @@ namespace boost { namespace program_options {
const std::string&
option_description::long_name() const
{
return m_long_name;
static std::string empty_string("");
return m_long_names.empty() ? empty_string : *m_long_names.begin();
}

const std::pair<const std::string*, std::size_t>
option_description::long_names() const
{
return (m_long_names.empty())
? std::pair<const std::string*, size_t>( NULL, 0 )
: std::pair<const std::string*, size_t>( &(*m_long_names.begin()), m_long_names.size());
}

option_description&
option_description::set_name(const char* _name)
option_description::set_names(const char* _names)
{
std::string name(_name);
string::size_type n = name.find(',');
if (n != string::npos) {
assert(n == name.size()-2);
m_long_name = name.substr(0, n);
m_short_name = '-' + name.substr(n+1,1);
} else {
m_long_name = name;
m_long_names.clear();
std::istringstream iss(_names);
std::string name;

while(std::getline(iss, name, ',')) {
m_long_names.push_back(name);
}
assert(!m_long_names.empty() && "No option names were specified");

bool try_interpreting_last_name_as_a_switch = m_long_names.size() > 1;
if (try_interpreting_last_name_as_a_switch) {
const std::string& last_name = *m_long_names.rbegin();
if (last_name.length() == 1) {
m_short_name = '-' + last_name;
m_long_names.pop_back();
// The following caters to the (valid) input of ",c" for some
// character c, where the caller only wants this option to have
// a short name.
if (m_long_names.size() == 1 && (*m_long_names.begin()).empty()) {
m_long_names.clear();
}
}
}
// We could theoretically also ensure no remaining long names
// are empty, or that none of them have length 1
return *this;
}

@@ -200,12 +234,12 @@ namespace boost { namespace program_options {
{
if (!m_short_name.empty())
{
return m_long_name.empty()
return m_long_names.empty()
? m_short_name
: string(m_short_name).append(" [ --").
append(m_long_name).append(" ]");
append(*m_long_names.begin()).append(" ]");
}
return string("--").append(m_long_name);
return string("--").append(*m_long_names.begin());
}

std::string
@@ -463,23 +463,27 @@ void test_additional_parser()
desc.add_options()
("response-file", value<string>(), "response file")
("foo", value<int>(), "foo")
("bar,baz", value<int>(), "bar")
;

vector<string> input;
input.push_back("@config");
input.push_back("--foo=1");
input.push_back("--baz=11");

cmdline cmd(input);
cmd.set_options_description(desc);
cmd.set_additional_parser(at_option_parser);

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

BOOST_REQUIRE(result.size() == 2);
BOOST_REQUIRE(result.size() == 3);
BOOST_CHECK_EQUAL(result[0].string_key, "response-file");
BOOST_CHECK_EQUAL(result[0].value[0], "config");
BOOST_CHECK_EQUAL(result[1].string_key, "foo");
BOOST_CHECK_EQUAL(result[1].value[0], "1");
BOOST_CHECK_EQUAL(result[2].string_key, "bar");
BOOST_CHECK_EQUAL(result[2].value[0], "11");

// Test that invalid options returned by additional style
// parser are detected.
@@ -5,5 +5,5 @@ b = true

[m1]
v1 = 1

v2 = 2
v3 = 3
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.