mirror of
https://github.com/catchorg/Catch2.git
synced 2025-09-14 01:15:39 +02:00
Compare commits
42 Commits
v2.0.0-dev
...
V2.0.0-dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ae21020640 | ||
![]() |
11f716f28d | ||
![]() |
c3ddd4a7e2 | ||
![]() |
c43ce85416 | ||
![]() |
4220f2eef2 | ||
![]() |
c1a91caf00 | ||
![]() |
96c5de678d | ||
![]() |
e68485e196 | ||
![]() |
88e912b4d1 | ||
![]() |
44244713f1 | ||
![]() |
eea9e1efd7 | ||
![]() |
601b2888ec | ||
![]() |
3049445d78 | ||
![]() |
c672512979 | ||
![]() |
57b4e0b64c | ||
![]() |
75a77b6f8c | ||
![]() |
5af918eefd | ||
![]() |
05b1ca2884 | ||
![]() |
c2b7bd15c0 | ||
![]() |
ba6845a865 | ||
![]() |
2eb93f47f7 | ||
![]() |
276393e4e5 | ||
![]() |
c7d9f02d5b | ||
![]() |
355ab78f4a | ||
![]() |
927f520a97 | ||
![]() |
cc0b093c20 | ||
![]() |
17cdf20968 | ||
![]() |
4899d891d3 | ||
![]() |
760a25e813 | ||
![]() |
79b405fd3f | ||
![]() |
f972732737 | ||
![]() |
61280e6d0a | ||
![]() |
7e9b53e40c | ||
![]() |
b80c5134f0 | ||
![]() |
70e0d48978 | ||
![]() |
11918b76d0 | ||
![]() |
5fe19f73e7 | ||
![]() |
c1416d55cb | ||
![]() |
2a1f8ae684 | ||
![]() |
9541e89e6a | ||
![]() |
80bbce8424 | ||
![]() |
3d49d83128 |
@@ -9,6 +9,7 @@ set(CATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(SELF_TEST_DIR ${CATCH_DIR}/projects/SelfTest)
|
||||
set(BENCHMARK_DIR ${CATCH_DIR}/projects/Benchmark)
|
||||
set(HEADER_DIR ${CATCH_DIR}/include)
|
||||
set(CATCH_VERSION_NUMBER 2.0.0-develop.6)
|
||||
|
||||
if(USE_CPP14)
|
||||
message(STATUS "Enabling C++14")
|
||||
@@ -68,6 +69,7 @@ set(TEST_SOURCES
|
||||
${SELF_TEST_DIR}/PartTrackerTests.cpp
|
||||
${SELF_TEST_DIR}/TagAliasTests.cpp
|
||||
${SELF_TEST_DIR}/TestMain.cpp
|
||||
${SELF_TEST_DIR}/ToStringChrono.cpp
|
||||
${SELF_TEST_DIR}/ToStringGeneralTests.cpp
|
||||
${SELF_TEST_DIR}/ToStringPair.cpp
|
||||
${SELF_TEST_DIR}/ToStringTuple.cpp
|
||||
@@ -274,33 +276,21 @@ set(HEADERS
|
||||
${REPORTER_HEADERS}
|
||||
)
|
||||
|
||||
|
||||
set(BENCH_SOURCES
|
||||
${BENCHMARK_DIR}/BenchMain.cpp
|
||||
${BENCHMARK_DIR}/StringificationBench.cpp
|
||||
)
|
||||
CheckFileList(BENCH_SOURCES ${BENCHMARK_DIR})
|
||||
|
||||
# Provide some groupings for IDEs
|
||||
SOURCE_GROUP("Tests" FILES ${TEST_SOURCES})
|
||||
SOURCE_GROUP("Surrogates" FILES ${SURROGATE_SOURCES})
|
||||
SOURCE_GROUP("Benchmarks" FILES ${BENCH_SOURCES})
|
||||
|
||||
# configure the executable
|
||||
include_directories(${HEADER_DIR})
|
||||
|
||||
add_definitions( -DCATCH_CONFIG_FULL_PROJECT )
|
||||
|
||||
# Projects consuming Catch via ExternalProject_Add might want to use install step
|
||||
# without building all of our selftests.
|
||||
if (NOT NO_SELFTEST)
|
||||
add_executable(SelfTest ${TEST_SOURCES} ${IMPL_SOURCES} ${REPORTER_SOURCES} ${SURROGATE_SOURCES} ${HEADERS})
|
||||
add_executable(Benchmark ${BENCH_SOURCES} ${IMPL_SOURCES} ${REPORTER_SOURCES} ${HEADERS})
|
||||
|
||||
# Add desired warnings
|
||||
if ( CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU" )
|
||||
target_compile_options( SelfTest PRIVATE -Wall -Wextra -Wunreachable-code )
|
||||
target_compile_options( Benchmark PRIVATE -Wall -Wextra -Wunreachable-code )
|
||||
endif()
|
||||
# Clang specific warning go here
|
||||
if ( CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
|
||||
@@ -308,8 +298,7 @@ if (NOT NO_SELFTEST)
|
||||
target_compile_options( SelfTest PRIVATE -Wweak-vtables -Wexit-time-destructors -Wglobal-constructors -Wmissing-noreturn )
|
||||
endif()
|
||||
if ( CMAKE_CXX_COMPILER_ID MATCHES "MSVC" )
|
||||
target_compile_options( SelfTest PRIVATE /W4 /w44265 /WX )
|
||||
target_compile_options( Benchmark PRIVATE /W4 )
|
||||
target_compile_options( SelfTest PRIVATE /W4 /w44265 /WX /w44061 /w44062 )
|
||||
endif()
|
||||
|
||||
|
||||
@@ -330,3 +319,17 @@ endif() # !NO_SELFTEST
|
||||
|
||||
|
||||
install(DIRECTORY "single_include/" DESTINATION "include/catch")
|
||||
|
||||
## Provide some pkg-config integration
|
||||
# Don't bother on Windows
|
||||
if(NOT WIN32 OR NOT CMAKE_HOST_SYSTEM_NAME MATCHES Windows)
|
||||
|
||||
set(PKGCONFIG_INSTALL_DIR
|
||||
"${CMAKE_INSTALL_PREFIX}/share/pkgconfig"
|
||||
CACHE PATH "Path where catch.pc is installed"
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/catch.pc.in ${CMAKE_CURRENT_BINARY_DIR}/catch.pc @ONLY)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/catch.pc DESTINATION ${PKGCONFIG_INSTALL_DIR})
|
||||
|
||||
endif()
|
||||
|
@@ -5,7 +5,7 @@
|
||||
[](https://travis-ci.org/philsquared/Catch?branch=catch2)
|
||||
[](https://ci.appveyor.com/project/philsquared/catch/branch/catch2)
|
||||
|
||||
<a href="https://github.com/philsquared/Catch/releases/download/v2.0.0-develop.1/catch.hpp">The latest version of the single header can be downloaded directly using this link</a>
|
||||
<a href="https://github.com/philsquared/Catch/releases/download/v2.0.0-develop.6/catch.hpp">The latest version of the single header can be downloaded directly using this link</a>
|
||||
|
||||
## What's the Catch?
|
||||
|
||||
|
9
catch.pc.in
Normal file
9
catch.pc.in
Normal file
@@ -0,0 +1,9 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
|
||||
Name: Catch
|
||||
Description: Testing library for C++
|
||||
Requires:
|
||||
Version: @CATCH_VERSION_NUMBER@
|
||||
Libs:
|
||||
Cflags: -I${prefix}/@INCLUDE_INSTALL_DIR@/include
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
||||
|
||||
class CatchConan(ConanFile):
|
||||
name = "Catch"
|
||||
version = "2.0.0-develop.4"
|
||||
version = "2.0.0-develop.6"
|
||||
description = "A modern, C++-native, header-only, framework for unit-tests, TDD and BDD"
|
||||
author = "philsquared"
|
||||
generators = "cmake"
|
||||
@@ -14,3 +14,6 @@ class CatchConan(ConanFile):
|
||||
|
||||
def package(self):
|
||||
self.copy(pattern="catch.hpp", src="single_include", dst="include")
|
||||
|
||||
def package_id(self):
|
||||
self.info.header_only()
|
||||
|
@@ -36,6 +36,8 @@
|
||||
# -- adds fixture class name to the test name #
|
||||
# PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) #
|
||||
# -- adds cmake target name to the test name #
|
||||
# PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) #
|
||||
# -- causes CMake to rerun when file with tests changes so that new tests will be discovered #
|
||||
# #
|
||||
#==================================================================================================#
|
||||
|
||||
@@ -45,6 +47,7 @@ option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OF
|
||||
option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF)
|
||||
option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON)
|
||||
option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON)
|
||||
option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF)
|
||||
|
||||
function(PrintDebugMessage)
|
||||
if(PARSE_CATCH_TESTS_VERBOSE)
|
||||
@@ -85,6 +88,15 @@ function(ParseFile SourceFile TestTarget)
|
||||
# Find definition of test names
|
||||
string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^\)]+\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}")
|
||||
|
||||
if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)
|
||||
PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property")
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}
|
||||
)
|
||||
endif()
|
||||
|
||||
foreach(TestName ${Tests})
|
||||
# Strip newlines
|
||||
string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}")
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<a id="top"></a>
|
||||
# Assertion Macros
|
||||
|
||||
**Contents**
|
||||
[Natural Expressions](#natural-expressions)
|
||||
[Exceptions](#exceptions)
|
||||
[Matcher expressions](#matcher-expressions)
|
||||
[Thread Safety](#thread-safety)
|
||||
**Contents**<br>
|
||||
[Natural Expressions](#natural-expressions)<br>
|
||||
[Exceptions](#exceptions)<br>
|
||||
[Matcher expressions](#matcher-expressions)<br>
|
||||
[Thread Safety](#thread-safety)<br>
|
||||
|
||||
Most test frameworks have a large collection of assertion macros to capture all possible conditional forms (```_EQUALS```, ```_NOTEQUALS```, ```_GREATER_THAN``` etc).
|
||||
|
||||
|
@@ -16,7 +16,7 @@ The XML Reporter writes in an XML format that is specific to Catch.
|
||||
|
||||
The advantage of this format is that it corresponds well to the way Catch works (especially the more unusual features, such as nested sections) and is a fully streaming format - that is it writes output as it goes, without having to store up all its results before it can start writing.
|
||||
|
||||
The disadvantage is that, being specific to Catch, no existing build servers understand the format natively. It can be used as input to an XSLT transformation that could covert it to, say, HTML - although this loses the streaming advantage, of course.
|
||||
The disadvantage is that, being specific to Catch, no existing build servers understand the format natively. It can be used as input to an XSLT transformation that could convert it to, say, HTML - although this loses the streaming advantage, of course.
|
||||
|
||||
### JUnit Reporter
|
||||
```-r junit```
|
||||
|
@@ -1,29 +1,29 @@
|
||||
<a id="top"></a>
|
||||
# Command line
|
||||
|
||||
**Contents**
|
||||
[Specifying which tests to run](#specifying-which-tests-to-run)
|
||||
[Choosing a reporter to use](#choosing-a-reporter-to-use)
|
||||
[Breaking into the debugger](#breaking-into-the-debugger)
|
||||
[Showing results for successful tests](#showing-results-for-successful-tests)
|
||||
[Aborting after a certain number of failures](#aborting-after-a-certain-number-of-failures)
|
||||
[Listing available tests, tags or reporters](#listing-available-tests-tags-or-reporters)
|
||||
[Sending output to a file](#sending-output-to-a-file)
|
||||
[Naming a test run](#naming-a-test-run)
|
||||
[Eliding assertions expected to throw](#eliding-assertions-expected-to-throw)
|
||||
[Make whitespace visible](#make-whitespace-visible)
|
||||
[Warnings](#warnings)
|
||||
[Reporting timings](#reporting-timings)
|
||||
[Load test names to run from a file](#load-test-names-to-run-from-a-file)
|
||||
[Just test names](#just-test-names)
|
||||
[Specify the order test cases are run](#specify-the-order-test-cases-are-run)
|
||||
[Specify a seed for the Random Number Generator](#specify-a-seed-for-the-random-number-generator)
|
||||
[Identify framework and version according to the libIdentify standard](#identify-framework-and-version-according-to-the-libidentify-standard)
|
||||
[Wait for key before continuing](#wait-for-key-before-continuing)
|
||||
[Specify multiples of clock resolution to run benchmarks for](#specify-multiples-of-clock-resolution-to-run-benchmarks-for)
|
||||
[Usage](#usage)
|
||||
[Specify the section to run](#specify-the-section-to-run)
|
||||
[Filenames as tags](#filenames-as-tags)
|
||||
**Contents**<br>
|
||||
[Specifying which tests to run](#specifying-which-tests-to-run)<br>
|
||||
[Choosing a reporter to use](#choosing-a-reporter-to-use)<br>
|
||||
[Breaking into the debugger](#breaking-into-the-debugger)<br>
|
||||
[Showing results for successful tests](#showing-results-for-successful-tests)<br>
|
||||
[Aborting after a certain number of failures](#aborting-after-a-certain-number-of-failures)<br>
|
||||
[Listing available tests, tags or reporters](#listing-available-tests-tags-or-reporters)<br>
|
||||
[Sending output to a file](#sending-output-to-a-file)<br>
|
||||
[Naming a test run](#naming-a-test-run)<br>
|
||||
[Eliding assertions expected to throw](#eliding-assertions-expected-to-throw)<br>
|
||||
[Make whitespace visible](#make-whitespace-visible)<br>
|
||||
[Warnings](#warnings)<br>
|
||||
[Reporting timings](#reporting-timings)<br>
|
||||
[Load test names to run from a file](#load-test-names-to-run-from-a-file)<br>
|
||||
[Just test names](#just-test-names)<br>
|
||||
[Specify the order test cases are run](#specify-the-order-test-cases-are-run)<br>
|
||||
[Specify a seed for the Random Number Generator](#specify-a-seed-for-the-random-number-generator)<br>
|
||||
[Identify framework and version according to the libIdentify standard](#identify-framework-and-version-according-to-the-libidentify-standard)<br>
|
||||
[Wait for key before continuing](#wait-for-key-before-continuing)<br>
|
||||
[Specify multiples of clock resolution to run benchmarks for](#specify-multiples-of-clock-resolution-to-run-benchmarks-for)<br>
|
||||
[Usage](#usage)<br>
|
||||
[Specify the section to run](#specify-the-section-to-run)<br>
|
||||
[Filenames as tags](#filenames-as-tags)<br>
|
||||
|
||||
Catch works quite nicely without any command line options at all - but for those times when you want greater control the following options are available.
|
||||
Click one of the followings links to take you straight to that option - or scroll on to browse the available options.
|
||||
|
@@ -1,14 +1,15 @@
|
||||
<a id="top"></a>
|
||||
# Compile-time configuration
|
||||
|
||||
**Contents**
|
||||
[main()/ implementation](#main-implementation)
|
||||
[Prefixing Catch macros](#prefixing-catch-macros)
|
||||
[Terminal colour](#terminal-colour)
|
||||
[Console width](#console-width)
|
||||
[stdout](#stdout)
|
||||
[Other toggles](#other-toggles)
|
||||
[Windows header clutter](#windows-header-clutter)
|
||||
**Contents**<br>
|
||||
[main()/ implementation](#main-implementation)<br>
|
||||
[Prefixing Catch macros](#prefixing-catch-macros)<br>
|
||||
[Terminal colour](#terminal-colour)<br>
|
||||
[Console width](#console-width)<br>
|
||||
[stdout](#stdout)<br>
|
||||
[Other toggles](#other-toggles)<br>
|
||||
[Windows header clutter](#windows-header-clutter)<br>
|
||||
[Enabling stringification](#enabling-stringification)<br>
|
||||
|
||||
Catch is designed to "just work" as much as possible. For most people the only configuration needed is telling Catch which source file should host all the implementation code (```CATCH_CONFIG_MAIN```).
|
||||
|
||||
@@ -105,7 +106,7 @@ When `CATCH_CONFIG_DISABLE_MATCHERS` is defined, all mentions of Catch's Matcher
|
||||
_Note: If you define `CATCH_CONFIG_DISABLE_MATCHERS` in the same file as Catch's main is implemented, your test executable will fail to link if you use Matchers anywhere._
|
||||
|
||||
### `CATCH_CONFIG_DISABLE_STRINGIFICATION`
|
||||
This toggle enables a workaround for VS 2017 bug. For details see [known limitations](limitations.md#Visual Studio 2017 -- raw string literal in assert fails to compile)
|
||||
This toggle enables a workaround for VS 2017 bug. For details see [known limitations](limitations.md#visual-studio-2017----raw-string-literal-in-assert-fails-to-compile).
|
||||
|
||||
### `CATCH_CONFIG_DISABLE`
|
||||
This toggle removes most of Catch from given file. This means that `TEST_CASE`s are not registered and assertions are turned into no-ops. Useful for keeping tests within implementation files (ie for functions with internal linkage), instead of in external files.
|
||||
@@ -121,6 +122,17 @@ On Windows Catch includes `windows.h`. To minimize global namespace clutter in t
|
||||
CATCH_CONFIG_NO_NOMINMAX // Stops Catch from using NOMINMAX macro
|
||||
CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN // Stops Catch from using WIN32_LEAN_AND_MEAN macro
|
||||
|
||||
|
||||
## Enabling stringification
|
||||
|
||||
By default, Catch does not stringify some types from the standard library. This is done to avoid dragging in various standard library headers by default. However, Catch does contain these and can be configured to provide them, using these macros:
|
||||
|
||||
CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER // Provide StringMaker specialization for std::pair
|
||||
CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER // Provide StringMaker specialization for std::tuple
|
||||
CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER // Provide StringMaker specialization for std::chrono::duration, std::chrono::timepoint
|
||||
CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS // Defines all of the above
|
||||
|
||||
|
||||
---
|
||||
|
||||
[Home](Readme.md#top)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<a id="top"></a>
|
||||
# Open Source projects using Catch
|
||||
|
||||
Catch is great for open source. With it's [liberal license](../LICENSE.txt) and single-header, dependency-free, distribution
|
||||
Catch is great for open source. With its [liberal license](../LICENSE.txt) and single-header, dependency-free, distribution
|
||||
it's easy to just drop the header into your project and start writing tests - what's not to like?
|
||||
|
||||
As a result Catch is now being used in many Open Source projects, including some quite well known ones.
|
||||
|
@@ -19,8 +19,12 @@
|
||||
* This is most noticeable in `CHECK(throws())`, which would previously report failure, properly stringify the exception and continue. Now it will report failure and stop executing current section.
|
||||
* Removed deprecated matcher utility functions `Not`, `AllOf` and `AnyOf`.
|
||||
* They are superseded by operators `!`, `&&` and `||`, which are natural and do not have limited arity
|
||||
* No longer accept non-const comparison operators
|
||||
|
||||
* Removed support for non-const comparison operators
|
||||
* Non-const comparison operators are an abomination that should not exist
|
||||
* They were breaking support for comparing function to function pointer
|
||||
* `std::pair` and `std::tuple` are no longer stringified by default
|
||||
* This is done to avoid dragging in `<tuple>` and `<utility>` headers in common path
|
||||
* Their stringification can be enabled per-file via new configuration macros
|
||||
|
||||
## Improvements
|
||||
* Reporters and Listeners can be defined in files different from the main file
|
||||
@@ -48,13 +52,25 @@
|
||||
* Exception translators are not registered
|
||||
* Reporters are not registered
|
||||
* Listeners are not registered
|
||||
* More warnings are silenced
|
||||
* Don't use console colour if running in XCode
|
||||
* Reporters/Listeners are now notified of fatal errors
|
||||
* This means specific signals or structured exceptions
|
||||
* The Reporter/Listener interface provides default, empty, implementation to preserve backward compatibility
|
||||
* Stringification of `std::chrono::duration` and `std::chrono::time_point` is now supported
|
||||
* Needs to be enabled by a per-file compile time configuration option
|
||||
* Add `pkg-config` support to CMake install command
|
||||
|
||||
|
||||
## Fixes
|
||||
* Don't use console colour if running in XCode
|
||||
* Explicit constructor in reporter base class
|
||||
* Many fixes for building in Objective-C context
|
||||
* Do not use SEH and console api under UWP
|
||||
* Swept out `-Wweak-vtables`, `-Wexit-time-destructors`, `-Wglobal-constructors` warnings
|
||||
* Compilation for Universal Windows Platform (UWP) is supported
|
||||
* SEH handling and colorized output are disabled when compiling for UWP
|
||||
* Implemented a workaround for `std::uncaught_exception` issues in libcxxrt
|
||||
* These issues caused incorrect section traversals
|
||||
* The workaround is only partial, user's test can still trigger the issue by using `throw;` to rethrow an exception
|
||||
* Suppressed C4061 warning under MSVC
|
||||
|
||||
|
||||
## Internal changes
|
||||
* The development version now uses .cpp files instead of header files containing implementation.
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<a id="top"></a>
|
||||
# Why do my tests take so long to compile?
|
||||
|
||||
**Contents**
|
||||
[Short answer](#short-answer)
|
||||
[Long answer](#long-answer)
|
||||
[Practical example](#practical-example)
|
||||
[Other possible solutions](#other-possible-solutions)
|
||||
**Contents**<br>
|
||||
[Short answer](#short-answer)<br>
|
||||
[Long answer](#long-answer)<br>
|
||||
[Practical example](#practical-example)<br>
|
||||
[Other possible solutions](#other-possible-solutions)<br>
|
||||
|
||||
Several people have reported that test code written with Catch takes much longer to compile than they would expect. Why is that?
|
||||
|
||||
|
@@ -39,13 +39,13 @@ All tag names beginning with non-alphanumeric characters are reserved by Catch.
|
||||
|
||||
* `[!throws]` - lets Catch know that this test is likely to throw an exception even if successful. This causes the test to be excluded when running with `-e` or `--nothrow`.
|
||||
|
||||
* `[!mayfail]` - doesn't fail the test if any given assertion fails (but still reports it). This can be useful to flag a work-in-progress, or a known issue that you don't want to immediately fix but still want to track in the your tests.
|
||||
* `[!mayfail]` - doesn't fail the test if any given assertion fails (but still reports it). This can be useful to flag a work-in-progress, or a known issue that you don't want to immediately fix but still want to track in your tests.
|
||||
|
||||
* `[!shouldfail]` - like `[!mayfail]` but *fails* the test if it *passes*. This can be useful if you want to be notified of accidental, or third-party, fixes.
|
||||
|
||||
* `[!nonportable]` - Indicates that behaviour may vary between platforms or compilers.
|
||||
|
||||
* `[#<filename>]` - running with `-#` or `--filenames-as-tags` causes Catch to add the filename, prefixed with `#` (and with any extension stripped) as a tag. e.g. tests in testfile.cpp would all be tagged `[#testfile]`.
|
||||
* `[#<filename>]` - running with `-#` or `--filenames-as-tags` causes Catch to add the filename, prefixed with `#` (and with any extension stripped), as a tag to all contained tests, e.g. tests in testfile.cpp would all be tagged `[#testfile]`.
|
||||
|
||||
* `[@<alias>]` - tag aliases all begin with `@` (see below).
|
||||
|
||||
@@ -53,7 +53,7 @@ All tag names beginning with non-alphanumeric characters are reserved by Catch.
|
||||
|
||||
## Tag aliases
|
||||
|
||||
Between tag expressions and wildcarded test names (as well as combinations of the two) quite complex patterns can be constructed to direct which test cases are run. If a complex pattern is used often it is convenient to be able to create an alias for the expression. this can be done, in code, using the following form:
|
||||
Between tag expressions and wildcarded test names (as well as combinations of the two) quite complex patterns can be constructed to direct which test cases are run. If a complex pattern is used often it is convenient to be able to create an alias for the expression. This can be done, in code, using the following form:
|
||||
|
||||
CATCH_REGISTER_TAG_ALIAS( <alias string>, <tag expression> )
|
||||
|
||||
|
@@ -1,14 +1,14 @@
|
||||
<a id="top"></a>
|
||||
# Tutorial
|
||||
|
||||
**Contents**
|
||||
[Getting Catch](#getting-catch)
|
||||
[Where to put it?](#where-to-put-it)
|
||||
[Writing tests](#writing-tests)
|
||||
[Test cases and sections](#test-cases-and-sections)
|
||||
[BDD-Style](#bdd-style)
|
||||
[Scaling up](#scaling-up)
|
||||
[Next steps](#next-steps)
|
||||
**Contents**<br>
|
||||
[Getting Catch](#getting-catch)<br>
|
||||
[Where to put it?](#where-to-put-it)<br>
|
||||
[Writing tests](#writing-tests)<br>
|
||||
[Test cases and sections](#test-cases-and-sections)<br>
|
||||
[BDD-Style](#bdd-style)<br>
|
||||
[Scaling up](#scaling-up)<br>
|
||||
[Next steps](#next-steps)<br>
|
||||
|
||||
## Getting Catch
|
||||
|
||||
|
520
include/external/clara.hpp
vendored
520
include/external/clara.hpp
vendored
@@ -1,14 +1,16 @@
|
||||
// v1.0
|
||||
// v1.0-develop.2
|
||||
// See https://github.com/philsquared/Clara
|
||||
|
||||
#ifndef CATCH_CLARA_HPP_INCLUDED
|
||||
#define CATCH_CLARA_HPP_INCLUDED
|
||||
|
||||
|
||||
#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH
|
||||
#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80
|
||||
#endif
|
||||
|
||||
#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
|
||||
#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH
|
||||
#endif
|
||||
|
||||
// ----------- #included from clara_textflow.hpp -----------
|
||||
|
||||
@@ -24,7 +26,6 @@
|
||||
#ifndef CATCH_CLARA_TEXTFLOW_HPP_INCLUDED
|
||||
#define CATCH_CLARA_TEXTFLOW_HPP_INCLUDED
|
||||
|
||||
|
||||
#include <cassert>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
@@ -350,9 +351,8 @@ namespace Catch { namespace clara { namespace TextFlow {
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
|
||||
#define CLARA_PLATFORM_WINDOWS
|
||||
|
||||
#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
|
||||
#define CATCH_PLATFORM_WINDOWS
|
||||
#endif
|
||||
|
||||
namespace Catch { namespace clara {
|
||||
@@ -360,15 +360,15 @@ namespace detail {
|
||||
|
||||
// Traits for extracting arg and return type of lambdas (for single argument lambdas)
|
||||
template<typename L>
|
||||
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};
|
||||
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};
|
||||
|
||||
template<typename ClassT, typename ReturnT, typename... Args>
|
||||
struct UnaryLambdaTraits<ReturnT(ClassT::*)(Args...) const> {
|
||||
struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {
|
||||
static const bool isValid = false;
|
||||
};
|
||||
|
||||
template<typename ClassT, typename ReturnT, typename ArgT>
|
||||
struct UnaryLambdaTraits<ReturnT(ClassT::*)(ArgT) const> {
|
||||
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;
|
||||
@@ -383,13 +383,13 @@ namespace detail {
|
||||
std::vector<std::string> m_args;
|
||||
|
||||
public:
|
||||
Args(int argc, char *argv[]) {
|
||||
Args( int argc, char *argv[] ) {
|
||||
m_exeName = argv[0];
|
||||
for (int i = 1; i < argc; ++i)
|
||||
m_args.push_back(argv[i]);
|
||||
for( int i = 1; i < argc; ++i )
|
||||
m_args.push_back( argv[i] );
|
||||
}
|
||||
|
||||
Args(std::initializer_list<std::string> args)
|
||||
Args( std::initializer_list<std::string> args )
|
||||
: m_exeName( *args.begin() ),
|
||||
m_args( args.begin()+1, args.end() )
|
||||
{}
|
||||
@@ -409,6 +409,14 @@ namespace detail {
|
||||
std::string token;
|
||||
};
|
||||
|
||||
inline auto isOptPrefix( char c ) -> bool {
|
||||
return c == '-'
|
||||
#ifdef CATCH_PLATFORM_WINDOWS
|
||||
|| c == '/'
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
|
||||
class TokenStream {
|
||||
using Iterator = std::vector<std::string>::const_iterator;
|
||||
@@ -417,40 +425,40 @@ namespace detail {
|
||||
std::vector<Token> m_tokenBuffer;
|
||||
|
||||
void loadBuffer() {
|
||||
m_tokenBuffer.resize(0);
|
||||
m_tokenBuffer.resize( 0 );
|
||||
|
||||
// Skip any empty strings
|
||||
while (it != itEnd && it->empty())
|
||||
while( it != itEnd && it->empty() )
|
||||
++it;
|
||||
|
||||
if (it != itEnd) {
|
||||
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)});
|
||||
if( isOptPrefix( 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) {
|
||||
if( next[1] != '-' && next.size() > 2 ) {
|
||||
std::string opt = "- ";
|
||||
for (size_t i = 1; i < next.size(); ++i) {
|
||||
for( size_t i = 1; i < next.size(); ++i ) {
|
||||
opt[1] = next[i];
|
||||
m_tokenBuffer.push_back({TokenType::Option, opt});
|
||||
m_tokenBuffer.push_back( { TokenType::Option, opt } );
|
||||
}
|
||||
} else {
|
||||
m_tokenBuffer.push_back({TokenType::Option, next});
|
||||
m_tokenBuffer.push_back( { TokenType::Option, next } );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_tokenBuffer.push_back({TokenType::Argument, next});
|
||||
m_tokenBuffer.push_back( { TokenType::Argument, next } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {}
|
||||
explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}
|
||||
|
||||
TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) {
|
||||
TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {
|
||||
loadBuffer();
|
||||
}
|
||||
|
||||
@@ -461,20 +469,20 @@ namespace detail {
|
||||
auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }
|
||||
|
||||
auto operator*() const -> Token {
|
||||
assert(!m_tokenBuffer.empty());
|
||||
assert( !m_tokenBuffer.empty() );
|
||||
return m_tokenBuffer.front();
|
||||
}
|
||||
|
||||
auto operator->() const -> Token const * {
|
||||
assert(!m_tokenBuffer.empty());
|
||||
assert( !m_tokenBuffer.empty() );
|
||||
return &m_tokenBuffer.front();
|
||||
}
|
||||
|
||||
auto operator++() -> TokenStream & {
|
||||
if (m_tokenBuffer.size() >= 2) {
|
||||
m_tokenBuffer.erase(m_tokenBuffer.begin());
|
||||
if( m_tokenBuffer.size() >= 2 ) {
|
||||
m_tokenBuffer.erase( m_tokenBuffer.begin() );
|
||||
} else {
|
||||
if (it != itEnd)
|
||||
if( it != itEnd )
|
||||
++it;
|
||||
loadBuffer();
|
||||
}
|
||||
@@ -490,7 +498,7 @@ namespace detail {
|
||||
};
|
||||
|
||||
protected:
|
||||
ResultBase(Type type) : m_type(type) {}
|
||||
ResultBase( Type type ) : m_type( type ) {}
|
||||
virtual ~ResultBase() = default;
|
||||
|
||||
virtual void enforceOk() const = 0;
|
||||
@@ -507,28 +515,28 @@ namespace detail {
|
||||
}
|
||||
|
||||
protected:
|
||||
ResultValueBase(Type type) : ResultBase(type) {}
|
||||
ResultValueBase( Type type ) : ResultBase( type ) {}
|
||||
|
||||
ResultValueBase(ResultValueBase const &other) : ResultBase(other) {
|
||||
if (m_type == ResultBase::Ok)
|
||||
new(&m_value) T(other.m_value);
|
||||
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);
|
||||
ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {
|
||||
new( &m_value ) T( value );
|
||||
}
|
||||
|
||||
auto operator=(ResultValueBase const &other) -> ResultValueBase & {
|
||||
if (m_type == ResultBase::Ok)
|
||||
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);
|
||||
if( m_type == ResultBase::Ok )
|
||||
new( &m_value ) T( other.m_value );
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ResultValueBase() {
|
||||
if (m_type == Ok)
|
||||
if( m_type == Ok )
|
||||
m_value.~T();
|
||||
}
|
||||
|
||||
@@ -547,37 +555,31 @@ namespace detail {
|
||||
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);
|
||||
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};
|
||||
}
|
||||
static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }
|
||||
static auto ok() -> BasicResult { return { ResultBase::Ok }; }
|
||||
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) {
|
||||
switch( m_type ) {
|
||||
case ResultBase::LogicError:
|
||||
throw std::logic_error(m_errorMessage);
|
||||
throw std::logic_error( m_errorMessage );
|
||||
case ResultBase::RuntimeError:
|
||||
throw std::runtime_error(m_errorMessage);
|
||||
throw std::runtime_error( m_errorMessage );
|
||||
case ResultBase::Ok:
|
||||
break;
|
||||
}
|
||||
@@ -585,10 +587,11 @@ namespace detail {
|
||||
|
||||
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);
|
||||
BasicResult( ResultBase::Type type, std::string const &message )
|
||||
: ResultValueBase<T>(type),
|
||||
m_errorMessage(message)
|
||||
{
|
||||
assert( m_type != ResultBase::Ok );
|
||||
}
|
||||
|
||||
using ResultValueBase<T>::ResultValueBase;
|
||||
@@ -602,12 +605,12 @@ namespace detail {
|
||||
class ParseState {
|
||||
public:
|
||||
|
||||
ParseState(ParseResultType type, TokenStream const &remainingTokens)
|
||||
: m_type(type),
|
||||
m_remainingTokens(remainingTokens) {}
|
||||
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:
|
||||
@@ -625,69 +628,62 @@ namespace detail {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline auto convertInto(std::string const &source, T& target) -> ParserResult {
|
||||
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");
|
||||
if( ss.fail() )
|
||||
return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" );
|
||||
else
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
inline auto convertInto(std::string const &source, std::string& target) -> ParserResult {
|
||||
inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {
|
||||
target = source;
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
inline auto convertInto(std::string const &source, bool &target) -> ParserResult {
|
||||
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) ); } );
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
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");
|
||||
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 {
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
bool flag;
|
||||
auto result = convertInto(arg, flag);
|
||||
if (result)
|
||||
setFlag(flag);
|
||||
auto result = convertInto( arg, flag );
|
||||
if( result )
|
||||
setFlag( flag );
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -696,10 +692,10 @@ namespace detail {
|
||||
struct BoundRef : BoundValueRefBase {
|
||||
T &m_ref;
|
||||
|
||||
explicit BoundRef(T &ref) : m_ref(ref) {}
|
||||
explicit BoundRef( T &ref ) : m_ref( ref ) {}
|
||||
|
||||
auto setValue(std::string const &arg) -> ParserResult override {
|
||||
return convertInto(arg, m_ref);
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
return convertInto( arg, m_ref );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -707,15 +703,15 @@ namespace detail {
|
||||
struct BoundRef<std::vector<T>> : BoundValueRefBase {
|
||||
std::vector<T> &m_ref;
|
||||
|
||||
explicit BoundRef(std::vector<T> &ref) : m_ref(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 {
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
T temp;
|
||||
auto result = convertInto(arg, temp);
|
||||
if (result)
|
||||
m_ref.push_back(temp);
|
||||
auto result = convertInto( arg, temp );
|
||||
if( result )
|
||||
m_ref.push_back( temp );
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -723,40 +719,40 @@ namespace detail {
|
||||
struct BoundFlagRef : BoundFlagRefBase {
|
||||
bool &m_ref;
|
||||
|
||||
explicit BoundFlagRef(bool &ref) : m_ref(ref) {}
|
||||
explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}
|
||||
|
||||
auto setFlag(bool flag) -> ParserResult override {
|
||||
auto setFlag( bool flag ) -> ParserResult override {
|
||||
m_ref = flag;
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
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");
|
||||
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);
|
||||
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);
|
||||
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 {
|
||||
inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
|
||||
ArgType temp;
|
||||
auto result = convertInto(arg, temp);
|
||||
auto result = convertInto( arg, temp );
|
||||
return !result
|
||||
? result
|
||||
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);
|
||||
? result
|
||||
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );
|
||||
};
|
||||
|
||||
|
||||
@@ -765,10 +761,10 @@ namespace detail {
|
||||
L m_lambda;
|
||||
|
||||
static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
|
||||
explicit BoundLambda(L const &lambda) : m_lambda(lambda) {}
|
||||
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);
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -779,16 +775,14 @@ namespace detail {
|
||||
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) {}
|
||||
explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
|
||||
|
||||
auto setFlag(bool flag) -> ParserResult override {
|
||||
return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(m_lambda, flag);
|
||||
auto setFlag( bool flag ) -> ParserResult override {
|
||||
return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );
|
||||
}
|
||||
};
|
||||
|
||||
enum class Optionality {
|
||||
Optional, Required
|
||||
};
|
||||
enum class Optionality { Optional, Required };
|
||||
|
||||
struct Parser;
|
||||
|
||||
@@ -799,8 +793,8 @@ namespace detail {
|
||||
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));
|
||||
auto parse( Args const &args ) const -> InternalParseResult {
|
||||
return parse( args.exeName(), TokenStream( args ) );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -808,7 +802,7 @@ namespace detail {
|
||||
class ComposableParserImpl : public ParserBase {
|
||||
public:
|
||||
template<typename T>
|
||||
auto operator+(T const &other) const -> Parser;
|
||||
auto operator|( T const &other ) const -> Parser;
|
||||
};
|
||||
|
||||
// Common code and state for Args and Opts
|
||||
@@ -820,17 +814,22 @@ namespace detail {
|
||||
std::string m_hint;
|
||||
std::string m_description;
|
||||
|
||||
explicit ParserRefImpl(std::shared_ptr<BoundRefBase> const &ref) : m_ref(ref) {}
|
||||
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) {}
|
||||
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) {}
|
||||
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 & {
|
||||
auto operator()( std::string const &description ) -> DerivedT & {
|
||||
m_description = description;
|
||||
return static_cast<DerivedT &>( *this );
|
||||
}
|
||||
@@ -850,7 +849,7 @@ namespace detail {
|
||||
}
|
||||
|
||||
auto cardinality() const -> size_t override {
|
||||
if (m_ref->isContainer())
|
||||
if( m_ref->isContainer() )
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
@@ -865,13 +864,13 @@ namespace detail {
|
||||
|
||||
template<typename LambdaT>
|
||||
static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundRefBase> {
|
||||
return std::make_shared<BoundLambda<LambdaT>>(lambda);
|
||||
return std::make_shared<BoundLambda<LambdaT>>( lambda) ;
|
||||
}
|
||||
|
||||
public:
|
||||
ExeName() : m_name(std::make_shared<std::string>("<executable>")) {}
|
||||
ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {}
|
||||
|
||||
explicit ExeName(std::string &ref) : ExeName() {
|
||||
explicit ExeName( std::string &ref ) : ExeName() {
|
||||
m_ref = std::make_shared<BoundRef<std::string>>( ref );
|
||||
}
|
||||
|
||||
@@ -882,14 +881,14 @@ namespace detail {
|
||||
|
||||
// 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));
|
||||
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)
|
||||
auto filename = ( lastSlash == std::string::npos )
|
||||
? newName
|
||||
: newName.substr( lastSlash+1 );
|
||||
|
||||
@@ -905,28 +904,30 @@ 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);
|
||||
if( !validationResult )
|
||||
return InternalParseResult( validationResult );
|
||||
|
||||
auto remainingTokens = tokens;
|
||||
auto const &token = *remainingTokens;
|
||||
if (token.type != TokenType::Argument)
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, 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);
|
||||
auto result = m_ref->setValue( remainingTokens->token );
|
||||
if( !result )
|
||||
return InternalParseResult( result );
|
||||
else
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));
|
||||
return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
|
||||
}
|
||||
};
|
||||
|
||||
inline auto normaliseOpt(std::string const &optName) -> std::string {
|
||||
if (optName[0] == '/')
|
||||
return "-" + optName.substr(1);
|
||||
inline auto normaliseOpt( std::string const &optName ) -> std::string {
|
||||
#ifdef CATCH_PLATFORM_WINDOWS
|
||||
if( optName[0] == '/' )
|
||||
return "-" + optName.substr( 1 );
|
||||
else
|
||||
#endif
|
||||
return optName;
|
||||
}
|
||||
|
||||
@@ -936,9 +937,9 @@ namespace detail {
|
||||
|
||||
public:
|
||||
template<typename LambdaT>
|
||||
explicit Opt( LambdaT const &ref ) : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}
|
||||
explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}
|
||||
|
||||
explicit Opt( bool &ref ) : ParserRefImpl(std::make_shared<BoundFlagRef>(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 ) {}
|
||||
@@ -946,34 +947,30 @@ namespace detail {
|
||||
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);
|
||||
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) {
|
||||
for( auto const &opt : m_optNames ) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
oss << ", ";
|
||||
oss << opt;
|
||||
}
|
||||
if (!m_hint.empty())
|
||||
if( !m_hint.empty() )
|
||||
oss << " <" << m_hint << ">";
|
||||
return {{oss.str(), m_description}};
|
||||
return { { oss.str(), m_description } };
|
||||
}
|
||||
|
||||
auto isMatch(std::string const &optToken) const -> bool {
|
||||
#ifdef CLARA_PLATFORM_WINDOWS
|
||||
auto isMatch( std::string const &optToken ) const -> bool {
|
||||
auto normalisedToken = normaliseOpt( optToken );
|
||||
#else
|
||||
auto const &normalisedToken = optToken;
|
||||
#endif
|
||||
for (auto const &name : m_optNames) {
|
||||
if (normaliseOpt(name) == normalisedToken)
|
||||
for( auto const &name : m_optNames ) {
|
||||
if( normaliseOpt( name ) == normalisedToken )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -983,46 +980,51 @@ namespace detail {
|
||||
|
||||
auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
|
||||
auto validationResult = validate();
|
||||
if (!validationResult)
|
||||
return InternalParseResult(validationResult);
|
||||
if( !validationResult )
|
||||
return InternalParseResult( validationResult );
|
||||
|
||||
auto remainingTokens = tokens;
|
||||
if (remainingTokens && remainingTokens->type == TokenType::Option) {
|
||||
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));
|
||||
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);
|
||||
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));
|
||||
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::Matched, ++remainingTokens ) );
|
||||
}
|
||||
}
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, 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 '/'");
|
||||
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" );
|
||||
#ifdef CATCH_PLATFORM_WINDOWS
|
||||
if( name[0] != '-' && name[0] != '/' )
|
||||
return Result::logicError( "Option name must begin with '-' or '/'" );
|
||||
#else
|
||||
if( name[0] != '-' )
|
||||
return Result::logicError( "Option name must begin with '-'" );
|
||||
#endif
|
||||
}
|
||||
return ParserRefImpl::validate();
|
||||
}
|
||||
@@ -1031,11 +1033,11 @@ namespace detail {
|
||||
struct Help : Opt {
|
||||
Help( bool &showHelpFlag )
|
||||
: Opt([&]( bool flag ) {
|
||||
showHelpFlag = flag;
|
||||
return ParserResult::ok(ParseResultType::ShortCircuitAll);
|
||||
showHelpFlag = flag;
|
||||
return ParserResult::ok( ParseResultType::ShortCircuitAll );
|
||||
})
|
||||
{
|
||||
static_cast<Opt &>(*this)
|
||||
static_cast<Opt &>( *this )
|
||||
("display usage information")
|
||||
["-?"]["-h"]["--help"]
|
||||
.optional();
|
||||
@@ -1049,61 +1051,61 @@ namespace detail {
|
||||
std::vector<Opt> m_options;
|
||||
std::vector<Arg> m_args;
|
||||
|
||||
auto operator+=(ExeName const &exeName) -> Parser & {
|
||||
auto operator|=( ExeName const &exeName ) -> Parser & {
|
||||
m_exeName = exeName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Arg const &arg) -> Parser & {
|
||||
auto operator|=( Arg const &arg ) -> Parser & {
|
||||
m_args.push_back(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Opt const &opt) -> Parser & {
|
||||
auto operator|=( Opt const &opt ) -> Parser & {
|
||||
m_options.push_back(opt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Parser const &other) -> Parser & {
|
||||
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 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());
|
||||
cols.insert( cols.end(), childCols.begin(), childCols.end() );
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
void writeToStream(std::ostream &os) const {
|
||||
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) {
|
||||
for( auto const &arg : m_args ) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
os << " ";
|
||||
if (arg.isOptional() && required) {
|
||||
if( arg.isOptional() && required ) {
|
||||
os << "[";
|
||||
required = false;
|
||||
}
|
||||
os << "<" << arg.hint() << ">";
|
||||
if (arg.cardinality() == 0)
|
||||
if( arg.cardinality() == 0 )
|
||||
os << " ... ";
|
||||
}
|
||||
if (!required)
|
||||
if( !required )
|
||||
os << "]";
|
||||
if (!m_options.empty())
|
||||
if( !m_options.empty() )
|
||||
os << " options";
|
||||
os << "\n\nwhere options are:" << std::endl;
|
||||
}
|
||||
@@ -1111,32 +1113,32 @@ namespace detail {
|
||||
auto rows = getHelpColumns();
|
||||
size_t consoleWidth = CATCH_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 )
|
||||
optWidth = (std::max)(optWidth, cols.left.size() + 2);
|
||||
|
||||
for (auto const &cols : rows) {
|
||||
for( auto const &cols : rows ) {
|
||||
auto row =
|
||||
TextFlow::Column(cols.left).width(optWidth).indent(2) +
|
||||
TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
|
||||
TextFlow::Spacer(4) +
|
||||
TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth);
|
||||
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);
|
||||
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) {
|
||||
for( auto const &opt : m_options ) {
|
||||
auto result = opt.validate();
|
||||
if (!result)
|
||||
if( !result )
|
||||
return result;
|
||||
}
|
||||
for (auto const &arg : m_args) {
|
||||
for( auto const &arg : m_args ) {
|
||||
auto result = arg.validate();
|
||||
if (!result)
|
||||
if( !result )
|
||||
return result;
|
||||
}
|
||||
return Result::ok();
|
||||
@@ -1144,47 +1146,47 @@ namespace detail {
|
||||
|
||||
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;
|
||||
auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
|
||||
|
||||
for (auto const &opt : m_options) {
|
||||
allParsers.push_back(&opt);
|
||||
if (!opt.isOptional())
|
||||
requiredParsers.insert(&opt);
|
||||
}
|
||||
struct ParserInfo {
|
||||
ParserBase const* parser = nullptr;
|
||||
size_t count = 0;
|
||||
};
|
||||
const size_t totalParsers = m_options.size() + m_args.size();
|
||||
assert( totalParsers < 512 );
|
||||
// ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
|
||||
ParserInfo parseInfos[512];
|
||||
|
||||
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);
|
||||
}
|
||||
{
|
||||
size_t i = 0;
|
||||
for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
|
||||
for (auto const &arg : m_args) parseInfos[i++].parser = &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;
|
||||
auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
|
||||
while( result.value().remainingTokens() ) {
|
||||
bool tokenParsed = false;
|
||||
|
||||
for( size_t i = 0; i < totalParsers; ++i ) {
|
||||
auto& parseInfo = parseInfos[i];
|
||||
if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
|
||||
result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
|
||||
if (!result)
|
||||
return result;
|
||||
if (result.value().type() != ParseResultType::NoMatch) {
|
||||
tokenParsed = true;
|
||||
++parseInfo.count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result || remainingTokenCount == result.value().remainingTokens().count())
|
||||
|
||||
if( result.value().type() == ParseResultType::ShortCircuitAll )
|
||||
return result;
|
||||
if( !tokenParsed )
|
||||
return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
|
||||
}
|
||||
// !TBD Check missing required options
|
||||
return result;
|
||||
@@ -1193,8 +1195,8 @@ namespace detail {
|
||||
|
||||
template<typename DerivedT>
|
||||
template<typename T>
|
||||
auto ComposableParserImpl<DerivedT>::operator+(T const &other) const -> Parser {
|
||||
return Parser() + static_cast<DerivedT const &>( *this ) + other;
|
||||
auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {
|
||||
return Parser() | static_cast<DerivedT const &>( *this ) | other;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
|
||||
double max(double lhs, double rhs) {
|
||||
double dmax(double lhs, double rhs) {
|
||||
if (lhs < rhs) {
|
||||
return rhs;
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@
|
||||
#ifndef TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
|
||||
#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED
|
||||
|
||||
#include "catch_enforce.h"
|
||||
#include "catch_tostring.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -17,7 +18,7 @@
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
|
||||
double max(double lhs, double rhs);
|
||||
double dmax(double lhs, double rhs);
|
||||
|
||||
class Approx {
|
||||
public:
|
||||
@@ -43,11 +44,12 @@ namespace Detail {
|
||||
friend bool operator == ( const T& lhs, Approx const& rhs ) {
|
||||
// Thanks to Richard Harris for his help refining this formula
|
||||
auto lhs_v = static_cast<double>(lhs);
|
||||
bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (max)(std::fabs(lhs_v), std::fabs(rhs.m_value)));
|
||||
bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale +
|
||||
dmax(std::fabs(lhs_v), std::fabs(rhs.m_value)));
|
||||
if (relativeOK) {
|
||||
return true;
|
||||
}
|
||||
return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin;
|
||||
return std::fabs(lhs_v - rhs.m_value) <= rhs.m_margin;
|
||||
}
|
||||
|
||||
template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
|
||||
@@ -94,6 +96,7 @@ namespace Detail {
|
||||
template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>
|
||||
Approx& margin( T const& newMargin ) {
|
||||
m_margin = static_cast<double>(newMargin);
|
||||
CATCH_ENFORCE(m_margin >= 0, "Invalid Approx::margin: " << m_margin << ", Approx::Margin has to be non-negative.");
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@@ -54,8 +54,8 @@ namespace Catch {
|
||||
}
|
||||
|
||||
std::string AssertionResult::getExpression() const {
|
||||
if (isFalseTest(m_info.resultDisposition))
|
||||
return '!' + std::string(m_info.capturedExpression);
|
||||
if( isFalseTest( m_info.resultDisposition ) )
|
||||
return "!(" + std::string(m_info.capturedExpression) + ")";
|
||||
else
|
||||
return m_info.capturedExpression;
|
||||
}
|
||||
|
@@ -98,84 +98,84 @@ namespace Catch {
|
||||
|
||||
auto cli
|
||||
= ExeName( config.processName )
|
||||
+ Help( config.showHelp )
|
||||
+ Opt( config.listTests )
|
||||
| Help( config.showHelp )
|
||||
| Opt( config.listTests )
|
||||
["-l"]["--list-tests"]
|
||||
( "list all/matching test cases" )
|
||||
+ Opt( config.listTags )
|
||||
| Opt( config.listTags )
|
||||
["-t"]["--list-tags"]
|
||||
( "list all/matching tags" )
|
||||
+ Opt( config.showSuccessfulTests )
|
||||
| Opt( config.showSuccessfulTests )
|
||||
["-s"]["--success"]
|
||||
( "include successful tests in output" )
|
||||
+ Opt( config.shouldDebugBreak )
|
||||
| Opt( config.shouldDebugBreak )
|
||||
["-b"]["--break"]
|
||||
( "break into debugger on failure" )
|
||||
+ Opt( config.noThrow )
|
||||
| Opt( config.noThrow )
|
||||
["-e"]["--nothrow"]
|
||||
( "skip exception tests" )
|
||||
+ Opt( config.showInvisibles )
|
||||
| Opt( config.showInvisibles )
|
||||
["-i"]["--invisibles"]
|
||||
( "show invisibles (tabs, newlines)" )
|
||||
+ Opt( config.outputFilename, "filename" )
|
||||
| Opt( config.outputFilename, "filename" )
|
||||
["-o"]["--out"]
|
||||
( "output filename" )
|
||||
+ Opt( config.reporterNames, "name" )
|
||||
| Opt( config.reporterNames, "name" )
|
||||
["-r"]["--reporter"]
|
||||
( "reporter to use (defaults to console)" )
|
||||
+ Opt( config.name, "name" )
|
||||
| Opt( config.name, "name" )
|
||||
["-n"]["--name"]
|
||||
( "suite name" )
|
||||
+ Opt( [&]( bool ){ config.abortAfter = 1; } )
|
||||
| Opt( [&]( bool ){ config.abortAfter = 1; } )
|
||||
["-a"]["--abort"]
|
||||
( "abort at first failure" )
|
||||
+ Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
|
||||
| Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" )
|
||||
["-x"]["--abortx"]
|
||||
( "abort after x failures" )
|
||||
+ Opt( setWarning, "warning name" )
|
||||
| Opt( setWarning, "warning name" )
|
||||
["-w"]["--warn"]
|
||||
( "enable warnings" )
|
||||
+ Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" )
|
||||
| Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" )
|
||||
["-d"]["--durations"]
|
||||
( "show test durations" )
|
||||
+ Opt( loadTestNamesFromFile, "filename" )
|
||||
| Opt( loadTestNamesFromFile, "filename" )
|
||||
["-f"]["--input-file"]
|
||||
( "load test names to run from a file" )
|
||||
+ Opt( config.filenamesAsTags )
|
||||
| Opt( config.filenamesAsTags )
|
||||
["-#"]["--filenames-as-tags"]
|
||||
( "adds a tag for the filename" )
|
||||
+ Opt( config.sectionsToRun, "section name" )
|
||||
| Opt( config.sectionsToRun, "section name" )
|
||||
["-c"]["--section"]
|
||||
( "specify section to run" )
|
||||
+ Opt( setVerbosity, "quiet|normal|high" )
|
||||
| Opt( setVerbosity, "quiet|normal|high" )
|
||||
["-v"]["--verbosity"]
|
||||
( "set output verbosity" )
|
||||
+ Opt( config.listTestNamesOnly )
|
||||
| Opt( config.listTestNamesOnly )
|
||||
["--list-test-names-only"]
|
||||
( "list all/matching test cases names only" )
|
||||
+ Opt( config.listReporters )
|
||||
| Opt( config.listReporters )
|
||||
["--list-reporters"]
|
||||
( "list all reporters" )
|
||||
+ Opt( setTestOrder, "decl|lex|rand" )
|
||||
| Opt( setTestOrder, "decl|lex|rand" )
|
||||
["--order"]
|
||||
( "test case order (defaults to decl)" )
|
||||
+ Opt( setRngSeed, "'time'|number" )
|
||||
| Opt( setRngSeed, "'time'|number" )
|
||||
["--rng-seed"]
|
||||
( "set a specific seed for random numbers" )
|
||||
+ Opt( setColourUsage, "yes|no" )
|
||||
| Opt( setColourUsage, "yes|no" )
|
||||
["--use-colour"]
|
||||
( "should output be colourised" )
|
||||
+ Opt( config.libIdentify )
|
||||
| Opt( config.libIdentify )
|
||||
["--libidentify"]
|
||||
( "report name and version according to libidentify standard" )
|
||||
+ Opt( setWaitForKeypress, "start|exit|both" )
|
||||
| Opt( setWaitForKeypress, "start|exit|both" )
|
||||
["--wait-for-keypress"]
|
||||
( "waits for a keypress before exiting" )
|
||||
+ Opt( config.benchmarkResolutionMultiple, "multiplier" )
|
||||
| Opt( config.benchmarkResolutionMultiple, "multiplier" )
|
||||
["--benchmark-resolution-multiple"]
|
||||
( "multiple of clock resolution to run benchmarks" )
|
||||
|
||||
+ Arg( config.testsOrTags, "test name|pattern|tags" )
|
||||
| Arg( config.testsOrTags, "test name|pattern|tags" )
|
||||
( "which test or tests to use" );
|
||||
|
||||
return cli;
|
||||
|
@@ -82,7 +82,7 @@
|
||||
|
||||
// Universal Windows platform does not support SEH
|
||||
// Or console colours (or console at all...)
|
||||
# if (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
# define CATCH_CONFIG_COLOUR_NONE
|
||||
# else
|
||||
# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
|
||||
@@ -92,17 +92,16 @@
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// All supported compilers support COUNTER macro,
|
||||
//but user still might want to turn it off
|
||||
#define CATCH_INTERNAL_CONFIG_COUNTER
|
||||
// Use of __COUNTER__ is suppressed during code analysis in
|
||||
// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
|
||||
// handled by it.
|
||||
// Otherwise all supported compilers support COUNTER macro,
|
||||
// but user still might want to turn it off
|
||||
#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )
|
||||
#define CATCH_INTERNAL_CONFIG_COUNTER
|
||||
#endif
|
||||
|
||||
|
||||
// Now set the actual defines based on the above + anything the user has configured
|
||||
|
||||
// Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for
|
||||
// analytics) because, at time of writing, __COUNTER__ is not properly handled by it.
|
||||
// This does not affect compilation
|
||||
#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__)
|
||||
#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
|
||||
# define CATCH_CONFIG_COUNTER
|
||||
#endif
|
||||
#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH)
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "catch_context.h"
|
||||
#include "catch_platform.h"
|
||||
#include "catch_debugger.h"
|
||||
#include "catch_windows_h_proxy.h"
|
||||
|
||||
namespace Catch {
|
||||
namespace {
|
||||
@@ -53,8 +54,6 @@ namespace Catch {
|
||||
|
||||
#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) /////////////////////////////////////////
|
||||
|
||||
#include "catch_windows_h_proxy.h"
|
||||
|
||||
namespace Catch {
|
||||
namespace {
|
||||
|
||||
|
@@ -77,7 +77,7 @@ namespace Catch {
|
||||
|
||||
// Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)
|
||||
template<typename LhsT, typename RhsT>
|
||||
auto compareEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return lhs == rhs; };
|
||||
auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return lhs == rhs; };
|
||||
template<typename T>
|
||||
auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }
|
||||
template<typename T>
|
||||
@@ -106,36 +106,36 @@ namespace Catch {
|
||||
ExprLhs( LhsT lhs ) : m_lhs( lhs ) {}
|
||||
|
||||
template<typename RhsT>
|
||||
auto operator == ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs );
|
||||
auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs );
|
||||
}
|
||||
auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
|
||||
return BinaryExpr<LhsT, bool>( m_lhs == rhs, m_lhs, "==", rhs );
|
||||
}
|
||||
|
||||
template<typename RhsT>
|
||||
auto operator != ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs );
|
||||
auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs );
|
||||
}
|
||||
auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const {
|
||||
return BinaryExpr<LhsT, bool>( m_lhs != rhs, m_lhs, "!=", rhs );
|
||||
}
|
||||
|
||||
template<typename RhsT>
|
||||
auto operator > ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( m_lhs > rhs, m_lhs, ">", rhs );
|
||||
auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( m_lhs > rhs, m_lhs, ">", rhs );
|
||||
}
|
||||
template<typename RhsT>
|
||||
auto operator < ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( m_lhs < rhs, m_lhs, "<", rhs );
|
||||
auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( m_lhs < rhs, m_lhs, "<", rhs );
|
||||
}
|
||||
template<typename RhsT>
|
||||
auto operator >= ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( m_lhs >= rhs, m_lhs, ">=", rhs );
|
||||
auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( m_lhs >= rhs, m_lhs, ">=", rhs );
|
||||
}
|
||||
template<typename RhsT>
|
||||
auto operator <= ( RhsT&& rhs ) -> BinaryExpr<LhsT, RhsT&> const {
|
||||
return BinaryExpr<LhsT, RhsT&>( m_lhs <= rhs, m_lhs, "<=", rhs );
|
||||
auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const {
|
||||
return BinaryExpr<LhsT, RhsT const&>( m_lhs <= rhs, m_lhs, "<=", rhs );
|
||||
}
|
||||
|
||||
auto makeUnaryExpr() const -> UnaryExpr<LhsT> {
|
||||
@@ -162,4 +162,8 @@ namespace Catch {
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif // TWOBLUECUBES_CATCH_DECOMPOSER_H_INCLUDED
|
||||
|
@@ -37,7 +37,7 @@ namespace Catch {
|
||||
#endif
|
||||
}
|
||||
catch( TestFailureException& ) {
|
||||
throw;
|
||||
std::rethrow_exception(std::current_exception());
|
||||
}
|
||||
catch( std::exception& ex ) {
|
||||
return ex.what();
|
||||
@@ -55,7 +55,7 @@ namespace Catch {
|
||||
|
||||
std::string ExceptionTranslatorRegistry::tryTranslators() const {
|
||||
if( m_translators.empty() )
|
||||
throw;
|
||||
std::rethrow_exception(std::current_exception());
|
||||
else
|
||||
return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() );
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
static std::string translatorName( signature )
|
||||
#endif
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace Catch {
|
||||
std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override {
|
||||
try {
|
||||
if( it == itEnd )
|
||||
throw;
|
||||
std::rethrow_exception(std::current_exception());
|
||||
else
|
||||
return (*it)->translate( it+1, itEnd );
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ namespace Catch {
|
||||
return std::rand() % n;
|
||||
}
|
||||
RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const {
|
||||
return std::rand() % max();
|
||||
return std::rand() % (max)();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,10 +20,10 @@ namespace Catch {
|
||||
unsigned int rngSeed();
|
||||
|
||||
struct RandomNumberGenerator {
|
||||
using result_type = std::ptrdiff_t;
|
||||
using result_type = unsigned int;
|
||||
|
||||
static constexpr result_type min() { return 0; }
|
||||
static constexpr result_type max() { return 1000000; }
|
||||
static constexpr result_type (min)() { return 0; }
|
||||
static constexpr result_type (max)() { return 1000000; }
|
||||
|
||||
result_type operator()( result_type n ) const;
|
||||
result_type operator()() const;
|
||||
|
@@ -12,7 +12,6 @@
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
|
||||
@@ -57,7 +56,7 @@ namespace Catch {
|
||||
public:
|
||||
static const bool value = decltype(test<std::ostream, const T&>(0))::value;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
// If we decide for C++14, change these to enable_if_ts
|
||||
@@ -241,7 +240,53 @@ namespace Catch {
|
||||
}
|
||||
};
|
||||
|
||||
// === Pair ===
|
||||
template<typename T>
|
||||
struct EnumStringMaker {
|
||||
static std::string convert(const T& t) {
|
||||
return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<T>::type>(t));
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __OBJC__
|
||||
template<>
|
||||
struct StringMaker<NSString*> {
|
||||
static std::string convert(NSString * nsstring) {
|
||||
if (!nsstring)
|
||||
return "nil";
|
||||
return std::string("@") + [nsstring UTF8String];
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct StringMaker<NSObject*> {
|
||||
static std::string convert(NSObject* nsObject) {
|
||||
return ::Catch::Detail::stringify([nsObject description]);
|
||||
}
|
||||
|
||||
};
|
||||
namespace Detail {
|
||||
inline std::string stringify( NSString* nsstring ) {
|
||||
return StringMaker<NSString*>::convert( nsstring );
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
#endif // __OBJC__
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Separate std-lib types stringification, so it can be selectively enabled
|
||||
// This means that we do not bring in
|
||||
|
||||
#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)
|
||||
# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
|
||||
# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
|
||||
# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
||||
#endif
|
||||
|
||||
// Separate std::pair specialization
|
||||
#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)
|
||||
#include <utility>
|
||||
namespace Catch {
|
||||
template<typename T1, typename T2>
|
||||
struct StringMaker<std::pair<T1, T2> > {
|
||||
static std::string convert(const std::pair<T1, T2>& pair) {
|
||||
@@ -254,9 +299,13 @@ namespace Catch {
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
|
||||
|
||||
|
||||
|
||||
// Separate std::tuple specialization
|
||||
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
|
||||
#include <tuple>
|
||||
namespace Catch {
|
||||
namespace Detail {
|
||||
template<
|
||||
typename Tuple,
|
||||
@@ -292,40 +341,126 @@ namespace Catch {
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct EnumStringMaker {
|
||||
static std::string convert(const T& t) {
|
||||
return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<T>::type>(t));
|
||||
// Separate std::chrono::duration specialization
|
||||
#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
|
||||
#include <ctime>
|
||||
#include <ratio>
|
||||
#include <chrono>
|
||||
|
||||
template <class Ratio>
|
||||
struct ratio_string {
|
||||
static std::string symbol();
|
||||
};
|
||||
|
||||
template <class Ratio>
|
||||
std::string ratio_string<Ratio>::symbol() {
|
||||
std::ostringstream oss;
|
||||
oss << '[' << Ratio::num << '/'
|
||||
<< Ratio::den << ']';
|
||||
return oss.str();
|
||||
}
|
||||
template <>
|
||||
struct ratio_string<std::atto> {
|
||||
static std::string symbol() { return "a"; }
|
||||
};
|
||||
template <>
|
||||
struct ratio_string<std::femto> {
|
||||
static std::string symbol() { return "f"; }
|
||||
};
|
||||
template <>
|
||||
struct ratio_string<std::pico> {
|
||||
static std::string symbol() { return "p"; }
|
||||
};
|
||||
template <>
|
||||
struct ratio_string<std::nano> {
|
||||
static std::string symbol() { return "n"; }
|
||||
};
|
||||
template <>
|
||||
struct ratio_string<std::micro> {
|
||||
static std::string symbol() { return "u"; }
|
||||
};
|
||||
template <>
|
||||
struct ratio_string<std::milli> {
|
||||
static std::string symbol() { return "m"; }
|
||||
};
|
||||
|
||||
namespace Catch {
|
||||
////////////
|
||||
// std::chrono::duration specializations
|
||||
template<typename Value, typename Ratio>
|
||||
struct StringMaker<std::chrono::duration<Value, Ratio>> {
|
||||
static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {
|
||||
std::ostringstream oss;
|
||||
oss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
template<typename Value>
|
||||
struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {
|
||||
static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {
|
||||
std::ostringstream oss;
|
||||
oss << duration.count() << " s";
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
template<typename Value>
|
||||
struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {
|
||||
static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {
|
||||
std::ostringstream oss;
|
||||
oss << duration.count() << " m";
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
template<typename Value>
|
||||
struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {
|
||||
static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {
|
||||
std::ostringstream oss;
|
||||
oss << duration.count() << " h";
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __OBJC__
|
||||
template<>
|
||||
struct StringMaker<NSString*> {
|
||||
static std::string convert(NSString * nsstring) {
|
||||
if (!nsstring)
|
||||
return "nil";
|
||||
return std::string("@") + [nsstring UTF8String];
|
||||
////////////
|
||||
// std::chrono::time_point specialization
|
||||
// Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>
|
||||
template<typename Clock, typename Duration>
|
||||
struct StringMaker<std::chrono::time_point<Clock, Duration>> {
|
||||
static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {
|
||||
return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch";
|
||||
}
|
||||
};
|
||||
template<>
|
||||
struct StringMaker<NSObject*> {
|
||||
static std::string convert(NSObject* nsObject) {
|
||||
return ::Catch::Detail::stringify([nsObject description]);
|
||||
}
|
||||
// std::chrono::time_point<system_clock> specialization
|
||||
template<typename Duration>
|
||||
struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
|
||||
static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
|
||||
auto converted = std::chrono::system_clock::to_time_t(time_point);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
std::tm timeInfo = {};
|
||||
gmtime_s(&timeInfo, &converted);
|
||||
#else
|
||||
std::tm* timeInfo = std::gmtime(&converted);
|
||||
#endif
|
||||
|
||||
auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
|
||||
char timeStamp[timeStampSize];
|
||||
const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
|
||||
|
||||
#ifdef _MSC_VER
|
||||
std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
|
||||
#else
|
||||
std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
|
||||
#endif
|
||||
return std::string(timeStamp);
|
||||
}
|
||||
};
|
||||
namespace Detail {
|
||||
inline std::string stringify( NSString* nsstring ) {
|
||||
return StringMaker<NSString*>::convert( nsstring );
|
||||
}
|
||||
}
|
||||
#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
||||
|
||||
} // namespace Detail
|
||||
#endif // __OBJC__
|
||||
|
||||
} // namespace Catch
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
|
@@ -37,7 +37,7 @@ namespace Catch {
|
||||
}
|
||||
|
||||
Version const& libraryVersion() {
|
||||
static Version version( 2, 0, 0, "develop", 4 );
|
||||
static Version version( 2, 0, 0, "develop", 6 );
|
||||
return version;
|
||||
}
|
||||
|
||||
|
@@ -11,14 +11,14 @@
|
||||
#include "catch_platform.h"
|
||||
|
||||
#if defined(CATCH_PLATFORM_WINDOWS)
|
||||
# if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
|
||||
# define CATCH_DEFINED_NOMINMAX
|
||||
# define NOMINMAX
|
||||
# endif
|
||||
# if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
|
||||
# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
|
||||
#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX)
|
||||
# define CATCH_DEFINED_NOMINMAX
|
||||
# define NOMINMAX
|
||||
#endif
|
||||
#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN)
|
||||
# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#ifdef __AFXDLL
|
||||
@@ -34,5 +34,6 @@
|
||||
# undef WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#endif // defined(CATCH_PLATFORM_WINDOWS)
|
||||
|
||||
#endif // TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED
|
||||
|
@@ -17,6 +17,14 @@
|
||||
#include <cfloat>
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
|
||||
// Note that 4062 (not all labels are handled
|
||||
// and default is missing) is enabled
|
||||
#endif
|
||||
|
||||
|
||||
namespace Catch {
|
||||
|
||||
namespace {
|
||||
@@ -639,3 +647,7 @@ namespace Catch {
|
||||
ConsoleReporter::~ConsoleReporter() {}
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
@@ -28,6 +28,14 @@ namespace Catch {
|
||||
reporter->noMatchingTestCases( spec );
|
||||
}
|
||||
|
||||
void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {
|
||||
for( auto const& reporter : m_reporters )
|
||||
reporter->benchmarkStarting( benchmarkInfo );
|
||||
}
|
||||
void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) {
|
||||
for( auto const& reporter : m_reporters )
|
||||
reporter->benchmarkEnded( benchmarkStats );
|
||||
}
|
||||
|
||||
void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) {
|
||||
for( auto const& reporter : m_reporters )
|
||||
|
@@ -26,6 +26,9 @@ namespace Catch {
|
||||
|
||||
static std::set<Verbosity> getSupportedVerbosities();
|
||||
|
||||
void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override;
|
||||
void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override;
|
||||
|
||||
void testRunStarting( TestRunInfo const& testRunInfo ) override;
|
||||
void testGroupStarting( GroupInfo const& groupInfo ) override;
|
||||
void testCaseStarting( TestCaseInfo const& testInfo ) override;
|
||||
|
@@ -133,6 +133,7 @@ namespace Catch {
|
||||
<< "]\n";
|
||||
}
|
||||
}
|
||||
stream.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -146,6 +147,7 @@ namespace Catch {
|
||||
StreamingReporterBase::testCaseStarting( testInfo );
|
||||
stream << "##teamcity[testStarted name='"
|
||||
<< escape( testInfo.name ) << "']\n";
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
void testCaseEnded( TestCaseStats const& testCaseStats ) override {
|
||||
@@ -161,6 +163,7 @@ namespace Catch {
|
||||
stream << "##teamcity[testFinished name='"
|
||||
<< escape( testCaseStats.testInfo.name ) << "' duration='"
|
||||
<< m_testTimer.getElapsedMilliseconds() << "']\n";
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@@ -13,6 +13,13 @@
|
||||
#include "internal/catch_xmlwriter.h"
|
||||
#include "../internal/catch_timer.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch
|
||||
// Note that 4062 (not all labels are handled
|
||||
// and default is missing) is enabled
|
||||
#endif
|
||||
|
||||
namespace Catch {
|
||||
class XmlReporter : public StreamingReporterBase<XmlReporter> {
|
||||
public:
|
||||
@@ -223,3 +230,7 @@ namespace Catch {
|
||||
CATCH_REGISTER_REPORTER( "xml", XmlReporter )
|
||||
|
||||
} // end namespace Catch
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
@@ -24,6 +24,8 @@ TEST_CASE
|
||||
REQUIRE( Approx( d ) == 1.23 );
|
||||
REQUIRE( Approx( d ) != 1.22 );
|
||||
REQUIRE( Approx( d ) != 1.24 );
|
||||
|
||||
REQUIRE( 0 == Approx(0) );
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -146,11 +148,29 @@ TEST_CASE( "Approximate PI", "[Approx][PI]" )
|
||||
TEST_CASE( "Absolute margin", "[Approx]" ) {
|
||||
REQUIRE( 104.0 != Approx(100.0) );
|
||||
REQUIRE( 104.0 == Approx(100.0).margin(5) );
|
||||
REQUIRE( 104.0 == Approx(100.0).margin(4) );
|
||||
REQUIRE( 104.0 != Approx(100.0).margin(3) );
|
||||
REQUIRE( 100.3 != Approx(100.0) );
|
||||
REQUIRE( 100.3 == Approx(100.0).margin(0.5) );
|
||||
}
|
||||
|
||||
TEST_CASE("Approx with exactly-representable margin", "[Approx]") {
|
||||
CHECK( 0.25f == Approx(0.0f).margin(0.25f) );
|
||||
|
||||
CHECK( 0.0f == Approx(0.25f).margin(0.25f) );
|
||||
CHECK( 0.5f == Approx(0.25f).margin(0.25f) );
|
||||
|
||||
CHECK( 245.0f == Approx(245.25f).margin(0.25f) );
|
||||
CHECK( 245.5f == Approx(245.25f).margin(0.25f) );
|
||||
}
|
||||
|
||||
TEST_CASE("Approx setters validate their arguments", "[Approx]") {
|
||||
REQUIRE_NOTHROW(Approx(0).margin(0));
|
||||
REQUIRE_NOTHROW(Approx(0).margin(1234656));
|
||||
|
||||
REQUIRE_THROWS_AS(Approx(0).margin(-2), std::domain_error);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class StrongDoubleTypedef
|
||||
|
@@ -58,6 +58,8 @@ with expansion:
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK_FALSE( true )
|
||||
with expansion:
|
||||
!true
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK( !trueValue )
|
||||
@@ -76,8 +78,6 @@ with expansion:
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK_FALSE( 1 == 1 )
|
||||
with expansion:
|
||||
!(1 == 1)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
A METHOD_AS_TEST_CASE based test run that fails
|
||||
@@ -1003,6 +1003,6 @@ with expansion:
|
||||
"{?}" == "1"
|
||||
|
||||
===============================================================================
|
||||
test cases: 176 | 125 passed | 47 failed | 4 failed as expected
|
||||
assertions: 878 | 761 passed | 96 failed | 21 failed as expected
|
||||
test cases: 182 | 131 passed | 47 failed | 4 failed as expected
|
||||
assertions: 896 | 779 passed | 96 failed | 21 failed as expected
|
||||
|
||||
|
@@ -235,6 +235,8 @@ with expansion:
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK_FALSE( true )
|
||||
with expansion:
|
||||
!true
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK( !trueValue )
|
||||
@@ -253,8 +255,6 @@ with expansion:
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK_FALSE( 1 == 1 )
|
||||
with expansion:
|
||||
!(1 == 1)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
'Not' checks that should succeed
|
||||
@@ -279,6 +279,8 @@ with expansion:
|
||||
ConditionTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE_FALSE( false )
|
||||
with expansion:
|
||||
!false
|
||||
|
||||
ConditionTests.cpp:<line number>:
|
||||
PASSED:
|
||||
@@ -301,8 +303,6 @@ with expansion:
|
||||
ConditionTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE_FALSE( 1 == 2 )
|
||||
with expansion:
|
||||
!(1 == 2)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
(unimplemented) static bools can be evaluated
|
||||
@@ -490,6 +490,12 @@ PASSED:
|
||||
with expansion:
|
||||
104.0 == Approx( 100.0 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( 104.0 == Approx(100.0).margin(4) )
|
||||
with expansion:
|
||||
104.0 == Approx( 100.0 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( 104.0 != Approx(100.0).margin(3) )
|
||||
@@ -552,6 +558,60 @@ PASSED:
|
||||
with message:
|
||||
anonymous test case
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Approx setters validate their arguments
|
||||
-------------------------------------------------------------------------------
|
||||
ApproxTests.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE_NOTHROW( Approx(0).margin(0) )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE_NOTHROW( Approx(0).margin(1234656) )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE_THROWS_AS( Approx(0).margin(-2), std::domain_error )
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Approx with exactly-representable margin
|
||||
-------------------------------------------------------------------------------
|
||||
ApproxTests.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
CHECK( 0.25f == Approx(0.0f).margin(0.25f) )
|
||||
with expansion:
|
||||
0.25f == Approx( 0.0 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
CHECK( 0.0f == Approx(0.25f).margin(0.25f) )
|
||||
with expansion:
|
||||
0.0f == Approx( 0.25 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
CHECK( 0.5f == Approx(0.25f).margin(0.25f) )
|
||||
with expansion:
|
||||
0.5f == Approx( 0.25 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
CHECK( 245.0f == Approx(245.25f).margin(0.25f) )
|
||||
with expansion:
|
||||
245.0f == Approx( 245.25 )
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
CHECK( 245.5f == Approx(245.25f).margin(0.25f) )
|
||||
with expansion:
|
||||
245.5f == Approx( 245.25 )
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Approximate PI
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -738,6 +798,24 @@ PASSED:
|
||||
with expansion:
|
||||
true
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Bitfields can be captured (#1027)
|
||||
-------------------------------------------------------------------------------
|
||||
TrickyTests.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
TrickyTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( y.v == 0 )
|
||||
with expansion:
|
||||
0 == 0
|
||||
|
||||
TrickyTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( 0 == y.v )
|
||||
with expansion:
|
||||
0 == 0
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Capture and info messages
|
||||
Capture should stringify like assertions
|
||||
@@ -3336,7 +3414,7 @@ TrickyTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( (std::pair<int, int>( 1, 2 )) == aNicePair )
|
||||
with expansion:
|
||||
{ 1, 2 } == { 1, 2 }
|
||||
{?} == {?}
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Pointers can be compared to null
|
||||
@@ -4183,6 +4261,12 @@ PASSED:
|
||||
with expansion:
|
||||
Approx( 1.23 ) != 1.24
|
||||
|
||||
ApproxTests.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( 0 == Approx(0) )
|
||||
with expansion:
|
||||
0 == Approx( 0.0 )
|
||||
|
||||
Message from section one
|
||||
-------------------------------------------------------------------------------
|
||||
Standard output from all sections is reported
|
||||
@@ -4613,6 +4697,62 @@ PASSED:
|
||||
with expansion:
|
||||
11 == 11
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Stringifying std::chrono::duration helpers
|
||||
-------------------------------------------------------------------------------
|
||||
ToStringChrono.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( minute == seconds )
|
||||
with expansion:
|
||||
1 m == 60 s
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( hour != seconds )
|
||||
with expansion:
|
||||
1 h != 60 s
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( micro != milli )
|
||||
with expansion:
|
||||
1 us != 1 ms
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( nano != micro )
|
||||
with expansion:
|
||||
1 ns != 1 us
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Stringifying std::chrono::duration with weird ratios
|
||||
-------------------------------------------------------------------------------
|
||||
ToStringChrono.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( half_minute != femto_second )
|
||||
with expansion:
|
||||
1 [30/1]s != 1 fs
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Stringifying std::chrono::time_point<system_clock>
|
||||
-------------------------------------------------------------------------------
|
||||
ToStringChrono.cpp:<line number>
|
||||
...............................................................................
|
||||
|
||||
ToStringChrono.cpp:<line number>:
|
||||
PASSED:
|
||||
REQUIRE( now != later )
|
||||
with expansion:
|
||||
{iso8601-timestamp}
|
||||
!=
|
||||
{iso8601-timestamp}
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
Tabs and newlines show in output
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -7434,6 +7574,6 @@ MiscTests.cpp:<line number>:
|
||||
PASSED:
|
||||
|
||||
===============================================================================
|
||||
test cases: 176 | 123 passed | 49 failed | 4 failed as expected
|
||||
assertions: 877 | 757 passed | 99 failed | 21 failed as expected
|
||||
test cases: 182 | 129 passed | 49 failed | 4 failed as expected
|
||||
assertions: 895 | 775 passed | 99 failed | 21 failed as expected
|
||||
|
||||
|
@@ -235,6 +235,8 @@ with expansion:
|
||||
|
||||
ConditionTests.cpp:<line number>: FAILED:
|
||||
CHECK_FALSE( true )
|
||||
with expansion:
|
||||
!true
|
||||
|
||||
===============================================================================
|
||||
test cases: 9 | 6 passed | 1 failed | 2 failed as expected
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuitesloose text artifact
|
||||
>
|
||||
<testsuite name="<exe-name>" errors="15" failures="85" tests="878" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<testsuite name="<exe-name>" errors="15" failures="85" tests="896" hostname="tbd" time="{duration}" timestamp="{iso8601-timestamp}">
|
||||
<testcase classname="<exe-name>.global" name="# A test name that starts with a #" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="#1005: Comparing pointer to int and long (NULL can be either on various systems)" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="#748 - captures with unexpected exceptions/outside assertions" time="{duration}">
|
||||
@@ -100,6 +100,8 @@ ExceptionTests.cpp:<line number>
|
||||
</error>
|
||||
</testcase>
|
||||
<testcase classname="<exe-name>.global" name="Anonymous test case 1" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Approx setters validate their arguments" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Approx with exactly-representable margin" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Approximate PI" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Approximate comparisons with different epsilons" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Approximate comparisons with floats" time="{duration}"/>
|
||||
@@ -109,6 +111,7 @@ ExceptionTests.cpp:<line number>
|
||||
<testcase classname="<exe-name>.global" name="Assertions then sections/A section" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Assertions then sections/A section/Another section" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Assertions then sections/A section/Another other section" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Bitfields can be captured (#1027)" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Capture and info messages/Capture should stringify like assertions" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Capture and info messages/Info should NOT stringify the way assertions do" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Character pretty printing/Specifically escaped" time="{duration}"/>
|
||||
@@ -519,6 +522,9 @@ StringRef.tests.cpp:<line number>
|
||||
<testcase classname="<exe-name>.global" name="StringRef/to std::string/implicitly constructed" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="StringRef/to std::string/explicitly constructed" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="StringRef/to std::string/assigned" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Stringifying std::chrono::duration helpers" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Stringifying std::chrono::duration with weird ratios" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Stringifying std::chrono::time_point<system_clock>" time="{duration}"/>
|
||||
<testcase classname="<exe-name>.global" name="Tabs and newlines show in output" time="{duration}">
|
||||
<failure message=""if ($b == 10) {
|
||||
$a = 20;
|
||||
|
@@ -214,7 +214,7 @@
|
||||
</Expression>
|
||||
<Expression success="false" type="CHECK_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!true
|
||||
!(true)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!true
|
||||
@@ -230,7 +230,7 @@
|
||||
</Expression>
|
||||
<Expression success="false" type="CHECK_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!trueValue
|
||||
!(trueValue)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!true
|
||||
@@ -246,7 +246,7 @@
|
||||
</Expression>
|
||||
<Expression success="false" type="CHECK_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!1 == 1
|
||||
!(1 == 1)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!(1 == 1)
|
||||
@@ -281,7 +281,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!false
|
||||
!(false)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
@@ -297,7 +297,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!falseValue
|
||||
!(falseValue)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
@@ -313,7 +313,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/ConditionTests.cpp" >
|
||||
<Original>
|
||||
!1 == 2
|
||||
!(1 == 2)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!(1 == 2)
|
||||
@@ -393,7 +393,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
!is_true<false>::value
|
||||
!(is_true<false>::value)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
@@ -495,6 +495,14 @@
|
||||
104.0 == Approx( 100.0 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
104.0 == Approx(100.0).margin(4)
|
||||
</Original>
|
||||
<Expanded>
|
||||
104.0 == Approx( 100.0 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
104.0 != Approx(100.0).margin(3)
|
||||
@@ -568,6 +576,76 @@
|
||||
<TestCase name="Anonymous test case 1" filename="projects/<exe-name>/VariadicMacrosTests.cpp" >
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Approx setters validate their arguments" tags="[Approx]" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Expression success="true" type="REQUIRE_NOTHROW" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
Approx(0).margin(0)
|
||||
</Original>
|
||||
<Expanded>
|
||||
Approx(0).margin(0)
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_NOTHROW" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
Approx(0).margin(1234656)
|
||||
</Original>
|
||||
<Expanded>
|
||||
Approx(0).margin(1234656)
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_THROWS_AS" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
Approx(0).margin(-2), std::domain_error
|
||||
</Original>
|
||||
<Expanded>
|
||||
Approx(0).margin(-2), std::domain_error
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Approx with exactly-representable margin" tags="[Approx]" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Expression success="true" type="CHECK" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
0.25f == Approx(0.0f).margin(0.25f)
|
||||
</Original>
|
||||
<Expanded>
|
||||
0.25f == Approx( 0.0 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
0.0f == Approx(0.25f).margin(0.25f)
|
||||
</Original>
|
||||
<Expanded>
|
||||
0.0f == Approx( 0.25 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
0.5f == Approx(0.25f).margin(0.25f)
|
||||
</Original>
|
||||
<Expanded>
|
||||
0.5f == Approx( 0.25 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
245.0f == Approx(245.25f).margin(0.25f)
|
||||
</Original>
|
||||
<Expanded>
|
||||
245.0f == Approx( 245.25 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
245.5f == Approx(245.25f).margin(0.25f)
|
||||
</Original>
|
||||
<Expanded>
|
||||
245.5f == Approx( 245.25 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Approximate PI" tags="[Approx][PI]" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
@@ -750,6 +828,25 @@
|
||||
</Section>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Bitfields can be captured (#1027)" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
y.v == 0
|
||||
</Original>
|
||||
<Expanded>
|
||||
0 == 0
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
0 == y.v
|
||||
</Original>
|
||||
<Expanded>
|
||||
0 == 0
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Capture and info messages" filename="projects/<exe-name>/ToStringGeneralTests.cpp" >
|
||||
<Section name="Capture should stringify like assertions" filename="projects/<exe-name>/ToStringGeneralTests.cpp" >
|
||||
<Info>
|
||||
@@ -956,7 +1053,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
!std::vector<int>{1, 2} == std::vector<int>{1, 2, 3}
|
||||
!(std::vector<int>{1, 2} == std::vector<int>{1, 2, 3})
|
||||
</Original>
|
||||
<Expanded>
|
||||
!({ 1, 2 } == { 1, 2, 3 })
|
||||
@@ -964,7 +1061,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK_FALSE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
!std::vector<int>{1, 2} == std::vector<int>{1, 2, 3}
|
||||
!(std::vector<int>{1, 2} == std::vector<int>{1, 2, 3})
|
||||
</Original>
|
||||
<Expanded>
|
||||
!({ 1, 2 } == { 1, 2, 3 })
|
||||
@@ -1794,7 +1891,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
!d >= Approx( 1.24 )
|
||||
!(d >= Approx( 1.24 ))
|
||||
</Original>
|
||||
<Expanded>
|
||||
!(1.23 >= Approx( 1.24 ))
|
||||
@@ -2211,7 +2308,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE_FALSE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
!d <= Approx( 1.22 )
|
||||
!(d <= Approx( 1.22 ))
|
||||
</Original>
|
||||
<Expanded>
|
||||
!(1.23 <= Approx( 1.22 ))
|
||||
@@ -2354,7 +2451,7 @@
|
||||
</Expression>
|
||||
<Expression success="true" type="CHECK_FALSE" filename="projects/<exe-name>/TrickyTests.cpp" >
|
||||
<Original>
|
||||
!False
|
||||
!(False)
|
||||
</Original>
|
||||
<Expanded>
|
||||
!{?}
|
||||
@@ -3853,7 +3950,7 @@
|
||||
(std::pair<int, int>( 1, 2 )) == aNicePair
|
||||
</Original>
|
||||
<Expanded>
|
||||
{ 1, 2 } == { 1, 2 }
|
||||
{?} == {?}
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
@@ -4776,6 +4873,14 @@ A string sent directly to stderr
|
||||
Approx( 1.23 ) != 1.24
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ApproxTests.cpp" >
|
||||
<Original>
|
||||
0 == Approx(0)
|
||||
</Original>
|
||||
<Expanded>
|
||||
0 == Approx( 0.0 )
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Standard output from all sections is reported" tags="[.][messages]" filename="projects/<exe-name>/MessageTests.cpp" >
|
||||
@@ -5250,6 +5355,65 @@ Message from section two
|
||||
</Section>
|
||||
<OverallResult success="false"/>
|
||||
</TestCase>
|
||||
<TestCase name="Stringifying std::chrono::duration helpers" tags="[chrono][toString]" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
minute == seconds
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 m == 60 s
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
hour != seconds
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 h != 60 s
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
micro != milli
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 us != 1 ms
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
nano != micro
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 ns != 1 us
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Stringifying std::chrono::duration with weird ratios" tags="[chrono][toString]" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
half_minute != femto_second
|
||||
</Original>
|
||||
<Expanded>
|
||||
1 [30/1]s != 1 fs
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Stringifying std::chrono::time_point<system_clock>" tags="[chrono][toString]" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Expression success="true" type="REQUIRE" filename="projects/<exe-name>/ToStringChrono.cpp" >
|
||||
<Original>
|
||||
now != later
|
||||
</Original>
|
||||
<Expanded>
|
||||
{iso8601-timestamp}
|
||||
!=
|
||||
{iso8601-timestamp}
|
||||
</Expanded>
|
||||
</Expression>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<TestCase name="Tabs and newlines show in output" tags="[.][failing][whitespace]" filename="projects/<exe-name>/MiscTests.cpp" >
|
||||
<Expression success="false" type="CHECK" filename="projects/<exe-name>/MiscTests.cpp" >
|
||||
<Original>
|
||||
@@ -7555,7 +7719,7 @@ loose text artifact
|
||||
<Section name="replace no chars" filename="projects/<exe-name>/TestMain.cpp" >
|
||||
<Expression success="true" type="CHECK_FALSE" filename="projects/<exe-name>/TestMain.cpp" >
|
||||
<Original>
|
||||
!Catch::replaceInPlace( letters, "x", "z" )
|
||||
!(Catch::replaceInPlace( letters, "x", "z" ))
|
||||
</Original>
|
||||
<Expanded>
|
||||
!false
|
||||
@@ -8209,7 +8373,7 @@ loose text artifact
|
||||
</Section>
|
||||
<OverallResult success="true"/>
|
||||
</TestCase>
|
||||
<OverallResults successes="757" failures="100" expectedFailures="21"/>
|
||||
<OverallResults successes="775" failures="100" expectedFailures="21"/>
|
||||
</Group>
|
||||
<OverallResults successes="757" failures="99" expectedFailures="21"/>
|
||||
<OverallResults successes="775" failures="99" expectedFailures="21"/>
|
||||
</Catch>
|
||||
|
41
projects/SelfTest/ToStringChrono.cpp
Normal file
41
projects/SelfTest/ToStringChrono.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
TEST_CASE("Stringifying std::chrono::duration helpers", "[toString][chrono]") {
|
||||
// No literals because we still support c++11
|
||||
auto hour = std::chrono::hours(1);
|
||||
auto minute = std::chrono::minutes(1);
|
||||
auto seconds = std::chrono::seconds(60);
|
||||
auto micro = std::chrono::microseconds(1);
|
||||
auto milli = std::chrono::milliseconds(1);
|
||||
auto nano = std::chrono::nanoseconds(1);
|
||||
REQUIRE(minute == seconds);
|
||||
REQUIRE(hour != seconds);
|
||||
REQUIRE(micro != milli);
|
||||
REQUIRE(nano != micro);
|
||||
}
|
||||
|
||||
TEST_CASE("Stringifying std::chrono::duration with weird ratios", "[toString][chrono]") {
|
||||
std::chrono::duration<int64_t, std::ratio<30>> half_minute(1);
|
||||
std::chrono::duration<int64_t, std::ratio<1, 1000000000000000>> femto_second(1);
|
||||
REQUIRE(half_minute != femto_second);
|
||||
}
|
||||
|
||||
TEST_CASE("Stringifying std::chrono::time_point<system_clock>", "[toString][chrono]") {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto later = now + std::chrono::minutes(2);
|
||||
REQUIRE(now != later);
|
||||
}
|
||||
|
||||
TEST_CASE("Stringifying std::chrono::time_point<Clock>", "[toString][chrono][!nonportable]") {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
auto later = now + std::chrono::minutes(2);
|
||||
REQUIRE(now != later);
|
||||
|
||||
auto now2 = std::chrono::steady_clock::now();
|
||||
auto later2 = now2 + std::chrono::minutes(2);
|
||||
REQUIRE(now2 != later2);
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
#define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE( "std::pair<int,std::string> -> toString", "[toString][pair]" ) {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
@@ -168,7 +168,7 @@ namespace ObjectWithConversions
|
||||
{
|
||||
struct Object
|
||||
{
|
||||
operator unsigned int() {return 0xc0000000;}
|
||||
operator unsigned int() const {return 0xc0000000;}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -436,3 +436,12 @@ TEST_CASE("#925: comparing function pointer to function address failed to compil
|
||||
TestClass test;
|
||||
REQUIRE(utility::synchronizing_callback != test.testMethod_uponComplete_arg);
|
||||
}
|
||||
|
||||
TEST_CASE( "Bitfields can be captured (#1027)" ) {
|
||||
struct Y {
|
||||
uint32_t v : 1;
|
||||
};
|
||||
Y y{ 0 };
|
||||
REQUIRE( y.v == 0 );
|
||||
REQUIRE( 0 == y.v );
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ filelocParser = re.compile(r'''
|
||||
lineNumberParser = re.compile(r' line="[0-9]*"')
|
||||
hexParser = re.compile(r'\b(0[xX][0-9a-fA-F]+)\b')
|
||||
durationsParser = re.compile(r' time="[0-9]*\.[0-9]*"')
|
||||
timestampsParser = re.compile(r' timestamp="\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z"')
|
||||
timestampsParser = re.compile(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z')
|
||||
versionParser = re.compile(r'Catch v[0-9]+\.[0-9]+\.[0-9]+(-develop\.[0-9]+)?')
|
||||
nullParser = re.compile(r'\b(__null|nullptr)\b')
|
||||
exeNameParser = re.compile(r'''
|
||||
@@ -44,6 +44,7 @@ errnoParser = re.compile(r'''
|
||||
|
|
||||
\(\*_errno\(\)\)
|
||||
''', re.VERBOSE)
|
||||
sinceEpochParser = re.compile(r'\d+ .+ since epoch')
|
||||
|
||||
if len(sys.argv) == 2:
|
||||
cmdPath = sys.argv[1]
|
||||
@@ -97,9 +98,10 @@ def filterLine(line):
|
||||
|
||||
# strip durations and timestamps
|
||||
line = durationsParser.sub(' time="{duration}"', line)
|
||||
line = timestampsParser.sub(' timestamp="{iso8601-timestamp}"', line)
|
||||
line = timestampsParser.sub('{iso8601-timestamp}', line)
|
||||
line = specialCaseParser.sub('file:\g<1>', line)
|
||||
line = errnoParser.sub('errno', line)
|
||||
line = sinceEpochParser.sub('{since-epoch-report}', line)
|
||||
return line
|
||||
|
||||
|
||||
|
@@ -22,27 +22,16 @@ class LineMapper:
|
||||
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"
|
||||
for idFrom, idTo in self.idMap.iteritems():
|
||||
r = re.compile("(.*)" + idFrom + "(.*)")
|
||||
|
||||
m = r.match( line )
|
||||
if m:
|
||||
line = m.group(1) + idTo + m.group(2) + "\n"
|
||||
|
||||
m = nsCloseRe.match( line )
|
||||
if m:
|
||||
originalNs = m.group(3)
|
||||
@@ -61,6 +50,7 @@ class LineMapper:
|
||||
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 ):
|
||||
|
@@ -15,7 +15,7 @@ idMap = {
|
||||
"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",
|
||||
"CLARA_PLATFORM_WINDOWS": ""
|
||||
"CLARA_PLATFORM_WINDOWS": "CATCH_PLATFORM_WINDOWS"
|
||||
}
|
||||
|
||||
# outer namespace to add
|
||||
|
@@ -15,6 +15,7 @@ versionPath = os.path.join( rootPath, "internal/catch_version.cpp" )
|
||||
readmePath = os.path.join( catchPath, "README.md" )
|
||||
conanPath = os.path.join(catchPath, 'conanfile.py')
|
||||
conanTestPath = os.path.join(catchPath, 'test_package', 'conanfile.py')
|
||||
cmakePath = os.path.join(catchPath, 'CMakeLists.txt')
|
||||
|
||||
class Version:
|
||||
def __init__(self):
|
||||
@@ -126,6 +127,17 @@ def updateConanTestFile(version):
|
||||
for line in lines:
|
||||
f.write( line + "\n" )
|
||||
|
||||
def updateCmakeFile(version):
|
||||
cmakeParser = re.compile(r'set(CATCH_VERSION_NUMBER \d+\.\d+\.\d+)')
|
||||
with open(cmakePath, 'r') as file:
|
||||
lines = file.readlines()
|
||||
with open(cmakePath, 'w') as file:
|
||||
for line in lines:
|
||||
if 'set(CATCH_VERSION_NUMBER ' in line:
|
||||
file.write('set(CATCH_VERSION_NUMBER {0})\n'.format(version.getVersionString()))
|
||||
else:
|
||||
file.write(line)
|
||||
|
||||
def performUpdates(version):
|
||||
# First update version file, so we can regenerate single header and
|
||||
# have it ready for upload to wandbox, when updating readme
|
||||
@@ -134,3 +146,4 @@ def performUpdates(version):
|
||||
updateReadmeFile(version)
|
||||
updateConanFile(version)
|
||||
updateConanTestFile(version)
|
||||
updateCmakeFile(version)
|
||||
|
446
scripts/updateDocumentToC.py
Normal file
446
scripts/updateDocumentToC.py
Normal file
@@ -0,0 +1,446 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# updateDocumentToC.py
|
||||
#
|
||||
# Insert table of contents at top of Catch markdown documents.
|
||||
#
|
||||
# This script is distributed under the GNU General Public License v3.0
|
||||
#
|
||||
# It is based on markdown-toclify version 1.7.1 by Sebastian Raschka,
|
||||
# https://github.com/rasbt/markdown-toclify
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
from scriptCommon import catchPath
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Configuration:
|
||||
|
||||
minTocEntries = 4
|
||||
|
||||
headingExcludeDefault = [1,3,4,5] # use level 2 headers for at default
|
||||
headingExcludeRelease = [2,3,4,5] # use level 1 headers for release-notes.md
|
||||
|
||||
documentsDefault = os.path.join(os.path.relpath(catchPath), 'docs/*.md')
|
||||
releaseNotesName = 'release-notes.md'
|
||||
|
||||
contentTitle = '**Contents**'
|
||||
contentLineNo = 4
|
||||
contentLineNdx = contentLineNo - 1
|
||||
|
||||
# End configuration
|
||||
|
||||
VALIDS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-&'
|
||||
|
||||
def readLines(in_file):
|
||||
"""Returns a list of lines from a input markdown file."""
|
||||
|
||||
with open(in_file, 'r') as inf:
|
||||
in_contents = inf.read().split('\n')
|
||||
return in_contents
|
||||
|
||||
def removeLines(lines, remove=('[[back to top]', '<a class="mk-toclify"')):
|
||||
"""Removes existing [back to top] links and <a id> tags."""
|
||||
|
||||
if not remove:
|
||||
return lines[:]
|
||||
|
||||
out = []
|
||||
for l in lines:
|
||||
if l.startswith(remove):
|
||||
continue
|
||||
out.append(l)
|
||||
return out
|
||||
|
||||
def removeToC(lines):
|
||||
"""Removes existing table of contents starting at index contentLineNdx."""
|
||||
if not lines[contentLineNdx ].startswith(contentTitle):
|
||||
return lines[:]
|
||||
|
||||
result_top = lines[:contentLineNdx]
|
||||
|
||||
pos = contentLineNdx + 1
|
||||
while lines[pos].startswith('['):
|
||||
pos = pos + 1
|
||||
|
||||
result_bottom = lines[pos + 1:]
|
||||
|
||||
return result_top + result_bottom
|
||||
|
||||
def dashifyHeadline(line):
|
||||
"""
|
||||
Takes a header line from a Markdown document and
|
||||
returns a tuple of the
|
||||
'#'-stripped version of the head line,
|
||||
a string version for <a id=''></a> anchor tags,
|
||||
and the level of the headline as integer.
|
||||
E.g.,
|
||||
>>> dashifyHeadline('### some header lvl3')
|
||||
('Some header lvl3', 'some-header-lvl3', 3)
|
||||
|
||||
"""
|
||||
stripped_right = line.rstrip('#')
|
||||
stripped_both = stripped_right.lstrip('#')
|
||||
level = len(stripped_right) - len(stripped_both)
|
||||
stripped_wspace = stripped_both.strip()
|
||||
|
||||
# character replacements
|
||||
replaced_colon = stripped_wspace.replace('.', '')
|
||||
replaced_slash = replaced_colon.replace('/', '')
|
||||
rem_nonvalids = ''.join([c if c in VALIDS
|
||||
else '-' for c in replaced_slash])
|
||||
|
||||
lowered = rem_nonvalids.lower()
|
||||
dashified = re.sub(r'(-)\1+', r'\1', lowered) # remove duplicate dashes
|
||||
dashified = dashified.strip('-') # strip dashes from start and end
|
||||
|
||||
# exception '&' (double-dash in github)
|
||||
dashified = dashified.replace('-&-', '--')
|
||||
|
||||
return [stripped_wspace, dashified, level]
|
||||
|
||||
def tagAndCollect(lines, id_tag=True, back_links=False, exclude_h=None):
|
||||
"""
|
||||
Gets headlines from the markdown document and creates anchor tags.
|
||||
|
||||
Keyword arguments:
|
||||
lines: a list of sublists where every sublist
|
||||
represents a line from a Markdown document.
|
||||
id_tag: if true, creates inserts a the <a id> tags (not req. by GitHub)
|
||||
back_links: if true, adds "back to top" links below each headline
|
||||
exclude_h: header levels to exclude. E.g., [2, 3]
|
||||
excludes level 2 and 3 headings.
|
||||
|
||||
Returns a tuple of 2 lists:
|
||||
1st list:
|
||||
A modified version of the input list where
|
||||
<a id="some-header"></a> anchor tags where inserted
|
||||
above the header lines (if github is False).
|
||||
|
||||
2nd list:
|
||||
A list of 3-value sublists, where the first value
|
||||
represents the heading, the second value the string
|
||||
that was inserted assigned to the IDs in the anchor tags,
|
||||
and the third value is an integer that reprents the headline level.
|
||||
E.g.,
|
||||
[['some header lvl3', 'some-header-lvl3', 3], ...]
|
||||
|
||||
"""
|
||||
out_contents = []
|
||||
headlines = []
|
||||
for l in lines:
|
||||
saw_headline = False
|
||||
|
||||
orig_len = len(l)
|
||||
l_stripped = l.lstrip()
|
||||
|
||||
if l_stripped.startswith(('# ', '## ', '### ', '#### ', '##### ', '###### ')):
|
||||
|
||||
# comply with new markdown standards
|
||||
|
||||
# not a headline if '#' not followed by whitespace '##no-header':
|
||||
if not l.lstrip('#').startswith(' '):
|
||||
continue
|
||||
# not a headline if more than 6 '#':
|
||||
if len(l) - len(l.lstrip('#')) > 6:
|
||||
continue
|
||||
# headers can be indented by at most 3 spaces:
|
||||
if orig_len - len(l_stripped) > 3:
|
||||
continue
|
||||
|
||||
# ignore empty headers
|
||||
if not set(l) - {'#', ' '}:
|
||||
continue
|
||||
|
||||
saw_headline = True
|
||||
dashified = dashifyHeadline(l)
|
||||
|
||||
if not exclude_h or not dashified[-1] in exclude_h:
|
||||
if id_tag:
|
||||
id_tag = '<a class="mk-toclify" id="%s"></a>'\
|
||||
% (dashified[1])
|
||||
out_contents.append(id_tag)
|
||||
headlines.append(dashified)
|
||||
|
||||
out_contents.append(l)
|
||||
if back_links and saw_headline:
|
||||
out_contents.append('[[back to top](#table-of-contents)]')
|
||||
return out_contents, headlines
|
||||
|
||||
def positioningHeadlines(headlines):
|
||||
"""
|
||||
Strips unnecessary whitespaces/tabs if first header is not left-aligned
|
||||
"""
|
||||
left_just = False
|
||||
for row in headlines:
|
||||
if row[-1] == 1:
|
||||
left_just = True
|
||||
break
|
||||
if not left_just:
|
||||
for row in headlines:
|
||||
row[-1] -= 1
|
||||
return headlines
|
||||
|
||||
def createToc(headlines, hyperlink=True, top_link=False, no_toc_header=False):
|
||||
"""
|
||||
Creates the table of contents from the headline list
|
||||
that was returned by the tagAndCollect function.
|
||||
|
||||
Keyword Arguments:
|
||||
headlines: list of lists
|
||||
e.g., ['Some header lvl3', 'some-header-lvl3', 3]
|
||||
hyperlink: Creates hyperlinks in Markdown format if True,
|
||||
e.g., '- [Some header lvl1](#some-header-lvl1)'
|
||||
top_link: if True, add a id tag for linking the table
|
||||
of contents itself (for the back-to-top-links)
|
||||
no_toc_header: suppresses TOC header if True.
|
||||
|
||||
Returns a list of headlines for a table of contents
|
||||
in Markdown format,
|
||||
e.g., [' - [Some header lvl3](#some-header-lvl3)', ...]
|
||||
|
||||
"""
|
||||
processed = []
|
||||
if not no_toc_header:
|
||||
if top_link:
|
||||
processed.append('<a class="mk-toclify" id="table-of-contents"></a>\n')
|
||||
processed.append(contentTitle + '<br>')
|
||||
|
||||
for line in headlines:
|
||||
if hyperlink:
|
||||
item = '[%s](#%s)' % (line[0], line[1])
|
||||
else:
|
||||
item = '%s- %s' % ((line[2]-1)*' ', line[0])
|
||||
processed.append(item + '<br>')
|
||||
processed.append('\n')
|
||||
return processed
|
||||
|
||||
def buildMarkdown(toc_headlines, body, spacer=0, placeholder=None):
|
||||
"""
|
||||
Returns a string with the Markdown output contents incl.
|
||||
the table of contents.
|
||||
|
||||
Keyword arguments:
|
||||
toc_headlines: lines for the table of contents
|
||||
as created by the createToc function.
|
||||
body: contents of the Markdown file including
|
||||
ID-anchor tags as returned by the
|
||||
tagAndCollect function.
|
||||
spacer: Adds vertical space after the table
|
||||
of contents. Height in pixels.
|
||||
placeholder: If a placeholder string is provided, the placeholder
|
||||
will be replaced by the TOC instead of inserting the TOC at
|
||||
the top of the document
|
||||
|
||||
"""
|
||||
if spacer:
|
||||
spacer_line = ['\n<div style="height:%spx;"></div>\n' % (spacer)]
|
||||
toc_markdown = "\n".join(toc_headlines + spacer_line)
|
||||
else:
|
||||
toc_markdown = "\n".join(toc_headlines)
|
||||
|
||||
if placeholder:
|
||||
body_markdown = "\n".join(body)
|
||||
markdown = body_markdown.replace(placeholder, toc_markdown)
|
||||
else:
|
||||
body_markdown_p1 = "\n".join(body[:contentLineNdx ]) + '\n'
|
||||
body_markdown_p2 = "\n".join(body[ contentLineNdx:])
|
||||
markdown = body_markdown_p1 + toc_markdown + body_markdown_p2
|
||||
|
||||
return markdown
|
||||
|
||||
def outputMarkdown(markdown_cont, output_file):
|
||||
"""
|
||||
Writes to an output file if `outfile` is a valid path.
|
||||
|
||||
"""
|
||||
if output_file:
|
||||
with open(output_file, 'w') as out:
|
||||
out.write(markdown_cont)
|
||||
|
||||
def markdownToclify(
|
||||
input_file,
|
||||
output_file=None,
|
||||
min_toc_len=2,
|
||||
github=False,
|
||||
back_to_top=False,
|
||||
nolink=False,
|
||||
no_toc_header=False,
|
||||
spacer=0,
|
||||
placeholder=None,
|
||||
exclude_h=None):
|
||||
""" Function to add table of contents to markdown files.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
input_file: str
|
||||
Path to the markdown input file.
|
||||
|
||||
output_file: str (defaul: None)
|
||||
Path to the markdown output file.
|
||||
|
||||
min_toc_len: int (default: 2)
|
||||
Miniumum number of entries to create a table of contents for.
|
||||
|
||||
github: bool (default: False)
|
||||
Uses GitHub TOC syntax if True.
|
||||
|
||||
back_to_top: bool (default: False)
|
||||
Inserts back-to-top links below headings if True.
|
||||
|
||||
nolink: bool (default: False)
|
||||
Creates the table of contents without internal links if True.
|
||||
|
||||
no_toc_header: bool (default: False)
|
||||
Suppresses the Table of Contents header if True
|
||||
|
||||
spacer: int (default: 0)
|
||||
Inserts horizontal space (in pixels) after the table of contents.
|
||||
|
||||
placeholder: str (default: None)
|
||||
Inserts the TOC at the placeholder string instead
|
||||
of inserting the TOC at the top of the document.
|
||||
|
||||
exclude_h: list (default None)
|
||||
Excludes header levels, e.g., if [2, 3], ignores header
|
||||
levels 2 and 3 in the TOC.
|
||||
|
||||
Returns
|
||||
-----------
|
||||
changed: Boolean
|
||||
True if the file has been updated, False otherwise.
|
||||
|
||||
"""
|
||||
cleaned_contents = removeLines(
|
||||
removeToC(readLines(input_file)),
|
||||
remove=('[[back to top]', '<a class="mk-toclify"'))
|
||||
|
||||
processed_contents, raw_headlines = tagAndCollect(
|
||||
cleaned_contents,
|
||||
id_tag=not github,
|
||||
back_links=back_to_top,
|
||||
exclude_h=exclude_h)
|
||||
|
||||
# add table of contents?
|
||||
if len(raw_headlines) < min_toc_len:
|
||||
processed_headlines = []
|
||||
else:
|
||||
leftjustified_headlines = positioningHeadlines(raw_headlines)
|
||||
|
||||
processed_headlines = createToc(
|
||||
leftjustified_headlines,
|
||||
hyperlink=not nolink,
|
||||
top_link=not nolink and not github,
|
||||
no_toc_header=no_toc_header)
|
||||
|
||||
if nolink:
|
||||
processed_contents = cleaned_contents
|
||||
|
||||
cont = buildMarkdown(
|
||||
toc_headlines=processed_headlines,
|
||||
body=processed_contents,
|
||||
spacer=spacer,
|
||||
placeholder=placeholder)
|
||||
|
||||
if output_file:
|
||||
outputMarkdown(cont, output_file)
|
||||
|
||||
def isReleaseNotes(f):
|
||||
return os.path.basename(f) == releaseNotesName
|
||||
|
||||
def excludeHeadingsFor(f):
|
||||
return headingExcludeRelease if isReleaseNotes(f) else headingExcludeDefault
|
||||
|
||||
def updateSingleDocumentToC(input_file, min_toc_len, verbose=False):
|
||||
"""Add or update table of contents in specified file. Return 1 if file changed, 0 otherwise."""
|
||||
if verbose :
|
||||
print( 'file: {}'.format(input_file))
|
||||
|
||||
output_file = input_file + '.tmp'
|
||||
|
||||
markdownToclify(
|
||||
input_file=input_file,
|
||||
output_file=output_file,
|
||||
min_toc_len=min_toc_len,
|
||||
github=True,
|
||||
back_to_top=False,
|
||||
nolink=False,
|
||||
no_toc_header=False,
|
||||
spacer=False,
|
||||
placeholder=False,
|
||||
exclude_h=excludeHeadingsFor(input_file))
|
||||
|
||||
# prevent race-condition (Python 3.3):
|
||||
if sys.version_info >= (3, 3):
|
||||
os.replace(output_file, input_file)
|
||||
else:
|
||||
os.remove(input_file)
|
||||
os.rename(output_file, input_file)
|
||||
|
||||
return 1
|
||||
|
||||
def updateDocumentToC(paths, min_toc_len, verbose):
|
||||
"""Add or update table of contents to specified paths. Return number of changed files"""
|
||||
n = 0
|
||||
for g in paths:
|
||||
for f in glob.glob(g):
|
||||
if os.path.isfile(f):
|
||||
n = n + updateSingleDocumentToC(input_file=f, min_toc_len=min_toc_len, verbose=verbose)
|
||||
return n
|
||||
|
||||
def updateDocumentToCMain():
|
||||
"""Add or update table of contents to specified paths."""
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Add or update table of contents in markdown documents.',
|
||||
epilog="""""",
|
||||
formatter_class=argparse.RawTextHelpFormatter)
|
||||
|
||||
parser.add_argument(
|
||||
'Input',
|
||||
metavar='file',
|
||||
type=str,
|
||||
nargs=argparse.REMAINDER,
|
||||
help='files to process, at default: docs/*.md')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='report the name of the file being processed')
|
||||
|
||||
parser.add_argument(
|
||||
'--min-toc-entries',
|
||||
dest='minTocEntries',
|
||||
default=minTocEntries,
|
||||
type=int,
|
||||
metavar='N',
|
||||
help='the minimum number of entries to create a table of contents for [{deflt}]'.format(deflt=minTocEntries))
|
||||
|
||||
parser.add_argument(
|
||||
'--remove-toc',
|
||||
action='store_const',
|
||||
dest='minTocEntries',
|
||||
const=99,
|
||||
help='remove all tables of contents')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
paths = args.Input if len(args.Input) > 0 else [documentsDefault]
|
||||
|
||||
changedFiles = updateDocumentToC(paths=paths, min_toc_len=args.minTocEntries, verbose=args.verbose)
|
||||
|
||||
if changedFiles > 0:
|
||||
print( "Processed table of contents in " + str(changedFiles) + " file(s)" )
|
||||
else:
|
||||
print( "No table of contents added or updated" )
|
||||
|
||||
if __name__ == '__main__':
|
||||
updateDocumentToCMain()
|
||||
|
||||
# end of file
|
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ class CatchConanTest(ConanFile):
|
||||
settings = "os", "compiler", "arch", "build_type"
|
||||
username = getenv("CONAN_USERNAME", "philsquared")
|
||||
channel = getenv("CONAN_CHANNEL", "testing")
|
||||
requires = "Catch/2.0.0-develop.4@%s/%s" % (username, channel)
|
||||
requires = "Catch/2.0.0-develop.6@%s/%s" % (username, channel)
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
|
515
third_party/clara.hpp
vendored
515
third_party/clara.hpp
vendored
@@ -1,4 +1,4 @@
|
||||
// v1.0
|
||||
// v1.0-develop.2
|
||||
// See https://github.com/philsquared/Clara
|
||||
|
||||
#ifndef CLARA_HPP_INCLUDED
|
||||
@@ -8,6 +8,9 @@
|
||||
#define CLARA_CONFIG_CONSOLE_WIDTH 80
|
||||
#endif
|
||||
|
||||
#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
|
||||
#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
|
||||
#endif
|
||||
|
||||
// ----------- #included from clara_textflow.hpp -----------
|
||||
|
||||
@@ -133,7 +136,7 @@ namespace clara { namespace TextFlow {
|
||||
|
||||
auto operator *() const -> std::string {
|
||||
assert( m_stringIndex < m_column.m_strings.size() );
|
||||
assert( m_pos < m_end );
|
||||
assert( m_pos <= m_end );
|
||||
if( m_pos + m_column.m_width < m_end )
|
||||
return addIndentAndSuffix(line().substr(m_pos, m_len));
|
||||
else
|
||||
@@ -357,15 +360,15 @@ namespace detail {
|
||||
|
||||
// Traits for extracting arg and return type of lambdas (for single argument lambdas)
|
||||
template<typename L>
|
||||
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype(&L::operator())> {};
|
||||
struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};
|
||||
|
||||
template<typename ClassT, typename ReturnT, typename... Args>
|
||||
struct UnaryLambdaTraits<ReturnT(ClassT::*)(Args...) const> {
|
||||
struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {
|
||||
static const bool isValid = false;
|
||||
};
|
||||
|
||||
template<typename ClassT, typename ReturnT, typename ArgT>
|
||||
struct UnaryLambdaTraits<ReturnT(ClassT::*)(ArgT) const> {
|
||||
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;
|
||||
@@ -380,13 +383,13 @@ namespace detail {
|
||||
std::vector<std::string> m_args;
|
||||
|
||||
public:
|
||||
Args(int argc, char *argv[]) {
|
||||
Args( int argc, char *argv[] ) {
|
||||
m_exeName = argv[0];
|
||||
for (int i = 1; i < argc; ++i)
|
||||
m_args.push_back(argv[i]);
|
||||
for( int i = 1; i < argc; ++i )
|
||||
m_args.push_back( argv[i] );
|
||||
}
|
||||
|
||||
Args(std::initializer_list<std::string> args)
|
||||
Args( std::initializer_list<std::string> args )
|
||||
: m_exeName( *args.begin() ),
|
||||
m_args( args.begin()+1, args.end() )
|
||||
{}
|
||||
@@ -406,6 +409,14 @@ namespace detail {
|
||||
std::string token;
|
||||
};
|
||||
|
||||
inline auto isOptPrefix( char c ) -> bool {
|
||||
return c == '-'
|
||||
#ifdef CLARA_PLATFORM_WINDOWS
|
||||
|| c == '/'
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
// Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
|
||||
class TokenStream {
|
||||
using Iterator = std::vector<std::string>::const_iterator;
|
||||
@@ -414,40 +425,40 @@ namespace detail {
|
||||
std::vector<Token> m_tokenBuffer;
|
||||
|
||||
void loadBuffer() {
|
||||
m_tokenBuffer.resize(0);
|
||||
m_tokenBuffer.resize( 0 );
|
||||
|
||||
// Skip any empty strings
|
||||
while (it != itEnd && it->empty())
|
||||
while( it != itEnd && it->empty() )
|
||||
++it;
|
||||
|
||||
if (it != itEnd) {
|
||||
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)});
|
||||
if( isOptPrefix( 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) {
|
||||
if( next[1] != '-' && next.size() > 2 ) {
|
||||
std::string opt = "- ";
|
||||
for (size_t i = 1; i < next.size(); ++i) {
|
||||
for( size_t i = 1; i < next.size(); ++i ) {
|
||||
opt[1] = next[i];
|
||||
m_tokenBuffer.push_back({TokenType::Option, opt});
|
||||
m_tokenBuffer.push_back( { TokenType::Option, opt } );
|
||||
}
|
||||
} else {
|
||||
m_tokenBuffer.push_back({TokenType::Option, next});
|
||||
m_tokenBuffer.push_back( { TokenType::Option, next } );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_tokenBuffer.push_back({TokenType::Argument, next});
|
||||
m_tokenBuffer.push_back( { TokenType::Argument, next } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {}
|
||||
explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}
|
||||
|
||||
TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) {
|
||||
TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {
|
||||
loadBuffer();
|
||||
}
|
||||
|
||||
@@ -458,20 +469,20 @@ namespace detail {
|
||||
auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }
|
||||
|
||||
auto operator*() const -> Token {
|
||||
assert(!m_tokenBuffer.empty());
|
||||
assert( !m_tokenBuffer.empty() );
|
||||
return m_tokenBuffer.front();
|
||||
}
|
||||
|
||||
auto operator->() const -> Token const * {
|
||||
assert(!m_tokenBuffer.empty());
|
||||
assert( !m_tokenBuffer.empty() );
|
||||
return &m_tokenBuffer.front();
|
||||
}
|
||||
|
||||
auto operator++() -> TokenStream & {
|
||||
if (m_tokenBuffer.size() >= 2) {
|
||||
m_tokenBuffer.erase(m_tokenBuffer.begin());
|
||||
if( m_tokenBuffer.size() >= 2 ) {
|
||||
m_tokenBuffer.erase( m_tokenBuffer.begin() );
|
||||
} else {
|
||||
if (it != itEnd)
|
||||
if( it != itEnd )
|
||||
++it;
|
||||
loadBuffer();
|
||||
}
|
||||
@@ -487,7 +498,7 @@ namespace detail {
|
||||
};
|
||||
|
||||
protected:
|
||||
ResultBase(Type type) : m_type(type) {}
|
||||
ResultBase( Type type ) : m_type( type ) {}
|
||||
virtual ~ResultBase() = default;
|
||||
|
||||
virtual void enforceOk() const = 0;
|
||||
@@ -504,28 +515,28 @@ namespace detail {
|
||||
}
|
||||
|
||||
protected:
|
||||
ResultValueBase(Type type) : ResultBase(type) {}
|
||||
ResultValueBase( Type type ) : ResultBase( type ) {}
|
||||
|
||||
ResultValueBase(ResultValueBase const &other) : ResultBase(other) {
|
||||
if (m_type == ResultBase::Ok)
|
||||
new(&m_value) T(other.m_value);
|
||||
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);
|
||||
ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {
|
||||
new( &m_value ) T( value );
|
||||
}
|
||||
|
||||
auto operator=(ResultValueBase const &other) -> ResultValueBase & {
|
||||
if (m_type == ResultBase::Ok)
|
||||
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);
|
||||
if( m_type == ResultBase::Ok )
|
||||
new( &m_value ) T( other.m_value );
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ResultValueBase() {
|
||||
if (m_type == Ok)
|
||||
if( m_type == Ok )
|
||||
m_value.~T();
|
||||
}
|
||||
|
||||
@@ -544,37 +555,31 @@ namespace detail {
|
||||
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);
|
||||
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};
|
||||
}
|
||||
static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }
|
||||
static auto ok() -> BasicResult { return { ResultBase::Ok }; }
|
||||
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) {
|
||||
switch( m_type ) {
|
||||
case ResultBase::LogicError:
|
||||
throw std::logic_error(m_errorMessage);
|
||||
throw std::logic_error( m_errorMessage );
|
||||
case ResultBase::RuntimeError:
|
||||
throw std::runtime_error(m_errorMessage);
|
||||
throw std::runtime_error( m_errorMessage );
|
||||
case ResultBase::Ok:
|
||||
break;
|
||||
}
|
||||
@@ -582,10 +587,11 @@ namespace detail {
|
||||
|
||||
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);
|
||||
BasicResult( ResultBase::Type type, std::string const &message )
|
||||
: ResultValueBase<T>(type),
|
||||
m_errorMessage(message)
|
||||
{
|
||||
assert( m_type != ResultBase::Ok );
|
||||
}
|
||||
|
||||
using ResultValueBase<T>::ResultValueBase;
|
||||
@@ -599,12 +605,12 @@ namespace detail {
|
||||
class ParseState {
|
||||
public:
|
||||
|
||||
ParseState(ParseResultType type, TokenStream const &remainingTokens)
|
||||
: m_type(type),
|
||||
m_remainingTokens(remainingTokens) {}
|
||||
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:
|
||||
@@ -622,69 +628,62 @@ namespace detail {
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline auto convertInto(std::string const &source, T& target) -> ParserResult {
|
||||
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");
|
||||
if( ss.fail() )
|
||||
return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" );
|
||||
else
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
inline auto convertInto(std::string const &source, std::string& target) -> ParserResult {
|
||||
inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {
|
||||
target = source;
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
return ParserResult::ok( ParseResultType::Matched );
|
||||
}
|
||||
inline auto convertInto(std::string const &source, bool &target) -> ParserResult {
|
||||
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) ); } );
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
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");
|
||||
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 {
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
bool flag;
|
||||
auto result = convertInto(arg, flag);
|
||||
if (result)
|
||||
setFlag(flag);
|
||||
auto result = convertInto( arg, flag );
|
||||
if( result )
|
||||
setFlag( flag );
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -693,10 +692,10 @@ namespace detail {
|
||||
struct BoundRef : BoundValueRefBase {
|
||||
T &m_ref;
|
||||
|
||||
explicit BoundRef(T &ref) : m_ref(ref) {}
|
||||
explicit BoundRef( T &ref ) : m_ref( ref ) {}
|
||||
|
||||
auto setValue(std::string const &arg) -> ParserResult override {
|
||||
return convertInto(arg, m_ref);
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
return convertInto( arg, m_ref );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -704,15 +703,15 @@ namespace detail {
|
||||
struct BoundRef<std::vector<T>> : BoundValueRefBase {
|
||||
std::vector<T> &m_ref;
|
||||
|
||||
explicit BoundRef(std::vector<T> &ref) : m_ref(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 {
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
T temp;
|
||||
auto result = convertInto(arg, temp);
|
||||
if (result)
|
||||
m_ref.push_back(temp);
|
||||
auto result = convertInto( arg, temp );
|
||||
if( result )
|
||||
m_ref.push_back( temp );
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -720,40 +719,40 @@ namespace detail {
|
||||
struct BoundFlagRef : BoundFlagRefBase {
|
||||
bool &m_ref;
|
||||
|
||||
explicit BoundFlagRef(bool &ref) : m_ref(ref) {}
|
||||
explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}
|
||||
|
||||
auto setFlag(bool flag) -> ParserResult override {
|
||||
auto setFlag( bool flag ) -> ParserResult override {
|
||||
m_ref = flag;
|
||||
return ParserResult::ok(ParseResultType::Matched);
|
||||
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");
|
||||
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);
|
||||
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);
|
||||
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 {
|
||||
inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
|
||||
ArgType temp;
|
||||
auto result = convertInto(arg, temp);
|
||||
auto result = convertInto( arg, temp );
|
||||
return !result
|
||||
? result
|
||||
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(lambda, temp);
|
||||
? result
|
||||
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );
|
||||
};
|
||||
|
||||
|
||||
@@ -762,10 +761,10 @@ namespace detail {
|
||||
L m_lambda;
|
||||
|
||||
static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
|
||||
explicit BoundLambda(L const &lambda) : m_lambda(lambda) {}
|
||||
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);
|
||||
auto setValue( std::string const &arg ) -> ParserResult override {
|
||||
return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -776,16 +775,14 @@ namespace detail {
|
||||
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) {}
|
||||
explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
|
||||
|
||||
auto setFlag(bool flag) -> ParserResult override {
|
||||
return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke(m_lambda, flag);
|
||||
auto setFlag( bool flag ) -> ParserResult override {
|
||||
return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );
|
||||
}
|
||||
};
|
||||
|
||||
enum class Optionality {
|
||||
Optional, Required
|
||||
};
|
||||
enum class Optionality { Optional, Required };
|
||||
|
||||
struct Parser;
|
||||
|
||||
@@ -796,8 +793,8 @@ namespace detail {
|
||||
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));
|
||||
auto parse( Args const &args ) const -> InternalParseResult {
|
||||
return parse( args.exeName(), TokenStream( args ) );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -805,7 +802,7 @@ namespace detail {
|
||||
class ComposableParserImpl : public ParserBase {
|
||||
public:
|
||||
template<typename T>
|
||||
auto operator+(T const &other) const -> Parser;
|
||||
auto operator|( T const &other ) const -> Parser;
|
||||
};
|
||||
|
||||
// Common code and state for Args and Opts
|
||||
@@ -817,17 +814,22 @@ namespace detail {
|
||||
std::string m_hint;
|
||||
std::string m_description;
|
||||
|
||||
explicit ParserRefImpl(std::shared_ptr<BoundRefBase> const &ref) : m_ref(ref) {}
|
||||
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) {}
|
||||
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) {}
|
||||
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 & {
|
||||
auto operator()( std::string const &description ) -> DerivedT & {
|
||||
m_description = description;
|
||||
return static_cast<DerivedT &>( *this );
|
||||
}
|
||||
@@ -847,7 +849,7 @@ namespace detail {
|
||||
}
|
||||
|
||||
auto cardinality() const -> size_t override {
|
||||
if (m_ref->isContainer())
|
||||
if( m_ref->isContainer() )
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
@@ -862,13 +864,13 @@ namespace detail {
|
||||
|
||||
template<typename LambdaT>
|
||||
static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundRefBase> {
|
||||
return std::make_shared<BoundLambda<LambdaT>>(lambda);
|
||||
return std::make_shared<BoundLambda<LambdaT>>( lambda) ;
|
||||
}
|
||||
|
||||
public:
|
||||
ExeName() : m_name(std::make_shared<std::string>("<executable>")) {}
|
||||
ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {}
|
||||
|
||||
explicit ExeName(std::string &ref) : ExeName() {
|
||||
explicit ExeName( std::string &ref ) : ExeName() {
|
||||
m_ref = std::make_shared<BoundRef<std::string>>( ref );
|
||||
}
|
||||
|
||||
@@ -879,14 +881,14 @@ namespace detail {
|
||||
|
||||
// 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));
|
||||
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)
|
||||
auto filename = ( lastSlash == std::string::npos )
|
||||
? newName
|
||||
: newName.substr( lastSlash+1 );
|
||||
|
||||
@@ -902,28 +904,30 @@ 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);
|
||||
if( !validationResult )
|
||||
return InternalParseResult( validationResult );
|
||||
|
||||
auto remainingTokens = tokens;
|
||||
auto const &token = *remainingTokens;
|
||||
if (token.type != TokenType::Argument)
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, 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);
|
||||
auto result = m_ref->setValue( remainingTokens->token );
|
||||
if( !result )
|
||||
return InternalParseResult( result );
|
||||
else
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::Matched, ++remainingTokens));
|
||||
return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
|
||||
}
|
||||
};
|
||||
|
||||
inline auto normaliseOpt(std::string const &optName) -> std::string {
|
||||
if (optName[0] == '/')
|
||||
return "-" + optName.substr(1);
|
||||
inline auto normaliseOpt( std::string const &optName ) -> std::string {
|
||||
#ifdef CLARA_PLATFORM_WINDOWS
|
||||
if( optName[0] == '/' )
|
||||
return "-" + optName.substr( 1 );
|
||||
else
|
||||
#endif
|
||||
return optName;
|
||||
}
|
||||
|
||||
@@ -933,9 +937,9 @@ namespace detail {
|
||||
|
||||
public:
|
||||
template<typename LambdaT>
|
||||
explicit Opt( LambdaT const &ref ) : ParserRefImpl(std::make_shared<BoundFlagLambda<LambdaT>>(ref)) {}
|
||||
explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}
|
||||
|
||||
explicit Opt( bool &ref ) : ParserRefImpl(std::make_shared<BoundFlagRef>(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 ) {}
|
||||
@@ -943,34 +947,30 @@ namespace detail {
|
||||
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);
|
||||
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) {
|
||||
for( auto const &opt : m_optNames ) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
oss << ", ";
|
||||
oss << opt;
|
||||
}
|
||||
if (!m_hint.empty())
|
||||
if( !m_hint.empty() )
|
||||
oss << " <" << m_hint << ">";
|
||||
return {{oss.str(), m_description}};
|
||||
return { { oss.str(), m_description } };
|
||||
}
|
||||
|
||||
auto isMatch(std::string const &optToken) const -> bool {
|
||||
#ifdef CLARA_PLATFORM_WINDOWS
|
||||
auto isMatch( std::string const &optToken ) const -> bool {
|
||||
auto normalisedToken = normaliseOpt( optToken );
|
||||
#else
|
||||
auto const &normalisedToken = optToken;
|
||||
#endif
|
||||
for (auto const &name : m_optNames) {
|
||||
if (normaliseOpt(name) == normalisedToken)
|
||||
for( auto const &name : m_optNames ) {
|
||||
if( normaliseOpt( name ) == normalisedToken )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -980,46 +980,51 @@ namespace detail {
|
||||
|
||||
auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
|
||||
auto validationResult = validate();
|
||||
if (!validationResult)
|
||||
return InternalParseResult(validationResult);
|
||||
if( !validationResult )
|
||||
return InternalParseResult( validationResult );
|
||||
|
||||
auto remainingTokens = tokens;
|
||||
if (remainingTokens && remainingTokens->type == TokenType::Option) {
|
||||
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));
|
||||
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);
|
||||
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));
|
||||
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::Matched, ++remainingTokens ) );
|
||||
}
|
||||
}
|
||||
return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, 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 '/'");
|
||||
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" );
|
||||
#ifdef CLARA_PLATFORM_WINDOWS
|
||||
if( name[0] != '-' && name[0] != '/' )
|
||||
return Result::logicError( "Option name must begin with '-' or '/'" );
|
||||
#else
|
||||
if( name[0] != '-' )
|
||||
return Result::logicError( "Option name must begin with '-'" );
|
||||
#endif
|
||||
}
|
||||
return ParserRefImpl::validate();
|
||||
}
|
||||
@@ -1028,11 +1033,11 @@ namespace detail {
|
||||
struct Help : Opt {
|
||||
Help( bool &showHelpFlag )
|
||||
: Opt([&]( bool flag ) {
|
||||
showHelpFlag = flag;
|
||||
return ParserResult::ok(ParseResultType::ShortCircuitAll);
|
||||
showHelpFlag = flag;
|
||||
return ParserResult::ok( ParseResultType::ShortCircuitAll );
|
||||
})
|
||||
{
|
||||
static_cast<Opt &>(*this)
|
||||
static_cast<Opt &>( *this )
|
||||
("display usage information")
|
||||
["-?"]["-h"]["--help"]
|
||||
.optional();
|
||||
@@ -1046,61 +1051,61 @@ namespace detail {
|
||||
std::vector<Opt> m_options;
|
||||
std::vector<Arg> m_args;
|
||||
|
||||
auto operator+=(ExeName const &exeName) -> Parser & {
|
||||
auto operator|=( ExeName const &exeName ) -> Parser & {
|
||||
m_exeName = exeName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Arg const &arg) -> Parser & {
|
||||
auto operator|=( Arg const &arg ) -> Parser & {
|
||||
m_args.push_back(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Opt const &opt) -> Parser & {
|
||||
auto operator|=( Opt const &opt ) -> Parser & {
|
||||
m_options.push_back(opt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator+=(Parser const &other) -> Parser & {
|
||||
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 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());
|
||||
cols.insert( cols.end(), childCols.begin(), childCols.end() );
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
void writeToStream(std::ostream &os) const {
|
||||
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) {
|
||||
for( auto const &arg : m_args ) {
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
os << " ";
|
||||
if (arg.isOptional() && required) {
|
||||
if( arg.isOptional() && required ) {
|
||||
os << "[";
|
||||
required = false;
|
||||
}
|
||||
os << "<" << arg.hint() << ">";
|
||||
if (arg.cardinality() == 0)
|
||||
if( arg.cardinality() == 0 )
|
||||
os << " ... ";
|
||||
}
|
||||
if (!required)
|
||||
if( !required )
|
||||
os << "]";
|
||||
if (!m_options.empty())
|
||||
if( !m_options.empty() )
|
||||
os << " options";
|
||||
os << "\n\nwhere options are:" << std::endl;
|
||||
}
|
||||
@@ -1108,32 +1113,32 @@ namespace detail {
|
||||
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 )
|
||||
optWidth = (std::max)(optWidth, cols.left.size() + 2);
|
||||
|
||||
for (auto const &cols : rows) {
|
||||
for( auto const &cols : rows ) {
|
||||
auto row =
|
||||
TextFlow::Column(cols.left).width(optWidth).indent(2) +
|
||||
TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
|
||||
TextFlow::Spacer(4) +
|
||||
TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth);
|
||||
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);
|
||||
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) {
|
||||
for( auto const &opt : m_options ) {
|
||||
auto result = opt.validate();
|
||||
if (!result)
|
||||
if( !result )
|
||||
return result;
|
||||
}
|
||||
for (auto const &arg : m_args) {
|
||||
for( auto const &arg : m_args ) {
|
||||
auto result = arg.validate();
|
||||
if (!result)
|
||||
if( !result )
|
||||
return result;
|
||||
}
|
||||
return Result::ok();
|
||||
@@ -1141,47 +1146,47 @@ namespace detail {
|
||||
|
||||
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;
|
||||
auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
|
||||
|
||||
for (auto const &opt : m_options) {
|
||||
allParsers.push_back(&opt);
|
||||
if (!opt.isOptional())
|
||||
requiredParsers.insert(&opt);
|
||||
}
|
||||
struct ParserInfo {
|
||||
ParserBase const* parser = nullptr;
|
||||
size_t count = 0;
|
||||
};
|
||||
const size_t totalParsers = m_options.size() + m_args.size();
|
||||
assert( totalParsers < 512 );
|
||||
// ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
|
||||
ParserInfo parseInfos[512];
|
||||
|
||||
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);
|
||||
}
|
||||
{
|
||||
size_t i = 0;
|
||||
for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
|
||||
for (auto const &arg : m_args) parseInfos[i++].parser = &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;
|
||||
auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
|
||||
while( result.value().remainingTokens() ) {
|
||||
bool tokenParsed = false;
|
||||
|
||||
for( size_t i = 0; i < totalParsers; ++i ) {
|
||||
auto& parseInfo = parseInfos[i];
|
||||
if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
|
||||
result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
|
||||
if (!result)
|
||||
return result;
|
||||
if (result.value().type() != ParseResultType::NoMatch) {
|
||||
tokenParsed = true;
|
||||
++parseInfo.count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result || remainingTokenCount == result.value().remainingTokens().count())
|
||||
|
||||
if( result.value().type() == ParseResultType::ShortCircuitAll )
|
||||
return result;
|
||||
if( !tokenParsed )
|
||||
return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
|
||||
}
|
||||
// !TBD Check missing required options
|
||||
return result;
|
||||
@@ -1190,8 +1195,8 @@ namespace detail {
|
||||
|
||||
template<typename DerivedT>
|
||||
template<typename T>
|
||||
auto ComposableParserImpl<DerivedT>::operator+(T const &other) const -> Parser {
|
||||
return Parser() + static_cast<DerivedT const &>( *this ) + other;
|
||||
auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {
|
||||
return Parser() | static_cast<DerivedT const &>( *this ) | other;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
Reference in New Issue
Block a user