Compare commits
30 Commits
fix_iter_a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70fb4e4814 | ||
|
|
4bf61f0869 | ||
|
|
1e175bc1db | ||
|
|
3bf268481d | ||
|
|
f7194e9c59 | ||
|
|
71d5bf7265 | ||
|
|
d3de64f26e | ||
|
|
e84ab5f67c | ||
|
|
7bf29108d5 | ||
|
|
cd61c685eb | ||
|
|
554396be3b | ||
|
|
78b90d8f0c | ||
|
|
c8c932f891 | ||
|
|
ddc695ebac | ||
|
|
bf1b5a96e0 | ||
|
|
90b318105f | ||
|
|
a526762eb8 | ||
|
|
beda973ec6 | ||
|
|
714a105fe6 | ||
|
|
16cc254919 | ||
|
|
120205ac5a | ||
|
|
89a9d334f2 | ||
|
|
44739d3023 | ||
|
|
134f60f973 | ||
|
|
eb787304d6 | ||
|
|
c12ce65503 | ||
|
|
b80bdfddf0 | ||
|
|
799ee0e3e8 | ||
|
|
58daccc945 | ||
|
|
e1f8c16702 |
24
.github/workflows/cifuzz.yml
vendored
Normal file
24
.github/workflows/cifuzz.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: CIFuzz
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cxxopts'
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'cxxopts'
|
||||
language: c++
|
||||
fuzz-seconds: 300
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
6
.github/workflows/cmake.yml
vendored
6
.github/workflows/cmake.yml
vendored
@ -20,11 +20,9 @@ jobs:
|
||||
build-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ]
|
||||
os: [ ubuntu-20.04, ubuntu-22.04 ]
|
||||
compiler: [ g++-9, g++-10, clang++ ]
|
||||
include:
|
||||
- os: ubuntu-18.04
|
||||
compiler: g++-7
|
||||
|
||||
name: Build and Test on Ubuntu
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
|
||||
@ -6,3 +6,11 @@ cc_library(
|
||||
strip_include_prefix = "include",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
|
||||
|
||||
cc_fuzz_test(
|
||||
name = "cxxopts_fuzz_test",
|
||||
srcs = ["test/fuzz.cpp"],
|
||||
deps = [":cxxopts"],
|
||||
)
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@ -3,17 +3,51 @@
|
||||
This is the changelog for `cxxopts`, a C++11 library for parsing command line
|
||||
options. The project adheres to semantic versioning.
|
||||
|
||||
## Unreleased
|
||||
## 3.2.1
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix compilation with optional on C++20.
|
||||
|
||||
## 3.2
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix unannotated fallthrough.
|
||||
* Fix sign conversion with Unicode output.
|
||||
* Don't initialize regex in static initialiser.
|
||||
* Fix incorrect integer overflow checks.
|
||||
|
||||
### Added
|
||||
|
||||
* Add fuzzing to CI
|
||||
|
||||
### Changed
|
||||
|
||||
* Change quote output to '' to match Windows.
|
||||
* Don't split positional arguments by the list delimiter.
|
||||
* Order help groups by the order they were added.
|
||||
|
||||
## 3.1.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed version number in header.
|
||||
* Fixed cast warning in Unicode function.
|
||||
|
||||
## 3.1
|
||||
|
||||
### Added
|
||||
|
||||
* Support for multiple long names for the same option (= multiple long aliases)
|
||||
* Add a `program()` function to retrieve the program name.
|
||||
* Added a .clang-format file.
|
||||
* Added iterator and printing for a ParseResult.
|
||||
|
||||
### Changed
|
||||
|
||||
* Cleanup exception code, add cxxopts::exceptions namespace.
|
||||
* Renamed several exceptions to be more descriptive, and added to a nested namespace.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
||||
11
README.md
11
README.md
@ -85,9 +85,6 @@ result["opt"].as<type>()
|
||||
to get its value. If "opt" doesn't exist, or isn't of the right type, then an
|
||||
exception will be thrown.
|
||||
|
||||
Note that the result of `options.parse` should only be used as long as the
|
||||
`options` object that created it is in scope.
|
||||
|
||||
## Unrecognised arguments
|
||||
|
||||
You can allow unrecognised arguments to be skipped. This applies to both
|
||||
@ -196,8 +193,8 @@ therefore, `-o false` does not work.
|
||||
|
||||
## `std::vector<T>` values
|
||||
|
||||
Parsing of list of values in form of an `std::vector<T>` is also supported, as long as `T`
|
||||
can be parsed. To separate single values in a list the definition `CXXOPTS_VECTOR_DELIMITER`
|
||||
Parsing a list of values into a `std::vector<T>` is also supported, as long as `T`
|
||||
can be parsed. To separate single values in a list the define symbol `CXXOPTS_VECTOR_DELIMITER`
|
||||
is used, which is ',' by default. Ensure that you use no whitespaces between values because
|
||||
those would be interpreted as the next command line option. Example for a command line option
|
||||
that can be parsed as a `std::vector<double>`:
|
||||
@ -278,7 +275,3 @@ GCC >= 4.9 or clang >= 3.1 with libc++ are known to work.
|
||||
The following compilers are known not to work:
|
||||
|
||||
* MSVC 2013
|
||||
|
||||
# TODO list
|
||||
|
||||
* Allow unrecognised options.
|
||||
|
||||
16
WORKSPACE
16
WORKSPACE
@ -0,0 +1,16 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "rules_fuzzing",
|
||||
sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118",
|
||||
strip_prefix = "rules_fuzzing-0.3.2",
|
||||
urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"],
|
||||
)
|
||||
|
||||
load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
|
||||
|
||||
rules_fuzzing_dependencies()
|
||||
|
||||
load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
|
||||
|
||||
rules_fuzzing_init()
|
||||
@ -27,6 +27,7 @@ THE SOFTWARE.
|
||||
#ifndef CXXOPTS_HPP_INCLUDED
|
||||
#define CXXOPTS_HPP_INCLUDED
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
@ -40,6 +41,7 @@ THE SOFTWARE.
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
|
||||
#ifdef CXXOPTS_NO_EXCEPTIONS
|
||||
#include <iostream>
|
||||
@ -54,8 +56,8 @@ THE SOFTWARE.
|
||||
#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern
|
||||
#define CXXOPTS_LINKONCE __declspec(selectany) extern
|
||||
#else
|
||||
#define CXXOPTS_LINKONCE_CONST
|
||||
#define CXXOPTS_LINKONCE
|
||||
#define CXXOPTS_LINKONCE_CONST
|
||||
#define CXXOPTS_LINKONCE
|
||||
#endif
|
||||
|
||||
#ifndef CXXOPTS_NO_REGEX
|
||||
@ -72,6 +74,14 @@ THE SOFTWARE.
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define CXXOPTS_FALLTHROUGH
|
||||
#ifdef __has_cpp_attribute
|
||||
#if __has_cpp_attribute(fallthrough)
|
||||
#undef CXXOPTS_FALLTHROUGH
|
||||
#define CXXOPTS_FALLTHROUGH [[fallthrough]]
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201603L
|
||||
#define CXXOPTS_NODISCARD [[nodiscard]]
|
||||
#else
|
||||
@ -83,8 +93,8 @@ THE SOFTWARE.
|
||||
#endif
|
||||
|
||||
#define CXXOPTS__VERSION_MAJOR 3
|
||||
#define CXXOPTS__VERSION_MINOR 0
|
||||
#define CXXOPTS__VERSION_PATCH 0
|
||||
#define CXXOPTS__VERSION_MINOR 2
|
||||
#define CXXOPTS__VERSION_PATCH 1
|
||||
|
||||
#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6
|
||||
#define CXXOPTS_NULL_DEREF_IGNORE
|
||||
@ -229,10 +239,10 @@ stringAppend(String& s, Iterator begin, Iterator end)
|
||||
}
|
||||
|
||||
inline
|
||||
std::size_t
|
||||
size_t
|
||||
stringLength(const String& s)
|
||||
{
|
||||
return s.length();
|
||||
return static_cast<size_t>(s.length());
|
||||
}
|
||||
|
||||
inline
|
||||
@ -336,13 +346,8 @@ empty(const std::string& s)
|
||||
namespace cxxopts {
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'");
|
||||
CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'");
|
||||
#else
|
||||
CXXOPTS_LINKONCE_CONST std::string LQUOTE("‘");
|
||||
CXXOPTS_LINKONCE_CONST std::string RQUOTE("’");
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we
|
||||
@ -364,6 +369,9 @@ class Value : public std::enable_shared_from_this<Value>
|
||||
std::shared_ptr<Value>
|
||||
clone() const = 0;
|
||||
|
||||
virtual void
|
||||
add(const std::string& text) const = 0;
|
||||
|
||||
virtual void
|
||||
parse(const std::string& text) const = 0;
|
||||
|
||||
@ -757,29 +765,31 @@ inline ArguDesc ParseArgument(const char *arg, bool &matched)
|
||||
|
||||
namespace {
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> integer_pattern
|
||||
("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)");
|
||||
const char* const integer_pattern =
|
||||
"(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)";
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> truthy_pattern
|
||||
("(t|T)(rue)?|1");
|
||||
const char* const truthy_pattern =
|
||||
"(t|T)(rue)?|1";
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> falsy_pattern
|
||||
("(f|F)(alse)?|0");
|
||||
const char* const falsy_pattern =
|
||||
"(f|F)(alse)?|0";
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> option_matcher
|
||||
("--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)");
|
||||
const char* const option_pattern =
|
||||
"--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)";
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> option_specifier
|
||||
("([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*");
|
||||
const char* const option_specifier_pattern =
|
||||
"([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*";
|
||||
CXXOPTS_LINKONCE
|
||||
std::basic_regex<char> option_specifier_separator(", *");
|
||||
const char* const option_specifier_separator_pattern = ", *";
|
||||
|
||||
} // namespace
|
||||
|
||||
inline IntegerDesc SplitInteger(const std::string &text)
|
||||
{
|
||||
static const std::basic_regex<char> integer_matcher(integer_pattern);
|
||||
|
||||
std::smatch match;
|
||||
std::regex_match(text, match, integer_pattern);
|
||||
std::regex_match(text, match, integer_matcher);
|
||||
|
||||
if (match.length() == 0)
|
||||
{
|
||||
@ -803,15 +813,17 @@ inline IntegerDesc SplitInteger(const std::string &text)
|
||||
|
||||
inline bool IsTrueText(const std::string &text)
|
||||
{
|
||||
static const std::basic_regex<char> truthy_matcher(truthy_pattern);
|
||||
std::smatch result;
|
||||
std::regex_match(text, result, truthy_pattern);
|
||||
std::regex_match(text, result, truthy_matcher);
|
||||
return !result.empty();
|
||||
}
|
||||
|
||||
inline bool IsFalseText(const std::string &text)
|
||||
{
|
||||
static const std::basic_regex<char> falsy_matcher(falsy_pattern);
|
||||
std::smatch result;
|
||||
std::regex_match(text, result, falsy_pattern);
|
||||
std::regex_match(text, result, falsy_matcher);
|
||||
return !result.empty();
|
||||
}
|
||||
|
||||
@ -820,22 +832,25 @@ inline bool IsFalseText(const std::string &text)
|
||||
// (without considering which or how many are single-character)
|
||||
inline OptionNames split_option_names(const std::string &text)
|
||||
{
|
||||
if (!std::regex_match(text.c_str(), option_specifier))
|
||||
static const std::basic_regex<char> option_specifier_matcher(option_specifier_pattern);
|
||||
if (!std::regex_match(text.c_str(), option_specifier_matcher))
|
||||
{
|
||||
throw_or_mimic<exceptions::invalid_option_format>(text);
|
||||
}
|
||||
|
||||
OptionNames split_names;
|
||||
|
||||
static const std::basic_regex<char> option_specifier_separator_matcher(option_specifier_separator_pattern);
|
||||
constexpr int use_non_matches { -1 };
|
||||
auto token_iterator = std::sregex_token_iterator(
|
||||
text.begin(), text.end(), option_specifier_separator, use_non_matches);
|
||||
text.begin(), text.end(), option_specifier_separator_matcher, use_non_matches);
|
||||
std::copy(token_iterator, std::sregex_token_iterator(), std::back_inserter(split_names));
|
||||
return split_names;
|
||||
}
|
||||
|
||||
inline ArguDesc ParseArgument(const char *arg, bool &matched)
|
||||
{
|
||||
static const std::basic_regex<char> option_matcher(option_pattern);
|
||||
std::match_results<const char*> result;
|
||||
std::regex_match(arg, result, option_matcher);
|
||||
matched = !result.empty();
|
||||
@ -958,13 +973,26 @@ integer_parser(const std::string& text, T& value)
|
||||
throw_or_mimic<exceptions::incorrect_argument_type>(text);
|
||||
}
|
||||
|
||||
const US next = static_cast<US>(result * base + digit);
|
||||
if (result > next)
|
||||
US limit = 0;
|
||||
if (negative)
|
||||
{
|
||||
limit = static_cast<US>(std::abs(static_cast<intmax_t>(std::numeric_limits<T>::min())));
|
||||
}
|
||||
else
|
||||
{
|
||||
limit = std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
if (base != 0 && result > limit / base)
|
||||
{
|
||||
throw_or_mimic<exceptions::incorrect_argument_type>(text);
|
||||
}
|
||||
if (result * base > limit - digit)
|
||||
{
|
||||
throw_or_mimic<exceptions::incorrect_argument_type>(text);
|
||||
}
|
||||
|
||||
result = next;
|
||||
result = static_cast<US>(result * base + digit);
|
||||
}
|
||||
|
||||
detail::check_signed_range<T>(negative, result, text);
|
||||
@ -1034,25 +1062,6 @@ parse_value(const std::string& text, T& value) {
|
||||
stringstream_parser(text, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
parse_value(const std::string& text, std::vector<T>& value)
|
||||
{
|
||||
if (text.empty()) {
|
||||
T v;
|
||||
parse_value(text, v);
|
||||
value.emplace_back(std::move(v));
|
||||
return;
|
||||
}
|
||||
std::stringstream in(text);
|
||||
std::string token;
|
||||
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
|
||||
T v;
|
||||
parse_value(token, v);
|
||||
value.emplace_back(std::move(v));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CXXOPTS_HAS_OPTIONAL
|
||||
template <typename T>
|
||||
void
|
||||
@ -1075,6 +1084,41 @@ void parse_value(const std::string& text, char& c)
|
||||
c = text[0];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
parse_value(const std::string& text, std::vector<T>& value)
|
||||
{
|
||||
if (text.empty()) {
|
||||
T v;
|
||||
parse_value(text, v);
|
||||
value.emplace_back(std::move(v));
|
||||
return;
|
||||
}
|
||||
std::stringstream in(text);
|
||||
std::string token;
|
||||
while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {
|
||||
T v;
|
||||
parse_value(token, v);
|
||||
value.emplace_back(std::move(v));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
add_value(const std::string& text, T& value)
|
||||
{
|
||||
parse_value(text, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
add_value(const std::string& text, std::vector<T>& value)
|
||||
{
|
||||
T v;
|
||||
add_value(text, v);
|
||||
value.emplace_back(std::move(v));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct type_is_container
|
||||
{
|
||||
@ -1126,6 +1170,12 @@ class abstract_value : public Value
|
||||
m_implicit_value = rhs.m_implicit_value;
|
||||
}
|
||||
|
||||
void
|
||||
add(const std::string& text) const override
|
||||
{
|
||||
add_value(text, *m_store);
|
||||
}
|
||||
|
||||
void
|
||||
parse(const std::string& text) const override
|
||||
{
|
||||
@ -1411,6 +1461,19 @@ struct HelpGroupDetails
|
||||
class OptionValue
|
||||
{
|
||||
public:
|
||||
void
|
||||
add
|
||||
(
|
||||
const std::shared_ptr<const OptionDetails>& details,
|
||||
const std::string& text
|
||||
)
|
||||
{
|
||||
ensure_value(details);
|
||||
++m_count;
|
||||
m_value->add(text);
|
||||
m_long_names = &details->long_names();
|
||||
}
|
||||
|
||||
void
|
||||
parse
|
||||
(
|
||||
@ -1475,6 +1538,15 @@ CXXOPTS_DIAGNOSTIC_POP
|
||||
return CXXOPTS_RTTI_CAST<const values::standard_value<T>&>(*m_value).get();
|
||||
}
|
||||
|
||||
#ifdef CXXOPTS_HAS_OPTIONAL
|
||||
template <typename T>
|
||||
std::optional<T>
|
||||
as_optional() const
|
||||
{
|
||||
return as<T>();
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
void
|
||||
ensure_value(const std::shared_ptr<const OptionDetails>& details)
|
||||
@ -1497,7 +1569,7 @@ CXXOPTS_DIAGNOSTIC_POP
|
||||
class KeyValue
|
||||
{
|
||||
public:
|
||||
KeyValue(std::string key_, std::string value_)
|
||||
KeyValue(std::string key_, std::string value_) noexcept
|
||||
: m_key(std::move(key_))
|
||||
, m_value(std::move(value_))
|
||||
{
|
||||
@ -1550,7 +1622,8 @@ class ParseResult
|
||||
Iterator(const Iterator&) = default;
|
||||
|
||||
// GCC complains about m_iter not being initialised in the member
|
||||
// initializer list
|
||||
// initializer list
|
||||
CXXOPTS_DIAGNOSTIC_PUSH
|
||||
CXXOPTS_IGNORE_WARNING("-Weffc++")
|
||||
Iterator(const ParseResult *pr, bool end=false)
|
||||
: m_pr(pr)
|
||||
@ -1686,6 +1759,24 @@ CXXOPTS_DIAGNOSTIC_POP
|
||||
return viter->second;
|
||||
}
|
||||
|
||||
#ifdef CXXOPTS_HAS_OPTIONAL
|
||||
template <typename T>
|
||||
std::optional<T>
|
||||
as_optional(const std::string& option) const
|
||||
{
|
||||
auto iter = m_keys.find(option);
|
||||
if (iter != m_keys.end())
|
||||
{
|
||||
auto viter = m_values.find(iter->second);
|
||||
if (viter != m_values.end())
|
||||
{
|
||||
return viter->second.as_optional<T>();
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::vector<KeyValue>&
|
||||
arguments() const
|
||||
{
|
||||
@ -1780,7 +1871,7 @@ class OptionParser
|
||||
);
|
||||
|
||||
void
|
||||
add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);
|
||||
add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg);
|
||||
|
||||
void
|
||||
parse_option
|
||||
@ -1983,6 +2074,7 @@ class Options
|
||||
std::unordered_set<std::string> m_positional_set{};
|
||||
|
||||
//mapping from groups to help options
|
||||
std::vector<std::string> m_group{};
|
||||
std::map<std::string, HelpGroupDetails> m_help{};
|
||||
};
|
||||
|
||||
@ -2235,6 +2327,7 @@ OptionAdder::operator()
|
||||
case 1:
|
||||
short_name = *first_short_name_iter;
|
||||
option_names.erase(first_short_name_iter);
|
||||
CXXOPTS_FALLTHROUGH;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
@ -2326,9 +2419,13 @@ OptionParser::checked_parse_arg
|
||||
|
||||
inline
|
||||
void
|
||||
OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)
|
||||
OptionParser::add_to_option(const std::shared_ptr<OptionDetails>& value, const std::string& arg)
|
||||
{
|
||||
parse_option(iter->second, option, arg);
|
||||
auto hash = value->hash();
|
||||
auto& result = m_parsed[hash];
|
||||
result.add(value, arg);
|
||||
|
||||
m_sequential.emplace_back(value->essential_name(), arg);
|
||||
}
|
||||
|
||||
inline
|
||||
@ -2345,14 +2442,14 @@ OptionParser::consume_positional(const std::string& a, PositionalListIterator& n
|
||||
auto& result = m_parsed[iter->second->hash()];
|
||||
if (result.count() == 0)
|
||||
{
|
||||
add_to_option(iter, *next, a);
|
||||
add_to_option(iter->second, a);
|
||||
++next;
|
||||
return true;
|
||||
}
|
||||
++next;
|
||||
continue;
|
||||
}
|
||||
add_to_option(iter, *next, a);
|
||||
add_to_option(iter->second, a);
|
||||
return true;
|
||||
}
|
||||
throw_or_mimic<exceptions::no_such_option>(*next);
|
||||
@ -2616,6 +2713,12 @@ Options::add_option
|
||||
}
|
||||
|
||||
//add the help details
|
||||
|
||||
if (m_help.find(group) == m_help.end())
|
||||
{
|
||||
m_group.push_back(group);
|
||||
}
|
||||
|
||||
auto& options = m_help[group];
|
||||
|
||||
options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,
|
||||
@ -2747,19 +2850,7 @@ inline
|
||||
void
|
||||
Options::generate_all_groups_help(String& result) const
|
||||
{
|
||||
std::vector<std::string> all_groups;
|
||||
|
||||
std::transform(
|
||||
m_help.begin(),
|
||||
m_help.end(),
|
||||
std::back_inserter(all_groups),
|
||||
[] (const std::map<std::string, HelpGroupDetails>::value_type& group)
|
||||
{
|
||||
return group.first;
|
||||
}
|
||||
);
|
||||
|
||||
generate_group_help(result, all_groups);
|
||||
generate_group_help(result, m_group);
|
||||
}
|
||||
|
||||
inline
|
||||
@ -2799,19 +2890,7 @@ inline
|
||||
std::vector<std::string>
|
||||
Options::groups() const
|
||||
{
|
||||
std::vector<std::string> g;
|
||||
|
||||
std::transform(
|
||||
m_help.begin(),
|
||||
m_help.end(),
|
||||
std::back_inserter(g),
|
||||
[] (const std::map<std::string, HelpGroupDetails>::value_type& pair)
|
||||
{
|
||||
return pair.first;
|
||||
}
|
||||
);
|
||||
|
||||
return g;
|
||||
return m_group;
|
||||
}
|
||||
|
||||
inline
|
||||
|
||||
@ -51,3 +51,10 @@ add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND}
|
||||
|
||||
add_executable(link_test link_a.cpp link_b.cpp)
|
||||
target_link_libraries(link_test cxxopts)
|
||||
|
||||
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND ("${CMAKE_SYSTEM}" MATCHES "Linux"))
|
||||
add_executable(fuzzer fuzz.cpp)
|
||||
target_link_libraries(fuzzer PRIVATE cxxopts)
|
||||
target_compile_options(fuzzer PRIVATE -fsanitize=fuzzer)
|
||||
target_link_options(fuzzer PRIVATE -fsanitize=fuzzer)
|
||||
endif()
|
||||
|
||||
24938
test/catch.hpp
24938
test/catch.hpp
File diff suppressed because it is too large
Load Diff
107
test/fuzz.cpp
Normal file
107
test/fuzz.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include <cassert>
|
||||
#include <cxxopts.hpp>
|
||||
#include <fuzzer/FuzzedDataProvider.h>
|
||||
|
||||
constexpr int kMaxOptions = 1024;
|
||||
constexpr int kMaxArgSize = 1024;
|
||||
|
||||
enum class ParseableTypes
|
||||
{
|
||||
kInt,
|
||||
kString,
|
||||
kVectorString,
|
||||
kFloat,
|
||||
kDouble,
|
||||
|
||||
// Marker for fuzzer.
|
||||
kMaxValue,
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
add_fuzzed_option(cxxopts::Options* options, FuzzedDataProvider* provider)
|
||||
{
|
||||
assert(options);
|
||||
assert(provider);
|
||||
|
||||
options->add_options()(provider->ConsumeRandomLengthString(kMaxArgSize),
|
||||
provider->ConsumeRandomLengthString(kMaxArgSize),
|
||||
cxxopts::value<T>());
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
try
|
||||
{
|
||||
FuzzedDataProvider provider(data, size);
|
||||
|
||||
// Randomly generate a usage string.
|
||||
cxxopts::Options options(provider.ConsumeRandomLengthString(kMaxArgSize),
|
||||
provider.ConsumeRandomLengthString(kMaxArgSize));
|
||||
|
||||
// Randomly generate a set of flags configurations.
|
||||
for (int i = 0; i < provider.ConsumeIntegralInRange<int>(0, kMaxOptions);
|
||||
i++)
|
||||
{
|
||||
switch (provider.ConsumeEnum<ParseableTypes>())
|
||||
{
|
||||
case ParseableTypes::kInt:
|
||||
add_fuzzed_option<int>(&options, &provider);
|
||||
break;
|
||||
case ParseableTypes::kString:
|
||||
add_fuzzed_option<std::string>(&options, &provider);
|
||||
break;
|
||||
case ParseableTypes::kVectorString:
|
||||
add_fuzzed_option<std::vector<std::string>>(&options, &provider);
|
||||
break;
|
||||
case ParseableTypes::kFloat:
|
||||
add_fuzzed_option<float>(&options, &provider);
|
||||
break;
|
||||
case ParseableTypes::kDouble:
|
||||
add_fuzzed_option<double>(&options, &provider);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Sometimes allow unrecognised options.
|
||||
if (provider.ConsumeBool())
|
||||
{
|
||||
options.allow_unrecognised_options();
|
||||
}
|
||||
// Sometimes allow trailing positional arguments.
|
||||
if (provider.ConsumeBool())
|
||||
{
|
||||
std::string positional_option_name =
|
||||
provider.ConsumeRandomLengthString(kMaxArgSize);
|
||||
options.add_options()(positional_option_name,
|
||||
provider.ConsumeRandomLengthString(kMaxArgSize),
|
||||
cxxopts::value<std::vector<std::string>>());
|
||||
options.parse_positional({positional_option_name});
|
||||
}
|
||||
|
||||
// Build command line input.
|
||||
const int argc = provider.ConsumeIntegralInRange<int>(1, kMaxOptions);
|
||||
|
||||
std::vector<std::string> command_line_container;
|
||||
command_line_container.reserve(argc);
|
||||
|
||||
std::vector<const char*> argv;
|
||||
argv.reserve(argc);
|
||||
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
command_line_container.push_back(
|
||||
provider.ConsumeRandomLengthString(kMaxArgSize));
|
||||
argv.push_back(command_line_container[i].c_str());
|
||||
}
|
||||
|
||||
// Parse command line;
|
||||
auto result = options.parse(argc, argv.data());
|
||||
} catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
149
test/options.cpp
149
test/options.cpp
@ -112,7 +112,7 @@ TEST_CASE("Basic options", "[options]")
|
||||
CHECK(arguments[2].key() == "value");
|
||||
CHECK(arguments[3].key() == "av");
|
||||
|
||||
CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value&);
|
||||
CHECK_THROWS_AS(result["nothing"].as<std::string>(), cxxopts::exceptions::option_has_no_value);
|
||||
|
||||
CHECK(options.program() == "tester");
|
||||
}
|
||||
@ -147,7 +147,7 @@ TEST_CASE("Short options", "[options]")
|
||||
CHECK(result["c"].as<std::string>() == "foo=something");
|
||||
|
||||
REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
|
||||
cxxopts::exceptions::invalid_option_format&);
|
||||
cxxopts::exceptions::invalid_option_format);
|
||||
}
|
||||
|
||||
TEST_CASE("No positional", "[positional]")
|
||||
@ -238,9 +238,6 @@ TEST_CASE("No positional with extras", "[positional]")
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
|
||||
auto old_argv = argv;
|
||||
auto old_argc = argc;
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
|
||||
auto& unmatched = result.unmatched();
|
||||
@ -260,7 +257,7 @@ TEST_CASE("Positional not valid", "[positional]") {
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
|
||||
}
|
||||
|
||||
TEST_CASE("Positional with empty arguments", "[positional]") {
|
||||
@ -288,6 +285,37 @@ TEST_CASE("Positional with empty arguments", "[positional]") {
|
||||
REQUIRE(actual == expected);
|
||||
}
|
||||
|
||||
TEST_CASE("Positional with list delimiter", "[positional]") {
|
||||
std::string single;
|
||||
std::vector<std::string> positional;
|
||||
|
||||
cxxopts::Options options("test_all_positional_list_delimiter", " - test all positional with list delimiters");
|
||||
options.add_options()
|
||||
("single", "Single positional param",
|
||||
cxxopts::value<std::string>(single))
|
||||
("positional", "Positional parameters vector",
|
||||
cxxopts::value<std::vector<std::string>>(positional))
|
||||
;
|
||||
|
||||
Argv av({"tester", "a,b", "c,d", "e"});
|
||||
|
||||
auto argc = av.argc();
|
||||
auto argv = av.argv();
|
||||
|
||||
std::vector<std::string> pos_names = {"single", "positional"};
|
||||
|
||||
options.parse_positional(pos_names.begin(), pos_names.end());
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
|
||||
CHECK(result.unmatched().size() == 0);
|
||||
REQUIRE(positional.size() == 2);
|
||||
|
||||
CHECK(single == "a,b");
|
||||
CHECK(positional[0] == "c,d");
|
||||
CHECK(positional[1] == "e");
|
||||
}
|
||||
|
||||
TEST_CASE("Empty with implicit value", "[implicit]")
|
||||
{
|
||||
cxxopts::Options options("empty_implicit", "doesn't handle empty");
|
||||
@ -319,7 +347,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]")
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::missing_argument);
|
||||
}
|
||||
|
||||
SECTION("With equal-separated true") {
|
||||
@ -489,7 +517,7 @@ TEST_CASE("Unsigned integers", "[options]")
|
||||
auto argc = av.argc();
|
||||
|
||||
options.parse_positional("positional");
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
|
||||
}
|
||||
|
||||
TEST_CASE("Integer bounds", "[integer]")
|
||||
@ -524,14 +552,32 @@ TEST_CASE("Overflow on boundary", "[integer]")
|
||||
using namespace cxxopts::values;
|
||||
|
||||
int8_t si;
|
||||
int16_t si16;
|
||||
int64_t si64;
|
||||
uint8_t ui;
|
||||
uint16_t ui16;
|
||||
uint64_t ui64;
|
||||
|
||||
CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("128", si)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-129", si)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("256", ui)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-0x81", si)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("0x80", si)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("0x100", ui)), cxxopts::exceptions::incorrect_argument_type);
|
||||
|
||||
CHECK_THROWS_AS((integer_parser("65536", ui16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("75536", ui16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("32768", si16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-32769", si16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-42769", si16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-75536", si16)), cxxopts::exceptions::incorrect_argument_type);
|
||||
|
||||
CHECK_THROWS_AS((integer_parser("18446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("28446744073709551616", ui64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("9223372036854775808", si64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-9223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-10223372036854775809", si64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("-28446744073709551616", si64)), cxxopts::exceptions::incorrect_argument_type);
|
||||
}
|
||||
|
||||
TEST_CASE("Integer overflow", "[options]")
|
||||
@ -548,11 +594,11 @@ TEST_CASE("Integer overflow", "[options]")
|
||||
auto argc = av.argc();
|
||||
|
||||
options.parse_positional("positional");
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
|
||||
|
||||
int integer = 0;
|
||||
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS((integer_parser("23423423423", integer)), cxxopts::exceptions::incorrect_argument_type);
|
||||
CHECK_THROWS_AS((integer_parser("234234234234", integer)), cxxopts::exceptions::incorrect_argument_type);
|
||||
}
|
||||
|
||||
TEST_CASE("Floats", "[options]")
|
||||
@ -593,7 +639,7 @@ TEST_CASE("Invalid integers", "[integer]") {
|
||||
auto argc = av.argc();
|
||||
|
||||
options.parse_positional("positional");
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::incorrect_argument_type);
|
||||
}
|
||||
|
||||
TEST_CASE("Booleans", "[boolean]") {
|
||||
@ -663,11 +709,13 @@ TEST_CASE("std::vector", "[vector]") {
|
||||
#ifdef CXXOPTS_HAS_OPTIONAL
|
||||
TEST_CASE("std::optional", "[optional]") {
|
||||
std::optional<std::string> optional;
|
||||
std::optional<bool> opt_bool;
|
||||
cxxopts::Options options("optional", " - tests optional");
|
||||
options.add_options()
|
||||
("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional));
|
||||
("optional", "an optional option", cxxopts::value<std::optional<std::string>>(optional))
|
||||
("optional_bool", "an boolean optional", cxxopts::value<std::optional<bool>>(opt_bool)->default_value("false"));
|
||||
|
||||
Argv av({"optional", "--optional", "foo"});
|
||||
Argv av({"optional", "--optional", "foo", "--optional_bool", "true"});
|
||||
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
@ -676,6 +724,8 @@ TEST_CASE("std::optional", "[optional]") {
|
||||
|
||||
REQUIRE(optional.has_value());
|
||||
CHECK(*optional == "foo");
|
||||
CHECK(opt_bool.has_value());
|
||||
CHECK(*opt_bool);
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -699,7 +749,7 @@ TEST_CASE("Unrecognised options", "[options]") {
|
||||
auto argc = av.argc();
|
||||
|
||||
SECTION("Default behaviour") {
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
|
||||
}
|
||||
|
||||
SECTION("After allowing unrecognised options") {
|
||||
@ -726,7 +776,7 @@ TEST_CASE("Allow bad short syntax", "[options]") {
|
||||
auto argc = av.argc();
|
||||
|
||||
SECTION("Default behaviour") {
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
|
||||
}
|
||||
|
||||
SECTION("After allowing unrecognised options") {
|
||||
@ -749,7 +799,7 @@ TEST_CASE("Invalid option syntax", "[options]") {
|
||||
auto argc = av.argc();
|
||||
|
||||
SECTION("Default behaviour") {
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::invalid_option_syntax);
|
||||
}
|
||||
}
|
||||
|
||||
@ -768,9 +818,60 @@ TEST_CASE("Options empty", "[options]") {
|
||||
auto** argv = argv_.argv();
|
||||
|
||||
CHECK(options.groups().empty());
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option&);
|
||||
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::exceptions::no_such_option);
|
||||
}
|
||||
|
||||
#ifdef CXXOPTS_HAS_OPTIONAL
|
||||
TEST_CASE("Optional value", "[optional]")
|
||||
{
|
||||
cxxopts::Options options("options", "query as std::optional");
|
||||
options.add_options()
|
||||
("int", "Integer", cxxopts::value<int>())
|
||||
("float", "Float", cxxopts::value<float>())
|
||||
("string", "String", cxxopts::value<std::string>())
|
||||
;
|
||||
|
||||
SECTION("Available") {
|
||||
Argv av({
|
||||
"--int",
|
||||
"42",
|
||||
"--float",
|
||||
"3.141",
|
||||
"--string",
|
||||
"Hello"
|
||||
});
|
||||
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
|
||||
CHECK(result.as_optional<int>("int"));
|
||||
CHECK(result.as_optional<float>("float"));
|
||||
CHECK(result.as_optional<string>("string"));
|
||||
|
||||
CHECK(*result.as_optional<int>("int") == 42);
|
||||
CHECK(*result.as_optional<float>("float") == 3.141);
|
||||
CHECK(*result.as_optional<string>("string") == "Hello");
|
||||
}
|
||||
|
||||
SECTION("Unavailable") {
|
||||
Argv av({
|
||||
});
|
||||
|
||||
auto** argv = av.argv();
|
||||
auto argc = av.argc();
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
|
||||
CHECK(!result.as_optional<int>("int"));
|
||||
CHECK(!result.as_optional<float>("float"));
|
||||
CHECK(!result.as_optional<string>("string"));
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Initializer list with group", "[options]") {
|
||||
cxxopts::Options options("Initializer list group", " - test initializer list with group");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user