mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-24 03:53:29 +01:00
1227 lines
40 KiB
C++
1227 lines
40 KiB
C++
// 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 <cassert>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
|
|
#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
|
|
#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<std::string> m_strings;
|
|
size_t m_width = CLARA_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;
|
|
size_t m_end = 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();
|
|
m_end = m_pos;
|
|
while( m_end < line().size() && line()[m_end] != '\n' )
|
|
++m_end;
|
|
|
|
if( m_end < m_pos + width ) {
|
|
m_len = m_end - 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 < m_end );
|
|
if( m_pos + m_column.m_width < m_end )
|
|
return addIndentAndSuffix(line().substr(m_pos, m_len));
|
|
else
|
|
return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos));
|
|
}
|
|
|
|
auto operator ++() -> iterator& {
|
|
m_pos += m_len;
|
|
if( m_pos < line().size() && line()[m_pos] == '\n' )
|
|
m_pos += 1;
|
|
else
|
|
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<Column> m_columns;
|
|
|
|
public:
|
|
|
|
class iterator {
|
|
friend Columns;
|
|
struct EndTag {};
|
|
|
|
std::vector<Column> const& m_columns;
|
|
std::vector<Column::iterator> 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 <memory>
|
|
#include <set>
|
|
#include <algorithm>
|
|
|
|
#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
|
|
#define CLARA_PLATFORM_WINDOWS
|
|
#endif
|
|
|
|
namespace clara {
|
|
namespace detail {
|
|
|
|
// Traits for extracting arg and return type of lambdas (for single argument lambdas)
|
|
template<typename L>
|
|
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};
|
|
|
|
template<typename ClassT, typename ReturnT, typename... Args>
|
|
struct UnaryLambdaTraits<ReturnT(ClassT::*)(Args...) const> {
|
|
static const bool isValid = false;
|
|
};
|
|
|
|
template<typename ClassT, typename ReturnT, typename ArgT>
|
|
struct UnaryLambdaTraits<ReturnT(ClassT::*)(ArgT) const> {
|
|
static const bool isValid = true;
|
|
using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::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<std::string> 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<std::string> 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<std::string>::const_iterator;
|
|
Iterator it;
|
|
Iterator itEnd;
|
|
std::vector<Token> 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<typename T>
|
|
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<void> : public ResultBase {
|
|
protected:
|
|
using ResultBase::ResultBase;
|
|
};
|
|
|
|
template<typename T = void>
|
|
class BasicResult : public ResultValueBase<T> {
|
|
public:
|
|
template<typename U>
|
|
explicit BasicResult(BasicResult<U> const &other)
|
|
: ResultValueBase<T>(other.type()),
|
|
m_errorMessage(other.errorMessage()) {
|
|
assert(type() != ResultBase::Ok);
|
|
}
|
|
|
|
static auto ok() -> BasicResult { return {ResultBase::Ok}; }
|
|
|
|
template<typename U>
|
|
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<T>(type),
|
|
m_errorMessage(message) {
|
|
assert(m_type != ResultBase::Ok);
|
|
}
|
|
|
|
using ResultValueBase<T>::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<void>;
|
|
using ParserResult = BasicResult<ParseResultType>;
|
|
using InternalParseResult = BasicResult<ParseState>;
|
|
|
|
struct HelpColumns {
|
|
std::string left;
|
|
std::string right;
|
|
};
|
|
|
|
template<typename T>
|
|
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<char>( ::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<typename T>
|
|
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<typename T>
|
|
struct BoundRef<std::vector<T>> : BoundValueRefBase {
|
|
std::vector<T> &m_ref;
|
|
|
|
explicit BoundRef(std::vector<T> &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<typename ReturnType>
|
|
struct LambdaInvoker {
|
|
static_assert(std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult");
|
|
|
|
template<typename L, typename ArgType>
|
|
static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult {
|
|
return lambda(arg);
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct LambdaInvoker<void> {
|
|
template<typename L, typename ArgType>
|
|
static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult {
|
|
lambda(arg);
|
|
return ParserResult::ok(ParseResultType::Matched);
|
|
}
|
|
};
|
|
|
|
template<typename ArgType, typename L>
|
|
inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult {
|
|
ArgType temp;
|
|
auto result = convertInto(arg, temp);
|
|
return !result
|
|
? result
|
|
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);
|
|
};
|
|
|
|
|
|
template<typename L>
|
|
struct BoundLambda : BoundValueRefBase {
|
|
L m_lambda;
|
|
|
|
static_assert( UnaryLambdaTraits<L>::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<typename UnaryLambdaTraits<L>::ArgType>(m_lambda, arg);
|
|
}
|
|
};
|
|
|
|
template<typename L>
|
|
struct BoundFlagLambda : BoundFlagRefBase {
|
|
L m_lambda;
|
|
|
|
static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
|
|
static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" );
|
|
|
|
explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {}
|
|
|
|
auto setFlag(bool flag) -> ParserResult override {
|
|
return LambdaInvoker<typename UnaryLambdaTraits<L>::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<typename DerivedT>
|
|
class ComposableParserImpl : public ParserBase {
|
|
public:
|
|
template<typename T>
|
|
auto operator+(T const &other) const -> Parser;
|
|
};
|
|
|
|
// Common code and state for Args and Opts
|
|
template<typename DerivedT>
|
|
class ParserRefImpl : public ComposableParserImpl<DerivedT> {
|
|
protected:
|
|
Optionality m_optionality = Optionality::Optional;
|
|
std::shared_ptr<BoundRefBase> m_ref;
|
|
std::string m_hint;
|
|
std::string m_description;
|
|
|
|
explicit ParserRefImpl(std::shared_ptr<BoundRefBase> const &ref) : m_ref(ref) {}
|
|
|
|
public:
|
|
template<typename T>
|
|
ParserRefImpl(T &ref, std::string const &hint) : m_ref(std::make_shared<BoundRef<T>>(ref)), m_hint(hint) {}
|
|
|
|
template<typename LambdaT>
|
|
ParserRefImpl(LambdaT const &ref, std::string const &hint) : m_ref(std::make_shared<BoundLambda<LambdaT>>(ref)),
|
|
m_hint(hint) {}
|
|
|
|
auto operator()(std::string const &description) -> DerivedT & {
|
|
m_description = description;
|
|
return static_cast<DerivedT &>( *this );
|
|
}
|
|
|
|
auto optional() -> DerivedT & {
|
|
m_optionality = Optionality::Optional;
|
|
return static_cast<DerivedT &>( *this );
|
|
};
|
|
|
|
auto required() -> DerivedT & {
|
|
m_optionality = Optionality::Required;
|
|
return static_cast<DerivedT &>( *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<ExeName> {
|
|
std::shared_ptr<std::string> m_name;
|
|
std::shared_ptr<BoundRefBase> m_ref;
|
|
|
|
template<typename LambdaT>
|
|
static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundRefBase> {
|
|
return std::make_shared<BoundLambda<LambdaT>>(lambda);
|
|
}
|
|
|
|
public:
|
|
ExeName() : m_name(std::make_shared<std::string>("<executable>")) {}
|
|
|
|
explicit ExeName(std::string &ref) : ExeName() {
|
|
m_ref = std::make_shared<BoundRef<std::string>>( ref );
|
|
}
|
|
|
|
template<typename LambdaT>
|
|
explicit ExeName( LambdaT const& lambda ) : ExeName() {
|
|
m_ref = std::make_shared<BoundLambda<LambdaT>>( 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<Arg> {
|
|
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<Opt> {
|
|
protected:
|
|
std::vector<std::string> m_optNames;
|
|
|
|
public:
|
|
template<typename LambdaT>
|
|
explicit Opt( LambdaT const &ref ) : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}
|
|
|
|
explicit Opt( bool &ref ) : ParserRefImpl(std::make_shared<BoundFlagRef>(ref)) {}
|
|
|
|
template<typename LambdaT>
|
|
Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
|
|
|
|
template<typename T>
|
|
Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
|
|
|
|
auto operator[](std::string const &optName) -> Opt & {
|
|
m_optNames.push_back(optName);
|
|
return *this;
|
|
}
|
|
|
|
auto getHelpColumns() const -> std::vector<HelpColumns> {
|
|
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<Opt &>(*this)
|
|
("display usage information")
|
|
["-?"]["-h"]["--help"]
|
|
.optional();
|
|
}
|
|
};
|
|
|
|
|
|
struct Parser : ParserBase {
|
|
|
|
mutable ExeName m_exeName;
|
|
std::vector<Opt> m_options;
|
|
std::vector<Arg> 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<typename T>
|
|
auto operator+(T const &other) const -> Parser {
|
|
return Parser(*this) += other;
|
|
}
|
|
|
|
auto getHelpColumns() const -> std::vector<HelpColumns> {
|
|
std::vector<HelpColumns> 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<ParserBase const *> allParsers;
|
|
allParsers.reserve(m_args.size() + m_options.size());
|
|
std::set<ParserBase const *> 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<typename DerivedT>
|
|
template<typename T>
|
|
auto ComposableParserImpl<DerivedT>::operator+(T const &other) const -> Parser {
|
|
return Parser() + static_cast<DerivedT const &>( *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
|