Skip to content

Commit b00c5f3

Browse files
committed
feat: full support for cucumber expressions
Fixes #178
1 parent 1c7fcde commit b00c5f3

31 files changed

+2408
-146
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
FROM ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86
1+
FROM ghcr.io/philips-software/amp-devcontainer-cpp:6.0.1@sha256:0b238dbdd0e39a9704fc5317269f654b8bfd23868bad9491e806ae417d645352
22

33
HEALTHCHECK NONE

cucumber_cpp/acceptance_test/steps/Steps.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "cucumber_cpp/library/Steps.hpp"
22
#include "gmock/gmock.h"
33
#include "gtest/gtest.h"
4+
#include <cstdint>
45
#include <iostream>
56
#include <string>
67

@@ -45,7 +46,7 @@ THEN("two expectations are raised")
4546
EXPECT_THAT(1, testing::Eq(0));
4647
}
4748

48-
WHEN("I print {string} with value {int}", (const std::string& str, std::int32_t value))
49+
WHEN("I print {string} with value {int}", (const std::string& str, std::int64_t value))
4950
{
5051
std::cout << "print: " << str << " with value " << value;
5152
}

cucumber_cpp/library/Body.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#ifndef CUCUMBER_CPP_BODY_HPP
22
#define CUCUMBER_CPP_BODY_HPP
33

4+
#include <any>
45
#include <string>
6+
#include <variant>
57
#include <vector>
68

79
namespace cucumber_cpp::library
@@ -10,7 +12,7 @@ namespace cucumber_cpp::library
1012
{
1113
virtual ~Body() = default;
1214

13-
virtual void Execute(const std::vector<std::string>& args = {}) = 0;
15+
virtual void Execute(const std::variant<std::vector<std::string>, std::vector<std::any>>& args = {}) = 0;
1416
};
1517

1618
template<class T>

cucumber_cpp/library/BodyMacro.hpp

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,51 @@
2121

2222
#define BODY_STRUCT CONCAT(BodyImpl, __LINE__)
2323

24-
#define BODY(matcher, type, targs, registration, base) \
25-
namespace \
26-
{ \
27-
struct BODY_STRUCT : cucumber_cpp::library::Body \
28-
, base \
29-
{ \
30-
/* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \
31-
/* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \
32-
using myBase = base; \
33-
using myBase::myBase; \
34-
\
35-
void Execute(const std::vector<std::string>& args) override \
36-
{ \
37-
cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \
38-
EXPECT_NO_THROW(ExecuteWithArgs(args, std::function<void targs>{})); \
39-
} \
40-
\
41-
template<class... TArgs> \
42-
void ExecuteWithArgs(const std::vector<std::string>& args, std::function<void(TArgs...)> /*unused*/) \
43-
{ \
44-
ExecuteWithArgs<TArgs...>(args, std::make_index_sequence<sizeof...(TArgs)>{}); \
45-
} \
46-
\
47-
template<class... TArgs, std::size_t... I> \
48-
void ExecuteWithArgs(const std::vector<std::string>& args, std::index_sequence<I...> /*unused*/) \
49-
{ \
50-
ExecuteWithArgs(cucumber_cpp::library::engine::StringTo<std::remove_cvref_t<TArgs>>(args[I])...); \
51-
} \
52-
\
53-
private: \
54-
void ExecuteWithArgs targs; \
55-
static const std::size_t ID; \
56-
}; \
57-
} \
58-
const std::size_t BODY_STRUCT::ID = registration<BODY_STRUCT>(matcher, type); \
24+
#define BODY(matcher, type, targs, registration, base) \
25+
namespace \
26+
{ \
27+
struct BODY_STRUCT : cucumber_cpp::library::Body \
28+
, base \
29+
{ \
30+
/* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \
31+
/* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \
32+
using myBase = base; \
33+
using myBase::myBase; \
34+
\
35+
void Execute(const std::variant<std::vector<std::string>, std::vector<std::any>>& args) override \
36+
{ \
37+
cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \
38+
EXPECT_NO_THROW(ExecuteWithArgs(args, std::function<void targs>{})); \
39+
} \
40+
\
41+
template<class... TArgs> \
42+
void ExecuteWithArgs(const std::variant<std::vector<std::string>, std::vector<std::any>>& args, std::function<void(TArgs...)> /*unused*/) \
43+
{ \
44+
\
45+
if (std::holds_alternative<std::vector<std::string>>(args)) \
46+
ExecuteWithArgs<TArgs...>(std::get<std::vector<std::string>>(args), std::make_index_sequence<sizeof...(TArgs)>{}); \
47+
else \
48+
ExecuteWithArgs<TArgs...>(std::get<std::vector<std::any>>(args), std::make_index_sequence<sizeof...(TArgs)>{}); \
49+
} \
50+
\
51+
template<class... TArgs, std::size_t... I> \
52+
void ExecuteWithArgs(const std::vector<std::string>& args, std::index_sequence<I...> /*unused*/) \
53+
{ \
54+
ExecuteWithArgs(cucumber_cpp::library::engine::StringTo<std::remove_cvref_t<TArgs>>(args[I])...); \
55+
} \
56+
\
57+
template<class... TArgs, std::size_t... I> \
58+
void ExecuteWithArgs(const std::vector<std::any>& args, std::index_sequence<I...> /*unused*/) \
59+
{ \
60+
ExecuteWithArgs(std::any_cast<std::remove_cvref_t<TArgs>>(args[I])...); \
61+
} \
62+
\
63+
private: \
64+
void ExecuteWithArgs targs; \
65+
static const std::size_t ID; \
66+
}; \
67+
} \
68+
const std::size_t BODY_STRUCT::ID = registration<BODY_STRUCT>(matcher, type); \
5969
void BODY_STRUCT::ExecuteWithArgs targs
6070

