Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/jarro2783/cxxopts.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJarryd Beck <jarro.2783@gmail.com>2020-09-23 13:05:00 +0300
committerJarryd Beck <jarro.2783@gmail.com>2020-10-01 10:12:03 +0300
commitfedf9d7b57297765c18b95aecc9625f0104a0fde (patch)
tree8463d3a1f2c10b3d4cdeee840f79a8a594c12a82
parentfd5cdfd5476a63f2cd5f764b50c315f040be5efe (diff)
Refactor parser
Major refactor of the parsing code organisation to improve encapsulation and not modify the input arguments. The returned result no longer has pointers into the original option specification.
-rw-r--r--include/cxxopts.hpp308
-rw-r--r--test/options.cpp17
2 files changed, 190 insertions, 135 deletions
diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp
index e91987d..1de4717 100644
--- a/include/cxxopts.hpp
+++ b/include/cxxopts.hpp
@@ -30,6 +30,7 @@ THE SOFTWARE.
#include <exception>
#include <iostream>
#include <limits>
+#include <list>
#include <map>
#include <memory>
#include <regex>
@@ -1013,6 +1014,7 @@ namespace cxxopts
, m_value(std::move(val))
, m_count(0)
{
+ m_hash = std::hash<std::string>{}(m_long + m_short);
}
OptionDetails(const OptionDetails& rhs)
@@ -1058,12 +1060,20 @@ namespace cxxopts
return m_long;
}
+ size_t
+ hash() const
+ {
+ return m_hash;
+ }
+
private:
std::string m_short;
std::string m_long;
String m_desc;
std::shared_ptr<const Value> m_value;
int m_count;
+
+ size_t m_hash;
};
struct HelpOptionDetails
@@ -1198,45 +1208,65 @@ namespace cxxopts
std::string m_value;
};
+ using ParsedHashMap = std::unordered_map<size_t, OptionValue>;
+ using NameHashMap = std::unordered_map<std::string, size_t>;
+
class ParseResult
{
public:
- ParseResult(
- std::shared_ptr<
- std::unordered_map<std::string, std::shared_ptr<OptionDetails>>
- >,
- std::vector<std::string>,
- bool allow_unrecognised,
- int&, const char**&);
+ ParseResult() {}
+
+ ParseResult(const ParseResult&) = default;
+
+ ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)
+ : m_keys(std::move(keys))
+ , 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
count(const std::string& o) const
{
- auto iter = m_options->find(o);
- if (iter == m_options->end())
+ auto iter = m_keys.find(o);
+ if (iter == m_keys.end())
{
return 0;
}
- auto riter = m_results.find(iter->second);
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ return 0;
+ }
- return riter->second.count();
+ return viter->second.count();
}
const OptionValue&
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);
}
- auto riter = m_results.find(iter->second);
+ auto viter = m_values.find(iter->second);
+
+ if (viter == m_values.end())
+ {
+ throw_or_mimic<option_not_present_exception>(option);
+ }
- return riter->second;
+ return viter->second;
}
const std::vector<KeyValue>&
@@ -1245,49 +1275,17 @@ namespace cxxopts
return m_sequential;
}
- private:
-
- void
- parse(int& argc, const 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,
- const 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;
+ const std::vector<std::string>&
+ unmatched() const
+ {
+ return m_unmatched;
+ }
+ private:
+ NameHashMap m_keys;
+ ParsedHashMap m_values;
std::vector<KeyValue> m_sequential;
+ std::vector<std::string> m_unmatched;
};
struct Option
@@ -1312,9 +1310,66 @@ namespace cxxopts
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
{
- using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;
public:
explicit Options(std::string program, std::string help_string = "")
@@ -1325,7 +1380,6 @@ namespace cxxopts
, m_show_positional(false)
, m_allow_unrecognised(false)
, m_options(std::make_shared<OptionMap>())
- , m_next_positional(m_positional.end())
{
}
@@ -1358,7 +1412,7 @@ namespace cxxopts
}
ParseResult
- parse(int& argc, const char**& argv);
+ parse(int argc, const char** argv);
OptionAdder
add_options(std::string group = "");
@@ -1444,11 +1498,13 @@ namespace cxxopts
std::shared_ptr<OptionMap> m_options;
std::vector<std::string> m_positional;
- std::vector<std::string>::iterator m_next_positional;
std::unordered_set<std::string> m_positional_set;
//mapping from groups to help options
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
@@ -1610,24 +1666,6 @@ namespace cxxopts
} // 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, const 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
void
Options::add_options
(
@@ -1706,21 +1744,24 @@ OptionAdder::operator()
inline
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
void
-ParseResult::parse_option
+OptionParser::parse_option
(
const std::shared_ptr<OptionDetails>& value,
const std::string& /*name*/,
const std::string& arg
)
{
- auto& result = m_results[value];
+ auto hash = value->hash();
+ auto& result = m_parsed[hash];
result.parse(value, arg);
m_sequential.emplace_back(value->long_name(), arg);
@@ -1728,7 +1769,7 @@ ParseResult::parse_option
inline
void
-ParseResult::checked_parse_arg
+OptionParser::checked_parse_arg
(
int argc,
const char* argv[],
@@ -1764,43 +1805,36 @@ ParseResult::checked_parse_arg
inline
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);
}
inline
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);
- if (iter != m_options->end())
+ auto iter = m_options.find(*next);
+ 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 (result.count() == 0)
{
- add_to_option(*m_next_positional, a);
- ++m_next_positional;
+ add_to_option(iter, *next, a);
+ ++next;
return true;
}
- ++m_next_positional;
+ ++next;
continue;
}
- add_to_option(*m_next_positional, a);
+ add_to_option(iter, *next, a);
return true;
}
- throw_or_mimic<option_not_exists_exception>(*m_next_positional);
+ throw_or_mimic<option_not_exists_exception>(*next);
}
return false;
@@ -1818,7 +1852,6 @@ void
Options::parse_positional(std::vector<std::string> options)
{
m_positional = std::move(options);
- m_next_positional = m_positional.begin();
m_positional_set.insert(m_positional.begin(), m_positional.end());
}
@@ -1832,21 +1865,21 @@ Options::parse_positional(std::initializer_list<std::string> options)
inline
ParseResult
-Options::parse(int& argc, const char**& argv)
+Options::parse(int argc, const char** argv)
{
- ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv);
- return result;
+ OptionParser parser(*m_options, m_positional, m_allow_unrecognised);
+
+ return parser.parse(argc, argv);
}
-inline
-void
-ParseResult::parse(int& argc, const char**& argv)
+inline ParseResult
+OptionParser::parse(int argc, const char** argv)
{
int current = 1;
-
- int nextKeep = 1;
-
bool consume_remaining = false;
+ PositionalListIterator next_positional = m_positional.begin();
+
+ std::vector<std::string> unmatched;
while (current != argc)
{
@@ -1873,13 +1906,12 @@ ParseResult::parse(int& argc, const char**& argv)
//if true is returned here then it was consumed, otherwise it is
//ignored
- if (consume_positional(argv[current]))
+ if (consume_positional(argv[current], next_positional))
{
}
else
{
- argv[nextKeep] = argv[current];
- ++nextKeep;
+ unmatched.push_back(argv[current]);
}
//if we return from here then it was parsed successfully, so continue
}
@@ -1893,9 +1925,9 @@ ParseResult::parse(int& argc, const char**& argv)
for (std::size_t i = 0; i != s.size(); ++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)
{
@@ -1927,15 +1959,14 @@ ParseResult::parse(int& argc, const char**& argv)
{
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)
{
// keep unrecognised options in argument list, skip to next argument
- argv[nextKeep] = argv[current];
- ++nextKeep;
+ unmatched.push_back(argv[current]);
++current;
continue;
}
@@ -1964,12 +1995,13 @@ ParseResult::parse(int& argc, const char**& argv)
++current;
}
- for (auto& opt : *m_options)
+ for (auto& opt : m_options)
{
auto& detail = opt.second;
const auto& value = detail->value();
- auto& store = m_results[detail];
+ //auto& store = m_results[detail];
+ auto& store = m_parsed[detail->hash()];
if(value.has_default() && !store.count() && !store.has_default()){
parse_default(detail);
@@ -1980,7 +2012,7 @@ ParseResult::parse(int& argc, const char**& argv)
{
while (current < argc)
{
- if (!consume_positional(argv[current])) {
+ if (!consume_positional(argv[current], next_positional)) {
break;
}
++current;
@@ -1988,14 +2020,33 @@ ParseResult::parse(int& argc, const char**& argv)
//adjust argv for any that couldn't be swallowed
while (current != argc) {
- argv[nextKeep] = argv[current];
- ++nextKeep;
+ unmatched.push_back(argv[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();
+ //if (m_parsed.find(hash) != m_parsed.end())
+ {
+ m_keys[detail.short_name()] = hash;
+ m_keys[detail.long_name()] = hash;
+ }
+
+ m_parsed.emplace(hash, OptionValue());
+ }
}
inline
@@ -2034,6 +2085,11 @@ Options::add_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
auto& options = m_help[group];
diff --git a/test/options.cpp b/test/options.cpp
index 0dddfe4..b98bade 100644
--- a/test/options.cpp
+++ b/test/options.cpp
@@ -154,7 +154,7 @@ TEST_CASE("All positional", "[positional]")
auto result = options.parse(argc, argv);
- REQUIRE(argc == 1);
+ CHECK(result.unmatched().size() == 0);
REQUIRE(positional.size() == 3);
CHECK(positional[0] == "a");
@@ -182,7 +182,7 @@ TEST_CASE("Some positional explicit", "[positional]")
auto result = options.parse(argc, argv);
- CHECK(argc == 1);
+ CHECK(result.unmatched().size() == 0);
CHECK(result.count("output"));
CHECK(result["input"].as<std::string>() == "b");
CHECK(result["output"].as<std::string>() == "a");
@@ -209,11 +209,10 @@ TEST_CASE("No positional with extras", "[positional]")
auto old_argv = argv;
auto old_argc = argc;
- options.parse(argc, argv);
+ auto result = options.parse(argc, argv);
- REQUIRE(argc == old_argc - 1);
- CHECK(argv[0] == std::string("extras"));
- CHECK(argv[1] == std::string("a"));
+ auto& unmatched = result.unmatched();
+ CHECK((unmatched == std::vector<std::string>{"a", "b", "c", "d"}));
}
TEST_CASE("Positional not valid", "[positional]") {
@@ -643,9 +642,9 @@ TEST_CASE("Unrecognised options", "[options]") {
SECTION("After allowing unrecognised options") {
options.allow_unrecognised_options();
- CHECK_NOTHROW(options.parse(argc, argv));
- REQUIRE(argc == 3);
- CHECK_THAT(argv[1], Catch::Equals("--unknown"));
+ auto result = options.parse(argc, argv);
+ auto& unmatched = result.unmatched();
+ CHECK((unmatched == std::vector<std::string>{"--unknown", "--another_unknown"}));
}
}