// v1.0 // See https://github.com/philsquared/Clara #ifndef CLARA_HPP_INCLUDED #define CLARA_HPP_INCLUDED #ifndef CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH 80 #endif // ----------- #included from clara_textflow.hpp ----------- // TextFlowCpp // // A single-header library for wrapping and laying out basic text, by Phil Nash // // This work is licensed under the BSD 2-Clause license. // See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause // // This project is hosted at https://github.com/philsquared/textflowcpp #ifndef CLARA_TEXTFLOW_HPP_INCLUDED #define CLARA_TEXTFLOW_HPP_INCLUDED #include #include #include #include #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH #endif namespace clara { namespace TextFlow { inline auto isWhitespace( char c ) -> bool { static std::string chars = " \t\n\r"; return chars.find( c ) != std::string::npos; } inline auto isBreakableBefore( char c ) -> bool { static std::string chars = "[({<|"; return chars.find( c ) != std::string::npos; } inline auto isBreakableAfter( char c ) -> bool { static std::string chars = "])}>.,:;*+-=&/\\"; return chars.find( c ) != std::string::npos; } class Columns; class Column { std::vector m_strings; size_t m_width = TEXTFLOW_CONFIG_CONSOLE_WIDTH; size_t m_indent = 0; size_t m_initialIndent = std::string::npos; public: class iterator { friend Column; Column const& m_column; size_t m_stringIndex = 0; size_t m_pos = 0; size_t m_len = 0; bool m_suffix = false; iterator( Column const& column, size_t stringIndex ) : m_column( column ), m_stringIndex( stringIndex ) {} auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } auto isBoundary( size_t at ) const -> bool { assert( at > 0 ); assert( at <= line().size() ); return at == line().size() || ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || isBreakableBefore( line()[at] ) || isBreakableAfter( line()[at-1] ); } void calcLength() { assert( m_stringIndex < m_column.m_strings.size() ); m_suffix = false; auto width = m_column.m_width-indent(); if( line().size() < m_pos + width ) { m_len = line().size() - m_pos; } else { size_t len = width; while (len > 0 && !isBoundary(m_pos + len)) --len; while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) --len; if (len > 0) { m_len = len; } else { m_suffix = true; m_len = width - 1; } } } auto indent() const -> size_t { auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; return initial == std::string::npos ? m_column.m_indent : initial; } auto addIndentAndSuffix(std::string const &plain) const -> std::string { return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); } public: explicit iterator( Column const& column ) : m_column( column ) { assert( m_column.m_width > m_column.m_indent ); assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); calcLength(); if( m_len == 0 ) m_stringIndex++; // Empty string } auto operator *() const -> std::string { assert( m_stringIndex < m_column.m_strings.size() ); assert( m_pos < line().size() ); if( m_pos + m_column.m_width < line().size() ) return addIndentAndSuffix(line().substr(m_pos, m_len)); else return addIndentAndSuffix(line().substr(m_pos)); } auto operator ++() -> iterator& { m_pos += m_len; while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) ++m_pos; if( m_pos == line().size() ) { m_pos = 0; ++m_stringIndex; } if( m_stringIndex < m_column.m_strings.size() ) calcLength(); return *this; } auto operator ++(int) -> iterator { iterator prev( *this ); operator++(); return prev; } auto operator ==( iterator const& other ) const -> bool { return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex && &m_column == &other.m_column; } auto operator !=( iterator const& other ) const -> bool { return !operator==( other ); } }; using const_iterator = iterator; explicit Column( std::string const& text ) { m_strings.push_back( text ); } auto width( size_t newWidth ) -> Column& { assert( newWidth > 0 ); m_width = newWidth; return *this; } auto indent( size_t newIndent ) -> Column& { m_indent = newIndent; return *this; } auto initialIndent( size_t newIndent ) -> Column& { m_initialIndent = newIndent; return *this; } auto width() const -> size_t { return m_width; } auto begin() const -> iterator { return iterator( *this ); } auto end() const -> iterator { return { *this, m_strings.size() }; } inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { bool first = true; for( auto line : col ) { if( first ) first = false; else os << "\n"; os << line; } return os; } auto operator + ( Column const& other ) -> Columns; auto toString() const -> std::string { std::ostringstream oss; oss << *this; return oss.str(); } }; class Spacer : public Column { public: explicit Spacer( size_t spaceWidth ) : Column( "" ) { width( spaceWidth ); } }; class Columns { std::vector m_columns; public: class iterator { friend Columns; struct EndTag {}; std::vector const& m_columns; std::vector m_iterators; size_t m_activeIterators; iterator( Columns const& columns, EndTag ) : m_columns( columns.m_columns ), m_activeIterators( 0 ) { m_iterators.reserve( m_columns.size() ); for( auto const& col : m_columns ) m_iterators.push_back( col.end() ); } public: explicit iterator( Columns const& columns ) : m_columns( columns.m_columns ), m_activeIterators( m_columns.size() ) { m_iterators.reserve( m_columns.size() ); for( auto const& col : m_columns ) m_iterators.push_back( col.begin() ); } auto operator ==( iterator const& other ) const -> bool { return m_iterators == other.m_iterators; } auto operator !=( iterator const& other ) const -> bool { return m_iterators != other.m_iterators; } auto operator *() const -> std::string { std::string row, padding; for( size_t i = 0; i < m_columns.size(); ++i ) { auto width = m_columns[i].width(); if( m_iterators[i] != m_columns[i].end() ) { std::string col = *m_iterators[i]; row += padding + col; if( col.size() < width ) padding = std::string( width - col.size(), ' ' ); else padding = ""; } else { padding += std::string( width, ' ' ); } } return row; } auto operator ++() -> iterator& { for( size_t i = 0; i < m_columns.size(); ++i ) { if (m_iterators[i] != m_columns[i].end()) ++m_iterators[i]; } return *this; } auto operator ++(int) -> iterator { iterator prev( *this ); operator++(); return prev; } }; using const_iterator = iterator; auto begin() const -> iterator { return iterator( *this ); } auto end() const -> iterator { return { *this, iterator::EndTag() }; } auto operator += ( Column const& col ) -> Columns& { m_columns.push_back( col ); return *this; } auto operator + ( Column const& col ) -> Columns { Columns combined = *this; combined += col; return combined; } inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { bool first = true; for( auto line : cols ) { if( first ) first = false; else os << "\n"; os << line; } return os; } auto toString() const -> std::string { std::ostringstream oss; oss << *this; return oss.str(); } }; inline auto Column::operator + ( Column const& other ) -> Columns { Columns cols; cols += *this; cols += other; return cols; } }} // namespace clara::TextFlow #endif // CLARA_TEXTFLOW_HPP_INCLUDED // ----------- end of #include from clara_textflow.hpp ----------- // ........... back in clara.hpp #include #include #include namespace clara { namespace detail { // Traits for extracting arg and return type of lambdas (for single argument lambdas) template struct UnaryLambdaTraits : UnaryLambdaTraits {}; template struct UnaryLambdaTraits { static const bool isValid = false; }; template struct UnaryLambdaTraits { static const bool isValid = true; using ArgType = typename std::remove_const::type>::type;; using ReturnType = ReturnT; }; class TokenStream; // Transport for raw args (copied from main args, or supplied via init list for testing) class Args { friend TokenStream; std::string m_exeName; std::vector m_args; public: Args(int argc, char *argv[]) { m_exeName = argv[0]; for (int i = 1; i < argc; ++i) m_args.push_back(argv[i]); } Args(std::initializer_list args) : m_exeName( *args.begin() ), m_args( args.begin()+1, args.end() ) {} auto exeName() const -> std::string { return m_exeName; } }; // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string // may encode an option + its argument if the : or = form is used enum class TokenType { Option, Argument }; struct Token { TokenType type; std::string token; }; // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled class TokenStream { using Iterator = std::vector::const_iterator; Iterator it; Iterator itEnd; std::vector m_tokenBuffer; void loadBuffer() { m_tokenBuffer.resize(0); // Skip any empty strings while (it != itEnd && it->empty()) ++it; if (it != itEnd) { auto const &next = *it; if (next[0] == '-' || next[0] == '/') { auto delimiterPos = next.find_first_of(" :="); if (delimiterPos != std::string::npos) { m_tokenBuffer.push_back({TokenType::Option, next.substr(0, delimiterPos)}); m_tokenBuffer.push_back({TokenType::Argument, next.substr(delimiterPos + 1)}); } else { if (next[1] != '-' && next.size() > 2) { std::string opt = "- "; for (size_t i = 1; i < next.size(); ++i) { opt[1] = next[i]; m_tokenBuffer.push_back({TokenType::Option, opt}); } } else { m_tokenBuffer.push_back({TokenType::Option, next}); } } } else { m_tokenBuffer.push_back({TokenType::Argument, next}); } } } public: explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {} TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { loadBuffer(); } explicit operator bool() const { return !m_tokenBuffer.empty() || it != itEnd; } auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } auto operator*() const -> Token { assert(!m_tokenBuffer.empty()); return m_tokenBuffer.front(); } auto operator->() const -> Token const * { assert(!m_tokenBuffer.empty()); return &m_tokenBuffer.front(); } auto operator++() -> TokenStream & { if (m_tokenBuffer.size() >= 2) { m_tokenBuffer.erase(m_tokenBuffer.begin()); } else { if (it != itEnd) ++it; loadBuffer(); } return *this; } }; class ResultBase { public: enum Type { Ok, LogicError, RuntimeError }; protected: ResultBase(Type type) : m_type(type) {} virtual ~ResultBase() = default; virtual void enforceOk() const = 0; Type m_type; }; template class ResultValueBase : public ResultBase { public: auto value() const -> T const & { enforceOk(); return m_value; } protected: ResultValueBase(Type type) : ResultBase(type) {} ResultValueBase(ResultValueBase const &other) : ResultBase(other) { if (m_type == ResultBase::Ok) new(&m_value) T(other.m_value); } ResultValueBase(Type, T const &value) : ResultBase(Ok) { new(&m_value) T(value); } auto operator=(ResultValueBase const &other) -> ResultValueBase & { if (m_type == ResultBase::Ok) m_value.~T(); ResultBase::operator=(other); if (m_type == ResultBase::Ok) new(&m_value) T(other.m_value); return *this; } ~ResultValueBase() { if (m_type == Ok) m_value.~T(); } union { T m_value; }; }; template<> class ResultValueBase : public ResultBase { protected: using ResultBase::ResultBase; }; template class BasicResult : public ResultValueBase { public: template explicit BasicResult(BasicResult const &other) : ResultValueBase(other.type()), m_errorMessage(other.errorMessage()) { assert(type() != ResultBase::Ok); } static auto ok() -> BasicResult { return {ResultBase::Ok}; } template static auto ok(U const &value) -> BasicResult { return {ResultBase::Ok, value}; } static auto logicError(std::string const &message) -> BasicResult { return {ResultBase::LogicError, message}; } static auto runtimeError(std::string const &message) -> BasicResult { return {ResultBase::RuntimeError, message}; } explicit operator bool() const { return m_type == ResultBase::Ok; } auto type() const -> ResultBase::Type { return m_type; } auto errorMessage() const -> std::string { return m_errorMessage; } protected: virtual void enforceOk() const { // !TBD: If no exceptions, std::terminate here or something switch (m_type) { case ResultBase::LogicError: throw std::logic_error(m_errorMessage); case ResultBase::RuntimeError: throw std::runtime_error(m_errorMessage); case ResultBase::Ok: break; } } std::string m_errorMessage; // Only populated if resultType is an error BasicResult(ResultBase::Type type, std::string const &message) : ResultValueBase(type), m_errorMessage(message) { assert(m_type != ResultBase::Ok); } using ResultValueBase::ResultValueBase; using ResultBase::m_type; }; enum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame }; class ParseState { public: ParseState(ParseResultType type, TokenStream const &remainingTokens) : m_type(type), m_remainingTokens(remainingTokens) {} auto type() const -> ParseResultType { return m_type; } auto remainingTokens() const -> TokenStream { return m_remainingTokens; } private: ParseResultType m_type; TokenStream m_remainingTokens; }; using Result = BasicResult; using ParserResult = BasicResult; using InternalParseResult = BasicResult; struct HelpColumns { std::string left; std::string right; }; template inline auto convertInto(std::string const &source, T& target) -> ParserResult { std::stringstream ss; ss << source; ss >> target; if (ss.fail()) return ParserResult::runtimeError("Unable to convert '" + source + "' to destination type"); else return ParserResult::ok(ParseResultType::Matched); } inline auto convertInto(std::string const &source, std::string& target) -> ParserResult { target = source; return ParserResult::ok(ParseResultType::Matched); } inline auto convertInto(std::string const &source, bool &target) -> ParserResult { std::string srcLC = source; std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), [](char c) { return static_cast( ::tolower(c) ); } ); if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") target = true; else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") target = false; else return ParserResult::runtimeError("Expected a boolean value but did not recognise: '" + source + "'"); return ParserResult::ok(ParseResultType::Matched); } struct BoundRefBase { BoundRefBase() = default; BoundRefBase(BoundRefBase const &) = delete; BoundRefBase(BoundRefBase &&) = delete; BoundRefBase &operator=(BoundRefBase const &) = delete; BoundRefBase &operator=(BoundRefBase &&) = delete; virtual ~BoundRefBase() = default; virtual auto isFlag() const -> bool = 0; virtual auto isContainer() const -> bool { return false; } virtual auto setValue(std::string const &arg) -> ParserResult = 0; virtual auto setFlag(bool flag) -> ParserResult = 0; }; struct BoundValueRefBase : BoundRefBase { auto isFlag() const -> bool override { return false; } auto setFlag(bool) -> ParserResult override { return ParserResult::logicError("Flags can only be set on boolean fields"); } }; struct BoundFlagRefBase : BoundRefBase { auto isFlag() const -> bool override { return true; } auto setValue(std::string const &arg) -> ParserResult override { bool flag; auto result = convertInto(arg, flag); if (result) setFlag(flag); return result; } }; template struct BoundRef : BoundValueRefBase { T &m_ref; explicit BoundRef(T &ref) : m_ref(ref) {} auto setValue(std::string const &arg) -> ParserResult override { return convertInto(arg, m_ref); } }; template struct BoundRef> : BoundValueRefBase { std::vector &m_ref; explicit BoundRef(std::vector &ref) : m_ref(ref) {} auto isContainer() const -> bool override { return true; } auto setValue(std::string const &arg) -> ParserResult override { T temp; auto result = convertInto(arg, temp); if (result) m_ref.push_back(temp); return result; } }; struct BoundFlagRef : BoundFlagRefBase { bool &m_ref; explicit BoundFlagRef(bool &ref) : m_ref(ref) {} auto setFlag(bool flag) -> ParserResult override { m_ref = flag; return ParserResult::ok(ParseResultType::Matched); } }; template struct LambdaInvoker { static_assert(std::is_same::value, "Lambda must return void or clara::ParserResult"); template static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { return lambda(arg); } }; template<> struct LambdaInvoker { template static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { lambda(arg); return ParserResult::ok(ParseResultType::Matched); } }; template inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult { ArgType temp; auto result = convertInto(arg, temp); return !result ? result : LambdaInvoker::ReturnType>::invoke(lambda, temp); }; template struct BoundLambda : BoundValueRefBase { L m_lambda; static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); explicit BoundLambda(L const &lambda) : m_lambda(lambda) {} auto setValue(std::string const &arg) -> ParserResult override { return invokeLambda::ArgType>(m_lambda, arg); } }; template struct BoundFlagLambda : BoundFlagRefBase { L m_lambda; static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {} auto setFlag(bool flag) -> ParserResult override { return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); } }; enum class Optionality { Optional, Required }; struct Parser; class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } auto parse(Args const &args) const -> InternalParseResult { return parse( args.exeName(), TokenStream(args)); } }; template class ComposableParserImpl : public ParserBase { public: template auto operator+(T const &other) const -> Parser; }; // Common code and state for Args and Opts template class ParserRefImpl : public ComposableParserImpl { protected: Optionality m_optionality = Optionality::Optional; std::shared_ptr m_ref; std::string m_hint; std::string m_description; explicit ParserRefImpl(std::shared_ptr const &ref) : m_ref(ref) {} public: template ParserRefImpl(T &ref, std::string const &hint) : m_ref(std::make_shared>(ref)), m_hint(hint) {} template ParserRefImpl(LambdaT const &ref, std::string const &hint) : m_ref(std::make_shared>(ref)), m_hint(hint) {} auto operator()(std::string const &description) -> DerivedT & { m_description = description; return static_cast( *this ); } auto optional() -> DerivedT & { m_optionality = Optionality::Optional; return static_cast( *this ); }; auto required() -> DerivedT & { m_optionality = Optionality::Required; return static_cast( *this ); }; auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } auto cardinality() const -> size_t override { if (m_ref->isContainer()) return 0; else return 1; } auto hint() const -> std::string { return m_hint; } }; class ExeName : public ComposableParserImpl { std::shared_ptr m_name; std::shared_ptr m_ref; template static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { return std::make_shared>(lambda); } public: ExeName() : m_name(std::make_shared("")) {} explicit ExeName(std::string &ref) : ExeName() { m_ref = std::make_shared>( ref ); } template explicit ExeName( LambdaT const& lambda ) : ExeName() { m_ref = std::make_shared>( lambda ); } // The exe name is not parsed out of the normal tokens, but is handled specially auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); } auto name() const -> std::string { return *m_name; } auto set( std::string const& newName ) -> ParserResult { auto lastSlash = newName.find_last_of( "\\/" ); auto filename = (lastSlash == std::string::npos) ? newName : newName.substr( lastSlash+1 ); *m_name = filename; if( m_ref ) return m_ref->setValue( filename ); else return ParserResult::ok( ParseResultType::Matched ); } }; class Arg : public ParserRefImpl { public: using ParserRefImpl::ParserRefImpl; auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { auto validationResult = validate(); if (!validationResult) return InternalParseResult(validationResult); auto remainingTokens = tokens; auto const &token = *remainingTokens; if (token.type != TokenType::Argument) return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); auto result = m_ref->setValue(remainingTokens->token); if (!result) return InternalParseResult(result); else return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); } }; inline auto normaliseOpt(std::string const &optName) -> std::string { if (optName[0] == '/') return "-" + optName.substr(1); else return optName; } class Opt : public ParserRefImpl { protected: std::vector m_optNames; public: using ParserRefImpl::ParserRefImpl; template explicit Opt( LambdaT const &ref ) : ParserRefImpl(std::make_shared>(ref)) {} explicit Opt( bool &ref ) : ParserRefImpl(std::make_shared(ref)) {} auto operator[](std::string const &optName) -> Opt & { m_optNames.push_back(optName); return *this; } auto getHelpColumns() const -> std::vector { std::ostringstream oss; bool first = true; for (auto const &opt : m_optNames) { if (first) first = false; else oss << ", "; oss << opt; } if (!m_hint.empty()) oss << " <" << m_hint << ">"; return {{oss.str(), m_description}}; } auto isMatch(std::string const &optToken) const -> bool { #ifdef CLARA_PLATFORM_WINDOWS auto normalisedToken = normaliseOpt( optToken ); #else auto const &normalisedToken = optToken; #endif for (auto const &name : m_optNames) { if (normaliseOpt(name) == normalisedToken) return true; } return false; } using ParserBase::parse; auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { auto validationResult = validate(); if (!validationResult) return InternalParseResult(validationResult); auto remainingTokens = tokens; if (remainingTokens && remainingTokens->type == TokenType::Option) { auto const &token = *remainingTokens; if (isMatch(token.token)) { if (m_ref->isFlag()) { auto result = m_ref->setFlag(true); if (!result) return InternalParseResult(result); if (result.value() == ParseResultType::ShortCircuitAll) return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); } else { ++remainingTokens; if (!remainingTokens) return InternalParseResult::runtimeError("Expected argument following " + token.token); auto const &argToken = *remainingTokens; if (argToken.type != TokenType::Argument) return InternalParseResult::runtimeError("Expected argument following " + token.token); auto result = m_ref->setValue(argToken.token); if (!result) return InternalParseResult(result); if (result.value() == ParseResultType::ShortCircuitAll) return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); } return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens)); } } return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); } auto validate() const -> Result override { if (m_optNames.empty()) return Result::logicError("No options supplied to Opt"); for (auto const &name : m_optNames) { if (name.empty()) return Result::logicError("Option name cannot be empty"); if (name[0] != '-' && name[0] != '/') return Result::logicError("Option name must begin with '-' or '/'"); } return ParserRefImpl::validate(); } }; struct Help : Opt { Help( bool &showHelpFlag ) : Opt([&]( bool flag ) { showHelpFlag = flag; return ParserResult::ok(ParseResultType::ShortCircuitAll); }) { static_cast(*this) ("display usage information") ["-?"]["-h"]["--help"] .optional(); } }; struct Parser : ParserBase { mutable ExeName m_exeName; std::vector m_options; std::vector m_args; auto operator+=(ExeName const &exeName) -> Parser & { m_exeName = exeName; return *this; } auto operator+=(Arg const &arg) -> Parser & { m_args.push_back(arg); return *this; } auto operator+=(Opt const &opt) -> Parser & { m_options.push_back(opt); return *this; } auto operator+=(Parser const &other) -> Parser & { m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); return *this; } template auto operator+(T const &other) const -> Parser { return Parser(*this) += other; } auto getHelpColumns() const -> std::vector { std::vector cols; for (auto const &o : m_options) { auto childCols = o.getHelpColumns(); cols.insert(cols.end(), childCols.begin(), childCols.end()); } return cols; } void writeToStream(std::ostream &os) const { if (!m_exeName.name().empty()) { os << "usage:\n" << " " << m_exeName.name() << " "; bool required = true, first = true; for (auto const &arg : m_args) { if (first) first = false; else os << " "; if (arg.isOptional() && required) { os << "["; required = false; } os << "<" << arg.hint() << ">"; if (arg.cardinality() == 0) os << " ... "; } if (!required) os << "]"; if (!m_options.empty()) os << " options"; os << "\n\nwhere options are:" << std::endl; } auto rows = getHelpColumns(); size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; size_t optWidth = 0; for (auto const &cols : rows) optWidth = std::max(optWidth, cols.left.size() + 2); for (auto const &cols : rows) { auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); os << row << std::endl; } } friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & { parser.writeToStream(os); return os; } auto validate() const -> Result override { for (auto const &opt : m_options) { auto result = opt.validate(); if (!result) return result; } for (auto const &arg : m_args) { auto result = arg.validate(); if (!result) return result; } return Result::ok(); } using ParserBase::parse; auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult override { std::vector allParsers; allParsers.reserve(m_args.size() + m_options.size()); std::set requiredParsers; for (auto const &opt : m_options) { allParsers.push_back(&opt); if (!opt.isOptional()) requiredParsers.insert(&opt); } size_t optionalArgs = 0; for (auto const &arg : m_args) { allParsers.push_back(&arg); if (!arg.isOptional()) { if (optionalArgs > 0) return InternalParseResult::logicError( "Required arguments must preceed any optional arguments"); else ++optionalArgs; requiredParsers.insert(&arg); } } m_exeName.set( exeName ); auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); while (result.value().remainingTokens()) { auto remainingTokenCount = result.value().remainingTokens().count(); for (auto parser : allParsers) { result = parser->parse( exeName, result.value().remainingTokens() ); if (!result || result.value().type() != ParseResultType::NoMatch) { if (parser->cardinality() == 1) allParsers.erase(std::remove(allParsers.begin(), allParsers.end(), parser), allParsers.end()); requiredParsers.erase(parser); break; } } if (!result || remainingTokenCount == result.value().remainingTokens().count()) return result; } // !TBD Check missing required options return result; } }; template template auto ComposableParserImpl::operator+(T const &other) const -> Parser { return Parser() + static_cast( *this ) + other; } } // namespace detail // A Combined parser using detail::Parser; // A parser for options using detail::Opt; // A parser for arguments using detail::Arg; // Wrapper for argc, argv from main() using detail::Args; // Specifies the name of the executable using detail::ExeName; // Convenience wrapper for option parser that specifies the help option using detail::Help; // enum of result types from a parse using detail::ParseResultType; // Result type for parser operation using detail::ParserResult; } // namespace clara #endif // CLARA_HPP_INCLUDED