6171
#endif

cucumber_cpp/library/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ target_compile_options(cucumber_cpp.library
4646
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
4747
)
4848

49+
add_subdirectory(cucumber_expression)
4950
add_subdirectory(engine)
5051
add_subdirectory(report)
5152
add_subdirectory(util)

cucumber_cpp/library/StepRegistry.cpp

Lines changed: 68 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,56 @@
11
#include "cucumber_cpp/library/StepRegistry.hpp"
2-
#include "cucumber_cpp/library/Context.hpp"
2+
#include "cucumber_cpp/library/cucumber_expression/Matcher.hpp"
3+
#include "cucumber_cpp/library/engine/StepType.hpp"
34
#include <algorithm>
45
#include <cstddef>
5-
#include <memory>
66
#include <ranges>
77
#include <regex>
8-
#include <source_location>
98
#include <string>
109
#include <utility>
10+
#include <variant>
1111
#include <vector>
1212

1313
namespace cucumber_cpp::library
1414
{
1515
namespace
1616
{
17-
std::string ToString(std::string toString)
18-
{
19-
return toString;
20-
}
17+
// std::string ToString(std::string toString)
18+
// {
19+
// return toString;
20+
// }
2121

22-
std::vector<std::string> ToVector(const std::smatch& matchResults)
23-
{
24-
auto range = matchResults | std::views::drop(1) | std::views::transform(ToString);
25-
return { range.begin(), range.end() };
26-
}
22+
// std::vector<std::string> ToVector(const std::smatch& matchResults)
23+
// {
24+
// auto range = matchResults | std::views::drop(1) | std::views::transform(ToString);
25+
// return { range.begin(), range.end() };
26+
// }
2727

28-
std::string ParseCucumberExpression(std::string string)
29-
{
30-
if (!(string.starts_with('^') && string.ends_with('$')))
31-
{
32-
string = std::regex_replace(string, std::regex(R"(\(.*\))"), R"((?:$&)?)");
28+
// std::string ParseCucumberExpression(std::string string)
29+
// {
30+
// if (!(string.starts_with('^') && string.ends_with('$')))
31+
// {
32+
// string = std::regex_replace(string, std::regex(R"(\(.*\))"), R"((?:$&)?)");
3333

34-
string = std::regex_replace(string, std::regex(R"(\{int\})"), R"((-?\d+))");
35-
string = std::regex_replace(string, std::regex(R"(\{biginteger\})"), R"((-?\d+))");
36-
string = std::regex_replace(string, std::regex(R"(\{byte\})"), R"((-?\d+))");
37-
string = std::regex_replace(string, std::regex(R"(\{short\})"), R"((-?\d+))");
38-
string = std::regex_replace(string, std::regex(R"(\{long\})"), R"((-?\d+))");
34+
// string = std::regex_replace(string, std::regex(R"(\{int\})"), R"((-?\d+))");
35+
// string = std::regex_replace(string, std::regex(R"(\{biginteger\})"), R"((-?\d+))");
36+
// string = std::regex_replace(string, std::regex(R"(\{byte\})"), R"((-?\d+))");
37+
// string = std::regex_replace(string, std::regex(R"(\{short\})"), R"((-?\d+))");
38+
// string = std::regex_replace(string, std::regex(R"(\{long\})"), R"((-?\d+))");
3939

40-
string = std::regex_replace(string, std::regex(R"(\{float\})"), R"((-?\d+\.\d+))");
41-
string = std::regex_replace(string, std::regex(R"(\{bigdecimal\})"), R"((-?\d+\.\d+))");
42-
string = std::regex_replace(string, std::regex(R"(\{double\})"), R"((-?\d+\.\d+))");
40+
// string = std::regex_replace(string, std::regex(R"(\{float\})"), R"((-?\d+\.\d+))");
41+
// string = std::regex_replace(string, std::regex(R"(\{bigdecimal\})"), R"((-?\d+\.\d+))");
42+
// string = std::regex_replace(string, std::regex(R"(\{double\})"), R"((-?\d+\.\d+))");
4343

44-
string = std::regex_replace(string, std::regex(R"(\{word\})"), R"(([^\s]+))");
45-
string = std::regex_replace(string, std::regex(R"(\{string\})"), R"-("+([^"]+)"+)-");
44+
// string = std::regex_replace(string, std::regex(R"(\{word\})"), R"(([^\s]+))");
45+
// string = std::regex_replace(string, std::regex(R"(\{string\})"), R"-("+([^"]+)"+)-");
4646

47-
string = std::regex_replace(string, std::regex(R"(\{\})"), R"((.*))");
47+
// string = std::regex_replace(string, std::regex(R"(\{\})"), R"((.*))");
4848

49-
string = "^" + string + "$";
50-
}
49+
// string = "^" + string + "$";
50+
// }
5151

52-
return string;
53-
}
52+
// return string;
53+
// }
5454

5555
auto TypeFilter(engine::StepType stepType)
5656
{
@@ -61,37 +61,37 @@ namespace cucumber_cpp::library
6161
};
6262
}
6363

64-
RegexMatch::RegexMatch(const std::regex& regex, const std::string& expression)
65-
{
66-
std::smatch smatch;
67-
matched = std::regex_search(expression, smatch, regex);
68-
matches = ToVector(smatch);
69-
}
70-
71-
bool RegexMatch::Matched() const
72-
{
73-
return matched;
74-
}
75-
76-
std::vector<std::string> RegexMatch::Matches() const
77-
{
78-
return matches;
79-
}
80-
81-
StepRegex::StepRegex(const std::string& string)
82-
: string{ string }
83-
, regex{ ParseCucumberExpression(string) }
84-
{}
85-
86-
std::unique_ptr<RegexMatch> StepRegex::Match(const std::string& expression) const
87-
{
88-
return std::make_unique<RegexMatch>(regex, expression);
89-
}
90-
91-
std::string StepRegex::String() const
92-
{
93-
return string;
94-
}
64+
// RegexMatch::RegexMatch(const std::regex& regex, const std::string& expression)
65+
// {
66+
// std::smatch smatch;
67+
// matched = std::regex_search(expression, smatch, regex);
68+
// matches = ToVector(smatch);
69+
// }
70+
71+
// bool RegexMatch::Matched() const
72+
// {
73+
// return matched;
74+
// }
75+
76+
// std::vector<std::string> RegexMatch::Matches() const
77+
// {
78+
// return matches;
79+
// }
80+
81+
// StepRegex::StepRegex(const std::string& string)
82+
// : string{ string }
83+
// , regex{ ParseCucumberExpression(string) }
84+
// {}
85+
86+
// std::unique_ptr<RegexMatch> StepRegex::Match(const std::string& expression) const
87+
// {
88+
// return std::make_unique<RegexMatch>(regex, expression);
89+
// }
90+
91+
// std::string StepRegex::String() const
92+
// {
93+
// return string;
94+
// }
9595

9696
StepRegistry& StepRegistry::Instance()
9797
{
@@ -104,11 +104,14 @@ namespace cucumber_cpp::library
104104
std::vector<StepMatch> matches;
105105

106106
for (Entry& entry : registry | std::views::filter(TypeFilter(stepType)))
107-
if (auto match = entry.regex.Match(expression); match->Matched())
107+
{
108+
auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, entry.regex);
109+
if (match)
108110
{
109-
matches.emplace_back(entry.factory, match->Matches(), entry.regex.String());
111+
matches.emplace_back(entry.factory, *match, std::visit(cucumber_expression::patternVisitor, entry.regex));
110112
++entry.used;
111113
}
114+
}
112115

113116
if (matches.empty())
114117
throw StepNotFoundError{};

0 commit comments

Comments
 (0)