diff options
author | Jarryd Beck <jarro.2783@gmail.com> | 2017-11-15 10:04:20 +0300 |
---|---|---|
committer | Jarryd Beck <jarro.2783@gmail.com> | 2017-11-15 10:04:20 +0300 |
commit | 8893afe13cc47dd0be4f25b5ae491e652c146098 (patch) | |
tree | 6c2c56bf4940eee0aec1ce397d9225a4f6756241 | |
parent | d7b930846cdccfc8bcecc4d7150ddcbadffac360 (diff) | |
parent | 70b9230639cad36c54d19aae37ae890e44939cdf (diff) |
Merge branch '2_0'v2.0.0
-rw-r--r-- | CHANGELOG.md | 29 | ||||
-rw-r--r-- | README.md | 19 | ||||
-rw-r--r-- | include/cxxopts.hpp | 648 | ||||
-rw-r--r-- | src/example.cpp | 36 | ||||
-rw-r--r-- | test/CMakeLists.txt | 3 | ||||
-rw-r--r-- | test/link_a.cpp | 6 | ||||
-rw-r--r-- | test/link_b.cpp | 1 | ||||
-rw-r--r-- | test/options.cpp | 174 |
8 files changed, 621 insertions, 295 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8b4ba45 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +This is the changelog for `cxxopts`, a C++11 library for parsing command line +options. The project adheres to semantic versioning. + +## 2.0 + +### Changed + +* `Options::parse` returns a ParseResult rather than storing the parse + result internally. +* Options with default values now get counted as appearing once if they + were not specified by the user. + +### Added + +* A new `ParseResult` object that is the immutable result of parsing. It + responds to the same `count` and `operator[]` as `Options` of 1.x did. +* The function `ParseResult::arguments` returns a vector of the parsed + arguments to iterate through in the order they were provided. +* The symbol `cxxopts::version` for the version of the library. +* Booleans can be specified with various strings and explicitly set false. + +## 1.x + +The 1.x series was the first major version of the library, with release numbers +starting to follow semantic versioning, after 0.x being unstable. It never had +a changelog maintained for it. Releases mostly contained bug fixes, with the +occasional feature added. @@ -39,12 +39,12 @@ Any type can be given as long as it can be parsed, with operator>>. To parse the command line do: - options.parse(argc, argv); + auto result = options.parse(argc, argv); -To retrieve an option use `options.count("option")` to get the number of times +To retrieve an option use `result.count("option")` to get the number of times it appeared, and - options["opt"].as<type>() + 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. @@ -84,6 +84,17 @@ If an option had both, then not specifying it would give the value `"value"`, writing it on the command line as `--option` would give the value `"implicit"`, and writing `--option=another` would give it the value `"another"`. +Note that the default and implicit value is always stored as a string, +regardless of the type that you want to store it in. It will be parsed as +though it was given on the command line. + +## Boolean values + +Boolean options have a default implicit value of `"true"`, which can be +overridden. The effect is that writing `-o` by itself will set option `o` to +`true`. However, they can also be written with various strings using either +`=value` or the next argument. + # Linking This is a header only library. @@ -93,9 +104,7 @@ This is a header only library. The only build requirement is a C++ compiler that supports C++11 regular expressions. For example GCC >= 4.9 or clang with libc++. - # TODO list * Allow unrecognised options. * Various help strings. -* Unicode aware for help strings. diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 17bb590..3117e9d 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -22,13 +22,8 @@ THE SOFTWARE. */ -#ifndef CXX_OPTS_HPP -#define CXX_OPTS_HPP - -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" -#endif +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED #include <cstring> #include <cctype> @@ -39,9 +34,17 @@ THE SOFTWARE. #include <regex> #include <sstream> #include <string> +#include <unordered_map> #include <unordered_set> #include <vector> +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = {2, 0, 0}; +} + //when we ask cxxopts to use Unicode, help strings are processed using ICU, //which results in the correct lengths being computed for strings when they //are formatted for the help output @@ -168,12 +171,14 @@ namespace cxxopts namespace std { + inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) { return cxxopts::UnicodeStringIterator(&s, 0); } + inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s) { @@ -258,6 +263,12 @@ namespace cxxopts { public: + virtual ~Value() = default; + + virtual + std::shared_ptr<Value> + clone() const = 0; + virtual void parse(const std::string& text) const = 0; @@ -265,9 +276,6 @@ namespace cxxopts parse() const = 0; virtual bool - has_arg() const = 0; - - virtual bool has_default() const = 0; virtual bool @@ -287,6 +295,9 @@ namespace cxxopts virtual std::shared_ptr<Value> implicit_value(const std::string& value) = 0; + + virtual bool + is_boolean() const = 0; }; class OptionException : public std::exception @@ -385,7 +396,7 @@ namespace cxxopts ) : OptionParseException( u8"Option " + LQUOTE + option + RQUOTE + - u8" does not take an argument, but argument" + + u8" does not take an argument, but argument " + LQUOTE + arg + RQUOTE + " given" ) { @@ -432,6 +443,10 @@ namespace cxxopts { std::basic_regex<char> integer_pattern ("(-)?(0x)?([1-9a-zA-Z][0-9a-zA-Z]*)|((0x)?0)"); + std::basic_regex<char> truthy_pattern + ("t|true|T|True"); + std::basic_regex<char> falsy_pattern + ("(f|false|F|False)?"); } namespace detail @@ -561,11 +576,6 @@ namespace cxxopts value = checked_negate<T>(result, text, std::integral_constant<bool, is_signed>()); - //if (!is_signed) - //{ - // throw argument_incorrect_type(text); - //} - //value = -result; } else { @@ -641,11 +651,25 @@ namespace cxxopts inline void - parse_value(const std::string& /*text*/, bool& value) + parse_value(const std::string& text, bool& value) { - //TODO recognise on, off, yes, no, enable, disable - //so that we can write --long=yes explicitly - value = true; + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); } inline @@ -674,18 +698,6 @@ namespace cxxopts } template <typename T> - struct value_has_arg - { - static constexpr bool value = true; - }; - - template <> - struct value_has_arg<bool> - { - static constexpr bool value = false; - }; - - template <typename T> struct type_is_container { static constexpr bool value = false; @@ -698,20 +710,42 @@ namespace cxxopts }; template <typename T> - class standard_value final : public Value + class abstract_value : public Value { + using Self = abstract_value<T>; + public: - standard_value() + abstract_value() : m_result(std::make_shared<T>()) , m_store(m_result.get()) { } - standard_value(T* t) + abstract_value(T* t) : m_store(t) { } + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared<T>(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + void parse(const std::string& text) const { @@ -731,12 +765,6 @@ namespace cxxopts } bool - has_arg() const - { - return value_has_arg<T>::value; - } - - bool has_default() const { return m_default; @@ -748,15 +776,17 @@ namespace cxxopts return m_implicit; } - virtual std::shared_ptr<Value> - default_value(const std::string& value){ + std::shared_ptr<Value> + default_value(const std::string& value) + { m_default = true; m_default_value = value; return shared_from_this(); } - virtual std::shared_ptr<Value> - implicit_value(const std::string& value){ + std::shared_ptr<Value> + implicit_value(const std::string& value) + { m_implicit = true; m_implicit_value = value; return shared_from_this(); @@ -774,6 +804,12 @@ namespace cxxopts return m_implicit_value; } + bool + is_boolean() const + { + return std::is_same<T, bool>::value; + } + const T& get() const { @@ -790,11 +826,59 @@ namespace cxxopts protected: std::shared_ptr<T> m_result; T* m_store; + bool m_default = false; - std::string m_default_value; bool m_implicit = false; + + std::string m_default_value; std::string m_implicit_value; }; + + template <typename T> + class standard_value : public abstract_value<T> + { + public: + using abstract_value<T>::abstract_value; + + std::shared_ptr<Value> + clone() const + { + return std::make_shared<standard_value<T>>(*this); + } + }; + + template <> + class standard_value<bool> : public abstract_value<bool> + { + public: + ~standard_value() = default; + + standard_value() + { + set_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_implicit(); + } + + std::shared_ptr<Value> + clone() const + { + return std::make_shared<standard_value<bool>>(*this); + } + + private: + + void + set_implicit() + { + m_implicit = true; + m_implicit_value = "true"; + } + }; } template <typename T> @@ -818,62 +902,59 @@ namespace cxxopts public: OptionDetails ( + const std::string& short_name, + const std::string& long_name, const String& desc, std::shared_ptr<const Value> val ) - : m_desc(desc) + : m_short(short_name) + , m_long(long_name) + , m_desc(desc) , m_value(val) , m_count(0) { } - const String& - description() const + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) { - return m_desc; + m_value = rhs.m_value->clone(); } - bool - has_arg() const - { - return m_value->has_arg(); - } + OptionDetails(OptionDetails&& rhs) = default; - void - parse(const std::string& text) + const String& + description() const { - m_value->parse(text); - ++m_count; + return m_desc; } - void - parse_default() - { - m_value->parse(); + const Value& value() const { + return *m_value; } - int - count() const + std::shared_ptr<Value> + make_storage() const { - return m_count; + return m_value->clone(); } - const Value& value() const { - return *m_value; + const std::string& + short_name() const + { + return m_short; } - template <typename T> - const T& - as() const + const std::string& + long_name() const { -#ifdef CXXOPTS_NO_RTTI - return static_cast<const values::standard_value<T>&>(*m_value).get(); -#else - return dynamic_cast<const values::standard_value<T>&>(*m_value).get(); -#endif + return m_long; } private: + std::string m_short; + std::string m_long; String m_desc; std::shared_ptr<const Value> m_value; int m_count; @@ -884,13 +965,13 @@ namespace cxxopts std::string s; std::string l; String desc; - bool has_arg; bool has_default; std::string default_value; bool has_implicit; std::string implicit_value; std::string arg_help; bool is_container; + bool is_boolean; }; struct HelpGroupDetails @@ -900,45 +981,104 @@ namespace cxxopts std::vector<HelpOptionDetails> options; }; - class Options + class OptionValue { public: + void + parse + ( + std::shared_ptr<const OptionDetails> details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } - Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , m_help_string(toLocalString(std::move(help_string))) - , m_positional_help("positional parameters") - , m_next_positional(m_positional.end()) + void + parse_default(std::shared_ptr<const OptionDetails> details) { + ensure_value(details); + m_value->parse(); + m_count++; } - inline - Options& - positional_help(std::string help_text) + size_t + count() const { - m_positional_help = std::move(help_text); - return *this; + return m_count; } - inline + template <typename T> + const T& + as() const + { +#ifdef CXXOPTS_NO_RTTI + return static_cast<const values::standard_value<T>&>(*m_value).get(); +#else + return dynamic_cast<const values::standard_value<T>&>(*m_value).get(); +#endif + } + + private: void - parse(int& argc, char**& argv); + ensure_value(std::shared_ptr<const OptionDetails> details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } - inline - OptionAdder - add_options(std::string group = ""); + std::shared_ptr<Value> m_value; + size_t m_count = 0; + }; - inline - void - add_option - ( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr<const Value> value, - std::string arg_help - ); + class KeyValue + { + public: + KeyValue(std::string key, std::string value) + : m_key(std::move(key)) + , m_value(std::move(value)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const std::string + value() const + { + return m_value; + } + + template <typename T> + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::unordered_map<std::string, std::shared_ptr<OptionDetails>>&, + std::vector<std::string>, + int&, char**&); int count(const std::string& o) const @@ -949,10 +1089,12 @@ namespace cxxopts return 0; } - return iter->second->count(); + auto riter = m_results.find(iter->second); + + return riter->second.count(); } - const OptionDetails& + const OptionValue& operator[](const std::string& option) const { auto iter = m_options.find(option); @@ -962,49 +1104,31 @@ namespace cxxopts throw option_not_present_exception(option); } - return *iter->second; - } - - //parse positional arguments into the given option - inline - void - parse_positional(std::string option); + auto riter = m_results.find(iter->second); - inline - void - parse_positional(std::vector<std::string> options); + return riter->second; + } - inline - std::string - help(const std::vector<std::string>& groups = {""}) const; + const std::vector<KeyValue>& + arguments() const + { + return m_sequential; + } - inline - const std::vector<std::string> - groups() const; + private: - inline - const HelpGroupDetails& - group_help(const std::string& group) const; + OptionValue& + get_option(std::shared_ptr<OptionDetails>); - private: + void + parse(int& argc, char**& argv); - inline void - add_one_option - ( - const std::string& option, - std::shared_ptr<OptionDetails> details - ); + add_to_option(const std::string& option, const std::string& arg); - inline bool consume_positional(std::string a); - inline - void - add_to_option(const std::string& option, const std::string& arg); - - inline void parse_option ( @@ -1013,7 +1137,9 @@ namespace cxxopts const std::string& arg = "" ); - inline + void + parse_default(std::shared_ptr<OptionDetails> details); + void checked_parse_arg ( @@ -1024,11 +1150,88 @@ namespace cxxopts const std::string& name ); - inline + const 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; + + std::vector<KeyValue> m_sequential; + }; + + class Options + { + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr<const Value> value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector<std::string> options); + + std::string + help(const std::vector<std::string>& groups = {""}) const; + + const std::vector<std::string> + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr<OptionDetails> details + ); + String help_one_group(const std::string& group) const; - inline void generate_group_help ( @@ -1036,15 +1239,15 @@ namespace cxxopts const std::vector<std::string>& groups ) const; - inline void generate_all_groups_help(String& result) const; std::string m_program; String m_help_string; std::string m_positional_help; + bool m_show_positional; - std::map<std::string, std::shared_ptr<OptionDetails>> m_options; + 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; @@ -1062,7 +1265,6 @@ namespace cxxopts { } - inline OptionAdder& operator() ( @@ -1078,24 +1280,6 @@ namespace cxxopts std::string m_group; }; - // A helper function for setting required arguments - inline - void - check_required - ( - const Options& options, - const std::vector<std::string>& required - ) - { - for (auto& r : required) - { - if (options.count(r) == 0) - { - throw option_required_exception(r); - } - } - } - namespace { constexpr int OPTION_LONGEST = 30; @@ -1132,10 +1316,10 @@ namespace cxxopts result += " --" + toLocalString(l); } - if (o.has_arg) - { - auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + if (!o.is_boolean) + { if (o.has_implicit) { result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; @@ -1213,12 +1397,28 @@ namespace cxxopts } } +inline +ParseResult::ParseResult +( + const std::unordered_map<std::string, std::shared_ptr<OptionDetails>>& options, + std::vector<std::string> positional, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +{ + parse(argc, argv); +} + +inline OptionAdder Options::add_options(std::string group) { return OptionAdder(*this, std::move(group)); } +inline OptionAdder& OptionAdder::operator() ( @@ -1276,19 +1476,31 @@ OptionAdder::operator() return *this; } +inline +void +ParseResult::parse_default(std::shared_ptr<OptionDetails> details) +{ + m_results[details].parse_default(details); +} + +inline void -Options::parse_option +ParseResult::parse_option ( std::shared_ptr<OptionDetails> value, const std::string& /*name*/, const std::string& arg ) { - value->parse(arg); + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); } +inline void -Options::checked_parse_arg +ParseResult::checked_parse_arg ( int argc, char* argv[], @@ -1322,8 +1534,9 @@ Options::checked_parse_arg } } +inline void -Options::add_to_option(const std::string& option, const std::string& arg) +ParseResult::add_to_option(const std::string& option, const std::string& arg) { auto iter = m_options.find(option); @@ -1335,17 +1548,19 @@ Options::add_to_option(const std::string& option, const std::string& arg) parse_option(iter->second, option, arg); } +inline bool -Options::consume_positional(std::string a) +ParseResult::consume_positional(std::string a) { while (m_next_positional != m_positional.end()) { auto iter = m_options.find(*m_next_positional); if (iter != m_options.end()) { + auto& result = m_results[iter->second]; if (!iter->second->value().is_container()) { - if (iter->second->count() == 0) + if (result.count() == 0) { add_to_option(*m_next_positional, a); ++m_next_positional; @@ -1369,12 +1584,14 @@ Options::consume_positional(std::string a) return false; } +inline void Options::parse_positional(std::string option) { parse_positional(std::vector<std::string>{option}); } +inline void Options::parse_positional(std::vector<std::string> options) { @@ -1384,9 +1601,18 @@ Options::parse_positional(std::vector<std::string> options) m_positional_set.insert(m_positional.begin(), m_positional.end()); } -void +inline +ParseResult Options::parse(int& argc, char**& argv) { + ParseResult result(m_options, m_positional, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ int current = 1; int nextKeep = 1; @@ -1440,27 +1666,19 @@ Options::parse(int& argc, char**& argv) auto value = iter->second; - //if no argument then just add it - if (!value->has_arg()) + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) { - parse_option(value, name); + parse_option(value, name, value->value().get_implicit_value()); } else { - //it must be the last argument - if (i + 1 == s.size()) - { - checked_parse_arg(argc, argv, current, value, name); - } - else if (value->value().has_implicit()) - { - parse_option(value, name, value->value().get_implicit_value()); - } - else - { - //error - throw option_requires_argument_exception(name); - } + //error + throw option_requires_argument_exception(name); } } } @@ -1482,26 +1700,12 @@ Options::parse(int& argc, char**& argv) { //parse the option given - //but if it doesn't take an argument, this is an error - if (!opt->has_arg()) - { - throw option_not_has_argument_exception(name, result[3]); - } - parse_option(opt, name, result[3]); } else { - if (opt->has_arg()) - { - //parse the next argument - checked_parse_arg(argc, argv, current, opt, name); - } - else - { - //parse with empty argument - parse_option(opt, name); - } + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); } } @@ -1515,8 +1719,10 @@ Options::parse(int& argc, char**& argv) auto& detail = opt.second; auto& value = detail->value(); - if(!detail->count() && value.has_default()){ - detail->parse_default(); + auto& store = m_results[detail]; + + if(!store.count() && value.has_default()){ + parse_default(detail); } } @@ -1542,6 +1748,7 @@ Options::parse(int& argc, char**& argv) } +inline void Options::add_option ( @@ -1554,7 +1761,7 @@ Options::add_option ) { auto stringDesc = toLocalString(std::move(desc)); - auto option = std::make_shared<OptionDetails>(stringDesc, value); + auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value); if (s.size() > 0) { @@ -1570,13 +1777,14 @@ Options::add_option auto& options = m_help[group]; options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, - value->has_arg(), value->has_default(), value->get_default_value(), value->has_implicit(), value->get_implicit_value(), std::move(arg_help), - value->is_container()}); + value->is_container(), + value->is_boolean()}); } +inline void Options::add_one_option ( @@ -1592,6 +1800,7 @@ Options::add_one_option } } +inline String Options::help_one_group(const std::string& g) const { @@ -1616,7 +1825,9 @@ Options::help_one_group(const std::string& g) const for (const auto& o : group->second.options) { - if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end()) + if (o.is_container && + m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) { continue; } @@ -1634,7 +1845,9 @@ Options::help_one_group(const std::string& g) const auto fiter = format.begin(); for (const auto& o : group->second.options) { - if (o.is_container && m_positional_set.find(o.l) != m_positional_set.end()) + if (o.is_container && + m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) { continue; } @@ -1662,6 +1875,7 @@ Options::help_one_group(const std::string& g) const return result; } +inline void Options::generate_group_help ( @@ -1684,6 +1898,7 @@ Options::generate_group_help } } +inline void Options::generate_all_groups_help(String& result) const { @@ -1698,6 +1913,7 @@ Options::generate_all_groups_help(String& result) const generate_group_help(result, all_groups); } +inline std::string Options::help(const std::vector<std::string>& help_groups) const { @@ -1722,6 +1938,7 @@ Options::help(const std::vector<std::string>& help_groups) const return toUTF8String(result); } +inline const std::vector<std::string> Options::groups() const { @@ -1740,6 +1957,7 @@ Options::groups() const return g; } +inline const HelpGroupDetails& Options::group_help(const std::string& group) const { @@ -1748,8 +1966,4 @@ Options::group_help(const std::string& group) const } -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif - -#endif //CXX_OPTS_HPP +#endif //CXXOPTS_HPP_INCLUDED diff --git a/src/example.cpp b/src/example.cpp index 44271b4..b541774 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -31,7 +31,9 @@ int main(int argc, char* argv[]) try { cxxopts::Options options(argv[0], " - example command line options"); - options.positional_help("[optional args]"); + options + .positional_help("[optional args]") + .show_positional_help(); bool apple = false; @@ -63,9 +65,9 @@ int main(int argc, char* argv[]) options.parse_positional({"input", "output", "positional"}); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - if (options.count("help")) + if (result.count("help")) { std::cout << options.help({"", "Group"}) << std::endl; exit(0); @@ -73,18 +75,18 @@ int main(int argc, char* argv[]) if (apple) { - std::cout << "Saw option ‘a’ " << options.count("a") << " times " << + std::cout << "Saw option ‘a’ " << result.count("a") << " times " << std::endl; } - if (options.count("b")) + if (result.count("b")) { std::cout << "Saw option ‘b’" << std::endl; } - if (options.count("f")) + if (result.count("f")) { - auto& ff = options["f"].as<std::vector<std::string>>(); + auto& ff = result["f"].as<std::vector<std::string>>(); std::cout << "Files" << std::endl; for (const auto& f : ff) { @@ -92,36 +94,36 @@ int main(int argc, char* argv[]) } } - if (options.count("input")) + if (result.count("input")) { - std::cout << "Input = " << options["input"].as<std::string>() + std::cout << "Input = " << result["input"].as<std::string>() << std::endl; } - if (options.count("output")) + if (result.count("output")) { - std::cout << "Output = " << options["output"].as<std::string>() + std::cout << "Output = " << result["output"].as<std::string>() << std::endl; } - if (options.count("positional")) + if (result.count("positional")) { std::cout << "Positional = {"; - auto& v = options["positional"].as<std::vector<std::string>>(); + auto& v = result["positional"].as<std::vector<std::string>>(); for (const auto& s : v) { std::cout << s << ", "; } std::cout << "}" << std::endl; } - if (options.count("int")) + if (result.count("int")) { - std::cout << "int = " << options["int"].as<int>() << std::endl; + std::cout << "int = " << result["int"].as<int>() << std::endl; } - if (options.count("float")) + if (result.count("float")) { - std::cout << "float = " << options["float"].as<float>() << std::endl; + std::cout << "float = " << result["float"].as<float>() << std::endl; } std::cout << "Arguments remain = " << argc << std::endl; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0920e90..1969545 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,4 +29,7 @@ if (CXXOPTS_BUILD_TESTS) "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" ) + + add_executable(link_test link_a.cpp link_b.cpp) + target_link_libraries(link_test cxxopts) endif() diff --git a/test/link_a.cpp b/test/link_a.cpp new file mode 100644 index 0000000..3611692 --- /dev/null +++ b/test/link_a.cpp @@ -0,0 +1,6 @@ +#include "cxxopts.hpp" + +int main(int, char**) +{ + return 0; +} diff --git a/test/link_b.cpp b/test/link_b.cpp new file mode 100644 index 0000000..e48e22a --- /dev/null +++ b/test/link_b.cpp @@ -0,0 +1 @@ +#include <cxxopts.hpp> diff --git a/test/options.cpp b/test/options.cpp index d428f9a..afd7292 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -71,17 +71,27 @@ TEST_CASE("Basic options", "[options]") char** actual_argv = argv.argv(); auto argc = argv.argc(); - options.parse(argc, actual_argv); - - CHECK(options.count("long") == 1); - CHECK(options.count("s") == 1); - CHECK(options.count("value") == 1); - CHECK(options.count("a") == 1); - CHECK(options["value"].as<std::string>() == "value"); - CHECK(options["a"].as<std::string>() == "b"); - CHECK(options.count("6") == 1); - CHECK(options.count("p") == 2); - CHECK(options.count("space") == 2); + auto result = options.parse(argc, actual_argv); + + CHECK(result.count("long") == 1); + CHECK(result.count("s") == 1); + CHECK(result.count("value") == 1); + CHECK(result.count("a") == 1); + CHECK(result["value"].as<std::string>() == "value"); + CHECK(result["a"].as<std::string>() == "b"); + CHECK(result.count("6") == 1); + CHECK(result.count("p") == 2); + CHECK(result.count("space") == 2); + + auto& arguments = result.arguments(); + REQUIRE(arguments.size() == 7); + CHECK(arguments[0].key() == "long"); + CHECK(arguments[0].value() == "true"); + CHECK(arguments[0].as<bool>() == true); + + CHECK(arguments[1].key() == "short"); + CHECK(arguments[2].key() == "value"); + CHECK(arguments[3].key() == "av"); } TEST_CASE("Short options", "[options]") @@ -96,36 +106,15 @@ TEST_CASE("Short options", "[options]") auto actual_argv = argv.argv(); auto argc = argv.argc(); - options.parse(argc, actual_argv); + auto result = options.parse(argc, actual_argv); - CHECK(options.count("a") == 1); - CHECK(options["a"].as<std::string>() == "value"); + CHECK(result.count("a") == 1); + CHECK(result["a"].as<std::string>() == "value"); REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), cxxopts::invalid_option_format_error); } -TEST_CASE("Required arguments", "[options]") -{ - cxxopts::Options options("required", " - test required options"); - options.add_options() - ("one", "one option") - ("two", "second option") - ; - - Argv argv({ - "required", - "--one" - }); - - auto aargv = argv.argv(); - auto argc = argv.argc(); - - options.parse(argc, aargv); - REQUIRE_THROWS_AS(cxxopts::check_required(options, {"two"}), - cxxopts::option_required_exception); -} - TEST_CASE("No positional", "[positional]") { cxxopts::Options options("test_no_positional", @@ -135,7 +124,7 @@ TEST_CASE("No positional", "[positional]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); REQUIRE(argc == 4); CHECK(strcmp(argv[1], "a") == 0); @@ -158,7 +147,7 @@ TEST_CASE("All positional", "[positional]") options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); REQUIRE(argc == 1); REQUIRE(positional.size() == 3); @@ -186,14 +175,14 @@ TEST_CASE("Some positional explicit", "[positional]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); CHECK(argc == 1); - CHECK(options.count("output")); - CHECK(options["input"].as<std::string>() == "b"); - CHECK(options["output"].as<std::string>() == "a"); + CHECK(result.count("output")); + CHECK(result["input"].as<std::string>() == "b"); + CHECK(result["output"].as<std::string>() == "a"); - auto& positional = options["positional"].as<std::vector<std::string>>(); + auto& positional = result["positional"].as<std::vector<std::string>>(); REQUIRE(positional.size() == 2); CHECK(positional[0] == "c"); @@ -234,10 +223,58 @@ TEST_CASE("Empty with implicit value", "[implicit]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("implicit") == 1); - REQUIRE(options["implicit"].as<std::string>() == ""); + REQUIRE(result.count("implicit") == 1); + REQUIRE(result["implicit"].as<std::string>() == ""); +} + +TEST_CASE("Default values", "[default]") +{ + cxxopts::Options options("defaults", "has defaults"); + options.add_options() + ("default", "Has implicit", cxxopts::value<int>() + ->default_value("42")); + + SECTION("Sets defaults") { + Argv av({"implicit"}); + + char** argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + CHECK(result.count("default") == 1); + CHECK(result["default"].as<int>() == 42); + } + + SECTION("When values provided") { + Argv av({"implicit", "--default", "5"}); + + char** argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + CHECK(result.count("default") == 1); + CHECK(result["default"].as<int>() == 5); + } +} + +TEST_CASE("Parse into a reference", "[reference]") +{ + int value = 0; + + cxxopts::Options options("into_reference", "parses into a reference"); + options.add_options() + ("ref", "A reference", cxxopts::value(value)); + + Argv av({"into_reference", "--ref", "42"}); + + auto argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + CHECK(result.count("ref") == 1); + CHECK(value == 42); } TEST_CASE("Integers", "[options]") @@ -252,11 +289,12 @@ TEST_CASE("Integers", "[options]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("positional") == 7); + REQUIRE(result.count("positional") == 7); - auto& positional = options["positional"].as<std::vector<int>>(); + auto& positional = result["positional"].as<std::vector<int>>(); + REQUIRE(positional.size() == 7); CHECK(positional[0] == 5); CHECK(positional[1] == 6); CHECK(positional[2] == -6); @@ -295,11 +333,11 @@ TEST_CASE("Integer bounds", "[integer]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("positional") == 5); + REQUIRE(result.count("positional") == 5); - auto& positional = options["positional"].as<std::vector<int8_t>>(); + auto& positional = result["positional"].as<std::vector<int8_t>>(); CHECK(positional[0] == 127); CHECK(positional[1] == -128); CHECK(positional[2] == 0x7f); @@ -351,14 +389,14 @@ TEST_CASE("Floats", "[options]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("double") == 1); - REQUIRE(options.count("positional") == 4); + REQUIRE(result.count("double") == 1); + REQUIRE(result.count("positional") == 4); - CHECK(options["double"].as<double>() == 0.5); + CHECK(result["double"].as<double>() == 0.5); - auto& positional = options["positional"].as<std::vector<float>>(); + auto& positional = result["positional"].as<std::vector<float>>(); CHECK(positional[0] == 4); CHECK(positional[1] == -4); CHECK(positional[2] == 1.5e6); @@ -378,3 +416,27 @@ TEST_CASE("Invalid integers", "[integer]") { options.parse_positional("positional"); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::argument_incorrect_type); } + +TEST_CASE("Booleans", "[boolean]") { + cxxopts::Options options("parses_floats", "parses floats correctly"); + options.add_options() + ("bool", "A Boolean", cxxopts::value<bool>()) + ("debug", "Debugging", cxxopts::value<bool>()) + ("timing", "Timing", cxxopts::value<bool>()) + ; + + Argv av({"booleans", "--bool=false", "--debug", "true", "--timing"}); + + char** argv = av.argv(); + auto argc = av.argc(); + + auto result = options.parse(argc, argv); + + REQUIRE(result.count("bool") == 1); + REQUIRE(result.count("debug") == 1); + REQUIRE(result.count("timing") == 1); + + CHECK(result["bool"].as<bool>() == false); + CHECK(result["debug"].as<bool>() == true); + CHECK(result["timing"].as<bool>() == true); +} |