Merge branch 'master' into master

This commit is contained in:
Daniel Lemire 2020-10-01 09:31:02 -04:00 committed by GitHub
commit 197a1e0599
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 345 additions and 193 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ CMakeFiles/
Testing/ Testing/
CTestTestfile.cmake CTestTestfile.cmake
cmake_install.cmake cmake_install.cmake
bazel-*

8
BUILD Normal file
View File

@ -0,0 +1,8 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "cxxopts",
hdrs = ["include/cxxopts.hpp"],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View File

@ -3,7 +3,7 @@
This is the changelog for `cxxopts`, a C++11 library for parsing command line This is the changelog for `cxxopts`, a C++11 library for parsing command line
options. The project adheres to semantic versioning. options. The project adheres to semantic versioning.
## Next version ## 3.0
### Changed ### Changed
@ -12,6 +12,13 @@ options. The project adheres to semantic versioning.
* Fix duplicate default options when there is a short and long option. * Fix duplicate default options when there is a short and long option.
* Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions. * Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions.
* Fix char parsing for space and check for length. * Fix char parsing for space and check for length.
* Change argument type in `Options::parse` from `char**` to `const char**`.
* Refactor parser to not change its arguments.
* `ParseResult` doesn't depend on a reference to the parser.
### Added
* A list of unmatched arguments is available in `ParseResult`.
## 2.2 ## 2.2

View File

@ -73,8 +73,9 @@ target_include_directories(cxxopts INTERFACE
) )
if(CXXOPTS_ENABLE_INSTALL) if(CXXOPTS_ENABLE_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/cxxopts" CACHE STRING
"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.")
set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake")
set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake")
@ -100,8 +101,8 @@ if(CXXOPTS_ENABLE_INSTALL)
NAMESPACE cxxopts::) NAMESPACE cxxopts::)
# Install the header file and export the target # Install the header file and export the target
install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib) install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include) install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif() endif()
add_subdirectory(src) add_subdirectory(src)

View File

@ -5,6 +5,25 @@
Note that `master` is generally a work in progress, and you probably want to use a Note that `master` is generally a work in progress, and you probably want to use a
tagged release version. tagged release version.
## Version 3 breaking changes
If you have used version 2, there are a couple of breaking changes in (the as
yet unreleased, current master) version 3 that you should be aware of. If you are new to
`cxxopts` you can skip this section.
The parser no longer modifies its arguments, so you can pass a const `argc` and
`argv` and expect them not to be changed.
The `ParseResult` object no longer depends on the parser. So it can be returned
from a scope outside the parser and still work. Now that the inputs are not
modified, `ParseResult` stores a list of the unmatched arguments. These are
retrieved like follows:
```cpp
auto result = options.parse(argc, argv);
result.unmatched(); // get the unmatched arguments
```
# Quick start # Quick start
This is a lightweight C++ option parser library, supporting the standard GNU This is a lightweight C++ option parser library, supporting the standard GNU
@ -69,6 +88,23 @@ exception will be thrown.
Note that the result of `options.parse` should only be used as long as the Note that the result of `options.parse` should only be used as long as the
`options` object that created it is in scope. `options` object that created it is in scope.
## Unrecognised arguments
You can allow unrecognised arguments to be skipped. This applies to both
positional arguments that are not parsed into another option, and `--`
arguments that do not match an argument that you specify. This is done by
calling:
```cpp
options.allow_unrecognised_options();
```
and in the result object they are retrieved with:
```cpp
result.unmatched()
```
## Exceptions ## Exceptions
Exceptional situations throw C++ exceptions. There are two types of Exceptional situations throw C++ exceptions. There are two types of
@ -146,6 +182,22 @@ that can be parsed as a `std::vector<double>`:
--my_list=1,-2.1,3,4.5 --my_list=1,-2.1,3,4.5
~~~ ~~~
## Options specified multiple times
The same option can be specified several times, with different arguments, which will all
be recorded in order of appearance. An example:
~~~
--use train --use bus --use ferry
~~~
this is supported through the use of a vector of value for the option:
~~~
options.add_options()
("use", "Usable means of transport", cxxopts::value<std::vector<std::string>>())
~~~
## Custom help ## Custom help
The string after the program name on the first line of the help can be The string after the program name on the first line of the help can be

0
WORKSPACE Normal file
View File

View File

