mirror of
https://github.com/boostorg/program_options.git
synced 2026-01-19 04:22:15 +00:00
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
This commit is contained in:
committed by
Vladimir Prus
parent
4b81beaaa9
commit
589d55837e
@@ -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() == '*'))
|
||||
{
|
||||
result = approximate_match;
|
||||
// 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)
|
||||
{
|
||||
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,9 +126,12 @@ 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,
|
||||
@@ -132,7 +139,8 @@ namespace boost { namespace program_options {
|
||||
// in the source.
|
||||
return option;
|
||||
else
|
||||
return m_long_name;
|
||||
return first_long_name;
|
||||
}
|
||||
else
|
||||
return m_short_name;
|
||||
}
|
||||
@@ -140,12 +148,13 @@ namespace boost { namespace program_options {
|
||||
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,11 +463,13 @@ 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);
|
||||
@@ -475,11 +477,13 @@ void test_additional_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
|
||||
|
||||
@@ -45,6 +45,56 @@ void test_ambiguous()
|
||||
}
|
||||
|
||||
|
||||
void test_ambiguous_long()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("cfgfile,c", value<string>()->multitoken(), "the config file")
|
||||
("output,c", value<string>(), "the output file")
|
||||
("output,o", value<string>(), "the output file")
|
||||
;
|
||||
|
||||
const char* cmdline[] = {"program", "--cfgfile", "file", "--output", "anotherfile"};
|
||||
|
||||
variables_map vm;
|
||||
try {
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc), vm);
|
||||
}
|
||||
catch (ambiguous_option& e)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(e.alternatives().size(), 2);
|
||||
BOOST_CHECK_EQUAL(e.get_option_name(), "--output");
|
||||
BOOST_CHECK_EQUAL(e.alternatives()[0], "output");
|
||||
BOOST_CHECK_EQUAL(e.alternatives()[1], "output");
|
||||
}
|
||||
}
|
||||
|
||||
void test_ambiguous_multiple_long_names()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("cfgfile,foo,c", value<string>()->multitoken(), "the config file")
|
||||
("output,foo,o", value<string>(), "the output file")
|
||||
;
|
||||
|
||||
const char* cmdline[] = {"program", "--foo", "file"};
|
||||
|
||||
variables_map vm;
|
||||
try {
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc), vm);
|
||||
}
|
||||
catch (ambiguous_option& e)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(e.alternatives().size(), 2);
|
||||
BOOST_CHECK_EQUAL(e.get_option_name(), "--foo");
|
||||
BOOST_CHECK_EQUAL(e.alternatives()[0], "cfgfile");
|
||||
BOOST_CHECK_EQUAL(e.alternatives()[1], "output");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void test_unknown_option()
|
||||
{
|
||||
@@ -100,7 +150,6 @@ void test_multiple_values()
|
||||
}
|
||||
|
||||
|
||||
|
||||
void test_multiple_occurrences()
|
||||
{
|
||||
options_description desc;
|
||||
@@ -109,20 +158,67 @@ void test_multiple_occurrences()
|
||||
;
|
||||
|
||||
const char* cmdline[] = {"program", "--cfgfile", "file", "-c", "anotherfile"};
|
||||
|
||||
|
||||
variables_map vm;
|
||||
try {
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (multiple_occurrences& e)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
|
||||
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
|
||||
BOOST_CHECK_EQUAL(string(e.what()), "option '--cfgfile' cannot be specified more than once");
|
||||
}
|
||||
}
|
||||
|
||||
void test_multiple_occurrences_with_different_names()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("cfgfile,config-file,c", value<string>(), "the configfile")
|
||||
;
|
||||
|
||||
const char* cmdline[] = {"program", "--config-file", "file", "--cfgfile", "anotherfile"};
|
||||
|
||||
variables_map vm;
|
||||
try {
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (multiple_occurrences& e)
|
||||
{
|
||||
BOOST_CHECK( (e.get_option_name() == "--cfgfile") || (e.get_option_name() == "--configfile"));
|
||||
BOOST_CHECK(
|
||||
(string(e.what()) == "option '--cfgfile' cannot be specified more than once") ||
|
||||
(string(e.what()) == "option '--configfile' cannot be specified more than once")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_multiple_occurrences_with_non_key_names()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("cfgfile,config-file,c", value<string>(), "the configfile")
|
||||
;
|
||||
|
||||
const char* cmdline[] = {"program", "--config-file", "file", "-c", "anotherfile"};
|
||||
|
||||
variables_map vm;
|
||||
try {
|
||||
store(parse_command_line(sizeof(cmdline)/sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (multiple_occurrences& e)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(e.get_option_name(), "--cfgfile");
|
||||
BOOST_CHECK_EQUAL(string(e.what()), "option '--cfgfile' cannot be specified more than once");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_missing_value()
|
||||
@@ -154,9 +250,13 @@ void test_missing_value()
|
||||
int main(int /*ac*/, char** /*av*/)
|
||||
{
|
||||
test_ambiguous();
|
||||
test_ambiguous_long();
|
||||
test_ambiguous_multiple_long_names();
|
||||
test_unknown_option();
|
||||
test_multiple_values();
|
||||
test_multiple_occurrences();
|
||||
test_multiple_occurrences_with_different_names();
|
||||
test_multiple_occurrences_with_non_key_names();
|
||||
test_missing_value();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -74,6 +74,61 @@ void test_approximation()
|
||||
// BOOST_CHECK(*(++a.begin()) == "foo");
|
||||
}
|
||||
|
||||
void test_approximation_with_multiname_options()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("foo", new untyped_value())
|
||||
("fee", new untyped_value())
|
||||
("fe,baz", new untyped_value())
|
||||
("chroots,all-chroots", new untyped_value())
|
||||
("sessions,all-sessions", new untyped_value())
|
||||
("everything,all", new untyped_value())
|
||||
("qux,fo", new untyped_value())
|
||||
;
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("fo", true).long_name(), "qux");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("all", true).long_name(), "everything");
|
||||
BOOST_CHECK_EQUAL(desc.find("all-ch", true).long_name(), "chroots");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().second, 1u);
|
||||
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().first[0], "foo");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().second, 2u);
|
||||
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().first[0], "fe");
|
||||
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().second, 2u);
|
||||
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[0], "fizbaz");
|
||||
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
|
||||
}
|
||||
|
||||
void test_long_names_for_option_description()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("foo", new untyped_value())
|
||||
("fe,baz", new untyped_value())
|
||||
("chroots,all-chroots", new untyped_value())
|
||||
("sessions,all-sessions", new untyped_value())
|
||||
("everything,all", new untyped_value())
|
||||
("qux,fo,q", new untyped_value())
|
||||
;
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().second, 1u);
|
||||
BOOST_CHECK_EQUAL(desc.find("foo", false, false, false).long_names().first[0], "foo");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().second, 2u);
|
||||
BOOST_CHECK_EQUAL(desc.find("fe", false, false, false).long_names().first[0], "fe");
|
||||
BOOST_CHECK_EQUAL(desc.find("baz", false, false, false).long_names().first[1], "baz");
|
||||
|
||||
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().second, 2u);
|
||||
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().first[0], "qux");
|
||||
BOOST_CHECK_EQUAL(desc.find("qux", false, false, false).long_names().first[1], "fo");
|
||||
}
|
||||
|
||||
|
||||
void test_formatting()
|
||||
{
|
||||
// Long option descriptions used to crash on MSVC-8.0.
|
||||
@@ -124,6 +179,21 @@ void test_formatting()
|
||||
);
|
||||
}
|
||||
|
||||
void test_multiname_option_formatting()
|
||||
{
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("foo,bar", new untyped_value(), "a multiple-name option")
|
||||
;
|
||||
|
||||
stringstream ss;
|
||||
ss << desc;
|
||||
BOOST_CHECK_EQUAL(ss.str(),
|
||||
" --foo arg a multiple-name option\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void test_formatting_description_length()
|
||||
{
|
||||
{
|
||||
@@ -245,12 +315,28 @@ void test_value_name()
|
||||
);
|
||||
}
|
||||
|
||||
void test_multiname_key_and_switch_selection()
|
||||
{
|
||||
// cases:
|
||||
// foo,f -> f
|
||||
// foo, c -> c
|
||||
// foo,f,g -> g
|
||||
// f,g,h -> h
|
||||
// f,foo throws
|
||||
// foo,bar -> no switch
|
||||
// foo,f,bar -> no switch
|
||||
|
||||
// what about empty strings - consecutive ,'s ?
|
||||
}
|
||||
|
||||
int main(int, char* [])
|
||||
{
|
||||
test_type();
|
||||
test_approximation();
|
||||
test_long_names_for_option_description();
|
||||
test_formatting();
|
||||
test_multiname_key_and_switch_selection();
|
||||
test_multiname_option_formatting();
|
||||
test_formatting_description_length();
|
||||
test_long_default_value();
|
||||
test_word_wrapping();
|
||||
|
||||
@@ -16,6 +16,7 @@ using namespace boost;
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
using namespace std;
|
||||
|
||||
#if defined(__sun)
|
||||
@@ -70,20 +71,18 @@ pair<string, string> additional_parser(const std::string&)
|
||||
return pair<string, string>();
|
||||
}
|
||||
|
||||
void test_command_line()
|
||||
{
|
||||
// The following commented out blocks used to test parsing
|
||||
// command line without syntax specification behaviour.
|
||||
// It is disabled now and probably will never be enabled again:
|
||||
// it is not possible to figure out what command line means without
|
||||
// user's help.
|
||||
#if 0
|
||||
namespace command_line {
|
||||
|
||||
#if 0
|
||||
// The following commented out blocks used to test parsing
|
||||
// command line without syntax specification behaviour.
|
||||
// It is disabled now and probably will never be enabled again:
|
||||
// it is not possible to figure out what command line means without
|
||||
// user's help.
|
||||
void test_parsing_without_specifying_options() {
|
||||
char* cmdline1[] = { "--a", "--b=12", "-f", "-g4", "-", "file" };
|
||||
|
||||
options_and_arguments a1 =
|
||||
parse_command_line(cmdline1,
|
||||
cmdline1 + sizeof(cmdline1)/sizeof(cmdline1[0]));
|
||||
|
||||
options_and_arguments a1 = parse_command_line(cmdline1,
|
||||
cmdline1 + sizeof(cmdline1) / sizeof(cmdline1[0]));
|
||||
BOOST_REQUIRE(a1.options().size() == 4);
|
||||
BOOST_CHECK(a1.options()[0] == msp("a", ""));
|
||||
BOOST_CHECK(a1.options()[1] == msp("b", "12"));
|
||||
@@ -92,71 +91,80 @@ void test_command_line()
|
||||
BOOST_REQUIRE(a1.arguments().size() == 2);
|
||||
BOOST_CHECK(a1.arguments()[0] == "-");
|
||||
BOOST_CHECK(a1.arguments()[1] == "file");
|
||||
|
||||
char* cmdline2[] = { "--a", "--", "file" };
|
||||
|
||||
options_and_arguments a2 =
|
||||
parse_command_line(cmdline2,
|
||||
cmdline2 + sizeof(cmdline2)/sizeof(cmdline2[0]));
|
||||
|
||||
options_and_arguments a2 = parse_command_line(cmdline2,
|
||||
cmdline2 + sizeof(cmdline2) / sizeof(cmdline2[0]));
|
||||
BOOST_REQUIRE(a2.options().size() == 1);
|
||||
BOOST_CHECK(a2.options()[0] == msp("a", ""));
|
||||
BOOST_CHECK(a2.arguments().size() == 1);
|
||||
BOOST_CHECK(a2.arguments()[0] == "file");
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void test_many_different_options() {
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("foo,f", new untyped_value(), "")
|
||||
// Explicit qualification is a workaround for vc6
|
||||
("bar,b", po::value<std::string>(), "")
|
||||
( // Explicit qualification is a workaround for vc6
|
||||
"bar,b", po::value<std::string>(), "")
|
||||
("car,voiture", new untyped_value())
|
||||
("dog,dawg", new untyped_value())
|
||||
("baz", new untyped_value())
|
||||
("plug*", new untyped_value())
|
||||
;
|
||||
("plug*", new untyped_value());
|
||||
const char* cmdline3_[] = { "--foo=12", "-f4", "--bar=11", "-b4",
|
||||
"--plug3=10"};
|
||||
"--voiture=15", "--dawg=16", "--dog=17", "--plug3=10" };
|
||||
vector<string> cmdline3 = sv(cmdline3_,
|
||||
sizeof(cmdline3_)/sizeof(const char*));
|
||||
vector<option> a3 =
|
||||
command_line_parser(cmdline3).options(desc).run().options;
|
||||
|
||||
BOOST_CHECK_EQUAL(a3.size(), 5u);
|
||||
|
||||
sizeof(cmdline3_) / sizeof(const char*));
|
||||
vector<option> a3 =
|
||||
command_line_parser(cmdline3).options(desc).run().options;
|
||||
BOOST_CHECK_EQUAL(a3.size(), 8u);
|
||||
check_value(a3[0], "foo", "12");
|
||||
check_value(a3[1], "foo", "4");
|
||||
check_value(a3[2], "bar", "11");
|
||||
check_value(a3[3], "bar", "4");
|
||||
check_value(a3[4], "plug3", "10");
|
||||
check_value(a3[4], "car", "15");
|
||||
check_value(a3[5], "dog", "16");
|
||||
check_value(a3[6], "dog", "17");
|
||||
check_value(a3[7], "plug3", "10");
|
||||
|
||||
// Regression test: check that '0' as style is interpreted as
|
||||
// 'default_style'
|
||||
vector<option> a4 =
|
||||
parse_command_line(sizeof(cmdline3_)/sizeof(const char*), cmdline3_,
|
||||
desc, 0, additional_parser).options;
|
||||
|
||||
BOOST_CHECK_EQUAL(a4.size(), 4u);
|
||||
vector<option> a4 = parse_command_line(
|
||||
sizeof(cmdline3_) / sizeof(const char*), cmdline3_, desc, 0,
|
||||
additional_parser).options;
|
||||
// The default style is unix-style, where the first argument on the command-line
|
||||
// is the name of a binary, not an option value, so that should be ignored
|
||||
BOOST_CHECK_EQUAL(a4.size(), 7u);
|
||||
check_value(a4[0], "foo", "4");
|
||||
check_value(a4[1], "bar", "11");
|
||||
check_value(a4[2], "bar", "4");
|
||||
check_value(a4[3], "car", "15");
|
||||
check_value(a4[4], "dog", "16");
|
||||
check_value(a4[5], "dog", "17");
|
||||
check_value(a4[6], "plug3", "10");
|
||||
}
|
||||
|
||||
void test_not_crashing_with_empty_string_values() {
|
||||
// Check that we don't crash on empty values of type 'string'
|
||||
const char* cmdline4[] = {"", "--open", ""};
|
||||
const char* cmdline4[] = { "", "--open", "" };
|
||||
options_description desc2;
|
||||
desc2.add_options()
|
||||
("open", po::value<string>())
|
||||
;
|
||||
desc2.add_options()("open", po::value<string>());
|
||||
variables_map vm;
|
||||
po::store(po::parse_command_line(sizeof(cmdline4)/sizeof(const char*), const_cast<char**>(cmdline4), desc2), vm);
|
||||
po::store(
|
||||
po::parse_command_line(sizeof(cmdline4) / sizeof(const char*),
|
||||
const_cast<char**>(cmdline4), desc2), vm);
|
||||
}
|
||||
|
||||
const char* cmdline5[] = {"", "-p7", "-o", "1", "2", "3", "-x8"};
|
||||
void test_multitoken() {
|
||||
const char* cmdline5[] = { "", "-p7", "-o", "1", "2", "3", "-x8" };
|
||||
options_description desc3;
|
||||
desc3.add_options()
|
||||
(",p", po::value<string>())
|
||||
(",o", po::value<string>()->multitoken())
|
||||
(",x", po::value<string>())
|
||||
;
|
||||
vector<option> a5 =
|
||||
parse_command_line(sizeof(cmdline5)/sizeof(const char*), const_cast<char**>(cmdline5),
|
||||
desc3, 0, additional_parser).options;
|
||||
(",x", po::value<string>());
|
||||
vector<option> a5 = parse_command_line(
|
||||
sizeof(cmdline5) / sizeof(const char*),
|
||||
const_cast<char**>(cmdline5), desc3, 0, additional_parser).options;
|
||||
BOOST_CHECK_EQUAL(a5.size(), 3u);
|
||||
check_value(a5[0], "-p", "7");
|
||||
BOOST_REQUIRE(a5[1].value.size() == 3);
|
||||
@@ -164,28 +172,60 @@ void test_command_line()
|
||||
BOOST_CHECK_EQUAL(a5[1].value[0], "1");
|
||||
BOOST_CHECK_EQUAL(a5[1].value[1], "2");
|
||||
BOOST_CHECK_EQUAL(a5[1].value[2], "3");
|
||||
check_value(a5[2], "-x", "8");
|
||||
check_value(a5[2], "-x", "8");
|
||||
}
|
||||
|
||||
void test_multitoken_and_multiname() {
|
||||
const char* cmdline[] = { "program", "-fone", "-b", "two", "--foo", "three", "four", "-zfive", "--fee", "six" };
|
||||
options_description desc;
|
||||
desc.add_options()
|
||||
("bar,b", po::value<string>())
|
||||
("foo,fee,f", po::value<string>()->multitoken())
|
||||
("fizbaz,baz,z", po::value<string>());
|
||||
|
||||
vector<option> parsed_options = parse_command_line(
|
||||
sizeof(cmdline) / sizeof(const char*),
|
||||
const_cast<char**>(cmdline), desc, 0, additional_parser).options;
|
||||
|
||||
BOOST_CHECK_EQUAL(parsed_options.size(), 5u);
|
||||
check_value(parsed_options[0], "foo", "one");
|
||||
check_value(parsed_options[1], "bar", "two");
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo");
|
||||
BOOST_REQUIRE(parsed_options[2].value.size() == 2);
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three");
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four");
|
||||
check_value(parsed_options[3], "fizbaz", "five");
|
||||
check_value(parsed_options[4], "foo", "six");
|
||||
|
||||
const char* cmdline_2[] = { "program", "-fone", "-b", "two", "--fee", "three", "four", "-zfive", "--foo", "six" };
|
||||
|
||||
parsed_options = parse_command_line(
|
||||
sizeof(cmdline_2) / sizeof(const char*),
|
||||
const_cast<char**>(cmdline_2), desc, 0, additional_parser).options;
|
||||
|
||||
BOOST_CHECK_EQUAL(parsed_options.size(), 5u);
|
||||
check_value(parsed_options[0], "foo", "one");
|
||||
check_value(parsed_options[1], "bar", "two");
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].string_key, "foo");
|
||||
BOOST_REQUIRE(parsed_options[2].value.size() == 2);
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].value[0], "three");
|
||||
BOOST_CHECK_EQUAL(parsed_options[2].value[1], "four");
|
||||
check_value(parsed_options[3], "fizbaz", "five");
|
||||
check_value(parsed_options[4], "foo", "six");
|
||||
}
|
||||
|
||||
|
||||
po::options_description desc4( "" );
|
||||
void test_multitoken_vector_option() {
|
||||
po::options_description desc4("");
|
||||
desc4.add_options()
|
||||
( "multitoken,m",
|
||||
po::value< std::vector< std::string > >()->multitoken(),
|
||||
"values"
|
||||
)
|
||||
( "file",
|
||||
po::value< std::string >(),
|
||||
"the file to process"
|
||||
)
|
||||
;
|
||||
|
||||
("multitoken,multi-token,m", po::value<std::vector<std::string> >()->multitoken(), "values")
|
||||
("file", po::value<std::string>(), "the file to process");
|
||||
po::positional_options_description p;
|
||||
p.add( "file", 1 );
|
||||
|
||||
const char* cmdline6[] = {"", "-m", "token1", "token2", "--", "some_file"};
|
||||
vector<option> a6 =
|
||||
command_line_parser(sizeof(cmdline6)/sizeof(const char*), const_cast<char**>(cmdline6)).options(desc4).positional(p)
|
||||
.run().options;
|
||||
p.add("file", 1);
|
||||
const char* cmdline6[] = { "", "-m", "token1", "token2", "--", "some_file" };
|
||||
vector<option> a6 =
|
||||
command_line_parser(sizeof(cmdline6) / sizeof(const char*),
|
||||
const_cast<char**>(cmdline6)).options(desc4).positional(p).run().options;
|
||||
BOOST_CHECK_EQUAL(a6.size(), 2u);
|
||||
BOOST_REQUIRE(a6[0].value.size() == 2);
|
||||
BOOST_CHECK_EQUAL(a6[0].string_key, "multitoken");
|
||||
@@ -196,6 +236,21 @@ void test_command_line()
|
||||
BOOST_CHECK_EQUAL(a6[1].value[0], "some_file");
|
||||
}
|
||||
|
||||
} // namespace command_line
|
||||
|
||||
void test_command_line()
|
||||
{
|
||||
#if 0
|
||||
command_line::test_parsing_without_specifying_options();
|
||||
#endif
|
||||
command_line::test_many_different_options();
|
||||
// Check that we don't crash on empty values of type 'string'
|
||||
command_line::test_not_crashing_with_empty_string_values();
|
||||
command_line::test_multitoken();
|
||||
command_line::test_multitoken_vector_option();
|
||||
command_line::test_multitoken_and_multiname();
|
||||
}
|
||||
|
||||
void test_config_file(const char* config_file)
|
||||
{
|
||||
options_description desc;
|
||||
@@ -206,6 +261,7 @@ void test_config_file(const char* config_file)
|
||||
("plug*", new untyped_value)
|
||||
("m1.v1", new untyped_value)
|
||||
("m1.v2", new untyped_value)
|
||||
("m1.v3,alias3", new untyped_value)
|
||||
("b", bool_switch())
|
||||
;
|
||||
|
||||
@@ -218,27 +274,30 @@ void test_config_file(const char* config_file)
|
||||
"v1 = 1\n"
|
||||
"\n"
|
||||
"v2 = 2\n"
|
||||
"v3 = 3\n"
|
||||
;
|
||||
|
||||
stringstream ss(content1);
|
||||
vector<option> a1 = parse_config_file(ss, desc).options;
|
||||
BOOST_REQUIRE(a1.size() == 6);
|
||||
BOOST_REQUIRE(a1.size() == 7);
|
||||
check_value(a1[0], "gv1", "0");
|
||||
check_value(a1[1], "empty_value", "");
|
||||
check_value(a1[2], "plug3", "7");
|
||||
check_value(a1[3], "b", "true");
|
||||
check_value(a1[4], "m1.v1", "1");
|
||||
check_value(a1[5], "m1.v2", "2");
|
||||
check_value(a1[6], "m1.v3", "3");
|
||||
|
||||
// same test, but now options come from file
|
||||
vector<option> a2 = parse_config_file<char>(config_file, desc).options;
|
||||
BOOST_REQUIRE(a2.size() == 6);
|
||||
BOOST_REQUIRE(a2.size() == 7);
|
||||
check_value(a2[0], "gv1", "0");
|
||||
check_value(a2[1], "empty_value", "");
|
||||
check_value(a2[2], "plug3", "7");
|
||||
check_value(a2[3], "b", "true");
|
||||
check_value(a2[4], "m1.v1", "1");
|
||||
check_value(a2[5], "m1.v2", "2");
|
||||
check_value(a2[6], "m1.v3", "3");
|
||||
}
|
||||
|
||||
void test_environment()
|
||||
@@ -249,7 +308,7 @@ void test_environment()
|
||||
("bar", new untyped_value, "")
|
||||
;
|
||||
|
||||
#if defined(_WIN32) && ! defined(__BORLANDC__)
|
||||
#if (defined(_WIN32) && ! defined(__BORLANDC__)) || (defined(__CYGWIN__))
|
||||
_putenv("PO_TEST_FOO=1");
|
||||
#else
|
||||
putenv(const_cast<char*>("PO_TEST_FOO=1"));
|
||||
@@ -257,9 +316,9 @@ void test_environment()
|
||||
parsed_options p = parse_environment(desc, "PO_TEST_");
|
||||
|
||||
BOOST_REQUIRE(p.options.size() == 1);
|
||||
BOOST_CHECK(p.options[0].string_key == "foo");
|
||||
BOOST_CHECK (p.options[0].string_key == "foo");
|
||||
BOOST_REQUIRE(p.options[0].value.size() == 1);
|
||||
BOOST_CHECK(p.options[0].value[0] == "1");
|
||||
BOOST_CHECK (p.options[0].value[0] == "1");
|
||||
|
||||
//TODO: since 'bar' does not allow a value, it cannot appear in environemt,
|
||||
// which already has a value.
|
||||
@@ -316,6 +375,8 @@ void test_unregistered()
|
||||
check_value(a3[1], "m1.v1", "1");
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int, char* av[])
|
||||
{
|
||||
test_command_line();
|
||||
|
||||
@@ -23,36 +23,36 @@ void required_throw_test()
|
||||
;
|
||||
|
||||
variables_map vm;
|
||||
bool throwed = false;
|
||||
bool thrown = false;
|
||||
{
|
||||
// This test must throw exception
|
||||
string cmdline = "prg -f file.txt";
|
||||
vector< string > tokens = split_unix(cmdline);
|
||||
throwed = false;
|
||||
thrown = false;
|
||||
try {
|
||||
store(command_line_parser(tokens).options(opts).run(), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (required_option& e) {
|
||||
BOOST_CHECK_EQUAL(e.what(), string("the option '--cfgfile' is required but missing"));
|
||||
throwed = true;
|
||||
thrown = true;
|
||||
}
|
||||
BOOST_CHECK(throwed);
|
||||
BOOST_CHECK(thrown);
|
||||
}
|
||||
|
||||
{
|
||||
// This test mustn't throw exception
|
||||
string cmdline = "prg -c config.txt";
|
||||
vector< string > tokens = split_unix(cmdline);
|
||||
throwed = false;
|
||||
thrown = false;
|
||||
try {
|
||||
store(command_line_parser(tokens).options(opts).run(), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (required_option& e) {
|
||||
throwed = true;
|
||||
thrown = true;
|
||||
}
|
||||
BOOST_CHECK(!throwed);
|
||||
BOOST_CHECK(!thrown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +67,12 @@ void simple_required_test(const char* config_file)
|
||||
;
|
||||
|
||||
variables_map vm;
|
||||
bool throwed = false;
|
||||
bool thrown = false;
|
||||
{
|
||||
// This test must throw exception
|
||||
string cmdline = "prg -f file.txt";
|
||||
vector< string > tokens = split_unix(cmdline);
|
||||
throwed = false;
|
||||
thrown = false;
|
||||
try {
|
||||
// options coming from different sources
|
||||
store(command_line_parser(tokens).options(opts).run(), vm);
|
||||
@@ -80,9 +80,35 @@ void simple_required_test(const char* config_file)
|
||||
notify(vm);
|
||||
}
|
||||
catch (required_option& e) {
|
||||
throwed = true;
|
||||
thrown = true;
|
||||
}
|
||||
BOOST_CHECK(!throwed);
|
||||
BOOST_CHECK(!thrown);
|
||||
}
|
||||
}
|
||||
|
||||
void multiname_required_test()
|
||||
{
|
||||
options_description opts;
|
||||
opts.add_options()
|
||||
("foo,bar", value<string>()->required(), "the foo")
|
||||
;
|
||||
|
||||
variables_map vm;
|
||||
bool thrown = false;
|
||||
{
|
||||
// This test must throw exception
|
||||
string cmdline = "prg --bar file.txt";
|
||||
vector< string > tokens = split_unix(cmdline);
|
||||
thrown = false;
|
||||
try {
|
||||
// options coming from different sources
|
||||
store(command_line_parser(tokens).options(opts).run(), vm);
|
||||
notify(vm);
|
||||
}
|
||||
catch (required_option& e) {
|
||||
thrown = true;
|
||||
}
|
||||
BOOST_CHECK(!thrown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +118,7 @@ int main(int /*argc*/, char* av[])
|
||||
{
|
||||
required_throw_test();
|
||||
simple_required_test(av[1]);
|
||||
multiname_required_test();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ void test_command_line()
|
||||
// Explicit qualification is a workaround for vc6
|
||||
("bar,b", po::value<std::string>(), "")
|
||||
("baz", new untyped_value())
|
||||
("plug*", new untyped_value())
|
||||
("qux,plug*", new untyped_value())
|
||||
;
|
||||
|
||||
const wchar_t* cmdline4_[] = { L"--foo=1\u0FF52", L"-f4", L"--bar=11",
|
||||
@@ -126,6 +126,7 @@ void test_command_line()
|
||||
check_value(a4[0], "foo", L"1\u0FF52");
|
||||
check_value(a4[1], "foo", L"4");
|
||||
check_value(a4[2], "bar", L"11");
|
||||
check_value(a4[4], "qux", L"10");
|
||||
}
|
||||
|
||||
// Since we've already tested conversion between parser encoding and
|
||||
|
||||
Reference in New Issue
Block a user