From da5964af782fc6dc1d7c8f19507e967f3a286ca9 Mon Sep 17 00:00:00 2001 From: Phil Nash Date: Tue, 20 Jun 2017 18:03:37 +0100 Subject: [PATCH] Updated version of Clara (should fix Windows compile issues) - embedded using new embed script --- include/external/clara.hpp | 102 ++- include/internal/catch_clara.h | 8 +- scripts/embed.py | 73 ++ scripts/embedClara.py | 24 + third_party/clara.hpp | 1211 ++++++++++++++++++++++++++++++++ 5 files changed, 1376 insertions(+), 42 deletions(-) create mode 100644 scripts/embed.py create mode 100644 scripts/embedClara.py create mode 100644 third_party/clara.hpp diff --git a/include/external/clara.hpp b/include/external/clara.hpp index f4ad627f..b2eced66 100644 --- a/include/external/clara.hpp +++ b/include/external/clara.hpp @@ -4,23 +4,40 @@ #ifndef CATCH_CLARA_HPP_INCLUDED #define CATCH_CLARA_HPP_INCLUDED + +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_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 CATCH_CLARA_TEXTFLOW_HPP_INCLUDED +#define CATCH_CLARA_TEXTFLOW_HPP_INCLUDED + + #include #include #include #include -#include -#include -#include -#ifndef CLARA_CONFIG_CONSOLE_WIDTH -#define CLARA_CONFIG_CONSOLE_WIDTH 80 +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH + #endif -namespace Catch { -namespace clara { -// Included from TextFlow.hpp -namespace TextFlow { +namespace Catch { namespace clara { namespace TextFlow +{ inline auto isWhitespace( char c ) -> bool { static std::string chars = " \t\n\r"; @@ -39,7 +56,7 @@ namespace TextFlow { class Column { std::vector m_strings; - size_t m_width = CLARA_CONFIG_CONSOLE_WIDTH; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; size_t m_indent = 0; size_t m_initialIndent = std::string::npos; @@ -51,12 +68,12 @@ namespace TextFlow { size_t m_stringIndex = 0; size_t m_pos = 0; - size_t m_len; + size_t m_len = 0; bool m_suffix = false; iterator( Column const& column, size_t stringIndex ) - : m_column( column ), - m_stringIndex( stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) {} auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } @@ -143,9 +160,9 @@ namespace TextFlow { auto operator ==( iterator const& other ) const -> bool { return - m_pos == other.m_pos && - m_stringIndex == other.m_stringIndex && - &m_column == &other.m_column; + 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 ); @@ -171,7 +188,7 @@ namespace TextFlow { auto width() const -> size_t { return m_width; } auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return iterator( *this, m_strings.size() ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { bool first = true; @@ -197,7 +214,7 @@ namespace TextFlow { class Spacer : public Column { public: - Spacer( size_t spaceWidth ) : Column( "" ) { + explicit Spacer( size_t spaceWidth ) : Column( "" ) { width( spaceWidth ); } }; @@ -216,8 +233,8 @@ namespace TextFlow { size_t m_activeIterators; iterator( Columns const& columns, EndTag ) - : m_columns( columns.m_columns ), - m_activeIterators( 0 ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) { m_iterators.reserve( m_columns.size() ); @@ -226,9 +243,9 @@ namespace TextFlow { } public: - iterator( Columns const& columns ) - : m_columns( columns.m_columns ), - m_activeIterators( m_columns.size() ) + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) { m_iterators.reserve( m_columns.size() ); @@ -277,7 +294,7 @@ namespace TextFlow { using const_iterator = iterator; auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return iterator( *this, iterator::EndTag() ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } auto operator += ( Column const& col ) -> Columns& { m_columns.push_back( col ); @@ -315,9 +332,19 @@ namespace TextFlow { cols += other; return cols; } +}}} // namespace Catch::clara::TextFlow -} // namespace TextFlow +#endif // CATCH_CLARA_TEXTFLOW_HPP_INCLUDED +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp + + +#include +#include +#include + +namespace Catch { namespace clara { namespace detail { // Traits for extracting arg and return type of lambdas (for single argument lambdas) @@ -475,8 +502,7 @@ namespace detail { new(&m_value) T(other.m_value); } - ResultValueBase(Type type, T const &value) - : ResultBase(Ok) { + ResultValueBase(Type, T const &value) : ResultBase(Ok) { new(&m_value) T(value); } @@ -600,10 +626,9 @@ namespace detail { 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(), ::tolower); + 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") @@ -869,7 +894,7 @@ namespace detail { public: using ParserRefImpl::ParserRefImpl; - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { auto validationResult = validate(); if (!validationResult) return InternalParseResult(validationResult); @@ -941,10 +966,10 @@ namespace detail { using ParserBase::parse; - auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { - auto result = validate(); - if (!result) - return InternalParseResult(result); + 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) { @@ -1069,6 +1094,7 @@ namespace detail { } auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; size_t optWidth = 0; for (auto const &cols : rows) optWidth = std::max(optWidth, cols.left.size() + 2); @@ -1077,7 +1103,7 @@ namespace detail { auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) + - TextFlow::Column(cols.right).width(73 - optWidth); + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); os << row << std::endl; } } @@ -1131,7 +1157,7 @@ namespace detail { auto result = InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); while (result.value().remainingTokens()) { - int remainingTokenCount = result.value().remainingTokens().count(); + auto remainingTokenCount = result.value().remainingTokens().count(); for (auto parser : allParsers) { result = parser->parse( exeName, result.value().remainingTokens() ); if (!result || result.value().type() != ParseResultType::NoMatch) { @@ -1183,7 +1209,7 @@ using detail::ParseResultType; using detail::ParserResult; -} // namespace clara -} // namespace Catch +}} // namespace Catch::clara + #endif // CATCH_CLARA_HPP_INCLUDED diff --git a/include/internal/catch_clara.h b/include/internal/catch_clara.h index 9c66a55d..1aeb4658 100644 --- a/include/internal/catch_clara.h +++ b/include/internal/catch_clara.h @@ -11,10 +11,10 @@ // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH -#undef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH #endif -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #include "../external/clara.hpp" @@ -22,7 +22,7 @@ // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif diff --git a/scripts/embed.py b/scripts/embed.py new file mode 100644 index 00000000..85a7fcfb --- /dev/null +++ b/scripts/embed.py @@ -0,0 +1,73 @@ +import re + +preprocessorRe = re.compile( r'\s*#.*' ) + +fdefineRe = re.compile( r'\s*#\s*define\s*(\S*)\s*\(' ) # #defines that take arguments +defineRe = re.compile( r'\s*#\s*define\s*(\S*)(\s+)(.*)' ) # all #defines +undefRe = re.compile( r'\s*#\s*undef\s*(\S*)' ) # all #undefs + +ifdefCommonRe = re.compile( r'\s*#\s*if' ) # all #ifdefs +ifdefRe = re.compile( r'\s*#\s*ifdef\s*(\S*)' ) +ifndefRe = re.compile( r'\s*#\s*ifndef\s*(\S*)' ) +endifRe = re.compile( r'\s*#\s*endif\s*//\s*(.*)' ) +elseRe = re.compile( r'\s*#\s*else' ) +ifRe = re.compile( r'\s*#\s*if\s+(.*)' ) + +nsRe = re.compile( r'(.*?\s*\s*namespace\s+)(\w+)(\s*{?)(.*)' ) +nsCloseRe = re.compile( r'(.*\s*})(\s*\/\/\s*namespace\s+)(\w+)(\s*)(.*)' ) + + +class LineMapper: + def __init__( self, idMap, outerNamespace ): + self.idMap = idMap + self.outerNamespace = outerNamespace + + def replaceId( self, lineNo, id ): + if not self.idMap.has_key( id ): + raise ValueError( "Unrecognised macro identifier: '{0}' on line: {1}".format( id, lineNo ) ) + subst = self.idMap[id] + if subst == "": + return id + else: + return subst + + # TBD: + # #if, #ifdef, comments after #else + def mapLine( self, lineNo, line ): + m = ifndefRe.match( line ) + if m: + return "#ifndef " + self.replaceId( lineNo, m.group(1)) + "\n" + m = defineRe.match( line ) + if m: + return "#define {0}{1}{2}\n".format( self.replaceId( lineNo, m.group(1)), m.group(2), m.group(3) ) + m = endifRe.match( line ) + if m: + return "#endif // " + self.replaceId( lineNo, m.group(1)) + "\n" + m = nsCloseRe.match( line ) + if m: + originalNs = m.group(3) + # print("[{0}] originalNs: '{1}' - closing".format(lineNo, originalNs)) + # print( " " + line ) + # print( " 1:[{0}]\n 2:[{1}]\n 3:[{2}]\n 4:[{3}]\n 5:[{4}]".format( m.group(1), m.group(2), m.group(3), m.group(4), m.group(5) ) ) + if self.outerNamespace.has_key(originalNs): + outerNs, innerNs = self.outerNamespace[originalNs] + return "{0}}}{1}{2}::{3}{4}{5}\n".format( m.group(1), m.group(2), outerNs, innerNs, m.group(4), m.group(5)) + m = nsRe.match( line ) + if m: + originalNs = m.group(2) + # print("[{0}] originalNs: '{1}'".format(lineNo, originalNs)) + # print( " " + line ) + # print( " 1:[{0}]\n 2:[{1}]\n 3:[{2}]\n 4:[{3}]".format( m.group(1), m.group(2), m.group(3), m.group(4) ) ) + if self.outerNamespace.has_key(originalNs): + outerNs, innerNs = self.outerNamespace[originalNs] + return "{0}{1} {{ namespace {2}{3}{4}\n".format( m.group(1), outerNs, innerNs, m.group(3), m.group(4) ) + return line + + def mapFile(self, filenameIn, filenameOut ): + print( "Embedding:\n {0}\nas:\n {1}".format( filenameIn, filenameOut ) ) + with open( filenameIn, 'r' ) as f, open( filenameOut, 'w' ) as outf: + lineNo = 1 + for line in f: + outf.write( self.mapLine( lineNo, line ) ) + lineNo = lineNo + 1 + print( "Written {0} lines".format( lineNo ) ) \ No newline at end of file diff --git a/scripts/embedClara.py b/scripts/embedClara.py new file mode 100644 index 00000000..a35c221e --- /dev/null +++ b/scripts/embedClara.py @@ -0,0 +1,24 @@ +# Execute this script any time you import a new copy of Clara into the third_party area +import os +import sys +import embed + +rootPath = os.path.dirname(os.path.realpath( os.path.dirname(sys.argv[0]))) + +filename = os.path.join( rootPath, "third_party", "clara.hpp" ) +outfilename = os.path.join( rootPath, "include", "external", "clara.hpp" ) + + +# Mapping of pre-processor identifiers +idMap = { + "CLARA_HPP_INCLUDED": "CATCH_CLARA_HPP_INCLUDED", + "CLARA_CONFIG_CONSOLE_WIDTH": "CATCH_CLARA_CONFIG_CONSOLE_WIDTH", + "CLARA_TEXTFLOW_HPP_INCLUDED": "CATCH_CLARA_TEXTFLOW_HPP_INCLUDED", + "CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH": "CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH" + } + +# outer namespace to add +outerNamespace = { "clara": ("Catch", "clara") } + +mapper = embed.LineMapper( idMap, outerNamespace ) +mapper.mapFile( filename, outfilename ) \ No newline at end of file diff --git a/third_party/clara.hpp b/third_party/clara.hpp new file mode 100644 index 00000000..5455e5af --- /dev/null +++ b/third_party/clara.hpp @@ -0,0 +1,1211 @@ +// 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 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 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