@ -30,6 +30,7 @@ THE SOFTWARE.
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <list>
#include <map> #include <map>
#include <memory> #include <memory>
#include <regex> #include <regex>
@ -45,12 +46,18 @@ THE SOFTWARE.
#define CXXOPTS_HAS_OPTIONAL #define CXXOPTS_HAS_OPTIONAL
#endif #endif
#if __cplusplus >= 201603L
#define CXXOPTS_NODISCARD [[nodiscard]]
#else
#define CXXOPTS_NODISCARD
#endif
#ifndef CXXOPTS_VECTOR_DELIMITER #ifndef CXXOPTS_VECTOR_DELIMITER
#define CXXOPTS_VECTOR_DELIMITER ',' #define CXXOPTS_VECTOR_DELIMITER ','
#endif #endif
#define CXXOPTS__VERSION_MAJOR 2 #define CXXOPTS__VERSION_MAJOR 3
#define CXXOPTS__VERSION_MINOR 2 #define CXXOPTS__VERSION_MINOR 0
#define CXXOPTS__VERSION_PATCH 0 #define CXXOPTS__VERSION_PATCH 0
namespace cxxopts namespace cxxopts
@ -339,6 +346,7 @@ namespace cxxopts
{ {
} }
CXXOPTS_NODISCARD
const char* const char*
what() const noexcept override what() const noexcept override
{ {
@ -452,6 +460,18 @@ namespace cxxopts
} }
}; };
class option_has_no_value_exception : public OptionException
{
public:
explicit option_has_no_value_exception(const std::string& option)
: OptionException(
option.empty() ?
("Option " + LQUOTE + option + RQUOTE + " has no value") :
"Option has no value")
{
}
};
class argument_incorrect_type : public OptionParseException class argument_incorrect_type : public OptionParseException
{ {
public: public:
@ -554,21 +574,20 @@ namespace cxxopts
} // namespace detail } // namespace detail
template <typename R, typename T> template <typename R, typename T>
R void
checked_negate(T&& t, const std::string&, std::true_type) checked_negate(R& r, T&& t, const std::string&, std::true_type)
{ {
// if we got to here, then `t` is a positive number that fits into // if we got to here, then `t` is a positive number that fits into
// `R`. So to avoid MSVC C4146, we first cast it to `R`. // `R`. So to avoid MSVC C4146, we first cast it to `R`.
// See https://github.com/jarro2783/cxxopts/issues/62 for more details. // See https://github.com/jarro2783/cxxopts/issues/62 for more details.
return static_cast<R>(-static_cast<R>(t-1)-1); r = static_cast<R>(-static_cast<R>(t-1)-1);
} }
template <typename R, typename T> template <typename R, typename T>
T void
checked_negate(T&& t, const std::string& text, std::false_type) checked_negate(R&, T&&, const std::string& text, std::false_type)
{ {
throw_or_mimic<argument_incorrect_type>(text); throw_or_mimic<argument_incorrect_type>(text);
return t;
} }
template <typename T> template <typename T>
@ -633,9 +652,7 @@ namespace cxxopts
if (negative) if (negative)
{ {
value = checked_negate<T>(result, checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());
text,
std::integral_constant<bool, is_signed>());
} }
else else
{ {
@ -932,6 +949,7 @@ namespace cxxopts
public: public:
using abstract_value<T>::abstract_value; using abstract_value<T>::abstract_value;
CXXOPTS_NODISCARD
std::shared_ptr<Value> std::shared_ptr<Value>
clone() const clone() const
{ {
@ -1007,6 +1025,7 @@ namespace cxxopts
, m_value(std::move(val)) , m_value(std::move(val))
, m_count(0) , m_count(0)
{ {
m_hash = std::hash<std::string>{}(m_long + m_short);
} }
OptionDetails(const OptionDetails& rhs) OptionDetails(const OptionDetails& rhs)
@ -1018,40 +1037,54 @@ namespace cxxopts
OptionDetails(OptionDetails&& rhs) = default; OptionDetails(OptionDetails&& rhs) = default;
CXXOPTS_NODISCARD
const String& const String&
description() const description() const
{ {
return m_desc; return m_desc;
} }
const Value& value() const { CXXOPTS_NODISCARD
const Value&
value() const {
return *m_value; return *m_value;
} }
CXXOPTS_NODISCARD
std::shared_ptr<Value> std::shared_ptr<Value>
make_storage() const make_storage() const
{ {
return m_value->clone(); return m_value->clone();
} }
CXXOPTS_NODISCARD
const std::string& const std::string&
short_name() const short_name() const
{ {
return m_short; return m_short;
} }
CXXOPTS_NODISCARD
const std::string& const std::string&
long_name() const long_name() const
{ {
return m_long; return m_long;
} }
size_t
hash() const
{
return m_hash;
}
private: private:
std::string m_short{}; std::string m_short{};
std::string m_long{}; std::string m_long{};
String m_desc{}; String m_desc{};
std::shared_ptr<const Value> m_value{}; std::shared_ptr<const Value> m_value{};
int m_count; int m_count;
size_t m_hash;
}; };
struct HelpOptionDetails struct HelpOptionDetails
@ -1088,6 +1121,7 @@ namespace cxxopts
ensure_value(details); ensure_value(details);
++m_count; ++m_count;
m_value->parse(text); m_value->parse(text);
m_long_name = &details->long_name();
} }
void void
@ -1095,9 +1129,11 @@ namespace cxxopts
{ {
ensure_value(details); ensure_value(details);
m_default = true; m_default = true;
m_long_name = &details->long_name();
m_value->parse(); m_value->parse();
} }
CXXOPTS_NODISCARD
size_t size_t
count() const noexcept count() const noexcept
{ {
@ -1105,6 +1141,7 @@ namespace cxxopts
} }
// TODO: maybe default options should count towards the number of arguments // TODO: maybe default options should count towards the number of arguments
CXXOPTS_NODISCARD
bool bool
has_default() const noexcept has_default() const noexcept
{ {
@ -1116,7 +1153,8 @@ namespace cxxopts
as() const as() const
{ {
if (m_value == nullptr) { if (m_value == nullptr) {
throw_or_mimic<std::domain_error>("No value"); throw_or_mimic<option_has_no_value_exception>(
m_long_name == nullptr ? "" : *m_long_name);
} }
#ifdef CXXOPTS_NO_RTTI #ifdef CXXOPTS_NO_RTTI
@ -1136,6 +1174,10 @@ namespace cxxopts
} }
} }
const std::string* m_long_name = nullptr;
// Holding this pointer is safe, since OptionValue's only exist in key-value pairs,
// where the key has the string we point to.
std::shared_ptr<Value> m_value{}; std::shared_ptr<Value> m_value{};
size_t m_count = 0; size_t m_count = 0;
bool m_default = false; bool m_default = false;
@ -1150,15 +1192,15 @@ namespace cxxopts
{ {
} }
const CXXOPTS_NODISCARD
std::string& const std::string&
key() const key() const
{ {
return m_key; return m_key;
} }
const CXXOPTS_NODISCARD
std::string& const std::string&
value() const value() const
{ {
return m_value; return m_value;
@ -1178,45 +1220,65 @@ namespace cxxopts
std::string m_value; std::string m_value;
}; };
using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
using NameHashMap = std::unordered_map<std::string, size_t>;
class ParseResult class ParseResult
{ {
public: public:
ParseResult( ParseResult() {}
std::shared_ptr<
std::unordered_map<std::string, std::shared_ptr<OptionDetails>> ParseResult(const ParseResult&) = default;
>,
std::vector<std::string>, ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
bool allow_unrecognised, : m_keys(std::move(keys))
int&, char**&); , m_values(std::move(values))
, m_sequential(std::move(sequential))
, m_unmatched(std::move(unmatched_args))
{
}
ParseResult& operator=(ParseResult&&) = default;
ParseResult& operator=(const ParseResult&) = default;
size_t size_t
count(const std::string& o) const count(const std::string& o) const
{ {
auto iter = m_options->find(o); auto iter = m_keys.find(o);
if (iter == m_options->end()) if (iter == m_keys.end())
{ {
return 0; return 0;
} }
auto riter = m_results.find(iter->second); auto viter = m_values.find(iter->second);
return riter->second.count(); if (viter == m_values.end())
{
return 0;
}
return viter->second.count();
} }
const OptionValue& const OptionValue&
operator[](const std::string& option) const operator[](const std::string& option) const
{ {
auto iter = m_options->find(option); auto iter = m_keys.find(option);
if (iter == m_options->end()) if (iter == m_keys.end())
{ {
throw_or_mimic<option_not_present_exception>(option); throw_or_mimic<option_not_present_exception>(option);
} }
auto riter = m_results.find(iter->second); auto viter = m_values.find(iter->second);
return riter->second; if (viter == m_values.end())
{
throw_or_mimic<option_not_present_exception>(option);
}
return viter->second;
} }
const std::vector<KeyValue>& const std::vector<KeyValue>&
@ -1225,49 +1287,17 @@ namespace cxxopts
return m_sequential; return m_sequential;
} }
const std::vector<std::string>&
unmatched() const
{
return m_unmatched;
}
private: private:
NameHashMap m_keys{};
void ParsedHashMap m_values{};
parse(int& argc, char**& argv);
void
add_to_option(const std::string& option, const std::string& arg);
bool
consume_positional(const std::string& a);
void
parse_option
(
const std::shared_ptr<OptionDetails>& value,
const std::string& name,
const std::string& arg = ""
);
void
parse_default(const std::shared_ptr<OptionDetails>& details);
void
checked_parse_arg
(
int argc,
char* argv[],
int& current,
const std::shared_ptr<OptionDetails>& value,
const std::string& name
);
const std::shared_ptr<
std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
> m_options;
std::vector<std::string> m_positional{};
std::vector<std::string>::iterator m_next_positional;
std::unordered_set<std::string> m_positional_set{};
std::unordered_map<std::shared_ptr<OptionDetails>, OptionValue> m_results{};
bool m_allow_unrecognised;
std::vector<KeyValue> m_sequential{}; std::vector<KeyValue> m_sequential{};
std::vector<std::string> m_unmatched{};
}; };
struct Option struct Option
@ -1292,9 +1322,66 @@ namespace cxxopts
std::string arg_help_; std::string arg_help_;
}; };
using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
using PositionalList = std::vector<std::string>;
using PositionalListIterator = PositionalList::const_iterator;
class OptionParser
{
public:
OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)
: m_options(options)
, m_positional(positional)
, m_allow_unrecognised(allow_unrecognised)
{
}
ParseResult
parse(int argc, const char** argv);
bool
consume_positional(const std::string& a, PositionalListIterator& next);
void
checked_parse_arg
(
int argc,
const char* argv[],
int& current,
const std::shared_ptr<OptionDetails>& value,
const std::string& name
);
void
add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
void
parse_option
(
const std::shared_ptr<OptionDetails>& value,
const std::string& name,
const std::string& arg = ""
);
void
parse_default(const std::shared_ptr<OptionDetails>& details);
private:
void finalise_aliases();
const OptionMap& m_options;
const PositionalList& m_positional;
std::vector<KeyValue> m_sequential;
bool m_allow_unrecognised;
ParsedHashMap m_parsed;
NameHashMap m_keys;
};
class Options class Options
{ {
using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
public: public:
explicit Options(std::string program, std::string help_string = "") explicit Options(std::string program, std::string help_string = "")
@ -1305,7 +1392,6 @@ namespace cxxopts
, m_show_positional(false) , m_show_positional(false)
, m_allow_unrecognised(false) , m_allow_unrecognised(false)
, m_options(std::make_shared<OptionMap>()) , m_options(std::make_shared<OptionMap>())
, m_next_positional(m_positional.end())
{ {
} }
@ -1338,7 +1424,7 @@ namespace cxxopts
} }
ParseResult ParseResult
parse(int& argc, char**& argv); parse(int argc, const char** argv);
OptionAdder OptionAdder
add_options(std::string group = ""); add_options(std::string group = "");
@ -1415,20 +1501,22 @@ namespace cxxopts
void void
generate_all_groups_help(String& result) const; generate_all_groups_help(String& result) const;
std::string m_program; std::string m_program{};
String m_help_string; String m_help_string{};
std::string m_custom_help{}; std::string m_custom_help{};
std::string m_positional_help; std::string m_positional_help{};
bool m_show_positional; bool m_show_positional;
bool m_allow_unrecognised; bool m_allow_unrecognised;
std::shared_ptr<OptionMap> m_options; std::shared_ptr<OptionMap> m_options;
std::vector<std::string> m_positional{}; std::vector<std::string> m_positional{};
std::vector<std::string>::iterator m_next_positional;
std::unordered_set<std::string> m_positional_set{}; std::unordered_set<std::string> m_positional_set{};
//mapping from groups to help options //mapping from groups to help options
std::map<std::string, HelpGroupDetails> m_help{}; std::map<std::string, HelpGroupDetails> m_help{};
std::list<OptionDetails> m_option_list{};
std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{};
}; };
class OptionAdder class OptionAdder
@ -1589,24 +1677,6 @@ namespace cxxopts
} }
} // namespace } // namespace
inline
ParseResult::ParseResult
(
std::shared_ptr<
std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
> options,
std::vector<std::string> positional,
bool allow_unrecognised,
int& argc, char**& argv
)
: m_options(std::move(options))
, m_positional(std::move(positional))
, m_next_positional(m_positional.begin())
, m_allow_unrecognised(allow_unrecognised)
{
parse(argc, argv);
}
inline inline
void void
Options::add_options Options::add_options
@ -1686,21 +1756,24 @@ OptionAdder::operator()
inline inline
void void
ParseResult::parse_default(const std::shared_ptr<OptionDetails>& details) OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)
{ {
m_results[details].parse_default(details); // TODO: remove the duplicate code here
auto& store = m_parsed[details->hash()];
store.parse_default(details);
} }
inline inline
void void
ParseResult::parse_option OptionParser::parse_option
( (
const std::shared_ptr<OptionDetails>& value, const std::shared_ptr<OptionDetails>& value,
const std::string& /*name*/, const std::string& /*name*/,
const std::string& arg const std::string& arg
) )
{ {
auto& result = m_results[value]; auto hash = value->hash();
auto& result = m_parsed[hash];
result.parse(value, arg); result.parse(value, arg);
m_sequential.emplace_back(value->long_name(), arg); m_sequential.emplace_back(value->long_name(), arg);
@ -1708,10 +1781,10 @@ ParseResult::parse_option
inline inline
void void
ParseResult::checked_parse_arg OptionParser::checked_parse_arg
( (
int argc, int argc,
char* argv[], const char* argv[],
int& current, int& current,
const std::shared_ptr<OptionDetails>& value, const std::shared_ptr<OptionDetails>& value,
const std::string& name const std::string& name
@ -1744,43 +1817,36 @@ ParseResult::checked_parse_arg
inline inline
void void
ParseResult::add_to_option(const std::string& option, const std::string& arg) OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
{ {
auto iter = m_options->find(option);
if (iter == m_options->end())
{
throw_or_mimic<option_not_exists_exception>(option);
}
parse_option(iter->second, option, arg); parse_option(iter->second, option, arg);
} }
inline inline
bool bool
ParseResult::consume_positional(const std::string& a) OptionParser::consume_positional(const std::string& a, PositionalListIterator& next)
{ {
while (m_next_positional != m_positional.end()) while (next != m_positional.end())
{ {
auto iter = m_options->find(*m_next_positional); auto iter = m_options.find(*next);
if (iter != m_options->end()) if (iter != m_options.end())
{ {
auto& result = m_results[iter->second]; auto& result = m_parsed[iter->second->hash()];
if (!iter->second->value().is_container()) if (!iter->second->value().is_container())
{ {
if (result.count() == 0) if (result.count() == 0)
{ {
add_to_option(*m_next_positional, a); add_to_option(iter, *next, a);
++m_next_positional; ++next;
return true; return true;
} }
++m_next_positional; ++next;
continue; continue;
} }
add_to_option(*m_next_positional, a); add_to_option(iter, *next, a);
return true; return true;
} }
throw_or_mimic<option_not_exists_exception>(*m_next_positional); throw_or_mimic<option_not_exists_exception>(*next);
} }
return false; return false;
@ -1798,7 +1864,6 @@ void
Options::parse_positional(std::vector<std::string> options) Options::parse_positional(std::vector<std::string> options)
{ {
m_positional = std::move(options); m_positional = std::move(options);
m_next_positional = m_positional.begin();
m_positional_set.insert(m_positional.begin(), m_positional.end()); m_positional_set.insert(m_positional.begin(), m_positional.end());
} }
@ -1812,21 +1877,21 @@ Options::parse_positional(std::initializer_list<std::string> options)
inline inline
ParseResult ParseResult
Options::parse(int& argc, char**& argv) Options::parse(int argc, const char** argv)
{ {
ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
return result;
return parser.parse(argc, argv);
} }
inline inline ParseResult
void OptionParser::parse(int argc, const char** argv)
ParseResult::parse(int& argc, char**& argv)
{ {
int current = 1; int current = 1;
int nextKeep = 1;
bool consume_remaining = false; bool consume_remaining = false;
PositionalListIterator next_positional = m_positional.begin();
std::vector<std::string> unmatched;
while (current != argc) while (current != argc)
{ {
@ -1853,13 +1918,12 @@ ParseResult::parse(int& argc, char**& argv)
//if true is returned here then it was consumed, otherwise it is //if true is returned here then it was consumed, otherwise it is
//ignored //ignored
if (consume_positional(argv[current])) if (consume_positional(argv[current], next_positional))
{ {
} }
else else
{ {
argv[nextKeep] = argv[current]; unmatched.push_back(argv[current]);
++nextKeep;
} }
//if we return from here then it was parsed successfully, so continue //if we return from here then it was parsed successfully, so continue
} }
@ -1873,9 +1937,9 @@ ParseResult::parse(int& argc, char**& argv)
for (std::size_t i = 0; i != s.size(); ++i) for (std::size_t i = 0; i != s.size(); ++i)
{ {
std::string name(1, s[i]); std::string name(1, s[i]);
auto iter = m_options->find(name); auto iter = m_options.find(name);
if (iter == m_options->end()) if (iter == m_options.end())
{ {
if (m_allow_unrecognised) if (m_allow_unrecognised)
{ {
@ -1907,15 +1971,14 @@ ParseResult::parse(int& argc, char**& argv)
{ {
const std::string& name = result[1]; const std::string& name = result[1];
auto iter = m_options->find(name); auto iter = m_options.find(name);
if (iter == m_options->end()) if (iter == m_options.end())
{ {
if (m_allow_unrecognised) if (m_allow_unrecognised)
{ {
// keep unrecognised options in argument list, skip to next argument // keep unrecognised options in argument list, skip to next argument
argv[nextKeep] = argv[current]; unmatched.push_back(argv[current]);
++nextKeep;
++current; ++current;
continue; continue;
} }
@ -1944,12 +2007,12 @@ ParseResult::parse(int& argc, char**& argv)
++current; ++current;
} }
for (auto& opt : *m_options) for (auto& opt : m_options)
{ {
auto& detail = opt.second; auto& detail = opt.second;
const auto& value = detail->value(); const auto& value = detail->value();
auto& store = m_results[detail]; auto& store = m_parsed[detail->hash()];
if(value.has_default() && !store.count() && !store.has_default()){ if(value.has_default() && !store.count() && !store.has_default()){
parse_default(detail); parse_default(detail);
@ -1960,7 +2023,7 @@ ParseResult::parse(int& argc, char**& argv)
{ {
while (current < argc) while (current < argc)
{ {
if (!consume_positional(argv[current])) { if (!consume_positional(argv[current], next_positional)) {
break; break;
} }
++current; ++current;
@ -1968,14 +2031,30 @@ ParseResult::parse(int& argc, char**& argv)
//adjust argv for any that couldn't be swallowed //adjust argv for any that couldn't be swallowed
while (current != argc) { while (current != argc) {
argv[nextKeep] = argv[current]; unmatched.push_back(argv[current]);
++nextKeep;
++current; ++current;
} }
} }
argc = nextKeep; finalise_aliases();
ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));
return parsed;
}
inline
void
OptionParser::finalise_aliases()
{
for (auto& option: m_options)
{
auto& detail = *option.second;
auto hash = detail.hash();
m_keys[detail.short_name()] = hash;
m_keys[detail.long_name()] = hash;
m_parsed.emplace(hash, OptionValue());
}
} }
inline inline
@ -2014,6 +2093,11 @@ Options::add_option
add_one_option(l, option); add_one_option(l, option);
} }
m_option_list.push_front(*option.get());
auto iter = m_option_list.begin();
m_option_map[s] = iter;
m_option_map[l] = iter;
//add the help details //add the help details
auto& options = m_help[group]; auto& options = m_help[group];

View File

@ -27,7 +27,7 @@ THE SOFTWARE.
#include "cxxopts.hpp" #include "cxxopts.hpp"
void void
parse(int argc, char* argv[]) parse(int argc, const char* argv[])
{ {
try try
{ {
@ -160,7 +160,7 @@ parse(int argc, char* argv[])
} }
} }
int main(int argc, char* argv[]) int main(int argc, const char* argv[])
{ {
parse(argc, argv); parse(argc, argv);

View File

@ -8,7 +8,7 @@ class Argv {
public: public:
Argv(std::initializer_list<const char*> args) Argv(std::initializer_list<const char*> args)
: m_argv(new char*[args.size()]) : m_argv(new const char*[args.size()])
, m_argc(static_cast<int>(args.size())) , m_argc(static_cast<int>(args.size()))
{ {
int i = 0; int i = 0;
@ -26,7 +26,7 @@ class Argv {
} }
} }
char** argv() const { const char** argv() const {
return m_argv.get(); return m_argv.get();
} }
@ -37,7 +37,7 @@ class Argv {
private: private:
std::vector<std::unique_ptr<char[]>> m_args; std::vector<std::unique_ptr<char[]>> m_args;
std::unique_ptr<char*[]> m_argv; std::unique_ptr<const char*[]> m_argv;
int m_argc; int m_argc;
}; };
@ -69,7 +69,7 @@ TEST_CASE("Basic options", "[options]")
"--space", "--space",
}); });
char** actual_argv = argv.argv(); auto** actual_argv = argv.argv();
auto argc = argv.argc(); auto argc = argv.argc();
auto result = options.parse(argc, actual_argv); auto result = options.parse(argc, actual_argv);
@ -94,7 +94,7 @@ TEST_CASE("Basic options", "[options]")
CHECK(arguments[2].key() == "value"); CHECK(arguments[2].key() == "value");
CHECK(arguments[3].key() == "av"); CHECK(arguments[3].key() == "av");
CHECK_THROWS_AS(result["nothing"].as<std::string>(), std::domain_error&); CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::option_has_no_value_exception&);
} }
TEST_CASE("Short options", "[options]") TEST_CASE("Short options", "[options]")
@ -125,7 +125,7 @@ TEST_CASE("No positional", "[positional]")
Argv av({"tester", "a", "b", "def"}); Argv av({"tester", "a", "b", "def"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -154,7 +154,7 @@ TEST_CASE("All positional", "[positional]")
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(argc == 1); CHECK(result.unmatched().size() == 0);
REQUIRE(positional.size() == 3); REQUIRE(positional.size() == 3);
CHECK(positional[0] == "a"); CHECK(positional[0] == "a");
@ -177,12 +177,12 @@ TEST_CASE("Some positional explicit", "[positional]")
Argv av({"tester", "--output", "a", "b", "c", "d"}); Argv av({"tester", "--output", "a", "b", "c", "d"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
CHECK(argc == 1); CHECK(result.unmatched().size() == 0);
CHECK(result.count("output")); CHECK(result.count("output"));
CHECK(result["input"].as<std::string>() == "b"); CHECK(result["input"].as<std::string>() == "b");
CHECK(result["output"].as<std::string>() == "a"); CHECK(result["output"].as<std::string>() == "a");
@ -203,17 +203,16 @@ TEST_CASE("No positional with extras", "[positional]")
Argv av({"extras", "--", "a", "b", "c", "d"}); Argv av({"extras", "--", "a", "b", "c", "d"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto old_argv = argv; auto old_argv = argv;
auto old_argc = argc; auto old_argc = argc;
options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(argc == old_argc - 1); auto& unmatched = result.unmatched();
CHECK(argv[0] == std::string("extras")); CHECK((unmatched == std::vector<std::string>{"a", "b", "c", "d"}));
CHECK(argv[1] == std::string("a"));
} }
TEST_CASE("Positional not valid", "[positional]") { TEST_CASE("Positional not valid", "[positional]") {
@ -226,7 +225,7 @@ TEST_CASE("Positional not valid", "[positional]") {
Argv av({"foobar", "bar", "baz"}); Argv av({"foobar", "bar", "baz"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&);
@ -241,7 +240,7 @@ TEST_CASE("Empty with implicit value", "[implicit]")
Argv av({"implicit", "--implicit="}); Argv av({"implicit", "--implicit="});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -260,7 +259,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
SECTION("When no value provided") { SECTION("When no value provided") {
Argv av({"no_implicit", "--bool"}); Argv av({"no_implicit", "--bool"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::missing_argument_exception&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::missing_argument_exception&);
@ -269,7 +268,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
SECTION("With equal-separated true") { SECTION("With equal-separated true") {
Argv av({"no_implicit", "--bool=true"}); Argv av({"no_implicit", "--bool=true"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -280,7 +279,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
SECTION("With equal-separated false") { SECTION("With equal-separated false") {
Argv av({"no_implicit", "--bool=false"}); Argv av({"no_implicit", "--bool=false"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -291,7 +290,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
SECTION("With space-separated true") { SECTION("With space-separated true") {
Argv av({"no_implicit", "--bool", "true"}); Argv av({"no_implicit", "--bool", "true"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -302,7 +301,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
SECTION("With space-separated false") { SECTION("With space-separated false") {
Argv av({"no_implicit", "--bool", "false"}); Argv av({"no_implicit", "--bool", "false"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -323,7 +322,7 @@ TEST_CASE("Default values", "[default]")
SECTION("Sets defaults") { SECTION("Sets defaults") {
Argv av({"implicit"}); Argv av({"implicit"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -339,7 +338,7 @@ TEST_CASE("Default values", "[default]")
SECTION("When values provided") { SECTION("When values provided") {
Argv av({"implicit", "--default", "5"}); Argv av({"implicit", "--default", "5"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -374,7 +373,7 @@ TEST_CASE("Integers", "[options]")
Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"}); Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
@ -401,7 +400,7 @@ TEST_CASE("Leading zero integers", "[options]")
Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"}); Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
@ -425,7 +424,7 @@ TEST_CASE("Unsigned integers", "[options]")
Argv av({"ints", "--", "-2"}); Argv av({"ints", "--", "-2"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
@ -504,7 +503,7 @@ TEST_CASE("Floats", "[options]")
Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"}); Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
@ -529,7 +528,7 @@ TEST_CASE("Invalid integers", "[integer]") {
Argv av({"ints", "--", "Ae"}); Argv av({"ints", "--", "Ae"});
char **argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
@ -554,7 +553,7 @@ TEST_CASE("Booleans", "[boolean]") {
Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"}); Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
@ -588,7 +587,7 @@ TEST_CASE("std::vector", "[vector]") {
Argv av({"vector", "--vector", "1,-2.1,3,4.5"}); Argv av({"vector", "--vector", "1,-2.1,3,4.5"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse(argc, argv); options.parse(argc, argv);
@ -609,7 +608,7 @@ TEST_CASE("std::optional", "[optional]") {
Argv av({"optional", "--optional", "foo"}); Argv av({"optional", "--optional", "foo"});
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse(argc, argv); options.parse(argc, argv);
@ -634,7 +633,7 @@ TEST_CASE("Unrecognised options", "[options]") {
"--another_unknown", "--another_unknown",
}); });
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
@ -643,9 +642,9 @@ TEST_CASE("Unrecognised options", "[options]") {
SECTION("After allowing unrecognised options") { SECTION("After allowing unrecognised options") {
options.allow_unrecognised_options(); options.allow_unrecognised_options();
CHECK_NOTHROW(options.parse(argc, argv)); auto result = options.parse(argc, argv);
REQUIRE(argc == 3); auto& unmatched = result.unmatched();
CHECK_THAT(argv[1], Catch::Equals("--unknown")); CHECK((unmatched == std::vector<std::string>{"--unknown", "--another_unknown"}));
} }
} }
@ -661,7 +660,7 @@ TEST_CASE("Allow bad short syntax", "[options]") {
"-some_bad_short", "-some_bad_short",
}); });
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
@ -684,7 +683,7 @@ TEST_CASE("Invalid option syntax", "[options]") {
"--a", "--a",
}); });
char** argv = av.argv(); auto** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
SECTION("Default behaviour") { SECTION("Default behaviour") {
@ -704,7 +703,7 @@ TEST_CASE("Options empty", "[options]") {
"--unknown" "--unknown"
}); });
auto argc = argv_.argc(); auto argc = argv_.argc();
char** argv = argv_.argv(); auto** argv = argv_.argv();
CHECK(options.groups().empty()); CHECK(options.groups().empty());
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&);
@ -733,7 +732,7 @@ TEST_CASE("Initializer list with group", "[options]") {
"8000", "8000",
"-t", "-t",
}); });
char** actual_argv = argv.argv(); auto** actual_argv = argv.argv();
auto argc = argv.argc(); auto argc = argv.argc();
auto result = options.parse(argc, actual_argv); auto result = options.parse(argc, actual_argv);
@ -763,7 +762,7 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") {
"4" "4"
}); });
auto argc = argv_.argc(); auto argc = argv_.argc();
char** argv = argv_.argv(); auto** argv = argv_.argv();
auto result = options.parse(argc, argv); auto result = options.parse(argc, argv);
CHECK(result.arguments().size()==2); CHECK(result.arguments().size()==2);