From 5e3a9626d9e2507bc4f076b8c9ce1b3865031d2e Mon Sep 17 00:00:00 2001 From: Pavel Artemkin Date: Sun, 22 May 2022 16:11:13 +0500 Subject: [PATCH] back to single include (#5) * back to single include * better string concat * organize code --- BUILD.bazel | 1 - CMakeLists.txt | 4 +- README.md | 4 + include/.clang-format | 207 +++++ include/CMakeLists.txt | 2 +- include/cxxopts.hpp | 1702 ++++++++++++++++++++++++++++++++-------- src/cxxopts.cpp | 1612 ------------------------------------- 7 files changed, 1600 insertions(+), 1932 deletions(-) create mode 100644 include/.clang-format delete mode 100644 src/cxxopts.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 083561d..a66fae7 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,7 +3,6 @@ load("@rules_cc//cc:defs.bzl", "cc_library") cc_library( name = "cxxopts", hdrs = ["include/cxxopts.hpp"], - srcs = ["src/cxxopts.cpp"], strip_include_prefix = "include", visibility = ["//visibility:public"], ) diff --git a/CMakeLists.txt b/CMakeLists.txt index f303eeb..c5e7e66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,12 +55,10 @@ if (CXXOPTS_ENABLE_WARNINGS) cxxopts_enable_warnings() endif() -add_library(cxxopts STATIC) +add_library(cxxopts INTERFACE) add_library(cxxopts::cxxopts ALIAS cxxopts) add_subdirectory(include) -target_sources(cxxopts PRIVATE "src/cxxopts.cpp") - # Link against the ICU library when requested if(CXXOPTS_USE_UNICODE_HELP) cxxopts_use_unicode() diff --git a/README.md b/README.md index 7fbf4fd..652b30f 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,10 @@ options.add_options("group name", { }); ``` +# Linking + +This is a header only library. + # Release versions Note that `master` is generally a work in progress, and you probably want to use a diff --git a/include/.clang-format b/include/.clang-format new file mode 100644 index 0000000..5bc01ef --- /dev/null +++ b/include/.clang-format @@ -0,0 +1,207 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: None +AlignEscapedNewlines: DontAlign +AlignOperands: AlignAfterOperator +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BasedOnStyle: '' +BinPackArguments: true +BinPackParameters: false +BitFieldColonSpacing: Both +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: MultiLine + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyNamespace: true + SplitEmptyRecord: true +BreakAfterJavaFieldAnnotations: false +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: Always +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +PackConstructorInitializers: Never +QualifierAlignment: Leave +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: AfterHash +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PPIndentWidth: 1 +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: 1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 0a24484..1123c5a 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -17,7 +17,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -target_include_directories(cxxopts PUBLIC +target_include_directories(cxxopts INTERFACE $ $ ) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index e9a194c..93070c1 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -26,8 +26,13 @@ THE SOFTWARE. #ifndef CXXOPTS_HPP_INCLUDED #define CXXOPTS_HPP_INCLUDED +#include +#include +#include #include +#include #include +#include #include #include #include @@ -39,10 +44,10 @@ THE SOFTWARE. #ifdef __has_include # if __has_include() -# include -# ifdef __cpp_lib_optional -# define CXXOPTS_HAS_OPTIONAL -# endif +# include +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif # endif #endif @@ -77,94 +82,279 @@ THE SOFTWARE. #define CXXOPTS__VERSION_MAJOR 5 #define CXXOPTS__VERSION_MINOR 2 -#define CXXOPTS__VERSION_PATCH 0 +#define CXXOPTS__VERSION_PATCH 1 namespace cxxopts { static constexpr struct { uint8_t major, minor, patch; -} version = { - CXXOPTS__VERSION_MAJOR, - CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH +} version = {CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH}; + +#ifdef _WIN32 +static const std::string LQUOTE("\'"); +static const std::string RQUOTE("\'"); +#else +static const std::string LQUOTE("‘"); +static const std::string RQUOTE("’"); +#endif + +static constexpr size_t OPTION_LONGEST = 30; +static constexpr size_t OPTION_DESC_GAP = 2; + +#ifdef CXXOPTS_USE_UNICODE +using cxx_string = icu::UnicodeString; +#else +using cxx_string = std::string; +#endif + +} // namespace cxxopts + +// 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 +// it is necessary to make sure that can be found by the +// compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE + +namespace cxxopts { + +static inline cxx_string to_local_string(std::string s) { + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +class unicode_string_iterator + : public std::iterator { +public: + unicode_string_iterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) { + } + + value_type operator*() const { + return s->char32At(i); + } + + bool operator==(const unicode_string_iterator& rhs) const { + return s == rhs.s && i == rhs.i; + } + + bool operator!=(const unicode_string_iterator& rhs) const { + return !(*this == rhs); + } + + unicode_string_iterator& operator++() { + ++i; + return *this; + } + + unicode_string_iterator operator+(int32_t v) { + return unicode_string_iterator(s, i + v); + } + +private: + const icu::UnicodeString* s; + int32_t i; }; +static inline cxx_string& string_append(cxx_string& s, cxx_string a) { + return s.append(std::move(a)); +} + +static inline cxx_string& string_append(cxx_string& s, size_t n, UChar32 c) { + for (size_t i = 0; i != n; ++i) { + s.append(c); + } + + return s; +} + +template +static inline cxx_string& string_append(cxx_string& s, + Iterator begin, + Iterator end) { + while (begin != end) { + s.append(*begin); + ++begin; + } + + return s; +} + +static inline size_t string_length(const cxx_string& s) { + return s.length(); +} + +static inline std::string to_utf8_string(const cxx_string& s) { + std::string result; + s.toUTF8String(result); + + return result; +} + +static inline bool empty(const cxx_string& s) { + return s.isEmpty(); +} + } // namespace cxxopts +namespace std { + +cxxopts::unicode_string_iterator begin(const icu::UnicodeString& s) { + return cxxopts::unicode_string_iterator(&s, 0); +} + +cxxopts::unicode_string_iterator end(const icu::UnicodeString& s) { + return cxxopts::unicode_string_iterator(&s, s.length()); +} + +} // namespace std + +#else // ifdef CXXOPTS_USE_UNICODE + +namespace cxxopts { + +template +static inline T to_local_string(T&& t) { + return std::forward(t); +} + +static inline size_t string_length(const cxx_string& s) { + return s.length(); +} + +static inline cxx_string& string_append(cxx_string& s, const cxx_string& a) { + return s.append(a); +} + +static inline cxx_string& string_append(cxx_string& s, size_t n, char c) { + return s.append(n, c); +} + +template +static inline cxx_string& string_append(cxx_string& s, + Iterator begin, + Iterator end) { + return s.append(begin, end); +} + +template +static inline std::string to_utf8_string(T&& t) { + return std::forward(t); +} + +static inline bool empty(const std::string& s) { + return s.empty(); +} + +} // namespace cxxopts + +#endif // ifdef CXXOPTS_USE_UNICODE + /** -* \defgroup Exceptions -* @{ -*/ + * \defgroup Exceptions + * @{ + */ namespace cxxopts { class option_error : public std::runtime_error { public: - explicit option_error(const std::string& what_arg); + explicit option_error(const std::string& what_arg) + : std::runtime_error(what_arg) { + } }; class parse_error : public option_error { public: - explicit parse_error(const std::string& what_arg); + explicit parse_error(const std::string& what_arg) + : option_error(what_arg) { + } }; class spec_error : public option_error { public: - explicit spec_error(const std::string& what_arg); + explicit spec_error(const std::string& what_arg) + : option_error(what_arg) { + } }; class option_exists_error : public spec_error { public: - explicit option_exists_error(const std::string& option); + explicit option_exists_error(const std::string& option) + : spec_error("Option " + LQUOTE + option + RQUOTE + " already exists") { + } }; class invalid_option_format_error : public spec_error { public: - explicit invalid_option_format_error(const std::string& format); + explicit invalid_option_format_error(const std::string& format) + : spec_error("Invalid option format " + LQUOTE + format + RQUOTE) { + } }; class option_syntax_error : public parse_error { public: - explicit option_syntax_error(const std::string& text); + explicit option_syntax_error(const std::string& text) + : parse_error("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") { + } }; class option_not_exists_error : public parse_error { public: - explicit option_not_exists_error(const std::string& option); + explicit option_not_exists_error(const std::string& option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " does not exist") { + } }; class missing_argument_error : public parse_error { public: - explicit missing_argument_error(const std::string& option); + explicit missing_argument_error(const std::string& option) + : parse_error("Option " + LQUOTE + option + RQUOTE + + " is missing an argument") { + } }; class option_requires_argument_error : public parse_error { public: - explicit option_requires_argument_error(const std::string& option); + explicit option_requires_argument_error(const std::string& option) + : parse_error("Option " + LQUOTE + option + RQUOTE + + " requires an argument") { + } }; class option_not_present_error : public parse_error { public: - explicit option_not_present_error(const std::string& option); + explicit option_not_present_error(const std::string& option) + : parse_error("Option " + LQUOTE + option + RQUOTE + " not present") { + } }; class argument_incorrect_type : public parse_error { public: - explicit argument_incorrect_type( - const std::string& arg, - const std::string& type = {}); + explicit argument_incorrect_type(const std::string& arg, + const std::string& type = {}) + : parse_error( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + + (type.empty() ? std::string() : (": " + type + " expected"))) { + } }; class option_has_no_value_error : public option_error { public: - explicit option_has_no_value_error(const std::string& name); + explicit option_has_no_value_error(const std::string& name) + : option_error(name.empty() + ? "Option has no value" + : "Option " + LQUOTE + name + RQUOTE + " has no value") { + } }; namespace detail { -template -CXXOPTS_NORETURN -void throw_or_mimic(Args&& ... args) { +template +CXXOPTS_NORETURN void throw_or_mimic(Args&&... args) { static_assert(std::is_base_of::value, "throw_or_mimic only works on std::exception and " "deriving classes"); @@ -186,74 +376,233 @@ void throw_or_mimic(Args&& ... args) { /**@}*/ - /** -* \defgroup Value parsing -* @{ -*/ + * \defgroup Value parsing + * @{ + */ namespace cxxopts { namespace detail { -void -parse_value(const std::string& text, uint8_t& value); +template +struct signed_check; + +template +struct signed_check { + template + bool operator()(const U u, const bool negative) const noexcept { + return ((static_cast(std::numeric_limits::min()) >= u) && negative) || + (static_cast(std::numeric_limits::max()) >= u); + } +}; + +template +struct signed_check { + template + bool operator()(const U, const bool) const noexcept { + return true; + } +}; -void -parse_value(const std::string& text, int8_t& value); +template +inline bool check_signed_range(const U value, const bool negative) noexcept { + return signed_check::is_signed>()(value, negative); +} -void -parse_value(const std::string& text, uint16_t& value); +template +inline bool checked_negate(R& r, T&& t, std::true_type) noexcept { + // 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`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t - 1) - 1); + return true; +} -void -parse_value(const std::string& text, int16_t& value); +template +inline bool checked_negate(R&, T&&, std::false_type) noexcept { + return false; +} -void -parse_value(const std::string& text, uint32_t& value); +inline bool parse_uint64(const std::string& text, + uint64_t& value, + bool& negative) noexcept { + const char* p = text.c_str(); + // String should not be empty. + if (*p == 0) { + return false; + } + // Parse sign. + if (*p == '+') { + ++p; + } else if (*p == '-') { + negative = true; + ++p; + } + // Not an integer value. + if (*p == 0) { + return false; + } else { + value = 0; + } + // Hex number. + if (*p == '0' && *(p + 1) == 'x') { + p += 2; + if (*p == 0) { + return false; + } + for (; *p; ++p) { + uint64_t digit = 0; + + if (*p >= '0' && *p <= '9') { + digit = static_cast(*p - '0'); + } else if (*p >= 'a' && *p <= 'f') { + digit = static_cast(*p - 'a' + 10); + } else if (*p >= 'A' && *p <= 'F') { + digit = static_cast(*p - 'A' + 10); + } else { + return false; + } -void -parse_value(const std::string& text, int32_t& value); + const uint64_t next = value * 16u + digit; + if (value > next) { + return false; + } else { + value = next; + } + } + // Decimal number. + } else { + for (; *p; ++p) { + uint64_t digit = 0; + + if (*p >= '0' && *p <= '9') { + digit = static_cast(*p - '0'); + } else { + return false; + } + if ((value > std::numeric_limits::max() / 10u) || + (value == std::numeric_limits::max() / 10u && digit > 5)) + { + return false; + } else { + value = value * 10u + digit; + } + } + } + return true; +} + +template ::value>::type* = nullptr> +inline void parse_value(const std::string& text, T& value) { + using US = typename std::make_unsigned::type; -void -parse_value(const std::string& text, uint64_t& value); + uint64_t u64_result{0}; + US result{0}; + bool negative{false}; + + // Parse text to the uint64_t value. + if (!parse_uint64(text, u64_result, negative)) { + throw_or_mimic(text, "integer"); + } + // Check unsigned overflow. + if (u64_result > std::numeric_limits::max()) { + throw_or_mimic(text, "integer"); + } else { + result = static_cast(u64_result); + } + // Check signed overflow. + if (!check_signed_range(result, negative)) { + throw_or_mimic(text, "integer"); + } + // Negate value. + if (negative) { + if (!checked_negate( + value, result, + std::integral_constant::is_signed>())) + { + throw_or_mimic(text, "integer"); + } + } else { + value = static_cast(result); + } +} -void -parse_value(const std::string& text, int64_t& value); +inline void parse_value(const std::string& text, float& value) { + value = std::stof(text); +} -void -parse_value(const std::string& text, float& value); +inline void parse_value(const std::string& text, double& value) { + value = std::stod(text); +} -void -parse_value(const std::string& text, double& value); +inline void parse_value(const std::string& text, long double& value) { + value = std::stold(text); +} -void -parse_value(const std::string& text, long double& value); +inline void parse_value(const std::string& text, bool& value) { + switch (text.size()) { + case 1: { + const char ch = text[0]; + if (ch == '1' || ch == 't' || ch == 'T') { + value = true; + return; + } + if (ch == '0' || ch == 'f' || ch == 'F') { + value = false; + return; + } + break; + } + case 4: + if ((text[0] == 't' || text[0] == 'T') && + (text[1] == 'r' && text[2] == 'u' && text[3] == 'e')) + { + value = true; + return; + } + break; + case 5: + if ((text[0] == 'f' || text[0] == 'F') && + (text[1] == 'a' && text[2] == 'l' && text[3] == 's' && + text[4] == 'e')) + { + value = false; + return; + } + break; + } + throw_or_mimic(text, "bool"); +} -void -parse_value(const std::string& text, bool& value); +inline void parse_value(const std::string& text, char& c) { + if (text.length() != 1) { + throw_or_mimic(text, "char"); + } -void -parse_value(const std::string& text, char& c); + c = text[0]; +} -void -parse_value(const std::string& text, std::string& value); +inline void parse_value(const std::string& text, std::string& value) { + value = text; +} // The fallback parser. It uses the stringstream parser to parse all types // that have not been overloaded explicitly. It has to be placed in the // source code before all other more specialized templates. -template -void -parse_value(const std::string& text, T& value) { +template ::value>::type* = nullptr> +void parse_value(const std::string& text, T& value) { std::istringstream in{text}; in >> value; if (!in) { - detail::throw_or_mimic(text); + throw_or_mimic(text); } } #ifdef CXXOPTS_HAS_OPTIONAL template -void -parse_value(const std::string& text, std::optional& value) { +void parse_value(const std::string& text, std::optional& value) { T result; parse_value(text, result); value = std::move(result); @@ -289,19 +638,17 @@ struct value_parser> { /// Value of type std::vector can act as container. static constexpr bool is_container = true; - void - parse( - const parse_context& ctx, - const std::string& text, - std::vector& value) - { + void parse(const parse_context& ctx, + const std::string& text, + std::vector& value) { using parser_type = value_parser; - static_assert(!parser_type::is_container || - !value_parser::is_container, - "dimensions of a container type should not exceed 2"); + static_assert( + !parser_type::is_container || + !value_parser::is_container, + "dimensions of a container type should not exceed 2"); - auto parse_item = [&ctx] (const std::string& txt) { + auto parse_item = [&ctx](const std::string& txt) { T v; parser_type().parse(ctx, txt, v); return v; @@ -323,18 +670,19 @@ struct value_parser> { /**@}*/ - /** -* \defgroup Value setup -* @{ -*/ + * \defgroup Value setup + * @{ + */ namespace cxxopts { namespace detail { #if defined(__GNUC__) -// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: -// warning: base class 'class std::enable_shared_from_this' has accessible non-virtual destructor +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor # pragma GCC diagnostic ignored "-Wnon-virtual-dtor" # pragma GCC diagnostic push // This will be ignored under other compilers like LLVM clang. @@ -346,43 +694,36 @@ class value_base : public std::enable_shared_from_this { virtual ~value_base() = default; /** Returns whether the default value was set. */ - bool - has_default() const noexcept { + bool has_default() const noexcept { return default_; } /** Returns whether the env variable was set. */ - bool - has_env() const noexcept { + bool has_env() const noexcept { return env_; } /** Returns whether the implicit value was set. */ - bool - has_implicit() const noexcept { + bool has_implicit() const noexcept { return implicit_; } /** Returns default value. */ - std::string - get_default_value() const { + std::string get_default_value() const { return default_value_; } /** Returns env variable. */ - std::string - get_env_var() const { + std::string get_env_var() const { return env_var_; } /** Returns implicit value. */ - std::string - get_implicit_value() const { + std::string get_implicit_value() const { return implicit_value_; } - bool - get_no_value() const noexcept { + bool get_no_value() const noexcept { return no_value_; } @@ -395,8 +736,7 @@ class value_base : public std::enable_shared_from_this { template typename std::enable_if< !std::is_same::type>::value, - std::shared_ptr - >::type + std::shared_ptr>::type default_value(T&& value) { default_ = true; default_value_.assign(std::forward(value)); @@ -404,8 +744,7 @@ class value_base : public std::enable_shared_from_this { } /** Sets delimiter for list values. */ - std::shared_ptr - delimiter(const char del) { + std::shared_ptr delimiter(const char del) { parse_ctx_.delimiter = del; return shared_from_this(); } @@ -414,8 +753,7 @@ class value_base : public std::enable_shared_from_this { template typename std::enable_if< !std::is_same::type>::value, - std::shared_ptr - >::type + std::shared_ptr>::type env(T&& var) { env_ = true; env_var_.assign(std::forward(var)); @@ -431,8 +769,7 @@ class value_base : public std::enable_shared_from_this { template typename std::enable_if< !std::is_same::type>::value, - std::shared_ptr - >::type + std::shared_ptr>::type implicit_value(T&& value) { implicit_ = true; implicit_value_.assign(std::forward(value)); @@ -440,8 +777,7 @@ class value_base : public std::enable_shared_from_this { } /** Clears implicit value. */ - std::shared_ptr - no_implicit_value() { + std::shared_ptr no_implicit_value() { no_value_ = false; implicit_ = false; implicit_value_.clear(); @@ -449,48 +785,40 @@ class value_base : public std::enable_shared_from_this { } /** Sets no-value field. */ - std::shared_ptr - no_value(const bool on = true) { + std::shared_ptr no_value(const bool on = true) { no_value_ = on; return shared_from_this(); } /** Returns whether the type of the value is boolean. */ - bool - is_boolean() const noexcept { + bool is_boolean() const noexcept { return do_is_boolean(); } /** Returns whether the type of the value is container. */ - bool - is_container() const noexcept { + bool is_container() const noexcept { return do_is_container(); } /** Parses the given text into the value. */ - void - parse(const std::string& text) const { + void parse(const std::string& text) const { return do_parse(parse_ctx_, text); } /** Parses the default value. */ - void - parse() const { + void parse() const { return do_parse(parse_ctx_, default_value_); } protected: - virtual bool - do_is_boolean() const noexcept = 0; + virtual bool do_is_boolean() const noexcept = 0; - virtual bool - do_is_container() const noexcept = 0; + virtual bool do_is_container() const noexcept = 0; - virtual void - do_parse(const parse_context& ctx, const std::string& text) const = 0; + virtual void do_parse(const parse_context& ctx, + const std::string& text) const = 0; - void - set_default_and_implicit() { + void set_default_and_implicit() { if (is_boolean()) { default_ = true; default_value_ = "false"; @@ -527,38 +855,34 @@ class value_base : public std::enable_shared_from_this { template class basic_value : public value_base { using parser_type = value_parser; + public: basic_value() : result_(new T{}) - , store_(result_.get()) - { + , store_(result_.get()) { set_default_and_implicit(); } explicit basic_value(T* const t) - : store_(t) - { + : store_(t) { set_default_and_implicit(); } - const T& - get() const { + const T& get() const { return *store_; } protected: - bool - do_is_boolean() const noexcept final override { + bool do_is_boolean() const noexcept final override { return std::is_same::value; } - bool - do_is_container() const noexcept final override { + bool do_is_container() const noexcept final override { return parser_type::is_container; } - void - do_parse(const parse_context& ctx, const std::string& text) const override { + void do_parse(const parse_context& ctx, + const std::string& text) const override { parser_type().parse(ctx, text, *store_); } @@ -577,8 +901,7 @@ class basic_value : public value_base { * Creates value holder for the specific type. */ template -std::shared_ptr> -inline value() { +std::shared_ptr> inline value() { return std::make_shared>(); } @@ -586,8 +909,7 @@ inline value() { * Creates value holder for the specific type. */ template -std::shared_ptr> -inline value(T& t) { +std::shared_ptr> inline value(T& t) { return std::make_shared>(&t); } @@ -595,48 +917,49 @@ inline value(T& t) { /**@}*/ - namespace cxxopts { -class options; -class parse_result; - -#ifdef CXXOPTS_USE_UNICODE - using cxx_string = icu::UnicodeString; -#else - using cxx_string = std::string; -#endif - class option_details { public: - option_details( - std::string short_name, - std::string long_name, - cxx_string desc, - std::shared_ptr val); + option_details(std::string short_name, + std::string long_name, + cxx_string desc, + std::shared_ptr val) + : short_(std::move(short_name)) + , long_(std::move(long_name)) + , desc_(std::move(desc)) + , hash_(std::hash{}(long_ + short_)) + , value_(std::move(val)) { + } CXXOPTS_NODISCARD - const cxx_string& - description() const; + const cxx_string& description() const { + return desc_; + } CXXOPTS_NODISCARD - std::shared_ptr - value() const; + std::shared_ptr value() const { + return value_; + } CXXOPTS_NODISCARD - const std::string& - canonical_name() const; + const std::string& canonical_name() const { + return long_.empty() ? short_ : long_; + } CXXOPTS_NODISCARD - const std::string& - short_name() const; + const std::string& short_name() const { + return short_; + } CXXOPTS_NODISCARD - const std::string& - long_name() const; + const std::string& long_name() const { + return long_; + } - size_t - hash() const noexcept; + size_t hash() const noexcept { + return hash_; + } private: /// Short name of the option. @@ -659,28 +982,30 @@ class option_value { * the command line arguments. */ CXXOPTS_NODISCARD - size_t - count() const noexcept; + size_t count() const noexcept { + return count_; + } // TODO: maybe default options should count towards the number of arguments CXXOPTS_NODISCARD - bool - has_default() const noexcept; + bool has_default() const noexcept { + return default_; + } /** * Returns true if there was a value for the option in the * command line arguments. */ CXXOPTS_NODISCARD - bool - has_value() const noexcept; + bool has_value() const noexcept { + return value_ != nullptr; + } /** * Casts option value to the specific type. */ template - const T& - as() const { + const T& as() const { if (!has_value()) { detail::throw_or_mimic(long_name_); } @@ -695,23 +1020,34 @@ class option_value { /** * Parses option value from the given text. */ - void - parse( - const std::shared_ptr& details, - const std::string& text); + void parse(const std::shared_ptr& details, + const std::string& text) { + ensure_value(details); + ++count_; + value_->parse(text); + long_name_ = details->long_name(); + } /** * Parses option value from the default value. */ - void - parse_default(const std::shared_ptr& details); + void parse_default(const std::shared_ptr& details) { + ensure_value(details); + default_ = true; + long_name_ = details->long_name(); + value_->parse(); + } - void - parse_no_value(const std::shared_ptr& details); + void parse_no_value(const std::shared_ptr& details) { + long_name_ = details->long_name(); + } private: - void - ensure_value(const std::shared_ptr& details); + void ensure_value(const std::shared_ptr& details) { + if (value_ == nullptr) { + value_ = details->value(); + } + } std::string long_name_{}; // Holding this pointer is safe, since option_value's only exist @@ -733,17 +1069,24 @@ class parse_result { class key_value { public: - key_value(std::string key, std::string value); + key_value(std::string key, std::string value) + : key_(std::move(key)) + , value_(std::move(value)) { + } CXXOPTS_NODISCARD - const std::string& key() const; + const std::string& key() const { + return key_; + } CXXOPTS_NODISCARD - const std::string& value() const; + const std::string& value() const { + return value_; + } /** - * Parses the value to a variable of the specific type. - */ + * Parses the value to a variable of the specific type. + */ template T as() const { T result; @@ -760,12 +1103,17 @@ class parse_result { parse_result() = default; parse_result(const parse_result&) = default; parse_result(parse_result&&) = default; - parse_result( - name_hash_map&& keys, - parsed_hash_map&& values, - std::vector&& sequential, - std::vector&& unmatched_args, - size_t consumed); + parse_result(name_hash_map&& keys, + parsed_hash_map&& values, + std::vector&& sequential, + std::vector&& unmatched_args, + size_t consumed) + : keys_(std::move(keys)) + , values_(std::move(values)) + , sequential_(std::move(sequential)) + , unmatched_(std::move(unmatched_args)) + , consumed_arguments_(consumed) { + } parse_result& operator=(const parse_result&) = default; parse_result& operator=(parse_result&&) = default; @@ -774,32 +1122,58 @@ class parse_result { * Returns a number of occurrences of the option in * the command line arguments. */ - size_t - count(const std::string& name) const; + size_t count(const std::string& name) const { + const auto ki = keys_.find(name); + if (ki == keys_.end()) { + return 0; + } - bool - has(const std::string& name) const; + const auto vi = values_.find(ki->second); + if (vi == values_.end()) { + return 0; + } - const option_value& - operator[](const std::string& name) const; + return vi->second.count(); + } + + bool has(const std::string& name) const { + return count(name) != 0; + } + + const option_value& operator[](const std::string& name) const { + const auto ki = keys_.find(name); + if (ki == keys_.end()) { + detail::throw_or_mimic(name); + } + + const auto vi = values_.find(ki->second); + if (vi == values_.end()) { + detail::throw_or_mimic(name); + } + + return vi->second; + } /** * Returns list of recognized options with non empty value. */ - const std::vector& - arguments() const; + const std::vector& arguments() const { + return sequential_; + } /** * Returns number of consumed command line arguments. */ - size_t - consumed() const; + size_t consumed() const { + return consumed_arguments_; + } /** * Returns list of unmatched arguments. */ - const std::vector& - unmatched() const; + const std::vector& unmatched() const { + return unmatched_; + } private: name_hash_map keys_{}; @@ -811,19 +1185,359 @@ class parse_result { size_t consumed_arguments_{0}; }; +namespace detail { + +class option_parser { + using option_map = + std::unordered_map>; + using positional_list = std::vector; + using positional_list_iterator = positional_list::const_iterator; + + struct option_data { + std::string name{}; + std::string value{}; + bool is_long{false}; + bool has_value{false}; + }; + +public: + option_parser(const option_map& options, + const positional_list& positional, + bool allow_unrecognised, + bool stop_on_positional) + : options_(options) + , positional_(positional) + , allow_unrecognised_(allow_unrecognised) + , stop_on_positional_(stop_on_positional) { + } + + parse_result parse(const int argc, const char* const* argv) { + int current = 1; + auto next_positional = positional_.begin(); + std::vector unmatched; + + while (current < argc) { + if (is_dash_dash(argv[current])) { + // Skip dash-dash argument. + ++current; + if (stop_on_positional_) { + break; + } + // Try to consume all remaining arguments as positional. + for (; current < argc; ++current) { + if (!consume_positional(argv[current], next_positional)) { + break; + } + } + // Adjust argv for any that couldn't be swallowed. + for (; current != argc; ++current) { + unmatched.emplace_back(argv[current]); + } + break; + } + + option_data result; + + if (!parse_argument(argv[current], result)) { + // Not a flag. + // But if it starts with a `-`, then it's an error. + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!allow_unrecognised_) { + detail::throw_or_mimic(argv[current]); + } + } + if (stop_on_positional_) { + break; + } + // If true is returned here then it was consumed, otherwise it + // is ignored. + if (!consume_positional(argv[current], next_positional)) { + unmatched.emplace_back(argv[current]); + } + // If we return from here then it was parsed successfully, so + // continue. + } else { + // Short or long option? + if (result.is_long == false) { + const std::string& seq = result.name; + // Iterate over the sequence of short options. + for (std::size_t i = 0; i != seq.size(); ++i) { + const std::string name(1, seq[i]); + const auto oi = options_.find(name); + + if (oi == options_.end()) { + if (allow_unrecognised_) { + unmatched.push_back(std::string("-") + seq[i]); + continue; + } + // Error. + detail::throw_or_mimic(name); + } + + const auto& opt = oi->second; + if (i + 1 == seq.size()) { + // It must be the last argument. + checked_parse_arg(argc, argv, current, opt, name); + } else if (opt->value()->has_implicit()) { + parse_option(opt, opt->value()->get_implicit_value()); + } else { + parse_option(opt, seq.substr(i + 1)); + break; + } + } + } else { + const std::string& name = result.name; + const auto oi = options_.find(name); + + if (oi == options_.end()) { + if (allow_unrecognised_) { + // Keep unrecognised options in argument list, + // skip to next argument. + unmatched.emplace_back(argv[current]); + ++current; + continue; + } + // Error. + detail::throw_or_mimic(name); + } + + const auto& opt = oi->second; + // Equal sign provided for the long option? + if (result.has_value) { + // Parse the option given. + parse_option(opt, result.value); + } else { + // Parse the next argument. + checked_parse_arg(argc, argv, current, opt, name); + } + } + } + + ++current; + } + + // Setup default or env values. + for (auto& opt : options_) { + auto& detail = opt.second; + auto& store = parsed_[detail->hash()]; + const auto& value = detail->value(); + + // Skip options with parsed values. + if (store.count() || store.has_default()) { + continue; + } + // Try to setup env value. + if (value->has_env()) { + if (const char* env = std::getenv(value->get_env_var().c_str())) { + store.parse(detail, std::string(env)); + continue; + } + } + // Try to setup default value. + if (value->has_default()) { + store.parse_default(detail); + } else { + store.parse_no_value(detail); + } + } + + parse_result::name_hash_map keys; + // Finalize aliases. + for (const auto& option : options_) { + const auto& detail = option.second; + const auto hash = detail->hash(); + + if (detail->short_name().size()) { + keys[detail->short_name()] = hash; + } + if (detail->long_name().size()) { + keys[detail->long_name()] = hash; + } + } + + assert(stop_on_positional_ || argc == current || argc == 0); + + return parse_result(std::move(keys), std::move(parsed_), + std::move(sequential_), std::move(unmatched), current); + } + +private: + bool consume_positional(const std::string& arg, + positional_list_iterator& next) { + for (; next != positional_.end(); ++next) { + const auto oi = options_.find(*next); + if (oi == options_.end()) { + detail::throw_or_mimic(*next); + } + if (oi->second->value()->is_container()) { + parse_option(oi->second, arg); + return true; + } + if (parsed_[oi->second->hash()].count() == 0) { + parse_option(oi->second, arg); + ++next; + return true; + } + } + return false; + } + + bool is_dash_dash(const char* str) const { + return (str[0] != 0 && str[0] == '-') && (str[1] != 0 && str[1] == '-') && + (str[2] == 0); + } + + bool is_dash_dash_or_option_name(const char* const arg) const { + // The dash-dash symbol has a special meaning and cannot + // be interpreted as an option value. + if (is_dash_dash(arg)) { + return true; + } + + option_data result; + // The argument does not match an option format + // so that it can be safely consumed as a value. + if (!parse_argument(arg, result)) { + return false; + } + + auto check_name = [this](const std::string& opt) { + return options_.find(opt) != options_.end(); + }; + // Check that the argument does not match any + // existing option. + if (result.is_long) { + return check_name(result.name); + } else { + return check_name(result.name.substr(0, 1)); + } + + return false; + } + + void checked_parse_arg(const int argc, + const char* const* argv, + int& current, + const std::shared_ptr& value, + const std::string& name) { + auto parse_implicit = [&]() { + if (value->value()->has_implicit()) { + parse_option(value, value->value()->get_implicit_value()); + } else { + detail::throw_or_mimic(name); + } + }; + + if (current + 1 == argc || value->value()->get_no_value()) { + // Last argument or the option without value. + parse_implicit(); + } else { + const char* const arg = argv[current + 1]; + // Check that we do not silently consume any option as a value + // of another option. + if (arg[0] == '-' && is_dash_dash_or_option_name(arg)) { + parse_implicit(); + } else { + // Parse argument as a value for the option. + parse_option(value, arg); + ++current; + } + } + } + + bool parse_argument(const std::string& text, option_data& data) const { + const char* p = text.c_str(); + // String should not be empty and should starts with '-'. + if (*p == 0 || *p != '-') { + return false; + } else { + ++p; + } + // Long option starts with '--'. + if (*p == '-') { + ++p; + if (isalnum(*p) && *(p + 1) != 0) { + data.is_long = true; + data.name += *p; + ++p; + } else { + return false; + } + for (; *p; ++p) { + if (*p == '=') { + ++p; + data.has_value = true; + data.value.assign(p, text.c_str() + text.size()); + break; + } + if (*p == '-' || *p == '_' || isalnum(*p)) { + data.name += *p; + } else { + return false; + } + } + return data.name.size() > 1; + // Short option. + } else { + // Single char short option. + if (*(p + 1) == 0) { + if (*p == '?' || isalnum(*p)) { + data.name = *p; + return true; + } + return false; + } + // Multiple short options. + for (; *p; ++p) { + if (isalnum(*p)) { + data.name += *p; + } else { + return false; + } + } + return true; + } + return false; + } + + void parse_option(const std::shared_ptr& details, + const std::string& arg) { + parsed_[details->hash()].parse(details, arg); + sequential_.emplace_back(details->canonical_name(), arg); + } + + void parse_default(const std::shared_ptr& details) { + parsed_[details->hash()].parse_default(details); + } + + void parse_no_value(const std::shared_ptr& details) { + parsed_[details->hash()].parse_no_value(details); + } + +private: + const option_map& options_; + const positional_list& positional_; + const bool allow_unrecognised_; + const bool stop_on_positional_; + + std::vector sequential_{}; + parse_result::parsed_hash_map parsed_{}; +}; + +} // namespace detail + +class options; + class option { public: - option( - std::string opts, - std::string desc, - std::shared_ptr value = ::cxxopts::value(), - std::string arg_help = {} - ) noexcept + option(std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = {}) noexcept : opts_(std::move(opts)) , desc_(std::move(desc)) , value_(std::move(value)) - , arg_help_(std::move(arg_help)) - { + , arg_help_(std::move(arg_help)) { } private: @@ -862,21 +1576,81 @@ class options { class option_adder { public: - option_adder(const std::string group, options& options); + option_adder(const std::string group, options& options) + : group_(std::move(group)) + , options_(options) { + } - option_adder& - operator() ( - const std::string& opts, - const std::string& desc, - const std::shared_ptr& value - = ::cxxopts::value(), - const std::string arg_help = {}); + option_adder& operator()(const std::string& opts, + const std::string& desc, + const std::shared_ptr& value = + ::cxxopts::value(), + const std::string arg_help = {}) { + std::string s; + std::string l; + if (parse_option_specifier(opts, s, l)) { + assert(s.empty() || s.size() == 1); + assert(l.empty() || l.size() > 1); + + options_.add_option(group_, std::move(s), std::move(l), desc, value, + std::move(arg_help)); + } else { + detail::throw_or_mimic(opts); + } + return *this; + } private: - bool - parse_option_specifier(const std::string& text, - std::string& s, - std::string& l) const; + bool parse_option_specifier(const std::string& text, + std::string& s, + std::string& l) const { + const char* p = text.c_str(); + if (*p == 0) { + return false; + } else { + s.clear(); + l.clear(); + } + // Short option. + if (*(p + 1) == 0 || *(p + 1) == ',') { + if (*p == '?' || isalnum(*p)) { + s = *p; + ++p; + } else { + return false; + } + } + // Skip comma. + if (*p == ',') { + if (s.empty()) { + return false; + } + ++p; + } + // Skip spaces. + while (*p && *p == ' ') { + ++p; + } + // Valid specifier without long option. + if (*p == 0) { + return true; + } else { + l.reserve((text.c_str() + text.size()) - p); + } + // First char of an option name should be alnum. + if (isalnum(*p)) { + l += *p; + ++p; + } + for (; *p; ++p) { + if (*p == '-' || *p == '_' || isalnum(*p)) { + l += *p; + } else { + return false; + } + } + return l.size() > 1; + } private: const std::string group_; @@ -884,141 +1658,439 @@ class options { }; public: - explicit options(std::string program, std::string help_string = {}); + explicit options(std::string program, std::string help_string = {}) + : program_(std::move(program)) + , help_string_(to_local_string(std::move(help_string))) + , custom_help_("[OPTION...]") + , positional_help_("positional parameters") { + } /** * Adds list of options to the specific group. */ - void - add_options( - const std::string& group, - std::initializer_list